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.FontMetrics; 47 import java.awt.Graphics; 48 import java.awt.Insets; 49 import java.awt.Rectangle; 50 import java.awt.event.ActionEvent; 51 import java.awt.event.ActionListener; 52 import java.awt.event.MouseEvent; 53 import java.io.IOException; 54 import java.io.ObjectInputStream; 55 import java.io.ObjectOutputStream; 56 import java.util.EventObject; 57 58 import javax.swing.DefaultCellEditor; 59 import javax.swing.Icon; 60 import javax.swing.JTextField; 61 import javax.swing.JTree; 62 import javax.swing.SwingUtilities; 63 import javax.swing.UIDefaults; 64 import javax.swing.UIManager; 65 import javax.swing.border.Border; 66 import javax.swing.event.CellEditorListener; 67 import javax.swing.event.EventListenerList; 68 import javax.swing.event.TreeSelectionEvent; 69 import javax.swing.event.TreeSelectionListener; 70 71 /** 72 * DefaultTreeCellEditor 73 * @author Andrew Selkirk 74 */ 75 public class DefaultTreeCellEditor 76 implements ActionListener, TreeCellEditor, TreeSelectionListener 77 { 78 /** 79 * EditorContainer 80 */ 81 public class EditorContainer extends Container 82 { 83 /** 84 * Creates an <code>EditorContainer</code> object. 85 */ EditorContainer()86 public EditorContainer() 87 { 88 // Do nothing here. 89 } 90 91 /** 92 * This method only exists for API compatibility and is useless as it does 93 * nothing. It got probably introduced by accident. 94 */ EditorContainer()95 public void EditorContainer() 96 { 97 // Do nothing here. 98 } 99 100 /** 101 * Returns the preferred size for the Container. 102 * 103 * @return Dimension of EditorContainer 104 */ getPreferredSize()105 public Dimension getPreferredSize() 106 { 107 Dimension containerSize = super.getPreferredSize(); 108 containerSize.width += DefaultTreeCellEditor.this.offset; 109 return containerSize; 110 } 111 112 /** 113 * Overrides Container.paint to paint the node's icon and use the selection 114 * color for the background. 115 * 116 * @param g - 117 * the specified Graphics window 118 */ paint(Graphics g)119 public void paint(Graphics g) 120 { 121 Rectangle tr = tree.getPathBounds(lastPath); 122 if (tr != null) 123 { 124 Insets i = ((DefaultTextField) editingComponent).getBorder() 125 .getBorderInsets(this); 126 int textIconGap = 3; 127 tr.x -= i.left; 128 129 // paints icon 130 if (editingIcon != null) 131 { 132 editingIcon.paintIcon(this, g, tr.x - editingIcon. 133 getIconWidth()/2, tr.y + i.top + i.bottom); 134 tr.x += editingIcon.getIconWidth()/2 + textIconGap; 135 } 136 137 tr.width += offset; 138 139 // paint background 140 g.translate(tr.x, tr.y); 141 editingComponent.setSize(new Dimension(tr.width, tr.height)); 142 editingComponent.paint(g); 143 g.translate(-tr.x, -tr.y); 144 } 145 super.paint(g); 146 } 147 148 /** 149 * Lays out this Container. If editing, the editor will be placed at offset 150 * in the x direction and 0 for y. 151 */ doLayout()152 public void doLayout() 153 { 154 if (DefaultTreeCellEditor.this.tree.isEditing()) 155 setLocation(offset, 0); 156 super.doLayout(); 157 } 158 } 159 160 /** 161 * DefaultTextField 162 */ 163 public class DefaultTextField extends JTextField 164 { 165 /** 166 * border 167 */ 168 protected Border border; 169 170 /** 171 * Creates a <code>DefaultTextField</code> object. 172 * 173 * @param border the border to use 174 */ DefaultTextField(Border border)175 public DefaultTextField(Border border) 176 { 177 this.border = border; 178 } 179 180 /** 181 * Gets the font of this component. 182 * @return this component's font; if a font has not been set for 183 * this component, the font of its parent is returned (if the parent 184 * is not null, otherwise null is returned). 185 */ getFont()186 public Font getFont() 187 { 188 Font font = super.getFont(); 189 if (font == null) 190 { 191 Component parent = getParent(); 192 if (parent != null) 193 return parent.getFont(); 194 return null; 195 } 196 return font; 197 } 198 199 /** 200 * Returns the border of the text field. 201 * 202 * @return the border 203 */ getBorder()204 public Border getBorder() 205 { 206 return border; 207 } 208 209 /** 210 * Overrides JTextField.getPreferredSize to return the preferred size 211 * based on current font, if set, or else use renderer's font. 212 * 213 * @return the Dimension of this textfield. 214 */ getPreferredSize()215 public Dimension getPreferredSize() 216 { 217 String s = getText(); 218 219 Font f = getFont(); 220 221 if (f != null) 222 { 223 FontMetrics fm = getToolkit().getFontMetrics(f); 224 225 return new Dimension(SwingUtilities.computeStringWidth(fm, s), 226 fm.getHeight()); 227 } 228 return renderer.getPreferredSize(); 229 } 230 } 231 232 private EventListenerList listenerList = new EventListenerList(); 233 234 /** 235 * Editor handling the editing. 236 */ 237 protected TreeCellEditor realEditor; 238 239 /** 240 * Renderer, used to get border and offsets from. 241 */ 242 protected DefaultTreeCellRenderer renderer; 243 244 /** 245 * Editing container, will contain the editorComponent. 246 */ 247 protected Container editingContainer; 248 249 /** 250 * Component used in editing, obtained from the editingContainer. 251 */ 252 protected transient Component editingComponent; 253 254 /** 255 * As of Java 2 platform v1.4 this field should no longer be used. 256 * If you wish to provide similar behavior you should directly 257 * override isCellEditable. 258 */ 259 protected boolean canEdit; 260 261 /** 262 * Used in editing. Indicates x position to place editingComponent. 263 */ 264 protected transient int offset; 265 266 /** 267 * JTree instance listening too. 268 */ 269 protected transient JTree tree; 270 271 /** 272 * Last path that was selected. 273 */ 274 protected transient TreePath lastPath; 275 276 /** 277 * Used before starting the editing session. 278 */ 279 protected transient javax.swing.Timer timer; 280 281 /** 282 * Row that was last passed into getTreeCellEditorComponent. 283 */ 284 protected transient int lastRow; 285 286 /** 287 * True if the border selection color should be drawn. 288 */ 289 protected Color borderSelectionColor; 290 291 /** 292 * Icon to use when editing. 293 */ 294 protected transient Icon editingIcon; 295 296 /** 297 * Font to paint with, null indicates font of renderer is to be used. 298 */ 299 protected Font font; 300 301 /** 302 * Helper field used to save the last path seen while the timer was 303 * running. 304 */ 305 private TreePath tPath; 306 307 /** 308 * Constructs a DefaultTreeCellEditor object for a JTree using the 309 * specified renderer and a default editor. (Use this constructor 310 * for normal editing.) 311 * 312 * @param tree - a JTree object 313 * @param renderer - a DefaultTreeCellRenderer object 314 */ DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer)315 public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) 316 { 317 this(tree, renderer, null); 318 } 319 320 /** 321 * Constructs a DefaultTreeCellEditor object for a JTree using the specified 322 * renderer and the specified editor. (Use this constructor 323 * for specialized editing.) 324 * 325 * @param tree - a JTree object 326 * @param renderer - a DefaultTreeCellRenderer object 327 * @param editor - a TreeCellEditor object 328 */ DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor)329 public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, 330 TreeCellEditor editor) 331 { 332 setTree(tree); 333 this.renderer = renderer; 334 335 if (editor == null) 336 editor = createTreeCellEditor(); 337 realEditor = editor; 338 339 lastPath = tree.getLeadSelectionPath(); 340 tree.addTreeSelectionListener(this); 341 editingContainer = createContainer(); 342 UIDefaults defaults = UIManager.getLookAndFeelDefaults(); 343 setFont(defaults.getFont("Tree.font")); 344 setBorderSelectionColor(defaults.getColor("Tree.selectionBorderColor")); 345 editingIcon = renderer.getIcon(); 346 timer = new javax.swing.Timer(1200, this); 347 } 348 349 /** 350 * Configures the editing component whenever it is null. 351 * 352 * @param tree the tree to configure to component for. 353 * @param renderer the renderer used to set up the nodes 354 * @param editor the editor used 355 */ configureEditingComponent(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor)356 private void configureEditingComponent(JTree tree, 357 DefaultTreeCellRenderer renderer, 358 TreeCellEditor editor) 359 { 360 if (tree != null && lastPath != null) 361 { 362 Object val = lastPath.getLastPathComponent(); 363 boolean isLeaf = tree.getModel().isLeaf(val); 364 boolean expanded = tree.isExpanded(lastPath); 365 determineOffset(tree, val, true, expanded, isLeaf, lastRow); 366 367 // set up icon 368 if (isLeaf) 369 renderer.setIcon(renderer.getLeafIcon()); 370 else if (expanded) 371 renderer.setIcon(renderer.getOpenIcon()); 372 else 373 renderer.setIcon(renderer.getClosedIcon()); 374 editingIcon = renderer.getIcon(); 375 376 editingComponent = getTreeCellEditorComponent(tree, val, true, 377 expanded, isLeaf, lastRow); 378 } 379 } 380 381 /** 382 * writeObject 383 * 384 * @param value0 385 * TODO 386 * @exception IOException 387 * TODO 388 */ writeObject(ObjectOutputStream value0)389 private void writeObject(ObjectOutputStream value0) throws IOException 390 { 391 // TODO 392 } 393 394 /** 395 * readObject 396 * @param value0 TODO 397 * @exception IOException TODO 398 * @exception ClassNotFoundException TODO 399 */ readObject(ObjectInputStream value0)400 private void readObject(ObjectInputStream value0) 401 throws IOException, ClassNotFoundException 402 { 403 // TODO 404 } 405 406 /** 407 * Sets the color to use for the border. 408 * @param newColor - the new border color 409 */ setBorderSelectionColor(Color newColor)410 public void setBorderSelectionColor(Color newColor) 411 { 412 this.borderSelectionColor = newColor; 413 } 414 415 /** 416 * Returns the color the border is drawn. 417 * @return Color 418 */ getBorderSelectionColor()419 public Color getBorderSelectionColor() 420 { 421 return borderSelectionColor; 422 } 423 424 /** 425 * Sets the font to edit with. null indicates the renderers 426 * font should be used. This will NOT override any font you have 427 * set in the editor the receiver was instantied with. If null for 428 * an editor was passed in, a default editor will be created that 429 * will pick up this font. 430 * 431 * @param font - the editing Font 432 */ setFont(Font font)433 public void setFont(Font font) 434 { 435 if (font != null) 436 this.font = font; 437 else 438 this.font = renderer.getFont(); 439 } 440 441 /** 442 * Gets the font used for editing. 443 * 444 * @return the editing font 445 */ getFont()446 public Font getFont() 447 { 448 return font; 449 } 450 451 /** 452 * Configures the editor. Passed onto the realEditor. 453 * Sets an initial value for the editor. This will cause 454 * the editor to stopEditing and lose any partially edited value 455 * if the editor is editing when this method is called. 456 * Returns the component that should be added to the client's Component 457 * hierarchy. Once installed in the client's hierarchy this component will 458 * then be able to draw and receive user input. 459 * 460 * @param tree - the JTree that is asking the editor to edit; this parameter can be null 461 * @param value - the value of the cell to be edited 462 * @param isSelected - true is the cell is to be rendered with selection highlighting 463 * @param expanded - true if the node is expanded 464 * @param leaf - true if the node is a leaf node 465 * @param row - the row index of the node being edited 466 * 467 * @return the component for editing 468 */ getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)469 public Component getTreeCellEditorComponent(JTree tree, Object value, 470 boolean isSelected, boolean expanded, 471 boolean leaf, int row) 472 { 473 if (realEditor == null) 474 createTreeCellEditor(); 475 476 return realEditor.getTreeCellEditorComponent(tree, value, isSelected, 477 expanded, leaf, row); 478 } 479 480 /** 481 * Returns the value currently being edited. 482 * 483 * @return the value currently being edited 484 */ getCellEditorValue()485 public Object getCellEditorValue() 486 { 487 return editingComponent; 488 } 489 490 /** 491 * If the realEditor returns true to this message, prepareForEditing 492 * is messaged and true is returned. 493 * 494 * @param event - the event the editor should use to consider whether to begin editing or not 495 * @return true if editing can be started 496 */ isCellEditable(EventObject event)497 public boolean isCellEditable(EventObject event) 498 { 499 if (editingComponent == null) 500 configureEditingComponent(tree, renderer, realEditor); 501 502 if (editingComponent != null && realEditor.isCellEditable(event)) 503 { 504 prepareForEditing(); 505 return true; 506 } 507 508 // Cell may not be currently editable, but may need to start timer. 509 if (shouldStartEditingTimer(event)) 510 startEditingTimer(); 511 else if (timer.isRunning()) 512 timer.stop(); 513 return false; 514 } 515 516 /** 517 * Messages the realEditor for the return value. 518 * 519 * @param event - 520 * the event the editor should use to start editing 521 * @return true if the editor would like the editing cell to be selected; 522 * otherwise returns false 523 */ shouldSelectCell(EventObject event)524 public boolean shouldSelectCell(EventObject event) 525 { 526 return true; 527 } 528 529 /** 530 * If the realEditor will allow editing to stop, the realEditor 531 * is removed and true is returned, otherwise false is returned. 532 * @return true if editing was stopped; false otherwise 533 */ stopCellEditing()534 public boolean stopCellEditing() 535 { 536 if (editingComponent != null && realEditor.stopCellEditing()) 537 { 538 timer.stop(); 539 return true; 540 } 541 return false; 542 } 543 544 /** 545 * Messages cancelCellEditing to the realEditor and removes it 546 * from this instance. 547 */ cancelCellEditing()548 public void cancelCellEditing() 549 { 550 if (editingComponent != null) 551 { 552 timer.stop(); 553 realEditor.cancelCellEditing(); 554 } 555 } 556 557 /** 558 * Adds a <code>CellEditorListener</code> object to this editor. 559 * 560 * @param listener the listener to add 561 */ addCellEditorListener(CellEditorListener listener)562 public void addCellEditorListener(CellEditorListener listener) 563 { 564 realEditor.addCellEditorListener(listener); 565 } 566 567 /** 568 * Removes a <code>CellEditorListener</code> object. 569 * 570 * @param listener the listener to remove 571 */ removeCellEditorListener(CellEditorListener listener)572 public void removeCellEditorListener(CellEditorListener listener) 573 { 574 realEditor.removeCellEditorListener(listener); 575 } 576 577 /** 578 * Returns all added <code>CellEditorListener</code> objects to this editor. 579 * 580 * @return an array of listeners 581 * 582 * @since 1.4 583 */ getCellEditorListeners()584 public CellEditorListener[] getCellEditorListeners() 585 { 586 return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class); 587 } 588 589 /** 590 * Resets lastPath. 591 * 592 * @param e - the event that characterizes the change. 593 */ valueChanged(TreeSelectionEvent e)594 public void valueChanged(TreeSelectionEvent e) 595 { 596 tPath = lastPath; 597 lastPath = e.getNewLeadSelectionPath(); 598 lastRow = tree.getRowForPath(lastPath); 599 configureEditingComponent(tree, renderer, realEditor); 600 } 601 602 /** 603 * Messaged when the timer fires, this will start the editing session. 604 * 605 * @param e the event that characterizes the action. 606 */ actionPerformed(ActionEvent e)607 public void actionPerformed(ActionEvent e) 608 { 609 if (lastPath != null && tPath != null && tPath.equals(lastPath)) 610 { 611 tree.startEditingAtPath(lastPath); 612 timer.stop(); 613 } 614 } 615 616 /** 617 * Sets the tree currently editing for. This is needed to add a selection 618 * listener. 619 * 620 * @param newTree - 621 * the new tree to be edited 622 */ setTree(JTree newTree)623 protected void setTree(JTree newTree) 624 { 625 tree = newTree; 626 } 627 628 /** 629 * Returns true if event is a MouseEvent and the click count is 1. 630 * 631 * @param event - the event being studied 632 * @return true if editing should start 633 */ shouldStartEditingTimer(EventObject event)634 protected boolean shouldStartEditingTimer(EventObject event) 635 { 636 if ((event instanceof MouseEvent) && 637 ((MouseEvent) event).getClickCount() == 1) 638 return true; 639 return false; 640 } 641 642 /** 643 * Starts the editing timer. 644 */ startEditingTimer()645 protected void startEditingTimer() 646 { 647 if (timer == null) 648 timer = new javax.swing.Timer(1200, this); 649 if (!timer.isRunning()) 650 timer.start(); 651 } 652 653 /** 654 * Returns true if event is null, or it is a MouseEvent with 655 * a click count > 2 and inHitRegion returns true. 656 * 657 * @param event - the event being studied 658 * @return true if event is null, or it is a MouseEvent with 659 * a click count > 2 and inHitRegion returns true 660 */ canEditImmediately(EventObject event)661 protected boolean canEditImmediately(EventObject event) 662 { 663 if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event). 664 getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(), 665 ((MouseEvent) event).getY()))) 666 return true; 667 return false; 668 } 669 670 /** 671 * Returns true if the passed in location is a valid mouse location 672 * to start editing from. This is implemented to return false if x is 673 * less than or equal to the width of the icon and icon 674 * gap displayed by the renderer. In other words this returns true if 675 * the user clicks over the text part displayed by the renderer, and 676 * false otherwise. 677 * 678 * @param x - the x-coordinate of the point 679 * @param y - the y-coordinate of the point 680 * 681 * @return true if the passed in location is a valid mouse location 682 */ inHitRegion(int x, int y)683 protected boolean inHitRegion(int x, int y) 684 { 685 Rectangle bounds = tree.getPathBounds(lastPath); 686 687 return bounds.contains(x, y); 688 } 689 690 /** 691 * determineOffset 692 * @param tree - 693 * @param value - 694 * @param isSelected - 695 * @param expanded - 696 * @param leaf - 697 * @param row - 698 */ determineOffset(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)699 protected void determineOffset(JTree tree, Object value, boolean isSelected, 700 boolean expanded, boolean leaf, int row) 701 { 702 renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, 703 leaf, row, true); 704 Icon c = renderer.getIcon(); 705 if (c != null) 706 offset = renderer.getIconTextGap() + c.getIconWidth(); 707 else 708 offset = 0; 709 } 710 711 /** 712 * Invoked just before editing is to start. Will add the 713 * editingComponent to the editingContainer. 714 */ prepareForEditing()715 protected void prepareForEditing() 716 { 717 editingContainer.add(editingComponent); 718 } 719 720 /** 721 * Creates the container to manage placement of editingComponent. 722 * 723 * @return the container to manage the placement of the editingComponent. 724 */ createContainer()725 protected Container createContainer() 726 { 727 return new DefaultTreeCellEditor.EditorContainer(); 728 } 729 730 /** 731 * This is invoked if a TreeCellEditor is not supplied in the constructor. 732 * It returns a TextField editor. 733 * 734 * @return a new TextField editor 735 */ createTreeCellEditor()736 protected TreeCellEditor createTreeCellEditor() 737 { 738 UIDefaults defaults = UIManager.getLookAndFeelDefaults(); 739 realEditor = new DefaultCellEditor(new DefaultTreeCellEditor.DefaultTextField( 740 defaults.getBorder("Tree.selectionBorder"))); 741 return realEditor; 742 } 743 } 744