2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 package com
.intellij
.ui
;
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
;
31 import javax
.swing
.plaf
.TreeUI
;
32 import javax
.swing
.plaf
.basic
.BasicTreeUI
;
34 import java
.lang
.ref
.WeakReference
;
35 import java
.util
.LinkedHashSet
;
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
;
48 public DeferredIconImpl(Icon baseIcon
, T param
, Function
<T
, Icon
> evaluator
) {
49 this(baseIcon
, param
, true, evaluator
);
52 public DeferredIconImpl(Icon baseIcon
, T param
, final boolean needReadAction
, Function
<T
, Icon
> evaluator
) {
54 myDelegateIcon
= nonNull(baseIcon
);
55 myEvaluator
= evaluator
;
56 myNeedReadAction
= needReadAction
;
59 private static Icon
nonNull(final Icon icon
) {
60 return icon
!= null ? icon
: EMPTY_ICON
;
63 public void paintIcon(final Component c
, final Graphics g
, final int x
, final int y
) {
64 myDelegateIcon
.paintIcon(c
, g
, x
, y
);
69 final Component target
;
71 final Container list
= SwingUtilities
.getAncestorOfClass(JList
.class, c
);
76 final Container tree
= SwingUtilities
.getAncestorOfClass(JTree
.class, c
);
81 final Container table
= SwingUtilities
.getAncestorOfClass(JTable
.class, c
);
91 myLastTarget
= new WeakReference
<Component
>(target
);
93 final Job
<Object
> job
= JobScheduler
.getInstance().createJob("Evaluating deferred icon", Job
.DEFAULT_PRIORITY
);
94 job
.addTask(new Runnable() {
96 int oldWidth
= myDelegateIcon
.getIconWidth();
97 myDelegateIcon
= evaluate();
99 final boolean shouldRevalidate
= myDelegateIcon
.getIconWidth() != oldWidth
;
101 //noinspection SSBasedInspection
102 SwingUtilities
.invokeLater(new Runnable() {
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 ((BasicTreeUI
)ui
).setLeftChildIndent(((Integer
)UIManager
.get("Tree.leftChildIndent")).intValue());
116 c
.repaint(x
, y
, getIconWidth(), getIconHeight());
119 ourRepaintScheduler
.pushDirtyComponent(target
);
130 public Icon
evaluate() {
131 final Icon
[] evaluated
= new Icon
[1];
132 final Runnable runnable
= new Runnable() {
135 evaluated
[0] = nonNull(myEvaluator
.fun(myParam
));
137 catch (ProcessCanceledException e
) {
138 evaluated
[0] = EMPTY_ICON
;
140 catch (IndexNotReadyException e
) {
141 evaluated
[0] = EMPTY_ICON
;
145 if (myNeedReadAction
) {
146 IconDeferrerImpl
.evaluateDeferredInReadAction(runnable
);
149 IconDeferrerImpl
.evaluateDeferred(runnable
);
152 checkDoesntReferenceThis(evaluated
[0]);
157 private void checkDoesntReferenceThis(final Icon icon
) {
159 throw new IllegalStateException("Loop in icons delegation");
162 if (icon
instanceof DeferredIconImpl
) {
163 checkDoesntReferenceThis(((DeferredIconImpl
)icon
).myDelegateIcon
);
165 else if (icon
instanceof LayeredIcon
) {
166 for (Icon layer
: ((LayeredIcon
)icon
).getAllLayers()) {
167 checkDoesntReferenceThis(layer
);
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
));
179 public int getIconWidth() {
180 return myDelegateIcon
.getIconWidth();
183 public int getIconHeight() {
184 return myDelegateIcon
.getIconHeight();
187 public void invalidate() {
188 myIsScheduled
= false;
189 Component lastTarget
= myLastTarget
!= null ? myLastTarget
.get() : null;
190 if (lastTarget
!= null) {
191 lastTarget
.repaint();
195 private static class RepaintScheduler
{
196 private final Alarm myAlarm
= new Alarm();
197 private final Set
<Component
> myQueue
= new LinkedHashSet
<Component
>();
199 public void pushDirtyComponent(Component c
) {
200 myAlarm
.cancelAllRequests();
201 myAlarm
.addRequest(new Runnable() {
203 for (Component component
: myQueue
) {