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