1. /*
  2. * @(#)MetalFileChooserUI.java 1.83 04/03/30
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.metal;
  8. import javax.swing.*;
  9. import javax.swing.border.EmptyBorder;
  10. import javax.swing.filechooser.*;
  11. import javax.swing.event.*;
  12. import javax.swing.plaf.*;
  13. import javax.swing.plaf.basic.*;
  14. import javax.swing.table.*;
  15. import java.awt.*;
  16. import java.awt.event.*;
  17. import java.beans.*;
  18. import java.io.File;
  19. import java.io.FileNotFoundException;
  20. import java.io.IOException;
  21. import java.util.*;
  22. import sun.awt.shell.ShellFolder;
  23. import sun.swing.*;
  24. import com.sun.java.swing.SwingUtilities2;
  25. /**
  26. * Metal L&F implementation of a FileChooser.
  27. *
  28. * @version 1.83 03/30/04
  29. * @author Jeff Dinkins
  30. */
  31. public class MetalFileChooserUI extends BasicFileChooserUI {
  32. // Much of the Metal UI for JFilechooser is just a copy of
  33. // the windows implementation, but using Metal themed buttons, lists,
  34. // icons, etc. We are planning a complete rewrite, and hence we've
  35. // made most things in this class private.
  36. private JLabel lookInLabel;
  37. private JComboBox directoryComboBox;
  38. private DirectoryComboBoxModel directoryComboBoxModel;
  39. private Action directoryComboBoxAction = new DirectoryComboBoxAction();
  40. private FilterComboBoxModel filterComboBoxModel;
  41. private JTextField fileNameTextField;
  42. private FilePane filePane;
  43. private JToggleButton listViewButton;
  44. private JToggleButton detailsViewButton;
  45. private boolean useShellFolder;
  46. private JButton approveButton;
  47. private JButton cancelButton;
  48. private JPanel buttonPanel;
  49. private JPanel bottomPanel;
  50. private JComboBox filterComboBox;
  51. private static final Dimension hstrut5 = new Dimension(5, 1);
  52. private static final Dimension hstrut11 = new Dimension(11, 1);
  53. private static final Dimension vstrut5 = new Dimension(1, 5);
  54. private static final Insets shrinkwrap = new Insets(0,0,0,0);
  55. // Preferred and Minimum sizes for the dialog box
  56. private static int PREF_WIDTH = 500;
  57. private static int PREF_HEIGHT = 326;
  58. private static Dimension PREF_SIZE = new Dimension(PREF_WIDTH, PREF_HEIGHT);
  59. private static int MIN_WIDTH = 500;
  60. private static int MIN_HEIGHT = 326;
  61. private static Dimension MIN_SIZE = new Dimension(MIN_WIDTH, MIN_HEIGHT);
  62. private static int LIST_PREF_WIDTH = 405;
  63. private static int LIST_PREF_HEIGHT = 135;
  64. private static Dimension LIST_PREF_SIZE = new Dimension(LIST_PREF_WIDTH, LIST_PREF_HEIGHT);
  65. // Labels, mnemonics, and tooltips (oh my!)
  66. private int lookInLabelMnemonic = 0;
  67. private String lookInLabelText = null;
  68. private String saveInLabelText = null;
  69. private int fileNameLabelMnemonic = 0;
  70. private String fileNameLabelText = null;
  71. private int filesOfTypeLabelMnemonic = 0;
  72. private String filesOfTypeLabelText = null;
  73. private String upFolderToolTipText = null;
  74. private String upFolderAccessibleName = null;
  75. private String homeFolderToolTipText = null;
  76. private String homeFolderAccessibleName = null;
  77. private String newFolderToolTipText = null;
  78. private String newFolderAccessibleName = null;
  79. private String listViewButtonToolTipText = null;
  80. private String listViewButtonAccessibleName = null;
  81. private String detailsViewButtonToolTipText = null;
  82. private String detailsViewButtonAccessibleName = null;
  83. //
  84. // ComponentUI Interface Implementation methods
  85. //
  86. public static ComponentUI createUI(JComponent c) {
  87. return new MetalFileChooserUI((JFileChooser) c);
  88. }
  89. public MetalFileChooserUI(JFileChooser filechooser) {
  90. super(filechooser);
  91. }
  92. public void installUI(JComponent c) {
  93. super.installUI(c);
  94. }
  95. public void uninstallComponents(JFileChooser fc) {
  96. fc.removeAll();
  97. bottomPanel = null;
  98. buttonPanel = null;
  99. }
  100. private class MetalFileChooserUIAccessor implements FilePane.FileChooserUIAccessor {
  101. public JFileChooser getFileChooser() {
  102. return MetalFileChooserUI.this.getFileChooser();
  103. }
  104. public BasicDirectoryModel getModel() {
  105. return MetalFileChooserUI.this.getModel();
  106. }
  107. public JPanel createList() {
  108. return MetalFileChooserUI.this.createList(getFileChooser());
  109. }
  110. public JPanel createDetailsView() {
  111. return MetalFileChooserUI.this.createDetailsView(getFileChooser());
  112. }
  113. public boolean isDirectorySelected() {
  114. return MetalFileChooserUI.this.isDirectorySelected();
  115. }
  116. public File getDirectory() {
  117. return MetalFileChooserUI.this.getDirectory();
  118. }
  119. public Action getChangeToParentDirectoryAction() {
  120. return MetalFileChooserUI.this.getChangeToParentDirectoryAction();
  121. }
  122. public Action getApproveSelectionAction() {
  123. return MetalFileChooserUI.this.getApproveSelectionAction();
  124. }
  125. public Action getNewFolderAction() {
  126. return MetalFileChooserUI.this.getNewFolderAction();
  127. }
  128. public MouseListener createDoubleClickListener(JList list) {
  129. return MetalFileChooserUI.this.createDoubleClickListener(getFileChooser(),
  130. list);
  131. }
  132. public ListSelectionListener createListSelectionListener() {
  133. return MetalFileChooserUI.this.createListSelectionListener(getFileChooser());
  134. }
  135. }
  136. public void installComponents(JFileChooser fc) {
  137. FileSystemView fsv = fc.getFileSystemView();
  138. fc.setBorder(new EmptyBorder(12, 12, 11, 11));
  139. fc.setLayout(new BorderLayout(0, 11));
  140. filePane = new FilePane(new MetalFileChooserUIAccessor());
  141. fc.addPropertyChangeListener(filePane);
  142. updateUseShellFolder();
  143. // ********************************* //
  144. // **** Construct the top panel **** //
  145. // ********************************* //
  146. // Directory manipulation buttons
  147. JPanel topPanel = new JPanel(new BorderLayout(11, 0));
  148. JPanel topButtonPanel = new JPanel();
  149. topButtonPanel.setLayout(new BoxLayout(topButtonPanel, BoxLayout.LINE_AXIS));
  150. topPanel.add(topButtonPanel, BorderLayout.AFTER_LINE_ENDS);
  151. // Add the top panel to the fileChooser
  152. fc.add(topPanel, BorderLayout.NORTH);
  153. // ComboBox Label
  154. lookInLabel = new JLabel(lookInLabelText);
  155. lookInLabel.setDisplayedMnemonic(lookInLabelMnemonic);
  156. topPanel.add(lookInLabel, BorderLayout.BEFORE_LINE_BEGINS);
  157. // CurrentDir ComboBox
  158. directoryComboBox = new JComboBox() {
  159. public Dimension getPreferredSize() {
  160. Dimension d = super.getPreferredSize();
  161. // Must be small enough to not affect total width.
  162. d.width = 150;
  163. return d;
  164. }
  165. };
  166. directoryComboBox.getAccessibleContext().setAccessibleDescription(lookInLabelText);
  167. directoryComboBox.putClientProperty( "JComboBox.isTableCellEditor", Boolean.TRUE );
  168. lookInLabel.setLabelFor(directoryComboBox);
  169. directoryComboBoxModel = createDirectoryComboBoxModel(fc);
  170. directoryComboBox.setModel(directoryComboBoxModel);
  171. directoryComboBox.addActionListener(directoryComboBoxAction);
  172. directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
  173. directoryComboBox.setAlignmentX(JComponent.LEFT_ALIGNMENT);
  174. directoryComboBox.setAlignmentY(JComponent.TOP_ALIGNMENT);
  175. directoryComboBox.setMaximumRowCount(8);
  176. topPanel.add(directoryComboBox, BorderLayout.CENTER);
  177. // Up Button
  178. JButton upFolderButton = new JButton(getChangeToParentDirectoryAction());
  179. upFolderButton.setText(null);
  180. upFolderButton.setIcon(upFolderIcon);
  181. upFolderButton.setToolTipText(upFolderToolTipText);
  182. upFolderButton.getAccessibleContext().setAccessibleName(upFolderAccessibleName);
  183. upFolderButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
  184. upFolderButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
  185. upFolderButton.setMargin(shrinkwrap);
  186. topButtonPanel.add(upFolderButton);
  187. topButtonPanel.add(Box.createRigidArea(hstrut5));
  188. // Home Button
  189. File homeDir = fsv.getHomeDirectory();
  190. String toolTipText = homeFolderToolTipText;
  191. if (fsv.isRoot(homeDir)) {
  192. toolTipText = getFileView(fc).getName(homeDir); // Probably "Desktop".
  193. }
  194. JButton b = new JButton(homeFolderIcon);
  195. b.setToolTipText(toolTipText);
  196. b.getAccessibleContext().setAccessibleName(homeFolderAccessibleName);
  197. b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
  198. b.setAlignmentY(JComponent.CENTER_ALIGNMENT);
  199. b.setMargin(shrinkwrap);
  200. b.addActionListener(getGoHomeAction());
  201. topButtonPanel.add(b);
  202. topButtonPanel.add(Box.createRigidArea(hstrut5));
  203. // New Directory Button
  204. if (!UIManager.getBoolean("FileChooser.readOnly")) {
  205. b = new JButton(filePane.getNewFolderAction());
  206. b.setText(null);
  207. b.setIcon(newFolderIcon);
  208. b.setToolTipText(newFolderToolTipText);
  209. b.getAccessibleContext().setAccessibleName(newFolderAccessibleName);
  210. b.setAlignmentX(JComponent.LEFT_ALIGNMENT);
  211. b.setAlignmentY(JComponent.CENTER_ALIGNMENT);
  212. b.setMargin(shrinkwrap);
  213. }
  214. topButtonPanel.add(b);
  215. topButtonPanel.add(Box.createRigidArea(hstrut5));
  216. // View button group
  217. ButtonGroup viewButtonGroup = new ButtonGroup();
  218. // List Button
  219. listViewButton = new JToggleButton(listViewIcon);
  220. listViewButton.setToolTipText(listViewButtonToolTipText);
  221. listViewButton.getAccessibleContext().setAccessibleName(listViewButtonAccessibleName);
  222. listViewButton.setSelected(true);
  223. listViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
  224. listViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
  225. listViewButton.setMargin(shrinkwrap);
  226. listViewButton.addActionListener(filePane.getViewTypeAction(FilePane.VIEWTYPE_LIST));
  227. topButtonPanel.add(listViewButton);
  228. viewButtonGroup.add(listViewButton);
  229. // Details Button
  230. detailsViewButton = new JToggleButton(detailsViewIcon);
  231. detailsViewButton.setToolTipText(detailsViewButtonToolTipText);
  232. detailsViewButton.getAccessibleContext().setAccessibleName(detailsViewButtonAccessibleName);
  233. detailsViewButton.setAlignmentX(JComponent.LEFT_ALIGNMENT);
  234. detailsViewButton.setAlignmentY(JComponent.CENTER_ALIGNMENT);
  235. detailsViewButton.setMargin(shrinkwrap);
  236. detailsViewButton.addActionListener(filePane.getViewTypeAction(FilePane.VIEWTYPE_DETAILS));
  237. topButtonPanel.add(detailsViewButton);
  238. viewButtonGroup.add(detailsViewButton);
  239. filePane.addPropertyChangeListener(new PropertyChangeListener() {
  240. public void propertyChange(PropertyChangeEvent e) {
  241. if ("viewType".equals(e.getPropertyName())) {
  242. int viewType = filePane.getViewType();
  243. switch (viewType) {
  244. case FilePane.VIEWTYPE_LIST:
  245. listViewButton.setSelected(true);
  246. break;
  247. case FilePane.VIEWTYPE_DETAILS:
  248. detailsViewButton.setSelected(true);
  249. break;
  250. }
  251. }
  252. }
  253. });
  254. // ************************************** //
  255. // ******* Add the directory pane ******* //
  256. // ************************************** //
  257. fc.add(getAccessoryPanel(), BorderLayout.AFTER_LINE_ENDS);
  258. JComponent accessory = fc.getAccessory();
  259. if(accessory != null) {
  260. getAccessoryPanel().add(accessory);
  261. }
  262. filePane.setPreferredSize(LIST_PREF_SIZE);
  263. fc.add(filePane, BorderLayout.CENTER);
  264. // ********************************** //
  265. // **** Construct the bottom panel ** //
  266. // ********************************** //
  267. JPanel bottomPanel = getBottomPanel();
  268. bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
  269. fc.add(bottomPanel, BorderLayout.SOUTH);
  270. // FileName label and textfield
  271. JPanel fileNamePanel = new JPanel();
  272. fileNamePanel.setLayout(new BoxLayout(fileNamePanel, BoxLayout.LINE_AXIS));
  273. bottomPanel.add(fileNamePanel);
  274. bottomPanel.add(Box.createRigidArea(vstrut5));
  275. AlignedLabel fileNameLabel = new AlignedLabel(fileNameLabelText);
  276. fileNameLabel.setDisplayedMnemonic(fileNameLabelMnemonic);
  277. fileNamePanel.add(fileNameLabel);
  278. fileNameTextField = new JTextField(35) {
  279. public Dimension getMaximumSize() {
  280. return new Dimension(Short.MAX_VALUE, super.getPreferredSize().height);
  281. }
  282. };
  283. fileNamePanel.add(fileNameTextField);
  284. fileNameLabel.setLabelFor(fileNameTextField);
  285. fileNameTextField.addFocusListener(
  286. new FocusAdapter() {
  287. public void focusGained(FocusEvent e) {
  288. if (!getFileChooser().isMultiSelectionEnabled()) {
  289. filePane.clearSelection();
  290. }
  291. }
  292. }
  293. );
  294. if (fc.isMultiSelectionEnabled()) {
  295. setFileName(fileNameString(fc.getSelectedFiles()));
  296. } else {
  297. setFileName(fileNameString(fc.getSelectedFile()));
  298. }
  299. // Filetype label and combobox
  300. JPanel filesOfTypePanel = new JPanel();
  301. filesOfTypePanel.setLayout(new BoxLayout(filesOfTypePanel, BoxLayout.LINE_AXIS));
  302. bottomPanel.add(filesOfTypePanel);
  303. AlignedLabel filesOfTypeLabel = new AlignedLabel(filesOfTypeLabelText);
  304. filesOfTypeLabel.setDisplayedMnemonic(filesOfTypeLabelMnemonic);
  305. filesOfTypePanel.add(filesOfTypeLabel);
  306. filterComboBoxModel = createFilterComboBoxModel();
  307. fc.addPropertyChangeListener(filterComboBoxModel);
  308. filterComboBox = new JComboBox(filterComboBoxModel);
  309. filterComboBox.getAccessibleContext().setAccessibleDescription(filesOfTypeLabelText);
  310. filesOfTypeLabel.setLabelFor(filterComboBox);
  311. filterComboBox.setRenderer(createFilterComboBoxRenderer());
  312. filesOfTypePanel.add(filterComboBox);
  313. // buttons
  314. getButtonPanel().setLayout(new ButtonAreaLayout());
  315. approveButton = new JButton(getApproveButtonText(fc));
  316. // Note: Metal does not use mnemonics for approve and cancel
  317. approveButton.addActionListener(getApproveSelectionAction());
  318. approveButton.setToolTipText(getApproveButtonToolTipText(fc));
  319. getButtonPanel().add(approveButton);
  320. cancelButton = new JButton(cancelButtonText);
  321. cancelButton.setToolTipText(cancelButtonToolTipText);
  322. cancelButton.addActionListener(getCancelSelectionAction());
  323. getButtonPanel().add(cancelButton);
  324. if(fc.getControlButtonsAreShown()) {
  325. addControlButtons();
  326. }
  327. groupLabels(new AlignedLabel[] { fileNameLabel, filesOfTypeLabel });
  328. }
  329. private void updateUseShellFolder() {
  330. // Decide whether to use the ShellFolder class to populate shortcut
  331. // panel and combobox.
  332. JFileChooser fc = getFileChooser();
  333. Boolean prop =
  334. (Boolean)fc.getClientProperty("FileChooser.useShellFolder");
  335. if (prop != null) {
  336. useShellFolder = prop.booleanValue();
  337. } else {
  338. // See if FileSystemView.getRoots() returns the desktop folder,
  339. // i.e. the normal Windows hierarchy.
  340. useShellFolder = false;
  341. File[] roots = fc.getFileSystemView().getRoots();
  342. if (roots != null && roots.length == 1) {
  343. File[] cbFolders = (File[])ShellFolder.get("fileChooserComboBoxFolders");
  344. if (cbFolders != null && cbFolders.length > 0 && roots[0] == cbFolders[0]) {
  345. useShellFolder = true;
  346. }
  347. }
  348. }
  349. }
  350. protected JPanel getButtonPanel() {
  351. if (buttonPanel == null) {
  352. buttonPanel = new JPanel();
  353. }
  354. return buttonPanel;
  355. }
  356. protected JPanel getBottomPanel() {
  357. if(bottomPanel == null) {
  358. bottomPanel = new JPanel();
  359. }
  360. return bottomPanel;
  361. }
  362. protected void installStrings(JFileChooser fc) {
  363. super.installStrings(fc);
  364. Locale l = fc.getLocale();
  365. lookInLabelMnemonic = UIManager.getInt("FileChooser.lookInLabelMnemonic");
  366. lookInLabelText = UIManager.getString("FileChooser.lookInLabelText",l);
  367. saveInLabelText = UIManager.getString("FileChooser.saveInLabelText",l);
  368. fileNameLabelMnemonic = UIManager.getInt("FileChooser.fileNameLabelMnemonic");
  369. fileNameLabelText = UIManager.getString("FileChooser.fileNameLabelText",l);
  370. filesOfTypeLabelMnemonic = UIManager.getInt("FileChooser.filesOfTypeLabelMnemonic");
  371. filesOfTypeLabelText = UIManager.getString("FileChooser.filesOfTypeLabelText",l);
  372. upFolderToolTipText = UIManager.getString("FileChooser.upFolderToolTipText",l);
  373. upFolderAccessibleName = UIManager.getString("FileChooser.upFolderAccessibleName",l);
  374. homeFolderToolTipText = UIManager.getString("FileChooser.homeFolderToolTipText",l);
  375. homeFolderAccessibleName = UIManager.getString("FileChooser.homeFolderAccessibleName",l);
  376. newFolderToolTipText = UIManager.getString("FileChooser.newFolderToolTipText",l);
  377. newFolderAccessibleName = UIManager.getString("FileChooser.newFolderAccessibleName",l);
  378. listViewButtonToolTipText = UIManager.getString("FileChooser.listViewButtonToolTipText",l);
  379. listViewButtonAccessibleName = UIManager.getString("FileChooser.listViewButtonAccessibleName",l);
  380. detailsViewButtonToolTipText = UIManager.getString("FileChooser.detailsViewButtonToolTipText",l);
  381. detailsViewButtonAccessibleName = UIManager.getString("FileChooser.detailsViewButtonAccessibleName",l);
  382. }
  383. protected void installListeners(JFileChooser fc) {
  384. super.installListeners(fc);
  385. ActionMap actionMap = getActionMap();
  386. SwingUtilities.replaceUIActionMap(fc, actionMap);
  387. }
  388. protected ActionMap getActionMap() {
  389. return createActionMap();
  390. }
  391. protected ActionMap createActionMap() {
  392. ActionMap map = new ActionMapUIResource();
  393. FilePane.addActionsToMap(map, filePane.getActions());
  394. return map;
  395. }
  396. protected JPanel createList(JFileChooser fc) {
  397. return filePane.createList();
  398. }
  399. protected JPanel createDetailsView(JFileChooser fc) {
  400. return filePane.createDetailsView();
  401. }
  402. /**
  403. * Creates a selection listener for the list of files and directories.
  404. *
  405. * @param fc a <code>JFileChooser</code>
  406. * @return a <code>ListSelectionListener</code>
  407. */
  408. public ListSelectionListener createListSelectionListener(JFileChooser fc) {
  409. return super.createListSelectionListener(fc);
  410. }
  411. // Obsolete class, not used in this version.
  412. protected class SingleClickListener extends MouseAdapter {
  413. public SingleClickListener(JList list) {
  414. }
  415. }
  416. // Obsolete class, not used in this version.
  417. protected class FileRenderer extends DefaultListCellRenderer {
  418. }
  419. public void uninstallUI(JComponent c) {
  420. // Remove listeners
  421. c.removePropertyChangeListener(filterComboBoxModel);
  422. c.removePropertyChangeListener(filePane);
  423. cancelButton.removeActionListener(getCancelSelectionAction());
  424. approveButton.removeActionListener(getApproveSelectionAction());
  425. fileNameTextField.removeActionListener(getApproveSelectionAction());
  426. super.uninstallUI(c);
  427. }
  428. /**
  429. * Returns the preferred size of the specified
  430. * <code>JFileChooser</code>.
  431. * The preferred size is at least as large,
  432. * in both height and width,
  433. * as the preferred size recommended
  434. * by the file chooser's layout manager.
  435. *
  436. * @param c a <code>JFileChooser</code>
  437. * @return a <code>Dimension</code> specifying the preferred
  438. * width and height of the file chooser
  439. */
  440. public Dimension getPreferredSize(JComponent c) {
  441. int prefWidth = PREF_SIZE.width;
  442. Dimension d = c.getLayout().preferredLayoutSize(c);
  443. if (d != null) {
  444. return new Dimension(d.width < prefWidth ? prefWidth : d.width,
  445. d.height < PREF_SIZE.height ? PREF_SIZE.height : d.height);
  446. } else {
  447. return new Dimension(prefWidth, PREF_SIZE.height);
  448. }
  449. }
  450. /**
  451. * Returns the minimum size of the <code>JFileChooser</code>.
  452. *
  453. * @param c a <code>JFileChooser</code>
  454. * @return a <code>Dimension</code> specifying the minimum
  455. * width and height of the file chooser
  456. */
  457. public Dimension getMinimumSize(JComponent c) {
  458. return MIN_SIZE;
  459. }
  460. /**
  461. * Returns the maximum size of the <code>JFileChooser</code>.
  462. *
  463. * @param c a <code>JFileChooser</code>
  464. * @return a <code>Dimension</code> specifying the maximum
  465. * width and height of the file chooser
  466. */
  467. public Dimension getMaximumSize(JComponent c) {
  468. return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  469. }
  470. private String fileNameString(File file) {
  471. if (file == null) {
  472. return null;
  473. } else {
  474. JFileChooser fc = getFileChooser();
  475. if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
  476. return file.getPath();
  477. } else {
  478. return file.getName();
  479. }
  480. }
  481. }
  482. private String fileNameString(File[] files) {
  483. StringBuffer buf = new StringBuffer();
  484. for (int i = 0; files != null && i < files.length; i++) {
  485. if (i > 0) {
  486. buf.append(" ");
  487. }
  488. if (files.length > 1) {
  489. buf.append("\"");
  490. }
  491. buf.append(fileNameString(files[i]));
  492. if (files.length > 1) {
  493. buf.append("\"");
  494. }
  495. }
  496. return buf.toString();
  497. }
  498. /* The following methods are used by the PropertyChange Listener */
  499. private void doSelectedFileChanged(PropertyChangeEvent e) {
  500. File f = (File) e.getNewValue();
  501. JFileChooser fc = getFileChooser();
  502. if (f != null
  503. && ((fc.isFileSelectionEnabled() && !f.isDirectory())
  504. || (f.isDirectory() && fc.isDirectorySelectionEnabled()))) {
  505. setFileName(fileNameString(f));
  506. }
  507. }
  508. private void doSelectedFilesChanged(PropertyChangeEvent e) {
  509. File[] files = (File[]) e.getNewValue();
  510. JFileChooser fc = getFileChooser();
  511. if (files != null
  512. && files.length > 0
  513. && (files.length > 1 || fc.isDirectorySelectionEnabled() || !files[0].isDirectory())) {
  514. setFileName(fileNameString(files));
  515. }
  516. }
  517. private void doDirectoryChanged(PropertyChangeEvent e) {
  518. JFileChooser fc = getFileChooser();
  519. FileSystemView fsv = fc.getFileSystemView();
  520. clearIconCache();
  521. File currentDirectory = fc.getCurrentDirectory();
  522. if(currentDirectory != null) {
  523. directoryComboBoxModel.addItem(currentDirectory);
  524. if (fc.isDirectorySelectionEnabled() && !fc.isFileSelectionEnabled()) {
  525. if (fsv.isFileSystem(currentDirectory)) {
  526. setFileName(currentDirectory.getPath());
  527. } else {
  528. setFileName(null);
  529. }
  530. }
  531. }
  532. }
  533. private void doFilterChanged(PropertyChangeEvent e) {
  534. clearIconCache();
  535. }
  536. private void doFileSelectionModeChanged(PropertyChangeEvent e) {
  537. clearIconCache();
  538. JFileChooser fc = getFileChooser();
  539. File currentDirectory = fc.getCurrentDirectory();
  540. if (currentDirectory != null
  541. && fc.isDirectorySelectionEnabled()
  542. && !fc.isFileSelectionEnabled()
  543. && fc.getFileSystemView().isFileSystem(currentDirectory)) {
  544. setFileName(currentDirectory.getPath());
  545. } else {
  546. setFileName(null);
  547. }
  548. }
  549. private void doAccessoryChanged(PropertyChangeEvent e) {
  550. if(getAccessoryPanel() != null) {
  551. if(e.getOldValue() != null) {
  552. getAccessoryPanel().remove((JComponent) e.getOldValue());
  553. }
  554. JComponent accessory = (JComponent) e.getNewValue();
  555. if(accessory != null) {
  556. getAccessoryPanel().add(accessory, BorderLayout.CENTER);
  557. }
  558. }
  559. }
  560. private void doApproveButtonTextChanged(PropertyChangeEvent e) {
  561. JFileChooser chooser = getFileChooser();
  562. approveButton.setText(getApproveButtonText(chooser));
  563. approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
  564. }
  565. private void doDialogTypeChanged(PropertyChangeEvent e) {
  566. JFileChooser chooser = getFileChooser();
  567. approveButton.setText(getApproveButtonText(chooser));
  568. approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
  569. if (chooser.getDialogType() == JFileChooser.SAVE_DIALOG) {
  570. lookInLabel.setText(saveInLabelText);
  571. } else {
  572. lookInLabel.setText(lookInLabelText);
  573. }
  574. }
  575. private void doApproveButtonMnemonicChanged(PropertyChangeEvent e) {
  576. // Note: Metal does not use mnemonics for approve and cancel
  577. }
  578. private void doControlButtonsChanged(PropertyChangeEvent e) {
  579. if(getFileChooser().getControlButtonsAreShown()) {
  580. addControlButtons();
  581. } else {
  582. removeControlButtons();
  583. }
  584. }
  585. /*
  586. * Listen for filechooser property changes, such as
  587. * the selected file changing, or the type of the dialog changing.
  588. */
  589. public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) {
  590. return new PropertyChangeListener() {
  591. public void propertyChange(PropertyChangeEvent e) {
  592. String s = e.getPropertyName();
  593. if(s.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
  594. doSelectedFileChanged(e);
  595. } else if (s.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
  596. doSelectedFilesChanged(e);
  597. } else if(s.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
  598. doDirectoryChanged(e);
  599. } else if(s.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)) {
  600. doFilterChanged(e);
  601. } else if(s.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
  602. doFileSelectionModeChanged(e);
  603. } else if(s.equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY)) {
  604. doAccessoryChanged(e);
  605. } else if (s.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY) ||
  606. s.equals(JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY)) {
  607. doApproveButtonTextChanged(e);
  608. } else if(s.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)) {
  609. doDialogTypeChanged(e);
  610. } else if(s.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY)) {
  611. doApproveButtonMnemonicChanged(e);
  612. } else if(s.equals(JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY)) {
  613. doControlButtonsChanged(e);
  614. } else if (s.equals("componentOrientation")) {
  615. ComponentOrientation o = (ComponentOrientation)e.getNewValue();
  616. JFileChooser cc = (JFileChooser)e.getSource();
  617. if (o != (ComponentOrientation)e.getOldValue()) {
  618. cc.applyComponentOrientation(o);
  619. }
  620. } else if (s == "FileChooser.useShellFolder") {
  621. updateUseShellFolder();
  622. doDirectoryChanged(e);
  623. } else if (s.equals("ancestor")) {
  624. if (e.getOldValue() == null && e.getNewValue() != null) {
  625. // Ancestor was added, set initial focus
  626. fileNameTextField.selectAll();
  627. fileNameTextField.requestFocus();
  628. }
  629. }
  630. }
  631. };
  632. }
  633. protected void removeControlButtons() {
  634. getBottomPanel().remove(getButtonPanel());
  635. }
  636. protected void addControlButtons() {
  637. getBottomPanel().add(getButtonPanel());
  638. }
  639. public void ensureFileIsVisible(JFileChooser fc, File f) {
  640. filePane.ensureFileIsVisible(fc, f);
  641. }
  642. public void rescanCurrentDirectory(JFileChooser fc) {
  643. filePane.rescanCurrentDirectory();
  644. }
  645. public String getFileName() {
  646. if (fileNameTextField != null) {
  647. return fileNameTextField.getText();
  648. } else {
  649. return null;
  650. }
  651. }
  652. public void setFileName(String filename) {
  653. if (fileNameTextField != null) {
  654. fileNameTextField.setText(filename);
  655. }
  656. }
  657. /**
  658. * Property to remember whether a directory is currently selected in the UI.
  659. * This is normally called by the UI on a selection event.
  660. *
  661. * @param directorySelected if a directory is currently selected.
  662. * @since 1.4
  663. */
  664. protected void setDirectorySelected(boolean directorySelected) {
  665. super.setDirectorySelected(directorySelected);
  666. JFileChooser chooser = getFileChooser();
  667. if(directorySelected) {
  668. if (approveButton != null) {
  669. approveButton.setText(directoryOpenButtonText);
  670. approveButton.setToolTipText(directoryOpenButtonToolTipText);
  671. }
  672. } else {
  673. if (approveButton != null) {
  674. approveButton.setText(getApproveButtonText(chooser));
  675. approveButton.setToolTipText(getApproveButtonToolTipText(chooser));
  676. }
  677. }
  678. }
  679. public String getDirectoryName() {
  680. // PENDING(jeff) - get the name from the directory combobox
  681. return null;
  682. }
  683. public void setDirectoryName(String dirname) {
  684. // PENDING(jeff) - set the name in the directory combobox
  685. }
  686. protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(JFileChooser fc) {
  687. return new DirectoryComboBoxRenderer();
  688. }
  689. //
  690. // Renderer for DirectoryComboBox
  691. //
  692. class DirectoryComboBoxRenderer extends DefaultListCellRenderer {
  693. IndentIcon ii = new IndentIcon();
  694. public Component getListCellRendererComponent(JList list, Object value,
  695. int index, boolean isSelected,
  696. boolean cellHasFocus) {
  697. super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  698. if (value == null) {
  699. setText("");
  700. return this;
  701. }
  702. File directory = (File)value;
  703. setText(getFileChooser().getName(directory));
  704. Icon icon = getFileChooser().getIcon(directory);
  705. ii.icon = icon;
  706. ii.depth = directoryComboBoxModel.getDepth(index);
  707. setIcon(ii);
  708. return this;
  709. }
  710. }
  711. final static int space = 10;
  712. class IndentIcon implements Icon {
  713. Icon icon = null;
  714. int depth = 0;
  715. public void paintIcon(Component c, Graphics g, int x, int y) {
  716. if (c.getComponentOrientation().isLeftToRight()) {
  717. icon.paintIcon(c, g, x+depth*space, y);
  718. } else {
  719. icon.paintIcon(c, g, x, y);
  720. }
  721. }
  722. public int getIconWidth() {
  723. return icon.getIconWidth() + depth*space;
  724. }
  725. public int getIconHeight() {
  726. return icon.getIconHeight();
  727. }
  728. }
  729. //
  730. // DataModel for DirectoryComboxbox
  731. //
  732. protected DirectoryComboBoxModel createDirectoryComboBoxModel(JFileChooser fc) {
  733. return new DirectoryComboBoxModel();
  734. }
  735. /**
  736. * Data model for a type-face selection combo-box.
  737. */
  738. protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel {
  739. Vector directories = new Vector();
  740. int[] depths = null;
  741. File selectedDirectory = null;
  742. JFileChooser chooser = getFileChooser();
  743. FileSystemView fsv = chooser.getFileSystemView();
  744. public DirectoryComboBoxModel() {
  745. // Add the current directory to the model, and make it the
  746. // selectedDirectory
  747. File dir = getFileChooser().getCurrentDirectory();
  748. if(dir != null) {
  749. addItem(dir);
  750. }
  751. }
  752. /**
  753. * Adds the directory to the model and sets it to be selected,
  754. * additionally clears out the previous selected directory and
  755. * the paths leading up to it, if any.
  756. */
  757. private void addItem(File directory) {
  758. if(directory == null) {
  759. return;
  760. }
  761. directories.clear();
  762. File[] baseFolders;
  763. if (useShellFolder) {
  764. baseFolders = (File[])ShellFolder.get("fileChooserComboBoxFolders");
  765. } else {
  766. baseFolders = fsv.getRoots();
  767. }
  768. directories.addAll(Arrays.asList(baseFolders));
  769. // Get the canonical (full) path. This has the side
  770. // benefit of removing extraneous chars from the path,
  771. // for example /foo/bar/ becomes /foo/bar
  772. File canonical = null;
  773. try {
  774. canonical = directory.getCanonicalFile();
  775. } catch (IOException e) {
  776. // Maybe drive is not ready. Can't abort here.
  777. canonical = directory;
  778. }
  779. // create File instances of each directory leading up to the top
  780. try {
  781. File sf = useShellFolder ? ShellFolder.getShellFolder(canonical)
  782. : canonical;
  783. File f = sf;
  784. Vector path = new Vector(10);
  785. do {
  786. path.addElement(f);
  787. } while ((f = f.getParentFile()) != null);
  788. int pathCount = path.size();
  789. // Insert chain at appropriate place in vector
  790. for (int i = 0; i < pathCount; i++) {
  791. f = (File)path.get(i);
  792. if (directories.contains(f)) {
  793. int topIndex = directories.indexOf(f);
  794. for (int j = i-1; j >= 0; j--) {
  795. directories.insertElementAt(path.get(j), topIndex+i-j);
  796. }
  797. break;
  798. }
  799. }
  800. calculateDepths();
  801. setSelectedItem(sf);
  802. } catch (FileNotFoundException ex) {
  803. calculateDepths();
  804. }
  805. }
  806. private void calculateDepths() {
  807. depths = new int[directories.size()];
  808. for (int i = 0; i < depths.length; i++) {
  809. File dir = (File)directories.get(i);
  810. File parent = dir.getParentFile();
  811. depths[i] = 0;
  812. if (parent != null) {
  813. for (int j = i-1; j >= 0; j--) {
  814. if (parent.equals((File)directories.get(j))) {
  815. depths[i] = depths[j] + 1;
  816. break;
  817. }
  818. }
  819. }
  820. }
  821. }
  822. public int getDepth(int i) {
  823. return (depths != null && i >= 0 && i < depths.length) ? depths[i] : 0;
  824. }
  825. public void setSelectedItem(Object selectedDirectory) {
  826. this.selectedDirectory = (File)selectedDirectory;
  827. fireContentsChanged(this, -1, -1);
  828. }
  829. public Object getSelectedItem() {
  830. return selectedDirectory;
  831. }
  832. public int getSize() {
  833. return directories.size();
  834. }
  835. public Object getElementAt(int index) {
  836. return directories.elementAt(index);
  837. }
  838. }
  839. //
  840. // Renderer for Types ComboBox
  841. //
  842. protected FilterComboBoxRenderer createFilterComboBoxRenderer() {
  843. return new FilterComboBoxRenderer();
  844. }
  845. /**
  846. * Render different type sizes and styles.
  847. */
  848. public class FilterComboBoxRenderer extends DefaultListCellRenderer {
  849. public Component getListCellRendererComponent(JList list,
  850. Object value, int index, boolean isSelected,
  851. boolean cellHasFocus) {
  852. super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  853. if (value != null && value instanceof FileFilter) {
  854. setText(((FileFilter)value).getDescription());
  855. }
  856. return this;
  857. }
  858. }
  859. //
  860. // DataModel for Types Comboxbox
  861. //
  862. protected FilterComboBoxModel createFilterComboBoxModel() {
  863. return new FilterComboBoxModel();
  864. }
  865. /**
  866. * Data model for a type-face selection combo-box.
  867. */
  868. protected class FilterComboBoxModel extends AbstractListModel implements ComboBoxModel, PropertyChangeListener {
  869. protected FileFilter[] filters;
  870. protected FilterComboBoxModel() {
  871. super();
  872. filters = getFileChooser().getChoosableFileFilters();
  873. }
  874. public void propertyChange(PropertyChangeEvent e) {
  875. String prop = e.getPropertyName();
  876. if(prop == JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY) {
  877. filters = (FileFilter[]) e.getNewValue();
  878. fireContentsChanged(this, -1, -1);
  879. } else if (prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY) {
  880. fireContentsChanged(this, -1, -1);
  881. }
  882. }
  883. public void setSelectedItem(Object filter) {
  884. if(filter != null) {
  885. getFileChooser().setFileFilter((FileFilter) filter);
  886. setFileName(null);
  887. fireContentsChanged(this, -1, -1);
  888. }
  889. }
  890. public Object getSelectedItem() {
  891. // Ensure that the current filter is in the list.
  892. // NOTE: we shouldnt' have to do this, since JFileChooser adds
  893. // the filter to the choosable filters list when the filter
  894. // is set. Lets be paranoid just in case someone overrides
  895. // setFileFilter in JFileChooser.
  896. FileFilter currentFilter = getFileChooser().getFileFilter();
  897. boolean found = false;
  898. if(currentFilter != null) {
  899. for(int i=0; i < filters.length; i++) {
  900. if(filters[i] == currentFilter) {
  901. found = true;
  902. }
  903. }
  904. if(found == false) {
  905. getFileChooser().addChoosableFileFilter(currentFilter);
  906. }
  907. }
  908. return getFileChooser().getFileFilter();
  909. }
  910. public int getSize() {
  911. if(filters != null) {
  912. return filters.length;
  913. } else {
  914. return 0;
  915. }
  916. }
  917. public Object getElementAt(int index) {
  918. if(index > getSize() - 1) {
  919. // This shouldn't happen. Try to recover gracefully.
  920. return getFileChooser().getFileFilter();
  921. }
  922. if(filters != null) {
  923. return filters[index];
  924. } else {
  925. return null;
  926. }
  927. }
  928. }
  929. public void valueChanged(ListSelectionEvent e) {
  930. JFileChooser fc = getFileChooser();
  931. File f = fc.getSelectedFile();
  932. if (!e.getValueIsAdjusting() && f != null && !getFileChooser().isTraversable(f)) {
  933. setFileName(fileNameString(f));
  934. }
  935. }
  936. /**
  937. * Acts when DirectoryComboBox has changed the selected item.
  938. */
  939. protected class DirectoryComboBoxAction extends AbstractAction {
  940. protected DirectoryComboBoxAction() {
  941. super("DirectoryComboBoxAction");
  942. }
  943. public void actionPerformed(ActionEvent e) {
  944. directoryComboBox.hidePopup();
  945. File f = (File)directoryComboBox.getSelectedItem();
  946. if (!getFileChooser().getCurrentDirectory().equals(f)) {
  947. getFileChooser().setCurrentDirectory(f);
  948. }
  949. }
  950. }
  951. protected JButton getApproveButton(JFileChooser fc) {
  952. return approveButton;
  953. }
  954. /**
  955. * <code>ButtonAreaLayout</code> behaves in a similar manner to
  956. * <code>FlowLayout</code>. It lays out all components from left to
  957. * right, flushed right. The widths of all components will be set
  958. * to the largest preferred size width.
  959. */
  960. private static class ButtonAreaLayout implements LayoutManager {
  961. private int hGap = 5;
  962. private int topMargin = 17;
  963. public void addLayoutComponent(String string, Component comp) {
  964. }
  965. public void layoutContainer(Container container) {
  966. Component[] children = container.getComponents();
  967. if (children != null && children.length > 0) {
  968. int numChildren = children.length;
  969. Dimension[] sizes = new Dimension[numChildren];
  970. Insets insets = container.getInsets();
  971. int yLocation = insets.top + topMargin;
  972. int maxWidth = 0;
  973. for (int counter = 0; counter < numChildren; counter++) {
  974. sizes[counter] = children[counter].getPreferredSize();
  975. maxWidth = Math.max(maxWidth, sizes[counter].width);
  976. }
  977. int xLocation, xOffset;
  978. if (container.getComponentOrientation().isLeftToRight()) {
  979. xLocation = container.getSize().width - insets.left - maxWidth;
  980. xOffset = hGap + maxWidth;
  981. } else {
  982. xLocation = insets.left;
  983. xOffset = -(hGap + maxWidth);
  984. }
  985. for (int counter = numChildren - 1; counter >= 0; counter--) {
  986. children[counter].setBounds(xLocation, yLocation,
  987. maxWidth, sizes[counter].height);
  988. xLocation -= xOffset;
  989. }
  990. }
  991. }
  992. public Dimension minimumLayoutSize(Container c) {
  993. if (c != null) {
  994. Component[] children = c.getComponents();
  995. if (children != null && children.length > 0) {
  996. int numChildren = children.length;
  997. int height = 0;
  998. Insets cInsets = c.getInsets();
  999. int extraHeight = topMargin + cInsets.top + cInsets.bottom;
  1000. int extraWidth = cInsets.left + cInsets.right;
  1001. int maxWidth = 0;
  1002. for (int counter = 0; counter < numChildren; counter++) {
  1003. Dimension aSize = children[counter].getPreferredSize();
  1004. height = Math.max(height, aSize.height);
  1005. maxWidth = Math.max(maxWidth, aSize.width);
  1006. }
  1007. return new Dimension(extraWidth + numChildren * maxWidth +
  1008. (numChildren - 1) * hGap,
  1009. extraHeight + height);
  1010. }
  1011. }
  1012. return new Dimension(0, 0);
  1013. }
  1014. public Dimension preferredLayoutSize(Container c) {
  1015. return minimumLayoutSize(c);
  1016. }
  1017. public void removeLayoutComponent(Component c) { }
  1018. }
  1019. private static void groupLabels(AlignedLabel[] group) {
  1020. for (int i = 0; i < group.length; i++) {
  1021. group[i].group = group;
  1022. }
  1023. }
  1024. private class AlignedLabel extends JLabel {
  1025. private AlignedLabel[] group;
  1026. private int maxWidth = 0;
  1027. AlignedLabel(String text) {
  1028. super(text);
  1029. setAlignmentX(JComponent.LEFT_ALIGNMENT);
  1030. }
  1031. public Dimension getPreferredSize() {
  1032. Dimension d = super.getPreferredSize();
  1033. // Align the width with all other labels in group.
  1034. return new Dimension(getMaxWidth() + 11, d.height);
  1035. }
  1036. private int getMaxWidth() {
  1037. if (maxWidth == 0 && group != null) {
  1038. int max = 0;
  1039. for (int i = 0; i < group.length; i++) {
  1040. max = Math.max(group[i].getSuperPreferredWidth(), max);
  1041. }
  1042. for (int i = 0; i < group.length; i++) {
  1043. group[i].maxWidth = max;
  1044. }
  1045. }
  1046. return maxWidth;
  1047. }
  1048. private int getSuperPreferredWidth() {
  1049. return super.getPreferredSize().width;
  1050. }
  1051. }
  1052. }