jsf navigation graph
[fedora-idea.git] / treeStructure / src / jetbrains / fabrique / ui / treeStructure / SimpleTree.java
blob9b76a0a94177dfef5297ce2673fdaa4395e2661b
1 /*
2 * Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
3 * Use is subject to license terms.
4 */
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;
18 import javax.swing.*;
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.*;
25 import java.awt.*;
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();
38 // From FTree:
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;
47 public SimpleTree() {
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());
57 setEditable(false);
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);
67 });
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
78 });
80 putClientProperty("JTree.lineStyle", "Angled");
81 setUI(new BasicTreeUI()); // In WindowsXP UI handles are not shown :(
84 public SimpleTree(TreeModel aModel) {
85 this();
86 setModel(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;
96 myPlace = aPlace;
99 public SimpleNode getNodeFor(int row) {
100 return getNodeFor(getPathForRow(row));
103 public SimpleNode getNodeFor(TreePath aPath) {
104 if (aPath == null) {
105 return NULL_NODE;
108 DefaultMutableTreeNode treeNode = ((DefaultMutableTreeNode) aPath.getLastPathComponent());
109 if (treeNode == null) {
110 return NULL_NODE;
113 final Object userObject = treeNode.getUserObject();
114 if (userObject instanceof SimpleNode) {
115 return (SimpleNode) userObject;
117 else {
118 return NULL_NODE;
122 @Nullable
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);
128 return null;
131 @Nullable
132 public SimpleNode getSelectedNode() {
133 if (isSelectionEmpty()) {
134 return null;
137 return getNodeFor(getSelectionPath());
140 public boolean isSelectionEmpty() {
141 final TreePath selection = super.getSelectionPath();
142 if (selection == null) {
143 return true;
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];
161 result.add(nodeFor);
162 lastNode = nodeFor;
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)) {
173 return;
177 if (expand) {
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)) {
188 return true;
191 final Object[] children = builder.getTreeStructure().getChildElements(node);
193 for (Object aChildren : children) {
194 if (visitDown(builder, (SimpleNode) aChildren, visitor)) {
195 return true;
198 return false;
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()) {
208 continue;
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);
222 // From FTree:
224 public void cancelEditing() {
225 if (isEditing()) {
226 cellEditor.cancelCellEditing();
227 doStopEditing();
231 public void editingStopped(ChangeEvent e) {
232 doStopEditing();
235 public void editingCanceled(ChangeEvent e) {
236 doStopEditing();
239 public JComponent getEditorComponent() {
240 return myEditorComponent;
243 public boolean isEditing() {
244 return myEditorComponent != null;
247 public TreePath getEditingPath() {
248 if (isEditing()) {
249 return getPathForRow(myEditingRow);
251 return super.getEditingPath();
254 public boolean isPathEditable(TreePath path) {
255 return true;
258 protected void paintComponent(Graphics g) {
259 super.paintComponent(g);
261 if (isEditing()) {
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();
282 if (result) {
283 if (!cellEditor.stopCellEditing()) {
284 cellEditor.cancelCellEditing();
286 doStopEditing();
288 return result;
291 public void startEditingAtPath(final TreePath path) {
292 if (path != null && isVisible(path)) {
294 if (isEditing() && !stopEditing()) {
295 return;
298 startEditing(path);
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);
316 putEditor(path);
318 if (myEditorComponent.isFocusable()) {
319 myEditorComponent.requestFocusInWindow();
322 SwingUtilities.invokeLater(new Runnable() {
323 public void run() {
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() {
346 if (isEditing()) {
347 remove(myEditorComponent);
348 myEditorComponent = null;
349 setSelectionRow(myEditingRow);
350 myEditingRow = INVALID;
351 repaint();
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)) {
380 return true;
384 return false;
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() {
393 public void run() {
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() {
406 return myPopupGroup;
409 protected void invokeContextMenu(final MouseEvent e) {
410 SwingUtilities.invokeLater(new Runnable() {
411 public void run() {
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()) {
421 invokePopup(e);
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) {
435 invokePopup(e);
438 public void mouseClicked(MouseEvent e) {
439 invokePopup(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) {
463 return false;
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;
476 return true;
479 return false;
483 if (wasFound) {
485 //debugTree(aBuilder);
487 setSelectedNode(aBuilder, found[0], shouldExpand);
490 return wasFound;
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);
497 return true;
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));