a11affc7279c436415d04a6e2aa74bf3b02f6091
[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                     // yep, reset size cache
110                     int indent = ((Integer)UIManager.get("Tree.leftChildIndent")).intValue();
111                     if (((BasicTreeUI)ui).getLeftChildIndent() != indent) {
112                       ((BasicTreeUI)ui).setLeftChildIndent(indent);
113                     }
114                   }
115                 }
116               }
117
118               if (c == target) {
119                 c.repaint(x, y, getIconWidth(), getIconHeight());
120               }
121               else {
122                 ourRepaintScheduler.pushDirtyComponent(target);
123               }
124             }
125           });
126         }
127       });
128
129       job.schedule();
130     }
131   }
132
133   public Icon evaluate() {
134     final Icon[] evaluated = new Icon[1];
135     final Runnable runnable = new Runnable() {
136       public void run() {
137         try {
138           evaluated[0] = nonNull(myEvaluator.fun(myParam));
139         }
140         catch (ProcessCanceledException e) {
141           evaluated[0] = EMPTY_ICON;
142         }
143         catch (IndexNotReadyException e) {
144           evaluated[0] = EMPTY_ICON;
145         }
146       }
147     };
148     if (myNeedReadAction) {
149       IconDeferrerImpl.evaluateDeferredInReadAction(runnable);
150     }
151     else {
152       IconDeferrerImpl.evaluateDeferred(runnable);
153     }
154
155     checkDoesntReferenceThis(evaluated[0]);
156
157     return evaluated[0];
158   }
159
160   private void checkDoesntReferenceThis(final Icon icon) {
161     if (icon == this) {
162       throw new IllegalStateException("Loop in icons delegation");
163     }
164
165     if (icon instanceof DeferredIconImpl) {
166       checkDoesntReferenceThis(((DeferredIconImpl)icon).myDelegateIcon);
167     }
168     else if (icon instanceof LayeredIcon) {
169       for (Icon layer : ((LayeredIcon)icon).getAllLayers()) {
170         checkDoesntReferenceThis(layer);
171       }
172     }
173     else if (icon instanceof RowIcon) {
174       final RowIcon rowIcon = (RowIcon)icon;
175       final int count = rowIcon.getIconCount();
176       for (int i = 0; i < count; i++) {
177         checkDoesntReferenceThis(rowIcon.getIcon(i));
178       }
179     }
180   }
181
182   public int getIconWidth() {
183     return myDelegateIcon.getIconWidth();
184   }
185
186   public int getIconHeight() {
187     return myDelegateIcon.getIconHeight();
188   }
189
190   public void invalidate() {
191     myIsScheduled = false;
192     Component lastTarget = myLastTarget != null ? myLastTarget.get() : null;
193     if (lastTarget != null) {
194       lastTarget.repaint();
195     }
196   }
197
198   private static class RepaintScheduler {
199     private final Alarm myAlarm = new Alarm();
200     private final Set<Component> myQueue = new LinkedHashSet<Component>();
201
202     public void pushDirtyComponent(Component c) {
203       myAlarm.cancelAllRequests();
204       myAlarm.addRequest(new Runnable() {
205         public void run() {
206           for (Component component : myQueue) {
207             component.repaint();
208           }
209           myQueue.clear();
210         }
211       }, 50);
212
213       myQueue.add(c);
214     }
215   }
216 }