1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.swing.plaf.synth;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.beans.*;
  30 import java.io.*;
  31 import java.util.*;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 
  35 import javax.swing.*;
  36 import javax.swing.event.*;
  37 import javax.swing.filechooser.*;
  38 import javax.swing.filechooser.FileFilter;
  39 import javax.swing.plaf.basic.*;
  40 import javax.swing.plaf.synth.*;
  41 import javax.swing.plaf.ActionMapUIResource;
  42 
  43 import sun.awt.shell.ShellFolder;
  44 import sun.swing.*;
  45 
  46 /**
  47  * Synth FileChooserUI implementation.
  48  * <p>
  49  * Note that the classes in the com.sun.java.swing.plaf.synth
  50  * package are not
  51  * part of the core Java APIs. They are a part of Sun's JDK and JRE
  52  * distributions. Although other licensees may choose to distribute
  53  * these classes, developers cannot depend on their availability in
  54  * non-Sun implementations. Additionally this API may change in
  55  * incompatible ways between releases. While this class is public, it
  56  * shoud be considered an implementation detail, and subject to change.
  57  *
  58  * @author Leif Samuelsson
  59  * @author Jeff Dinkins
  60  */
  61 public class SynthFileChooserUIImpl extends SynthFileChooserUI {
  62     private JLabel lookInLabel;
  63     private JComboBox<File> directoryComboBox;
  64     private DirectoryComboBoxModel directoryComboBoxModel;
  65     private Action directoryComboBoxAction = new DirectoryComboBoxAction();
  66 
  67     private FilterComboBoxModel filterComboBoxModel;
  68 
  69     private JTextField fileNameTextField;
  70 
  71     private FilePane filePane;
  72     private JToggleButton listViewButton;
  73     private JToggleButton detailsViewButton;
  74 
  75     private boolean readOnly;
  76 
  77     private JPanel buttonPanel;
  78     private JPanel bottomPanel;
  79 
  80     private JComboBox<FileFilter> filterComboBox;
  81 
  82     private static final Dimension hstrut5 = new Dimension(5, 1);
  83 
  84     private static final Insets shrinkwrap = new Insets(0,0,0,0);
  85 
  86     // Preferred and Minimum sizes for the dialog box
  87     private static Dimension LIST_PREF_SIZE = new Dimension(405, 135);
  88 
  89     // Labels, mnemonics, and tooltips (oh my!)
  90     private int    lookInLabelMnemonic = 0;
  91     private String lookInLabelText = null;
  92     private String saveInLabelText = null;
  93 
  94     private int    fileNameLabelMnemonic = 0;
  95     private String fileNameLabelText = null;
  96     private int    folderNameLabelMnemonic = 0;
  97     private String folderNameLabelText = null;
  98 
  99     private int    filesOfTypeLabelMnemonic = 0;
 100     private String filesOfTypeLabelText = null;
 101 
 102     private String upFolderToolTipText = null;
 103     private String upFolderAccessibleName = null;
 104 
 105     private String homeFolderToolTipText = null;
 106     private String homeFolderAccessibleName = null;
 107 
 108     private String newFolderToolTipText = null;
 109     private String newFolderAccessibleName = null;
 110 
 111     private String listViewButtonToolTipText = null;
 112     private String listViewButtonAccessibleName = null;
 113 
 114     private String detailsViewButtonToolTipText = null;
 115     private String detailsViewButtonAccessibleName = null;
 116 
 117     private AlignedLabel fileNameLabel;
 118     private final PropertyChangeListener modeListener = new PropertyChangeListener() {
 119         public void propertyChange(PropertyChangeEvent event) {
 120             if (fileNameLabel != null) {
 121                 populateFileNameLabel();
 122             }
 123         }
 124     };
 125 
 126     private void populateFileNameLabel() {
 127         if (getFileChooser().getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) {
 128             fileNameLabel.setText(folderNameLabelText);
 129             fileNameLabel.setDisplayedMnemonic(folderNameLabelMnemonic);
 130         } else {
 131             fileNameLabel.setText(fileNameLabelText);
 132             fileNameLabel.setDisplayedMnemonic(fileNameLabelMnemonic);
 133         }
 134     }
 135 
 136     public SynthFileChooserUIImpl(JFileChooser b) {
 137         super(b);
 138     }
 139 
 140 
 141     private class SynthFileChooserUIAccessor implements FilePane.FileChooserUIAccessor {
 142         public JFileChooser getFileChooser() {
 143             return SynthFileChooserUIImpl.this.getFileChooser();
 144         }
 145 
 146         public BasicDirectoryModel getModel() {
 147             return SynthFileChooserUIImpl.this.getModel();
 148         }
 149 
 150         public JPanel createList() {
 151             return null;
 152         }
 153 
 154         public JPanel createDetailsView() {
 155             return null;
 156         }
 157 
 158         public boolean isDirectorySelected() {
 159             return SynthFileChooserUIImpl.this.isDirectorySelected();
 160         }
 161 
 162         public File getDirectory() {
 163             return SynthFileChooserUIImpl.this.getDirectory();
 164         }
 165 
 166         public Action getChangeToParentDirectoryAction() {
 167             return SynthFileChooserUIImpl.this.getChangeToParentDirectoryAction();
 168         }
 169 
 170         public Action getApproveSelectionAction() {
 171             return SynthFileChooserUIImpl.this.getApproveSelectionAction();
 172         }
 173 
 174         public Action getNewFolderAction() {
 175             return SynthFileChooserUIImpl.this.getNewFolderAction();
 176         }
 177 
 178         public MouseListener createDoubleClickListener(JList<?> list) {
 179             return SynthFileChooserUIImpl.this.createDoubleClickListener(getFileChooser(),
 180                                                                      list);
 181         }
 182 
 183         public ListSelectionListener createListSelectionListener() {
 184             return SynthFileChooserUIImpl.this.createListSelectionListener(getFileChooser());
 185         }
 186     }
 187 
 188     protected void installDefaults(JFileChooser fc) {
 189         super.installDefaults(fc);
 190         readOnly = UIManager.getBoolean("FileChooser.readOnly");
 191     }
 192 
 193     @SuppressWarnings("serial") // anonymous classes inside
 194     public void installComponents(JFileChooser fc) {
 195         super.installComponents(fc);
 196 
 197         SynthContext context = getContext(fc, ENABLED);
 198 
 199         fc.setLayout(new BorderLayout(0, 11));
 200 
 201         // ********************************* //
 202         // **** Construct the top panel **** //
 203         // ********************************* //
 204 
 205         // Directory manipulation buttons
 206         JPanel topPanel = new JPanel(new BorderLayout(11, 0));
 207     JPanel topButtonPanel = new JPanel();
 208     topButtonPanel.setLayout(new BoxLayout(topButtonPanel, BoxLayout.LINE_AXIS));
 209     topPanel.add(topButtonPanel, BorderLayout.AFTER_LINE_ENDS);
 210 
 211         // Add the top panel to the fileChooser
 212         fc.add(topPanel, BorderLayout.NORTH);
 213 
 214         // ComboBox Label
 215         lookInLabel = new JLabel(lookInLabelText);
 216         lookInLabel.setDisplayedMnemonic(lookInLabelMnemonic);
 217         topPanel.add(lookInLabel, BorderLayout.BEFORE_LINE_BEGINS);
 218 
 219         // CurrentDir ComboBox
 220         directoryComboBox = new JComboBox<File>();
 221         directoryComboBox.getAccessibleContext().setAccessibleDescription(lookInLabelText);
 222         directoryComboBox.putClientProperty( "JComboBox.isTableCellEditor", Boolean.TRUE );
 223         lookInLabel.setLabelFor(directoryComboBox);
 224         directoryComboBoxModel = createDirectoryComboBoxModel(fc);
 225         directoryComboBox.setModel(directoryComboBoxModel);
 226         directoryComboBox.addActionListener(directoryComboBoxAction);
 227         directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
 228         directoryComboBox.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 229         directoryComboBox.setAlignmentY(JComponent.TOP_ALIGNMENT);
 230         directoryComboBox.setMaximumRowCount(8);
 231         topPanel.add(directoryComboBox, BorderLayout.CENTER);
 232 
 233         filePane = new FilePane(new SynthFileChooserUIAccessor());
 234         fc.addPropertyChangeListener(filePane);
 235 
 236         // Add 'Go Up' to context menu, plus 'Go Home' if on Unix
 237         JPopupMenu contextMenu = filePane.getComponentPopupMenu();
 238         if (contextMenu != null) {
 239             contextMenu.insert(getChangeToParentDirectoryAction(), 0);
 240             if (File.separatorChar == '/') {
 241                 contextMenu.insert(getGoHomeAction(), 1);
 242             }
 243         }
 244 
 245     FileSystemView fsv = fc.getFileSystemView();
 246 
 247     // Up Button
 248     JButton upFolderButton = new JButton(getChangeToParentDirectoryAction());
 249     upFolderButton.setText(null);
 250     upFolderButton.setIcon(upFolderIcon);
 251     upFolderButton.setToolTipText(upFolderToolTipText);
 252     upFolderButton.getAccessibleContext().setAccessibleName(upFolderAccessibleName);
 253     upFolderButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 254     upFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 255     upFolderButton.setMargin(shrinkwrap);
 256 
 257     topButtonPanel.add(upFolderButton);
 258     topButtonPanel.add(Box.createRigidArea(hstrut5));
 259 
 260     // Home Button
 261     File homeDir = fsv.getHomeDirectory();
 262     String toolTipText = homeFolderToolTipText;
 263     if (fsv.isRoot(homeDir)) {
 264         toolTipText = getFileView(fc).getName(homeDir); // Probably "Desktop".
 265     }
 266 
 267     JButton b = new JButton(homeFolderIcon);
 268     b.setToolTipText(toolTipText);
 269     b.getAccessibleContext().setAccessibleName(homeFolderAccessibleName);
 270     b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 271     b.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 272     b.setMargin(shrinkwrap);
 273 
 274     b.addActionListener(getGoHomeAction());
 275     topButtonPanel.add(b);
 276     topButtonPanel.add(Box.createRigidArea(hstrut5));
 277 
 278     // New Directory Button
 279     if (!readOnly) {
 280         b = new JButton(filePane.getNewFolderAction());
 281         b.setText(null);
 282         b.setIcon(newFolderIcon);
 283         b.setToolTipText(newFolderToolTipText);
 284         b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
 285         b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 286         b.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 287         b.setMargin(shrinkwrap);
 288         topButtonPanel.add(b);
 289         topButtonPanel.add(Box.createRigidArea(hstrut5));
 290     }
 291 
 292     // View button group
 293     ButtonGroup viewButtonGroup = new ButtonGroup();
 294 
 295     // List Button
 296     listViewButton = new JToggleButton(listViewIcon);
 297     listViewButton.setToolTipText(listViewButtonToolTipText);
 298     listViewButton.getAccessibleContext().setAccessibleName(listViewButtonAccessibleName);
 299     listViewButton.setSelected(true);
 300     listViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 301     listViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 302     listViewButton.setMargin(shrinkwrap);
 303     listViewButton.addActionListener(filePane.getViewTypeAction(FilePane.VIEWTYPE_LIST));
 304     topButtonPanel.add(listViewButton);
 305     viewButtonGroup.add(listViewButton);
 306 
 307     // Details Button
 308     detailsViewButton = new JToggleButton(detailsViewIcon);
 309     detailsViewButton.setToolTipText(detailsViewButtonToolTipText);
 310     detailsViewButton.getAccessibleContext().setAccessibleName(detailsViewButtonAccessibleName);
 311     detailsViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
 312     detailsViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
 313     detailsViewButton.setMargin(shrinkwrap);
 314     detailsViewButton.addActionListener(filePane.getViewTypeAction(FilePane.VIEWTYPE_DETAILS));
 315     topButtonPanel.add(detailsViewButton);
 316     viewButtonGroup.add(detailsViewButton);
 317 
 318     filePane.addPropertyChangeListener(new PropertyChangeListener() {
 319         public void propertyChange(PropertyChangeEvent e) {
 320             if ("viewType".equals(e.getPropertyName())) {
 321                 int viewType = filePane.getViewType();
 322                 switch (viewType) {
 323                     case FilePane.VIEWTYPE_LIST:
 324                         listViewButton.setSelected(true);
 325                         break;
 326                     case FilePane.VIEWTYPE_DETAILS:
 327                         detailsViewButton.setSelected(true);
 328                         break;
 329                 }
 330             }
 331         }
 332     });
 333 
 334         // ************************************** //
 335         // ******* Add the directory pane ******* //
 336         // ************************************** //
 337         fc.add(getAccessoryPanel(), BorderLayout.AFTER_LINE_ENDS);
 338         JComponent accessory = fc.getAccessory();
 339         if (accessory != null) {
 340             getAccessoryPanel().add(accessory);
 341         }
 342         filePane.setPreferredSize(LIST_PREF_SIZE);
 343         fc.add(filePane, BorderLayout.CENTER);
 344 
 345 
 346         // ********************************** //
 347         // **** Construct the bottom panel ** //
 348         // ********************************** //
 349         bottomPanel = new JPanel();
 350         bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
 351         fc.add(bottomPanel, BorderLayout.SOUTH);
 352 
 353         // FileName label and textfield
 354         JPanel fileNamePanel = new JPanel();
 355         fileNamePanel.setLayout(new BoxLayout(fileNamePanel, BoxLayout.LINE_AXIS));
 356         bottomPanel.add(fileNamePanel);
 357         bottomPanel.add(Box.createRigidArea(new Dimension(1, 5)));
 358 
 359         fileNameLabel = new AlignedLabel();
 360         populateFileNameLabel();
 361         fileNamePanel.add(fileNameLabel);
 362 
 363         fileNameTextField = new JTextField(35) {
 364             public Dimension getMaximumSize() {
 365                 return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height);
 366             }
 367         };
 368         fileNamePanel.add(fileNameTextField);
 369         fileNameLabel.setLabelFor(fileNameTextField);
 370         fileNameTextField.addFocusListener(
 371             new FocusAdapter() {
 372                 public void focusGained(FocusEvent e) {
 373                     if (!getFileChooser().isMultiSelectionEnabled()) {
 374                         filePane.clearSelection();
 375                     }
 376                 }
 377             }
 378         );
 379         if (fc.isMultiSelectionEnabled()) {
 380             setFileName(fileNameString(fc.getSelectedFiles()));
 381         } else {
 382             setFileName(fileNameString(fc.getSelectedFile()));
 383         }
 384 
 385 
 386         // Filetype label and combobox
 387         JPanel filesOfTypePanel = new JPanel();
 388         filesOfTypePanel.setLayout(new BoxLayout(filesOfTypePanel, BoxLayout.LINE_AXIS));
 389         bottomPanel.add(filesOfTypePanel);
 390 
 391         AlignedLabel filesOfTypeLabel = new AlignedLabel(filesOfTypeLabelText);
 392         filesOfTypeLabel.setDisplayedMnemonic(filesOfTypeLabelMnemonic);
 393         filesOfTypePanel.add(filesOfTypeLabel);
 394 
 395         filterComboBoxModel = createFilterComboBoxModel();
 396         fc.addPropertyChangeListener(filterComboBoxModel);
 397         filterComboBox = new JComboBox<FileFilter>(filterComboBoxModel);
 398         filterComboBox.getAccessibleContext().setAccessibleDescription(filesOfTypeLabelText);
 399         filesOfTypeLabel.setLabelFor(filterComboBox);
 400         filterComboBox.setRenderer(createFilterComboBoxRenderer());
 401         filesOfTypePanel.add(filterComboBox);
 402 
 403 
 404         // buttons
 405         buttonPanel = new JPanel();
 406         buttonPanel.setLayout(new ButtonAreaLayout());
 407 
 408         buttonPanel.add(getApproveButton(fc));
 409         buttonPanel.add(getCancelButton(fc));
 410 
 411         if (fc.getControlButtonsAreShown()) {
 412             addControlButtons();
 413         }
 414 
 415         groupLabels(new AlignedLabel[] { fileNameLabel, filesOfTypeLabel });
 416     }
 417 
 418     protected void installListeners(JFileChooser fc) {
 419         super.installListeners(fc);
 420         fc.addPropertyChangeListener(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY, modeListener);
 421     }
 422 
 423     protected void uninstallListeners(JFileChooser fc) {
 424         fc.removePropertyChangeListener(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY, modeListener);
 425         super.uninstallListeners(fc);
 426     }
 427 
 428     private String fileNameString(File file) {
 429         if (file == null) {
 430             return null;
 431         } else {
 432             JFileChooser fc = getFileChooser();
 433             if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
 434                 return file.getPath();
 435             } else {
 436                 return file.getName();
 437             }
 438         }
 439     }
 440 
 441     private String fileNameString(File[] files) {
 442         StringBuilder sb = new StringBuilder();
 443         for (int i = 0; files != null && i < files.length; i++) {
 444             if (i > 0) {
 445                 sb.append(" ");
 446             }
 447             if (files.length > 1) {
 448                 sb.append("\"");
 449             }
 450             sb.append(fileNameString(files[i]));
 451             if (files.length > 1) {
 452                 sb.append("\"");
 453             }
 454         }
 455         return sb.toString();
 456     }
 457 
 458     public void uninstallUI(JComponent c) {
 459         // Remove listeners
 460         c.removePropertyChangeListener(filterComboBoxModel);
 461         c.removePropertyChangeListener(filePane);
 462 
 463         if (filePane != null) {
 464             filePane.uninstallUI();
 465             filePane = null;
 466         }
 467 
 468         super.uninstallUI(c);
 469     }
 470 
 471     protected void installStrings(JFileChooser fc) {
 472         super.installStrings(fc);
 473 
 474         Locale l = fc.getLocale();
 475 
 476         lookInLabelMnemonic = getMnemonic("FileChooser.lookInLabelMnemonic", l);
 477         lookInLabelText = UIManager.getString("FileChooser.lookInLabelText", l);
 478         saveInLabelText = UIManager.getString("FileChooser.saveInLabelText", l);
 479 
 480         fileNameLabelMnemonic = getMnemonic("FileChooser.fileNameLabelMnemonic", l);
 481         fileNameLabelText = UIManager.getString("FileChooser.fileNameLabelText", l);
 482         folderNameLabelMnemonic = getMnemonic("FileChooser.folderNameLabelMnemonic", l);
 483         folderNameLabelText = UIManager.getString("FileChooser.folderNameLabelText", l);
 484 
 485         filesOfTypeLabelMnemonic = getMnemonic("FileChooser.filesOfTypeLabelMnemonic", l);
 486         filesOfTypeLabelText = UIManager.getString("FileChooser.filesOfTypeLabelText", l);
 487 
 488     upFolderToolTipText =  UIManager.getString("FileChooser.upFolderToolTipText",l);
 489     upFolderAccessibleName = UIManager.getString("FileChooser.upFolderAccessibleName",l);
 490 
 491     homeFolderToolTipText =  UIManager.getString("FileChooser.homeFolderToolTipText",l);
 492     homeFolderAccessibleName = UIManager.getString("FileChooser.homeFolderAccessibleName",l);
 493 
 494     newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText",l);
 495     newFolderAccessibleName = UIManager.getString("FileChooser.newFolderAccessibleName",l);
 496 
 497     listViewButtonToolTipText = UIManager.getString("FileChooser.listViewButtonToolTipText",l);
 498     listViewButtonAccessibleName = UIManager.getString("FileChooser.listViewButtonAccessibleName",l);
 499 
 500     detailsViewButtonToolTipText = UIManager.getString("FileChooser.detailsViewButtonToolTipText",l);
 501     detailsViewButtonAccessibleName = UIManager.getString("FileChooser.detailsViewButtonAccessibleName",l);
 502     }
 503 
 504     private int getMnemonic(String key, Locale l) {
 505         return SwingUtilities2.getUIDefaultsInt(key, l);
 506     }
 507 
 508 
 509     public String getFileName() {
 510         if (fileNameTextField != null) {
 511             return fileNameTextField.getText();
 512         } else {
 513             return null;
 514         }
 515     }
 516 
 517     public void setFileName(String fileName) {
 518         if (fileNameTextField != null) {
 519             fileNameTextField.setText(fileName);
 520         }
 521     }
 522 
 523     @Override public void rescanCurrentDirectory(JFileChooser fc) {
 524         filePane.rescanCurrentDirectory();
 525     }
 526 
 527     protected void doSelectedFileChanged(PropertyChangeEvent e) {
 528         super.doSelectedFileChanged(e);
 529 
 530         File f = (File) e.getNewValue();
 531         JFileChooser fc = getFileChooser();
 532         if (f != null
 533             && ((fc.isFileSelectionEnabled() && !f.isDirectory())
 534                 || (f.isDirectory() && fc.isDirectorySelectionEnabled()))) {
 535 
 536             setFileName(fileNameString(f));
 537         }
 538     }
 539 
 540     protected void doSelectedFilesChanged(PropertyChangeEvent e) {
 541         super.doSelectedFilesChanged(e);
 542 
 543         File[] files = (File[]) e.getNewValue();
 544         JFileChooser fc = getFileChooser();
 545         if (files != null
 546             && files.length > 0
 547             && (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) {
 548             setFileName(fileNameString(files));
 549         }
 550     }
 551 
 552     protected void doDirectoryChanged(PropertyChangeEvent e) {
 553         super.doDirectoryChanged(e);
 554 
 555         JFileChooser fc = getFileChooser();
 556         FileSystemView fsv = fc.getFileSystemView();
 557         File currentDirectory = fc.getCurrentDirectory();
 558 
 559         if (!readOnly && currentDirectory != null) {
 560             getNewFolderAction().setEnabled(filePane.canWrite(currentDirectory));
 561         }
 562 
 563         if (currentDirectory != null) {
 564             JComponent cb = getDirectoryComboBox();
 565             if (cb instanceof JComboBox) {
 566                 ComboBoxModel<?> model = ((JComboBox)cb).getModel();
 567                 if (model instanceof DirectoryComboBoxModel) {
 568                     ((DirectoryComboBoxModel)model).addItem(currentDirectory);
 569                 }
 570             }
 571 
 572             if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
 573                 if (fsv.isFileSystem(currentDirectory)) {
 574                     setFileName(currentDirectory.getPath());
 575                 } else {
 576                     setFileName(null);
 577                 }
 578             }
 579         }
 580     }
 581 
 582 
 583     protected void doFileSelectionModeChanged(PropertyChangeEvent e) {
 584         super.doFileSelectionModeChanged(e);
 585 
 586         JFileChooser fc = getFileChooser();
 587         File currentDirectory = fc.getCurrentDirectory();
 588         if (currentDirectory != null
 589             && fc.isDirectorySelectionEnabled()
 590             && !fc.isFileSelectionEnabled()
 591             && fc.getFileSystemView().isFileSystem(currentDirectory)) {
 592 
 593             setFileName(currentDirectory.getPath());
 594         } else {
 595             setFileName(null);
 596         }
 597     }
 598 
 599     protected void doAccessoryChanged(PropertyChangeEvent e) {
 600         if (getAccessoryPanel() != null) {
 601             if (e.getOldValue() != null) {
 602                 getAccessoryPanel().remove((JComponent)e.getOldValue());
 603             }
 604             JComponent accessory = (JComponent)e.getNewValue();
 605             if (accessory != null) {
 606                 getAccessoryPanel().add(accessory, BorderLayout.CENTER);
 607             }
 608         }
 609     }
 610 
 611     protected void doControlButtonsChanged(PropertyChangeEvent e) {
 612         super.doControlButtonsChanged(e);
 613 
 614         if (getFileChooser().getControlButtonsAreShown()) {
 615             addControlButtons();
 616         } else {
 617             removeControlButtons();
 618         }
 619     }
 620 
 621     protected void addControlButtons() {
 622         if (bottomPanel != null) {
 623             bottomPanel.add(buttonPanel);
 624         }
 625     }
 626 
 627     protected void removeControlButtons() {
 628         if (bottomPanel != null) {
 629             bottomPanel.remove(buttonPanel);
 630         }
 631     }
 632 
 633 
 634 
 635 
 636     // *******************************************************
 637     // ************ FileChooser UI PLAF methods **************
 638     // *******************************************************
 639 
 640     protected ActionMap createActionMap() {
 641         ActionMap map = new ActionMapUIResource();
 642         // add standard actions
 643         FilePane.addActionsToMap(map, filePane.getActions());
 644         // add synth only actions
 645         map.put("fileNameCompletion", getFileNameCompletionAction());
 646         return map;
 647     }
 648 
 649     // *****************************
 650     // ***** Directory Actions *****
 651     // *****************************
 652 
 653     protected JComponent getDirectoryComboBox() {
 654         return directoryComboBox;
 655     }
 656 
 657     protected Action getDirectoryComboBoxAction() {
 658         return directoryComboBoxAction;
 659     }
 660 
 661     protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(JFileChooser fc) {
 662         return new DirectoryComboBoxRenderer(directoryComboBox.getRenderer());
 663     }
 664 
 665     //
 666     // Renderer for DirectoryComboBox
 667     //
 668     // Synth has some odd behavior with regards to renderers. Renderers are styled
 669     // in a specific manner by the SynthComboBoxUI. If we extend DefaultListCellRenderer
 670     // here, then we get none of those benefits or behaviors, leading to poor
 671     // looking combo boxes.
 672     // So what we do here is delegate most jobs to the "real" or original renderer,
 673     // and simply monkey with the icon and text of the renderer.
 674     private class DirectoryComboBoxRenderer implements ListCellRenderer<File> {
 675         private ListCellRenderer<? super File> delegate;
 676         IndentIcon ii = new IndentIcon();
 677 
 678         private DirectoryComboBoxRenderer(ListCellRenderer<? super File> delegate) {
 679             this.delegate = delegate;
 680         }
 681 
 682         @Override
 683         public Component getListCellRendererComponent(JList<? extends File> list, File value, int index, boolean isSelected, boolean cellHasFocus) {
 684             Component c = delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
 685 
 686             assert c instanceof JLabel;
 687             JLabel label = (JLabel)c;
 688             if (value == null) {
 689                 label.setText("");
 690                 return label;
 691             }
 692             label.setText(getFileChooser().getName(value));
 693             Icon icon = getFileChooser().getIcon(value);
 694             ii.icon = icon;
 695             ii.depth = directoryComboBoxModel.getDepth(index);
 696             label.setIcon(ii);
 697 
 698             return label;
 699         }
 700     }
 701 
 702     final static int space = 10;
 703     class IndentIcon implements Icon {
 704 
 705         Icon icon = null;
 706         int depth = 0;
 707 
 708         public void paintIcon(Component c, Graphics g, int x, int y) {
 709             if (icon != null) {
 710                 if (c.getComponentOrientation().isLeftToRight()) {
 711                     icon.paintIcon(c, g, x+depth*space, y);
 712                 } else {
 713                     icon.paintIcon(c, g, x, y);
 714                 }
 715             }
 716         }
 717 
 718         public int getIconWidth() {
 719             return ((icon != null) ? icon.getIconWidth() : 0) + depth*space;
 720         }
 721 
 722         public int getIconHeight() {
 723             return (icon != null) ? icon.getIconHeight() : 0;
 724         }
 725 
 726     }
 727 
 728     //
 729     // DataModel for DirectoryComboxbox
 730     //
 731     protected DirectoryComboBoxModel createDirectoryComboBoxModel(JFileChooser fc) {
 732         return new DirectoryComboBoxModel();
 733     }
 734 
 735     /**
 736      * Data model for a type-face selection combo-box.
 737      */
 738     @SuppressWarnings("serial") // JDK-implementation class
 739     protected class DirectoryComboBoxModel extends AbstractListModel<File> implements ComboBoxModel<File> {
 740         Vector<File> directories = new Vector<File>();
 741         int[] depths = null;
 742         File selectedDirectory = null;
 743         JFileChooser chooser = getFileChooser();
 744         FileSystemView fsv = chooser.getFileSystemView();
 745 
 746         public DirectoryComboBoxModel() {
 747             // Add the current directory to the model, and make it the
 748             // selectedDirectory
 749             File dir = getFileChooser().getCurrentDirectory();
 750             if (dir != null) {
 751                 addItem(dir);
 752             }
 753         }
 754 
 755         /**
 756          * Adds the directory to the model and sets it to be selected,
 757          * additionally clears out the previous selected directory and
 758          * the paths leading up to it, if any.
 759          */
 760         public void addItem(File directory) {
 761 
 762             if (directory == null) {
 763                 return;
 764             }
 765 
 766             boolean useShellFolder = FilePane.usesShellFolder(chooser);
 767 
 768             int oldSize = directories.size();
 769             directories.clear();
 770             if (oldSize > 0) {
 771                 fireIntervalRemoved(this, 0, oldSize);
 772             }
 773 
 774             File[] baseFolders = (useShellFolder)
 775                     ? (File[]) ShellFolder.get("fileChooserComboBoxFolders")
 776                     : fsv.getRoots();
 777             directories.addAll(Arrays.asList(baseFolders));
 778 
 779             // Get the canonical (full) path. This has the side
 780             // benefit of removing extraneous chars from the path,
 781             // for example /foo/bar/ becomes /foo/bar
 782             File canonical;
 783             try {
 784                 canonical = ShellFolder.getNormalizedFile(directory);
 785             } catch (IOException e) {
 786                 // Maybe drive is not ready. Can't abort here.
 787                 canonical = directory;
 788             }
 789 
 790             // create File instances of each directory leading up to the top
 791             try {
 792                 File sf = useShellFolder ? ShellFolder.getShellFolder(canonical)
 793                                          : canonical;
 794                 File f = sf;
 795                 Vector<File> path = new Vector<File>(10);
 796                 do {
 797                     path.addElement(f);
 798                 } while ((f = f.getParentFile()) != null);
 799 
 800                 int pathCount = path.size();
 801                 // Insert chain at appropriate place in vector
 802                 for (int i = 0; i < pathCount; i++) {
 803                     f = path.get(i);
 804                     if (directories.contains(f)) {
 805                         int topIndex = directories.indexOf(f);
 806                         for (int j = i-1; j >= 0; j--) {
 807                             directories.insertElementAt(path.get(j), topIndex+i-j);
 808                         }
 809                         break;
 810                     }
 811                 }
 812                 calculateDepths();
 813                 setSelectedItem(sf);
 814             } catch (FileNotFoundException ex) {
 815                 calculateDepths();
 816             }
 817         }
 818 
 819         private void calculateDepths() {
 820             depths = new int[directories.size()];
 821             for (int i = 0; i < depths.length; i++) {
 822                 File dir = directories.get(i);
 823                 File parent = dir.getParentFile();
 824                 depths[i] = 0;
 825                 if (parent != null) {
 826                     for (int j = i-1; j >= 0; j--) {
 827                         if (parent.equals(directories.get(j))) {
 828                             depths[i] = depths[j] + 1;
 829                             break;
 830                         }
 831                     }
 832                 }
 833             }
 834         }
 835 
 836         public int getDepth(int i) {
 837             return (depths != null && i >= 0 && i < depths.length) ? depths[i] : 0;
 838         }
 839 
 840         public void setSelectedItem(Object selectedDirectory) {
 841             this.selectedDirectory = (File)selectedDirectory;
 842             fireContentsChanged(this, -1, -1);
 843         }
 844 
 845         public Object getSelectedItem() {
 846             return selectedDirectory;
 847         }
 848 
 849         public int getSize() {
 850             return directories.size();
 851         }
 852 
 853         public File getElementAt(int index) {
 854             return directories.elementAt(index);
 855         }
 856     }
 857 
 858     /**
 859      * Acts when DirectoryComboBox has changed the selected item.
 860      */
 861     @SuppressWarnings("serial") // JDK-implementation class
 862     protected class DirectoryComboBoxAction extends AbstractAction {
 863         protected DirectoryComboBoxAction() {
 864             super("DirectoryComboBoxAction");
 865         }
 866 
 867         public void actionPerformed(ActionEvent e) {
 868             directoryComboBox.hidePopup();
 869             JComponent cb = getDirectoryComboBox();
 870             if (cb instanceof JComboBox) {
 871                 File f = (File)((JComboBox)cb).getSelectedItem();
 872                 getFileChooser().setCurrentDirectory(f);
 873             }
 874         }
 875     }
 876 
 877     //
 878     // Renderer for Types ComboBox
 879     //
 880     protected FilterComboBoxRenderer createFilterComboBoxRenderer() {
 881         return new FilterComboBoxRenderer(filterComboBox.getRenderer());
 882     }
 883 
 884     /**
 885      * Render different type sizes and styles.
 886      */
 887     public class FilterComboBoxRenderer implements ListCellRenderer<FileFilter> {
 888         private ListCellRenderer<? super FileFilter> delegate;
 889         private FilterComboBoxRenderer(ListCellRenderer<? super FileFilter> delegate) {
 890             this.delegate = delegate;
 891         }
 892 
 893         public Component getListCellRendererComponent(JList<? extends FileFilter> list, FileFilter value, int index,
 894                                                       boolean isSelected, boolean cellHasFocus) {
 895             Component c = delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
 896 
 897             String text = null;
 898             if (value != null) {
 899                 text = value.getDescription();
 900             }
 901 
 902             //this should always be true, since SynthComboBoxUI's SynthComboBoxRenderer
 903             //extends JLabel
 904             assert c instanceof JLabel;
 905             if (text != null) {
 906                 ((JLabel)c).setText(text);
 907             }
 908             return c;
 909         }
 910     }
 911 
 912     //
 913     // DataModel for Types Comboxbox
 914     //
 915     protected FilterComboBoxModel createFilterComboBoxModel() {
 916         return new FilterComboBoxModel();
 917     }
 918 
 919     /**
 920      * Data model for a type-face selection combo-box.
 921      */
 922     @SuppressWarnings("serial") // JDK-implementation class
 923     protected class FilterComboBoxModel extends AbstractListModel<FileFilter> implements ComboBoxModel<FileFilter>,
 924             PropertyChangeListener {
 925         protected FileFilter[] filters;
 926         protected FilterComboBoxModel() {
 927             super();
 928             filters = getFileChooser().getChoosableFileFilters();
 929         }
 930 
 931         public void propertyChange(PropertyChangeEvent e) {
 932             String prop = e.getPropertyName();
 933             if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
 934                 filters = (FileFilter[]) e.getNewValue();
 935                 fireContentsChanged(this, -1, -1);
 936             } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
 937                 fireContentsChanged(this, -1, -1);
 938             }
 939         }
 940 
 941         public void setSelectedItem(Object filter) {
 942             if(filter != null) {
 943                 getFileChooser().setFileFilter((FileFilter) filter);
 944                 fireContentsChanged(this, -1, -1);
 945             }
 946         }
 947 
 948         public Object getSelectedItem() {
 949             // Ensure that the current filter is in the list.
 950             // NOTE: we shouldnt' have to do this, since JFileChooser adds
 951             // the filter to the choosable filters list when the filter
 952             // is set. Lets be paranoid just in case someone overrides
 953             // setFileFilter in JFileChooser.
 954             FileFilter currentFilter = getFileChooser().getFileFilter();
 955             boolean found = false;
 956             if(currentFilter != null) {
 957                 for (FileFilter filter : filters) {
 958                     if (filter == currentFilter) {
 959                         found = true;
 960                     }
 961                 }
 962                 if(found == false) {
 963                     getFileChooser().addChoosableFileFilter(currentFilter);
 964                 }
 965             }
 966             return getFileChooser().getFileFilter();
 967         }
 968 
 969         public int getSize() {
 970             if(filters != null) {
 971                 return filters.length;
 972             } else {
 973                 return 0;
 974             }
 975         }
 976 
 977         public FileFilter getElementAt(int index) {
 978             if(index > getSize() - 1) {
 979                 // This shouldn't happen. Try to recover gracefully.
 980                 return getFileChooser().getFileFilter();
 981             }
 982             if(filters != null) {
 983                 return filters[index];
 984             } else {
 985                 return null;
 986             }
 987         }
 988     }
 989 
 990 
 991 
 992     /**
 993      * <code>ButtonAreaLayout</code> behaves in a similar manner to
 994      * <code>FlowLayout</code>. It lays out all components from left to
 995      * right, flushed right. The widths of all components will be set
 996      * to the largest preferred size width.
 997      */
 998     private static class ButtonAreaLayout implements LayoutManager {
 999         private int hGap = 5;
1000         private int topMargin = 17;
1001 
1002         public void addLayoutComponent(String string, Component comp) {
1003         }
1004 
1005         public void layoutContainer(Container container) {
1006             Component[] children = container.getComponents();
1007 
1008             if (children != null && children.length > 0) {
1009                 int         numChildren = children.length;
1010                 Dimension[] sizes = new Dimension[numChildren];
1011                 Insets      insets = container.getInsets();
1012                 int         yLocation = insets.top + topMargin;
1013                 int         maxWidth = 0;
1014 
1015                 for (int counter = 0; counter < numChildren; counter++) {
1016                     sizes[counter] = children[counter].getPreferredSize();
1017                     maxWidth = Math.max(maxWidth, sizes[counter].width);
1018                 }
1019                 int xLocation, xOffset;
1020                 if (container.getComponentOrientation().isLeftToRight()) {
1021                     xLocation = container.getSize().width - insets.left - maxWidth;
1022                     xOffset = hGap + maxWidth;
1023                 } else {
1024                     xLocation = insets.left;
1025                     xOffset = -(hGap + maxWidth);
1026                 }
1027                 for (int counter = numChildren - 1; counter >= 0; counter--) {
1028                     children[counter].setBounds(xLocation, yLocation,
1029                                                 maxWidth, sizes[counter].height);
1030                     xLocation -= xOffset;
1031                 }
1032             }
1033         }
1034 
1035         public Dimension minimumLayoutSize(Container c) {
1036             if (c != null) {
1037                 Component[] children = c.getComponents();
1038 
1039                 if (children != null && children.length > 0) {
1040                     int       numChildren = children.length;
1041                     int       height = 0;
1042                     Insets    cInsets = c.getInsets();
1043                     int       extraHeight = topMargin + cInsets.top + cInsets.bottom;
1044                     int       extraWidth = cInsets.left + cInsets.right;
1045                     int       maxWidth = 0;
1046 
1047                     for (int counter = 0; counter < numChildren; counter++) {
1048                         Dimension aSize = children[counter].getPreferredSize();
1049                         height = Math.max(height, aSize.height);
1050                         maxWidth = Math.max(maxWidth, aSize.width);
1051                     }
1052                     return new Dimension(extraWidth + numChildren * maxWidth +
1053                                          (numChildren - 1) * hGap,
1054                                          extraHeight + height);
1055                 }
1056             }
1057             return new Dimension(0, 0);
1058         }
1059 
1060         public Dimension preferredLayoutSize(Container c) {
1061             return minimumLayoutSize(c);
1062         }
1063 
1064         public void removeLayoutComponent(Component c) { }
1065     }
1066 
1067     private static void groupLabels(AlignedLabel[] group) {
1068         for (int i = 0; i < group.length; i++) {
1069             group[i].group = group;
1070         }
1071     }
1072 
1073     @SuppressWarnings("serial") // JDK-implementation class
1074     private class AlignedLabel extends JLabel {
1075         private AlignedLabel[] group;
1076         private int maxWidth = 0;
1077 
1078         AlignedLabel() {
1079             super();
1080             setAlignmentX(JComponent.LEFT_ALIGNMENT);
1081         }
1082 
1083         AlignedLabel(String text) {
1084             super(text);
1085             setAlignmentX(JComponent.LEFT_ALIGNMENT);
1086         }
1087 
1088         public Dimension getPreferredSize() {
1089             Dimension d = super.getPreferredSize();
1090             // Align the width with all other labels in group.
1091             return new Dimension(getMaxWidth() + 11, d.height);
1092         }
1093 
1094         private int getMaxWidth() {
1095             if (maxWidth == 0 && group != null) {
1096                 int max = 0;
1097                 for (int i = 0; i < group.length; i++) {
1098                     max = Math.max(group[i].getSuperPreferredWidth(), max);
1099                 }
1100                 for (int i = 0; i < group.length; i++) {
1101                     group[i].maxWidth = max;
1102                 }
1103             }
1104             return maxWidth;
1105         }
1106 
1107         private int getSuperPreferredWidth() {
1108             return super.getPreferredSize().width;
1109         }
1110     }
1111 }