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