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