/* MetalFileChooserUI.java --
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing.plaf.metal;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicFileChooserUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;


/**
 * A UI delegate for the {@link JFileChooser} component.  This class is only
 * partially implemented and is not usable yet.
 */
public class MetalFileChooserUI 
  extends BasicFileChooserUI
{
  
  /**
   * A renderer for the files and directories in the file chooser table.
   */
  class TableFileRenderer
    extends DefaultTableCellRenderer
  {
    
    /**
     * Creates a new renderer.
     */
    public TableFileRenderer()
    {
      super();
    }
    
    /**
     * Returns a component that can render the specified value.
     * 
     * @param table  the table
     * @param value  the string value of the cell
     * @param isSelected  is the item selected?
     * @param hasFocus  does the item have the focus?
     * @param row  the row
     * @param column  the column
     * 
     * @return The renderer.
     */
    public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column)
    {
      if (column == 0)
        {
          FileView v = getFileView(getFileChooser());
          ListModel lm = fileList.getModel();
          if (row < lm.getSize())
            setIcon(v.getIcon((File) lm.getElementAt(row)));
        }
      else
        setIcon(null);
      
      setText(value.toString());
      setOpaque(true);
      setEnabled(table.isEnabled());
      setFont(fileList.getFont());
      
      if (startEditing && column == 0 || !isSelected)
        {
          setBackground(table.getBackground());
          setForeground(table.getForeground());
        }
      else
        {
          setBackground(table.getSelectionBackground());
          setForeground(table.getSelectionForeground());
        }

      if (hasFocus)
        setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
      else
        setBorder(noFocusBorder);
      
      return this;
    }
  }
  
  /**
   * ActionListener for the list view.
   */
  class ListViewActionListener implements ActionListener
  {
    
    /**
     * This method is invoked when an action occurs.
     * 
     * @param e -
     *          the <code>ActionEvent</code> that occurred
     */
    public void actionPerformed(ActionEvent e)
    {
      if (!listView)
        {
          int[] index = fileTable.getSelectedRows();
          listView = true;
          JFileChooser fc = getFileChooser();
          fc.remove(fileTablePanel);
          createList(fc);

          fileList.getSelectionModel().clearSelection();
          if (index.length > 0)
              for (int i = 0; i < index.length; i++)
                fileList.getSelectionModel().addSelectionInterval(index[i], index[i]);
          
          fc.add(fileListPanel, BorderLayout.CENTER);
          fc.revalidate();
          fc.repaint();
        }
    }
  }
  
  /**
   * ActionListener for the details view.
   */
  class DetailViewActionListener implements ActionListener
  {
    
    /**
     * This method is invoked when an action occurs.
     * 
     * @param e -
     *          the <code>ActionEvent</code> that occurred
     */
    public void actionPerformed(ActionEvent e)
    {
      if (listView)
        {
          int[] index = fileList.getSelectedIndices();
          JFileChooser fc = getFileChooser();
          listView = false;
          fc.remove(fileListPanel);
          
          if (fileTable == null)
            createDetailsView(fc);
          else
            updateTable();

          fileTable.getSelectionModel().clearSelection();
          if (index.length > 0)
            {
              for (int i = 0; i < index.length; i++)
                fileTable.getSelectionModel().addSelectionInterval(index[i], index[i]);
            }
          
          fc.add(fileTablePanel, BorderLayout.CENTER);
          fc.revalidate();
          fc.repaint();
        }
    }
  }
  
  /**
   * A property change listener.
   */
  class MetalFileChooserPropertyChangeListener 
    implements PropertyChangeListener
  {
    /**
     * Default constructor.
     */
    public MetalFileChooserPropertyChangeListener()
    {
    }
    
    /**
     * Handles a property change event.
     * 
     * @param e  the event.
     */
    public void propertyChange(PropertyChangeEvent e)
    {
      JFileChooser filechooser = getFileChooser();
      
      String n = e.getPropertyName();
      if (n.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY))
        {
          int mode = -1; 
          if (filechooser.isMultiSelectionEnabled())
            mode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
          else
            mode = ListSelectionModel.SINGLE_SELECTION;
          
          if (listView)
            fileList.setSelectionMode(mode);
          else
            fileTable.setSelectionMode(mode);
        }
      else if (n.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
        {
          File file = filechooser.getSelectedFile();
          
          if (file != null
              && filechooser.getDialogType() == JFileChooser.SAVE_DIALOG)
            {
              if (file.isDirectory() && filechooser.isTraversable(file))
                {
                  directoryLabel = look;
                  dirLabel.setText(directoryLabel);
                  filechooser.setApproveButtonText(openButtonText);
                  filechooser.setApproveButtonToolTipText(openButtonToolTipText);
                }
              else if (file.isFile())
                {
                  directoryLabel = save;
                  dirLabel.setText(directoryLabel);
                  filechooser.setApproveButtonText(saveButtonText);
                  filechooser.setApproveButtonToolTipText(saveButtonToolTipText);
                }
            }
            
          if (file == null)
            setFileName(null);
          else if (file.isFile() || filechooser.getFileSelectionMode() 
		   != JFileChooser.FILES_ONLY)
	    setFileName(file.getName());
          int index = -1;
          index = getModel().indexOf(file);
          if (index >= 0)
            {
              if (listView)
                {
                  fileList.setSelectedIndex(index);
                  fileList.ensureIndexIsVisible(index);
                  fileList.revalidate();
                  fileList.repaint();
                }
              else
                {
                  fileTable.getSelectionModel().addSelectionInterval(index, index);
                  fileTable.scrollRectToVisible(fileTable.getCellRect(index, 0, true));
                  fileTable.revalidate();
                  fileTable.repaint();
                }
            }
        }
      
      else if (n.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY))
        {
          if (listView)
            {
              fileList.clearSelection();
              fileList.revalidate();
              fileList.repaint();
            }
          else
            {
              fileTable.clearSelection();
              fileTable.revalidate();
              fileTable.repaint();
            }

          setDirectorySelected(false);
          File currentDirectory = filechooser.getCurrentDirectory();
          setDirectory(currentDirectory);
          boolean hasParent = currentDirectory.getParentFile() != null;
          getChangeToParentDirectoryAction().setEnabled(hasParent);
        }
      
      else if (n.equals(JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY))
        {
          filterModel.propertyChange(e);
        }
      else if (n.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY))
        {
          filterModel.propertyChange(e);
        }
      else if (n.equals(JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY)
                 || n.equals(JFileChooser.DIALOG_TITLE_CHANGED_PROPERTY))
        {
          Window owner = SwingUtilities.windowForComponent(filechooser);
          if (owner instanceof JDialog)
            ((JDialog) owner).setTitle(getDialogTitle(filechooser));
          approveButton.setText(getApproveButtonText(filechooser));
          approveButton.setToolTipText(
                  getApproveButtonToolTipText(filechooser));
          approveButton.setMnemonic(getApproveButtonMnemonic(filechooser));
        }
      
      else if (n.equals(JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY))
        approveButton.setText(getApproveButtonText(filechooser));
      
      else if (n.equals(
              JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY))
        approveButton.setToolTipText(getApproveButtonToolTipText(filechooser));
      
      else if (n.equals(JFileChooser.APPROVE_BUTTON_MNEMONIC_CHANGED_PROPERTY))
        approveButton.setMnemonic(getApproveButtonMnemonic(filechooser));

      else if (n.equals(
              JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY))
        {
          if (filechooser.getControlButtonsAreShown())
            {
              topPanel.add(controls, BorderLayout.EAST);
            }
          else
            topPanel.remove(controls);
          topPanel.revalidate();
          topPanel.repaint();
          topPanel.doLayout();
        }
      
      else if (n.equals(
              JFileChooser.ACCEPT_ALL_FILE_FILTER_USED_CHANGED_PROPERTY))
        {
          if (filechooser.isAcceptAllFileFilterUsed())
            filechooser.addChoosableFileFilter(
                    getAcceptAllFileFilter(filechooser));
          else
            filechooser.removeChoosableFileFilter(
                    getAcceptAllFileFilter(filechooser));
        }
      
      else if (n.equals(JFileChooser.ACCESSORY_CHANGED_PROPERTY))
        {
          JComponent old = (JComponent) e.getOldValue();
          if (old != null)
            getAccessoryPanel().remove(old);
          JComponent newval = (JComponent) e.getNewValue();
          if (newval != null)
            getAccessoryPanel().add(newval);
        }
      
      if (n.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)
          || n.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)
          || n.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY))
        {
          // Remove editing component
          if (fileTable != null)
            fileTable.removeAll();
          if (fileList != null)
            fileList.removeAll();
          startEditing = false;
          
          // Set text on button back to original.
          if (filechooser.getDialogType() == JFileChooser.SAVE_DIALOG)
            {
              directoryLabel = save;
              dirLabel.setText(directoryLabel);
              filechooser.setApproveButtonText(saveButtonText);
              filechooser.setApproveButtonToolTipText(saveButtonToolTipText);
            }
          
          rescanCurrentDirectory(filechooser);
        }
      
      filechooser.revalidate();
      filechooser.repaint();
    }
  }
  
  /** 
   * A combo box model containing the selected directory and all its parent
   * directories.
   */
  protected class DirectoryComboBoxModel
    extends AbstractListModel
    implements ComboBoxModel
  {
    /** Storage for the items in the model. */
    private List items;
    
    /** The index of the selected item. */
    private int selectedIndex;
    
    /**
     * Creates a new model.
     */
    public DirectoryComboBoxModel() 
    {
      items = new java.util.ArrayList();
      selectedIndex = -1;
    }
    
    /**
     * Returns the number of items in the model.
     * 
     * @return The number of items in the model.
     */
    public int getSize()
    {
      return items.size();
    }
    
    /**
     * Returns the item at the specified index.
     * 
     * @param index  the item index.
     * 
     * @return The item.
     */
    public Object getElementAt(int index)
    {
      return items.get(index);
    }
    
    /**
     * Returns the depth of the item at the given <code>index</code>.
     * 
     * @param index  the item index.
     * 
     * @return The depth.
     */
    public int getDepth(int index)
    {
      return Math.max(index, 0);
    }

    /**
     * Returns the selected item, or <code>null</code> if no item is selected.
     * 
     * @return The selected item, or <code>null</code>.
     */
    public Object getSelectedItem()
    {
      if (selectedIndex >= 0) 
        return items.get(selectedIndex);
      else
        return null;
    }
    
    /**
     * Sets the selected item.  This clears all the directories from the
     * existing list, and repopulates it with the new selected directory
     * and all its parent directories.
     * 
     * @param selectedDirectory  the selected directory.
     */
    public void setSelectedItem(Object selectedDirectory)
    {
      items.clear();
      FileSystemView fsv = getFileChooser().getFileSystemView();
      File parent = (File) selectedDirectory;
      while (parent != null)
        {
          items.add(0, parent);
          parent = fsv.getParentDirectory(parent);
        }
      selectedIndex = items.indexOf(selectedDirectory);
      fireContentsChanged(this, 0, items.size() - 1);
    }
    
  }

  /**
   * Handles changes to the selection in the directory combo box.
   */
  protected class DirectoryComboBoxAction
    extends AbstractAction
  {
    /**
     * Creates a new action.
     */
    protected DirectoryComboBoxAction()
    {
      // Nothing to do here.
    }
    
    /**
     * Handles the action event.
     * 
     * @param e  the event.
     */
    public void actionPerformed(ActionEvent e)
    {
      JFileChooser fc = getFileChooser();
      fc.setCurrentDirectory((File) directoryModel.getSelectedItem());
    }
  }

  /**
   * A renderer for the items in the directory combo box.
   */
  class DirectoryComboBoxRenderer
    extends DefaultListCellRenderer
  {
    /**
     * This is the icon that is displayed in the combobox. This wraps
     * the standard icon and adds indendation.
     */
    private IndentIcon indentIcon;

    /**
     * Creates a new renderer.
     */
    public DirectoryComboBoxRenderer(JFileChooser fc)
    {
      indentIcon = new IndentIcon();
    }
    
    /**
     * Returns a component that can be used to paint the given value within 
     * the list.
     * 
     * @param list  the list.
     * @param value  the value (a {@link File}).
     * @param index  the item index.
     * @param isSelected  is the item selected?
     * @param cellHasFocus  does the list cell have focus?
     * 
     * @return The list cell renderer.
     */
    public Component getListCellRendererComponent(JList list, Object value,
                                                  int index,
                                                  boolean isSelected,
                                                  boolean cellHasFocus)
    {
      super.getListCellRendererComponent(list, value, index, isSelected,
                                         cellHasFocus);
      File file = (File) value;
      setText(getFileChooser().getName(file));

      // Install indented icon.
      Icon icon = getFileChooser().getIcon(file);
      indentIcon.setIcon(icon);
      int depth = directoryModel.getDepth(index);
      indentIcon.setDepth(depth);
      setIcon(indentIcon);

      return this;
    }
  }

  /**
   * An icon that wraps another icon and adds indentation.
   */
  class IndentIcon
    implements Icon
  {

    /**
     * The indentation level.
     */
    private static final int INDENT = 10;

    /**
     * The wrapped icon.
     */
    private Icon icon;

    /**
     * The current depth.
     */
    private int depth;

    /**
     * Sets the icon to be wrapped.
     *
     * @param i the icon
     */
    void setIcon(Icon i)
    {
      icon = i;
    }

    /**
     * Sets the indentation depth.
     *
     * @param d the depth to set
     */
    void setDepth(int d)
    {
      depth = d;
    }

    public int getIconHeight()
    {
      return icon.getIconHeight();
    }

    public int getIconWidth()
    {
      return icon.getIconWidth() + depth * INDENT;
    }

    public void paintIcon(Component c, Graphics g, int x, int y)
    {
      icon.paintIcon(c, g, x + depth * INDENT, y);
    }
      
  }

  /**
   * A renderer for the files and directories in the file chooser.
   */
  protected class FileRenderer
    extends DefaultListCellRenderer
  {
    
    /**
     * Creates a new renderer.
     */
    protected FileRenderer()
    {
      // Nothing to do here.
    }
    
    /**
     * Returns a component that can render the specified value.
     * 
     * @param list  the list.
     * @param value  the value (a {@link File}).
     * @param index  the index.
     * @param isSelected  is the item selected?
     * @param cellHasFocus  does the item have the focus?
     * 
     * @return The renderer.
     */
    public Component getListCellRendererComponent(JList list, Object value,
        int index, boolean isSelected, boolean cellHasFocus)
    {
      FileView v = getFileView(getFileChooser());
      File f = (File) value;
      if (f != null)
        {
          setText(v.getName(f));
          setIcon(v.getIcon(f));
        }
      else
        {
          setText("");
          setIcon(null);
        }
      setOpaque(true);
      if (isSelected)
        {
          setBackground(list.getSelectionBackground());
          setForeground(list.getSelectionForeground());
        }
      else
        {
          setBackground(list.getBackground());
          setForeground(list.getForeground());
        }

      setEnabled(list.isEnabled());
      setFont(list.getFont());

      if (cellHasFocus)
        setBorder(UIManager.getBorder("List.focusCellHighlightBorder"));
      else
        setBorder(noFocusBorder);
      return this;
    }
  }

  /**
   * A combo box model for the file selection filters.
   */
  protected class FilterComboBoxModel
    extends AbstractListModel
    implements ComboBoxModel, PropertyChangeListener
  {

    /** Storage for the filters in the model. */
    protected FileFilter[] filters;

    /** The index of the selected file filter. */
    private Object selected;
    
    /**
     * Creates a new model.
     */
    protected FilterComboBoxModel()
    {
      filters = new FileFilter[1];
      filters[0] = getAcceptAllFileFilter(getFileChooser());
      selected = filters[0];
    }
    
    /**
     * Handles property changes.
     * 
     * @param e  the property change event.
     */
    public void propertyChange(PropertyChangeEvent e)
    {
      if (e.getPropertyName().equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY))
        {
          JFileChooser fc = getFileChooser();
          FileFilter[] choosableFilters = fc.getChoosableFileFilters();
          filters = choosableFilters;
          fireContentsChanged(this, 0, filters.length);
          selected = e.getNewValue();
          fireContentsChanged(this, -1, -1);
        }
      else if (e.getPropertyName().equals(
              JFileChooser.CHOOSABLE_FILE_FILTER_CHANGED_PROPERTY))
        {
          // repopulate list
          JFileChooser fc = getFileChooser();
          FileFilter[] choosableFilters = fc.getChoosableFileFilters();
          filters = choosableFilters;
          fireContentsChanged(this, 0, filters.length);
        }
    }
    
    /**
     * Sets the selected filter.
     * 
     * @param filter  the filter (<code>null</code> ignored).
     */
    public void setSelectedItem(Object filter)
    {
      if (filter != null)
      {
          selected = filter;
          fireContentsChanged(this, -1, -1);
      }
    }
    
    /**
     * Returns the selected file filter.
     * 
     * @return The selected file filter.
     */
    public Object getSelectedItem()
    {
      return selected;
    }
    
    /**
     * Returns the number of items in the model.
     * 
     * @return The number of items in the model.
     */
    public int getSize()
    {
      return filters.length;
    }
    
    /**
     * Returns the item at the specified index.
     * 
     * @param index  the item index.
     * 
     * @return The item at the specified index.
     */
    public Object getElementAt(int index)
    {
      return filters[index];
    }
    
  }

  /**
   * A renderer for the items in the file filter combo box.
   */
  public class FilterComboBoxRenderer
    extends DefaultListCellRenderer
  {
    /**
     * Creates a new renderer.
     */
    public FilterComboBoxRenderer()
    {
      // Nothing to do here.
    }
    
    /**
     * Returns a component that can be used to paint the given value within 
     * the list.
     * 
     * @param list  the list.
     * @param value  the value (a {@link FileFilter}).
     * @param index  the item index.
     * @param isSelected  is the item selected?
     * @param cellHasFocus  does the list cell have focus?
     * 
     * @return This component as the renderer.
     */
    public Component getListCellRendererComponent(JList list, Object value,
        int index, boolean isSelected, boolean cellHasFocus)
    {
      super.getListCellRendererComponent(list, value, index, isSelected, 
                                         cellHasFocus);
      FileFilter filter = (FileFilter) value;
      setText(filter.getDescription());
      return this;
    }
  }

  /**
   * A listener for selection events in the file list.
   * 
   * @see #createListSelectionListener(JFileChooser)
   */
  class MetalFileChooserSelectionListener 
    implements ListSelectionListener
  {
    /**
     * Creates a new <code>SelectionListener</code> object.
     */
    protected MetalFileChooserSelectionListener()
    {
      // Do nothing here.
    }

    /**
     * Makes changes to different properties when
     * a value has changed in the filechooser's selection.
     *
     * @param e - the list selection event that occured.
     */
    public void valueChanged(ListSelectionEvent e)
    {
      File f = (File) fileList.getSelectedValue();
      if (f == null)
        return;
      JFileChooser filechooser = getFileChooser();
      if (! filechooser.isTraversable(f))
        filechooser.setSelectedFile(f);
      else
        filechooser.setSelectedFile(null);
    }
  }

  /**
   * A mouse listener for the {@link JFileChooser}.
   * This listener is used for editing filenames.
   */
  protected class SingleClickListener
    extends MouseAdapter
  {
    
    /** Stores instance of the list */
    JList list;
    
    /** 
     * Stores the current file that is being edited.
     * It is null if nothing is currently being edited.
     */
    File editFile;
    
    /** The current file chooser. */
    JFileChooser fc;
    
    /** The last file selected. */
    Object lastSelected;
    
    /** The textfield used for editing. */
    JTextField editField;
    
    /**
     * Creates a new listener.
     * 
     * @param list  the directory/file list.
     */
    public SingleClickListener(JList list)
    {
      this.list = list;
      editFile = null;
      fc = getFileChooser();
      lastSelected = null;
      startEditing = false;
    }
    
    /**
     * Receives notification of a mouse click event.
     * 
     * @param e  the event.
     */
    public void mouseClicked(MouseEvent e)
    {
      if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1)
        {
          int index = list.locationToIndex(e.getPoint());
          File[] sf = fc.getSelectedFiles();
          if ((!fc.isMultiSelectionEnabled() || (sf != null && sf.length <= 1))
              && index >= 0 && !startEditing && list.isSelectedIndex(index))
            {
              Object tmp = list.getModel().getElementAt(index);
              if (lastSelected != null && lastSelected.equals(tmp))
                editFile(index);
              lastSelected = tmp;
            }
          else
              completeEditing();
        }
      else
        completeEditing();
    }
    
    /**
     * Sets up the text editor for the current file.
     * 
     * @param index -
     *          the current index of the item in the list to be edited.
     */
    void editFile(int index)
    {
      Rectangle bounds = list.getCellBounds(index, index);
      list.scrollRectToVisible(bounds);
      editFile = (File) list.getModel().getElementAt(index);
      if (editFile.canWrite())
        {
          startEditing = true;
          editField = new JTextField(editFile.getName());
          editField.addActionListener(new EditingActionListener());
          
          Icon icon = getFileView(fc).getIcon(editFile);
          if (icon != null)
            {
              int padding = icon.getIconWidth() + 4;
              bounds.x += padding;
              bounds.width -= padding;
            }
          editField.setBounds(bounds);
          
          list.add(editField);
          
          editField.requestFocus();
          editField.selectAll();
        }
      else
        completeEditing();
      list.repaint();
    }
    
    /** 
     * Completes the editing.
     */
    void completeEditing()
    {
      if (editField != null && editFile != null)
        {
          String text = editField.getText();
          if (text != null && text != "" && !text.equals(fc.getName(editFile)))
	    {
	      File f = fc.getFileSystemView().
		createFileObject(fc.getCurrentDirectory(), text);
              if ( editFile.renameTo(f) )
                rescanCurrentDirectory(fc);
	    }
          list.remove(editField);
        }
      startEditing = false;
      editFile = null;
      lastSelected = null;
      editField = null;
      list.repaint();
    }
    
    /**
     * ActionListener for the editing text field.
     */
    class EditingActionListener implements ActionListener
    {
      
      /**
       * This method is invoked when an action occurs.
       * 
       * @param e -
       *          the <code>ActionEvent</code> that occurred
       */
      public void actionPerformed(ActionEvent e)
      {
	if (editField != null)
          completeEditing();
      }
    }
  }

  /**
   * A mouse listener for the {@link JFileChooser}.
   * This listener is used for the table
   */
  private class TableClickListener extends MouseAdapter
  {

    /** Stores instance of the table */
    JTable table;

    /** Stores instance of the file chooser */
    JFileChooser fc;

    /** The last selected file. */
    Object lastSelected;
    
    /** 
     * Stores the current file that is being edited.
     * It is null if nothing is currently being edited.
     */
    File editFile;
    
    /** The textfield used for editing. */
    JTextField editField;

    /**
     * Creates a new listener.
     * 
     * @param table the directory/file table
     * @param fc the JFileChooser
     */
    public TableClickListener(JTable table, JFileChooser fc)
    {
      this.table = table;
      this.fc = fc;
      lastSelected = fileList.getSelectedValue();
      setDirectorySelected(false);
      startEditing = false;
      editFile = null;
      editField = null;
    }

    /**
     * Receives notification of a mouse click event.
     * 
     * @param e the event.
     */
    public void mouseClicked(MouseEvent e)
    {
      int row = table.getSelectedRow();
      Object selVal = fileList.getModel().getElementAt(row);
      if (selVal == null)
        return;
      FileSystemView fsv = fc.getFileSystemView();
      if (e.getClickCount() == 1 &&
          selVal.equals(lastSelected) &&
          e.getButton() == MouseEvent.BUTTON1)
        {
          File[] sf = fc.getSelectedFiles();
          if ((!fc.isMultiSelectionEnabled() || (sf != null && sf.length <= 1))
              && !startEditing)
            {
              editFile = (File) selVal;
              editFile(row);
            }
        }
      else if (e.getClickCount() >= 2 &&
          selVal.equals(lastSelected))
        {
          if (startEditing)
            completeEditing();
          File f = fsv.createFileObject(lastSelected.toString());
          if (fc.isTraversable(f))
            {
              fc.setCurrentDirectory(f);
              fc.rescanCurrentDirectory();
            }
          else
            {
              fc.setSelectedFile(f);
              fc.approveSelection();
              closeDialog();
            }
        }
      else
        {
          if (startEditing)
            completeEditing();
          String path = selVal.toString();
          File f = fsv.createFileObject(path);
          fc.setSelectedFile(f);
          if (fc.isTraversable(f))
            {
              setDirectorySelected(true);
              setDirectory(f);
            }
          else
            {
              setDirectorySelected(false);
              setDirectory(null);
            }
          lastSelected = selVal;
          if (f.isFile())
            setFileName(path.substring(path.lastIndexOf("/") + 1));
          else if (fc.getFileSelectionMode() != JFileChooser.FILES_ONLY)
            setFileName(path);
        }
      fileTable.repaint();
    }

    /**
     * Sets up the text editor for the current file.
     * 
     * @param row -
     *          the current row of the item in the list to be edited.
     */
    void editFile(int row)
    {
      Rectangle bounds = table.getCellRect(row, 0, true);
      table.scrollRectToVisible(bounds);
      if (editFile.canWrite())
        {
          startEditing = true;
          editField = new JTextField(editFile.getName());
          editField.addActionListener(new EditingActionListener());

          // Need to adjust y pos
          bounds.y = row * table.getRowHeight();
          editField.setBounds(bounds);
          
          table.add(editField);
          
          editField.requestFocus();
          editField.selectAll();
        }
      else
        completeEditing();
      table.repaint();
    }
    
    /** 
     * Completes the editing.
     */
    void completeEditing()
    {
      if (editField != null && editFile != null)
        {
          String text = editField.getText();
          if (text != null && text != "" && !text.equals(fc.getName(editFile)))
              if (editFile.renameTo(fc.getFileSystemView().createFileObject(
                  fc.getCurrentDirectory(), text)))
                rescanCurrentDirectory(fc);
          table.remove(editField);
        }
      startEditing = false;
      editFile = null;
      editField = null;
      table.repaint();
    }
    
    /**
     * ActionListener for the editing text field.
     */
    class EditingActionListener implements ActionListener
    {
      
      /**
       * This method is invoked when an action occurs.
       * 
       * @param e -
       *          the <code>ActionEvent</code> that occurred
       */
      public void actionPerformed(ActionEvent e)
      {
	if (editField != null)
          completeEditing();
      }
    }
    
    /**
     * Closes the dialog.
     */
    public void closeDialog()
    {
      Window owner = SwingUtilities.windowForComponent(fc);
      if (owner instanceof JDialog)
        ((JDialog) owner).dispose();
    }
  } 
  
  /** The text for a label describing the directory combo box. */
  private String directoryLabel;
  
  private JComboBox directoryComboBox;
  
  /** The model for the directory combo box. */
  DirectoryComboBoxModel directoryModel;
  
  /** The text for a label describing the file text field. */
  private String fileLabel;
  
  /** The file name text field. */
  private JTextField fileTextField;
  
  /** The text for a label describing the filter combo box. */
  private String filterLabel;

  /** 
   * The top panel (contains the directory combo box and the control buttons). 
   */
  private JPanel topPanel;
  
  /** A panel containing the control buttons ('up', 'home' etc.). */
  private JPanel controls;

  /** 
   * The panel that contains the filename field and the filter combobox. 
   */
  private JPanel bottomPanel;

  /** 
   * The panel that contains the 'Open' (or 'Save') and 'Cancel' buttons. 
   */
  private JPanel buttonPanel;
  
  private JButton approveButton;
  
  /** The file list. */
  JList fileList;
  
  /** The file table. */
  JTable fileTable;
  
  /** The panel containing the file list. */
  JPanel fileListPanel;
  
  /** The panel containing the file table. */
  JPanel fileTablePanel;
  
  /** The filter combo box model. */
  private FilterComboBoxModel filterModel;

  /** The action map. */
  private ActionMap actionMap;
  
  /** True if currently in list view. */
  boolean listView;
  
  /** True if we can or have started editing a cell. */
  boolean startEditing;
  
  /** The scrollpane used for the table and list. */
  JScrollPane scrollPane;
  
  /** The text for the label when saving. */
  String save;
  
  /** The text for the label when opening a directory. */
  String look;
  
  /** The label for the file combo box. */
  JLabel dirLabel;
  
  /** Listeners. */
  ListSelectionListener listSelList;
  MouseListener doubleClickList;
  SingleClickListener singleClickList;
  TableClickListener tableClickList;
  
  /**
   * A factory method that returns a UI delegate for the specified
   * component.
   * 
   * @param c  the component (which should be a {@link JFileChooser}).
   */
  public static ComponentUI createUI(JComponent c)
  {
    JFileChooser chooser = (JFileChooser) c;
    return new MetalFileChooserUI(chooser);
  }

  /**
   * Creates a new instance of this UI delegate.
   * 
   * @param filechooser  the file chooser component.
   */
  public MetalFileChooserUI(JFileChooser filechooser)
  {
    super(filechooser);
    bottomPanel = new JPanel(new GridLayout(3, 2));
    buttonPanel = new JPanel();
  }

  public void installUI(JComponent c)
  {
    super.installUI(c);
    actionMap = createActionMap();
  }
  
  public void uninstallUI(JComponent c)
  {
    super.uninstallUI(c);
    actionMap = null;
  }
  
  /**
   * Installs the sub-components of the file chooser.
   * 
   * @param fc  the file chooser component.
   */
  public void installComponents(JFileChooser fc)
  {
    fc.setLayout(new BorderLayout());
    topPanel = new JPanel(new BorderLayout());
    dirLabel = new JLabel(directoryLabel);
    topPanel.add(dirLabel, BorderLayout.WEST);
    this.controls = new JPanel();
    addControlButtons();
    
    JPanel dirPanel = new JPanel(new VerticalMidLayout());
    directoryModel = createDirectoryComboBoxModel(fc);
    directoryComboBox = new JComboBox(directoryModel);
    directoryComboBox.setRenderer(createDirectoryComboBoxRenderer(fc));
    dirPanel.add(directoryComboBox);
    topPanel.add(dirPanel);
    topPanel.add(controls, BorderLayout.EAST);
    topPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 0, 8));
    fc.add(topPanel, BorderLayout.NORTH);
    
    JPanel list = createList(fc);
    list.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
    fc.add(list, BorderLayout.CENTER);
    
    JPanel bottomPanel = getBottomPanel();
    filterModel = createFilterComboBoxModel();
    JComboBox fileFilterCombo = new JComboBox(filterModel);
    fileFilterCombo.setRenderer(createFilterComboBoxRenderer());
    
    fileTextField = new JTextField();
    JPanel fileNamePanel = new JPanel(new VerticalMidLayout());
    fileNamePanel.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 5));
    fileNamePanel.add(fileTextField);
    JPanel row1 = new JPanel(new BorderLayout());
    row1.add(new JLabel(this.fileLabel), BorderLayout.WEST);
    row1.add(fileNamePanel);
    bottomPanel.add(row1);
    
    JPanel row2 = new JPanel(new BorderLayout());
    row2.add(new JLabel(this.filterLabel), BorderLayout.WEST);
    row2.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
    row2.add(fileFilterCombo);
    bottomPanel.add(row2);
    JPanel buttonPanel = new JPanel(new ButtonLayout());
    
    approveButton = new JButton(getApproveSelectionAction());
    approveButton.setText(getApproveButtonText(fc));
    approveButton.setToolTipText(getApproveButtonToolTipText(fc));
    approveButton.setMnemonic(getApproveButtonMnemonic(fc));
    buttonPanel.add(approveButton);
    buttonPanel.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0));
    
    JButton cancelButton = new JButton(getCancelSelectionAction());
    cancelButton.setText(cancelButtonText);
    cancelButton.setToolTipText(cancelButtonToolTipText);
    cancelButton.setMnemonic(cancelButtonMnemonic);
    buttonPanel.add(cancelButton);
    bottomPanel.add(buttonPanel, BorderLayout.SOUTH);
    bottomPanel.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
    fc.add(bottomPanel, BorderLayout.SOUTH);
    
    fc.add(getAccessoryPanel(), BorderLayout.EAST);
  }
  
  /**
   * Uninstalls the components added by 
   * {@link #installComponents(JFileChooser)}.
   * 
   * @param fc  the file chooser.
   */
  public void uninstallComponents(JFileChooser fc)
  {
    fc.remove(bottomPanel);
    bottomPanel = null;
    fc.remove(fileListPanel);
    fc.remove(fileTablePanel);
    fileTablePanel = null;
    fileListPanel = null;
    fc.remove(topPanel);
    topPanel = null;
    
    directoryModel = null;
    fileTextField = null;
    directoryComboBox = null;
  }
  
  /**
   * Returns the panel that contains the 'Open' (or 'Save') and 'Cancel' 
   * buttons.
   * 
   * @return The panel.
   */
  protected JPanel getButtonPanel()
  {
    return buttonPanel;    
  }
  
  /**
   * Creates and returns a new panel that will be used for the controls at
   * the bottom of the file chooser.
   * 
   * @return A new panel.
   */
  protected JPanel getBottomPanel()
  {
    if (bottomPanel == null)
      bottomPanel = new JPanel(new GridLayout(3, 2));
    return bottomPanel;
  }
  
  /**
   * Fetches localised strings for use by the labels and buttons on the
   * file chooser.
   * 
   * @param fc  the file chooser.
   */
  protected void installStrings(JFileChooser fc)
  { 
     super.installStrings(fc);
     look = "Look In: ";
     save = "Save In: ";
     if (fc.getDialogType() == JFileChooser.SAVE_DIALOG)
       directoryLabel = save;
     else
       directoryLabel = look;
     
     fileLabel = "File Name: ";
     filterLabel = "Files of Type: ";
     
     this.cancelButtonMnemonic = 0;
     this.cancelButtonText = "Cancel";
     this.cancelButtonToolTipText = "Abort file chooser dialog";
     
     this.directoryOpenButtonMnemonic = 0;
     this.directoryOpenButtonText = "Open";
     this.directoryOpenButtonToolTipText = "Open selected directory";
     
     this.helpButtonMnemonic = 0;
     this.helpButtonText = "Help";
     this.helpButtonToolTipText = "Filechooser help";
     
     this.openButtonMnemonic = 0;
     this.openButtonText = "Open";
     this.openButtonToolTipText = "Open selected file";
     
     this.saveButtonMnemonic = 0;
     this.saveButtonText = "Save";
     this.saveButtonToolTipText = "Save selected file";
     
     this.updateButtonMnemonic = 0;
     this.updateButtonText = "Update";
     this.updateButtonToolTipText = "Update directory listing";   
  }
  
  /**
   * Installs the listeners required.
   * 
   * @param fc  the file chooser.
   */
  protected void installListeners(JFileChooser fc)
  {
    directoryComboBox.setAction(new DirectoryComboBoxAction());
    fc.addPropertyChangeListener(filterModel);
    listSelList = createListSelectionListener(fc);
    doubleClickList = this.createDoubleClickListener(fc, fileList);
    singleClickList = new SingleClickListener(fileList);
    fileList.addListSelectionListener(listSelList);
    fileList.addMouseListener(doubleClickList);
    fileList.addMouseListener(singleClickList);
    super.installListeners(fc);
  }
  
  protected void uninstallListeners(JFileChooser fc) 
  {
    super.uninstallListeners(fc);
    fc.removePropertyChangeListener(filterModel);
    directoryComboBox.setAction(null);
    fileList.removeListSelectionListener(listSelList);
    fileList.removeMouseListener(doubleClickList);
    fileList.removeMouseListener(singleClickList);
    
    if (fileTable != null)
      fileTable.removeMouseListener(tableClickList);
  }
  
  protected ActionMap getActionMap()
  {
    if (actionMap == null)
      actionMap = createActionMap();
    return actionMap;
  }
  
  /**
   * Creates and returns an action map.
   * 
   * @return The action map.
   */
  protected ActionMap createActionMap()
  {
    ActionMap map = new ActionMap();
    map.put("approveSelection", getApproveSelectionAction());
    map.put("cancelSelection", getCancelSelectionAction());
    map.put("Go Up", getChangeToParentDirectoryAction());
    return map;
  }

  /**
   * Creates a panel containing a list of files.
   * 
   * @param fc  the file chooser.
   * 
   * @return A panel.
   */
  protected JPanel createList(JFileChooser fc)
  {
    if (fileList == null)
      {
        fileListPanel = new JPanel(new BorderLayout());
        fileList = new JList(getModel());
        scrollPane = new JScrollPane(fileList);
        fileList.setLayoutOrientation(JList.VERTICAL_WRAP);
        fileList.setCellRenderer(new FileRenderer());
      }
    else
      {
        fileList.setModel(getModel());
        fileListPanel.removeAll();
        scrollPane.getViewport().setView(fileList);
      }
    fileListPanel.add(scrollPane);
    // This size was determined using BeanShell and dumping the JFileChooser
    // component hierarchy. Sun has an internal FilePane class in there, but
    // that probably doesn't matter atm.
    fileListPanel.setPreferredSize(new Dimension(405, 135));
    return fileListPanel;
  }
  
  /**
   * Creates a panel containing a table within a scroll pane.
   * 
   * @param fc  the file chooser.
   * 
   * @return The details view.
   */
  protected JPanel createDetailsView(JFileChooser fc)
  {
    fileTablePanel = new JPanel(new BorderLayout());
    
    Object[] cols = new Object[] {"Name", "Size", "Modified"};
    Object[][] rows = new Object[fileList.getModel().getSize()][3];
    
    fileTable = new JTable(new DefaultTableModel(rows, cols));
    
    if (fc.isMultiSelectionEnabled())
      fileTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    else
      fileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    
    fileTable.setShowGrid(false);
    fileTable.setColumnSelectionAllowed(false);
    fileTable.setDefaultRenderer(Object.class, new TableFileRenderer());

    tableClickList = new TableClickListener(fileTable, fc);
    fileTable.addMouseListener(tableClickList);
    
    return updateTable();  
  }
  
  /**
   * Sets the values in the table, and puts it in the panel.
   * 
   * @return the panel containing the table.
   */
  JPanel updateTable()
  {
    DefaultTableModel mod = (DefaultTableModel) fileTable.getModel();
    ListModel lm = fileList.getModel();
    DateFormat dt = DateFormat.getDateTimeInstance(DateFormat.SHORT,
                                                   DateFormat.SHORT);
    File curr = null;
    int size = lm.getSize();
    int rc = mod.getRowCount();

    // If there are not enough rows
    for (int x = rc; x < size; x++)
      mod.addRow(new Object[3]);

    for (int i = 0; i < size; i++)
      {
        curr = (File) lm.getElementAt(i);
        fileTable.setValueAt(curr.getName(), i, 0);
        fileTable.setValueAt(formatSize(curr.length()), i, 1);
        fileTable.setValueAt(dt.format(new Date(curr.lastModified())), i, 2);
      }

    // If there are too many rows
    while (rc > size)
      mod.removeRow(--rc);

    scrollPane.getViewport().setView(fileTable);
    scrollPane.setColumnHeaderView(fileTable.getTableHeader());

    fileTablePanel.removeAll();
    fileTablePanel.add(scrollPane);

    return fileTablePanel;
  }
  
  /**
   * Formats bytes into the appropriate size.
   * 
   * @param bytes the number of bytes to convert
   * @return a string representation of the size
   */
  private String formatSize(long bytes)
  {
    NumberFormat nf = NumberFormat.getNumberInstance();
    long mb = (long) Math.pow(2, 20);
    long kb = (long) Math.pow(2, 10);
    long gb = (long) Math.pow(2, 30);
    double size = 0;
    String id = "";
    
    if ((bytes / gb) >= 1)
      {
        size = (double) bytes / (double) gb;
        id = "GB";
      }
    else if ((bytes / mb) >= 1)
      {
        size = (double) bytes / (double) mb;
        id = "MB";
      }
    else if ((bytes / kb) >= 1)
      {
        size = (double) bytes / (double) kb;
        id = "KB";
      }
    else
      {
        size = bytes;
        id = "Bytes";
      }
    
    return nf.format(size) + " " + id;
  }
  /**
   * Creates a listener that monitors selections in the directory/file list
   * and keeps the {@link JFileChooser} component up to date.
   * 
   * @param fc  the file chooser.
   * 
   * @return The listener.
   * 
   * @see #installListeners(JFileChooser)
   */
  public ListSelectionListener createListSelectionListener(JFileChooser fc)
  {
    return new MetalFileChooserSelectionListener();
  }
  
  /**
   * Returns the preferred size for the file chooser component.
   * 
   * @return The preferred size.
   */
  public Dimension getPreferredSize(JComponent c)
  {
    Dimension tp = topPanel.getPreferredSize();
    Dimension bp = bottomPanel.getPreferredSize();
    Dimension fl = fileListPanel.getPreferredSize();
    return new Dimension(fl.width, tp.height + bp.height + fl.height);
  }

  /**
   * Returns the minimum size for the file chooser component.
   * 
   * @return The minimum size.
   */
  public Dimension getMinimumSize(JComponent c)
  {
    Dimension tp = topPanel.getMinimumSize();
    Dimension bp = bottomPanel.getMinimumSize();
    Dimension fl = fileListPanel.getMinimumSize();
    return new Dimension(fl.width, tp.height + bp.height + fl.height);
  }
  
  /**
   * Returns the maximum size for the file chooser component.
   * 
   * @return The maximum size.
   */
  public Dimension getMaximumSize(JComponent c)
  {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  }
  
  /**
   * Creates a property change listener that monitors the {@link JFileChooser}
   * for property change events and updates the component display accordingly.
   * 
   * @param fc  the file chooser.
   * 
   * @return The property change listener.
   * 
   * @see #installListeners(JFileChooser)
   */
  public PropertyChangeListener createPropertyChangeListener(JFileChooser fc)
  {
    return new MetalFileChooserPropertyChangeListener();
  }

  /**
   * Creates and returns a new instance of {@link DirectoryComboBoxModel}.
   * 
   * @return A new instance of {@link DirectoryComboBoxModel}.
   */
  protected MetalFileChooserUI.DirectoryComboBoxModel 
      createDirectoryComboBoxModel(JFileChooser fc)
  {
    return new DirectoryComboBoxModel();
  }

  /**
   * Creates a new instance of the renderer used in the directory
   * combo box.
   * 
   * @param fc  the file chooser.
   * 
   * @return The renderer.
   */
  protected DirectoryComboBoxRenderer createDirectoryComboBoxRenderer(
          JFileChooser fc)
  {
    return new DirectoryComboBoxRenderer(fc);
  }

  /**
   * Creates and returns a new instance of {@link FilterComboBoxModel}.
   * 
   * @return A new instance of {@link FilterComboBoxModel}.
   */
  protected FilterComboBoxModel createFilterComboBoxModel()
  {
    return new FilterComboBoxModel();  
  }

  /**
   * Creates and returns a new instance of {@link FilterComboBoxRenderer}.
   * 
   * @return A new instance of {@link FilterComboBoxRenderer}.
   */
  protected MetalFileChooserUI.FilterComboBoxRenderer 
      createFilterComboBoxRenderer()
  {
    return new FilterComboBoxRenderer(); 
  }

  /**
   * Adds the control buttons ('up', 'home' etc.) to the panel.
   */
  protected void addControlButtons()
  {
    JButton upButton = new JButton(getChangeToParentDirectoryAction());
    upButton.setText(null);
    upButton.setIcon(this.upFolderIcon);
    upButton.setMargin(new Insets(0, 0, 0, 0));
    controls.add(upButton);
    
    JButton homeButton = new JButton(getGoHomeAction());
    homeButton.setText(null);
    homeButton.setIcon(this.homeFolderIcon);
    homeButton.setMargin(new Insets(0, 0, 0, 0));
    controls.add(homeButton);
    
    JButton newFolderButton = new JButton(getNewFolderAction());
    newFolderButton.setText(null);
    newFolderButton.setIcon(this.newFolderIcon);
    newFolderButton.setMargin(new Insets(0, 0, 0, 0));
    controls.add(newFolderButton);
    
    JToggleButton listButton = new JToggleButton(this.listViewIcon);
    listButton.setMargin(new Insets(0, 0, 0, 0));
    listButton.addActionListener(new ListViewActionListener());
    listButton.setSelected(true);
    listView = true; 
    controls.add(listButton);
    
    JToggleButton detailButton = new JToggleButton(this.detailsViewIcon);
    detailButton.setMargin(new Insets(0, 0, 0, 0));
    detailButton.addActionListener(new DetailViewActionListener());
    detailButton.setSelected(false);
    controls.add(detailButton);

    ButtonGroup buttonGroup = new ButtonGroup();
    buttonGroup.add(listButton);
    buttonGroup.add(detailButton);
  }
  
  /**
   * Removes all the buttons from the control panel.
   */
  protected void removeControlButtons()
  {
    controls.removeAll();
    controls.revalidate();
    controls.repaint();
  }
  
  /**
   * Updates the current directory.
   * 
   * @param fc  the file chooser to update.
   */
  public void rescanCurrentDirectory(JFileChooser fc)
  {
    directoryModel.setSelectedItem(fc.getCurrentDirectory());
    getModel().validateFileCache();
    if (!listView)
        updateTable();
    else
      createList(fc);
  }
  
  /**
   * Returns the file name in the text field.
   * 
   * @return The file name.
   */
  public String getFileName()
  {
    String result = null;
    if (fileTextField != null) 
      result = fileTextField.getText();
    return result;
  }
  
  /**
   * Sets the file name in the text field.
   * 
   * @param filename  the file name.
   */
  public void setFileName(String filename)
  {
    fileTextField.setText(filename);
  }
  
  /**
   * DOCUMENT ME!!
   * 
   * @param e - DOCUMENT ME!
   */
  public void valueChanged(ListSelectionEvent e)
  {
    // FIXME: Not sure what we should be doing here, if anything.
  }
  
  /**
   * Returns the approve button.
   * 
   * @return The approve button.
   */
  protected JButton getApproveButton(JFileChooser fc)
  {
    return approveButton;
  }

  /**
   * A layout manager that is used to arrange the subcomponents of the
   * {@link JFileChooser}.
   */
  class VerticalMidLayout implements LayoutManager
  {
    /**
     * Performs the layout.
     * 
     * @param parent  the container.
     */
    public void layoutContainer(Container parent) 
    {
      int count = parent.getComponentCount();
      if (count > 0)
        {
          Insets insets = parent.getInsets();
          Component c = parent.getComponent(0);
          Dimension prefSize = c.getPreferredSize();
          int h = parent.getHeight() - insets.top - insets.bottom;
          int adj = Math.max(0, (h - prefSize.height) / 2);
          c.setBounds(insets.left, insets.top + adj, parent.getWidth() 
              - insets.left - insets.right, 
              (int) Math.min(prefSize.getHeight(), h));
        }
    }
    
    /**
     * Returns the minimum layout size.
     * 
     * @param parent  the container.
     * 
     * @return The minimum layout size.
     */
    public Dimension minimumLayoutSize(Container parent) 
    {
      return preferredLayoutSize(parent);
    }
    
    /**
     * Returns the preferred layout size.
     * 
     * @param parent  the container.
     * 
     * @return The preferred layout size.
     */
    public Dimension preferredLayoutSize(Container parent) 
    {
      if (parent.getComponentCount() > 0)
        {
          return parent.getComponent(0).getPreferredSize();
        }
      else return null;
    }
    
    /**
     * This layout manager does not need to track components, so this 
     * method does nothing.
     * 
     * @param name  the name the component is associated with.
     * @param component  the component.
     */
    public void addLayoutComponent(String name, Component component) 
    {
      // do nothing
    }
    
    /**
     * This layout manager does not need to track components, so this 
     * method does nothing.
     * 
     * @param component  the component.
     */
    public void removeLayoutComponent(Component component) 
    {
      // do nothing
    }
  }

  /**
   * A layout manager that is used to arrange buttons for the
   * {@link JFileChooser}.
   */
  class ButtonLayout implements LayoutManager
  {
    static final int GAP = 4;
      
    /**
     * Performs the layout.
     * 
     * @param parent  the container.
     */
    public void layoutContainer(Container parent) 
    {
      int count = parent.getComponentCount();
      if (count > 0)
        {
          // first find the widest button
          int maxW = 0;
          for (int i = 0; i < count; i++)
            {
              Component c = parent.getComponent(i);
              Dimension prefSize = c.getPreferredSize();
              maxW = Math.max(prefSize.width, maxW);
            }
  
          // then position the buttons
          Insets insets = parent.getInsets();
          int availableH = parent.getHeight() - insets.top - insets.bottom;
          int currentX = parent.getWidth() - insets.right;
          for (int i = count - 1; i >= 0; i--)
            {
              Component c = parent.getComponent(i);
              Dimension prefSize = c.getPreferredSize();      
              int adj = Math.max(0, (availableH - prefSize.height) / 2);
              currentX = currentX - prefSize.width;
              c.setBounds(currentX, insets.top + adj, prefSize.width, 
                  (int) Math.min(prefSize.getHeight(), availableH));
              currentX = currentX - GAP;
            }
        }
    }
    
    /**
     * Returns the minimum layout size.
     * 
     * @param parent  the container.
     * 
     * @return The minimum layout size.
     */
    public Dimension minimumLayoutSize(Container parent) 
    {
      return preferredLayoutSize(parent);
    }
    
    /**
     * Returns the preferred layout size.
     * 
     * @param parent  the container.
     * 
     * @return The preferred layout size.
     */
    public Dimension preferredLayoutSize(Container parent) 
    {
      Insets insets = parent.getInsets();
      int maxW = 0;
      int maxH = 0;
      int count = parent.getComponentCount();
      if (count > 0) 
        {
          for (int i = 0; i < count; i++)
            {
              Component c = parent.getComponent(i);
              Dimension d = c.getPreferredSize();
              maxW = Math.max(d.width, maxW);
              maxH = Math.max(d.height, maxH);
            }
        }
      return new Dimension(maxW * count + GAP * (count - 1) + insets.left 
              + insets.right, maxH + insets.top + insets.bottom);
    }
    
    /**
     * This layout manager does not need to track components, so this 
     * method does nothing.
     * 
     * @param name  the name the component is associated with.
     * @param component  the component.
     */
    public void addLayoutComponent(String name, Component component) 
    {
      // do nothing
    }
    
    /**
     * This layout manager does not need to track components, so this 
     * method does nothing.
     * 
     * @param component  the component.
     */
    public void removeLayoutComponent(Component component) 
    {
      // do nothing
    }
  }

}
