2 * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
3 * Use is subject to license terms.
5 package jetbrains
.fabrique
.ui
.treeStructure
;
7 import com
.intellij
.ide
.util
.treeView
.AbstractTreeBuilder
;
8 import com
.intellij
.openapi
.actionSystem
.ActionGroup
;
9 import com
.intellij
.openapi
.actionSystem
.ActionManager
;
10 import com
.intellij
.openapi
.actionSystem
.ActionPopupMenu
;
11 import com
.intellij
.openapi
.application
.ApplicationManager
;
12 import com
.intellij
.openapi
.application
.ModalityState
;
13 import com
.intellij
.ui
.TreeSpeedSearch
;
14 import com
.intellij
.ui
.TreeToolTipHandler
;
15 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
16 import org
.jetbrains
.annotations
.Nullable
;
19 import javax
.swing
.event
.CellEditorListener
;
20 import javax
.swing
.event
.ChangeEvent
;
21 import javax
.swing
.event
.TreeSelectionEvent
;
22 import javax
.swing
.event
.TreeSelectionListener
;
23 import javax
.swing
.plaf
.basic
.BasicTreeUI
;
24 import javax
.swing
.tree
.*;
26 import java
.awt
.event
.*;
27 import java
.util
.ArrayList
;
29 public class SimpleTree
extends JTree
implements CellEditorListener
{
31 protected MouseListener myMouseListener
= new MyMouseListener();
33 private ActionGroup myPopupGroup
;
34 private String myPlace
;
36 private static SimpleNode NULL_NODE
= new NullNode();
39 private static final int INVALID
= -1;
40 private JComponent myEditorComponent
;
41 private boolean myEscapePressed
;
42 private int myEditingRow
;
43 private boolean myIgnoreSelectionChange
;
45 private int myMinHeightInRows
= 5;
48 setModel(new DefaultTreeModel(new PatchedDefaultMutableTreeNode()));
49 TreeToolTipHandler
.install(this);
50 TreeUtil
.installActions(this);
52 new TreeSpeedSearch(this);
54 addMouseListener(myMouseListener
);
55 setCellRenderer(new SimpleNodeRenderer());
58 getSelectionModel().setSelectionMode(TreeSelectionModel
.DISCONTIGUOUS_TREE_SELECTION
);
59 ToolTipManager
.sharedInstance().registerComponent(this);
61 getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
62 public void valueChanged(TreeSelectionEvent e
) {
63 if (!myIgnoreSelectionChange
&& hasSingleSelection()) {
64 getNodeFor(getSelectionPath()).handleSelection(SimpleTree
.this);
69 addKeyListener(new KeyAdapter() {
70 public void keyPressed(KeyEvent e
) {
71 if (e
.getKeyCode() == KeyEvent
.VK_ENTER
&& hasSingleSelection()) {
72 handleDoubleClickOrEnter(getSelectionPath(), e
);
74 if (e
.getKeyCode() == KeyEvent
.VK_F2
&& e
.getModifiers() == 0) {
75 e
.consume(); // ignore start editing by F2
80 putClientProperty("JTree.lineStyle", "Angled");
81 setUI(new BasicTreeUI()); // In WindowsXP UI handles are not shown :(
84 public SimpleTree(TreeModel aModel
) {
89 public boolean accept(AbstractTreeBuilder builder
, SimpleNodeVisitor visitor
) {
90 final DefaultMutableTreeNode root
= (DefaultMutableTreeNode
) getModel().getRoot();
91 return visitDown(builder
, (SimpleNode
) root
.getUserObject(), visitor
);
94 public void setPopupGroup(ActionGroup aPopupGroup
, String aPlace
) {
95 myPopupGroup
= aPopupGroup
;
99 public SimpleNode
getNodeFor(int row
) {
100 return getNodeFor(getPathForRow(row
));
103 public SimpleNode
getNodeFor(TreePath aPath
) {
108 DefaultMutableTreeNode treeNode
= ((DefaultMutableTreeNode
) aPath
.getLastPathComponent());
109 if (treeNode
== null) {
113 final Object userObject
= treeNode
.getUserObject();
114 if (userObject
instanceof SimpleNode
) {
115 return (SimpleNode
) userObject
;
123 public TreePath
getPathFor(SimpleNode node
) {
124 final TreeNode nodeWithObject
= TreeUtil
.findNodeWithObject((DefaultMutableTreeNode
) getModel().getRoot(), node
);
125 if (nodeWithObject
!= null) {
126 return TreeUtil
.getPathFromRoot(nodeWithObject
);
132 public SimpleNode
getSelectedNode() {
133 if (isSelectionEmpty()) {
137 return getNodeFor(getSelectionPath());
140 public boolean isSelectionEmpty() {
141 final TreePath selection
= super.getSelectionPath();
142 if (selection
== null) {
146 return getNodeFor(selection
) == NULL_NODE
;
149 public SimpleNode
[] getSelectedNodesIfUniform() {
150 java
.util
.List
<SimpleNode
> result
= new ArrayList
<SimpleNode
>();
151 TreePath
[] selectionPaths
= getSelectionPaths();
152 if (selectionPaths
!= null) {
153 SimpleNode lastNode
= null;
154 for (TreePath selectionPath
: selectionPaths
) {
155 SimpleNode nodeFor
= getNodeFor(selectionPath
);
157 if (lastNode
!= null && lastNode
.getClass() != nodeFor
.getClass()) {
158 return new SimpleNode
[0];
165 return result
.toArray(new SimpleNode
[result
.size()]);
168 public void setSelectedNode(AbstractTreeBuilder builder
, SimpleNode node
, boolean expand
) {
169 final TreePath selected
= getSelectionPath();
170 if (selected
!= null) {
171 final SimpleNode selectedNode
= getNodeFor(selected
);
172 if (selectedNode
.equals(node
)) {
178 builder
.buildNodeForElement(node
);
181 final TreePath path
= getPathFor(node
);
182 setSelectionPath(path
);
183 scrollPathToVisible(path
);
186 private boolean visitDown(AbstractTreeBuilder builder
, final SimpleNode node
, SimpleNodeVisitor visitor
) {
187 if (visitor
.accept(node
)) {
191 final Object
[] children
= builder
.getTreeStructure().getChildElements(node
);
193 for (Object aChildren
: children
) {
194 if (visitDown(builder
, (SimpleNode
) aChildren
, visitor
)) {
201 protected void paintChildren(Graphics g
) {
202 super.paintChildren(g
);
203 g
.setColor(UIManager
.getColor("Tree.line"));
205 for (int row
= 0; row
< getRowCount(); row
++) {
206 final TreePath path
= getPathForRow(row
);
207 if (!getNodeFor(path
).shouldHaveSeparator()) {
211 final Rectangle bounds
= getRowBounds(row
);
212 int x
= (int) (bounds
.getMaxX());
213 int y
= (int) (bounds
.getY() + (bounds
.height
/ 2));
214 g
.drawLine(x
, y
, getWidth() - 5, y
);
218 public void doClick(int row
) {
219 setSelectionRow(row
);
224 public void cancelEditing() {
226 cellEditor
.cancelCellEditing();
231 public void editingStopped(ChangeEvent e
) {
235 public void editingCanceled(ChangeEvent e
) {
239 public JComponent
getEditorComponent() {
240 return myEditorComponent
;
243 public boolean isEditing() {
244 return myEditorComponent
!= null;
247 public TreePath
getEditingPath() {
249 return getPathForRow(myEditingRow
);
251 return super.getEditingPath();
254 public boolean isPathEditable(TreePath path
) {
258 protected void paintComponent(Graphics g
) {
259 super.paintComponent(g
);
262 Rectangle editedNodeRect
= getRowBounds(myEditingRow
);
263 if (editedNodeRect
== null) return;
264 g
.setColor(getBackground());
265 g
.fillRect(editedNodeRect
.x
, editedNodeRect
.y
, editedNodeRect
.width
, editedNodeRect
.height
);
269 public void setCellEditor(TreeCellEditor aCellEditor
) {
270 if (cellEditor
!= null) {
271 cellEditor
.removeCellEditorListener(this);
273 super.setCellEditor(aCellEditor
);
275 if (cellEditor
!= null) {
276 cellEditor
.addCellEditorListener(this);
280 public boolean stopEditing() {
281 boolean result
= isEditing();
283 if (!cellEditor
.stopCellEditing()) {
284 cellEditor
.cancelCellEditing();
291 public void startEditingAtPath(final TreePath path
) {
292 if (path
!= null && isVisible(path
)) {
294 if (isEditing() && !stopEditing()) {
302 private void startEditing(final TreePath path
) {
304 CellEditor editor
= getCellEditor();
305 if (editor
!= null && editor
.isCellEditable(null) && isPathEditable(path
)) {
307 getSelectionModel().clearSelection();
308 getSelectionModel().setSelectionPath(path
);
310 myEditingRow
= getRowForPath(path
);
311 myEditorComponent
= (JComponent
) getCellEditor().getTreeCellEditorComponent(this,
312 path
.getLastPathComponent(),
313 isPathSelected(path
), isExpanded(path
),
314 treeModel
.isLeaf(path
.getLastPathComponent()), myEditingRow
);
318 if (myEditorComponent
.isFocusable()) {
319 myEditorComponent
.requestFocusInWindow();
322 SwingUtilities
.invokeLater(new Runnable() {
324 scrollPathToVisible(path
);
330 private void putEditor(TreePath path
) {
332 add(myEditorComponent
);
333 Rectangle nodeBounds
= getPathBounds(path
);
334 Dimension editorPrefSize
= myEditorComponent
.getPreferredSize();
335 if (editorPrefSize
.height
> nodeBounds
.height
) {
336 nodeBounds
.y
-= (editorPrefSize
.height
- nodeBounds
.height
) / 2;
337 nodeBounds
.height
= editorPrefSize
.height
;
340 myEditorComponent
.setBounds(nodeBounds
);
342 myEscapePressed
= false;
345 private void doStopEditing() {
347 remove(myEditorComponent
);
348 myEditorComponent
= null;
349 setSelectionRow(myEditingRow
);
350 myEditingRow
= INVALID
;
355 public boolean isEscapePressed() {
356 return myEscapePressed
;
359 public void setEscapePressed() {
360 myEscapePressed
= true;
363 public void addSelectionPath(TreePath path
) {
364 myIgnoreSelectionChange
= true;
365 super.addSelectionPath(path
);
366 myIgnoreSelectionChange
= false;
369 public void addSelectionPaths(TreePath
[] path
) {
370 myIgnoreSelectionChange
= true;
371 super.addSelectionPaths(path
);
372 myIgnoreSelectionChange
= false;
375 private boolean isSelected(TreePath path
) {
376 TreePath
[] selectionPaths
= getSelectionPaths();
377 if (selectionPaths
!= null) {
378 for (TreePath selectionPath
: selectionPaths
) {
379 if (path
.equals(selectionPath
)) {
387 public boolean isMultipleSelection() {
388 return getSelectionRows() != null && getSelectionRows().length
> 1;
391 private void handleDoubleClickOrEnter(final TreePath treePath
, final InputEvent e
) {
392 Runnable runnable
= new Runnable() {
394 getNodeFor(treePath
).handleDoubleClickOrEnter(SimpleTree
.this, e
);
397 ApplicationManager
.getApplication().invokeLater(runnable
, ModalityState
.stateForComponent(this));
400 // TODO: move to some util?
401 public static boolean isDoubleClick(MouseEvent e
) {
402 return e
!= null && e
.getClickCount() > 0 && e
.getClickCount() % 2 == 0;
405 protected ActionGroup
getPopupGroup() {
409 protected void invokeContextMenu(final MouseEvent e
) {
410 SwingUtilities
.invokeLater(new Runnable() {
412 final ActionPopupMenu menu
= ActionManager
.getInstance().createActionPopupMenu(myPlace
, myPopupGroup
);
413 menu
.getComponent().show(e
.getComponent(), e
.getPoint().x
, e
.getPoint().y
);
418 private class MyMouseListener
extends MouseAdapter
{
419 public void mousePressed(MouseEvent e
) {
420 if (e
.isPopupTrigger()) {
423 else if (isDoubleClick(e
)) {
424 handleDoubleClickOrEnter(getClosestPathForLocation(e
.getX(), e
.getY()), e
);
426 if (!TreeWizardPopupImpl.isLocationInExpandControl(SimpleTree.this, getSelectionPath(), e.getX(), e.getY())) {
427 TreePath treePath = getClosestPathForLocation(e.getX(), e.getY());
428 handleDoubleClickOrEnter(treePath, e);
434 public void mouseReleased(MouseEvent e
) {
438 public void mouseClicked(MouseEvent e
) {
442 private void invokePopup(final MouseEvent e
) {
443 if (e
.isPopupTrigger() && insideTreeItemsArea(e
)) {
445 selectPathUnderCursorIfNeeded(e
);
447 if (myPopupGroup
!= null) {
448 invokeContextMenu(e
);
453 private void selectPathUnderCursorIfNeeded(final MouseEvent e
) {
454 TreePath pathForLocation
= getClosestPathForLocation(e
.getX(), e
.getY());
455 if (!isSelected(pathForLocation
)) {
456 setSelectionPath(pathForLocation
);
460 private boolean insideTreeItemsArea(MouseEvent e
) {
461 Rectangle rowBounds
= getRowBounds(getRowCount() - 1);
462 if (rowBounds
== null) {
465 double lastItemBottomLine
= rowBounds
.getMaxY();
466 return e
.getY() <= lastItemBottomLine
;
470 public boolean select(AbstractTreeBuilder aBuilder
, final SimpleNodeVisitor aVisitor
, boolean shouldExpand
) {
471 final SimpleNode
[] found
= new SimpleNode
[1];
472 boolean wasFound
= accept(aBuilder
, new SimpleNodeVisitor() {
473 public boolean accept(SimpleNode simpleNode
) {
474 if (aVisitor
.accept(simpleNode
)) {
475 found
[0] = simpleNode
;
485 //debugTree(aBuilder);
487 setSelectedNode(aBuilder
, found
[0], shouldExpand
);
493 private void debugTree(AbstractTreeBuilder aBuilder
) {
494 TreeUtil
.traverseDepth((TreeNode
) aBuilder
.getTree().getModel().getRoot(), new TreeUtil
.Traverse() {
495 public boolean accept(Object node
) {
496 System
.out
.println("Node: " + node
);
502 private boolean hasSingleSelection() {
503 return !isSelectionEmpty() && getSelectionPaths().length
== 1;
506 public DefaultTreeModel
getBuilderModel() {
507 return (DefaultTreeModel
) getModel();
510 public Dimension
getPreferredScrollableViewportSize() {
511 return super.getPreferredSize();
514 public SimpleNodeRenderer
getRenderer() {
515 return (SimpleNodeRenderer
) getCellRenderer();
518 public String
toString() {
519 return getClass().getName() + '#' + System
.identityHashCode(this);
522 public final void setMinSizeInRows(int rows
) {
523 myMinHeightInRows
= rows
;
526 public Dimension
getMinimumSize() {
527 Dimension superSize
= super.getMinimumSize();
529 if (myMinHeightInRows
== -1) return superSize
;
530 int rowCount
= getRowCount();
531 if (rowCount
== 0) return superSize
;
533 double rowHeight
= getRowBounds(0).getHeight();
534 return new Dimension(superSize
.width
, (int) (rowHeight
* myMinHeightInRows
));