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