| /* |
| * 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<GraphicsConfiguration, VolatileImage> myImagesMap; |
| |
| WeakReference<JComponent> 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<JComponent>(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); |
| } |
| } |
| } |