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