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