GNU Classpath (0.95) | |
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: * Helper field used to save the last path seen while the timer was 312: * running. 313: */ 314: private TreePath tPath; 315: 316: /** 317: * Constructs a DefaultTreeCellEditor object for a JTree using the 318: * specified renderer and a default editor. (Use this constructor 319: * for normal editing.) 320: * 321: * @param tree - a JTree object 322: * @param renderer - a DefaultTreeCellRenderer object 323: */ 324: public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) 325: { 326: this(tree, renderer, null); 327: } 328: 329: /** 330: * Constructs a DefaultTreeCellEditor object for a JTree using the specified 331: * renderer and the specified editor. (Use this constructor 332: * for specialized editing.) 333: * 334: * @param tree - a JTree object 335: * @param renderer - a DefaultTreeCellRenderer object 336: * @param editor - a TreeCellEditor object 337: */ 338: public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, 339: TreeCellEditor editor) 340: { 341: this.renderer = renderer; 342: realEditor = editor; 343: if (realEditor == null) 344: realEditor = createTreeCellEditor(); 345: editingContainer = createContainer(); 346: setTree(tree); 347: Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); 348: setBorderSelectionColor(c); 349: } 350: 351: /** 352: * Configures the editing component whenever it is null. 353: * 354: * @param tree the tree to configure to component for. 355: * @param renderer the renderer used to set up the nodes 356: * @param editor the editor used 357: */ 358: private void configureEditingComponent(JTree tree, 359: DefaultTreeCellRenderer renderer, 360: TreeCellEditor editor) 361: { 362: if (tree != null && lastPath != null) 363: { 364: Object val = lastPath.getLastPathComponent(); 365: boolean isLeaf = tree.getModel().isLeaf(val); 366: boolean expanded = tree.isExpanded(lastPath); 367: determineOffset(tree, val, true, expanded, isLeaf, lastRow); 368: 369: // set up icon 370: if (isLeaf) 371: renderer.setIcon(renderer.getLeafIcon()); 372: else if (expanded) 373: renderer.setIcon(renderer.getOpenIcon()); 374: else 375: renderer.setIcon(renderer.getClosedIcon()); 376: editingIcon = renderer.getIcon(); 377: 378: editingComponent = getTreeCellEditorComponent(tree, val, true, 379: expanded, isLeaf, lastRow); 380: } 381: } 382: 383: /** 384: * writeObject 385: * 386: * @param value0 387: * TODO 388: * @exception IOException 389: * TODO 390: */ 391: private void writeObject(ObjectOutputStream value0) throws IOException 392: { 393: // TODO 394: } 395: 396: /** 397: * readObject 398: * @param value0 TODO 399: * @exception IOException TODO 400: * @exception ClassNotFoundException TODO 401: */ 402: private void readObject(ObjectInputStream value0) 403: throws IOException, ClassNotFoundException 404: { 405: // TODO 406: } 407: 408: /** 409: * Sets the color to use for the border. 410: * @param newColor - the new border color 411: */ 412: public void setBorderSelectionColor(Color newColor) 413: { 414: this.borderSelectionColor = newColor; 415: } 416: 417: /** 418: * Returns the color the border is drawn. 419: * @return Color 420: */ 421: public Color getBorderSelectionColor() 422: { 423: return borderSelectionColor; 424: } 425: 426: /** 427: * Sets the font to edit with. null indicates the renderers 428: * font should be used. This will NOT override any font you have 429: * set in the editor the receiver was instantied with. If null for 430: * an editor was passed in, a default editor will be created that 431: * will pick up this font. 432: * 433: * @param font - the editing Font 434: */ 435: public void setFont(Font font) 436: { 437: if (font != null) 438: this.font = font; 439: else 440: this.font = renderer.getFont(); 441: } 442: 443: /** 444: * Gets the font used for editing. 445: * 446: * @return the editing font 447: */ 448: public Font getFont() 449: { 450: return font; 451: } 452: 453: /** 454: * Configures the editor. Passed onto the realEditor. 455: * Sets an initial value for the editor. This will cause 456: * the editor to stopEditing and lose any partially edited value 457: * if the editor is editing when this method is called. 458: * Returns the component that should be added to the client's Component 459: * hierarchy. Once installed in the client's hierarchy this component will 460: * then be able to draw and receive user input. 461: * 462: * @param tree - the JTree that is asking the editor to edit; this parameter can be null 463: * @param value - the value of the cell to be edited 464: * @param isSelected - true is the cell is to be rendered with selection highlighting 465: * @param expanded - true if the node is expanded 466: * @param leaf - true if the node is a leaf node 467: * @param row - the row index of the node being edited 468: * 469: * @return the component for editing 470: */ 471: public Component getTreeCellEditorComponent(JTree tree, Object value, 472: boolean isSelected, 473: boolean expanded, 474: boolean leaf, int row) 475: { 476: setTree(tree); 477: lastRow = row; 478: determineOffset(tree, value, isSelected, expanded, leaf, row); 479: if (editingComponent != null) 480: editingContainer.remove(editingComponent); 481: 482: editingComponent = realEditor.getTreeCellEditorComponent(tree, value, 483: isSelected, 484: expanded, leaf, 485: row); 486: Font f = getFont(); 487: if (f == null) 488: { 489: if (renderer != null) 490: f = renderer.getFont(); 491: if (f == null) 492: f = tree.getFont(); 493: } 494: editingContainer.setFont(f); 495: prepareForEditing(); 496: return editingContainer; 497: } 498: 499: /** 500: * Returns the value currently being edited (requests it from the 501: * {@link #realEditor}. 502: * 503: * @return the value currently being edited 504: */ 505: public Object getCellEditorValue() 506: { 507: return realEditor.getCellEditorValue(); 508: } 509: 510: /** 511: * If the realEditor returns true to this message, prepareForEditing 512: * is messaged and true is returned. 513: * 514: * @param event - the event the editor should use to consider whether to 515: * begin editing or not 516: * @return true if editing can be started 517: */ 518: public boolean isCellEditable(EventObject event) 519: { 520: boolean ret = false; 521: boolean ed = false; 522: if (event != null) 523: { 524: if (event.getSource() instanceof JTree) 525: { 526: setTree((JTree) event.getSource()); 527: if (event instanceof MouseEvent) 528: { 529: MouseEvent me = (MouseEvent) event; 530: TreePath path = tree.getPathForLocation(me.getX(), me.getY()); 531: ed = lastPath != null && path != null && lastPath.equals(path); 532: if (path != null) 533: { 534: lastRow = tree.getRowForPath(path); 535: Object val = path.getLastPathComponent(); 536: boolean isSelected = tree.isRowSelected(lastRow); 537: boolean isExpanded = tree.isExpanded(path); 538: TreeModel m = tree.getModel(); 539: boolean isLeaf = m.isLeaf(val); 540: determineOffset(tree, val, isSelected, isExpanded, isLeaf, 541: lastRow); 542: } 543: } 544: } 545: } 546: if (! realEditor.isCellEditable(event)) 547: ret = false; 548: else 549: { 550: if (canEditImmediately(event)) 551: ret = true; 552: else if (ed && shouldStartEditingTimer(event)) 553: startEditingTimer(); 554: else if (timer != null && timer.isRunning()) 555: timer.stop(); 556: } 557: if (ret) 558: prepareForEditing(); 559: return ret; 560: 561: } 562: 563: /** 564: * Messages the realEditor for the return value. 565: * 566: * @param event - 567: * the event the editor should use to start editing 568: * @return true if the editor would like the editing cell to be selected; 569: * otherwise returns false 570: */ 571: public boolean shouldSelectCell(EventObject event) 572: { 573: return true; 574: } 575: 576: /** 577: * If the realEditor will allow editing to stop, the realEditor 578: * is removed and true is returned, otherwise false is returned. 579: * @return true if editing was stopped; false otherwise 580: */ 581: public boolean stopCellEditing() 582: { 583: boolean ret = false; 584: if (realEditor.stopCellEditing()) 585: { 586: finish(); 587: ret = true; 588: } 589: return ret; 590: } 591: 592: /** 593: * Messages cancelCellEditing to the realEditor and removes it 594: * from this instance. 595: */ 596: public void cancelCellEditing() 597: { 598: realEditor.cancelCellEditing(); 599: finish(); 600: } 601: 602: private void finish() 603: { 604: if (editingComponent != null) 605: editingContainer.remove(editingComponent); 606: editingComponent = null; 607: } 608: 609: /** 610: * Adds a <code>CellEditorListener</code> object to this editor. 611: * 612: * @param listener 613: * the listener to add 614: */ 615: public void addCellEditorListener(CellEditorListener listener) 616: { 617: realEditor.addCellEditorListener(listener); 618: } 619: 620: /** 621: * Removes a <code>CellEditorListener</code> object. 622: * 623: * @param listener the listener to remove 624: */ 625: public void removeCellEditorListener(CellEditorListener listener) 626: { 627: realEditor.removeCellEditorListener(listener); 628: } 629: 630: /** 631: * Returns all added <code>CellEditorListener</code> objects to this editor. 632: * 633: * @return an array of listeners 634: * 635: * @since 1.4 636: */ 637: public CellEditorListener[] getCellEditorListeners() 638: { 639: return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class); 640: } 641: 642: /** 643: * Resets lastPath. 644: * 645: * @param e - the event that characterizes the change. 646: */ 647: public void valueChanged(TreeSelectionEvent e) 648: { 649: if (tree != null) 650: { 651: if (tree.getSelectionCount() == 1) 652: lastPath = tree.getSelectionPath(); 653: else 654: lastPath = null; 655: } 656: // TODO: We really should do the following here, but can't due 657: // to buggy DefaultTreeSelectionModel. This selection model 658: // should only fire if the selection actually changes. 659: // if (timer != null) 660: // timer.stop(); 661: } 662: 663: /** 664: * Messaged when the timer fires. 665: * 666: * @param e the event that characterizes the action. 667: */ 668: public void actionPerformed(ActionEvent e) 669: { 670: if (tree != null && lastPath != null) 671: tree.startEditingAtPath(lastPath); 672: } 673: 674: /** 675: * Sets the tree currently editing for. This is needed to add a selection 676: * listener. 677: * 678: * @param newTree - 679: * the new tree to be edited 680: */ 681: protected void setTree(JTree newTree) 682: { 683: if (tree != newTree) 684: { 685: if (tree != null) 686: tree.removeTreeSelectionListener(this); 687: tree = newTree; 688: if (tree != null) 689: tree.addTreeSelectionListener(this); 690: 691: if (timer != null) 692: timer.stop(); 693: } 694: } 695: 696: /** 697: * Returns true if event is a MouseEvent and the click count is 1. 698: * 699: * @param event - the event being studied 700: * @return true if editing should start 701: */ 702: protected boolean shouldStartEditingTimer(EventObject event) 703: { 704: boolean ret = false; 705: if (event instanceof MouseEvent) 706: { 707: MouseEvent me = (MouseEvent) event; 708: ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 709: && inHitRegion(me.getX(), me.getY()); 710: } 711: return ret; 712: } 713: 714: /** 715: * Starts the editing timer (if one installed). 716: */ 717: protected void startEditingTimer() 718: { 719: if (timer == null) 720: { 721: timer = new Timer(1200, this); 722: timer.setRepeats(false); 723: } 724: timer.start(); 725: } 726: 727: /** 728: * Returns true if event is null, or it is a MouseEvent with 729: * a click count > 2 and inHitRegion returns true. 730: * 731: * @param event - the event being studied 732: * @return true if event is null, or it is a MouseEvent with 733: * a click count > 2 and inHitRegion returns true 734: */ 735: protected boolean canEditImmediately(EventObject event) 736: { 737: if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event). 738: getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(), 739: ((MouseEvent) event).getY()))) 740: return true; 741: return false; 742: } 743: 744: /** 745: * Returns true if the passed in location is a valid mouse location 746: * to start editing from. This is implemented to return false if x is 747: * less than or equal to the width of the icon and icon 748: * gap displayed by the renderer. In other words this returns true if 749: * the user clicks over the text part displayed by the renderer, and 750: * false otherwise. 751: * 752: * @param x - the x-coordinate of the point 753: * @param y - the y-coordinate of the point 754: * 755: * @return true if the passed in location is a valid mouse location 756: */ 757: protected boolean inHitRegion(int x, int y) 758: { 759: Rectangle bounds = tree.getPathBounds(lastPath); 760: return bounds.contains(x, y); 761: } 762: 763: /** 764: * determineOffset 765: * @param tree - 766: * @param value - 767: * @param isSelected - 768: * @param expanded - 769: * @param leaf - 770: * @param row - 771: */ 772: protected void determineOffset(JTree tree, Object value, boolean isSelected, 773: boolean expanded, boolean leaf, int row) 774: { 775: if (renderer != null) 776: { 777: if (leaf) 778: editingIcon = renderer.getLeafIcon(); 779: else if (expanded) 780: editingIcon = renderer.getOpenIcon(); 781: else 782: editingIcon = renderer.getClosedIcon(); 783: if (editingIcon != null) 784: offset = renderer.getIconTextGap() + editingIcon.getIconWidth(); 785: else 786: offset = renderer.getIconTextGap(); 787: } 788: else 789: { 790: editingIcon = null; 791: offset = 0; 792: } 793: } 794: 795: /** 796: * Invoked just before editing is to start. Will add the 797: * editingComponent to the editingContainer. 798: */ 799: protected void prepareForEditing() 800: { 801: if (editingComponent != null) 802: editingContainer.add(editingComponent); 803: } 804: 805: /** 806: * Creates the container to manage placement of editingComponent. 807: * 808: * @return the container to manage the placement of the editingComponent. 809: */ 810: protected Container createContainer() 811: { 812: return new DefaultTreeCellEditor.EditorContainer(); 813: } 814: 815: /** 816: * This is invoked if a TreeCellEditor is not supplied in the constructor. 817: * It returns a TextField editor. 818: * 819: * @return a new TextField editor 820: */ 821: protected TreeCellEditor createTreeCellEditor() 822: { 823: Border border = UIManager.getBorder("Tree.editorBorder"); 824: JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); 825: DefaultCellEditor editor = new DefaultCellEditor(tf); 826: editor.setClickCountToStart(1); 827: realEditor = editor; 828: return editor; 829: } 830: }
GNU Classpath (0.95) |