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:     setFont(UIManager.getFont("Tree.font"));
 343:     setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor"));
 344:     editingIcon = renderer.getIcon();
 345:     timer = new javax.swing.Timer(1200, this);
 346:   }
 347: 
 348:   /**
 349:    * Configures the editing component whenever it is null.
 350:    * 
 351:    * @param tree the tree to configure to component for.
 352:    * @param renderer the renderer used to set up the nodes
 353:    * @param editor the editor used 
 354:    */
 355:   private void configureEditingComponent(JTree tree,
 356:                                          DefaultTreeCellRenderer renderer,
 357:                                          TreeCellEditor editor)
 358:   {    
 359:     if (tree != null && lastPath != null)
 360:       {
 361:         Object val = lastPath.getLastPathComponent();
 362:         boolean isLeaf = tree.getModel().isLeaf(val);
 363:         boolean expanded = tree.isExpanded(lastPath);
 364:         determineOffset(tree, val, true, expanded, isLeaf, lastRow);
 365: 
 366:         // set up icon
 367:         if (isLeaf)
 368:           renderer.setIcon(renderer.getLeafIcon());
 369:         else if (expanded)
 370:           renderer.setIcon(renderer.getOpenIcon());
 371:         else
 372:           renderer.setIcon(renderer.getClosedIcon());
 373:         editingIcon = renderer.getIcon();
 374: 
 375:         editingComponent = getTreeCellEditorComponent(tree, val, true,
 376:                                                       expanded, isLeaf, lastRow);
 377:       }
 378:   }
 379:   
 380:   /**
 381:    * writeObject
 382:    * 
 383:    * @param value0
 384:    *          TODO
 385:    * @exception IOException
 386:    *              TODO
 387:    */
 388:   private void writeObject(ObjectOutputStream value0) throws IOException
 389:   {
 390:     // TODO
 391:   }
 392: 
 393:   /**
 394:    * readObject
 395:    * @param value0 TODO
 396:    * @exception IOException TODO
 397:    * @exception ClassNotFoundException TODO
 398:    */
 399:   private void readObject(ObjectInputStream value0)
 400:     throws IOException, ClassNotFoundException
 401:   {
 402:     // TODO
 403:   }
 404: 
 405:   /**
 406:    * Sets the color to use for the border.
 407:    * @param newColor - the new border color
 408:    */
 409:   public void setBorderSelectionColor(Color newColor)
 410:   {
 411:     this.borderSelectionColor = newColor;
 412:   }
 413: 
 414:   /**
 415:    * Returns the color the border is drawn.
 416:    * @return Color
 417:    */
 418:   public Color getBorderSelectionColor()
 419:   {
 420:     return borderSelectionColor;
 421:   }
 422: 
 423:   /**
 424:    * Sets the font to edit with. null indicates the renderers 
 425:    * font should be used. This will NOT override any font you have 
 426:    * set in the editor the receiver was instantied with. If null for 
 427:    * an editor was passed in, a default editor will be created that 
 428:    * will pick up this font.
 429:    * 
 430:    * @param font - the editing Font
 431:    */
 432:   public void setFont(Font font)
 433:   {
 434:     if (font != null)
 435:       this.font = font;
 436:     else
 437:       this.font = renderer.getFont();
 438:   }
 439: 
 440:   /**
 441:    * Gets the font used for editing.
 442:    * 
 443:    * @return the editing font
 444:    */
 445:   public Font getFont()
 446:   {
 447:     return font;
 448:   }
 449: 
 450:   /**
 451:    * Configures the editor. Passed onto the realEditor.
 452:    * Sets an initial value for the editor. This will cause 
 453:    * the editor to stopEditing and lose any partially edited value 
 454:    * if the editor is editing when this method is called. 
 455:    * Returns the component that should be added to the client's Component 
 456:    * hierarchy. Once installed in the client's hierarchy this component will 
 457:    * then be able to draw and receive user input. 
 458:    * 
 459:    * @param tree - the JTree that is asking the editor to edit; this parameter can be null
 460:    * @param value - the value of the cell to be edited
 461:    * @param isSelected - true is the cell is to be rendered with selection highlighting
 462:    * @param expanded - true if the node is expanded
 463:    * @param leaf - true if the node is a leaf node
 464:    * @param row - the row index of the node being edited
 465:    * 
 466:    * @return the component for editing
 467:    */
 468:   public Component getTreeCellEditorComponent(JTree tree, Object value,
 469:                                               boolean isSelected, boolean expanded,
 470:                                               boolean leaf, int row)
 471:   {
 472:     if (realEditor == null)
 473:       createTreeCellEditor();
 474: 
 475:     return realEditor.getTreeCellEditorComponent(tree, value, isSelected,
 476:                                                         expanded, leaf, row);
 477:   }
 478: 
 479:   /**
 480:    * Returns the value currently being edited.
 481:    * 
 482:    * @return the value currently being edited
 483:    */
 484:   public Object getCellEditorValue()
 485:   {
 486:     return editingComponent;
 487:   }
 488:   
 489:   /**
 490:    * If the realEditor returns true to this message, prepareForEditing  
 491:    * is messaged and true is returned.
 492:    * 
 493:    * @param event - the event the editor should use to consider whether to begin editing or not
 494:    * @return true if editing can be started
 495:    */
 496:   public boolean isCellEditable(EventObject event)
 497:   { 
 498:     if (editingComponent == null)
 499:         configureEditingComponent(tree, renderer, realEditor);
 500:     
 501:     if (editingComponent != null && realEditor.isCellEditable(event))
 502:       {
 503:         prepareForEditing();
 504:         return true;
 505:       }
 506:     
 507:     // Cell may not be currently editable, but may need to start timer.
 508:     if (shouldStartEditingTimer(event))
 509:       startEditingTimer();
 510:     else if (timer.isRunning())
 511:       timer.stop();
 512:     return false;
 513:   }
 514: 
 515:   /**
 516:    * Messages the realEditor for the return value.
 517:    * 
 518:    * @param event -
 519:    *          the event the editor should use to start editing
 520:    * @return true if the editor would like the editing cell to be selected;
 521:    *         otherwise returns false
 522:    */
 523:   public boolean shouldSelectCell(EventObject event)
 524:   {
 525:     return true;
 526:   }
 527: 
 528:   /**
 529:    * If the realEditor will allow editing to stop, the realEditor
 530:    * is removed and true is returned, otherwise false is returned.
 531:    * @return true if editing was stopped; false otherwise
 532:    */
 533:   public boolean stopCellEditing()
 534:   {
 535:     if (editingComponent != null && realEditor.stopCellEditing())
 536:       {
 537:         timer.stop();
 538:         return true;
 539:       }
 540:     return false;
 541:   }
 542: 
 543:   /**
 544:    * Messages cancelCellEditing to the realEditor and removes it
 545:    * from this instance.
 546:    */
 547:   public void cancelCellEditing()
 548:   {
 549:     if (editingComponent != null)
 550:       {
 551:         timer.stop();
 552:         realEditor.cancelCellEditing();
 553:       }
 554:   }
 555: 
 556:   /**
 557:    * Adds a <code>CellEditorListener</code> object to this editor.
 558:    *
 559:    * @param listener the listener to add
 560:    */
 561:   public void addCellEditorListener(CellEditorListener listener)
 562:   {
 563:     realEditor.addCellEditorListener(listener);
 564:   }
 565: 
 566:   /**
 567:    * Removes a <code>CellEditorListener</code> object.
 568:    *
 569:    * @param listener the listener to remove
 570:    */
 571:   public void removeCellEditorListener(CellEditorListener listener)
 572:   {
 573:     realEditor.removeCellEditorListener(listener);
 574:   }
 575: 
 576:   /**
 577:    * Returns all added <code>CellEditorListener</code> objects to this editor.
 578:    *
 579:    * @return an array of listeners
 580:    *
 581:    * @since 1.4
 582:    */
 583:   public CellEditorListener[] getCellEditorListeners()
 584:   {
 585:     return (CellEditorListener[]) listenerList.getListeners(CellEditorListener.class);
 586:   }
 587: 
 588:   /**
 589:    * Resets lastPath.
 590:    * 
 591:    * @param e - the event that characterizes the change.
 592:    */
 593:   public void valueChanged(TreeSelectionEvent e)
 594:   {
 595:     tPath = lastPath;
 596:     lastPath = e.getNewLeadSelectionPath();
 597:     lastRow = tree.getRowForPath(lastPath);
 598:     configureEditingComponent(tree, renderer, realEditor);
 599:   }
 600:   
 601:   /**
 602:    * Messaged when the timer fires, this will start the editing session.
 603:    * 
 604:    * @param e the event that characterizes the action.
 605:    */
 606:   public void actionPerformed(ActionEvent e)
 607:   {
 608:     if (lastPath != null && tPath != null && tPath.equals(lastPath))
 609:       {
 610:         tree.startEditingAtPath(lastPath);
 611:         timer.stop();
 612:       }
 613:   }
 614: 
 615:   /**
 616:    * Sets the tree currently editing for. This is needed to add a selection
 617:    * listener.
 618:    * 
 619:    * @param newTree -
 620:    *          the new tree to be edited
 621:    */
 622:   protected void setTree(JTree newTree)
 623:   {
 624:     tree = newTree;
 625:   }
 626: 
 627:   /**
 628:    * Returns true if event is a MouseEvent and the click count is 1.
 629:    * 
 630:    * @param event - the event being studied
 631:    * @return true if editing should start
 632:    */
 633:   protected boolean shouldStartEditingTimer(EventObject event)
 634:   {
 635:     if ((event instanceof MouseEvent) && 
 636:         ((MouseEvent) event).getClickCount() == 1)
 637:       return true;
 638:     return false;
 639:   }
 640: 
 641:   /**
 642:    * Starts the editing timer.
 643:    */
 644:   protected void startEditingTimer()
 645:   {
 646:     if (timer == null)
 647:       timer = new javax.swing.Timer(1200, this);
 648:     if (!timer.isRunning())
 649:       timer.start();
 650:   }
 651: 
 652:   /**
 653:    * Returns true if event is null, or it is a MouseEvent with 
 654:    * a click count > 2 and inHitRegion returns true.
 655:    * 
 656:    * @param event - the event being studied
 657:    * @return true if event is null, or it is a MouseEvent with 
 658:    * a click count > 2 and inHitRegion returns true 
 659:    */
 660:   protected boolean canEditImmediately(EventObject event)
 661:   {
 662:     if (event == null || !(event instanceof MouseEvent) || (((MouseEvent) event).
 663:         getClickCount() > 2 && inHitRegion(((MouseEvent) event).getX(), 
 664:                                          ((MouseEvent) event).getY())))
 665:       return true;
 666:     return false;
 667:   }
 668: 
 669:   /**
 670:    * Returns true if the passed in location is a valid mouse location 
 671:    * to start editing from. This is implemented to return false if x is
 672:    * less than or equal to the width of the icon and icon 
 673:    * gap displayed by the renderer. In other words this returns true if 
 674:    * the user clicks over the text part displayed by the renderer, and 
 675:    * false otherwise.
 676:    * 
 677:    * @param x - the x-coordinate of the point
 678:    * @param y - the y-coordinate of the point
 679:    * 
 680:    * @return true if the passed in location is a valid mouse location
 681:    */
 682:   protected boolean inHitRegion(int x, int y)
 683:   {
 684:     Rectangle bounds = tree.getPathBounds(lastPath);
 685:     
 686:     return bounds.contains(x, y);
 687:   }
 688: 
 689:   /**
 690:    * determineOffset
 691:    * @param tree -
 692:    * @param value - 
 693:    * @param isSelected - 
 694:    * @param expanded - 
 695:    * @param leaf - 
 696:    * @param row - 
 697:    */
 698:   protected void determineOffset(JTree tree, Object value, boolean isSelected,
 699:                                  boolean expanded, boolean leaf, int row)
 700:   {
 701:     renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, 
 702:                                           leaf, row, true);
 703:     Icon c = renderer.getIcon();
 704:     if (c != null)
 705:         offset = renderer.getIconTextGap() + c.getIconWidth();
 706:     else
 707:       offset = 0;
 708:   }
 709: 
 710:   /**
 711:    * Invoked just before editing is to start. Will add the 
 712:    * editingComponent to the editingContainer.
 713:    */
 714:   protected void prepareForEditing()
 715:   {
 716:     editingContainer.add(editingComponent);
 717:   }
 718: 
 719:   /**
 720:    * Creates the container to manage placement of editingComponent.
 721:    * 
 722:    * @return the container to manage the placement of the editingComponent.
 723:    */
 724:   protected Container createContainer()
 725:   {
 726:     return new DefaultTreeCellEditor.EditorContainer();
 727:   }
 728: 
 729:   /**
 730:    * This is invoked if a TreeCellEditor is not supplied in the constructor. 
 731:    * It returns a TextField editor.
 732:    * 
 733:    * @return a new TextField editor
 734:    */
 735:   protected TreeCellEditor createTreeCellEditor()
 736:   {
 737:     realEditor = new DefaultCellEditor(new DefaultTreeCellEditor.DefaultTextField(
 738:                                   UIManager.getBorder("Tree.selectionBorder")));
 739:     return realEditor;
 740:   }
 741: }