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