Frames | No Frames |
1: /* DefaultTreeCellEditor.java -- 2: Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.tree; 40: 41: import java.awt.Color; 42: import java.awt.Component; 43: import java.awt.Container; 44: import java.awt.Dimension; 45: import java.awt.Font; 46: import java.awt.Graphics; 47: import java.awt.Rectangle; 48: import java.awt.event.ActionEvent; 49: import java.awt.event.ActionListener; 50: import java.awt.event.MouseEvent; 51: import java.io.IOException; 52: import java.io.ObjectInputStream; 53: import java.io.ObjectOutputStream; 54: import java.util.EventObject; 55: 56: import javax.swing.DefaultCellEditor; 57: import javax.swing.Icon; 58: import javax.swing.JTextField; 59: import javax.swing.JTree; 60: import javax.swing.SwingUtilities; 61: import javax.swing.Timer; 62: import javax.swing.UIManager; 63: import javax.swing.border.Border; 64: import javax.swing.event.CellEditorListener; 65: import javax.swing.event.EventListenerList; 66: import javax.swing.event.TreeSelectionEvent; 67: import javax.swing.event.TreeSelectionListener; 68: 69: /** 70: * Participates in the tree cell editing. 71: * 72: * @author Andrew Selkirk 73: * @author Audrius Meskauskas 74: */ 75: public class DefaultTreeCellEditor 76: implements ActionListener, TreeCellEditor, TreeSelectionListener 77: { 78: /** 79: * This container that appears on the tree during editing session. 80: * It contains the editing component displays various other editor - 81: * specific parts like editing icon. 82: */ 83: public class EditorContainer extends Container 84: { 85: /** 86: * Use v 1.5 serial version UID for interoperability. 87: */ 88: static final long serialVersionUID = 6470339600449699810L; 89: 90: /** 91: * Creates an <code>EditorContainer</code> object. 92: */ 93: public EditorContainer() 94: { 95: setLayout(null); 96: } 97: 98: /** 99: * This method only exists for API compatibility and is useless as it does 100: * nothing. It got probably introduced by accident. 101: */ 102: public void EditorContainer() 103: { 104: // Do nothing here. 105: } 106: 107: /** 108: * Overrides Container.paint to paint the node's icon and use the selection 109: * color for the background. 110: * 111: * @param g - 112: * the specified Graphics window 113: */ 114: public void paint(Graphics g) 115: { 116: // Paint editing icon. 117: if (editingIcon != null) 118: { 119: // From the previous version, the left margin is taken as half 120: // of the icon width. 121: int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2); 122: editingIcon.paintIcon(this, g, 0, y); 123: } 124: // Paint border. 125: Color c = getBorderSelectionColor(); 126: if (c != null) 127: { 128: g.setColor(c); 129: g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); 130: } 131: super.paint(g); 132: } 133: 134: /** 135: * Lays out this Container, moving the editor component to the left 136: * (leaving place for the icon). 137: */ 138: public void doLayout() 139: { 140: if (editingComponent != null) 141: { 142: editingComponent.getPreferredSize(); 143: editingComponent.setBounds(offset, 0, getWidth() - offset, 144: getHeight()); 145: } 146: } 147: 148: public Dimension getPreferredSize() 149: { 150: Dimension dim; 151: if (editingComponent != null) 152: { 153: dim = editingComponent.getPreferredSize(); 154: dim.width += offset + 5; 155: if (renderer != null) 156: { 157: Dimension r = renderer.getPreferredSize(); 158: dim.height = Math.max(dim.height, r.height); 159: } 160: if (editingIcon != null) 161: dim.height = Math.max(dim.height, editingIcon.getIconHeight()); 162: dim.width = Math.max(100, dim.width); 163: } 164: else 165: dim = new Dimension(0, 0); 166: return dim; 167: } 168: } 169: 170: /** 171: * The default text field, used in the editing sessions. 172: */ 173: public class DefaultTextField extends JTextField 174: { 175: /** 176: * Use v 1.5 serial version UID for interoperability. 177: */ 178: static final long serialVersionUID = -6629304544265300143L; 179: 180: /** 181: * The border of the text field. 182: */ 183: protected Border border; 184: 185: /** 186: * Creates a <code>DefaultTextField</code> object. 187: * 188: * @param aBorder the border to use 189: */ 190: public DefaultTextField(Border aBorder) 191: { 192: border = aBorder; 193: } 194: 195: /** 196: * Gets the font of this component. 197: * @return this component's font; if a font has not been set for 198: * this component, the font of its parent is returned (if the parent 199: * is not null, otherwise null is returned). 200: */ 201: public Font getFont() 202: { 203: Font font = super.getFont(); 204: if (font == null) 205: { 206: Component parent = getParent(); 207: if (parent != null) 208: return parent.getFont(); 209: return null; 210: } 211: return font; 212: } 213: 214: /** 215: * Returns the border of the text field. 216: * 217: * @return the border 218: */ 219: public Border getBorder() 220: { 221: return border; 222: } 223: 224: /** 225: * Overrides JTextField.getPreferredSize to return the preferred size 226: * based on current font, if set, or else use renderer's font. 227: * 228: * @return the Dimension of this textfield. 229: */ 230: public Dimension getPreferredSize() 231: { 232: Dimension size = super.getPreferredSize(); 233: if (renderer != null && DefaultTreeCellEditor.this.getFont() == null) 234: { 235: size.height = renderer.getPreferredSize().height; 236: } 237: return renderer.getPreferredSize(); 238: } 239: } 240: 241: private EventListenerList listenerList = new EventListenerList(); 242: 243: /** 244: * Editor handling the editing. 245: */ 246: protected TreeCellEditor realEditor; 247: 248: /** 249: * Renderer, used to get border and offsets from. 250: */ 251: protected DefaultTreeCellRenderer renderer; 252: 253: /** 254: * Editing container, will contain the editorComponent. 255: */ 256: protected Container editingContainer; 257: 258: /** 259: * Component used in editing, obtained from the editingContainer. 260: */ 261: protected transient Component editingComponent; 262: 263: /** 264: * As of Java 2 platform v1.4 this field should no longer be used. 265: * If you wish to provide similar behavior you should directly 266: * override isCellEditable. 267: */ 268: protected boolean canEdit; 269: 270: /** 271: * Used in editing. Indicates x position to place editingComponent. 272: */ 273: protected transient int offset; 274: 275: /** 276: * JTree instance listening too. 277: */ 278: protected transient JTree tree; 279: 280: /** 281: * Last path that was selected. 282: */ 283: protected transient TreePath lastPath; 284: 285: /** 286: * Used before starting the editing session. 287: */ 288: protected transient javax.swing.Timer timer; 289: 290: /** 291: * Row that was last passed into getTreeCellEditorComponent. 292: */ 293: protected transient int lastRow; 294: 295: /** 296: * True if the border selection color should be drawn. 297: */ 298: protected Color borderSelectionColor; 299: 300: /** 301: * Icon to use when editing. 302: */ 303: protected transient Icon editingIcon; 304: 305: /** 306: * Font to paint with, null indicates font of renderer is to be used. 307: */ 308: protected Font font; 309: 310: /** 311: * Constructs a DefaultTreeCellEditor object for a JTree using the 312: * specified renderer and a default editor. (Use this constructor 313: * for normal editing.) 314: * 315: * @param tree - a JTree object 316: * @param renderer - a DefaultTreeCellRenderer object 317: */ 318: public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) 319: { 320: this(tree, renderer, null); 321: } 322: 323: /** 324: * Constructs a DefaultTreeCellEditor object for a JTree using the specified 325: * renderer and the specified editor. (Use this constructor 326: * for specialized editing.) 327: * 328: * @param tree - a JTree object 329: * @param renderer - a DefaultTreeCellRenderer object 330: * @param editor - a TreeCellEditor object 331: */ 332: public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, 333: TreeCellEditor editor) 334: { 335: this.renderer = renderer; 336: realEditor = editor; 337: if (realEditor == null) 338: realEditor = createTreeCellEditor(); 339: editingContainer = createContainer(); 340: setTree(tree); 341: Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); 342: setBorderSelectionColor(c); 343: } 344: 345: /** 346: * writeObject 347: * 348: * @param value0 349: * TODO 350: * @exception IOException 351: * TODO 352: */ 353: private void writeObject(ObjectOutputStream value0) throws IOException 354: { 355: // TODO 356: } 357: 358: /** 359: * readObject 360: * @param value0 TODO 361: * @exception IOException TODO 362: * @exception ClassNotFoundException TODO 363: */ 364: private void readObject(ObjectInputStream value0) 365: throws IOException, ClassNotFoundException 366: { 367: // TODO 368: } 369: 370: /** 371: * Sets the color to use for the border. 372: * @param newColor - the new border color 373: */ 374: public void setBorderSelectionColor(Color newColor) 375: { 376: this.borderSelectionColor = newColor; 377: } 378: 379: /** 380: * Returns the color the border is drawn. 381: * @return Color 382: */ 383: public Color getBorderSelectionColor() 384: { 385: return borderSelectionColor; 386: } 387: 388: /** 389: * Sets the font to edit with. null indicates the renderers 390: * font should be used. This will NOT override any font you have 391: * set in the editor the receiver was instantied with. If null for 392: * an editor was passed in, a default editor will be created that 393: * will pick up this font. 394: * 395: * @param font - the editing Font 396: */ 397: public void setFont(Font font) 398: { 399: if (font != null) 400: this.font = font; 401: else 402: this.font = renderer.getFont(); 403: } 404: 405: /** 406: * Gets the font used for editing. 407: * 408: * @return the editing font 409: */ 410: public Font getFont() 411: { 412: return font; 413: } 414: 415: /** 416: * Configures the editor. Passed onto the realEditor. 417: * Sets an initial value for the editor. This will cause 418: * the editor to stopEditing and lose any partially edited value 419: * if the editor is editing when this method is called. 420: * Returns the component that should be added to the client's Component 421: * hierarchy. Once installed in the client's hierarchy this component will 422: * then be able to draw and receive user input. 423: * 424: * @param tree - the JTree that is asking the editor to edit; this parameter can be null 425: * @param value - the value of the cell to be edited 426: * @param isSelected - true is the cell is to be rendered with selection highlighting 427: * @param expanded - true if the node is expanded 428: * @param leaf - true if the node is a leaf node 429: * @param row - the row index of the node being edited 430: * 431: * @return the component for editing 432: */ 433: public Component getTreeCellEditorComponent(JTree tree, Object value, 434: boolean isSelected, 435: boolean expanded, 436: boolean leaf, int row) 437: { 438: setTree(tree); 439: lastRow = row; 440: determineOffset(tree, value, isSelected, expanded, leaf, row); 441: if (editingComponent != null) 442: editingContainer.remove(editingComponent); 443: 444: editingComponent = realEditor.getTreeCellEditorComponent(tree, value, 445: isSelected, 446: expanded, leaf, 447: row); 448: Font f = getFont(); 449: if (f == null) 450: { 451: if (renderer != null) 452: f = renderer.getFont(); 453: if (f == null) 454: f = tree.getFont(); 455: } 456: editingContainer.setFont(f); 457: prepareForEditing(); 458: return editingContainer; 459: } 460: 461: /** 462: * Returns the value currently being edited (requests it from the 463: * {@link #realEditor}. 464: * 465: * @return the value currently being edited 466: */ 467: public Object getCellEditorValue() 468: { 469: return realEditor.getCellEditorValue(); 470: } 471: 472: /** 473: * If the realEditor returns true to this message, prepareForEditing 474: * is messaged and true is returned. 475: * 476: * @param event - the event the editor should use to consider whether to 477: * begin editing or not 478: * @return true if editing can be started 479: */ 480: public boolean isCellEditable(EventObject event) 481: { 482: boolean ret = false; 483: boolean ed = false; 484: if (event != null) 485: { 486: if (event.getSource() instanceof JTree) 487: { 488: setTree((JTree) event.getSource()); 489: if (event instanceof MouseEvent) 490: { 491: MouseEvent me = (MouseEvent) event; 492: TreePath path = tree.getPathForLocation(me.getX(), me.getY()); 493: ed = lastPath != null && path != null && lastPath.equals(path); 494: if (path != null) 495: { 496: lastRow = tree.getRowForPath(path); 497: Object val = path.getLastPathComponent(); 498: boolean isSelected = tree.isRowSelected(lastRow); 499: boolean isExpanded = tree.isExpanded(path); 500: TreeModel m = tree.getModel(); 501: boolean isLeaf = m.isLeaf(val); 502: determineOffset(tree, val, isSelected, isExpanded, isLeaf, 503: lastRow); 504: } 505: } 506: } 507: } 508: if (! realEditor.isCellEditable(event)) 509: ret = false; 510: else 511: { 512: if (canEditImmediately(event)) 513: ret = true; 514: else if (ed && shouldStartEditingTimer(event)) 515: startEditingTimer(); 516: else if (timer != null && timer.isRunning()) 517: timer.stop(); 518: } 519: if (ret) 520: prepareForEditing(); 521: return ret; 522: 523: } 524: 525: /** 526: * Messages the realEditor for the return value. 527: * 528: * @param event - 529: * the event the editor should use to start editing 530: * @return true if the editor would like the editing cell to be selected; 531: * otherwise returns false 532: */ 533: public boolean shouldSelectCell(EventObject event) 534: { 535: return true; 536: } 537: 538: /** 539: * If the realEditor will allow editing to stop, the realEditor 540: * is removed and true is returned, otherwise false is returned. 541: * @return true if editing was stopped; false otherwise 542: */ 543: public boolean stopCellEditing() 544: { 545: boolean ret = false; 546: if (realEditor.stopCellEditing()) 547: { 548: finish(); 549: ret = true; 550: } 551: return ret; 552: } 553: 554: /** 555: * Messages cancelCellEditing to the realEditor and removes it 556: * from this instance. 557: */ 558: public void cancelCellEditing() 559: { 560: realEditor.cancelCellEditing(); 561: finish(); 562: } 563: 564: private void finish() 565: { 566: if (editingComponent != null) 567: editingContainer.remove(editingComponent); 568: editingComponent = null; 569: } 570: 571: /** 572: * Adds a <code>CellEditorListener</code> object to this editor. 573: * 574: * @param listener 575: * the listener to add 576: */ 577: public void addCellEditorListener(CellEditorListener listener) 578: { 579: realEditor.addCellEditorListener(listener); 580: } 581: 582: /** 583: * Removes a <code>CellEditorListener</code> object. 584: * 585: * @param listener the listener to remove 586: */ 587: public void removeCellEditorListener(CellEditorListener listener) 588: { 589: realEditor.removeCellEditorListener(listener); 590: } 591: 592: /** 593: * Returns all added <code>CellEditorListener</code> objects to this editor. 594: * 595: * @return an array of listeners 596: * 597: * @since 1.4 598: */ 599: public CellEditorListener[] getCellEditorListeners() 600: { 601: return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class); 602: } 603: 604: /** 605: * Resets lastPath. 606: * 607: * @param e - the event that characterizes the change. 608: */ 609: public void valueChanged(TreeSelectionEvent e) 610: { 611: if (tree != null) 612: { 613: if (tree.getSelectionCount() == 1) 614: lastPath = tree.getSelectionPath(); 615: else 616: lastPath = null; 617: } 618: // TODO: We really should do the following here, but can't due 619: // to buggy DefaultTreeSelectionModel. This selection model 620: // should only fire if the selection actually changes. 621: // if (timer != null) 622: // timer.stop(); 623: } 624: 625: /** 626: * Messaged when the timer fires. 627: * 628: * @param e the event that characterizes the action. 629: */ 630: public void actionPerformed(ActionEvent e) 631: { 632: if (tree != null && lastPath != null) 633: tree.startEditingAtPath(lastPath); 634: } 635: 636: /** 637: * Sets the tree currently editing for. This is needed to add a selection 638: * listener. 639: * 640: * @param newTree - 641: * the new tree to be edited 642: */ 643: protected void setTree(JTree newTree) 644: { 645: if (tree != newTree) 646: { 647: if (tree != null) 648: tree.removeTreeSelectionListener(this); 649: tree = newTree; 650: if (tree != null) 651: tree.addTreeSelectionListener(this); 652: 653: if (timer != null) 654: timer.stop(); 655: } 656: } 657: 658: /** 659: * Returns true if event is a MouseEvent and the click count is 1. 660: * 661: * @param event - the event being studied 662: * @return true if editing should start 663: */ 664: protected boolean shouldStartEditingTimer(EventObject event) 665: { 666: boolean ret = false; 667: if (event instanceof MouseEvent) 668: { 669: MouseEvent me = (MouseEvent) event; 670: ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 671: && inHitRegion(me.getX(), me.getY()); 672: } 673: return ret; 674: } 675: 676: /** 677: * Starts the editing timer (if one installed). 678: */ 679: protected void startEditingTimer() 680: { 681: if (timer == null) 682: { 683: timer = new Timer(1200, this); 684: timer.setRepeats(false); 685: } 686: timer.start(); 687: } 688: 689: /** 690: * Returns true if event is null, or it is a MouseEvent with 691: * a click count > 2 and inHitRegion returns true. 692: * 693: * @param event - the event being studied 694: * @return true if event is null, or it is a MouseEvent with 695: * a click count > 2 and inHitRegion returns true 696: */ 697: protected boolean canEditImmediately(EventObject event) 698: { 699: if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event). 700: getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(), 701: ((MouseEvent) event).getY()))) 702: return true; 703: return false; 704: } 705: 706: /** 707: * Returns true if the passed in location is a valid mouse location 708: * to start editing from. This is implemented to return false if x is 709: * less than or equal to the width of the icon and icon 710: * gap displayed by the renderer. In other words this returns true if 711: * the user clicks over the text part displayed by the renderer, and 712: * false otherwise. 713: * 714: * @param x - the x-coordinate of the point 715: * @param y - the y-coordinate of the point 716: * 717: * @return true if the passed in location is a valid mouse location 718: */ 719: protected boolean inHitRegion(int x, int y) 720: { 721: Rectangle bounds = tree.getPathBounds(lastPath); 722: return bounds.contains(x, y); 723: } 724: 725: /** 726: * determineOffset 727: * @param tree - 728: * @param value - 729: * @param isSelected - 730: * @param expanded - 731: * @param leaf - 732: * @param row - 733: */ 734: protected void determineOffset(JTree tree, Object value, boolean isSelected, 735: boolean expanded, boolean leaf, int row) 736: { 737: if (renderer != null) 738: { 739: if (leaf) 740: editingIcon = renderer.getLeafIcon(); 741: else if (expanded) 742: editingIcon = renderer.getOpenIcon(); 743: else 744: editingIcon = renderer.getClosedIcon(); 745: if (editingIcon != null) 746: offset = renderer.getIconTextGap() + editingIcon.getIconWidth(); 747: else 748: offset = renderer.getIconTextGap(); 749: } 750: else 751: { 752: editingIcon = null; 753: offset = 0; 754: } 755: } 756: 757: /** 758: * Invoked just before editing is to start. Will add the 759: * editingComponent to the editingContainer. 760: */ 761: protected void prepareForEditing() 762: { 763: if (editingComponent != null) 764: editingContainer.add(editingComponent); 765: } 766: 767: /** 768: * Creates the container to manage placement of editingComponent. 769: * 770: * @return the container to manage the placement of the editingComponent. 771: */ 772: protected Container createContainer() 773: { 774: return new DefaultTreeCellEditor.EditorContainer(); 775: } 776: 777: /** 778: * This is invoked if a TreeCellEditor is not supplied in the constructor. 779: * It returns a TextField editor. 780: * 781: * @return a new TextField editor 782: */ 783: protected TreeCellEditor createTreeCellEditor() 784: { 785: Border border = UIManager.getBorder("Tree.editorBorder"); 786: JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); 787: DefaultCellEditor editor = new DefaultCellEditor(tf); 788: editor.setClickCountToStart(1); 789: realEditor = editor; 790: return editor; 791: } 792: }