1 /*
   2  * Copyright (c) 1998, 2008, 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      * @param event  the event being studied
 409      */
 410     protected boolean shouldStartEditingTimer(EventObject event) {
 411         if((event instanceof MouseEvent) &&
 412             SwingUtilities.isLeftMouseButton((MouseEvent)event)) {
 413             MouseEvent        me = (MouseEvent)event;
 414 
 415             return (me.getClickCount() == 1 &&
 416                     inHitRegion(me.getX(), me.getY()));
 417         }
 418         return false;
 419     }
 420 
 421     /**
 422      * Starts the editing timer.
 423      */
 424     protected void startEditingTimer() {
 425         if(timer == null) {
 426             timer = new Timer(1200, this);
 427             timer.setRepeats(false);
 428         }
 429         timer.start();
 430     }
 431 
 432     /**
 433      * Returns true if <code>event</code> is <code>null</code>,
 434      * or it is a <code>MouseEvent</code> with a click count &gt; 2
 435      * and <code>inHitRegion</code> returns true.
 436      * @param event the event being studied
 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     protected void determineOffset(JTree tree, Object value,
 481                                    boolean isSelected, boolean expanded,
 482                                    boolean leaf, int row) {
 483         if(renderer != null) {
 484             if(leaf)
 485                 editingIcon = renderer.getLeafIcon();
 486             else if(expanded)
 487                 editingIcon = renderer.getOpenIcon();
 488             else
 489                 editingIcon = renderer.getClosedIcon();
 490             if(editingIcon != null)
 491                 offset = renderer.getIconTextGap() +
 492                          editingIcon.getIconWidth();
 493             else
 494                 offset = renderer.getIconTextGap();
 495         }
 496         else {
 497             editingIcon = null;
 498             offset = 0;
 499         }
 500     }
 501 
 502     /**
 503      * Invoked just before editing is to start. Will add the
 504      * <code>editingComponent</code> to the
 505      * <code>editingContainer</code>.
 506      */
 507     protected void prepareForEditing() {
 508         if (editingComponent != null) {
 509             editingContainer.add(editingComponent);
 510         }
 511     }
 512 
 513     /**
 514      * Creates the container to manage placement of
 515      * <code>editingComponent</code>.
 516      */
 517     protected Container createContainer() {
 518         return new EditorContainer();
 519     }
 520 
 521     /**
 522      * This is invoked if a <code>TreeCellEditor</code>
 523      * is not supplied in the constructor.
 524      * It returns a <code>TextField</code> editor.
 525      * @return a new <code>TextField</code> editor
 526      */
 527     protected TreeCellEditor createTreeCellEditor() {
 528         Border              aBorder = UIManager.getBorder("Tree.editorBorder");
 529         DefaultCellEditor   editor = new DefaultCellEditor
 530             (new DefaultTextField(aBorder)) {
 531             public boolean shouldSelectCell(EventObject event) {
 532                 boolean retValue = super.shouldSelectCell(event);
 533                 return retValue;
 534             }
 535         };
 536 
 537         // One click to edit.
 538         editor.setClickCountToStart(1);
 539         return editor;
 540     }
 541 
 542     /**
 543      * Cleans up any state after editing has completed. Removes the
 544      * <code>editingComponent</code> the <code>editingContainer</code>.
 545      */
 546     private void cleanupAfterEditing() {
 547         if (editingComponent != null) {
 548             editingContainer.remove(editingComponent);
 549         }
 550         editingComponent = null;
 551     }
 552 
 553     // Serialization support.
 554     private void writeObject(ObjectOutputStream s) throws IOException {
 555         Vector<Object> values = new Vector<Object>();
 556 
 557         s.defaultWriteObject();
 558         // Save the realEditor, if its Serializable.
 559         if(realEditor != null && realEditor instanceof Serializable) {
 560             values.addElement("realEditor");
 561             values.addElement(realEditor);
 562         }
 563         s.writeObject(values);
 564     }
 565 
 566     private void readObject(ObjectInputStream s)
 567         throws IOException, ClassNotFoundException {
 568         s.defaultReadObject();
 569 
 570         Vector          values = (Vector)s.readObject();
 571         int             indexCounter = 0;
 572         int             maxCounter = values.size();
 573 
 574         if(indexCounter < maxCounter && values.elementAt(indexCounter).
 575            equals("realEditor")) {
 576             realEditor = (TreeCellEditor)values.elementAt(++indexCounter);
 577             indexCounter++;
 578         }
 579     }
 580 
 581 
 582     /**
 583      * <code>TextField</code> used when no editor is supplied.
 584      * This textfield locks into the border it is constructed with.
 585      * It also prefers its parents font over its font. And if the
 586      * renderer is not <code>null</code> and no font
 587      * has been specified the preferred height is that of the renderer.
 588      */
 589     public class DefaultTextField extends JTextField {
 590         /** Border to use. */
 591         protected Border         border;
 592 
 593         /**
 594          * Constructs a
 595          * <code>DefaultTreeCellEditor.DefaultTextField</code> object.
 596          *
 597          * @param border  a <code>Border</code> object
 598          * @since 1.4
 599          */
 600         public DefaultTextField(Border border) {
 601             setBorder(border);
 602         }
 603 
 604         /**
 605          * Sets the border of this component.<p>
 606          * This is a bound property.
 607          *
 608          * @param border the border to be rendered for this component
 609          * @see Border
 610          * @see CompoundBorder
 611          * @beaninfo
 612          *        bound: true
 613          *    preferred: true
 614          *    attribute: visualUpdate true
 615          *  description: The component's border.
 616          */
 617         public void setBorder(Border border) {
 618             super.setBorder(border);
 619             this.border = border;
 620         }
 621 
 622         /**
 623          * Overrides <code>JComponent.getBorder</code> to
 624          * returns the current border.
 625          */
 626         public Border getBorder() {
 627             return border;
 628         }
 629 
 630         // implements java.awt.MenuContainer
 631         public Font getFont() {
 632             Font     font = super.getFont();
 633 
 634             // Prefer the parent containers font if our font is a
 635             // FontUIResource
 636             if(font instanceof FontUIResource) {
 637                 Container     parent = getParent();
 638 
 639                 if(parent != null && parent.getFont() != null)
 640                     font = parent.getFont();
 641             }
 642             return font;
 643         }
 644 
 645         /**
 646          * Overrides <code>JTextField.getPreferredSize</code> to
 647          * return the preferred size based on current font, if set,
 648          * or else use renderer's font.
 649          * @return a <code>Dimension</code> object containing
 650          *   the preferred size
 651          */
 652         public Dimension getPreferredSize() {
 653             Dimension      size = super.getPreferredSize();
 654 
 655             // If not font has been set, prefer the renderers height.
 656             if(renderer != null &&
 657                DefaultTreeCellEditor.this.getFont() == null) {
 658                 Dimension     rSize = renderer.getPreferredSize();
 659 
 660                 size.height = rSize.height;
 661             }
 662             return size;
 663         }
 664     }
 665 
 666 
 667     /**
 668      * Container responsible for placing the <code>editingComponent</code>.
 669      */
 670     public class EditorContainer extends Container {
 671         /**
 672          * Constructs an <code>EditorContainer</code> object.
 673          */
 674         public EditorContainer() {
 675             setLayout(null);
 676         }
 677 
 678         // This should not be used. It will be removed when new API is
 679         // allowed.
 680         public void EditorContainer() {
 681             setLayout(null);
 682         }
 683 
 684         /**
 685          * Overrides <code>Container.paint</code> to paint the node's
 686          * icon and use the selection color for the background.
 687          */
 688         public void paint(Graphics g) {
 689             int width = getWidth();
 690             int height = getHeight();
 691 
 692             // Then the icon.
 693             if(editingIcon != null) {
 694                 int yLoc = calculateIconY(editingIcon);
 695 
 696                 if (getComponentOrientation().isLeftToRight()) {
 697                     editingIcon.paintIcon(this, g, 0, yLoc);
 698                 } else {
 699                     editingIcon.paintIcon(
 700                             this, g, width - editingIcon.getIconWidth(),
 701                             yLoc);
 702                 }
 703             }
 704 
 705             // Border selection color
 706             Color       background = getBorderSelectionColor();
 707             if(background != null) {
 708                 g.setColor(background);
 709                 g.drawRect(0, 0, width - 1, height - 1);
 710             }
 711             super.paint(g);
 712         }
 713 
 714         /**
 715          * Lays out this <code>Container</code>.  If editing,
 716          * the editor will be placed at
 717          * <code>offset</code> in the x direction and 0 for y.
 718          */
 719         public void doLayout() {
 720             if(editingComponent != null) {
 721                 int width = getWidth();
 722                 int height = getHeight();
 723                 if (getComponentOrientation().isLeftToRight()) {
 724                     editingComponent.setBounds(
 725                             offset, 0, width - offset, height);
 726                 } else {
 727                     editingComponent.setBounds(
 728                         0, 0, width - offset, height);
 729                 }
 730             }
 731         }
 732 
 733         /**
 734          * Calculate the y location for the icon.
 735          */
 736         private int calculateIconY(Icon icon) {
 737             // To make sure the icon position matches that of the
 738             // renderer, use the same algorithm as JLabel
 739             // (SwingUtilities.layoutCompoundLabel).
 740             int iconHeight = icon.getIconHeight();
 741             int textHeight = editingComponent.getFontMetrics(
 742                 editingComponent.getFont()).getHeight();
 743             int textY = iconHeight / 2 - textHeight / 2;
 744             int totalY = Math.min(0, textY);
 745             int totalHeight = Math.max(iconHeight, textY + textHeight) -
 746                 totalY;
 747             return getHeight() / 2 - (totalY + (totalHeight / 2));
 748         }
 749 
 750         /**
 751          * Returns the preferred size for the <code>Container</code>.
 752          * This will be at least preferred size of the editor plus
 753          * <code>offset</code>.
 754          * @return a <code>Dimension</code> containing the preferred
 755          *   size for the <code>Container</code>; if
 756          *   <code>editingComponent</code> is <code>null</code> the
 757          *   <code>Dimension</code> returned is 0, 0
 758          */
 759         public Dimension getPreferredSize() {
 760             if(editingComponent != null) {
 761                 Dimension         pSize = editingComponent.getPreferredSize();
 762 
 763                 pSize.width += offset + 5;
 764 
 765                 Dimension         rSize = (renderer != null) ?
 766                                           renderer.getPreferredSize() : null;
 767 
 768                 if(rSize != null)
 769                     pSize.height = Math.max(pSize.height, rSize.height);
 770                 if(editingIcon != null)
 771                     pSize.height = Math.max(pSize.height,
 772                                             editingIcon.getIconHeight());
 773 
 774                 // Make sure width is at least 100.
 775                 pSize.width = Math.max(pSize.width, 100);
 776                 return pSize;
 777             }
 778             return new Dimension(0, 0);
 779         }
 780     }
 781 }