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