1 /*
   2  * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.tree;
  26 
  27 import javax.swing.*;
  28 import javax.swing.border.*;
  29 import javax.swing.event.*;
  30 import javax.swing.plaf.FontUIResource;
  31 import java.awt.*;
  32 import java.awt.event.*;
  33 import java.beans.BeanProperty;
  34 import java.util.EventObject;
  35 
  36 /**
  37  * A <code>TreeCellEditor</code>. You need to supply an
  38  * instance of <code>DefaultTreeCellRenderer</code>
  39  * so that the icons can be obtained. You can optionally supply
  40  * a <code>TreeCellEditor</code> that will be layed out according
  41  * to the icon in the <code>DefaultTreeCellRenderer</code>.
  42  * If you do not supply a <code>TreeCellEditor</code>,
  43  * a <code>TextField</code> will be used. Editing is started
  44  * on a triple mouse click, or after a click, pause, click and
  45  * a delay of 1200 milliseconds.
  46  *<p>
  47  * <strong>Warning:</strong>
  48  * Serialized objects of this class will not be compatible with
  49  * future Swing releases. The current serialization support is
  50  * appropriate for short term storage or RMI between applications running
  51  * the same version of Swing.  As of 1.4, support for long term storage
  52  * of all JavaBeans&trade;
  53  * has been added to the <code>java.beans</code> package.
  54  * Please see {@link java.beans.XMLEncoder}.
  55  *
  56  * @see javax.swing.JTree
  57  *
  58  * @author Scott Violet
  59  */
  60 public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor,
  61             TreeSelectionListener {
  62     /** Editor handling the editing. */
  63     protected TreeCellEditor               realEditor;
  64 
  65     /** Renderer, used to get border and offsets from. */
  66     protected DefaultTreeCellRenderer      renderer;
  67 
  68     /** Editing container, will contain the <code>editorComponent</code>. */
  69     protected Container                    editingContainer;
  70 
  71     /**
  72      * Component used in editing, obtained from the
  73      * <code>editingContainer</code>.
  74      */
  75     protected transient Component          editingComponent;
  76 
  77     /**
  78      * As of Java 2 platform v1.4 this field should no longer be used. If
  79      * you wish to provide similar behavior you should directly override
  80      * <code>isCellEditable</code>.
  81      */
  82     protected boolean                      canEdit;
  83 
  84     /**
  85      * Used in editing. Indicates x position to place
  86      * <code>editingComponent</code>.
  87      */
  88     protected transient int                offset;
  89 
  90     /** <code>JTree</code> instance listening too. */
  91     protected transient JTree              tree;
  92 
  93     /** Last path that was selected. */
  94     protected transient TreePath           lastPath;
  95 
  96     /** Used before starting the editing session. */
  97     protected transient Timer              timer;
  98 
  99     /**
 100      * Row that was last passed into
 101      * <code>getTreeCellEditorComponent</code>.
 102      */
 103     protected transient int                lastRow;
 104 
 105     /** True if the border selection color should be drawn. */
 106     protected Color                        borderSelectionColor;
 107 
 108     /** Icon to use when editing. */
 109     protected transient Icon               editingIcon;
 110 
 111     /**
 112      * Font to paint with, <code>null</code> indicates
 113      * font of renderer is to be used.
 114      */
 115     protected Font                         font;
 116 
 117 
 118     /**
 119      * Constructs a <code>DefaultTreeCellEditor</code>
 120      * object for a JTree using the specified renderer and
 121      * a default editor. (Use this constructor for normal editing.)
 122      *
 123      * @param tree      a <code>JTree</code> object
 124      * @param renderer  a <code>DefaultTreeCellRenderer</code> object
 125      */
 126     public DefaultTreeCellEditor(JTree tree,
 127                                  DefaultTreeCellRenderer renderer) {
 128         this(tree, renderer, null);
 129     }
 130 
 131     /**
 132      * Constructs a <code>DefaultTreeCellEditor</code>
 133      * object for a <code>JTree</code> using the
 134      * specified renderer and the specified editor. (Use this constructor
 135      * for specialized editing.)
 136      *
 137      * @param tree      a <code>JTree</code> object
 138      * @param renderer  a <code>DefaultTreeCellRenderer</code> object
 139      * @param editor    a <code>TreeCellEditor</code> object
 140      */
 141     public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
 142                                  TreeCellEditor editor) {
 143         this.renderer = renderer;
 144         realEditor = editor;
 145         if(realEditor == null)
 146             realEditor = createTreeCellEditor();
 147         editingContainer = createContainer();
 148         setTree(tree);
 149         setBorderSelectionColor(UIManager.getColor
 150                                 ("Tree.editorBorderSelectionColor"));
 151     }
 152 
 153     /**
 154       * Sets the color to use for the border.
 155       * @param newColor the new border color
 156       */
 157     public void setBorderSelectionColor(Color newColor) {
 158         borderSelectionColor = newColor;
 159     }
 160 
 161     /**
 162       * Returns the color the border is drawn.
 163       * @return the border selection color
 164       */
 165     public Color getBorderSelectionColor() {
 166         return borderSelectionColor;
 167     }
 168 
 169     /**
 170      * Sets the font to edit with. <code>null</code> indicates
 171      * the renderers font should be used. This will NOT
 172      * override any font you have set in the editor
 173      * the receiver was instantiated with. If <code>null</code>
 174      * for an editor was passed in a default editor will be
 175      * created that will pick up this font.
 176      *
 177      * @param font  the editing <code>Font</code>
 178      * @see #getFont
 179      */
 180     public void setFont(Font font) {
 181         this.font = font;
 182     }
 183 
 184     /**
 185      * Gets the font used for editing.
 186      *
 187      * @return the editing <code>Font</code>
 188      * @see #setFont
 189      */
 190     public Font getFont() {
 191         return font;
 192     }
 193 
 194     //
 195     // TreeCellEditor
 196     //
 197 
 198     /**
 199      * Configures the editor.  Passed onto the <code>realEditor</code>.
 200      */
 201     public Component getTreeCellEditorComponent(JTree tree, Object value,
 202                                                 boolean isSelected,
 203                                                 boolean expanded,
 204                                                 boolean leaf, int row) {
 205         setTree(tree);
 206         lastRow = row;
 207         determineOffset(tree, value, isSelected, expanded, leaf, row);
 208 
 209         if (editingComponent != null) {
 210             editingContainer.remove(editingComponent);
 211         }
 212         editingComponent = realEditor.getTreeCellEditorComponent(tree, value,
 213                                         isSelected, expanded,leaf, row);
 214 
 215 
 216         // this is kept for backwards compatibility but isn't really needed
 217         // with the current BasicTreeUI implementation.
 218         TreePath        newPath = tree.getPathForRow(row);
 219 
 220         canEdit = (lastPath != null && newPath != null &&
 221                    lastPath.equals(newPath));
 222 
 223         Font            font = getFont();
 224 
 225         if(font == null) {
 226             if(renderer != null)
 227                 font = renderer.getFont();
 228             if(font == null)
 229                 font = tree.getFont();
 230         }
 231         editingContainer.setFont(font);
 232         prepareForEditing();
 233         return editingContainer;
 234     }
 235 
 236     /**
 237      * Returns the value currently being edited.
 238      * @return the value currently being edited
 239      */
 240     public Object getCellEditorValue() {
 241         return realEditor.getCellEditorValue();
 242     }
 243 
 244     /**
 245      * If the <code>realEditor</code> returns true to this
 246      * message, <code>prepareForEditing</code>
 247      * is messaged and true is returned.
 248      */
 249     public boolean isCellEditable(EventObject event) {
 250         boolean            retValue = false;
 251         boolean            editable = false;
 252 
 253         if (event != null) {
 254             if (event.getSource() instanceof JTree) {
 255                 setTree((JTree)event.getSource());
 256                 if (event instanceof MouseEvent) {
 257                     TreePath path = tree.getPathForLocation(
 258                                          ((MouseEvent)event).getX(),
 259                                          ((MouseEvent)event).getY());
 260                     editable = (lastPath != null && path != null &&
 261                                lastPath.equals(path));
 262                     if (path!=null) {
 263                         lastRow = tree.getRowForPath(path);
 264                         Object value = path.getLastPathComponent();
 265                         boolean isSelected = tree.isRowSelected(lastRow);
 266                         boolean expanded = tree.isExpanded(path);
 267                         TreeModel treeModel = tree.getModel();
 268                         boolean leaf = treeModel.isLeaf(value);
 269                         determineOffset(tree, value, isSelected,
 270                                         expanded, leaf, lastRow);
 271                     }
 272                 }
 273             }
 274         }
 275         if(!realEditor.isCellEditable(event))
 276             return false;
 277         if(canEditImmediately(event))
 278             retValue = true;
 279         else if(editable && shouldStartEditingTimer(event)) {
 280             startEditingTimer();
 281         }
 282         else if(timer != null && timer.isRunning())
 283             timer.stop();
 284         if(retValue)
 285             prepareForEditing();
 286         return retValue;
 287     }
 288 
 289     /**
 290      * Messages the <code>realEditor</code> for the return value.
 291      */
 292     public boolean shouldSelectCell(EventObject event) {
 293         return realEditor.shouldSelectCell(event);
 294     }
 295 
 296     /**
 297      * If the <code>realEditor</code> will allow editing to stop,
 298      * the <code>realEditor</code> is removed and true is returned,
 299      * otherwise false is returned.
 300      */
 301     public boolean stopCellEditing() {
 302         if(realEditor.stopCellEditing()) {
 303             cleanupAfterEditing();
 304             return true;
 305         }
 306         return false;
 307     }
 308 
 309     /**
 310      * Messages <code>cancelCellEditing</code> to the
 311      * <code>realEditor</code> and removes it from this instance.
 312      */
 313     public void cancelCellEditing() {
 314         realEditor.cancelCellEditing();
 315         cleanupAfterEditing();
 316     }
 317 
 318     /**
 319      * Adds the <code>CellEditorListener</code>.
 320      * @param l the listener to be added
 321      */
 322     public void addCellEditorListener(CellEditorListener l) {
 323         realEditor.addCellEditorListener(l);
 324     }
 325 
 326     /**
 327       * Removes the previously added <code>CellEditorListener</code>.
 328       * @param l the listener to be removed
 329       */
 330     public void removeCellEditorListener(CellEditorListener l) {
 331         realEditor.removeCellEditorListener(l);
 332     }
 333 
 334     /**
 335      * Returns an array of all the <code>CellEditorListener</code>s added
 336      * to this DefaultTreeCellEditor with addCellEditorListener().
 337      *
 338      * @return all of the <code>CellEditorListener</code>s added or an empty
 339      *         array if no listeners have been added
 340      * @since 1.4
 341      */
 342     public CellEditorListener[] getCellEditorListeners() {
 343         return ((DefaultCellEditor)realEditor).getCellEditorListeners();
 344     }
 345 
 346     //
 347     // TreeSelectionListener
 348     //
 349 
 350     /**
 351      * Resets <code>lastPath</code>.
 352      */
 353     public void valueChanged(TreeSelectionEvent e) {
 354         if(tree != null) {
 355             if(tree.getSelectionCount() == 1)
 356                 lastPath = tree.getSelectionPath();
 357             else
 358                 lastPath = null;
 359         }
 360         if(timer != null) {
 361             timer.stop();
 362         }
 363     }
 364 
 365     //
 366     // ActionListener (for Timer).
 367     //
 368 
 369     /**
 370      * Messaged when the timer fires, this will start the editing
 371      * session.
 372      */
 373     public void actionPerformed(ActionEvent e) {
 374         if(tree != null && lastPath != null) {
 375             tree.startEditingAtPath(lastPath);
 376         }
 377     }
 378 
 379     //
 380     // Local methods
 381     //
 382 
 383     /**
 384      * Sets the tree currently editing for. This is needed to add
 385      * a selection listener.
 386      * @param newTree the new tree to be edited
 387      */
 388     protected void setTree(JTree newTree) {
 389         if(tree != newTree) {
 390             if(tree != null)
 391                 tree.removeTreeSelectionListener(this);
 392             tree = newTree;
 393             if(tree != null)
 394                 tree.addTreeSelectionListener(this);
 395             if(timer != null) {
 396                 timer.stop();
 397             }
 398         }
 399     }
 400 
 401     /**
 402      * Returns true if <code>event</code> is a <code>MouseEvent</code>
 403      * and the click count is 1.
 404      *
 405      * @param event the event being studied
 406      * @return whether {@code event} should starts the editing timer
 407      */
 408     protected boolean shouldStartEditingTimer(EventObject event) {
 409         if((event instanceof MouseEvent) &&
 410             SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
 411             MouseEvent        me = (MouseEvent)event;
 412 
 413             return (me.getClickCount() == 1 &&
 414                     inHitRegion(me.getX(), me.getY()));
 415         }
 416         return false;
 417     }
 418 
 419     /**
 420      * Starts the editing timer.
 421      */
 422     protected void startEditingTimer() {
 423         if(timer == null) {
 424             timer = new Timer(1200, this);
 425             timer.setRepeats(false);
 426         }
 427         timer.start();
 428     }
 429 
 430     /**
 431      * Returns true if <code>event</code> is <code>null</code>,
 432      * or it is a <code>MouseEvent</code> with a click count &gt; 2
 433      * and <code>inHitRegion</code> returns true.
 434      *
 435      * @param event the event being studied
 436      * @return whether editing can be started for the given {@code event}
 437      */
 438     protected boolean canEditImmediately(EventObject event) {
 439         if((event instanceof MouseEvent) &&
 440            SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
 441             MouseEvent       me = (MouseEvent)event;
 442 
 443             return ((me.getClickCount() > 2) &&
 444                     inHitRegion(me.getX(), me.getY()));
 445         }
 446         return (event == null);
 447     }
 448 
 449     /**
 450      * Returns true if the passed in location is a valid mouse location
 451      * to start editing from. This is implemented to return false if
 452      * <code>x</code> is &lt;= the width of the icon and icon gap displayed
 453      * by the renderer. In other words this returns true if the user
 454      * clicks over the text part displayed by the renderer, and false
 455      * otherwise.
 456      * @param x the x-coordinate of the point
 457      * @param y the y-coordinate of the point
 458      * @return true if the passed in location is a valid mouse location
 459      */
 460     protected boolean inHitRegion(int x, int y) {
 461         if(lastRow != -1 && tree != null) {
 462             Rectangle bounds = tree.getRowBounds(lastRow);
 463             ComponentOrientation treeOrientation = tree.getComponentOrientation();
 464 
 465             if ( treeOrientation.isLeftToRight() ) {
 466                 if (bounds != null && x <= (bounds.x + offset) &&
 467                     offset < (bounds.width - 5)) {
 468                     return false;
 469                 }
 470             } else if ( bounds != null &&
 471                         ( x >= (bounds.x+bounds.width-offset+5) ||
 472                           x <= (bounds.x + 5) ) &&
 473                         offset < (bounds.width - 5) ) {
 474                 return false;
 475             }
 476         }
 477         return true;
 478     }
 479 
 480     /**
 481      * Determine the offset.
 482      * @param tree      a <code>JTree</code> object
 483      * @param value a value
 484      * @param isSelected selection status
 485      * @param expanded expansion status
 486      * @param leaf leaf status
 487      * @param row current row
 488      */
 489     protected void determineOffset(JTree tree, Object value,
 490                                    boolean isSelected, boolean expanded,
 491                                    boolean leaf, int row) {
 492         if(renderer != null) {
 493             if(leaf)
 494                 editingIcon = renderer.getLeafIcon();
 495             else if(expanded)
 496                 editingIcon = renderer.getOpenIcon();
 497             else
 498                 editingIcon = renderer.getClosedIcon();
 499             if(editingIcon != null)
 500                 offset = renderer.getIconTextGap() +
 501                          editingIcon.getIconWidth();
 502             else
 503                 offset = renderer.getIconTextGap();
 504         }
 505         else {
 506             editingIcon = null;
 507             offset = 0;
 508         }
 509     }
 510 
 511     /**
 512      * Invoked just before editing is to start. Will add the
 513      * <code>editingComponent</code> to the
 514      * <code>editingContainer</code>.
 515      */
 516     protected void prepareForEditing() {
 517         if (editingComponent != null) {
 518             editingContainer.add(editingComponent);
 519         }
 520     }
 521 
 522     /**
 523      * Creates the container to manage placement of
 524      * <code>editingComponent</code>.
 525      *
 526      * @return new Container object
 527      */
 528     protected Container createContainer() {
 529         return new EditorContainer();
 530     }
 531 
 532     /**
 533      * This is invoked if a <code>TreeCellEditor</code>
 534      * is not supplied in the constructor.
 535      * It returns a <code>TextField</code> editor.
 536      * @return a new <code>TextField</code> editor
 537      */
 538     protected TreeCellEditor createTreeCellEditor() {
 539         Border              aBorder = UIManager.getBorder("Tree.editorBorder");
 540         @SuppressWarnings("serial") // Safe: outer class is non-serializable
 541         DefaultCellEditor   editor = new DefaultCellEditor
 542             (new DefaultTextField(aBorder)) {
 543             public boolean shouldSelectCell(EventObject event) {
 544                 boolean retValue = super.shouldSelectCell(event);
 545                 return retValue;
 546             }
 547         };
 548 
 549         // One click to edit.
 550         editor.setClickCountToStart(1);
 551         return editor;
 552     }
 553 
 554     /**
 555      * Cleans up any state after editing has completed. Removes the
 556      * <code>editingComponent</code> the <code>editingContainer</code>.
 557      */
 558     private void cleanupAfterEditing() {
 559         if (editingComponent != null) {
 560             editingContainer.remove(editingComponent);
 561         }
 562         editingComponent = null;
 563     }
 564 
 565     /**
 566      * <code>TextField</code> used when no editor is supplied.
 567      * This textfield locks into the border it is constructed with.
 568      * It also prefers its parents font over its font. And if the
 569      * renderer is not <code>null</code> and no font
 570      * has been specified the preferred height is that of the renderer.
 571      */
 572     @SuppressWarnings("serial") // Safe: outer class is non-serializable
 573     public class DefaultTextField extends JTextField {
 574         /** Border to use. */
 575         protected Border         border;
 576 
 577         /**
 578          * Constructs a
 579          * <code>DefaultTreeCellEditor.DefaultTextField</code> object.
 580          *
 581          * @param border  a <code>Border</code> object
 582          * @since 1.4
 583          */
 584         public DefaultTextField(Border border) {
 585             setBorder(border);
 586         }
 587 
 588         /**
 589          * Sets the border of this component.<p>
 590          * This is a bound property.
 591          *
 592          * @param border the border to be rendered for this component
 593          * @see Border
 594          * @see CompoundBorder
 595          */
 596         @BeanProperty(preferred = true, visualUpdate = true, description
 597                 = "The component's border.")
 598         public void setBorder(Border border) {
 599             super.setBorder(border);
 600             this.border = border;
 601         }
 602 
 603         /**
 604          * Overrides <code>JComponent.getBorder</code> to
 605          * returns the current border.
 606          */
 607         public Border getBorder() {
 608             return border;
 609         }
 610 
 611         // implements java.awt.MenuContainer
 612         public Font getFont() {
 613             Font     font = super.getFont();
 614 
 615             // Prefer the parent containers font if our font is a
 616             // FontUIResource
 617             if(font instanceof FontUIResource) {
 618                 Container     parent = getParent();
 619 
 620                 if(parent != null && parent.getFont() != null)
 621                     font = parent.getFont();
 622             }
 623             return font;
 624         }
 625 
 626         /**
 627          * Overrides <code>JTextField.getPreferredSize</code> to
 628          * return the preferred size based on current font, if set,
 629          * or else use renderer's font.
 630          * @return a <code>Dimension</code> object containing
 631          *   the preferred size
 632          */
 633         public Dimension getPreferredSize() {
 634             Dimension      size = super.getPreferredSize();
 635 
 636             // If not font has been set, prefer the renderers height.
 637             if(renderer != null &&
 638                DefaultTreeCellEditor.this.getFont() == null) {
 639                 Dimension     rSize = renderer.getPreferredSize();
 640 
 641                 size.height = rSize.height;
 642             }
 643             return size;
 644         }
 645     }
 646 
 647 
 648     /**
 649      * Container responsible for placing the <code>editingComponent</code>.
 650      */
 651     @SuppressWarnings("serial") // Safe: outer class is non-serializable
 652     public class EditorContainer extends Container {
 653         /**
 654          * Constructs an <code>EditorContainer</code> object.
 655          */
 656         public EditorContainer() {
 657             setLayout(null);
 658         }
 659 
 660         // This should not be used. It will be removed when new API is
 661         // allowed.
 662         /**
 663          * Do not use.
 664          */
 665         public void EditorContainer() {
 666             setLayout(null);
 667         }
 668 
 669         /**
 670          * Overrides <code>Container.paint</code> to paint the node's
 671          * icon and use the selection color for the background.
 672          */
 673         public void paint(Graphics g) {
 674             int width = getWidth();
 675             int height = getHeight();
 676 
 677             // Then the icon.
 678             if(editingIcon != null) {
 679                 int yLoc = calculateIconY(editingIcon);
 680 
 681                 if (getComponentOrientation().isLeftToRight()) {
 682                     editingIcon.paintIcon(this, g, 0, yLoc);
 683                 } else {
 684                     editingIcon.paintIcon(
 685                             this, g, width - editingIcon.getIconWidth(),
 686                             yLoc);
 687                 }
 688             }
 689 
 690             // Border selection color
 691             Color       background = getBorderSelectionColor();
 692             if(background != null) {
 693                 g.setColor(background);
 694                 g.drawRect(0, 0, width - 1, height - 1);
 695             }
 696             super.paint(g);
 697         }
 698 
 699         /**
 700          * Lays out this <code>Container</code>.  If editing,
 701          * the editor will be placed at
 702          * <code>offset</code> in the x direction and 0 for y.
 703          */
 704         public void doLayout() {
 705             if(editingComponent != null) {
 706                 int width = getWidth();
 707                 int height = getHeight();
 708                 if (getComponentOrientation().isLeftToRight()) {
 709                     editingComponent.setBounds(
 710                             offset, 0, width - offset, height);
 711                 } else {
 712                     editingComponent.setBounds(
 713                         0, 0, width - offset, height);
 714                 }
 715             }
 716         }
 717 
 718         /**
 719          * Calculate the y location for the icon.
 720          */
 721         private int calculateIconY(Icon icon) {
 722             // To make sure the icon position matches that of the
 723             // renderer, use the same algorithm as JLabel
 724             // (SwingUtilities.layoutCompoundLabel).
 725             int iconHeight = icon.getIconHeight();
 726             int textHeight = editingComponent.getFontMetrics(
 727                 editingComponent.getFont()).getHeight();
 728             int textY = iconHeight / 2 - textHeight / 2;
 729             int totalY = Math.min(0, textY);
 730             int totalHeight = Math.max(iconHeight, textY + textHeight) -
 731                 totalY;
 732             return getHeight() / 2 - (totalY + (totalHeight / 2));
 733         }
 734 
 735         /**
 736          * Returns the preferred size for the <code>Container</code>.
 737          * This will be at least preferred size of the editor plus
 738          * <code>offset</code>.
 739          * @return a <code>Dimension</code> containing the preferred
 740          *   size for the <code>Container</code>; if
 741          *   <code>editingComponent</code> is <code>null</code> the
 742          *   <code>Dimension</code> returned is 0, 0
 743          */
 744         public Dimension getPreferredSize() {
 745             if(editingComponent != null) {
 746                 Dimension         pSize = editingComponent.getPreferredSize();
 747 
 748                 pSize.width += offset + 5;
 749 
 750                 Dimension         rSize = (renderer != null) ?
 751                                           renderer.getPreferredSize() : null;
 752 
 753                 if(rSize != null)
 754                     pSize.height = Math.max(pSize.height, rSize.height);
 755                 if(editingIcon != null)
 756                     pSize.height = Math.max(pSize.height,
 757                                             editingIcon.getIconHeight());
 758 
 759                 // Make sure width is at least 100.
 760                 pSize.width = Math.max(pSize.width, 100);
 761                 return pSize;
 762             }
 763             return new Dimension(0, 0);
 764         }
 765     }
 766 }