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