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) {
86 Disposer
.dispose(myBusyIcon
);
92 public void doLayout() {
95 updateBusyIconLocation();
98 private void updateBusyIconLocation() {
99 if (myBusyIcon
!= null) {
100 final Rectangle rec
= getVisibleRect();
102 final Dimension iconSize
= myBusyIcon
.getPreferredSize();
104 final Rectangle newBounds
= new Rectangle(rec
.x
+ rec
.width
- iconSize
.width
, rec
.y
, iconSize
.width
, iconSize
.height
);
105 if (!newBounds
.equals(myBusyIcon
.getBounds())) {
106 myBusyIcon
.setBounds(newBounds
);
113 * Hack to prevent loosing multiple selection on Mac when clicking Ctrl+Left Mouse Button.
114 * See faulty code at BasicTreeUI.selectPathForEvent():2245
118 protected void processMouseEvent(MouseEvent e
) {
119 if (SystemInfo
.isMac
) {
120 if (SwingUtilities
.isLeftMouseButton(e
) && e
.isControlDown() && e
.getID() == MouseEvent
.MOUSE_PRESSED
) {
121 int modifiers
= (e
.getModifiers() & ~
(MouseEvent
.CTRL_MASK
| MouseEvent
.BUTTON1_MASK
)) | MouseEvent
.BUTTON3_MASK
;
122 e
= new MouseEvent(e
.getComponent(), e
.getID(), e
.getWhen(), modifiers
, e
.getX(), e
.getY(), e
.getClickCount(), true,
126 super.processMouseEvent(e
);
130 * Disable Sun's speedsearch
132 public TreePath
getNextMatch(String prefix
, int startingRow
, Position
.Bias bias
) {
136 private static final int AUTOSCROLL_MARGIN
= 10;
138 public Insets
getAutoscrollInsets() {
139 return new Insets(getLocation().y
+ AUTOSCROLL_MARGIN
, 0, getParent().getHeight() - AUTOSCROLL_MARGIN
, getWidth() - 1);
142 public void autoscroll(Point p
) {
143 int realrow
= getClosestRowForLocation(p
.x
, p
.y
);
144 if (getLocation().y
+ p
.y
<= AUTOSCROLL_MARGIN
) {
145 if (realrow
>= 1) realrow
--;
148 if (realrow
< getRowCount() - 1) realrow
++;
150 scrollRowToVisible(realrow
);
154 protected void paintComponent(Graphics g
) {
155 g
.setColor(getBackground());
156 g
.fillRect(0, 0, getWidth(), getHeight());
160 super.paintComponent(g
);
163 protected boolean highlightSingleNode() {
167 private void paintNodeContent(Graphics g
) {
168 if (!(getUI() instanceof BasicTreeUI
)) return;
170 final AbstractTreeBuilder builder
= AbstractTreeBuilder
.getBuilderFor(this);
171 if (builder
== null || builder
.isDisposed()) return;
173 GraphicsConfig config
= new GraphicsConfig(g
);
174 config
.setAntialiasing(true);
176 final AbstractTreeStructure structure
= builder
.getTreeStructure();
178 for (int eachRow
= 0; eachRow
< getRowCount(); eachRow
++) {
179 final TreePath path
= getPathForRow(eachRow
);
180 PresentableNodeDescriptor node
= toPresentableNode(path
.getLastPathComponent());
181 if (node
== null) continue;
183 if (!node
.isContentHighlighted()) continue;
185 if (highlightSingleNode()) {
186 if (node
.isContentHighlighted()) {
187 final TreePath nodePath
= getPath(node
);
191 final Rectangle parentRect
= getPathBounds(nodePath
);
192 if (isExpanded(nodePath
)) {
193 final int[] max
= getMax(node
, structure
);
194 rect
= new Rectangle(parentRect
.x
, parentRect
.y
, Math
.max((int) parentRect
.getMaxX(), max
[1]) - parentRect
.x
- 1,
195 Math
.max((int) parentRect
.getMaxY(), max
[0]) - parentRect
.y
- 1);
202 final Color highlightColor
= node
.getHighlightColor();
203 g
.setColor(highlightColor
);
204 g
.fillRoundRect(rect
.x
, rect
.y
, rect
.width
, rect
.height
, 4, 4);
205 g
.setColor(highlightColor
.darker());
206 g
.drawRoundRect(rect
.x
, rect
.y
, rect
.width
, rect
.height
, 4, 4);
211 //todo: to investigate why it might happen under 1.6: http://www.productiveme.net:8080/browse/PM-217
212 if (node
.getParentDescriptor() == null) continue;
214 final Object
[] kids
= structure
.getChildElements(node
);
215 if (kids
.length
== 0) continue;
217 PresentableNodeDescriptor first
= null;
218 PresentableNodeDescriptor last
= null;
220 for (int i
= 0; i
< kids
.length
; i
++) {
221 final Object kid
= kids
[i
];
222 if (kid
instanceof PresentableNodeDescriptor
) {
223 PresentableNodeDescriptor eachKid
= (PresentableNodeDescriptor
) kid
;
224 if (!node
.isHighlightableContentNode(eachKid
)) continue;
233 if (first
== null || last
== null) continue;
234 Rectangle firstBounds
= getPathBounds(getPath(first
));
236 if (isExpanded(getPath(last
))) {
237 if (lastIndex
+ 1 < kids
.length
) {
238 final Object child
= kids
[lastIndex
+ 1];
239 if (child
instanceof PresentableNodeDescriptor
) {
240 PresentableNodeDescriptor nextKid
= (PresentableNodeDescriptor
) child
;
241 int nextRow
= getRowForPath(getPath(nextKid
));
242 last
= toPresentableNode(getPathForRow(nextRow
- 1).getLastPathComponent());
246 NodeDescriptor parentNode
= node
.getParentDescriptor();
247 if (parentNode
instanceof PresentableNodeDescriptor
) {
248 final PresentableNodeDescriptor ppd
= (PresentableNodeDescriptor
)parentNode
;
249 int nodeIndex
= node
.getIndex();
250 if (nodeIndex
+ 1 < structure
.getChildElements(ppd
).length
) {
251 PresentableNodeDescriptor nextChild
= ppd
.getChildToHighlightAt(nodeIndex
+ 1);
252 int nextRow
= getRowForPath(getPath(nextChild
));
253 last
= toPresentableNode(getPathForRow(nextRow
- 1).getLastPathComponent());
256 int lastRow
= getRowForPath(getPath(last
));
257 PresentableNodeDescriptor lastParent
= last
;
258 boolean lastWasFound
= false;
259 for (int i
= lastRow
+ 1; i
< getRowCount(); i
++) {
260 PresentableNodeDescriptor eachNode
= toPresentableNode(getPathForRow(i
).getLastPathComponent());
261 if (!node
.isParentOf(eachNode
)) {
266 lastParent
= eachNode
;
269 last
= toPresentableNode(getPathForRow(getRowCount() - 1).getLastPathComponent());
276 Rectangle lastBounds
= getPathBounds(getPath(last
));
278 if (firstBounds
== null || lastBounds
== null) continue;
280 Rectangle toPaint
= new Rectangle(firstBounds
.x
, firstBounds
.y
, 0, (int)lastBounds
.getMaxY() - firstBounds
.y
- 1);
282 toPaint
.width
= getWidth() - toPaint
.x
- 4;
284 final Color highlightColor
= first
.getHighlightColor();
285 g
.setColor(highlightColor
);
286 g
.fillRoundRect(toPaint
.x
, toPaint
.y
, toPaint
.width
, toPaint
.height
, 4, 4);
287 g
.setColor(highlightColor
.darker());
288 g
.drawRoundRect(toPaint
.x
, toPaint
.y
, toPaint
.width
, toPaint
.height
, 4, 4);
295 private int[] getMax(final PresentableNodeDescriptor node
, final AbstractTreeStructure structure
) {
298 final Object
[] children
= structure
.getChildElements(node
);
299 for (final Object child
: children
) {
300 if (child
instanceof PresentableNodeDescriptor
) {
301 final TreePath childPath
= getPath((PresentableNodeDescriptor
)child
);
302 if (childPath
!= null) {
303 if (isExpanded(childPath
)) {
304 final int[] tmp
= getMax((PresentableNodeDescriptor
)child
, structure
);
305 y
= Math
.max(y
, tmp
[0]);
306 x
= Math
.max(x
, tmp
[1]);
309 final Rectangle r
= getPathBounds(childPath
);
311 y
= Math
.max(y
, (int)r
.getMaxY());
312 x
= Math
.max(x
, (int)r
.getMaxX());
318 return new int[]{y
, x
};
322 private static PresentableNodeDescriptor
toPresentableNode(final Object pathComponent
) {
323 if (!(pathComponent
instanceof DefaultMutableTreeNode
)) return null;
324 final Object userObject
= ((DefaultMutableTreeNode
)pathComponent
).getUserObject();
325 if (!(userObject
instanceof PresentableNodeDescriptor
)) return null;
326 return (PresentableNodeDescriptor
)userObject
;
329 public TreePath
getPath(PresentableNodeDescriptor node
) {
330 final AbstractTreeBuilder builder
= AbstractTreeBuilder
.getBuilderFor(this);
331 final DefaultMutableTreeNode treeNode
= builder
.getNodeForElement(node
);
333 return treeNode
!= null ?
new TreePath(treeNode
.getPath()) : new TreePath(node
);
336 private class MyMouseListener
extends MouseAdapter
{
337 public void mousePressed(MouseEvent mouseevent
) {
338 if (!SwingUtilities
.isLeftMouseButton(mouseevent
) &&
339 (SwingUtilities
.isRightMouseButton(mouseevent
) || SwingUtilities
.isMiddleMouseButton(mouseevent
))) {
340 TreePath treepath
= getPathForLocation(mouseevent
.getX(), mouseevent
.getY());
341 if (treepath
!= null) {
342 if (getSelectionModel().getSelectionMode() != TreeSelectionModel
.SINGLE_TREE_SELECTION
) {
343 TreePath
[] selectionPaths
= getSelectionModel().getSelectionPaths();
344 if (selectionPaths
!= null) {
345 for (TreePath selectionPath
: selectionPaths
) {
346 if (selectionPath
== treepath
) return;
350 getSelectionModel().setSelectionPath(treepath
);
357 * This is patch for 4893787 SUN bug. The problem is that the BasicTreeUI.FocusHandler repaints
358 * only lead selection index on focus changes. It's a problem with multiple selected nodes.
360 private class MyFocusListener
extends FocusAdapter
{
361 private void focusChanges() {
362 TreePath
[] paths
= getSelectionPaths();
365 for (int i
= paths
.length
- 1; i
>= 0; i
--) {
366 Rectangle bounds
= ui
.getPathBounds(Tree
.this, paths
[i
]);
367 if (bounds
!= null) {
374 public void focusGained(FocusEvent e
) {
378 public void focusLost(FocusEvent e
) {
383 private class SelectionFixer
extends FocusAdapter
{
384 public void focusGained(final FocusEvent e
) {
385 final TreePath
[] paths
= getSelectionPaths();
386 if (paths
== null || paths
.length
== 0) {
387 for (int eachRow
= 0; eachRow
< getRowCount(); eachRow
++) {
388 final TreePath path
= getPathForRow(eachRow
);
389 if (path
!= null && isVisible(path
)) {
390 setSelectionPath(path
);
398 public final void setLineStyleAngled() {
399 UIUtil
.setLineStyleAngled(this);
402 public <T
> T
[] getSelectedNodes(Class
<T
> nodeType
, @Nullable NodeFilter
<T
> filter
) {
403 TreePath
[] paths
= getSelectionPaths();
404 if (paths
== null) return (T
[])Array
.newInstance(nodeType
, 0);
406 ArrayList
<T
> nodes
= new ArrayList
<T
>();
407 for (int i
= 0; i
< paths
.length
; i
++) {
408 Object last
= paths
[i
].getLastPathComponent();
409 if (nodeType
.isAssignableFrom(last
.getClass())) {
410 if (filter
!= null && !filter
.accept((T
)last
)) continue;
414 T
[] result
= (T
[])Array
.newInstance(nodeType
, nodes
.size());
415 nodes
.toArray(result
);
419 public static interface NodeFilter
<T
> {
420 boolean accept(T node
);
423 public void putInfo(Map
<String
, String
> info
) {
424 final TreePath
[] selection
= getSelectionPaths();
425 if (selection
== null) return;
427 StringBuffer nodesText
= new StringBuffer();
429 for (TreePath eachPath
: selection
) {
430 final Object eachNode
= eachPath
.getLastPathComponent();
432 getCellRenderer().getTreeCellRendererComponent(this, eachNode
, false, false, false, getRowForPath(eachPath
), false);
435 if (nodesText
.length() > 0) {
436 nodesText
.append(";");
438 nodesText
.append(c
.toString());
442 if (nodesText
.length() > 0) {
443 info
.put("selectedNodes", nodesText
.toString());
447 public void setPaintBusy(boolean paintBusy
) {
448 if (myBusy
== paintBusy
) return;
455 public void paint(Graphics g
) {
458 final Rectangle visible
= getVisibleRect();
460 if (!visible
.equals(myLastVisibleRec
)) {
461 updateBusyIconLocation();
464 myLastVisibleRec
= visible
;
467 private void updateBusy() {
469 if (myBusyIcon
== null) {
470 myBusyIcon
= new AsyncProcessIcon(toString());
471 myBusyIcon
.setPaintPassiveIcon(false);
476 if (myBusyIcon
!= null) {
480 myBusyIcon
.suspend();
481 SwingUtilities
.invokeLater(new Runnable() {
483 if (myBusyIcon
!= null) {
489 updateBusyIconLocation();