001 /* DefaultTreeCellEditor.java -- 002 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.tree; 040 041 import java.awt.Color; 042 import java.awt.Component; 043 import java.awt.Container; 044 import java.awt.Dimension; 045 import java.awt.Font; 046 import java.awt.Graphics; 047 import java.awt.Rectangle; 048 import java.awt.event.ActionEvent; 049 import java.awt.event.ActionListener; 050 import java.awt.event.MouseEvent; 051 import java.io.IOException; 052 import java.io.ObjectInputStream; 053 import java.io.ObjectOutputStream; 054 import java.util.EventObject; 055 056 import javax.swing.DefaultCellEditor; 057 import javax.swing.Icon; 058 import javax.swing.JTextField; 059 import javax.swing.JTree; 060 import javax.swing.SwingUtilities; 061 import javax.swing.Timer; 062 import javax.swing.UIManager; 063 import javax.swing.border.Border; 064 import javax.swing.event.CellEditorListener; 065 import javax.swing.event.EventListenerList; 066 import javax.swing.event.TreeSelectionEvent; 067 import javax.swing.event.TreeSelectionListener; 068 069 /** 070 * Participates in the tree cell editing. 071 * 072 * @author Andrew Selkirk 073 * @author Audrius Meskauskas 074 */ 075 public class DefaultTreeCellEditor 076 implements ActionListener, TreeCellEditor, TreeSelectionListener 077 { 078 /** 079 * This container that appears on the tree during editing session. 080 * It contains the editing component displays various other editor - 081 * specific parts like editing icon. 082 */ 083 public class EditorContainer extends Container 084 { 085 /** 086 * Use v 1.5 serial version UID for interoperability. 087 */ 088 static final long serialVersionUID = 6470339600449699810L; 089 090 /** 091 * Creates an <code>EditorContainer</code> object. 092 */ 093 public EditorContainer() 094 { 095 setLayout(null); 096 } 097 098 /** 099 * 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 }