Source for javax.swing.tree.DefaultTreeCellEditor

   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:      */
  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:      */
  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:      */
 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:      */
 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:      */
 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:      */
 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:      */
 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:      */
 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:      */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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: }