001 /*
002 * $Id: JXTree.java 3401 2009-07-22 18:09:08Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021
022 package org.jdesktop.swingx;
023
024 import java.applet.Applet;
025 import java.awt.Color;
026 import java.awt.Component;
027 import java.awt.KeyboardFocusManager;
028 import java.awt.Window;
029 import java.awt.event.ActionEvent;
030 import java.beans.PropertyChangeEvent;
031 import java.beans.PropertyChangeListener;
032 import java.util.Hashtable;
033 import java.util.Vector;
034 import java.util.logging.Logger;
035
036 import javax.swing.Action;
037 import javax.swing.ActionMap;
038 import javax.swing.CellEditor;
039 import javax.swing.Icon;
040 import javax.swing.JComponent;
041 import javax.swing.JPopupMenu;
042 import javax.swing.JTree;
043 import javax.swing.KeyStroke;
044 import javax.swing.SwingUtilities;
045 import javax.swing.UIManager;
046 import javax.swing.event.CellEditorListener;
047 import javax.swing.event.ChangeEvent;
048 import javax.swing.event.ChangeListener;
049 import javax.swing.event.TreeModelEvent;
050 import javax.swing.event.TreeModelListener;
051 import javax.swing.plaf.basic.BasicTreeUI;
052 import javax.swing.tree.DefaultTreeCellRenderer;
053 import javax.swing.tree.TreeCellRenderer;
054 import javax.swing.tree.TreeModel;
055 import javax.swing.tree.TreeNode;
056 import javax.swing.tree.TreePath;
057
058 import org.jdesktop.swingx.decorator.ComponentAdapter;
059 import org.jdesktop.swingx.decorator.CompoundHighlighter;
060 import org.jdesktop.swingx.decorator.Highlighter;
061 import org.jdesktop.swingx.decorator.UIDependent;
062 import org.jdesktop.swingx.renderer.StringValue;
063 import org.jdesktop.swingx.renderer.StringValues;
064 import org.jdesktop.swingx.rollover.RolloverProducer;
065 import org.jdesktop.swingx.rollover.RolloverRenderer;
066 import org.jdesktop.swingx.rollover.TreeRolloverController;
067 import org.jdesktop.swingx.rollover.TreeRolloverProducer;
068 import org.jdesktop.swingx.search.SearchFactory;
069 import org.jdesktop.swingx.search.Searchable;
070 import org.jdesktop.swingx.search.TreeSearchable;
071 import org.jdesktop.swingx.tree.DefaultXTreeCellEditor;
072 import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer;
073
074
075 /**
076 * Enhanced Tree component with support for SwingX rendering, highlighting,
077 * rollover and search functionality.
078 * <p>
079 *
080 * <h2>Rendering and Highlighting</h2>
081 *
082 * As all SwingX collection views, a JXTree is a HighlighterClient (PENDING JW:
083 * formally define and implement, like in AbstractTestHighlighter), that is it
084 * provides consistent api to add and remove Highlighters which can visually
085 * decorate the rendering component.
086 * <p>
087 *
088 * <pre><code>
089 *
090 * JXTree tree = new JXTree(new FileSystemModel());
091 * // use system file icons and name to render
092 * tree.setCellRenderer(new DefaultTreeRenderer(IconValues.FILE_ICON,
093 * StringValues.FILE_NAME));
094 * // highlight condition: file modified after a date
095 * HighlightPredicate predicate = new HighlightPredicate() {
096 * public boolean isHighlighted(Component renderer,
097 * ComponentAdapter adapter) {
098 * File file = getUserObject(adapter.getValue());
099 * return file != null ? lastWeek < file.lastModified : false;
100 * }
101 * };
102 * // highlight with foreground color
103 * tree.addHighlighter(new ColorHighlighter(predicate, null, Color.RED);
104 *
105 * </code></pre>
106 *
107 * <i>Note:</i> for full functionality, a DefaultTreeRenderer must be installed
108 * as TreeCellRenderer. This is not done by default, because there are
109 * unresolved issues when editing. PENDING JW: still? Check!
110 *
111 * <i>Note:</i> to support the highlighting this implementation wraps the
112 * TreeCellRenderer set by client code with a DelegatingRenderer which applies
113 * the Highlighter after delegating the default configuration to the wrappee. As
114 * a side-effect, getCellRenderer does return the wrapper instead of the custom
115 * renderer. To access the latter, client code must call getWrappedCellRenderer.
116 * <p>
117 * <h2>Rollover</h2>
118 *
119 * As all SwingX collection views, a JXTree supports per-cell rollover. If
120 * enabled, the component fires rollover events on enter/exit of a cell which by
121 * default is promoted to the renderer if it implements RolloverRenderer, that
122 * is simulates live behaviour. The rollover events can be used by client code
123 * as well, f.i. to decorate the rollover row using a Highlighter.
124 *
125 * <pre><code>
126 *
127 * JXTree tree = new JXTree();
128 * tree.setRolloverEnabled(true);
129 * tree.setCellRenderer(new DefaultTreeRenderer());
130 * tree.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW,
131 * null, Color.RED);
132 *
133 * </code></pre>
134 *
135 *
136 * <h2>Search</h2>
137 *
138 * As all SwingX collection views, a JXTree is searchable. A search action is
139 * registered in its ActionMap under the key "find". The default behaviour is to
140 * ask the SearchFactory to open a search component on this component. The
141 * default keybinding is retrieved from the SearchFactory, typically ctrl-f (or
142 * cmd-f for Mac). Client code can register custom actions and/or bindings as
143 * appropriate.
144 * <p>
145 *
146 * JXTree provides api to vend a renderer-controlled String representation of
147 * cell content. This allows the Searchable and Highlighters to use WYSIWYM
148 * (What-You-See-Is-What-You-Match), that is pattern matching against the actual
149 * string as seen by the user.
150 *
151 * <h2>Miscellaneous</h2>
152 *
153 * <ul>
154 * <li> Improved usability for editing: guarantees that the tree is the
155 * focusOwner if editing terminated by user gesture and guards against data
156 * corruption if focusLost while editing
157 * <li> Access methods for selection colors, for consistency with JXTable,
158 * JXList
159 * <li> Convenience methods and actions to expand, collapse all nodes
160 * </ul>
161 *
162 * @author Ramesh Gupta
163 * @author Jeanette Winzenburg
164 *
165 * @see org.jdesktop.swingx.renderer.DefaultTreeRenderer
166 * @see org.jdesktop.swingx.renderer.ComponentProvider
167 * @see org.jdesktop.swingx.decorator.Highlighter
168 * @see org.jdesktop.swingx.decorator.HighlightPredicate
169 * @see org.jdesktop.swingx.search.SearchFactory
170 * @see org.jdesktop.swingx.search.Searchable
171 *
172 */
173 public class JXTree extends JTree {
174 @SuppressWarnings("unused")
175 private static final Logger LOG = Logger.getLogger(JXTree.class.getName());
176
177
178 /** Empty int array used in getSelectedRows(). */
179 private static final int[] EMPTY_INT_ARRAY = new int[0];
180 /** Empty TreePath used in getSelectedPath() if selection empty. */
181 private static final TreePath[] EMPTY_TREEPATH_ARRAY = new TreePath[0];
182
183 /** Collection of active Highlighters. */
184 protected CompoundHighlighter compoundHighlighter;
185 /** Listener to changes of Highlighters in collection. */
186 private ChangeListener highlighterChangeListener;
187
188 /** Wrapper around the installed renderer, needed to support Highlighters. */
189 private DelegatingRenderer delegatingRenderer;
190
191 /**
192 * The RolloverProducer used if rollover is enabled.
193 */
194 private RolloverProducer rolloverProducer;
195
196 /**
197 * The RolloverController used if rollover is enabled.
198 */
199 private TreeRolloverController<JXTree> linkController;
200
201 private boolean overwriteIcons;
202 private Searchable searchable;
203
204 // hacks around core focus issues around editing.
205 /**
206 * The propertyChangeListener responsible for terminating
207 * edits if focus lost.
208 */
209 private CellEditorRemover editorRemover;
210 /**
211 * The CellEditorListener responsible to force the
212 * focus back to the tree after terminating edits.
213 */
214 private CellEditorListener editorListener;
215
216 /** Color of selected foreground. Added for consistent api across collection components. */
217 private Color selectionForeground;
218 /** Color of selected background. Added for consistent api across collection components. */
219 private Color selectionBackground;
220
221
222
223 /**
224 * Constructs a <code>JXTree</code> with a sample model. The default model
225 * used by this tree defines a leaf node as any node without children.
226 */
227 public JXTree() {
228 init();
229 }
230
231 /**
232 * Constructs a <code>JXTree</code> with each element of the specified array
233 * as the child of a new root node which is not displayed. By default, this
234 * tree defines a leaf node as any node without children.
235 *
236 * This version of the constructor simply invokes the super class version
237 * with the same arguments.
238 *
239 * @param value an array of objects that are children of the root.
240 */
241 public JXTree(Object[] value) {
242 super(value);
243 init();
244 }
245
246 /**
247 * Constructs a <code>JXTree</code> with each element of the specified
248 * Vector as the child of a new root node which is not displayed.
249 * By default, this tree defines a leaf node as any node without children.
250 *
251 * This version of the constructor simply invokes the super class version
252 * with the same arguments.
253 *
254 * @param value an Vector of objects that are children of the root.
255 */
256 public JXTree(Vector value) {
257 super(value);
258 init();
259 }
260
261 /**
262 * Constructs a <code>JXTree</code> created from a Hashtable which does not
263 * display with root. Each value-half of the key/value pairs in the HashTable
264 * becomes a child of the new root node. By default, the tree defines a leaf
265 * node as any node without children.
266 *
267 * This version of the constructor simply invokes the super class version
268 * with the same arguments.
269 *
270 * @param value a Hashtable containing objects that are children of the root.
271 */
272 public JXTree(Hashtable value) {
273 super(value);
274 init();
275 }
276
277 /**
278 * Constructs a <code>JXTree</code> with the specified TreeNode as its root,
279 * which displays the root node. By default, the tree defines a leaf node as
280 * any node without children.
281 *
282 * This version of the constructor simply invokes the super class version
283 * with the same arguments.
284 *
285 * @param root root node of this tree
286 */
287 public JXTree(TreeNode root) {
288 super(root, false);
289 init();
290 }
291
292 /**
293 * Constructs a <code>JXTree</code> with the specified TreeNode as its root,
294 * which displays the root node and which decides whether a node is a leaf
295 * node in the specified manner.
296 *
297 * This version of the constructor simply invokes the super class version
298 * with the same arguments.
299 *
300 * @param root root node of this tree
301 * @param asksAllowsChildren if true, only nodes that do not allow children
302 * are leaf nodes; otherwise, any node without children is a leaf node;
303 * @see javax.swing.tree.DefaultTreeModel#asksAllowsChildren
304 */
305 public JXTree(TreeNode root, boolean asksAllowsChildren) {
306 super(root, asksAllowsChildren);
307 init();
308 }
309
310 /**
311 * Constructs an instance of <code>JXTree</code> which displays the root
312 * node -- the tree is created using the specified data model.
313 *
314 * This version of the constructor simply invokes the super class version
315 * with the same arguments.
316 *
317 * @param newModel
318 * the <code>TreeModel</code> to use as the data model
319 */
320 public JXTree(TreeModel newModel) {
321 super(newModel);
322 init();
323 }
324
325 /**
326 * Instantiats JXTree state which is new compared to super. Installs the
327 * Delegating renderer and editor, registers actions and keybindings.
328 *
329 * This must be called from each constructor.
330 */
331 private void init() {
332 // Issue #1061-swingx: renderer inconsistencies
333 // force setting of renderer
334 setCellRenderer(createDefaultCellRenderer());
335 // Issue #233-swingx: default editor not bidi-compliant
336 // manually install an enhanced TreeCellEditor which
337 // behaves slightly better in RtoL orientation.
338 // Issue #231-swingx: icons lost
339 // Anyway, need to install the editor manually because
340 // the default install in BasicTreeUI doesn't know about
341 // the DelegatingRenderer and therefore can't see
342 // the DefaultTreeCellRenderer type to delegate to.
343 // As a consequence, the icons are lost in the default
344 // setup.
345 // JW PENDING need to mimic ui-delegate default re-set?
346 // JW PENDING alternatively, cleanup and use DefaultXXTreeCellEditor in incubator
347 if (getWrappedCellRenderer() instanceof DefaultTreeCellRenderer) {
348 setCellEditor(new DefaultXTreeCellEditor(this, (DefaultTreeCellRenderer) getWrappedCellRenderer()));
349 }
350 // Register the actions that this class can handle.
351 ActionMap map = getActionMap();
352 map.put("expand-all", new Actions("expand-all"));
353 map.put("collapse-all", new Actions("collapse-all"));
354 map.put("find", createFindAction());
355
356 KeyStroke findStroke = SearchFactory.getInstance().getSearchAccelerator();
357 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
358 }
359
360 /**
361 * Listens to the model and updates the {@code expandedState} accordingly
362 * when nodes are removed, or changed.
363 * <p>
364 * This class will expand an invisible root when a child has been added to
365 * it.
366 *
367 * @author Karl George Schaefer
368 */
369 protected class XTreeModelHandler extends TreeModelHandler {
370 /**
371 * {@inheritDoc}
372 */
373 @Override
374 public void treeNodesInserted(TreeModelEvent e) {
375 TreePath path = e.getTreePath();
376
377 //fixes SwingX bug #612
378 if (path.getParentPath() == null && !isRootVisible() && isCollapsed(path)) {
379 //should this be wrapped in SwingUtilities.invokeLater?
380 expandPath(path);
381 }
382
383 super.treeNodesInserted(e);
384 }
385 }
386
387 /**
388 * {@inheritDoc}
389 */
390 @Override
391 protected TreeModelListener createTreeModelListener() {
392 return new XTreeModelHandler();
393 }
394
395 /**
396 * A small class which dispatches actions.
397 * TODO: Is there a way that we can make this static?
398 */
399 private class Actions extends UIAction {
400 Actions(String name) {
401 super(name);
402 }
403
404 public void actionPerformed(ActionEvent evt) {
405 if ("expand-all".equals(getName())) {
406 expandAll();
407 }
408 else if ("collapse-all".equals(getName())) {
409 collapseAll();
410 }
411 }
412 }
413
414
415 //-------------------- search support
416
417 /**
418 * Creates and returns the action to invoke on a find request.
419 *
420 * @return the action to invoke on a find request.
421 */
422 private Action createFindAction() {
423 return new UIAction("find") {
424 public void actionPerformed(ActionEvent e) {
425 doFind();
426 }
427 };
428 }
429
430 /**
431 * Starts a search on this Tree's visible nodes. This implementation asks the
432 * SearchFactory to open a find widget on itself.
433 */
434 protected void doFind() {
435 SearchFactory.getInstance().showFindInput(this, getSearchable());
436 }
437
438 /**
439 * Returns a Searchable for this component, guaranteed to be not null. This
440 * implementation lazily creates a TreeSearchable if necessary.
441 *
442 *
443 * @return a not-null Searchable for this component.
444 *
445 * @see #setSearchable(Searchable)
446 * @see org.jdesktop.swingx.search.TreeSearchable
447 */
448 public Searchable getSearchable() {
449 if (searchable == null) {
450 searchable = new TreeSearchable(this);
451 }
452 return searchable;
453 }
454
455 /**
456 * Sets the Searchable for this component. If null, a default
457 * Searchable will be created and used.
458 *
459 * @param searchable the Searchable to use for this component, may be null to
460 * indicate using the default.
461 *
462 * @see #getSearchable()
463 */
464 public void setSearchable(Searchable searchable) {
465 this.searchable = searchable;
466 }
467
468 /**
469 * Returns the string representation of the cell value at the given position.
470 *
471 * @param row the row index of the cell in view coordinates
472 * @return the string representation of the cell value as it will appear in the
473 * table.
474 */
475 public String getStringAt(int row) {
476 return getStringAt(getPathForRow(row));
477 }
478
479 /**
480 * Returns the string representation of the cell value at the given position.
481 *
482 * @param path the TreePath representing the node.
483 * @return the string representation of the cell value as it will appear in the
484 * table, or null if the path is not visible.
485 */
486 public String getStringAt(TreePath path) {
487 if (path == null) return null;
488 TreeCellRenderer renderer = getDelegatingRenderer().getDelegateRenderer();
489 if (renderer instanceof StringValue) {
490 return ((StringValue) renderer).getString(path.getLastPathComponent());
491 }
492 return StringValues.TO_STRING.getString(path.getLastPathComponent());
493 }
494
495
496 //--------------------- misc. new api and super overrides
497
498 /**
499 * Collapses all nodes in this tree.
500 */
501 public void collapseAll() {
502 for (int i = getRowCount() - 1; i >= 0 ; i--) {
503 collapseRow(i);
504 }
505 }
506
507 /**
508 * Expands all nodes in this tree.
509 */
510 public void expandAll() {
511 if (getRowCount() == 0) {
512 expandRoot();
513 }
514 for (int i = 0; i < getRowCount(); i++) {
515 expandRow(i);
516 }
517 }
518
519 /**
520 * Expands the root path if a TreeModel has been set, does nothing if not.
521 *
522 */
523 private void expandRoot() {
524 TreeModel model = getModel();
525 if (model != null && model.getRoot() != null) {
526 expandPath(new TreePath(model.getRoot()));
527 }
528 }
529
530 /**
531 * {@inheritDoc}
532 * <p>
533 *
534 * Overridden to always return a not-null array (following SwingX
535 * convention).
536 */
537 @Override
538 public int[] getSelectionRows() {
539 int[] rows = super.getSelectionRows();
540 return rows != null ? rows : EMPTY_INT_ARRAY;
541 }
542
543 /**
544 * {@inheritDoc}
545 * <p>
546 *
547 * Overridden to always return a not-null array (following SwingX
548 * convention).
549 */
550 @Override
551 public TreePath[] getSelectionPaths() {
552 TreePath[] paths = super.getSelectionPaths();
553 return paths != null ? paths : EMPTY_TREEPATH_ARRAY;
554 }
555
556 /**
557 * Returns the background color for selected cells.
558 *
559 * @return the <code>Color</code> used for the background of
560 * selected list items
561 * @see #setSelectionBackground
562 * @see #setSelectionForeground
563 */
564 public Color getSelectionBackground() {
565 return selectionBackground;
566 }
567
568 /**
569 * Returns the selection foreground color.
570 *
571 * @return the <code>Color</code> object for the foreground property
572 * @see #setSelectionForeground
573 * @see #setSelectionBackground
574 */
575 public Color getSelectionForeground() {
576 return selectionForeground;
577 }
578
579 /**
580 * Sets the foreground color for selected cells. Cell renderers
581 * can use this color to render text and graphics for selected
582 * cells.
583 * <p>
584 * The default value of this property is defined by the look
585 * and feel implementation.
586 * <p>
587 * This is a JavaBeans bound property.
588 *
589 * @param selectionForeground the <code>Color</code> to use in the foreground
590 * for selected list items
591 * @see #getSelectionForeground
592 * @see #setSelectionBackground
593 * @see #setForeground
594 * @see #setBackground
595 * @see #setFont
596 * @beaninfo
597 * bound: true
598 * attribute: visualUpdate true
599 * description: The foreground color of selected cells.
600 */
601 public void setSelectionForeground(Color selectionForeground) {
602 Object oldValue = getSelectionForeground();
603 this.selectionForeground = selectionForeground;
604 firePropertyChange("selectionForeground", oldValue, getSelectionForeground());
605 repaint();
606 }
607
608 /**
609 * Sets the background color for selected cells. Cell renderers
610 * can use this color to the fill selected cells.
611 * <p>
612 * The default value of this property is defined by the look
613 * and feel implementation.
614 * <p>
615 * This is a JavaBeans bound property.
616 *
617 * @param selectionBackground the <code>Color</code> to use for the
618 * background of selected cells
619 * @see #getSelectionBackground
620 * @see #setSelectionForeground
621 * @see #setForeground
622 * @see #setBackground
623 * @see #setFont
624 * @beaninfo
625 * bound: true
626 * attribute: visualUpdate true
627 * description: The background color of selected cells.
628 */
629 public void setSelectionBackground(Color selectionBackground) {
630 Object oldValue = getSelectionBackground();
631 this.selectionBackground = selectionBackground;
632 firePropertyChange("selectionBackground", oldValue, getSelectionBackground());
633 repaint();
634 }
635
636
637 //------------------------- update ui
638
639 /**
640 * {@inheritDoc} <p>
641 *
642 * Overridden to update selection background/foreground. Mimicking behaviour of
643 * ui-delegates for JTable, JList.
644 */
645 @Override
646 public void updateUI() {
647 uninstallSelectionColors();
648 super.updateUI();
649 installSelectionColors();
650 updateHighlighterUI();
651 updateRendererEditorUI();
652 }
653
654
655 /**
656 * Quick fix for #1060-swingx: icons lost on toggling LAF
657 */
658 protected void updateRendererEditorUI() {
659 if (getCellEditor() instanceof UIDependent) {
660 ((UIDependent) getCellEditor()).updateUI();
661 }
662 // PENDING JW: here we get the DelegationRenderer which is not (yet) UIDependent
663 // need to think about how to handle the per-tree icons
664 // anyway, the "real" renderer usually is updated accidentally
665 // don't know exactly why, added to the comp hierarchy?
666 // if (getCellRenderer() instanceof UIDependent) {
667 // ((UIDependent) getCellRenderer()).updateUI();
668 // }
669 }
670
671 /**
672 * Installs selection colors from UIManager. <p>
673 *
674 * <b>Note:</b> this should be done in the UI delegate.
675 */
676 private void installSelectionColors() {
677 if (SwingXUtilities.isUIInstallable(getSelectionBackground())) {
678 setSelectionBackground(UIManager.getColor("Tree.selectionBackground"));
679 }
680 if (SwingXUtilities.isUIInstallable(getSelectionForeground())) {
681 setSelectionForeground(UIManager.getColor("Tree.selectionForeground"));
682 }
683
684 }
685
686 /**
687 * Uninstalls selection colors. <p>
688 *
689 * <b>Note:</b> this should be done in the UI delegate.
690 */
691 private void uninstallSelectionColors() {
692 if (SwingXUtilities.isUIInstallable(getSelectionBackground())) {
693 setSelectionBackground(null);
694 }
695 if (SwingXUtilities.isUIInstallable(getSelectionForeground())) {
696 setSelectionForeground(null);
697 }
698 }
699
700 /**
701 * Updates highlighter after <code>updateUI</code> changes.
702 *
703 * @see org.jdesktop.swingx.decorator.UIDependent
704 */
705 protected void updateHighlighterUI() {
706 if (compoundHighlighter == null) return;
707 compoundHighlighter.updateUI();
708 }
709
710
711
712 //------------------------ Rollover support
713
714 /**
715 * Sets the property to enable/disable rollover support. If enabled, the list
716 * fires property changes on per-cell mouse rollover state, i.e.
717 * when the mouse enters/leaves a list cell. <p>
718 *
719 * This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell
720 * rendered by a JXHyperlink.<p>
721 *
722 * The default value is false.
723 *
724 * @param rolloverEnabled a boolean indicating whether or not the rollover
725 * functionality should be enabled.
726 *
727 * @see #isRolloverEnabled()
728 * @see #getLinkController()
729 * @see #createRolloverProducer()
730 * @see org.jdesktop.swingx.rollover.RolloverRenderer
731 */
732 public void setRolloverEnabled(boolean rolloverEnabled) {
733 boolean old = isRolloverEnabled();
734 if (rolloverEnabled == old) return;
735 if (rolloverEnabled) {
736 rolloverProducer = createRolloverProducer();
737 addMouseListener(rolloverProducer);
738 addMouseMotionListener(rolloverProducer);
739 getLinkController().install(this);
740 } else {
741 removeMouseListener(rolloverProducer);
742 removeMouseMotionListener(rolloverProducer);
743 rolloverProducer = null;
744 getLinkController().release();
745 }
746 firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
747 }
748
749 /**
750 * Returns a boolean indicating whether or not rollover support is enabled.
751 *
752 * @return a boolean indicating whether or not rollover support is enabled.
753 *
754 * @see #setRolloverEnabled(boolean)
755 */
756 public boolean isRolloverEnabled() {
757 return rolloverProducer != null;
758 }
759
760 /**
761 * Returns the RolloverController for this component. Lazyly creates the
762 * controller if necessary, that is the return value is guaranteed to be
763 * not null. <p>
764 *
765 * PENDING JW: rename to getRolloverController
766 *
767 * @return the RolloverController for this tree, guaranteed to be not null.
768 *
769 * @see #setRolloverEnabled(boolean)
770 * @see #createLinkController()
771 * @see org.jdesktop.swingx.rollover.RolloverController
772 */
773 protected TreeRolloverController<JXTree> getLinkController() {
774 if (linkController == null) {
775 linkController = createLinkController();
776 }
777 return linkController;
778 }
779
780 /**
781 * Creates and returns a RolloverController appropriate for this tree.
782 *
783 * @return a RolloverController appropriate for this tree.
784 *
785 * @see #getLinkController()
786 * @see org.jdesktop.swingx.rollover.RolloverController
787 */
788 protected TreeRolloverController<JXTree> createLinkController() {
789 return new TreeRolloverController<JXTree>();
790 }
791
792 /**
793 * Creates and returns the RolloverProducer to use with this tree.
794 * <p>
795 *
796 * @return <code>RolloverProducer</code> to use with this tree
797 *
798 * @see #setRolloverEnabled(boolean)
799 */
800 protected RolloverProducer createRolloverProducer() {
801 return new TreeRolloverProducer();
802 }
803
804
805 //----------------------- Highlighter api
806
807 /**
808 * Sets the <code>Highlighter</code>s to the table, replacing any old settings.
809 * None of the given Highlighters must be null.<p>
810 *
811 * This is a bound property. <p>
812 *
813 * Note: as of version #1.257 the null constraint is enforced strictly. To remove
814 * all highlighters use this method without param.
815 *
816 * @param highlighters zero or more not null highlighters to use for renderer decoration.
817 * @throws NullPointerException if array is null or array contains null values.
818 *
819 * @see #getHighlighters()
820 * @see #addHighlighter(Highlighter)
821 * @see #removeHighlighter(Highlighter)
822 *
823 */
824 public void setHighlighters(Highlighter... highlighters) {
825 Highlighter[] old = getHighlighters();
826 getCompoundHighlighter().setHighlighters(highlighters);
827 firePropertyChange("highlighters", old, getHighlighters());
828 }
829
830 /**
831 * Returns the <code>Highlighter</code>s used by this table.
832 * Maybe empty, but guarantees to be never null.
833 *
834 * @return the Highlighters used by this table, guaranteed to never null.
835 * @see #setHighlighters(Highlighter[])
836 */
837 public Highlighter[] getHighlighters() {
838 return getCompoundHighlighter().getHighlighters();
839 }
840
841 /**
842 * Appends a <code>Highlighter</code> to the end of the list of used
843 * <code>Highlighter</code>s. The argument must not be null.
844 * <p>
845 *
846 * @param highlighter the <code>Highlighter</code> to add, must not be null.
847 * @throws NullPointerException if <code>Highlighter</code> is null.
848 *
849 * @see #removeHighlighter(Highlighter)
850 * @see #setHighlighters(Highlighter[])
851 */
852 public void addHighlighter(Highlighter highlighter) {
853 Highlighter[] old = getHighlighters();
854 getCompoundHighlighter().addHighlighter(highlighter);
855 firePropertyChange("highlighters", old, getHighlighters());
856 }
857
858 /**
859 * Removes the given Highlighter. <p>
860 *
861 * Does nothing if the Highlighter is not contained.
862 *
863 * @param highlighter the Highlighter to remove.
864 * @see #addHighlighter(Highlighter)
865 * @see #setHighlighters(Highlighter...)
866 */
867 public void removeHighlighter(Highlighter highlighter) {
868 Highlighter[] old = getHighlighters();
869 getCompoundHighlighter().removeHighlighter(highlighter);
870 firePropertyChange("highlighters", old, getHighlighters());
871 }
872
873 /**
874 * Returns the CompoundHighlighter assigned to the table, null if none.
875 * PENDING: open up for subclasses again?.
876 *
877 * @return the CompoundHighlighter assigned to the table.
878 */
879 protected CompoundHighlighter getCompoundHighlighter() {
880 if (compoundHighlighter == null) {
881 compoundHighlighter = new CompoundHighlighter();
882 compoundHighlighter.addChangeListener(getHighlighterChangeListener());
883 }
884 return compoundHighlighter;
885 }
886
887 /**
888 * Returns the <code>ChangeListener</code> to use with highlighters. Lazily
889 * creates the listener.
890 *
891 * @return the ChangeListener for observing changes of highlighters,
892 * guaranteed to be <code>not-null</code>
893 */
894 protected ChangeListener getHighlighterChangeListener() {
895 if (highlighterChangeListener == null) {
896 highlighterChangeListener = createHighlighterChangeListener();
897 }
898 return highlighterChangeListener;
899 }
900
901 /**
902 * Creates and returns the ChangeListener observing Highlighters.
903 * <p>
904 * Here: repaints the table on receiving a stateChanged.
905 *
906 * @return the ChangeListener defining the reaction to changes of
907 * highlighters.
908 */
909 protected ChangeListener createHighlighterChangeListener() {
910 return new ChangeListener() {
911 public void stateChanged(ChangeEvent e) {
912 repaint();
913 }
914 };
915 }
916
917 /**
918 * Sets the Icon to use for the handle of an expanded node.<p>
919 *
920 * Note: this will only succeed if the current ui delegate is
921 * a BasicTreeUI otherwise it will do nothing.<p>
922 *
923 * PENDING JW: incomplete api (no getter) and not a bound property.
924 *
925 * @param expandedIcon the Icon to use for the handle of an expanded node.
926 */
927 public void setExpandedIcon(Icon expandedIcon) {
928 if (getUI() instanceof BasicTreeUI) {
929 ((BasicTreeUI) getUI()).setExpandedIcon(expandedIcon);
930 }
931 }
932
933 /**
934 * Sets the Icon to use for the handle of a collapsed node.
935 *
936 * Note: this will only succeed if the current ui delegate is
937 * a BasicTreeUI otherwise it will do nothing.
938 *
939 * PENDING JW: incomplete api (no getter) and not a bound property.
940 *
941 * @param collapsedIcon the Icon to use for the handle of a collapsed node.
942 */
943 public void setCollapsedIcon(Icon collapsedIcon) {
944 if (getUI() instanceof BasicTreeUI) {
945 ((BasicTreeUI) getUI()).setCollapsedIcon(collapsedIcon);
946 }
947 }
948
949 /**
950 * Sets the Icon to use for a leaf node.<p>
951 *
952 * Note: this will only succeed if current renderer is a
953 * DefaultTreeCellRenderer.<p>
954 *
955 * PENDING JW: this (all setXXIcon) is old api pulled up from the JXTreeTable.
956 * Need to review if we really want it - problematic if sharing the same
957 * renderer instance across different trees.
958 *
959 * PENDING JW: incomplete api (no getter) and not a bound property.<p>
960 *
961 * @param leafIcon the Icon to use for a leaf node.
962 */
963 public void setLeafIcon(Icon leafIcon) {
964 getDelegatingRenderer().setLeafIcon(leafIcon);
965 }
966
967 /**
968 * Sets the Icon to use for an open folder node.
969 *
970 * Note: this will only succeed if current renderer is a
971 * DefaultTreeCellRenderer.
972 *
973 * PENDING JW: incomplete api (no getter) and not a bound property.
974 *
975 * @param openIcon the Icon to use for an open folder node.
976 */
977 public void setOpenIcon(Icon openIcon) {
978 getDelegatingRenderer().setOpenIcon(openIcon);
979 }
980
981 /**
982 * Sets the Icon to use for a closed folder node.
983 *
984 * Note: this will only succeed if current renderer is a
985 * DefaultTreeCellRenderer.
986 *
987 * PENDING JW: incomplete api (no getter) and not a bound property.
988 *
989 * @param closedIcon the Icon to use for a closed folder node.
990 */
991 public void setClosedIcon(Icon closedIcon) {
992 getDelegatingRenderer().setClosedIcon(closedIcon);
993 }
994
995 /**
996 * Property to control whether per-tree icons should be
997 * copied to the renderer on setCellRenderer. <p>
998 *
999 * The default value is false.
1000 *
1001 * PENDING: should update the current renderer's icons when
1002 * setting to true?
1003 *
1004 * @param overwrite a boolean to indicate if the per-tree Icons should
1005 * be copied to the new renderer on setCellRenderer.
1006 *
1007 * @see #isOverwriteRendererIcons()
1008 * @see #setLeafIcon(Icon)
1009 * @see #setOpenIcon(Icon)
1010 * @see #setClosedIcon(Icon)
1011 */
1012 public void setOverwriteRendererIcons(boolean overwrite) {
1013 if (overwriteIcons == overwrite) return;
1014 boolean old = overwriteIcons;
1015 this.overwriteIcons = overwrite;
1016 firePropertyChange("overwriteRendererIcons", old, overwrite);
1017 }
1018
1019 /**
1020 * Returns a boolean indicating whether the per-tree icons should be
1021 * copied to the renderer on setCellRenderer.
1022 *
1023 * @return true if a TreeCellRenderer's icons will be overwritten with the
1024 * tree's Icons, false if the renderer's icons will be unchanged.
1025 *
1026 * @see #setOverwriteRendererIcons(boolean)
1027 * @see #setLeafIcon(Icon)
1028 * @see #setOpenIcon(Icon)
1029 * @see #setClosedIcon(Icon)
1030 *
1031 */
1032 public boolean isOverwriteRendererIcons() {
1033 return overwriteIcons;
1034 }
1035
1036 private DelegatingRenderer getDelegatingRenderer() {
1037 if (delegatingRenderer == null) {
1038 // only called once... to get hold of the default?
1039 delegatingRenderer = new DelegatingRenderer();
1040 }
1041 return delegatingRenderer;
1042 }
1043
1044 /**
1045 * Creates and returns the default cell renderer to use. Subclasses may
1046 * override to use a different type.
1047 * <p>
1048 *
1049 * This implementation returns a renderer of type
1050 * <code>DefaultTreeCellRenderer</code>. <b>Note:</b> Will be changed to
1051 * return a renderer of type <code>DefaultTreeRenderer</code>,
1052 * once WrappingProvider is reasonably stable.
1053 *
1054 * @return the default cell renderer to use with this tree.
1055 */
1056 protected TreeCellRenderer createDefaultCellRenderer() {
1057 // return new DefaultTreeCellRenderer();
1058 return new DefaultXTreeCellRenderer();
1059 }
1060
1061 /**
1062 * {@inheritDoc} <p>
1063 *
1064 * Overridden to return the delegating renderer which is wrapped around the
1065 * original to support highlighting. The returned renderer is of type
1066 * DelegatingRenderer and guaranteed to not-null<p>
1067 *
1068 * @see #setCellRenderer(TreeCellRenderer)
1069 * @see DelegatingRenderer
1070 */
1071 @Override
1072 public TreeCellRenderer getCellRenderer() {
1073 // PENDING JW: something wrong here - why exactly can't we return super?
1074 // not even if we force the initial setting in init?
1075 // return super.getCellRenderer();
1076 return getDelegatingRenderer();
1077 }
1078
1079 /**
1080 * Returns the renderer installed by client code or the default if none has
1081 * been set.
1082 *
1083 * @return the wrapped renderer.
1084 * @see #setCellRenderer(TreeCellRenderer)
1085 */
1086 public TreeCellRenderer getWrappedCellRenderer() {
1087 return getDelegatingRenderer().getDelegateRenderer();
1088 }
1089
1090 /**
1091 * {@inheritDoc} <p>
1092 *
1093 * Overridden to wrap the given renderer in a DelegatingRenderer to support
1094 * highlighting. <p>
1095 *
1096 * Note: the wrapping implies that the renderer returned from the getCellRenderer
1097 * is <b>not</b> the renderer as given here, but the wrapper. To access the original,
1098 * use <code>getWrappedCellRenderer</code>.
1099 *
1100 * @see #getWrappedCellRenderer()
1101 * @see #getCellRenderer()
1102 */
1103 @Override
1104 public void setCellRenderer(TreeCellRenderer renderer) {
1105 // PENDING: do something against recursive setting
1106 // == multiple delegation...
1107 getDelegatingRenderer().setDelegateRenderer(renderer);
1108 super.setCellRenderer(delegatingRenderer);
1109 // quick hack for #1061: renderer/editor inconsistent
1110 if ((renderer instanceof DefaultTreeCellRenderer) &&
1111 (getCellEditor() instanceof DefaultXTreeCellEditor)) {
1112 ((DefaultXTreeCellEditor) getCellEditor()).setRenderer((DefaultTreeCellRenderer) renderer);
1113 }
1114 }
1115
1116
1117 /**
1118 * A decorator for the original TreeCellRenderer. Needed to hook highlighters
1119 * after messaging the delegate.<p>
1120 *
1121 * PENDING JW: formally implement UIDependent?
1122 * PENDING JW: missing updateUI anyway (got lost when c&p from JXList ;-)
1123 * PENDING JW: missing override of updateUI in xtree ...
1124 */
1125 public class DelegatingRenderer implements TreeCellRenderer, RolloverRenderer {
1126 private Icon closedIcon = null;
1127 private Icon openIcon = null;
1128 private Icon leafIcon = null;
1129
1130 private TreeCellRenderer delegate;
1131
1132 /**
1133 * Instantiates a DelegatingRenderer with tree's default renderer as delegate.
1134 */
1135 public DelegatingRenderer() {
1136 this(null);
1137 initIcons(new DefaultTreeCellRenderer());
1138 }
1139
1140 /**
1141 * Instantiates a DelegatingRenderer with the given delegate. If the
1142 * delegate is null, the default is created via the list's factory method.
1143 *
1144 * @param delegate the delegate to use, if null the tree's default is
1145 * created and used.
1146 */
1147 public DelegatingRenderer(TreeCellRenderer delegate) {
1148 initIcons((DefaultTreeCellRenderer) (delegate instanceof DefaultTreeCellRenderer ?
1149 delegate : new DefaultTreeCellRenderer()));
1150 setDelegateRenderer(delegate);
1151 }
1152
1153 /**
1154 * initially sets the icons to the defaults as given
1155 * by a DefaultTreeCellRenderer.
1156 *
1157 * @param renderer
1158 */
1159 private void initIcons(DefaultTreeCellRenderer renderer) {
1160 closedIcon = renderer.getDefaultClosedIcon();
1161 openIcon = renderer.getDefaultOpenIcon();
1162 leafIcon = renderer.getDefaultLeafIcon();
1163 }
1164
1165 /**
1166 * Sets the delegate. If the
1167 * delegate is null, the default is created via the list's factory method.
1168 * Updates the folder/leaf icons.
1169 *
1170 * THINK: how to update? always override with this.icons, only
1171 * if renderer's icons are null, update this icons if they are not,
1172 * update all if only one is != null.... ??
1173 *
1174 * @param delegate the delegate to use, if null the list's default is
1175 * created and used.
1176 */
1177 public void setDelegateRenderer(TreeCellRenderer delegate) {
1178 if (delegate == null) {
1179 delegate = createDefaultCellRenderer();
1180 }
1181 this.delegate = delegate;
1182 updateIcons();
1183 }
1184
1185 /**
1186 * tries to set the renderers icons. Can succeed only if the
1187 * delegate is a DefaultTreeCellRenderer.
1188 * THINK: how to update? always override with this.icons, only
1189 * if renderer's icons are null, update this icons if they are not,
1190 * update all if only one is != null.... ??
1191 *
1192 */
1193 private void updateIcons() {
1194 if (!isOverwriteRendererIcons()) return;
1195 setClosedIcon(closedIcon);
1196 setOpenIcon(openIcon);
1197 setLeafIcon(leafIcon);
1198 }
1199
1200 public void setClosedIcon(Icon closedIcon) {
1201 if (delegate instanceof DefaultTreeCellRenderer) {
1202 ((DefaultTreeCellRenderer) delegate).setClosedIcon(closedIcon);
1203 }
1204 this.closedIcon = closedIcon;
1205 }
1206
1207 public void setOpenIcon(Icon openIcon) {
1208 if (delegate instanceof DefaultTreeCellRenderer) {
1209 ((DefaultTreeCellRenderer) delegate).setOpenIcon(openIcon);
1210 }
1211 this.openIcon = openIcon;
1212 }
1213
1214 public void setLeafIcon(Icon leafIcon) {
1215 if (delegate instanceof DefaultTreeCellRenderer) {
1216 ((DefaultTreeCellRenderer) delegate).setLeafIcon(leafIcon);
1217 }
1218 this.leafIcon = leafIcon;
1219 }
1220
1221 //--------------- TreeCellRenderer
1222
1223 /**
1224 * Returns the delegate.
1225 *
1226 * @return the delegate renderer used by this renderer, guaranteed to
1227 * not-null.
1228 */
1229 public TreeCellRenderer getDelegateRenderer() {
1230 return delegate;
1231 }
1232
1233 /**
1234 * {@inheritDoc} <p>
1235 *
1236 * Overridden to apply the highlighters, if any, after calling the delegate.
1237 * The decorators are not applied if the row is invalid.
1238 */
1239 public Component getTreeCellRendererComponent(JTree tree, Object value,
1240 boolean selected, boolean expanded, boolean leaf, int row,
1241 boolean hasFocus) {
1242 Component result = delegate.getTreeCellRendererComponent(tree,
1243 value, selected, expanded, leaf, row, hasFocus);
1244
1245 if ((compoundHighlighter != null) && (row < getRowCount())
1246 && (row >= 0)) {
1247 result = compoundHighlighter.highlight(result,
1248 getComponentAdapter(row));
1249 }
1250
1251 return result;
1252 }
1253
1254 // ------------------ RolloverRenderer
1255
1256 public boolean isEnabled() {
1257 return (delegate instanceof RolloverRenderer)
1258 && ((RolloverRenderer) delegate).isEnabled();
1259 }
1260
1261 public void doClick() {
1262 if (isEnabled()) {
1263 ((RolloverRenderer) delegate).doClick();
1264 }
1265 }
1266
1267 }
1268
1269
1270 //----------------------- edit
1271
1272 /**
1273 * {@inheritDoc} <p>
1274 * Overridden to fix focus issues with editors.
1275 * This method installs and updates the internal CellEditorRemover which
1276 * terminates ongoing edits if appropriate. Additionally, it
1277 * registers a CellEditorListener with the cell editor to grab the
1278 * focus back to tree, if appropriate.
1279 *
1280 * @see #updateEditorRemover()
1281 */
1282 @Override
1283 public void startEditingAtPath(TreePath path) {
1284 super.startEditingAtPath(path);
1285 if (isEditing()) {
1286 updateEditorListener();
1287 updateEditorRemover();
1288 }
1289 }
1290
1291
1292 /**
1293 * Hack to grab focus after editing.
1294 */
1295 private void updateEditorListener() {
1296 if (editorListener == null) {
1297 editorListener = new CellEditorListener() {
1298
1299 public void editingCanceled(ChangeEvent e) {
1300 terminated(e);
1301 }
1302
1303 /**
1304 * @param e
1305 */
1306 private void terminated(ChangeEvent e) {
1307 analyseFocus();
1308 ((CellEditor) e.getSource()).removeCellEditorListener(editorListener);
1309 }
1310
1311 public void editingStopped(ChangeEvent e) {
1312 terminated(e);
1313 }
1314
1315 };
1316 }
1317 getCellEditor().addCellEditorListener(editorListener);
1318
1319 }
1320
1321 /**
1322 * This is called from cell editor listener if edit terminated.
1323 * Trying to analyse if we should grab the focus back to the
1324 * tree after. Brittle ... we assume we are the first to
1325 * get the event, so we can analyse the hierarchy before the
1326 * editing component is removed.
1327 */
1328 protected void analyseFocus() {
1329 if (isFocusOwnerDescending()) {
1330 requestFocusInWindow();
1331 }
1332 }
1333
1334
1335 /**
1336 * Returns a boolean to indicate if the current focus owner
1337 * is descending from this table.
1338 * Returns false if not editing, otherwise walks the focusOwner
1339 * hierarchy, taking popups into account. <p>
1340 *
1341 * PENDING: copied from JXTable ... should be somewhere in a utility
1342 * class?
1343 *
1344 * @return a boolean to indicate if the current focus
1345 * owner is contained.
1346 */
1347 private boolean isFocusOwnerDescending() {
1348 if (!isEditing()) return false;
1349 Component focusOwner =
1350 KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1351 // PENDING JW: special casing to not fall through ... really wanted?
1352 if (focusOwner == null) return false;
1353 if (SwingXUtilities.isDescendingFrom(focusOwner, this)) return true;
1354 // same with permanent focus owner
1355 Component permanent =
1356 KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
1357 return SwingXUtilities.isDescendingFrom(permanent, this);
1358 }
1359
1360
1361
1362 /**
1363 * Overridden to release the CellEditorRemover, if any.
1364 */
1365 @Override
1366 public void removeNotify() {
1367 if (editorRemover != null) {
1368 editorRemover.release();
1369 editorRemover = null;
1370 }
1371 super.removeNotify();
1372 }
1373
1374 /**
1375 * Lazily creates and updates the internal CellEditorRemover.
1376 *
1377 *
1378 */
1379 private void updateEditorRemover() {
1380 if (editorRemover == null) {
1381 editorRemover = new CellEditorRemover();
1382 }
1383 editorRemover.updateKeyboardFocusManager();
1384 }
1385
1386 /** This class tracks changes in the keyboard focus state. It is used
1387 * when the JXTree is editing to determine when to terminate the edit.
1388 * If focus switches to a component outside of the JXTree, but in the
1389 * same window, this will terminate editing. The exact terminate
1390 * behaviour is controlled by the invokeStopEditing property.
1391 *
1392 * @see javax.swing.JTree#setInvokesStopCellEditing(boolean)
1393 *
1394 */
1395 public class CellEditorRemover implements PropertyChangeListener {
1396 /** the focusManager this is listening to. */
1397 KeyboardFocusManager focusManager;
1398
1399 public CellEditorRemover() {
1400 updateKeyboardFocusManager();
1401 }
1402
1403 /**
1404 * Updates itself to listen to the current KeyboardFocusManager.
1405 *
1406 */
1407 public void updateKeyboardFocusManager() {
1408 KeyboardFocusManager current = KeyboardFocusManager.getCurrentKeyboardFocusManager();
1409 setKeyboardFocusManager(current);
1410 }
1411
1412 /**
1413 * stops listening.
1414 *
1415 */
1416 public void release() {
1417 setKeyboardFocusManager(null);
1418 }
1419
1420 /**
1421 * Sets the focusManager this is listening to.
1422 * Unregisters/registers itself from/to the old/new manager,
1423 * respectively.
1424 *
1425 * @param current the KeyboardFocusManager to listen too.
1426 */
1427 private void setKeyboardFocusManager(KeyboardFocusManager current) {
1428 if (focusManager == current)
1429 return;
1430 KeyboardFocusManager old = focusManager;
1431 if (old != null) {
1432 old.removePropertyChangeListener("permanentFocusOwner", this);
1433 }
1434 focusManager = current;
1435 if (focusManager != null) {
1436 focusManager.addPropertyChangeListener("permanentFocusOwner",
1437 this);
1438 }
1439
1440 }
1441 public void propertyChange(PropertyChangeEvent ev) {
1442 if (!isEditing()) {
1443 return;
1444 }
1445
1446 Component c = focusManager.getPermanentFocusOwner();
1447 JXTree tree = JXTree.this;
1448 while (c != null) {
1449 if (c instanceof JPopupMenu) {
1450 c = ((JPopupMenu) c).getInvoker();
1451 } else {
1452
1453 if (c == tree) {
1454 // focus remains inside the table
1455 return;
1456 } else if ((c instanceof Window) ||
1457 (c instanceof Applet && c.getParent() == null)) {
1458 if (c == SwingUtilities.getRoot(tree)) {
1459 if (tree.getInvokesStopCellEditing()) {
1460 tree.stopEditing();
1461 }
1462 if (tree.isEditing()) {
1463 tree.cancelEditing();
1464 }
1465 }
1466 break;
1467 }
1468 c = c.getParent();
1469 }
1470 }
1471 }
1472 }
1473
1474 // ------------------ oldish String conversion api, no longer recommended
1475
1476 /**
1477 * {@inheritDoc} <p>
1478 *
1479 * Overridden to initialize the String conversion method of the model, if any.<p>
1480 * PENDING JW: remove - that is an outdated approach?
1481 */
1482 @Override
1483 public void setModel(TreeModel newModel) {
1484 super.setModel(newModel);
1485 }
1486
1487
1488
1489 //------------------------------- ComponentAdapter
1490 /**
1491 * @return the unconfigured ComponentAdapter.
1492 */
1493 protected ComponentAdapter getComponentAdapter() {
1494 if (dataAdapter == null) {
1495 dataAdapter = new TreeAdapter(this);
1496 }
1497 return dataAdapter;
1498 }
1499
1500 /**
1501 * Convenience to access a configured ComponentAdapter.
1502 * Note: the column index of the configured adapter is always 0.
1503 *
1504 * @param index the row index in view coordinates, must be valid.
1505 * @return the configured ComponentAdapter.
1506 */
1507 protected ComponentAdapter getComponentAdapter(int index) {
1508 ComponentAdapter adapter = getComponentAdapter();
1509 adapter.column = 0;
1510 adapter.row = index;
1511 return adapter;
1512 }
1513
1514 protected ComponentAdapter dataAdapter;
1515
1516 protected static class TreeAdapter extends ComponentAdapter {
1517 private final JXTree tree;
1518
1519 /**
1520 * Constructs a <code>TableCellRenderContext</code> for the specified
1521 * target component.
1522 *
1523 * @param component the target component
1524 */
1525 public TreeAdapter(JXTree component) {
1526 super(component);
1527 tree = component;
1528 }
1529
1530 public JXTree getTree() {
1531 return tree;
1532 }
1533
1534 /**
1535 * {@inheritDoc}
1536 */
1537 @Override
1538 public boolean hasFocus() {
1539 return tree.isFocusOwner() && (tree.getLeadSelectionRow() == row);
1540 }
1541
1542 /**
1543 * {@inheritDoc}
1544 */
1545 @Override
1546 public Object getValueAt(int row, int column) {
1547 TreePath path = tree.getPathForRow(row);
1548 return path.getLastPathComponent();
1549 }
1550
1551 /**
1552 * {@inheritDoc} <p>
1553 *
1554 * JXTree doesn't support filtering/sorting. This implies that
1555 * model and view coordinates are the same. So this method is
1556 * implemented to call getValueAt(row, column).
1557 *
1558 */
1559 @Override
1560 public Object getFilteredValueAt(int row, int column) {
1561 /** TODO: Implement filtering */
1562 return getValueAt(row, column);
1563 }
1564
1565
1566 /**
1567 * {@inheritDoc} <p>
1568 *
1569 * JXTree doesn't support filtering/sorting. This implies that
1570 * model and view coordinates are the same. So this method is
1571 * implemented to call getValueAt(row, column).
1572 *
1573 */
1574 @Override
1575 public Object getValue() {
1576 return getValueAt(row, column);
1577 }
1578
1579
1580 /**
1581 * {@inheritDoc}
1582 */
1583 @Override
1584 public String getFilteredStringAt(int row, int column) {
1585 return tree.getStringAt(row);
1586 }
1587
1588 /**
1589 * {@inheritDoc}
1590 */
1591 @Override
1592 public String getString() {
1593 return tree.getStringAt(row);
1594 }
1595
1596 /**
1597 * {@inheritDoc}
1598 */
1599 @Override
1600 public String getStringAt(int row, int column) {
1601 return tree.getStringAt(row);
1602 }
1603
1604 /**
1605 * {@inheritDoc}
1606 */
1607 @Override
1608 public boolean isEditable() {
1609 //this is not as robust as JXTable; should it be? -- kgs
1610 return tree.isPathEditable(tree.getPathForRow(row));
1611 }
1612
1613 /**
1614 * {@inheritDoc}
1615 */
1616 @Override
1617 public boolean isSelected() {
1618 return tree.isRowSelected(row);
1619 }
1620
1621 /**
1622 * {@inheritDoc}
1623 */
1624 @Override
1625 public boolean isExpanded() {
1626 return tree.isExpanded(row);
1627 }
1628
1629 /**
1630 * {@inheritDoc}
1631 */
1632 @Override
1633 public int getDepth() {
1634 return tree.getPathForRow(row).getPathCount() - 1;
1635 }
1636
1637 /**
1638 * {@inheritDoc}
1639 */
1640 @Override
1641 public boolean isHierarchical() {
1642 return true;
1643 }
1644
1645 /**
1646 * {@inheritDoc}
1647 */
1648 @Override
1649 public boolean isLeaf() {
1650 return tree.getModel().isLeaf(getValue());
1651 }
1652
1653 /**
1654 * {@inheritDoc}
1655 */
1656 @Override
1657 public boolean isCellEditable(int row, int column) {
1658 return false; /** TODO: */
1659 }
1660 }
1661
1662
1663 }