/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Created by IntelliJ IDEA. * User: max * Date: Oct 30, 2006 * Time: 8:41:56 PM */ package com.intellij.ide; import com.intellij.openapi.diagnostic.Logger; import com.intellij.reference.SoftReference; import com.intellij.util.ReflectionUtil; import javax.swing.*; import java.awt.*; import java.awt.image.VolatileImage; import java.lang.ref.WeakReference; import java.util.Map; /** * see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6209673 */ public class IdeRepaintManager extends RepaintManager { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.HackyRepaintManager"); private Map myImagesMap; WeakReference myLastComponent; public Image getVolatileOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) { final Image buffer = super.getVolatileOffscreenBuffer(c, proposedWidth, proposedHeight); clearLeakyImages(false); // DisplayChangedListener might be unavailable return buffer; } // sync here is to avoid data race when two(!) AWT threads on startup try to compete for the single myImagesMap private synchronized void clearLeakyImages(boolean force) { if (myImagesMap == null) { myImagesMap = ReflectionUtil.getField(RepaintManager.class, this, Map.class, "volatileMap"); } if (force || myImagesMap.size() > 3 /*leave no more than 3 images (usually one per screen) if DisplayChangedListener is not available */ ) { //Force the RepaintManager to clear out all of the VolatileImage back-buffers that it has cached. // See Sun bug 6209673. Dimension size = getDoubleBufferMaximumSize(); setDoubleBufferMaximumSize(new Dimension(0, 0)); setDoubleBufferMaximumSize(size); } } private class DisplayChangeHandler implements sun.awt.DisplayChangedListener, Runnable { public void displayChanged() { EventQueue.invokeLater( this ); } public void paletteChanged() { EventQueue.invokeLater( this ); } public void run() { clearLeakyImages(true); } } // We must keep a strong reference to the DisplayChangedListener, // since SunDisplayChanger keeps only a WeakReference to it. private DisplayChangeHandler displayChangeHack; { try { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); env.getScreenDevices(); // init Class aClass = Class.forName("sun.awt.DisplayChangedListener"); // might be absent displayChangeHack = new DisplayChangeHandler(); if (aClass.isInstance(env)) { // Headless env does not implement sun.awt.DisplayChangedListener (and lacks addDisplayChangedListener) env.getClass() .getMethod("addDisplayChangedListener", new Class[]{aClass}) .invoke(env, displayChangeHack); } } catch (Throwable t) { if (!(t instanceof HeadlessException)) LOG.error("Cannot setup display change listener", t); } } @Override public void validateInvalidComponents() { super.validateInvalidComponents(); } @Override public void addInvalidComponent(final JComponent invalidComponent) { checkThreadViolations(invalidComponent); super.addInvalidComponent(invalidComponent); } @Override public void addDirtyRegion(final JComponent c, final int x, final int y, final int w, final int h) { checkThreadViolations(c); super.addDirtyRegion(c, x, y, w, h); } private void checkThreadViolations(JComponent c) { if (!SwingUtilities.isEventDispatchThread() && c.isShowing()) { boolean repaint = false; boolean fromSwing = false; boolean swingKnownNonAwtOperations = false; final Exception exception = new Exception(); StackTraceElement[] stackTrace = exception.getStackTrace(); for (StackTraceElement st : stackTrace) { String className = st.getClassName(); String methodName = st.getMethodName(); if (repaint && className.startsWith("javax.swing.")) { fromSwing = true; } if (repaint && "imageUpdate".equals(methodName)) { swingKnownNonAwtOperations = true; } if ("read".equals(methodName) && className.startsWith("javax.swing.JEditorPane") || "setCharacterAttributes".equals(methodName) && className.startsWith("javax.swing.text.DefaultStyledDocument")) { swingKnownNonAwtOperations = true; break; } if ("repaint".equals(methodName)) { repaint = true; fromSwing = false; } } if (swingKnownNonAwtOperations) { return; } if (repaint && !fromSwing) { //no problems here, since repaint() is thread safe return; } //ignore the last processed component if (SoftReference.dereference(myLastComponent) == c) { return; } myLastComponent = new WeakReference(c); LOG.warn("Access to realized (ever shown) UI components should be done only from the AWT event dispatch thread," + " revalidate(), invalidate() & repaint() is ok from any thread", exception); } } }