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