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