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 */
25package sun.swing.plaf.synth;
26
27import java.awt.*;
28import java.awt.event.*;
29import java.beans.*;
30import java.io.*;
31import java.util.*;
32import java.security.AccessController;
33import java.security.PrivilegedAction;
34
35import javax.swing.*;
36import javax.swing.event.*;
37import javax.swing.filechooser.*;
38import javax.swing.filechooser.FileFilter;
39import javax.swing.plaf.basic.*;
40import javax.swing.plaf.synth.*;
41import javax.swing.plaf.ActionMapUIResource;
42
43import sun.awt.shell.ShellFolder;
44import 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 */
61public 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    static final 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}
1112