1 /*
   2  * Copyright (c) 1998, 2006, 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.plaf.metal;
  27 
  28 import javax.swing.*;
  29 import javax.swing.border.EmptyBorder;
  30 import javax.swing.filechooser.*;
  31 import javax.swing.event.*;
  32 import javax.swing.plaf.*;
  33 import javax.swing.plaf.basic.*;
  34 import java.awt.*;
  35 import java.awt.event.*;
  36 import java.beans.*;
  37 import java.io.File;
  38 import java.io.FileNotFoundException;
  39 import java.io.IOException;
  40 import java.util.*;
  41 import java.security.AccessController;
  42 import java.security.PrivilegedAction;
  43 import javax.accessibility.*;
  44 
  45 import sun.awt.shell.ShellFolder;
  46 import sun.swing.*;
  47 
  48 /**
  49  * Metal L&F implementation of a FileChooser.
  50  *
  51  * @author Jeff Dinkins
  52  */
  53 public class MetalFileChooserUI extends BasicFileChooserUI {
  54 
  55     // Much of the Metal UI for JFilechooser is just a copy of
  56     // the windows implementation, but using Metal themed buttons, lists,
  57     // icons, etc. We are planning a complete rewrite, and hence we've
  58     // made most things in this class private.
  59 
  60     private JLabel lookInLabel;
  61     private JComboBox directoryComboBox;
  62     private DirectoryComboBoxModel directoryComboBoxModel;
  63     private Action directoryComboBoxAction = new DirectoryComboBoxAction();
  64 
  65     private FilterComboBoxModel filterComboBoxModel;
  66 
  67     private JTextField fileNameTextField;
  68 
  69     private FilePane filePane;
  70     private JToggleButton listViewButton;
  71     private JToggleButton detailsViewButton;
  72 
  73     private boolean useShellFolder;
  74 
  75     private JButton approveButton;
  76     private JButton cancelButton;
  77 
  78     private JPanel buttonPanel;
  79     private JPanel bottomPanel;
  80 
  81     private JComboBox filterComboBox;
  82 
  83     private static final Dimension hstrut5 = new Dimension(5, 1);
  84     private static final Dimension hstrut11 = new Dimension(11, 1);
  85 
  86     private static final Dimension vstrut5  = new Dimension(1, 5);
  87 
  88     private static final Insets shrinkwrap = new Insets(0,0,0,0);
  89 
  90     // Preferred and Minimum sizes for the dialog box
  91     private static int PREF_WIDTH = 500;
  92     private static int PREF_HEIGHT = 326;
  93     private static Dimension PREF_SIZE = new Dimension(PREF_WIDTH, PREF_HEIGHT);
  94 
  95     private static int MIN_WIDTH = 500;
  96     private static int MIN_HEIGHT = 326;
  97     private static Dimension MIN_SIZE = new Dimension(MIN_WIDTH, MIN_HEIGHT);
  98 
  99     private static int LIST_PREF_WIDTH = 405;
 100     private static int LIST_PREF_HEIGHT = 135;
 101     private static Dimension LIST_PREF_SIZE = new Dimension(LIST_PREF_WIDTH, LIST_PREF_HEIGHT);
 102 
 103     // Labels, mnemonics, and tooltips (oh my!)
 104     private int    lookInLabelMnemonic = 0;
 105     private String lookInLabelText = null;
 106     private String saveInLabelText = null;
 107 
 108     private int    fileNameLabelMnemonic = 0;
 109     private String fileNameLabelText = null;
 110     private int    folderNameLabelMnemonic = 0;
 111     private String folderNameLabelText = null;
 112 
 113     private int    filesOfTypeLabelMnemonic = 0;
 114     private String filesOfTypeLabelText = null;
 115 
 116     private String upFolderToolTipText = null;
 117     private String upFolderAccessibleName = null;
 118 
 119     private String homeFolderToolTipText = null;
 120     private String homeFolderAccessibleName = null;
 121 
 122     private String newFolderToolTipText = null;
 123     private String newFolderAccessibleName = null;
 124 
 125     private String listViewButtonToolTipText = null;
 126     private String listViewButtonAccessibleName = null;
 127 
 128     private String detailsViewButtonToolTipText = null;
 129     private String detailsViewButtonAccessibleName = null;
 130 
 131     private AlignedLabel fileNameLabel;
 132 
 133     private void populateFileNameLabel() {
 134         if (getFileChooser().getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) {
 135             fileNameLabel.setText(folderNameLabelText);
 136             fileNameLabel.setDisplayedMnemonic(folderNameLabelMnemonic);
 137         } else {
 138             fileNameLabel.setText(fileNameLabelText);
 139             fileNameLabel.setDisplayedMnemonic(fileNameLabelMnemonic);
 140         }
 141     }
 142 
 143     //
 144     // ComponentUI Interface Implementation methods
 145     //
 146     public static ComponentUI createUI(JComponent c) {
 147         return new MetalFileChooserUI((JFileChooser) c);
 148     }
 149 
 150     public MetalFileChooserUI(JFileChooser filechooser) {
 151         super(filechooser);
 152     }
 153 
 154     public void installUI(JComponent c) {
 155         super.installUI(c);
 156     }
 157 
 158     public void uninstallComponents(JFileChooser fc) {
 159         fc.removeAll();
 160         bottomPanel = null;
 161         buttonPanel = null;
 162     }
 163 
 164     private class MetalFileChooserUIAccessor implements FilePane.FileChooserUIAccessor {
 165         public JFileChooser getFileChooser() {
 166             return MetalFileChooserUI.this.getFileChooser();
 167         }
 168 
 169         public BasicDirectoryModel getModel() {
 170             return MetalFileChooserUI.this.getModel();
 171         }
 172 
 173         public JPanel createList() {
 174             return MetalFileChooserUI.this.createList(getFileChooser());
 175         }
 176 
 177         public JPanel createDetailsView() {
 178             return MetalFileChooserUI.this.createDetailsView(getFileChooser());
 179         }
 180 
 181         public boolean isDirectorySelected() {
 182             return MetalFileChooserUI.this.isDirectorySelected();
 183         }
 184 
 185         public File getDirectory() {
 186             return MetalFileChooserUI.this.getDirectory();
 187         }
 188 
 189         public Action getChangeToParentDirectoryAction() {
 190             return MetalFileChooserUI.this.getChangeToParentDirectoryAction();
 191         }
 192 
 193         public Action getApproveSelectionAction() {
 194             return MetalFileChooserUI.this.getApproveSelectionAction();
 195         }
 196 
 197         public Action getNewFolderAction() {
 198             return MetalFileChooserUI.this.getNewFolderAction();
 199         }
 200 
 201         public MouseListener createDoubleClickListener(JList list) {
 202             return MetalFileChooserUI.this.createDoubleClickListener(getFileChooser(),
 203                                                                      list);
 204         }
 205 
 206         public ListSelectionListener createListSelectionListener() {
 207             return MetalFileChooserUI.this.createListSelectionListener(getFileChooser());
 208         }
 209 
 210         public boolean usesShellFolder() {
 211             return useShellFolder;
 212         }
 213     }
 214 
 215     public void installComponents(JFileChooser fc) {
 216         FileSystemView fsv = fc.getFileSystemView();
 217 
 218         fc.setBorder(new EmptyBorder(12, 12, 11, 11));
 219         fc.setLayout(new BorderLayout(0, 11));
 220 
 221         filePane = new FilePane(new MetalFileChooserUIAccessor());
 222         fc.addPropertyChangeListener(filePane);
 223 
 224         updateUseShellFolder();
 225 
 226         // ********************************* //
 227         // **** Construct the top panel **** //
 228         // ********************************* //
 229 
 230         // Directory manipulation buttons
 231         JPanel topPanel = new JPanel(new BorderLayout(11, 0));
 232         JPanel topButtonPanel = new JPanel();
 233         topButtonPanel.setLayout(new BoxLayout(topButtonPanel, BoxLayout.LINE_AXIS));
 234         topPanel.add(topButtonPanel, BorderLayout.AFTER_LINE_ENDS);
 235 
 236         // Add the top panel to the fileChooser
 237         fc.add(topPanel, BorderLayout.NORTH);
 238 
 239         // ComboBox Label
 240         lookInLabel = new JLabel(lookInLabelText);
 241         lookInLabel.setDisplayedMnemonic(lookInLabelMnemonic);
 242         topPanel.add(lookInLabel, BorderLayout.BEFORE_LINE_BEGINS);
 243 
 244         // CurrentDir ComboBox
 245         directoryComboBox = new JComboBox() {
 246             public Dimension getPreferredSize() {
 247                 Dimension d = super.getPreferredSize();
 248                 // Must be small enough to not affect total width.
 249                 d.width = 150;
 250                 return d;
 251             }
 252         };
 253         directoryComboBox.putClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY,
 254                                             lookInLabelText);
 255         directoryComboBox.putClientProperty( "JComboBox.isTableCellEditor", Boolean.TRUE );
 256         lookInLabel.setLabelFor(directoryComboBox);
 257         directoryComboBoxModel = createDirectoryComboBoxModel(fc);
 258         directoryComboBox.setModel(directoryComboBoxModel);
 259         directoryComboBox.addActionListener(directoryComboBoxAction);
 260         directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
 261         directoryComboBox.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 262         directoryComboBox.setAlignmentY(JComponent.TOP_ALIGNMENT);
 263         directoryComboBox.setMaximumRowCount(8);
 264 
 265         topPanel.add(directoryComboBox, BorderLayout.CENTER);
 266 
 267         // Up Button
 268         JButton upFolderButton = new JButton(getChangeToParentDirectoryAction());
 269         upFolderButton.setText(null);
 270         upFolderButton.setIcon(upFolderIcon);
 271         upFolderButton.setToolTipText(upFolderToolTipText);
 272         upFolderButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
 273                                          upFolderAccessibleName);
 274         upFolderButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 275         upFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 276         upFolderButton.setMargin(shrinkwrap);
 277 
 278         topButtonPanel.add(upFolderButton);
 279         topButtonPanel.add(Box.createRigidArea(hstrut5));
 280 
 281         // Home Button
 282         File homeDir = fsv.getHomeDirectory();
 283         String toolTipText = homeFolderToolTipText;
 284         if (fsv.isRoot(homeDir)) {
 285             toolTipText = getFileView(fc).getName(homeDir); // Probably "Desktop".
 286         }
 287 
 288 
 289 
 290 
 291         JButton b = new JButton(homeFolderIcon);
 292         b.setToolTipText(toolTipText);
 293         b.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
 294                             homeFolderAccessibleName);
 295         b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 296         b.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 297         b.setMargin(shrinkwrap);
 298 
 299         b.addActionListener(getGoHomeAction());
 300         topButtonPanel.add(b);
 301         topButtonPanel.add(Box.createRigidArea(hstrut5));
 302 
 303         // New Directory Button
 304         if (!UIManager.getBoolean("FileChooser.readOnly")) {
 305             b = new JButton(filePane.getNewFolderAction());
 306             b.setText(null);
 307             b.setIcon(newFolderIcon);
 308             b.setToolTipText(newFolderToolTipText);
 309             b.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
 310                                 newFolderAccessibleName);
 311             b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 312             b.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 313             b.setMargin(shrinkwrap);
 314         }
 315         topButtonPanel.add(b);
 316         topButtonPanel.add(Box.createRigidArea(hstrut5));
 317 
 318         // View button group
 319         ButtonGroup viewButtonGroup = new ButtonGroup();
 320 
 321         // List Button
 322         listViewButton = new JToggleButton(listViewIcon);
 323         listViewButton.setToolTipText(listViewButtonToolTipText);
 324         listViewButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
 325                                          listViewButtonAccessibleName);
 326         listViewButton.setSelected(true);
 327         listViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 328         listViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 329         listViewButton.setMargin(shrinkwrap);
 330         listViewButton.addActionListener(filePane.getViewTypeAction(FilePane.VIEWTYPE_LIST));
 331         topButtonPanel.add(listViewButton);
 332         viewButtonGroup.add(listViewButton);
 333 
 334         // Details Button
 335         detailsViewButton = new JToggleButton(detailsViewIcon);
 336         detailsViewButton.setToolTipText(detailsViewButtonToolTipText);
 337         detailsViewButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
 338                                             detailsViewButtonAccessibleName);
 339         detailsViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 340         detailsViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 341         detailsViewButton.setMargin(shrinkwrap);
 342         detailsViewButton.addActionListener(filePane.getViewTypeAction(FilePane.VIEWTYPE_DETAILS));
 343         topButtonPanel.add(detailsViewButton);
 344         viewButtonGroup.add(detailsViewButton);
 345 
 346         filePane.addPropertyChangeListener(new PropertyChangeListener() {
 347             public void propertyChange(PropertyChangeEvent e) {
 348                 if ("viewType".equals(e.getPropertyName())) {
 349                     int viewType = filePane.getViewType();
 350                     switch (viewType) {
 351                       case FilePane.VIEWTYPE_LIST:
 352                         listViewButton.setSelected(true);
 353                         break;
 354 
 355                       case FilePane.VIEWTYPE_DETAILS:
 356                         detailsViewButton.setSelected(true);
 357                         break;
 358                     }
 359                 }
 360             }
 361         });
 362 
 363         // ************************************** //
 364         // ******* Add the directory pane ******* //
 365         // ************************************** //
 366         fc.add(getAccessoryPanel(), BorderLayout.AFTER_LINE_ENDS);
 367         JComponent accessory = fc.getAccessory();
 368         if(accessory != null) {
 369             getAccessoryPanel().add(accessory);
 370         }
 371         filePane.setPreferredSize(LIST_PREF_SIZE);
 372         fc.add(filePane, BorderLayout.CENTER);
 373 
 374         // ********************************** //
 375         // **** Construct the bottom panel ** //
 376         // ********************************** //
 377         JPanel bottomPanel = getBottomPanel();
 378         bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
 379         fc.add(bottomPanel, BorderLayout.SOUTH);
 380 
 381         // FileName label and textfield
 382         JPanel fileNamePanel = new JPanel();
 383         fileNamePanel.setLayout(new BoxLayout(fileNamePanel, BoxLayout.LINE_AXIS));
 384         bottomPanel.add(fileNamePanel);
 385         bottomPanel.add(Box.createRigidArea(vstrut5));
 386 
 387         fileNameLabel = new AlignedLabel();
 388         populateFileNameLabel();
 389         fileNamePanel.add(fileNameLabel);
 390 
 391         fileNameTextField = new JTextField(35) {
 392             public Dimension getMaximumSize() {
 393                 return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height);
 394             }
 395         };
 396         fileNamePanel.add(fileNameTextField);
 397         fileNameLabel.setLabelFor(fileNameTextField);
 398         fileNameTextField.addFocusListener(
 399             new FocusAdapter() {
 400                 public void focusGained(FocusEvent e) {
 401                     if (!getFileChooser().isMultiSelectionEnabled()) {
 402                         filePane.clearSelection();
 403                     }
 404                 }
 405             }
 406         );
 407         if (fc.isMultiSelectionEnabled()) {
 408             setFileName(fileNameString(fc.getSelectedFiles()));
 409         } else {
 410             setFileName(fileNameString(fc.getSelectedFile()));
 411         }
 412 
 413 
 414         // Filetype label and combobox
 415         JPanel filesOfTypePanel = new JPanel();
 416         filesOfTypePanel.setLayout(new BoxLayout(filesOfTypePanel, BoxLayout.LINE_AXIS));
 417         bottomPanel.add(filesOfTypePanel);
 418 
 419         AlignedLabel filesOfTypeLabel = new AlignedLabel(filesOfTypeLabelText);
 420         filesOfTypeLabel.setDisplayedMnemonic(filesOfTypeLabelMnemonic);
 421         filesOfTypePanel.add(filesOfTypeLabel);
 422 
 423         filterComboBoxModel = createFilterComboBoxModel();
 424         fc.addPropertyChangeListener(filterComboBoxModel);
 425         filterComboBox = new JComboBox(filterComboBoxModel);
 426         filterComboBox.putClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY,
 427                                          filesOfTypeLabelText);
 428         filesOfTypeLabel.setLabelFor(filterComboBox);
 429         filterComboBox.setRenderer(createFilterComboBoxRenderer());
 430         filesOfTypePanel.add(filterComboBox);
 431 
 432         // buttons
 433         getButtonPanel().setLayout(new ButtonAreaLayout());
 434 
 435         approveButton = new JButton(getApproveButtonText(fc));
 436         // Note: Metal does not use mnemonics for approve and cancel
 437         approveButton.addActionListener(getApproveSelectionAction());
 438         approveButton.setToolTipText(getApproveButtonToolTipText(fc));
 439         getButtonPanel().add(approveButton);
 440 
 441         cancelButton = new JButton(cancelButtonText);
 442         cancelButton.setToolTipText(cancelButtonToolTipText);
 443         cancelButton.addActionListener(getCancelSelectionAction());
 444         getButtonPanel().add(cancelButton);
 445 
 446         if(fc.getControlButtonsAreShown()) {
 447             addControlButtons();
 448         }
 449 
 450         groupLabels(new AlignedLabel[] { fileNameLabel, filesOfTypeLabel });
 451     }
 452 
 453     private void updateUseShellFolder() {
 454         // Decide whether to use the ShellFolder class to populate shortcut
 455         // panel and combobox.
 456         JFileChooser fc = getFileChooser();
 457         Boolean prop =
 458             (Boolean)fc.getClientProperty("FileChooser.useShellFolder");
 459         if (prop != null) {
 460             useShellFolder = prop.booleanValue();
 461         } else {
 462             useShellFolder = fc.getFileSystemView().equals(FileSystemView.getFileSystemView());
 463         }
 464     }
 465 
 466     protected JPanel getButtonPanel() {
 467         if (buttonPanel == null) {
 468             buttonPanel = new JPanel();
 469         }
 470         return buttonPanel;
 471     }
 472 
 473     protected JPanel getBottomPanel() {
 474         if(bottomPanel == null) {
 475             bottomPanel = new JPanel();
 476         }
 477         return bottomPanel;
 478     }
 479 
 480     protected void installStrings(JFileChooser fc) {
 481         super.installStrings(fc);
 482 
 483         Locale l = fc.getLocale();
 484 
 485         lookInLabelMnemonic = UIManager.getInt("FileChooser.lookInLabelMnemonic");
 486         lookInLabelText = UIManager.getString("FileChooser.lookInLabelText",l);
 487         saveInLabelText = UIManager.getString("FileChooser.saveInLabelText",l);
 488 
 489         fileNameLabelMnemonic = UIManager.getInt("FileChooser.fileNameLabelMnemonic");
 490         fileNameLabelText = UIManager.getString("FileChooser.fileNameLabelText",l);
 491         folderNameLabelMnemonic = UIManager.getInt("FileChooser.folderNameLabelMnemonic");
 492         folderNameLabelText = UIManager.getString("FileChooser.folderNameLabelText",l);
 493 
 494         filesOfTypeLabelMnemonic = UIManager.getInt("FileChooser.filesOfTypeLabelMnemonic");
 495         filesOfTypeLabelText = UIManager.getString("FileChooser.filesOfTypeLabelText",l);
 496 
 497         upFolderToolTipText =  UIManager.getString("FileChooser.upFolderToolTipText",l);
 498         upFolderAccessibleName = UIManager.getString("FileChooser.upFolderAccessibleName",l);
 499 
 500         homeFolderToolTipText =  UIManager.getString("FileChooser.homeFolderToolTipText",l);
 501         homeFolderAccessibleName = UIManager.getString("FileChooser.homeFolderAccessibleName",l);
 502 
 503         newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText",l);
 504         newFolderAccessibleName = UIManager.getString("FileChooser.newFolderAccessibleName",l);
 505 
 506         listViewButtonToolTipText = UIManager.getString("FileChooser.listViewButtonToolTipText",l);
 507         listViewButtonAccessibleName = UIManager.getString("FileChooser.listViewButtonAccessibleName",l);
 508 
 509         detailsViewButtonToolTipText = UIManager.getString("FileChooser.detailsViewButtonToolTipText",l);
 510         detailsViewButtonAccessibleName = UIManager.getString("FileChooser.detailsViewButtonAccessibleName",l);
 511     }
 512 
 513     protected void installListeners(JFileChooser fc) {
 514         super.installListeners(fc);
 515         ActionMap actionMap = getActionMap();
 516         SwingUtilities.replaceUIActionMap(fc, actionMap);
 517     }
 518 
 519     protected ActionMap getActionMap() {
 520         return createActionMap();
 521     }
 522 
 523     protected ActionMap createActionMap() {
 524         ActionMap map = new ActionMapUIResource();
 525         FilePane.addActionsToMap(map, filePane.getActions());
 526         return map;
 527     }
 528 
 529     protected JPanel createList(JFileChooser fc) {
 530         return filePane.createList();
 531     }
 532 
 533     protected JPanel createDetailsView(JFileChooser fc) {
 534         return filePane.createDetailsView();
 535     }
 536 
 537     /**
 538      * Creates a selection listener for the list of files and directories.
 539      *
 540      * @param fc a <code>JFileChooser</code>
 541      * @return a <code>ListSelectionListener</code>
 542      */
 543     public ListSelectionListener createListSelectionListener(JFileChooser fc) {
 544         return super.createListSelectionListener(fc);
 545     }
 546 
 547     // Obsolete class, not used in this version.
 548     protected class SingleClickListener extends MouseAdapter {
 549         public  SingleClickListener(JList list) {
 550         }
 551     }
 552 
 553     // Obsolete class, not used in this version.
 554     protected class FileRenderer extends DefaultListCellRenderer  {
 555     }
 556 
 557     public void uninstallUI(JComponent c) {
 558         // Remove listeners
 559         c.removePropertyChangeListener(filterComboBoxModel);
 560         c.removePropertyChangeListener(filePane);
 561         cancelButton.removeActionListener(getCancelSelectionAction());
 562         approveButton.removeActionListener(getApproveSelectionAction());
 563         fileNameTextField.removeActionListener(getApproveSelectionAction());
 564 
 565         if (filePane != null) {
 566             filePane.uninstallUI();
 567             filePane = null;
 568         }
 569 
 570         super.uninstallUI(c);
 571     }
 572 
 573     /**
 574      * Returns the preferred size of the specified
 575      * <code>JFileChooser</code>.
 576      * The preferred size is at least as large,
 577      * in both height and width,
 578      * as the preferred size recommended
 579      * by the file chooser's layout manager.
 580      *
 581      * @param c  a <code>JFileChooser</code>
 582      * @return   a <code>Dimension</code> specifying the preferred
 583      *           width and height of the file chooser
 584      */
 585     public Dimension getPreferredSize(JComponent c) {
 586         int prefWidth = PREF_SIZE.width;
 587         Dimension d = c.getLayout().preferredLayoutSize(c);
 588         if (d != null) {
 589             return new Dimension(d.width < prefWidth ? prefWidth : d.width,
 590                                  d.height < PREF_SIZE.height ? PREF_SIZE.height : d.height);
 591         } else {
 592             return new Dimension(prefWidth, PREF_SIZE.height);
 593         }
 594     }
 595 
 596     /**
 597      * Returns the minimum size of the <code>JFileChooser</code>.
 598      *
 599      * @param c  a <code>JFileChooser</code>
 600      * @return   a <code>Dimension</code> specifying the minimum
 601      *           width and height of the file chooser
 602      */
 603     public Dimension getMinimumSize(JComponent c) {
 604         return MIN_SIZE;
 605     }
 606 
 607     /**
 608      * Returns the maximum size of the <code>JFileChooser</code>.
 609      *
 610      * @param c  a <code>JFileChooser</code>
 611      * @return   a <code>Dimension</code> specifying the maximum
 612      *           width and height of the file chooser
 613      */
 614     public Dimension getMaximumSize(JComponent c) {
 615         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 616     }
 617 
 618     private String fileNameString(File file) {
 619         if (file == null) {
 620             return null;
 621         } else {
 622             JFileChooser fc = getFileChooser();
 623             if ((fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) ||
 624                 (fc.isDirectorySelectionEnabled() && fc.isFileSelectionEnabled() && fc.getFileSystemView().isFileSystemRoot(file))) {
 625                 return file.getPath();
 626             } else {
 627                 return file.getName();
 628             }
 629         }
 630     }
 631 
 632     private String fileNameString(File[] files) {
 633         StringBuffer buf = new StringBuffer();
 634         for (int i = 0; files != null && i < files.length; i++) {
 635             if (i > 0) {
 636                 buf.append(" ");
 637             }
 638             if (files.length > 1) {
 639                 buf.append("\"");
 640             }
 641             buf.append(fileNameString(files[i]));
 642             if (files.length > 1) {
 643                 buf.append("\"");
 644             }
 645         }
 646         return buf.toString();
 647     }
 648 
 649     /* The following methods are used by the PropertyChange Listener */
 650 
 651     private void doSelectedFileChanged(PropertyChangeEvent e) {
 652         File f = (File) e.getNewValue();
 653         JFileChooser fc = getFileChooser();
 654         if (f != null
 655             && ((fc.isFileSelectionEnabled() && !f.isDirectory())
 656                 || (f.isDirectory() && fc.isDirectorySelectionEnabled()))) {
 657 
 658             setFileName(fileNameString(f));
 659         }
 660     }
 661 
 662     private void doSelectedFilesChanged(PropertyChangeEvent e) {
 663         File[] files = (File[]) e.getNewValue();
 664         JFileChooser fc = getFileChooser();
 665         if (files != null
 666             && files.length > 0
 667             && (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) {
 668             setFileName(fileNameString(files));
 669         }
 670     }
 671 
 672     private void doDirectoryChanged(PropertyChangeEvent e) {
 673         JFileChooser fc = getFileChooser();
 674         FileSystemView fsv = fc.getFileSystemView();
 675 
 676         clearIconCache();
 677         File currentDirectory = fc.getCurrentDirectory();
 678         if(currentDirectory != null) {
 679             directoryComboBoxModel.addItem(currentDirectory);
 680 
 681             if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
 682                 if (fsv.isFileSystem(currentDirectory)) {
 683                     setFileName(currentDirectory.getPath());
 684                 } else {
 685                     setFileName(null);
 686                 }
 687             }
 688         }
 689     }
 690 
 691     private void doFilterChanged(PropertyChangeEvent e) {
 692         clearIconCache();
 693     }
 694 
 695     private void doFileSelectionModeChanged(PropertyChangeEvent e) {
 696         if (fileNameLabel != null) {
 697             populateFileNameLabel();
 698         }
 699         clearIconCache();
 700 
 701         JFileChooser fc = getFileChooser();
 702         File currentDirectory = fc.getCurrentDirectory();
 703         if (currentDirectory != null
 704             && fc.isDirectorySelectionEnabled()
 705             && !fc.isFileSelectionEnabled()
 706             && fc.getFileSystemView().isFileSystem(currentDirectory)) {
 707 
 708             setFileName(currentDirectory.getPath());
 709         } else {
 710             setFileName(null);
 711         }
 712     }
 713 
 714     private void doAccessoryChanged(PropertyChangeEvent e) {
 715         if(getAccessoryPanel() != null) {
 716             if(e.getOldValue() != null) {
 717                 getAccessoryPanel().remove((JComponent) e.getOldValue());
 718             }
 719             JComponent accessory = (JComponent) e.getNewValue();
 720             if(accessory != null) {
 721                 getAccessoryPanel().add(accessory, BorderLayout.CENTER);
 722             }
 723         }
 724     }
 725 
 726     private void doApproveButtonTextChanged(PropertyChangeEvent e) {
 727         JFileChooser chooser = getFileChooser();
 728         approveButton.setText(getApproveButtonText(chooser));
 729         approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
 730     }
 731 
 732     private void doDialogTypeChanged(PropertyChangeEvent e) {
 733         JFileChooser chooser = getFileChooser();
 734         approveButton.setText(getApproveButtonText(chooser));
 735         approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
 736         if (chooser.getDialogType() == JFileChooser.SAVE_DIALOG) {
 737             lookInLabel.setText(saveInLabelText);
 738         } else {
 739             lookInLabel.setText(lookInLabelText);
 740         }
 741     }
 742 
 743     private void doApproveButtonMnemonicChanged(PropertyChangeEvent e) {
 744         // Note: Metal does not use mnemonics for approve and cancel
 745     }
 746 
 747     private void doControlButtonsChanged(PropertyChangeEvent e) {
 748         if(getFileChooser().getControlButtonsAreShown()) {
 749             addControlButtons();
 750         } else {
 751             removeControlButtons();
 752         }
 753     }
 754 
 755     /*
 756      * Listen for filechooser property changes, such as
 757      * the selected file changing, or the type of the dialog changing.
 758      */
 759     public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) {
 760         return new PropertyChangeListener() {
 761             public void propertyChange(PropertyChangeEvent e) {
 762                 String s = e.getPropertyName();
 763                 if(s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
 764                     doSelectedFileChanged(e);
 765                 } else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
 766                     doSelectedFilesChanged(e);
 767                 } else if(s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
 768                     doDirectoryChanged(e);
 769                 } else if(s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
 770                     doFilterChanged(e);
 771                 } else if(s.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
 772                     doFileSelectionModeChanged(e);
 773                 } else if(s.equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY)) {
 774                     doAccessoryChanged(e);
 775                 } else if (s.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) ||
 776                            s.equals(JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY)) {
 777                     doApproveButtonTextChanged(e);
 778                 } else if(s.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)) {
 779                     doDialogTypeChanged(e);
 780                 } else if(s.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
 781                     doApproveButtonMnemonicChanged(e);
 782                 } else if(s.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
 783                     doControlButtonsChanged(e);
 784                 } else if (s.equals("componentOrientation")) {
 785                     ComponentOrientation o = (ComponentOrientation)e.getNewValue();
 786                     JFileChooser cc = (JFileChooser)e.getSource();
 787                     if (o != (ComponentOrientation)e.getOldValue()) {
 788                         cc.applyComponentOrientation(o);
 789                     }
 790                 } else if (s == "FileChooser.useShellFolder") {
 791                     updateUseShellFolder();
 792                     doDirectoryChanged(e);
 793                 } else if (s.equals("ancestor")) {
 794                     if (e.getOldValue() == null && e.getNewValue() != null) {
 795                         // Ancestor was added, set initial focus
 796                         fileNameTextField.selectAll();
 797                         fileNameTextField.requestFocus();
 798                     }
 799                 }
 800             }
 801         };
 802     }
 803 
 804 
 805     protected void removeControlButtons() {
 806         getBottomPanel().remove(getButtonPanel());
 807     }
 808 
 809     protected void addControlButtons() {
 810         getBottomPanel().add(getButtonPanel());
 811     }
 812 
 813     public void ensureFileIsVisible(JFileChooser fc, File f) {
 814         filePane.ensureFileIsVisible(fc, f);
 815     }
 816 
 817     public void rescanCurrentDirectory(JFileChooser fc) {
 818         filePane.rescanCurrentDirectory();
 819     }
 820 
 821     public String getFileName() {
 822         if (fileNameTextField != null) {
 823             return fileNameTextField.getText();
 824         } else {
 825             return null;
 826         }
 827     }
 828 
 829     public void setFileName(String filename) {
 830         if (fileNameTextField != null) {
 831             fileNameTextField.setText(filename);
 832         }
 833     }
 834 
 835     /**
 836      * Property to remember whether a directory is currently selected in the UI.
 837      * This is normally called by the UI on a selection event.
 838      *
 839      * @param directorySelected if a directory is currently selected.
 840      * @since 1.4
 841      */
 842     protected void setDirectorySelected(boolean directorySelected) {
 843         super.setDirectorySelected(directorySelected);
 844         JFileChooser chooser = getFileChooser();
 845         if(directorySelected) {
 846             if (approveButton != null) {
 847                 approveButton.setText(directoryOpenButtonText);
 848                 approveButton.setToolTipText(directoryOpenButtonToolTipText);
 849             }
 850         } else {
 851             if (approveButton != null) {
 852                 approveButton.setText(getApproveButtonText(chooser));
 853                 approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
 854             }
 855         }
 856     }
 857 
 858     public String getDirectoryName() {
 859         // PENDING(jeff) - get the name from the directory combobox
 860         return null;
 861     }
 862 
 863     public void setDirectoryName(String dirname) {
 864         // PENDING(jeff) - set the name in the directory combobox
 865     }
 866 
 867     protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(JFileChooser fc) {
 868         return new DirectoryComboBoxRenderer();
 869     }
 870 
 871     //
 872     // Renderer for DirectoryComboBox
 873     //
 874     class DirectoryComboBoxRenderer extends DefaultListCellRenderer  {
 875         IndentIcon ii = new IndentIcon();
 876         public Component getListCellRendererComponent(JList list, Object value,
 877                                                       int index, boolean isSelected,
 878                                                       boolean cellHasFocus) {
 879 
 880             super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
 881 
 882             if (value == null) {
 883                 setText("");
 884                 return this;
 885             }
 886             File directory = (File)value;
 887             setText(getFileChooser().getName(directory));
 888             Icon icon = getFileChooser().getIcon(directory);
 889             ii.icon = icon;
 890             ii.depth = directoryComboBoxModel.getDepth(index);
 891             setIcon(ii);
 892 
 893             return this;
 894         }
 895     }
 896 
 897     final static int space = 10;
 898     class IndentIcon implements Icon {
 899 
 900         Icon icon = null;
 901         int depth = 0;
 902 
 903         public void paintIcon(Component c, Graphics g, int x, int y) {
 904             if (c.getComponentOrientation().isLeftToRight()) {
 905                 icon.paintIcon(c, g, x+depth*space, y);
 906             } else {
 907                 icon.paintIcon(c, g, x, y);
 908             }
 909         }
 910 
 911         public int getIconWidth() {
 912             return icon.getIconWidth() + depth*space;
 913         }
 914 
 915         public int getIconHeight() {
 916             return icon.getIconHeight();
 917         }
 918 
 919     }
 920 
 921     //
 922     // DataModel for DirectoryComboxbox
 923     //
 924     protected DirectoryComboBoxModel createDirectoryComboBoxModel(JFileChooser fc) {
 925         return new DirectoryComboBoxModel();
 926     }
 927 
 928     /**
 929      * Data model for a type-face selection combo-box.
 930      */
 931     protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel {
 932         Vector directories = new Vector();
 933         int[] depths = null;
 934         File selectedDirectory = null;
 935         JFileChooser chooser = getFileChooser();
 936         FileSystemView fsv = chooser.getFileSystemView();
 937 
 938         public DirectoryComboBoxModel() {
 939             // Add the current directory to the model, and make it the
 940             // selectedDirectory
 941             File dir = getFileChooser().getCurrentDirectory();
 942             if(dir != null) {
 943                 addItem(dir);
 944             }
 945         }
 946 
 947         /**
 948          * Adds the directory to the model and sets it to be selected,
 949          * additionally clears out the previous selected directory and
 950          * the paths leading up to it, if any.
 951          */
 952         private void addItem(File directory) {
 953 
 954             if(directory == null) {
 955                 return;
 956             }
 957 
 958             directories.clear();
 959 
 960             File[] baseFolders = (useShellFolder)
 961                     ? (File[]) ShellFolder.get("fileChooserComboBoxFolders")
 962                     : fsv.getRoots();
 963             directories.addAll(Arrays.asList(baseFolders));
 964 
 965             // Get the canonical (full) path. This has the side
 966             // benefit of removing extraneous chars from the path,
 967             // for example /foo/bar/ becomes /foo/bar
 968             File canonical = null;
 969             try {
 970                 canonical = ShellFolder.getNormalizedFile(directory);
 971             } catch (IOException e) {
 972                 // Maybe drive is not ready. Can't abort here.
 973                 canonical = directory;
 974             }
 975 
 976             // create File instances of each directory leading up to the top
 977             try {
 978                 File sf = useShellFolder ? ShellFolder.getShellFolder(canonical)
 979                                          : canonical;
 980                 File f = sf;
 981                 Vector path = new Vector(10);
 982                 do {
 983                     path.addElement(f);
 984                 } while ((f = f.getParentFile()) != null);
 985 
 986                 int pathCount = path.size();
 987                 // Insert chain at appropriate place in vector
 988                 for (int i = 0; i < pathCount; i++) {
 989                     f = (File)path.get(i);
 990                     if (directories.contains(f)) {
 991                         int topIndex = directories.indexOf(f);
 992                         for (int j = i-1; j >= 0; j--) {
 993                             directories.insertElementAt(path.get(j), topIndex+i-j);
 994                         }
 995                         break;
 996                     }
 997                 }
 998                 calculateDepths();
 999                 setSelectedItem(sf);
1000             } catch (FileNotFoundException ex) {
1001                 calculateDepths();
1002             }
1003         }
1004 
1005         private void calculateDepths() {
1006             depths = new int[directories.size()];
1007             for (int i = 0; i < depths.length; i++) {
1008                 File dir = (File)directories.get(i);
1009                 File parent = dir.getParentFile();
1010                 depths[i] = 0;
1011                 if (parent != null) {
1012                     for (int j = i-1; j >= 0; j--) {
1013                         if (parent.equals((File)directories.get(j))) {
1014                             depths[i] = depths[j] + 1;
1015                             break;
1016                         }
1017                     }
1018                 }
1019             }
1020         }
1021 
1022         public int getDepth(int i) {
1023             return (depths != null && i >= 0 && i < depths.length) ? depths[i] : 0;
1024         }
1025 
1026         public void setSelectedItem(Object selectedDirectory) {
1027             this.selectedDirectory = (File)selectedDirectory;
1028             fireContentsChanged(this, -1, -1);
1029         }
1030 
1031         public Object getSelectedItem() {
1032             return selectedDirectory;
1033         }
1034 
1035         public int getSize() {
1036             return directories.size();
1037         }
1038 
1039         public Object getElementAt(int index) {
1040             return directories.elementAt(index);
1041         }
1042     }
1043 
1044     //
1045     // Renderer for Types ComboBox
1046     //
1047     protected FilterComboBoxRenderer createFilterComboBoxRenderer() {
1048         return new FilterComboBoxRenderer();
1049     }
1050 
1051     /**
1052      * Render different type sizes and styles.
1053      */
1054     public class FilterComboBoxRenderer extends DefaultListCellRenderer {
1055         public Component getListCellRendererComponent(JList list,
1056             Object value, int index, boolean isSelected,
1057             boolean cellHasFocus) {
1058 
1059             super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
1060 
1061             if (value != null && value instanceof FileFilter) {
1062                 setText(((FileFilter)value).getDescription());
1063             }
1064 
1065             return this;
1066         }
1067     }
1068 
1069     //
1070     // DataModel for Types Comboxbox
1071     //
1072     protected FilterComboBoxModel createFilterComboBoxModel() {
1073         return new FilterComboBoxModel();
1074     }
1075 
1076     /**
1077      * Data model for a type-face selection combo-box.
1078      */
1079     protected class FilterComboBoxModel extends AbstractListModel implements ComboBoxModel, PropertyChangeListener {
1080         protected FileFilter[] filters;
1081         protected FilterComboBoxModel() {
1082             super();
1083             filters = getFileChooser().getChoosableFileFilters();
1084         }
1085 
1086         public void propertyChange(PropertyChangeEvent e) {
1087             String prop = e.getPropertyName();
1088             if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
1089                 filters = (FileFilter[]) e.getNewValue();
1090                 fireContentsChanged(this, -1, -1);
1091             } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
1092                 fireContentsChanged(this, -1, -1);
1093             }
1094         }
1095 
1096         public void setSelectedItem(Object filter) {
1097             if(filter != null) {
1098                 getFileChooser().setFileFilter((FileFilter) filter);
1099                 fireContentsChanged(this, -1, -1);
1100             }
1101         }
1102 
1103         public Object getSelectedItem() {
1104             // Ensure that the current filter is in the list.
1105             // NOTE: we shouldnt' have to do this, since JFileChooser adds
1106             // the filter to the choosable filters list when the filter
1107             // is set. Lets be paranoid just in case someone overrides
1108             // setFileFilter in JFileChooser.
1109             FileFilter currentFilter = getFileChooser().getFileFilter();
1110             boolean found = false;
1111             if(currentFilter != null) {
1112                 for(int i=0; i < filters.length; i++) {
1113                     if(filters[i] == currentFilter) {
1114                         found = true;
1115                     }
1116                 }
1117                 if(found == false) {
1118                     getFileChooser().addChoosableFileFilter(currentFilter);
1119                 }
1120             }
1121             return getFileChooser().getFileFilter();
1122         }
1123 
1124         public int getSize() {
1125             if(filters != null) {
1126                 return filters.length;
1127             } else {
1128                 return 0;
1129             }
1130         }
1131 
1132         public Object getElementAt(int index) {
1133             if(index > getSize() - 1) {
1134                 // This shouldn't happen. Try to recover gracefully.
1135                 return getFileChooser().getFileFilter();
1136             }
1137             if(filters != null) {
1138                 return filters[index];
1139             } else {
1140                 return null;
1141             }
1142         }
1143     }
1144 
1145     public void valueChanged(ListSelectionEvent e) {
1146         JFileChooser fc = getFileChooser();
1147         File f = fc.getSelectedFile();
1148         if (!e.getValueIsAdjusting() && f != null && !getFileChooser().isTraversable(f)) {
1149             setFileName(fileNameString(f));
1150         }
1151     }
1152 
1153     /**
1154      * Acts when DirectoryComboBox has changed the selected item.
1155      */
1156     protected class DirectoryComboBoxAction extends AbstractAction {
1157         protected DirectoryComboBoxAction() {
1158             super("DirectoryComboBoxAction");
1159         }
1160 
1161         public void actionPerformed(ActionEvent e) {
1162             directoryComboBox.hidePopup();
1163             File f = (File)directoryComboBox.getSelectedItem();
1164             if (!getFileChooser().getCurrentDirectory().equals(f)) {
1165                 getFileChooser().setCurrentDirectory(f);
1166             }
1167         }
1168     }
1169 
1170     protected JButton getApproveButton(JFileChooser fc) {
1171         return approveButton;
1172     }
1173 
1174 
1175     /**
1176      * <code>ButtonAreaLayout</code> behaves in a similar manner to
1177      * <code>FlowLayout</code>. It lays out all components from left to
1178      * right, flushed right. The widths of all components will be set
1179      * to the largest preferred size width.
1180      */
1181     private static class ButtonAreaLayout implements LayoutManager {
1182         private int hGap = 5;
1183         private int topMargin = 17;
1184 
1185         public void addLayoutComponent(String string, Component comp) {
1186         }
1187 
1188         public void layoutContainer(Container container) {
1189             Component[] children = container.getComponents();
1190 
1191             if (children != null && children.length > 0) {
1192                 int         numChildren = children.length;
1193                 Dimension[] sizes = new Dimension[numChildren];
1194                 Insets      insets = container.getInsets();
1195                 int         yLocation = insets.top + topMargin;
1196                 int         maxWidth = 0;
1197 
1198                 for (int counter = 0; counter < numChildren; counter++) {
1199                     sizes[counter] = children[counter].getPreferredSize();
1200                     maxWidth = Math.max(maxWidth, sizes[counter].width);
1201                 }
1202                 int xLocation, xOffset;
1203                 if (container.getComponentOrientation().isLeftToRight()) {
1204                     xLocation = container.getSize().width - insets.left - maxWidth;
1205                     xOffset = hGap + maxWidth;
1206                 } else {
1207                     xLocation = insets.left;
1208                     xOffset = -(hGap + maxWidth);
1209                 }
1210                 for (int counter = numChildren - 1; counter >= 0; counter--) {
1211                     children[counter].setBounds(xLocation, yLocation,
1212                                                 maxWidth, sizes[counter].height);
1213                     xLocation -= xOffset;
1214                 }
1215             }
1216         }
1217 
1218         public Dimension minimumLayoutSize(Container c) {
1219             if (c != null) {
1220                 Component[] children = c.getComponents();
1221 
1222                 if (children != null && children.length > 0) {
1223                     int       numChildren = children.length;
1224                     int       height = 0;
1225                     Insets    cInsets = c.getInsets();
1226                     int       extraHeight = topMargin + cInsets.top + cInsets.bottom;
1227                     int       extraWidth = cInsets.left + cInsets.right;
1228                     int       maxWidth = 0;
1229 
1230                     for (int counter = 0; counter < numChildren; counter++) {
1231                         Dimension aSize = children[counter].getPreferredSize();
1232                         height = Math.max(height, aSize.height);
1233                         maxWidth = Math.max(maxWidth, aSize.width);
1234                     }
1235                     return new Dimension(extraWidth + numChildren * maxWidth +
1236                                          (numChildren - 1) * hGap,
1237                                          extraHeight + height);
1238                 }
1239             }
1240             return new Dimension(0, 0);
1241         }
1242 
1243         public Dimension preferredLayoutSize(Container c) {
1244             return minimumLayoutSize(c);
1245         }
1246 
1247         public void removeLayoutComponent(Component c) { }
1248     }
1249 
1250     private static void groupLabels(AlignedLabel[] group) {
1251         for (int i = 0; i < group.length; i++) {
1252             group[i].group = group;
1253         }
1254     }
1255 
1256     private class AlignedLabel extends JLabel {
1257         private AlignedLabel[] group;
1258         private int maxWidth = 0;
1259 
1260         AlignedLabel() {
1261             super();
1262             setAlignmentX(JComponent.LEFT_ALIGNMENT);
1263         }
1264 
1265 
1266         AlignedLabel(String text) {
1267             super(text);
1268             setAlignmentX(JComponent.LEFT_ALIGNMENT);
1269         }
1270 
1271         public Dimension getPreferredSize() {
1272             Dimension d = super.getPreferredSize();
1273             // Align the width with all other labels in group.
1274             return new Dimension(getMaxWidth() + 11, d.height);
1275         }
1276 
1277         private int getMaxWidth() {
1278             if (maxWidth == 0 && group != null) {
1279                 int max = 0;
1280                 for (int i = 0; i < group.length; i++) {
1281                     max = Math.max(group[i].getSuperPreferredWidth(), max);
1282                 }
1283                 for (int i = 0; i < group.length; i++) {
1284                     group[i].maxWidth = max;
1285                 }
1286             }
1287             return maxWidth;
1288         }
1289 
1290         private int getSuperPreferredWidth() {
1291             return super.getPreferredSize().width;
1292         }
1293     }
1294 }