Source for javax.swing.plaf.metal.MetalFileChooserUI

   1: /* MetalFileChooserUI.java --
   2:    Copyright (C) 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.metal;
  40: 
  41: import java.awt.BorderLayout;
  42: import java.awt.Component;
  43: import java.awt.Container;
  44: import java.awt.Dimension;
  45: import java.awt.GridLayout;
  46: import java.awt.Insets;
  47: import java.awt.LayoutManager;
  48: import java.awt.Rectangle;
  49: import java.awt.Window;
  50: import java.awt.event.ActionEvent;
  51: import java.awt.event.ActionListener;
  52: import java.awt.event.MouseAdapter;
  53: import java.awt.event.MouseEvent;
  54: import java.awt.event.MouseListener;
  55: import java.text.NumberFormat;
  56: 
  57: import java.beans.PropertyChangeEvent;
  58: import java.beans.PropertyChangeListener;
  59: 
  60: import java.io.File;
  61: 
  62: import javax.swing.AbstractAction;
  63: import javax.swing.AbstractListModel;
  64: import javax.swing.ActionMap;
  65: import javax.swing.BorderFactory;
  66: import javax.swing.ButtonGroup;
  67: import javax.swing.ComboBoxModel;
  68: import javax.swing.DefaultListCellRenderer;
  69: import javax.swing.Icon;
  70: import javax.swing.JButton;
  71: import javax.swing.JComboBox;
  72: import javax.swing.JComponent;
  73: import javax.swing.JDialog;
  74: import javax.swing.JFileChooser;
  75: import javax.swing.JLabel;
  76: import javax.swing.JList;
  77: import javax.swing.JPanel;
  78: import javax.swing.JScrollPane;
  79: import javax.swing.JTable;
  80: import javax.swing.JTextField;
  81: import javax.swing.JToggleButton;
  82: import javax.swing.JViewport;
  83: import javax.swing.ListModel;
  84: import javax.swing.ListSelectionModel;
  85: import javax.swing.SwingUtilities;
  86: import javax.swing.UIManager;
  87: import javax.swing.event.ListSelectionEvent;
  88: import javax.swing.event.ListSelectionListener;
  89: import javax.swing.filechooser.FileFilter;
  90: import javax.swing.filechooser.FileSystemView;
  91: import javax.swing.filechooser.FileView;
  92: import javax.swing.plaf.ComponentUI;
  93: import javax.swing.plaf.basic.BasicFileChooserUI;
  94: import javax.swing.table.DefaultTableCellRenderer;
  95: import javax.swing.table.DefaultTableModel;
  96: 
  97: import java.sql.Date;
  98: 
  99: import java.text.DateFormat;
 100: 
 101: import java.util.List;
 102: 
 103: 
 104: /**
 105:  * A UI delegate for the {@link JFileChooser} component.  This class is only
 106:  * partially implemented and is not usable yet.
 107:  */
 108: public class MetalFileChooserUI 
 109:   extends BasicFileChooserUI
 110: {
 111:   
 112:   /**
 113:    * A renderer for the files and directories in the file chooser table.
 114:    */
 115:   class TableFileRenderer
 116:     extends DefaultTableCellRenderer
 117:   {
 118:     
 119:     /**
 120:      * Creates a new renderer.
 121:      */
 122:     public TableFileRenderer()
 123:     {
 124:       super();
 125:     }
 126:     
 127:     /**
 128:      * Returns a component that can render the specified value.
 129:      * 
 130:      * @param table  the table
 131:      * @param value  the string value of the cell
 132:      * @param isSelected  is the item selected?
 133:      * @param hasFocus  does the item have the focus?
 134:      * @param row  the row
 135:      * @param column  the column
 136:      * 
 137:      * @return The renderer.
 138:      */
 139:     public Component getTableCellRendererComponent(JTable table, Object value,
 140:         boolean isSelected, boolean hasFocus, int row, int column)
 141:     {
 142:       if (column == 0)
 143:         {
 144:           FileView v = getFileView(getFileChooser());
 145:           ListModel lm = fileList.getModel();
 146:           if (row < lm.getSize())
 147:             setIcon(v.getIcon((File) lm.getElementAt(row)));
 148:         }
 149:       else
 150:         setIcon(null);
 151:       
 152:       setText(value.toString());
 153:       setOpaque(true);
 154:       setEnabled(table.isEnabled());
 155:       setFont(fileList.getFont());
 156:       
 157:       if (startEditing && column == 0 || !isSelected)
 158:         {
 159:           setBackground(table.getBackground());
 160:           setForeground(table.getForeground());
 161:         }
 162:       else
 163:         {
 164:           setBackground(table.getSelectionBackground());
 165:           setForeground(table.getSelectionForeground());
 166:         }
 167: 
 168:       if (hasFocus)
 169:         setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
 170:       else
 171:         setBorder(noFocusBorder);
 172:       
 173:       return this;
 174:     }
 175:   }
 176:   
 177:   /**
 178:    * ActionListener for the list view.
 179:    */
 180:   class ListViewActionListener implements ActionListener
 181:   {
 182:     
 183:     /**
 184:      * This method is invoked when an action occurs.
 185:      * 
 186:      * @param e -
 187:      *          the <code>ActionEvent</code> that occurred
 188:      */
 189:     public void actionPerformed(ActionEvent e)
 190:     {
 191:       if (!listView)
 192:         {
 193:           int[] index = fileTable.getSelectedRows();
 194:           listView = true;
 195:           JFileChooser fc = getFileChooser();
 196:           fc.remove(fileTablePanel);
 197:           createList(fc);
 198: 
 199:           fileList.getSelectionModel().clearSelection();
 200:           if (index.length > 0)
 201:               for (int i = 0; i < index.length; i++)
 202:                 fileList.getSelectionModel().addSelectionInterval(index[i], index[i]);
 203:           
 204:           fc.add(fileListPanel, BorderLayout.CENTER);
 205:           fc.revalidate();
 206:           fc.repaint();
 207:         }
 208:     }
 209:   }
 210:   
 211:   /**
 212:    * ActionListener for the details view.
 213:    */
 214:   class DetailViewActionListener implements ActionListener
 215:   {
 216:     
 217:     /**
 218:      * This method is invoked when an action occurs.
 219:      * 
 220:      * @param e -
 221:      *          the <code>ActionEvent</code> that occurred
 222:      */
 223:     public void actionPerformed(ActionEvent e)
 224:     {
 225:       if (listView)
 226:         {
 227:           int[] index = fileList.getSelectedIndices();
 228:           JFileChooser fc = getFileChooser();
 229:           listView = false;
 230:           fc.remove(fileListPanel);
 231:           
 232:           if (fileTable == null)
 233:             createDetailsView(fc);
 234:           else
 235:             updateTable();
 236: 
 237:           fileTable.getSelectionModel().clearSelection();
 238:           if (index.length > 0)
 239:             {
 240:               for (int i = 0; i < index.length; i++)
 241:                 fileTable.getSelectionModel().addSelectionInterval(index[i], index[i]);
 242:             }
 243:           
 244:           fc.add(fileTablePanel, BorderLayout.CENTER);
 245:           fc.revalidate();
 246:           fc.repaint();
 247:         }
 248:     }
 249:   }
 250:   
 251:   /**
 252:    * A property change listener.
 253:    */
 254:   class MetalFileChooserPropertyChangeListener 
 255:     implements PropertyChangeListener
 256:   {
 257:     /**
 258:      * Default constructor.
 259:      */
 260:     public MetalFileChooserPropertyChangeListener()
 261:     {
 262:     }
 263:     
 264:     /**
 265:      * Handles a property change event.
 266:      * 
 267:      * @param e  the event.
 268:      */
 269:     public void propertyChange(PropertyChangeEvent e)
 270:     {
 271:       JFileChooser filechooser = getFileChooser();
 272:       
 273:       String n = e.getPropertyName();
 274:       if (n.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY))
 275:         {
 276:           int mode = -1; 
 277:           if (filechooser.isMultiSelectionEnabled())
 278:             mode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
 279:           else
 280:             mode = ListSelectionModel.SINGLE_SELECTION;
 281:           
 282:           if (listView)
 283:             fileList.setSelectionMode(mode);
 284:           else
 285:             fileTable.setSelectionMode(mode);
 286:         }
 287:       else if (n.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
 288:         {
 289:           File file = filechooser.getSelectedFile();
 290:           
 291:           if (file != null
 292:               && filechooser.getDialogType() == JFileChooser.SAVE_DIALOG)
 293:             {
 294:               if (file.isDirectory() && filechooser.isTraversable(file))
 295:                 {
 296:                   directoryLabel = look;
 297:                   dirLabel.setText(directoryLabel);
 298:                   filechooser.setApproveButtonText(openButtonText);
 299:                   filechooser.setApproveButtonToolTipText(openButtonToolTipText);
 300:                 }
 301:               else if (file.isFile())
 302:                 {
 303:                   directoryLabel = save;
 304:                   dirLabel.setText(directoryLabel);
 305:                   filechooser.setApproveButtonText(saveButtonText);
 306:                   filechooser.setApproveButtonToolTipText(saveButtonToolTipText);
 307:                 }
 308:             }
 309:             
 310:           if (file == null)
 311:             setFileName(null);
 312:           else
 313:             setFileName(file.getName());
 314:           int index = -1;
 315:           index = getModel().indexOf(file);
 316:           if (index >= 0)
 317:             {
 318:               if (listView)
 319:                 {
 320:                   fileList.setSelectedIndex(index);
 321:                   fileList.ensureIndexIsVisible(index);
 322:                   fileList.revalidate();
 323:                   fileList.repaint();
 324:                 }
 325:               else
 326:                 {
 327:                   fileTable.getSelectionModel().addSelectionInterval(index, index);
 328:                   fileTable.scrollRectToVisible(fileTable.getCellRect(index, 0, true));
 329:                   fileTable.revalidate();
 330:                   fileTable.repaint();
 331:                 }
 332:             }
 333:         }
 334:       
 335:       else if (n.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY))
 336:         {
 337:           if (listView)
 338:             {
 339:               fileList.clearSelection();
 340:               fileList.revalidate();
 341:               fileList.repaint();
 342:             }
 343:           else
 344:             {
 345:               fileTable.clearSelection();
 346:               fileTable.revalidate();
 347:               fileTable.repaint();
 348:             }
 349: 
 350:           setDirectorySelected(false);
 351:           File currentDirectory = filechooser.getCurrentDirectory();
 352:           setDirectory(currentDirectory);
 353:           boolean hasParent = (currentDirectory.getParentFile() != null);
 354:           getChangeToParentDirectoryAction().setEnabled(hasParent);
 355:         }
 356:       
 357:       else if (n.equals(JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY))
 358:         {
 359:           filterModel.propertyChange(e);
 360:         }
 361:       else if (n.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY))
 362:         {
 363:           filterModel.propertyChange(e);
 364:         }
 365:       else if (n.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)
 366:                  || n.equals(JFileChooser.DIALOG_TITLE_CHANGED_PROPERTY))
 367:         {
 368:           Window owner = SwingUtilities.windowForComponent(filechooser);
 369:           if (owner instanceof JDialog)
 370:             ((JDialog) owner).setTitle(getDialogTitle(filechooser));
 371:           approveButton.setText(getApproveButtonText(filechooser));
 372:           approveButton.setToolTipText(
 373:                   getApproveButtonToolTipText(filechooser));
 374:           approveButton.setMnemonic(getApproveButtonMnemonic(filechooser));
 375:         }
 376:       
 377:       else if (n.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY))
 378:         approveButton.setText(getApproveButtonText(filechooser));
 379:       
 380:       else if (n.equals(
 381:               JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY))
 382:         approveButton.setToolTipText(getApproveButtonToolTipText(filechooser));
 383:       
 384:       else if (n.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY))
 385:         approveButton.setMnemonic(getApproveButtonMnemonic(filechooser));
 386: 
 387:       else if (n.equals(
 388:               JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY))
 389:         {
 390:           if (filechooser.getControlButtonsAreShown())
 391:             {
 392:               topPanel.add(controls, BorderLayout.EAST);
 393:             }
 394:           else
 395:             topPanel.remove(controls);
 396:           topPanel.revalidate();
 397:           topPanel.repaint();
 398:           topPanel.doLayout();
 399:         }
 400:       
 401:       else if (n.equals(
 402:               JFileChooser.ACCEPT_ALL_FILE_FILTER_USED_CHANGED_PROPERTY))
 403:         {
 404:           if (filechooser.isAcceptAllFileFilterUsed())
 405:             filechooser.addChoosableFileFilter(
 406:                     getAcceptAllFileFilter(filechooser));
 407:           else
 408:             filechooser.removeChoosableFileFilter(
 409:                     getAcceptAllFileFilter(filechooser));
 410:         }
 411:       
 412:       else if (n.equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY))
 413:         {
 414:           JComponent old = (JComponent) e.getOldValue();
 415:           if (old != null)
 416:             getAccessoryPanel().remove(old);
 417:           JComponent newval = (JComponent) e.getNewValue();
 418:           if (newval != null)
 419:             getAccessoryPanel().add(newval);
 420:         }
 421:       
 422:       if (n.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)
 423:           || n.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)
 424:           || n.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY))
 425:         {
 426:           // Remove editing component
 427:           if (fileTable != null)
 428:             fileTable.removeAll();
 429:           if (fileList != null)
 430:             fileList.removeAll();
 431:           startEditing = false;
 432:           
 433:           // Set text on button back to original.
 434:           if (filechooser.getDialogType() == JFileChooser.SAVE_DIALOG)
 435:             {
 436:               directoryLabel = save;
 437:               dirLabel.setText(directoryLabel);
 438:               filechooser.setApproveButtonText(saveButtonText);
 439:               filechooser.setApproveButtonToolTipText(saveButtonToolTipText);
 440:             }
 441:           
 442:           rescanCurrentDirectory(filechooser);
 443:         }
 444:       
 445:       filechooser.revalidate();
 446:       filechooser.repaint();
 447:     }
 448:   };
 449:   
 450:   /** 
 451:    * A combo box model containing the selected directory and all its parent
 452:    * directories.
 453:    */
 454:   protected class DirectoryComboBoxModel
 455:     extends AbstractListModel
 456:     implements ComboBoxModel
 457:   {
 458:     /** Storage for the items in the model. */
 459:     private List items;
 460:     
 461:     /** The index of the selected item. */
 462:     private int selectedIndex;
 463:     
 464:     /**
 465:      * Creates a new model.
 466:      */
 467:     public DirectoryComboBoxModel() 
 468:     {
 469:       items = new java.util.ArrayList();
 470:       selectedIndex = -1;
 471:     }
 472:     
 473:     /**
 474:      * Returns the number of items in the model.
 475:      * 
 476:      * @return The number of items in the model.
 477:      */
 478:     public int getSize()
 479:     {
 480:       return items.size();
 481:     }
 482:     
 483:     /**
 484:      * Returns the item at the specified index.
 485:      * 
 486:      * @param index  the item index.
 487:      * 
 488:      * @return The item.
 489:      */
 490:     public Object getElementAt(int index)
 491:     {
 492:       return items.get(index);
 493:     }
 494:     
 495:     /**
 496:      * Returns the depth of the item at the given <code>index</code>.
 497:      * 
 498:      * @param index  the item index.
 499:      * 
 500:      * @return The depth.
 501:      */
 502:     public int getDepth(int index)
 503:     {
 504:       return Math.max(index, 0);
 505:     }
 506: 
 507:     /**
 508:      * Returns the selected item, or <code>null</code> if no item is selected.
 509:      * 
 510:      * @return The selected item, or <code>null</code>.
 511:      */
 512:     public Object getSelectedItem()
 513:     {
 514:       if (selectedIndex >= 0) 
 515:         return items.get(selectedIndex);
 516:       else
 517:         return null;
 518:     }
 519:     
 520:     /**
 521:      * Sets the selected item.  This clears all the directories from the
 522:      * existing list, and repopulates it with the new selected directory
 523:      * and all its parent directories.
 524:      * 
 525:      * @param selectedDirectory  the selected directory.
 526:      */
 527:     public void setSelectedItem(Object selectedDirectory)
 528:     {
 529:       items.clear();
 530:       FileSystemView fsv = getFileChooser().getFileSystemView();
 531:       File parent = (File) selectedDirectory;
 532:       while (parent != null)
 533:         {
 534:           items.add(0, parent);
 535:           parent = fsv.getParentDirectory(parent);
 536:         }
 537:       selectedIndex = items.indexOf(selectedDirectory);
 538:       fireContentsChanged(this, 0, items.size() - 1);
 539:     }
 540:     
 541:   }
 542: 
 543:   /**
 544:    * Handles changes to the selection in the directory combo box.
 545:    */
 546:   protected class DirectoryComboBoxAction
 547:     extends AbstractAction
 548:   {
 549:     /**
 550:      * Creates a new action.
 551:      */
 552:     protected DirectoryComboBoxAction()
 553:     {
 554:       // Nothing to do here.
 555:     }
 556:     
 557:     /**
 558:      * Handles the action event.
 559:      * 
 560:      * @param e  the event.
 561:      */
 562:     public void actionPerformed(ActionEvent e)
 563:     {
 564:       JFileChooser fc = getFileChooser();
 565:       fc.setCurrentDirectory((File) directoryModel.getSelectedItem());
 566:     }
 567:   }
 568: 
 569:   /**
 570:    * A renderer for the items in the directory combo box.
 571:    */
 572:   class DirectoryComboBoxRenderer
 573:     extends DefaultListCellRenderer
 574:   {
 575:     /**
 576:      * Creates a new renderer.
 577:      */
 578:     public DirectoryComboBoxRenderer(JFileChooser fc)
 579:     { 
 580:     }
 581:     
 582:     /**
 583:      * Returns a component that can be used to paint the given value within 
 584:      * the list.
 585:      * 
 586:      * @param list  the list.
 587:      * @param value  the value (a {@link File}).
 588:      * @param index  the item index.
 589:      * @param isSelected  is the item selected?
 590:      * @param cellHasFocus  does the list cell have focus?
 591:      * 
 592:      * @return The list cell renderer.
 593:      */
 594:     public Component getListCellRendererComponent(JList list, Object value,
 595:         int index, boolean isSelected, boolean cellHasFocus)
 596:     {
 597:       FileView fileView = getFileView(getFileChooser());
 598:       File file = (File) value;
 599:       setIcon(fileView.getIcon(file));
 600:       setText(fileView.getName(file));
 601:       
 602:       if (isSelected)
 603:         {
 604:           setBackground(list.getSelectionBackground());
 605:           setForeground(list.getSelectionForeground());
 606:         }
 607:       else
 608:         {
 609:           setBackground(list.getBackground());
 610:           setForeground(list.getForeground());
 611:         }
 612: 
 613:       setEnabled(list.isEnabled());
 614:       setFont(list.getFont());
 615:       return this;
 616:     }
 617:   }
 618: 
 619:   /**
 620:    * A renderer for the files and directories in the file chooser.
 621:    */
 622:   protected class FileRenderer
 623:     extends DefaultListCellRenderer
 624:   {
 625:     
 626:     /**
 627:      * Creates a new renderer.
 628:      */
 629:     protected FileRenderer()
 630:     {
 631:       // Nothing to do here.
 632:     }
 633:     
 634:     /**
 635:      * Returns a component that can render the specified value.
 636:      * 
 637:      * @param list  the list.
 638:      * @param value  the value (a {@link File}).
 639:      * @param index  the index.
 640:      * @param isSelected  is the item selected?
 641:      * @param cellHasFocus  does the item have the focus?
 642:      * 
 643:      * @return The renderer.
 644:      */
 645:     public Component getListCellRendererComponent(JList list, Object value,
 646:         int index, boolean isSelected, boolean cellHasFocus)
 647:     {
 648:       FileView v = getFileView(getFileChooser());
 649:       File f = (File) value;
 650:       if (f != null)
 651:     {
 652:       setText(v.getName(f));
 653:       setIcon(v.getIcon(f));
 654:     }
 655:       else
 656:     {
 657:       setText("");
 658:       setIcon(null);
 659:     }
 660:       setOpaque(true);
 661:       if (isSelected)
 662:         {
 663:           setBackground(list.getSelectionBackground());
 664:           setForeground(list.getSelectionForeground());
 665:         }
 666:       else
 667:         {
 668:           setBackground(list.getBackground());
 669:           setForeground(list.getForeground());
 670:         }
 671: 
 672:       setEnabled(list.isEnabled());
 673:       setFont(list.getFont());
 674: 
 675:       if (cellHasFocus)
 676:         setBorder(UIManager.getBorder("List.focusCellHighlightBorder"));
 677:       else
 678:         setBorder(noFocusBorder);
 679:       return this;
 680:     }
 681:   }
 682: 
 683:   /**
 684:    * A combo box model for the file selection filters.
 685:    */
 686:   protected class FilterComboBoxModel
 687:     extends AbstractListModel
 688:     implements ComboBoxModel, PropertyChangeListener
 689:   {
 690: 
 691:     /** Storage for the filters in the model. */
 692:     protected FileFilter[] filters;
 693: 
 694:     /** The index of the selected file filter. */
 695:     private Object selected;
 696:     
 697:     /**
 698:      * Creates a new model.
 699:      */
 700:     protected FilterComboBoxModel()
 701:     {
 702:       filters = new FileFilter[1];
 703:       filters[0] = getAcceptAllFileFilter(getFileChooser());
 704:       selected = filters[0];
 705:     }
 706:     
 707:     /**
 708:      * Handles property changes.
 709:      * 
 710:      * @param e  the property change event.
 711:      */
 712:     public void propertyChange(PropertyChangeEvent e)
 713:     {
 714:       if (e.getPropertyName().equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY))
 715:         {
 716:           JFileChooser fc = getFileChooser();
 717:           FileFilter[] choosableFilters = fc.getChoosableFileFilters();
 718:           filters = choosableFilters;
 719:           fireContentsChanged(this, 0, filters.length);
 720:           selected = e.getNewValue();
 721:           fireContentsChanged(this, -1, -1);
 722:         }
 723:       else if (e.getPropertyName().equals(
 724:               JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY))
 725:         {
 726:           // repopulate list
 727:           JFileChooser fc = getFileChooser();
 728:           FileFilter[] choosableFilters = fc.getChoosableFileFilters();
 729:           filters = choosableFilters;
 730:           fireContentsChanged(this, 0, filters.length);
 731:         }
 732:     }
 733:     
 734:     /**
 735:      * Sets the selected filter.
 736:      * 
 737:      * @param filter  the filter (<code>null</code> ignored).
 738:      */
 739:     public void setSelectedItem(Object filter)
 740:     {
 741:       if (filter != null)
 742:       {
 743:           selected = filter;
 744:           fireContentsChanged(this, -1, -1);
 745:       }
 746:     }
 747:     
 748:     /**
 749:      * Returns the selected file filter.
 750:      * 
 751:      * @return The selected file filter.
 752:      */
 753:     public Object getSelectedItem()
 754:     {
 755:       return selected;
 756:     }
 757:     
 758:     /**
 759:      * Returns the number of items in the model.
 760:      * 
 761:      * @return The number of items in the model.
 762:      */
 763:     public int getSize()
 764:     {
 765:       return filters.length;
 766:     }
 767:     
 768:     /**
 769:      * Returns the item at the specified index.
 770:      * 
 771:      * @param index  the item index.
 772:      * 
 773:      * @return The item at the specified index.
 774:      */
 775:     public Object getElementAt(int index)
 776:     {
 777:       return filters[index];
 778:     }
 779:     
 780:   }
 781: 
 782:   /**
 783:    * A renderer for the items in the file filter combo box.
 784:    */
 785:   public class FilterComboBoxRenderer
 786:     extends DefaultListCellRenderer
 787:   {
 788:     /**
 789:      * Creates a new renderer.
 790:      */
 791:     public FilterComboBoxRenderer()
 792:     {
 793:       // Nothing to do here.
 794:     }
 795:     
 796:     /**
 797:      * Returns a component that can be used to paint the given value within 
 798:      * the list.
 799:      * 
 800:      * @param list  the list.
 801:      * @param value  the value (a {@link FileFilter}).
 802:      * @param index  the item index.
 803:      * @param isSelected  is the item selected?
 804:      * @param cellHasFocus  does the list cell have focus?
 805:      * 
 806:      * @return This component as the renderer.
 807:      */
 808:     public Component getListCellRendererComponent(JList list, Object value,
 809:         int index, boolean isSelected, boolean cellHasFocus)
 810:     {
 811:       super.getListCellRendererComponent(list, value, index, isSelected, 
 812:                                          cellHasFocus);
 813:       FileFilter filter = (FileFilter) value;
 814:       setText(filter.getDescription());
 815:       return this;
 816:     }
 817:   }
 818: 
 819:   /**
 820:    * A listener for selection events in the file list.
 821:    * 
 822:    * @see #createListSelectionListener(JFileChooser)
 823:    */
 824:   class MetalFileChooserSelectionListener 
 825:     implements ListSelectionListener
 826:   {
 827:     /**
 828:      * Creates a new <code>SelectionListener</code> object.
 829:      */
 830:     protected MetalFileChooserSelectionListener()
 831:     {
 832:       // Do nothing here.
 833:     }
 834: 
 835:     /**
 836:      * Makes changes to different properties when
 837:      * a value has changed in the filechooser's selection.
 838:      *
 839:      * @param e - the list selection event that occured.
 840:      */
 841:     public void valueChanged(ListSelectionEvent e)
 842:     {
 843:       File f = (File) fileList.getSelectedValue();
 844:       if (f == null)
 845:         return;
 846:       JFileChooser filechooser = getFileChooser();
 847:       if (! filechooser.isTraversable(f))
 848:         filechooser.setSelectedFile(f);
 849:       else
 850:         filechooser.setSelectedFile(null);
 851:     }
 852:   }
 853: 
 854:   /**
 855:    * A mouse listener for the {@link JFileChooser}.
 856:    * This listener is used for editing filenames.
 857:    */
 858:   protected class SingleClickListener
 859:     extends MouseAdapter
 860:   {
 861:     
 862:     /** Stores instance of the list */
 863:     JList list;
 864:     
 865:     /** 
 866:      * Stores the current file that is being edited.
 867:      * It is null if nothing is currently being edited.
 868:      */
 869:     File editFile;
 870:     
 871:     /** The current file chooser. */
 872:     JFileChooser fc;
 873:     
 874:     /** The last file selected. */
 875:     Object lastSelected;
 876:     
 877:     /** The textfield used for editing. */
 878:     JTextField editField;
 879:     
 880:     /**
 881:      * Creates a new listener.
 882:      * 
 883:      * @param list  the directory/file list.
 884:      */
 885:     public SingleClickListener(JList list)
 886:     {
 887:       this.list = list;
 888:       editFile = null;
 889:       fc = getFileChooser();
 890:       lastSelected = null;
 891:       startEditing = false;
 892:     }
 893:     
 894:     /**
 895:      * Receives notification of a mouse click event.
 896:      * 
 897:      * @param e  the event.
 898:      */
 899:     public void mouseClicked(MouseEvent e)
 900:     {
 901:       if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1)
 902:         {
 903:           int index = list.locationToIndex(e.getPoint());
 904:           File[] sf = fc.getSelectedFiles();
 905:           if ((!fc.isMultiSelectionEnabled() || (sf != null && sf.length <= 1))
 906:               && index >= 0 && !startEditing && list.isSelectedIndex(index))
 907:             {
 908:               Object tmp = list.getModel().getElementAt(index);
 909:               if (lastSelected != null && lastSelected.equals(tmp))
 910:                 editFile(index);
 911:               lastSelected = tmp;
 912:             }
 913:           else
 914:               completeEditing();
 915:         }
 916:       else
 917:         completeEditing();
 918:     }
 919:     
 920:     /**
 921:      * Sets up the text editor for the current file.
 922:      * 
 923:      * @param index -
 924:      *          the current index of the item in the list to be edited.
 925:      */
 926:     void editFile(int index)
 927:     {
 928:       Rectangle bounds = list.getCellBounds(index, index);
 929:       list.scrollRectToVisible(bounds);
 930:       editFile = (File) list.getModel().getElementAt(index);
 931:       if (editFile.canWrite())
 932:         {
 933:           startEditing = true;
 934:           editField = new JTextField(editFile.getName());
 935:           editField.addActionListener(new EditingActionListener());
 936:           
 937:           Icon icon = getFileView(fc).getIcon(editFile);
 938:           if (icon != null)
 939:             {
 940:               int padding = icon.getIconWidth() + 4;
 941:               bounds.x += padding;
 942:               bounds.width -= padding;
 943:             }
 944:           editField.setBounds(bounds);
 945:           
 946:           list.add(editField);
 947:           
 948:           editField.requestFocus();
 949:           editField.selectAll();
 950:         }
 951:       else
 952:         completeEditing();
 953:       list.repaint();
 954:     }
 955:     
 956:     /** 
 957:      * Completes the editing.
 958:      */
 959:     void completeEditing()
 960:     {
 961:       if (editField != null && editFile != null)
 962:         {
 963:           String text = editField.getText();
 964:           if (text != null && text != "" && !text.equals(fc.getName(editFile)))
 965:               if (editFile.renameTo
 966:                   (fc.getFileSystemView().createFileObject
 967:                    (fc.getCurrentDirectory(), text)))
 968:                   rescanCurrentDirectory(fc);
 969:           list.remove(editField);
 970:         }
 971:       startEditing = false;
 972:       editFile = null;
 973:       lastSelected = null;
 974:       editField = null;
 975:       list.repaint();
 976:     }
 977:     
 978:     /**
 979:      * ActionListener for the editing text field.
 980:      */
 981:     class EditingActionListener implements ActionListener
 982:     {
 983:       
 984:       /**
 985:        * This method is invoked when an action occurs.
 986:        * 
 987:        * @param e -
 988:        *          the <code>ActionEvent</code> that occurred
 989:        */
 990:       public void actionPerformed(ActionEvent e)
 991:       {
 992:         if (e.getActionCommand().equals("notify-field-accept"))
 993:           completeEditing();
 994:         else if (editField != null)
 995:           {
 996:             list.remove(editField);
 997:             startEditing = false;
 998:             editFile = null;
 999:             lastSelected = null;
1000:             editField = null;
1001:             list.repaint();
1002:           }
1003:       }
1004:     }
1005:   }
1006: 
1007:   /**
1008:    * A mouse listener for the {@link JFileChooser}.
1009:    * This listener is used for the table
1010:    */
1011:   private class TableClickListener extends MouseAdapter
1012:   {
1013: 
1014:     /** Stores instance of the table */
1015:     JTable table;
1016: 
1017:     /** Stores instance of the file chooser */
1018:     JFileChooser fc;
1019: 
1020:     /** The last selected file. */
1021:     Object lastSelected = null;
1022:     
1023:     /** 
1024:      * Stores the current file that is being edited.
1025:      * It is null if nothing is currently being edited.
1026:      */
1027:     File editFile;
1028:     
1029:     /** The textfield used for editing. */
1030:     JTextField editField;
1031: 
1032:     /**
1033:      * Creates a new listener.
1034:      * 
1035:      * @param table
1036:      *          the directory/file table
1037:      * @param fc
1038:      *          the JFileChooser
1039:      */
1040:     public TableClickListener(JTable table, JFileChooser fc)
1041:     {
1042:       this.table = table;
1043:       this.fc = fc;
1044:       lastSelected = fileList.getSelectedValue();
1045:       setDirectorySelected(false);
1046:       startEditing = false;
1047:       editFile = null;
1048:       editField = null;
1049:     }
1050: 
1051:     /**
1052:      * Receives notification of a mouse click event.
1053:      * 
1054:      * @param e
1055:      *          the event.
1056:      */
1057:     public void mouseClicked(MouseEvent e)
1058:     {
1059:       int row = table.getSelectedRow();
1060:       Object selVal = fileList.getModel().getElementAt(row);
1061:       if (selVal == null)
1062:         return;
1063:       FileSystemView fsv = fc.getFileSystemView();
1064:       if (e.getClickCount() == 1 &&
1065:           selVal.equals(lastSelected) &&
1066:           e.getButton() == MouseEvent.BUTTON1)
1067:         {
1068:           File[] sf = fc.getSelectedFiles();
1069:           if ((!fc.isMultiSelectionEnabled() || (sf != null && sf.length <= 1))
1070:               && !startEditing)
1071:             {
1072:               editFile = (File) selVal;
1073:               editFile(row);
1074:             }
1075:         }
1076:       else if (e.getClickCount() >= 2 &&
1077:           selVal.equals(lastSelected))
1078:         {
1079:           if (startEditing)
1080:             completeEditing();
1081:           File f = fsv.createFileObject(lastSelected.toString());
1082:           if (fc.isTraversable(f))
1083:             {
1084:               fc.setCurrentDirectory(f);
1085:               fc.rescanCurrentDirectory();
1086:             }
1087:           else
1088:             {
1089:               fc.setSelectedFile(f);
1090:               fc.approveSelection();
1091:               closeDialog();
1092:             }
1093:         }
1094:       else
1095:         {
1096:           if (startEditing)
1097:             completeEditing();
1098:           String path = selVal.toString();
1099:           File f = fsv.createFileObject(path);
1100:           fc.setSelectedFile(f);
1101:           if (fc.isTraversable(f))
1102:             {
1103:               setDirectorySelected(true);
1104:               setDirectory(f);
1105:             }
1106:           else
1107:             {
1108:               setDirectorySelected(false);
1109:               setDirectory(null);
1110:             }
1111:           lastSelected = selVal;
1112:           if (f.isFile())
1113:             setFileName(path.substring(path.lastIndexOf("/") + 1));
1114:           else if (fc.getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY)
1115:             setFileName(path);
1116:         }
1117:       fileTable.repaint();
1118:     }
1119: 
1120:     /**
1121:      * Sets up the text editor for the current file.
1122:      * 
1123:      * @param row -
1124:      *          the current row of the item in the list to be edited.
1125:      */
1126:     void editFile(int row)
1127:     {
1128:       Rectangle bounds = table.getCellRect(row, 0, true);
1129:       table.scrollRectToVisible(bounds);
1130:       if (editFile.canWrite())
1131:         {
1132:           startEditing = true;
1133:           editField = new JTextField(editFile.getName());
1134:           editField.addActionListener(new EditingActionListener());
1135: 
1136:           // Need to adjust y pos
1137:           bounds.y = row * table.getRowHeight();
1138:           editField.setBounds(bounds);
1139:           
1140:           table.add(editField);
1141:           
1142:           editField.requestFocus();
1143:           editField.selectAll();
1144:         }
1145:       else
1146:         completeEditing();
1147:       table.repaint();
1148:     }
1149:     
1150:     /** 
1151:      * Completes the editing.
1152:      */
1153:     void completeEditing()
1154:     {
1155:       if (editField != null && editFile != null)
1156:         {
1157:           String text = editField.getText();
1158:           if (text != null && text != "" && !text.equals(fc.getName(editFile)))
1159:               if (editFile.renameTo
1160:                   (fc.getFileSystemView().createFileObject
1161:                    (fc.getCurrentDirectory(), text)))
1162:                   rescanCurrentDirectory(fc);
1163:           table.remove(editField);
1164:         }
1165:       startEditing = false;
1166:       editFile = null;
1167:       editField = null;
1168:       table.repaint();
1169:     }
1170:     
1171:     /**
1172:      * ActionListener for the editing text field.
1173:      */
1174:     class EditingActionListener implements ActionListener
1175:     {
1176:       
1177:       /**
1178:        * This method is invoked when an action occurs.
1179:        * 
1180:        * @param e -
1181:        *          the <code>ActionEvent</code> that occurred
1182:        */
1183:       public void actionPerformed(ActionEvent e)
1184:       {
1185:         if (e.getActionCommand().equals("notify-field-accept"))
1186:           completeEditing();
1187:         else if (editField != null)
1188:           {
1189:             table.remove(editField);
1190:             startEditing = false;
1191:             editFile = null;
1192:             editField = null;
1193:             table.repaint();
1194:           }
1195:       }
1196:     }
1197:     
1198:     /**
1199:      * Closes the dialog.
1200:      */
1201:     public void closeDialog()
1202:     {
1203:       Window owner = SwingUtilities.windowForComponent(fc);
1204:       if (owner instanceof JDialog)
1205:         ((JDialog) owner).dispose();
1206:     }
1207:   } 
1208:   
1209:   /** The text for a label describing the directory combo box. */
1210:   private String directoryLabel;
1211:   
1212:   private JComboBox directoryComboBox;
1213:   
1214:   /** The model for the directory combo box. */
1215:   DirectoryComboBoxModel directoryModel;
1216:   
1217:   /** The text for a label describing the file text field. */
1218:   private String fileLabel;
1219:   
1220:   /** The file name text field. */
1221:   private JTextField fileTextField;
1222:   
1223:   /** The text for a label describing the filter combo box. */
1224:   private String filterLabel;
1225: 
1226:   /** 
1227:    * The top panel (contains the directory combo box and the control buttons). 
1228:    */
1229:   private JPanel topPanel;
1230:   
1231:   /** A panel containing the control buttons ('up', 'home' etc.). */
1232:   private JPanel controls;
1233: 
1234:   /** 
1235:    * The panel that contains the filename field and the filter combobox. 
1236:    */
1237:   private JPanel bottomPanel;
1238: 
1239:   /** 
1240:    * The panel that contains the 'Open' (or 'Save') and 'Cancel' buttons. 
1241:    */
1242:   private JPanel buttonPanel;
1243:   
1244:   private JButton approveButton;
1245:   
1246:   /** The file list. */
1247:   JList fileList;
1248:   
1249:   /** The file table. */
1250:   JTable fileTable;
1251:   
1252:   /** The panel containing the file list. */
1253:   JPanel fileListPanel;
1254:   
1255:   /** The panel containing the file table. */
1256:   JPanel fileTablePanel;
1257:   
1258:   /** The filter combo box model. */
1259:   private FilterComboBoxModel filterModel;
1260: 
1261:   /** The action map. */
1262:   private ActionMap actionMap;
1263:   
1264:   /** True if currently in list view. */
1265:   boolean listView;
1266:   
1267:   /** True if we can or have started editing a cell. */
1268:   boolean startEditing;
1269:   
1270:   /** The scrollpane used for the table and list. */
1271:   JScrollPane scrollPane;
1272:   
1273:   /** The text for the label when saving. */
1274:   String save;
1275:   
1276:   /** The text for the label when opening a directory. */
1277:   String look;
1278:   
1279:   /** The label for the file combo box. */
1280:   JLabel dirLabel;
1281:   
1282:   /** Listeners. */
1283:   ListSelectionListener listSelList;
1284:   MouseListener doubleClickList;
1285:   SingleClickListener singleClickList;
1286:   TableClickListener tableClickList;
1287:   
1288:   /**
1289:    * A factory method that returns a UI delegate for the specified
1290:    * component.
1291:    * 
1292:    * @param c  the component (which should be a {@link JFileChooser}).
1293:    */
1294:   public static ComponentUI createUI(JComponent c)
1295:   {
1296:     JFileChooser chooser = (JFileChooser) c;
1297:     return new MetalFileChooserUI(chooser);
1298:   }
1299: 
1300:   /**
1301:    * Creates a new instance of this UI delegate.
1302:    * 
1303:    * @param filechooser  the file chooser component.
1304:    */
1305:   public MetalFileChooserUI(JFileChooser filechooser)
1306:   {
1307:     super(filechooser);
1308:     bottomPanel = new JPanel(new GridLayout(3, 2));
1309:     buttonPanel = new JPanel();
1310:   }
1311: 
1312:   public void installUI(JComponent c)
1313:   {
1314:     super.installUI(c);
1315:     actionMap = createActionMap();
1316:   }
1317:   
1318:   public void uninstallUI(JComponent c)
1319:   {
1320:     super.uninstallUI(c);
1321:     actionMap = null;
1322:   }
1323:   
1324:   /**
1325:    * Installs the sub-components of the file chooser.
1326:    * 
1327:    * @param fc  the file chooser component.
1328:    */
1329:   public void installComponents(JFileChooser fc)
1330:   {
1331:     fc.setLayout(new BorderLayout());
1332:     topPanel = new JPanel(new BorderLayout());
1333:     dirLabel = new JLabel(directoryLabel);
1334:     topPanel.add(dirLabel, BorderLayout.WEST);
1335:     this.controls = new JPanel();
1336:     addControlButtons();
1337:     
1338:     JPanel dirPanel = new JPanel(new VerticalMidLayout());
1339:     directoryModel = createDirectoryComboBoxModel(fc);
1340:     directoryComboBox = new JComboBox(directoryModel);
1341:     directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
1342:     dirPanel.add(directoryComboBox);
1343:     topPanel.add(dirPanel);
1344:     topPanel.add(controls, BorderLayout.EAST);
1345:     topPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 0, 8));
1346:     fc.add(topPanel, BorderLayout.NORTH);
1347:     
1348:     JPanel list = createList(fc);
1349:     list.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
1350:     fc.add(list, BorderLayout.CENTER);
1351:     
1352:     JPanel bottomPanel = getBottomPanel();
1353:     filterModel = createFilterComboBoxModel();
1354:     JComboBox fileFilterCombo = new JComboBox(filterModel);
1355:     fileFilterCombo.setRenderer(createFilterComboBoxRenderer());
1356:     
1357:     fileTextField = new JTextField();
1358:     JPanel fileNamePanel = new JPanel(new VerticalMidLayout());
1359:     fileNamePanel.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 5));
1360:     fileNamePanel.add(fileTextField);
1361:     JPanel row1 = new JPanel(new BorderLayout());
1362:     row1.add(new JLabel(this.fileLabel), BorderLayout.WEST);
1363:     row1.add(fileNamePanel);
1364:     bottomPanel.add(row1);
1365:     
1366:     JPanel row2 = new JPanel(new BorderLayout());
1367:     row2.add(new JLabel(this.filterLabel), BorderLayout.WEST);
1368:     row2.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
1369:     row2.add(fileFilterCombo);
1370:     bottomPanel.add(row2);
1371:     JPanel buttonPanel = new JPanel(new ButtonLayout());
1372:     
1373:     approveButton = new JButton(getApproveSelectionAction());
1374:     approveButton.setText(getApproveButtonText(fc));
1375:     approveButton.setToolTipText(getApproveButtonToolTipText(fc));
1376:     approveButton.setMnemonic(getApproveButtonMnemonic(fc));
1377:     buttonPanel.add(approveButton);
1378:     buttonPanel.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0));
1379:     
1380:     JButton cancelButton = new JButton(getCancelSelectionAction());
1381:     cancelButton.setText(cancelButtonText);
1382:     cancelButton.setToolTipText(cancelButtonToolTipText);
1383:     cancelButton.setMnemonic(cancelButtonMnemonic);
1384:     buttonPanel.add(cancelButton);
1385:     bottomPanel.add(buttonPanel, BorderLayout.SOUTH);
1386:     bottomPanel.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
1387:     fc.add(bottomPanel, BorderLayout.SOUTH);
1388:     
1389:     fc.add(getAccessoryPanel(), BorderLayout.EAST);
1390:   }
1391:   
1392:   /**
1393:    * Uninstalls the components added by 
1394:    * {@link #installComponents(JFileChooser)}.
1395:    * 
1396:    * @param fc  the file chooser.
1397:    */
1398:   public void uninstallComponents(JFileChooser fc)
1399:   {
1400:     fc.remove(bottomPanel);
1401:     bottomPanel = null;
1402:     fc.remove(fileListPanel);
1403:     fc.remove(fileTablePanel);
1404:     fileTablePanel = null;
1405:     fileListPanel = null;
1406:     fc.remove(topPanel);
1407:     topPanel = null;
1408:     
1409:     directoryModel = null;
1410:     fileTextField = null;
1411:     directoryComboBox = null;
1412:   }
1413:   
1414:   /**
1415:    * Returns the panel that contains the 'Open' (or 'Save') and 'Cancel' 
1416:    * buttons.
1417:    * 
1418:    * @return The panel.
1419:    */
1420:   protected JPanel getButtonPanel()
1421:   {
1422:     return buttonPanel;    
1423:   }
1424:   
1425:   /**
1426:    * Creates and returns a new panel that will be used for the controls at
1427:    * the bottom of the file chooser.
1428:    * 
1429:    * @return A new panel.
1430:    */
1431:   protected JPanel getBottomPanel()
1432:   {
1433:     if (bottomPanel == null)
1434:       bottomPanel = new JPanel(new GridLayout(3, 2));
1435:     return bottomPanel;
1436:   }
1437:   
1438:   /**
1439:    * Fetches localised strings for use by the labels and buttons on the
1440:    * file chooser.
1441:    * 
1442:    * @param fc  the file chooser.
1443:    */
1444:   protected void installStrings(JFileChooser fc)
1445:   { 
1446:      super.installStrings(fc);
1447:      look = "Look In: ";
1448:      save = "Save In: ";
1449:      if (fc.getDialogType() == JFileChooser.SAVE_DIALOG)
1450:        directoryLabel = save;
1451:      else
1452:        directoryLabel = look;
1453:      
1454:      fileLabel = "File Name: ";
1455:      filterLabel = "Files of Type: ";
1456:      
1457:      this.cancelButtonMnemonic = 0;
1458:      this.cancelButtonText = "Cancel";
1459:      this.cancelButtonToolTipText = "Abort file chooser dialog";
1460:      
1461:      this.directoryOpenButtonMnemonic = 0;
1462:      this.directoryOpenButtonText = "Open";
1463:      this.directoryOpenButtonToolTipText = "Open selected directory";
1464:      
1465:      this.helpButtonMnemonic = 0;
1466:      this.helpButtonText = "Help";
1467:      this.helpButtonToolTipText = "Filechooser help";
1468:      
1469:      this.openButtonMnemonic = 0;
1470:      this.openButtonText = "Open";
1471:      this.openButtonToolTipText = "Open selected file";
1472:      
1473:      this.saveButtonMnemonic = 0;
1474:      this.saveButtonText = "Save";
1475:      this.saveButtonToolTipText = "Save selected file";
1476:      
1477:      this.updateButtonMnemonic = 0;
1478:      this.updateButtonText = "Update";
1479:      this.updateButtonToolTipText = "Update directory listing";   
1480:   }
1481:   
1482:   /**
1483:    * Installs the listeners required.
1484:    * 
1485:    * @param fc  the file chooser.
1486:    */
1487:   protected void installListeners(JFileChooser fc)
1488:   {
1489:     directoryComboBox.setAction(new DirectoryComboBoxAction());
1490:     fc.addPropertyChangeListener(filterModel);
1491:     listSelList = createListSelectionListener(fc);
1492:     doubleClickList = this.createDoubleClickListener(fc, fileList);
1493:     singleClickList = new SingleClickListener(fileList);
1494:     fileList.addListSelectionListener(listSelList);
1495:     fileList.addMouseListener(doubleClickList);
1496:     fileList.addMouseListener(singleClickList);
1497:     super.installListeners(fc);
1498:   }
1499:   
1500:   protected void uninstallListeners(JFileChooser fc) 
1501:   {
1502:     super.uninstallListeners(fc);
1503:     fc.removePropertyChangeListener(filterModel);
1504:     directoryComboBox.setAction(null);
1505:     fileList.removeListSelectionListener(listSelList);
1506:     fileList.removeMouseListener(doubleClickList);
1507:     fileList.removeMouseListener(singleClickList);
1508:     
1509:     if (fileTable != null)
1510:       fileTable.removeMouseListener(tableClickList);
1511:   }
1512:   
1513:   protected ActionMap getActionMap()
1514:   {
1515:     if (actionMap == null)
1516:       actionMap = createActionMap();
1517:     return actionMap;
1518:   }
1519:   
1520:   /**
1521:    * Creates and returns an action map.
1522:    * 
1523:    * @return The action map.
1524:    */
1525:   protected ActionMap createActionMap()
1526:   {
1527:     ActionMap map = new ActionMap();
1528:     map.put("approveSelection", getApproveSelectionAction());
1529:     map.put("cancelSelection", getCancelSelectionAction());
1530:     map.put("Go Up", getChangeToParentDirectoryAction());
1531:     return map;
1532:   }
1533: 
1534:   /**
1535:    * Creates a panel containing a list of files.
1536:    * 
1537:    * @param fc  the file chooser.
1538:    * 
1539:    * @return A panel.
1540:    */
1541:   protected JPanel createList(JFileChooser fc)
1542:   {
1543:     if (fileList == null)
1544:       {
1545:         fileListPanel = new JPanel(new BorderLayout());
1546:         fileList = new JList(getModel());
1547:         scrollPane = new JScrollPane(fileList);
1548:         scrollPane.setVerticalScrollBarPolicy
1549:                                         (JScrollPane.VERTICAL_SCROLLBAR_NEVER);
1550:         fileList.setLayoutOrientation(JList.VERTICAL_WRAP);
1551:         fileList.setCellRenderer(new FileRenderer());
1552:       }
1553:     else
1554:       {
1555:         fileList.setModel(getModel());
1556:         fileListPanel.removeAll();
1557:         scrollPane.getViewport().setView(fileList);
1558:       }
1559:     fileListPanel.add(scrollPane);
1560: 
1561:     return fileListPanel;
1562:   }
1563:   
1564:   /**
1565:    * Creates a panel containing a table within a scroll pane.
1566:    * 
1567:    * @param fc  the file chooser.
1568:    * 
1569:    * @return The details view.
1570:    */
1571:   protected JPanel createDetailsView(JFileChooser fc)
1572:   {
1573:     fileTablePanel = new JPanel(new BorderLayout());
1574:     
1575:     Object[] cols = new Object[] {"Name", "Size", "Modified"};
1576:     Object[][] rows = new Object[fileList.getModel().getSize()][3];
1577:     
1578:     fileTable = new JTable(new DefaultTableModel(rows, cols));
1579:     
1580:     if (fc.isMultiSelectionEnabled())
1581:       fileTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
1582:     else
1583:       fileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1584:     
1585:     fileTable.setShowGrid(false);
1586:     fileTable.setColumnSelectionAllowed(false);
1587:     fileTable.setDefaultRenderer(Object.class, new TableFileRenderer());
1588: 
1589:     tableClickList = new TableClickListener(fileTable, fc);
1590:     fileTable.addMouseListener(tableClickList);
1591:     
1592:     return updateTable();  
1593:   }
1594:   
1595:   /**
1596:    * Sets the values in the table, and puts it in the panel.
1597:    * 
1598:    * @return the panel containing the table.
1599:    */
1600:   JPanel updateTable()
1601:   {
1602:     DefaultTableModel mod = (DefaultTableModel) fileTable.getModel();
1603:     ListModel lm = fileList.getModel();
1604:     DateFormat dt = DateFormat.getDateTimeInstance(DateFormat.SHORT,
1605:                                                    DateFormat.SHORT);
1606:     File curr = null;
1607:     int size = lm.getSize();
1608:     int rc = mod.getRowCount();
1609: 
1610:     // If there are not enough rows
1611:     for (int x = rc; x < size; x++)
1612:       mod.addRow(new Object[3]);
1613: 
1614:     for (int i = 0; i < size; i++)
1615:       {
1616:         curr = (File) lm.getElementAt(i);
1617:         fileTable.setValueAt(curr.getName(), i, 0);
1618:         fileTable.setValueAt(formatSize(curr.length()), i, 1);
1619:         fileTable.setValueAt(dt.format(new Date(curr.lastModified())), i, 2);
1620:       }
1621: 
1622:     // If there are too many rows
1623:     while (rc > size)
1624:       mod.removeRow(--rc);
1625: 
1626:     scrollPane.getViewport().setView(fileTable);
1627:     scrollPane.setColumnHeaderView(fileTable.getTableHeader());
1628: 
1629:     fileTablePanel.removeAll();
1630:     fileTablePanel.add(scrollPane);
1631: 
1632:     return fileTablePanel;
1633:   }
1634:   
1635:   /**
1636:    * Formats bytes into the appropriate size.
1637:    * 
1638:    * @param bytes -
1639:    *          the number of bytes to convert
1640:    * @return a string representation of the size
1641:    */
1642:   private String formatSize(long bytes)
1643:   {
1644:     NumberFormat nf = NumberFormat.getNumberInstance();
1645:     long mb = (long) Math.pow(2, 20);
1646:     long kb = (long) Math.pow(2, 10);
1647:     long gb = (long) Math.pow(2, 30);
1648:     double size = 0;
1649:     String id = "";
1650:     
1651:     if ((bytes / gb) >= 1)
1652:       {
1653:         size = (double) bytes / (double) gb;
1654:         id = "GB";
1655:       }
1656:     else if ((bytes / mb) >= 1)
1657:       {
1658:         size = (double) bytes / (double) mb;
1659:         id = "MB";
1660:       }
1661:     else if ((bytes / kb) >= 1)
1662:       {
1663:         size = (double) bytes / (double) kb;
1664:         id = "KB";
1665:       }
1666:     else
1667:       {
1668:         size = bytes;
1669:         id = "Bytes";
1670:       }
1671:     
1672:     return nf.format(size) + " " + id;
1673:   }
1674:   /**
1675:    * Creates a listener that monitors selections in the directory/file list
1676:    * and keeps the {@link JFileChooser} component up to date.
1677:    * 
1678:    * @param fc  the file chooser.
1679:    * 
1680:    * @return The listener.
1681:    * 
1682:    * @see #installListeners(JFileChooser)
1683:    */
1684:   public ListSelectionListener createListSelectionListener(JFileChooser fc)
1685:   {
1686:     return new MetalFileChooserSelectionListener();
1687:   }
1688:   
1689:   /**
1690:    * Returns the preferred size for the file chooser component.
1691:    * 
1692:    * @return The preferred size.
1693:    */
1694:   public Dimension getPreferredSize(JComponent c)
1695:   {
1696:     Dimension tp = topPanel.getPreferredSize();
1697:     Dimension bp = bottomPanel.getPreferredSize();
1698:     Dimension fl = fileListPanel.getPreferredSize();
1699:     return new Dimension(fl.width, tp.height + bp.height + fl.height);
1700:   }
1701: 
1702:   /**
1703:    * Returns the minimum size for the file chooser component.
1704:    * 
1705:    * @return The minimum size.
1706:    */
1707:   public Dimension getMinimumSize(JComponent c)
1708:   {
1709:     Dimension tp = topPanel.getMinimumSize();
1710:     Dimension bp = bottomPanel.getMinimumSize();
1711:     Dimension fl = fileListPanel.getMinimumSize();
1712:     return new Dimension(fl.width, tp.height + bp.height + fl.height);
1713:   }
1714:   
1715:   /**
1716:    * Returns the maximum size for the file chooser component.
1717:    * 
1718:    * @return The maximum size.
1719:    */
1720:   public Dimension getMaximumSize(JComponent c)
1721:   {
1722:     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1723:   }
1724:   
1725:   /**
1726:    * Creates a property change listener that monitors the {@link JFileChooser}
1727:    * for property change events and updates the component display accordingly.
1728:    * 
1729:    * @param fc  the file chooser.
1730:    * 
1731:    * @return The property change listener.
1732:    * 
1733:    * @see #installListeners(JFileChooser)
1734:    */
1735:   public PropertyChangeListener createPropertyChangeListener(JFileChooser fc)
1736:   {
1737:     return new MetalFileChooserPropertyChangeListener();
1738:   }
1739: 
1740:   /**
1741:    * Creates and returns a new instance of {@link DirectoryComboBoxModel}.
1742:    * 
1743:    * @return A new instance of {@link DirectoryComboBoxModel}.
1744:    */
1745:   protected MetalFileChooserUI.DirectoryComboBoxModel 
1746:       createDirectoryComboBoxModel(JFileChooser fc)
1747:   {
1748:     return new DirectoryComboBoxModel();
1749:   }
1750: 
1751:   /**
1752:    * Creates a new instance of the renderer used in the directory
1753:    * combo box.
1754:    * 
1755:    * @param fc  the file chooser.
1756:    * 
1757:    * @return The renderer.
1758:    */
1759:   protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(
1760:           JFileChooser fc)
1761:   {
1762:     return new DirectoryComboBoxRenderer(fc);
1763:   }
1764: 
1765:   /**
1766:    * Creates and returns a new instance of {@link FilterComboBoxModel}.
1767:    * 
1768:    * @return A new instance of {@link FilterComboBoxModel}.
1769:    */
1770:   protected FilterComboBoxModel createFilterComboBoxModel()
1771:   {
1772:     return new FilterComboBoxModel();  
1773:   }
1774: 
1775:   /**
1776:    * Creates and returns a new instance of {@link FilterComboBoxRenderer}.
1777:    * 
1778:    * @return A new instance of {@link FilterComboBoxRenderer}.
1779:    */
1780:   protected MetalFileChooserUI.FilterComboBoxRenderer 
1781:       createFilterComboBoxRenderer()
1782:   {
1783:     return new FilterComboBoxRenderer(); 
1784:   }
1785: 
1786:   /**
1787:    * Adds the control buttons ('up', 'home' etc.) to the panel.
1788:    */
1789:   protected void addControlButtons()
1790:   {
1791:     JButton upButton = new JButton(getChangeToParentDirectoryAction());
1792:     upButton.setText(null);
1793:     upButton.setIcon(this.upFolderIcon);
1794:     upButton.setMargin(new Insets(0, 0, 0, 0));
1795:     controls.add(upButton);
1796:     
1797:     JButton homeButton = new JButton(getGoHomeAction());
1798:     homeButton.setText(null);
1799:     homeButton.setIcon(this.homeFolderIcon);
1800:     homeButton.setMargin(new Insets(0, 0, 0, 0));
1801:     controls.add(homeButton);
1802:     
1803:     JButton newFolderButton = new JButton(getNewFolderAction());
1804:     newFolderButton.setText(null);
1805:     newFolderButton.setIcon(this.newFolderIcon);
1806:     newFolderButton.setMargin(new Insets(0, 0, 0, 0));
1807:     controls.add(newFolderButton);
1808:     
1809:     JToggleButton listButton = new JToggleButton(this.listViewIcon);
1810:     listButton.setMargin(new Insets(0, 0, 0, 0));
1811:     listButton.addActionListener(new ListViewActionListener());
1812:     listButton.setSelected(true);
1813:     listView = true; 
1814:     controls.add(listButton);
1815:     
1816:     JToggleButton detailButton = new JToggleButton(this.detailsViewIcon);
1817:     detailButton.setMargin(new Insets(0, 0, 0, 0));
1818:     detailButton.addActionListener(new DetailViewActionListener());
1819:     detailButton.setSelected(false);
1820:     controls.add(detailButton);
1821: 
1822:     ButtonGroup buttonGroup = new ButtonGroup();
1823:     buttonGroup.add(listButton);
1824:     buttonGroup.add(detailButton);
1825:   }
1826:   
1827:   /**
1828:    * Removes all the buttons from the control panel.
1829:    */
1830:   protected void removeControlButtons()
1831:   {
1832:     controls.removeAll();
1833:     controls.revalidate();
1834:     controls.repaint();
1835:   }
1836:   
1837:   /**
1838:    * Updates the current directory.
1839:    * 
1840:    * @param the file chooser to update.
1841:    */
1842:   public void rescanCurrentDirectory(JFileChooser fc)
1843:   {
1844:     directoryModel.setSelectedItem(fc.getCurrentDirectory());
1845:     getModel().validateFileCache();
1846:     if (!listView)
1847:         updateTable();
1848:     else
1849:       createList(fc);
1850:   }
1851:   
1852:   /**
1853:    * Returns the file name in the text field.
1854:    * 
1855:    * @return The file name.
1856:    */
1857:   public String getFileName()
1858:   {
1859:     String result = null;
1860:     if (fileTextField != null) 
1861:       result = fileTextField.getText();
1862:     return result;
1863:   }
1864:   
1865:   /**
1866:    * Sets the file name in the text field.
1867:    * 
1868:    * @param filename  the file name.
1869:    */
1870:   public void setFileName(String filename)
1871:   {
1872:     fileTextField.setText(filename);
1873:   }
1874:   
1875:   /**
1876:    * DOCUMENT ME!!
1877:    * 
1878:    * @param e - DOCUMENT ME!
1879:    */
1880:   public void valueChanged(ListSelectionEvent e)
1881:   {
1882:     // FIXME: Not sure what we should be doing here, if anything.
1883:   }
1884:   
1885:   /**
1886:    * Returns the approve button.
1887:    * 
1888:    * @return The approve button.
1889:    */
1890:   protected JButton getApproveButton(JFileChooser fc)
1891:   {
1892:     return approveButton;
1893:   }
1894: 
1895:   /**
1896:    * A layout manager that is used to arrange the subcomponents of the
1897:    * {@link JFileChooser}.
1898:    */
1899:   class VerticalMidLayout implements LayoutManager
1900:   {
1901:     /**
1902:      * Performs the layout.
1903:      * 
1904:      * @param parent  the container.
1905:      */
1906:     public void layoutContainer(Container parent) 
1907:     {
1908:       int count = parent.getComponentCount();
1909:       if (count > 0)
1910:         {
1911:           Insets insets = parent.getInsets();
1912:           Component c = parent.getComponent(0);
1913:           Dimension prefSize = c.getPreferredSize();
1914:           int h = parent.getHeight() - insets.top - insets.bottom;
1915:           int adj = Math.max(0, (h - prefSize.height) / 2);
1916:           c.setBounds(insets.left, insets.top + adj, parent.getWidth() 
1917:               - insets.left - insets.right, 
1918:               (int) Math.min(prefSize.getHeight(), h));
1919:         }
1920:     }
1921:     
1922:     /**
1923:      * Returns the minimum layout size.
1924:      * 
1925:      * @param parent  the container.
1926:      * 
1927:      * @return The minimum layout size.
1928:      */
1929:     public Dimension minimumLayoutSize(Container parent) 
1930:     {
1931:       return preferredLayoutSize(parent);
1932:     }
1933:     
1934:     /**
1935:      * Returns the preferred layout size.
1936:      * 
1937:      * @param parent  the container.
1938:      * 
1939:      * @return The preferred layout size.
1940:      */
1941:     public Dimension preferredLayoutSize(Container parent) 
1942:     {
1943:       if (parent.getComponentCount() > 0)
1944:         {
1945:           return parent.getComponent(0).getPreferredSize();
1946:         }
1947:       else return null;
1948:     }
1949:     
1950:     /**
1951:      * This layout manager does not need to track components, so this 
1952:      * method does nothing.
1953:      * 
1954:      * @param name  the name the component is associated with.
1955:      * @param component  the component.
1956:      */
1957:     public void addLayoutComponent(String name, Component component) 
1958:     {
1959:       // do nothing
1960:     }
1961:     
1962:     /**
1963:      * This layout manager does not need to track components, so this 
1964:      * method does nothing.
1965:      * 
1966:      * @param component  the component.
1967:      */
1968:     public void removeLayoutComponent(Component component) {
1969:       // do nothing
1970:     }
1971:   }
1972: 
1973:   /**
1974:    * A layout manager that is used to arrange buttons for the
1975:    * {@link JFileChooser}.
1976:    */
1977:   class ButtonLayout implements LayoutManager
1978:   {
1979:     static final int GAP = 4;
1980:       
1981:     /**
1982:      * Performs the layout.
1983:      * 
1984:      * @param parent  the container.
1985:      */
1986:     public void layoutContainer(Container parent) 
1987:     {
1988:       int count = parent.getComponentCount();
1989:       if (count > 0)
1990:         {
1991:           // first find the widest button
1992:           int maxW = 0;
1993:           for (int i = 0; i < count; i++)
1994:             {
1995:               Component c = parent.getComponent(i);
1996:               Dimension prefSize = c.getPreferredSize();
1997:               maxW = Math.max(prefSize.width, maxW);
1998:             }
1999:   
2000:           // then position the buttons
2001:           Insets insets = parent.getInsets();
2002:           int availableH = parent.getHeight() - insets.top - insets.bottom;
2003:           int currentX = parent.getWidth() - insets.right;
2004:           for (int i = count - 1; i >= 0; i--)
2005:             {
2006:               Component c = parent.getComponent(i);
2007:               Dimension prefSize = c.getPreferredSize();      
2008:               int adj = Math.max(0, (availableH - prefSize.height) / 2);
2009:               currentX = currentX - prefSize.width;
2010:               c.setBounds(currentX, insets.top + adj, prefSize.width, 
2011:                   (int) Math.min(prefSize.getHeight(), availableH));
2012:               currentX = currentX - GAP;
2013:             }
2014:         }
2015:     }
2016:     
2017:     /**
2018:      * Returns the minimum layout size.
2019:      * 
2020:      * @param parent  the container.
2021:      * 
2022:      * @return The minimum layout size.
2023:      */
2024:     public Dimension minimumLayoutSize(Container parent) 
2025:     {
2026:       return preferredLayoutSize(parent);
2027:     }
2028:     
2029:     /**
2030:      * Returns the preferred layout size.
2031:      * 
2032:      * @param parent  the container.
2033:      * 
2034:      * @return The preferred layout size.
2035:      */
2036:     public Dimension preferredLayoutSize(Container parent) 
2037:     {
2038:       Insets insets = parent.getInsets();
2039:       int maxW = 0;
2040:       int maxH = 0;
2041:       int count = parent.getComponentCount();
2042:       if (count > 0) 
2043:         {
2044:           for (int i = 0; i < count; i++)
2045:             {
2046:               Component c = parent.getComponent(i);
2047:               Dimension d = c.getPreferredSize();
2048:               maxW = Math.max(d.width, maxW);
2049:               maxH = Math.max(d.height, maxH);
2050:             }
2051:         }
2052:       return new Dimension(maxW * count + GAP * (count - 1) + insets.left 
2053:               + insets.right, maxH + insets.top + insets.bottom);
2054:     }
2055:     
2056:     /**
2057:      * This layout manager does not need to track components, so this 
2058:      * method does nothing.
2059:      * 
2060:      * @param name  the name the component is associated with.
2061:      * @param component  the component.
2062:      */
2063:     public void addLayoutComponent(String name, Component component) 
2064:     {
2065:       // do nothing
2066:     }
2067:     
2068:     /**
2069:      * This layout manager does not need to track components, so this 
2070:      * method does nothing.
2071:      * 
2072:      * @param component  the component.
2073:      */
2074:     public void removeLayoutComponent(Component component) {
2075:       // do nothing
2076:     }
2077:   }
2078: 
2079: }