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 }