wide selection in trees under aqua laf + deferred icon tree cache invalidation fix...
[idea/community.git] / platform / lang-impl / src / com / intellij / ui / DeferredIconImpl.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * @author max
19  */
20 package com.intellij.ui;
21
22 import com.intellij.concurrency.Job;
23 import com.intellij.concurrency.JobScheduler;
24 import com.intellij.openapi.progress.ProcessCanceledException;
25 import com.intellij.openapi.project.IndexNotReadyException;
26 import com.intellij.util.Alarm;
27 import com.intellij.util.Function;
28 import com.intellij.util.ui.EmptyIcon;
29
30 import javax.swing.*;
31 import javax.swing.plaf.TreeUI;
32 import javax.swing.plaf.basic.BasicTreeUI;
33 import java.awt.*;
34 import java.lang.ref.WeakReference;
35 import java.util.LinkedHashSet;
36 import java.util.Set;
37
38 public class DeferredIconImpl<T> implements DeferredIcon {
39   private static final RepaintScheduler ourRepaintScheduler = new RepaintScheduler();
40   private volatile Icon myDelegateIcon;
41   private final Function<T, Icon> myEvaluator;
42   private volatile boolean myIsScheduled = false;
43   private final T myParam;
44   private WeakReference<Component> myLastTarget = null;
45   private static final EmptyIcon EMPTY_ICON = new EmptyIcon(16, 16);
46   private boolean myNeedReadAction;
47
48   public DeferredIconImpl(Icon baseIcon, T param, Function<T, Icon> evaluator) {
49     this(baseIcon, param, true, evaluator);
50   }
51
52   public DeferredIconImpl(Icon baseIcon, T param, final boolean needReadAction, Function<T, Icon> evaluator) {
53     myParam = param;
54     myDelegateIcon = nonNull(baseIcon);
55     myEvaluator = evaluator;
56     myNeedReadAction = needReadAction;
57   }
58
59   private static Icon nonNull(final Icon icon) {
60     return icon != null ? icon : EMPTY_ICON;
61   }
62
63   public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
64     myDelegateIcon.paintIcon(c, g, x, y);
65
66     if (!myIsScheduled) {
67       myIsScheduled = true;
68
69       final Component target;
70
71       final Container list = SwingUtilities.getAncestorOfClass(JList.class, c);
72       if (list != null) {
73         target = list;
74       }
75       else {
76         final Container tree = SwingUtilities.getAncestorOfClass(JTree.class, c);
77         if (tree != null) {
78           target = tree;
79         }
80         else {
81           final Container table = SwingUtilities.getAncestorOfClass(JTable.class, c);
82           if (table != null) {
83             target = table;
84           }
85           else {
86             target = c;
87           }
88         }
89       }
90
91       myLastTarget = new WeakReference<Component>(target);
92
93       final Job<Object> job = JobScheduler.getInstance().createJob("Evaluating deferred icon", Job.DEFAULT_PRIORITY);
94       job.addTask(new Runnable() {
95         public void run() {
96           int oldWidth = myDelegateIcon.getIconWidth();
97           myDelegateIcon = evaluate();
98
99           final boolean shouldRevalidate = myDelegateIcon.getIconWidth() != oldWidth;
100
101           //noinspection SSBasedInspection
102           SwingUtilities.invokeLater(new Runnable() {
103             public void run() {
104               if (shouldRevalidate) {
105                 // revalidate will not work: jtree caches size of nodes
106                 if (target instanceof JTree) {
107                   final TreeUI ui = ((JTree)target).getUI();
108                   if (ui instanceof BasicTreeUI) {
109                     // this call is "fake" and only need to reset tree layout cache
110                     ((BasicTreeUI)ui).setLeftChildIndent(((Integer)UIManager.get("Tree.leftChildIndent")).intValue());
111                   }
112                 }
113               }
114
115               if (c == target) {
116                 c.repaint(x, y, getIconWidth(), getIconHeight());
117               }
118               else {
119                 ourRepaintScheduler.pushDirtyComponent(target);
120               }
121             }
122           });
123         }
124       });
125
126       job.schedule();
127     }
128   }
129
130   public Icon evaluate() {
131     final Icon[] evaluated = new Icon[1];
132     final Runnable runnable = new Runnable() {
133       public void run() {
134         try {
135           evaluated[0] = nonNull(myEvaluator.fun(myParam));
136         }
137         catch (ProcessCanceledException e) {
138           evaluated[0] = EMPTY_ICON;
139         }
140         catch (IndexNotReadyException e) {
141           evaluated[0] = EMPTY_ICON;
142         }
143       }
144     };
145     if (myNeedReadAction) {
146       IconDeferrerImpl.evaluateDeferredInReadAction(runnable);
147     }
148     else {
149       IconDeferrerImpl.evaluateDeferred(runnable);
150     }
151
152     checkDoesntReferenceThis(evaluated[0]);
153
154     return evaluated[0];
155   }
156
157   private void checkDoesntReferenceThis(final Icon icon) {
158     if (icon == this) {
159       throw new IllegalStateException("Loop in icons delegation");
160     }
161
162     if (icon instanceof DeferredIconImpl) {
163       checkDoesntReferenceThis(((DeferredIconImpl)icon).myDelegateIcon);
164     }
165     else if (icon instanceof LayeredIcon) {
166       for (Icon layer : ((LayeredIcon)icon).getAllLayers()) {
167         checkDoesntReferenceThis(layer);
168       }
169     }
170     else if (icon instanceof RowIcon) {
171       final RowIcon rowIcon = (RowIcon)icon;
172       final int count = rowIcon.getIconCount();
173       for (int i = 0; i < count; i++) {
174         checkDoesntReferenceThis(rowIcon.getIcon(i));
175       }
176     }
177   }
178
179   public int getIconWidth() {
180     return myDelegateIcon.getIconWidth();
181   }
182
183   public int getIconHeight() {
184     return myDelegateIcon.getIconHeight();
185   }
186
187   public void invalidate() {
188     myIsScheduled = false;
189     Component lastTarget = myLastTarget != null ? myLastTarget.get() : null;
190     if (lastTarget != null) {
191       lastTarget.repaint();
192     }
193   }
194
195   private static class RepaintScheduler {
196     private final Alarm myAlarm = new Alarm();
197     private final Set<Component> myQueue = new LinkedHashSet<Component>();
198
199     public void pushDirtyComponent(Component c) {
200       myAlarm.cancelAllRequests();
201       myAlarm.addRequest(new Runnable() {
202         public void run() {
203           for (Component component : myQueue) {
204             component.repaint();
205           }
206           myQueue.clear();
207         }
208       }, 50);
209
210       myQueue.add(c);
211     }
212   }
213 }