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