2 * Copyright 2000-2007 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.
16 package com
.intellij
.ui
.treeStructure
;
18 import com
.intellij
.Patches
;
19 import com
.intellij
.ide
.util
.treeView
.*;
20 import com
.intellij
.openapi
.ui
.TestableUi
;
21 import com
.intellij
.openapi
.util
.Disposer
;
22 import com
.intellij
.openapi
.util
.SystemInfo
;
23 import com
.intellij
.openapi
.wm
.impl
.content
.GraphicsConfig
;
24 import com
.intellij
.util
.ui
.AsyncProcessIcon
;
25 import com
.intellij
.util
.ui
.UIUtil
;
26 import org
.jetbrains
.annotations
.Nullable
;
29 import javax
.swing
.plaf
.TreeUI
;
30 import javax
.swing
.plaf
.basic
.BasicTreeUI
;
31 import javax
.swing
.text
.Position
;
32 import javax
.swing
.tree
.*;
34 import java
.awt
.dnd
.Autoscroll
;
35 import java
.awt
.event
.FocusAdapter
;
36 import java
.awt
.event
.FocusEvent
;
37 import java
.awt
.event
.MouseAdapter
;
38 import java
.awt
.event
.MouseEvent
;
39 import java
.lang
.reflect
.Array
;
40 import java
.util
.ArrayList
;
43 public class Tree
extends JTree
implements Autoscroll
, TestableUi
{
45 private AsyncProcessIcon myBusyIcon
;
46 private boolean myBusy
;
47 private Rectangle myLastVisibleRec
;
53 public Tree(TreeModel treemodel
) {
58 public Tree(TreeNode root
) {
63 private void initTree_() {
64 addMouseListener(new MyMouseListener());
65 if (Patches
.SUN_BUG_ID_4893787
) {
66 addFocusListener(new MyFocusListener());
69 addFocusListener(new SelectionFixer());
71 setCellRenderer(new NodeRenderer());
75 public void addNotify() {
82 public void removeNotify() {
85 if (myBusyIcon
!= null) {
87 Disposer
.dispose(myBusyIcon
);
93 public void doLayout() {
96 updateBusyIconLocation();
99 private void updateBusyIconLocation() {
100 if (myBusyIcon
!= null) {
101 final Rectangle rec
= getVisibleRect();
103 final Dimension iconSize
= myBusyIcon
.getPreferredSize();
105 final Rectangle newBounds
= new Rectangle(rec
.x
+ rec
.width
- iconSize
.width
, rec
.y
, iconSize
.width
, iconSize
.height
);
106 if (!newBounds
.equals(myBusyIcon
.getBounds())) {
107 myBusyIcon
.setBounds(newBounds
);
114 * Hack to prevent loosing multiple selection on Mac when clicking Ctrl+Left Mouse Button.
115 * See faulty code at BasicTreeUI.selectPathForEvent():2245
119 protected void processMouseEvent(MouseEvent e
) {
120 if (SystemInfo
.isMac
) {
121 if (SwingUtilities
.isLeftMouseButton(e
) && e
.isControlDown() && e
.getID() == MouseEvent
.MOUSE_PRESSED
) {
122 int modifiers
= (e
.getModifiers() & ~
(MouseEvent
.CTRL_MASK
| MouseEvent
.BUTTON1_MASK
)) | MouseEvent
.BUTTON3_MASK
;
123 e
= new MouseEvent(e
.getComponent(), e
.getID(), e
.getWhen(), modifiers
, e
.getX(), e
.getY(), e
.getClickCount(), true,
127 super.processMouseEvent(e
);
131 * Disable Sun's speedsearch
133 public TreePath
getNextMatch(String prefix
, int startingRow
, Position
.Bias bias
) {
137 private static final int AUTOSCROLL_MARGIN
= 10;
139 public Insets
getAutoscrollInsets() {
140 return new Insets(getLocation().y
+ AUTOSCROLL_MARGIN
, 0, getParent().getHeight() - AUTOSCROLL_MARGIN
, getWidth() - 1);
143 public void autoscroll(Point p
) {
144 int realrow
= getClosestRowForLocation(p
.x
, p
.y
);
145 if (getLocation().y
+ p
.y
<= AUTOSCROLL_MARGIN
) {
146 if (realrow
>= 1) realrow
--;
149 if (realrow
< getRowCount() - 1) realrow
++;
151 scrollRowToVisible(realrow
);
155 protected void paintComponent(Graphics g
) {
156 g
.setColor(getBackground());
157 g
.fillRect(0, 0, getWidth(), getHeight());
161 super.paintComponent(g
);
164 protected boolean highlightSingleNode() {
168 private void paintNodeContent(Graphics g
) {
169 if (!(getUI() instanceof BasicTreeUI
)) return;
171 final AbstractTreeBuilder builder
= AbstractTreeBuilder
.getBuilderFor(this);
172 if (builder
== null || builder
.isDisposed()) return;
174 GraphicsConfig config
= new GraphicsConfig(g
);
175 config
.setAntialiasing(true);
177 final AbstractTreeStructure structure
= builder
.getTreeStructure();
179 for (int eachRow
= 0; eachRow
< getRowCount(); eachRow
++) {
180 final TreePath path
= getPathForRow(eachRow
);
181 PresentableNodeDescriptor node
= toPresentableNode(path
.getLastPathComponent());
182 if (node
== null) continue;
184 if (!node
.isContentHighlighted()) continue;
186 if (highlightSingleNode()) {
187 if (node
.isContentHighlighted()) {
188 final TreePath nodePath
= getPath(node
);
192 final Rectangle parentRect
= getPathBounds(nodePath
);
193 if (isExpanded(nodePath
)) {
194 final int[] max
= getMax(node
, structure
);
195 rect
= new Rectangle(parentRect
.x
, parentRect
.y
, Math
.max((int) parentRect
.getMaxX(), max
[1]) - parentRect
.x
- 1,
196 Math
.max((int) parentRect
.getMaxY(), max
[0]) - parentRect
.y
- 1);
203 final Color highlightColor
= node
.getHighlightColor();
204 g
.setColor(highlightColor
);
205 g
.fillRoundRect(rect
.x
, rect
.y
, rect
.width
, rect
.height
, 4, 4);
206 g
.setColor(highlightColor
.darker());
207 g
.drawRoundRect(rect
.x
, rect
.y
, rect
.width
, rect
.height
, 4, 4);
212 //todo: to investigate why it might happen under 1.6: http://www.productiveme.net:8080/browse/PM-217
213 if (node
.getParentDescriptor() == null) continue;
215 final Object
[] kids
= structure
.getChildElements(node
);
216 if (kids
.length
== 0) continue;
218 PresentableNodeDescriptor first
= null;
219 PresentableNodeDescriptor last
= null;
221 for (int i
= 0; i
< kids
.length
; i
++) {
222 final Object kid
= kids
[i
];
223 if (kid
instanceof PresentableNodeDescriptor
) {
224 PresentableNodeDescriptor eachKid
= (PresentableNodeDescriptor
) kid
;
225 if (!node
.isHighlightableContentNode(eachKid
)) continue;
234 if (first
== null || last
== null) continue;
235 Rectangle firstBounds
= getPathBounds(getPath(first
));
237 if (isExpanded(getPath(last
))) {
238 if (lastIndex
+ 1 < kids
.length
) {
239 final Object child
= kids
[lastIndex
+ 1];
240 if (child
instanceof PresentableNodeDescriptor
) {
241 PresentableNodeDescriptor nextKid
= (PresentableNodeDescriptor
) child
;
242 int nextRow
= getRowForPath(getPath(nextKid
));
243 last
= toPresentableNode(getPathForRow(nextRow
- 1).getLastPathComponent());
247 NodeDescriptor parentNode
= node
.getParentDescriptor();
248 if (parentNode
instanceof PresentableNodeDescriptor
) {
249 final PresentableNodeDescriptor ppd
= (PresentableNodeDescriptor
)parentNode
;
250 int nodeIndex
= node
.getIndex();
251 if (nodeIndex
+ 1 < structure
.getChildElements(ppd
).length
) {
252 PresentableNodeDescriptor nextChild
= ppd
.getChildToHighlightAt(nodeIndex
+ 1);
253 int nextRow
= getRowForPath(getPath(nextChild
));
254 last
= toPresentableNode(getPathForRow(nextRow
- 1).getLastPathComponent());
257 int lastRow
= getRowForPath(getPath(last
));
258 PresentableNodeDescriptor lastParent
= last
;
259 boolean lastWasFound
= false;
260 for (int i
= lastRow
+ 1; i
< getRowCount(); i
++) {
261 PresentableNodeDescriptor eachNode
= toPresentableNode(getPathForRow(i
).getLastPathComponent());
262 if (!node
.isParentOf(eachNode
)) {
267 lastParent
= eachNode
;
270 last
= toPresentableNode(getPathForRow(getRowCount() - 1).getLastPathComponent());
277 Rectangle lastBounds
= getPathBounds(getPath(last
));
279 if (firstBounds
== null || lastBounds
== null) continue;
281 Rectangle toPaint
= new Rectangle(firstBounds
.x
, firstBounds
.y
, 0, (int)lastBounds
.getMaxY() - firstBounds
.y
- 1);
283 toPaint
.width
= getWidth() - toPaint
.x
- 4;
285 final Color highlightColor
= first
.getHighlightColor();
286 g
.setColor(highlightColor
);
287 g
.fillRoundRect(toPaint
.x
, toPaint
.y
, toPaint
.width
, toPaint
.height
, 4, 4);
288 g
.setColor(highlightColor
.darker());
289 g
.drawRoundRect(toPaint
.x
, toPaint
.y
, toPaint
.width
, toPaint
.height
, 4, 4);
296 private int[] getMax(final PresentableNodeDescriptor node
, final AbstractTreeStructure structure
) {
299 final Object
[] children
= structure
.getChildElements(node
);
300 for (final Object child
: children
) {
301 if (child
instanceof PresentableNodeDescriptor
) {
302 final TreePath childPath
= getPath((PresentableNodeDescriptor
)child
);
303 if (childPath
!= null) {
304 if (isExpanded(childPath
)) {
305 final int[] tmp
= getMax((PresentableNodeDescriptor
)child
, structure
);
306 y
= Math
.max(y
, tmp
[0]);
307 x
= Math
.max(x
, tmp
[1]);
310 final Rectangle r
= getPathBounds(childPath
);
312 y
= Math
.max(y
, (int)r
.getMaxY());
313 x
= Math
.max(x
, (int)r
.getMaxX());
319 return new int[]{y
, x
};
323 private static PresentableNodeDescriptor
toPresentableNode(final Object pathComponent
) {
324 if (!(pathComponent
instanceof DefaultMutableTreeNode
)) return null;
325 final Object userObject
= ((DefaultMutableTreeNode
)pathComponent
).getUserObject();
326 if (!(userObject
instanceof PresentableNodeDescriptor
)) return null;
327 return (PresentableNodeDescriptor
)userObject
;
330 public TreePath
getPath(PresentableNodeDescriptor node
) {
331 final AbstractTreeBuilder builder
= AbstractTreeBuilder
.getBuilderFor(this);
332 final DefaultMutableTreeNode treeNode
= builder
.getNodeForElement(node
);
334 return treeNode
!= null ?
new TreePath(treeNode
.getPath()) : new TreePath(node
);
337 private class MyMouseListener
extends MouseAdapter
{
338 public void mousePressed(MouseEvent mouseevent
) {
339 if (!SwingUtilities
.isLeftMouseButton(mouseevent
) &&
340 (SwingUtilities
.isRightMouseButton(mouseevent
) || SwingUtilities
.isMiddleMouseButton(mouseevent
))) {
341 TreePath treepath
= getPathForLocation(mouseevent
.getX(), mouseevent
.getY());
342 if (treepath
!= null) {
343 if (getSelectionModel().getSelectionMode() != TreeSelectionModel
.SINGLE_TREE_SELECTION
) {
344 TreePath
[] selectionPaths
= getSelectionModel().getSelectionPaths();
345 if (selectionPaths
!= null) {
346 for (TreePath selectionPath
: selectionPaths
) {
347 if (selectionPath
== treepath
) return;
351 getSelectionModel().setSelectionPath(treepath
);
358 * This is patch for 4893787 SUN bug. The problem is that the BasicTreeUI.FocusHandler repaints
359 * only lead selection index on focus changes. It's a problem with multiple selected nodes.
361 private class MyFocusListener
extends FocusAdapter
{
362 private void focusChanges() {
363 TreePath
[] paths
= getSelectionPaths();
366 for (int i
= paths
.length
- 1; i
>= 0; i
--) {
367 Rectangle bounds
= ui
.getPathBounds(Tree
.this, paths
[i
]);
368 if (bounds
!= null) {
375 public void focusGained(FocusEvent e
) {
379 public void focusLost(FocusEvent e
) {
384 private class SelectionFixer
extends FocusAdapter
{
385 public void focusGained(final FocusEvent e
) {
386 final TreePath
[] paths
= getSelectionPaths();
387 if (paths
== null || paths
.length
== 0) {
388 for (int eachRow
= 0; eachRow
< getRowCount(); eachRow
++) {
389 final TreePath path
= getPathForRow(eachRow
);
390 if (path
!= null && isVisible(path
)) {
391 setSelectionPath(path
);
399 public final void setLineStyleAngled() {
400 UIUtil
.setLineStyleAngled(this);
403 public <T
> T
[] getSelectedNodes(Class
<T
> nodeType
, @Nullable NodeFilter
<T
> filter
) {
404 TreePath
[] paths
= getSelectionPaths();
405 if (paths
== null) return (T
[])Array
.newInstance(nodeType
, 0);
407 ArrayList
<T
> nodes
= new ArrayList
<T
>();
408 for (int i
= 0; i
< paths
.length
; i
++) {
409 Object last
= paths
[i
].getLastPathComponent();
410 if (nodeType
.isAssignableFrom(last
.getClass())) {
411 if (filter
!= null && !filter
.accept((T
)last
)) continue;
415 T
[] result
= (T
[])Array
.newInstance(nodeType
, nodes
.size());
416 nodes
.toArray(result
);
420 public static interface NodeFilter
<T
> {
421 boolean accept(T node
);
424 public void putInfo(Map
<String
, String
> info
) {
425 final TreePath
[] selection
= getSelectionPaths();
426 if (selection
== null) return;
428 StringBuffer nodesText
= new StringBuffer();
430 for (TreePath eachPath
: selection
) {
431 final Object eachNode
= eachPath
.getLastPathComponent();
433 getCellRenderer().getTreeCellRendererComponent(this, eachNode
, false, false, false, getRowForPath(eachPath
), false);
436 if (nodesText
.length() > 0) {
437 nodesText
.append(";");
439 nodesText
.append(c
.toString());
443 if (nodesText
.length() > 0) {
444 info
.put("selectedNodes", nodesText
.toString());
448 public void setPaintBusy(boolean paintBusy
) {
449 if (myBusy
== paintBusy
) return;
456 public void paint(Graphics g
) {
459 final Rectangle visible
= getVisibleRect();
461 if (!visible
.equals(myLastVisibleRec
)) {
462 updateBusyIconLocation();
465 myLastVisibleRec
= visible
;
468 private void updateBusy() {
470 if (myBusyIcon
== null) {
471 myBusyIcon
= new AsyncProcessIcon(toString());
472 myBusyIcon
.setPaintPassiveIcon(false);
477 if (myBusyIcon
!= null) {
481 myBusyIcon
.suspend();
482 SwingUtilities
.invokeLater(new Runnable() {
484 if (myBusyIcon
!= null) {
490 updateBusyIconLocation();