/*
 * @(#) FileBrowser.java 1.0 2001/21/1 
 * Copyright (c) 2001 Lawrence Rodrigues
 */
package com.vistech.listselect;
import java.io.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.awt.*;
import com.vistech.events.*;
import java.util.*;
import javax.swing.table.*;

/** The FileBrowser Class is a JavaBean that creates a embeddable panel to browse local directories.
  * It uses the JTree component to navigate through the file folders and uses JTable to list
  * the files in a directory. When a file is selected, it constructs a ListSelectEvent object with
  * selected filename and the path and sends it to the registered listeners.  
  * The receiver of this event can extract this info and use it to load that file, for instance, an Image.
  * Here is an example:
  * <pre>
  *       FileBrowser flBrowser = new FileBrowser();
  *       flbrowser.addListSelectListener (
  *            new ListSelectListener() {
  *                public void load(ListSelectEvent e){
  *                   File currentDir = e.getPath();
  *                   String[] flist = e.getFileList();
  *                    String path =
  *                    (currentDir !=null)? currentDir.toString()+File.separator+flist[0]:flist[0];
  *                   if(flist != null) loadImage(path);
  *                 }
  *             }
  *
  *        );
  * </pre>
  * Note that the list select event is a custom event defined in the com.vistech.events package.
  * If you are using the Visualization Framework, you can easily connect image loading beans such as
  * ImageLoaderJAI to the FileBrowser bean. 
  *
  * @version 2.0,   21 Jan 2001
  * @author Lawrence Rodrigues
  * @see com.vistech.events.ListSelectListener 
  * @see com.vistech.events.ListSelectEvent
  **/
public class FileBrowser extends JPanel{
   public final static int SORT_BY_DATE =2;
   public final static int SORT_BY_ALPHA = 1;
   protected static int[] sortModes = {SORT_BY_ALPHA, SORT_BY_DATE};
   protected static String[] sortModesString = {"BY_ALPHA", "BY_DATE"}; 

   protected int currentSortMode = SORT_BY_ALPHA;
   protected boolean multiSelect = false;
   protected String[] fileList;
   protected JTree tree;
   protected String curDir = ".";
   protected String topNode= ".";
   transient protected File currentDir;
   protected char separator = File.separatorChar;
   protected JTextField dirField, filterField;
   protected JButton upButton;
   protected String filterString = ".jpg,.jpeg,.gif,.bmp,.png,.pnm,.pbm,.bmp,.tif,.tiff";
   protected String fString[] = {".jpg",".jpeg", ".gif",".tif",".tiff", ".bmp", ".png", ".fpx", ".ppm", ".pnm",".pbm"};
   protected JScrollPane treeView;
   transient private Vector listSelListeners = new Vector();
   protected int selectionMode = ListSelectionModel.SINGLE_SELECTION;
   protected String[][] fileInfoList = new String[1][3];
   protected String columnNames[] = {"File", "Size"};
   protected JTable fileInfoTable;
   transient protected File roots[];
   protected JComboBox rootBox, sortBox;
   protected JCheckBox multiSelectBox;
   protected JScrollPane jsp;
  
   /** FileBrowser constructor.
     */
   public FileBrowser(){
      try{
        currentDir = new File(".");
        curDir = currentDir.getAbsolutePath();
        topNode = curDir;
        roots = File.listRoots();
      }catch (Exception e){}
      createUI();
   }

   /** Sets the path
     * @param the path.
     */
   public void setPath(File path){currentDir = path;}

   /** @return the current directory
     */
   public File gePath(){ return currentDir;}

   /** Sets the filter string.
     * @param filter the filter string.
     */
   public void setFilterString(String filter){
      filterString = filter;
      filterField.setText(filterString);
      updateFilterString(filterString);
   }

   /** @return the filter string.
      */
   public String getFilterString() {
      return filterString;
   }

   public void setMultiSelect(boolean multiSelect){
      this.multiSelect = multiSelect;
      ListSelectionModel lsm = fileInfoTable.getSelectionModel();
      if(multiSelect){
        lsm.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
      }else {
        lsm.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      }
   }

   public boolean getMultiSelect(){
      return multiSelect;
   }


   /** Creates the UI components and lays them out
     **/
   public void createUI (){
      JPanel pan = new JPanel();
      JPanel dirP = new JPanel();
      JLabel dirL = new JLabel("Directory ", SwingConstants.RIGHT);
      dirField = new JTextField(15);
      if(curDir != null) dirField.setText(curDir);

      JPanel filtP= new JPanel();
      JLabel filtL = new JLabel("Filter ", SwingConstants.RIGHT);

      filterField = new JTextField(15);
      filterField.setText(filterString);
      filtP.add(filtL);
      filtP.add(filterField);

      //JPanel multiP= new JPanel();
      //JLabel multiL = new JLabel("Multi Select", SwingConstants.RIGHT);
      multiSelectBox = new JCheckBox("Multi Select",false);
      //multiP.add(multiL);
      //multiP.add(multiSelectBox);

      sortBox = new JComboBox();
      for(int i=0;i<sortModes.length;i++){
          sortBox.addItem(sortModesString[i]);
      }


      upButton = new JButton("^");
      rootBox = new JComboBox();
      for(int i=0;i<roots.length;i++){
          rootBox.addItem(roots[i].getPath());
      }
    
      tree = createTreeView();
      treeView = new JScrollPane(tree);
      treeView.setPreferredSize(new Dimension(300,600));
      treeView.setBackground(Color.gray);

      GridBagLayout  gb = new GridBagLayout() ;
      GridBagConstraints c = new GridBagConstraints();
      setLayout(gb);
      c.weightx = 0.2; c.weighty = 0.1;
      c.fill =  GridBagConstraints.BOTH;
      c.insets = new Insets(5,1, 1,1);
     
      c.insets = new Insets(5,1, 1,1);
      JLabel sortLabel = new JLabel("Sort", SwingConstants.RIGHT);
      gb.setConstraints(sortLabel, c);
      add(sortLabel);
      c.gridwidth = 1;

      gb.setConstraints(sortBox, c);
      add(sortBox);
      c.gridwidth =  GridBagConstraints.REMAINDER;
      gb.setConstraints(multiSelectBox, c);
      add(multiSelectBox);
      c.gridwidth = 1;
      gb.setConstraints(filtL, c);
      add(filtL);
      c.gridwidth =  GridBagConstraints.REMAINDER;
      c.weightx = 0.0; c.weighty = 0.1;
      gb.setConstraints(filterField, c);
      add(filterField);
      c.insets = new Insets(1,1, 1,1);
      c.gridwidth = 1;
      c.weightx = 0.2; c.weighty = 0.1;
      gb.setConstraints(dirL, c);
      add(dirL);
      c.insets = new Insets(1,1, 1,1);
      c.weightx = 1.0; c.weighty = 0.1;

      gb.setConstraints(dirField, c);
      add(dirField);
      c.weightx = 0.2; c.weighty = 0.1;
      gb.setConstraints(rootBox, c);
      add(rootBox);

      c.gridwidth =  GridBagConstraints.REMAINDER;
      gb.setConstraints(upButton, c);
      add(upButton);
      c.fill =  GridBagConstraints.BOTH;
      c.weightx = 1.0; c.weighty = 5.0;
      fileInfoTable = createFileInfoTable();
      clickToLoad(fileInfoTable);
      fileInfoTable.setPreferredSize(new Dimension(70,4000));
      jsp = new JScrollPane(fileInfoTable);
      jsp.setPreferredSize(new Dimension(60,600));
      JSplitPane vsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeView,jsp);
      gb.setConstraints(vsp, c);
      add(vsp);
      vsp.setDividerLocation(120);
      vsp.setDividerSize(3);
      handleEvents();
      revalidate();
   }

   protected void handleEvents() {
      filterField.addActionListener(
            new ActionListener(){
               public void actionPerformed(ActionEvent e){
                  String filterString = ((JTextField)(e.getSource())).getText();
                  if(filterString == null) return;
                  updateFilterString(filterString);
               }
            }
      );

      dirField.addActionListener(
            new ActionListener(){
               public void actionPerformed(ActionEvent e){
                  String currentDirStr = ((JTextField)(e.getSource())).getText();
                  if(currentDirStr != null){
                     curDir = currentDirStr;
                     try {
                       currentDir = new File(curDir);
                       topNode = curDir;
                       updateTreeView(curDir);
                       treeView.revalidate();
                       FileBrowser.this.revalidate();
                    } catch(Exception e1) {}
                  }
               }
            }
      );
      upButton.addActionListener(
            new ActionListener(){
               public void actionPerformed(ActionEvent e){
                  try {
                    File dir = new File(topNode);
                    String currentDirStr = dir.getParent();
                    curDir = currentDirStr;
                    currentDir = new File(curDir);
                    topNode = curDir;
                    dirField.setText(curDir);
                    updateTreeView(curDir);
                    treeView.revalidate();
                    FileBrowser.this.revalidate();
                 }catch (Exception e1) {}
               }
            }
      );

      rootBox.addItemListener(
         new ItemListener(){
            public void itemStateChanged(ItemEvent e){
               JComboBox jcb = (JComboBox)e.getSource();
               File currentRootFile = roots[jcb.getSelectedIndex()];
                try {
                    curDir = currentRootFile.getPath();
                    currentDir = currentRootFile;
                    topNode = curDir;
                    dirField.setText(curDir);
                    updateTreeView(curDir);
                    treeView.revalidate();
                    FileBrowser.this.revalidate();
               }catch (Exception e1) {}
            }
         }
     );

    rootBox.addActionListener(
         new ActionListener(){
            public void actionPerformed(ActionEvent e){
               JComboBox jcb = (JComboBox)e.getSource();
               File currentRootFile = roots[jcb.getSelectedIndex()];
                try {
                    curDir = currentRootFile.getPath();
                    currentDir = currentRootFile;
                    topNode = curDir;
                    dirField.setText(curDir);
                    updateTreeView(curDir);
                    treeView.revalidate();
                    FileBrowser.this.revalidate();
               }catch (Exception e1) {}
            }
         }
     );

     sortBox.addActionListener(
         new ActionListener(){
            public void actionPerformed(ActionEvent e){
               JComboBox jcb = (JComboBox)e.getSource();
               currentSortMode = sortModes[jcb.getSelectedIndex()];
                try {
                    fileInfoList = sortFileList(curDir, fileInfoList, currentSortMode);
                    fileInfoTable.repaint();
               }catch (Exception e1) {}
            }
         }
     );


    multiSelectBox.addActionListener(
         new ActionListener(){
            public void actionPerformed(ActionEvent e){
               JCheckBox jcb = (JCheckBox)e.getSource();
               try {
                  boolean state = jcb.isSelected();
                  setMultiSelect(state);
               }catch (Exception e1) {}
            }
         }
     );


   }

   protected void updateFilterString(String fStr){
      StringTokenizer st = new StringTokenizer(fStr, ",");
      int num = st.countTokens();
      fString = new String[num];
      int i=0;
      while(st.hasMoreTokens()){
            String str = st.nextToken();
            String name = str.trim();
            fString[i++] = name;
      }
      updateTreeView(topNode);
      treeView.revalidate();
      FileBrowser.this.revalidate();
   }

   protected JTree createTreeView() {
      String[] filelist = listFilteredFiles(curDir, fString);
      if(filelist == null) return null;
      DefaultMutableTreeNode top = new DefaultMutableTreeNode(curDir);
      createNewNodes(top, filelist);

      JTree tree = new JTree(top);
      tree.getSelectionModel().setSelectionMode
                           (TreeSelectionModel.SINGLE_TREE_SELECTION);
      tree.addTreeSelectionListener(
          new TreeSelectionListener() {
             public void valueChanged(TreeSelectionEvent e) {
                TreePath selpath = e.getPath();
                expandNode(selpath);
             }
         });
       return tree;
   }

   protected JTree newTreeView(String curDir) {
       String[] filelist = listFilteredFiles(curDir, fString);
       DefaultMutableTreeNode top = new DefaultMutableTreeNode(curDir);
       createNewNodes(top, filelist);

       JTree tree = new JTree(top);
       tree.getSelectionModel().setSelectionMode
                            (TreeSelectionModel.SINGLE_TREE_SELECTION);
       tree.addTreeSelectionListener(
             new TreeSelectionListener() {
             public void valueChanged(TreeSelectionEvent e) {
                 
                 TreePath selpath = e.getPath();
                 expandNode(selpath);
              }
       });
       tree.revalidate();
       return tree;
   }

   protected void updateTreeView(String curDir) {
       String[] filelist = listFilteredFiles(curDir, fString);
       DefaultMutableTreeNode top = new DefaultMutableTreeNode(curDir);
       createNewNodes(top, filelist);
       DefaultTreeModel dft = new DefaultTreeModel(top);
       tree.setModel(dft);
   }

   private void createNewNodes(DefaultMutableTreeNode top, String[] flist) {
       if((top == null) || (flist == null)) return;
       DefaultMutableTreeNode file = null;
       for(int i=0; i< flist.length;i++){
           try{
             File fl = new File(curDir+File.separator+flist[i]);
             if(fl.isDirectory()) {
                file = new DefaultMutableTreeNode(flist[i]);
                top.add(file);
             }
             else {
                //System.out.println("local dir = "+curDir+ " fname="+flist[i]);
                //file = new DefaultMutableTreeNode(flist[i]);
             }
            
           } catch(Exception e){
               System.out.println(e);
               e.printStackTrace();
           }
       }
   }

   private void expandNode(TreePath selpath) {
      DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                                              (selpath.getLastPathComponent());
      Object[] pathobj = selpath.getPath();
      if(pathobj == null) return;
      StringBuffer path=null;
      for(int i=0;i<pathobj.length;i++){
          if(path== null) path= new StringBuffer(pathobj[0].toString());
          else path.append(pathobj[i].toString());
          if(i != pathobj.length-1) path.append(separator);
          curDir = path.toString();
          try {
            currentDir = new File(curDir);
            topNode = curDir;
            dirField.setText(path.toString());
          } catch(Exception e){}
      }
      try {
            String filename = path.toString();
            long len = path.length();
            if(isDirectory(path.toString())){
               tree.collapsePath(selpath);
               curDir = path.toString();
               File dir = new File(curDir);
               currentDir = dir;
               File[] flist = dir.listFiles();
               if(flist == null) return;
               String[] filelist = filterFileNames(flist, fString);
               if(filelist == null) return;
               createNewNodes(node,filelist);
            } else {
               int[] indices = fileInfoTable.getSelectedRows();
               String filenames[] = new String[indices.length];
               for(int i=0;i<indices.length;i++){
                   filenames[i] = fileInfoList[indices[i]][0];
                   //System.out.println(" first = "+filenames[i]);
               }
               ListSelectEvent ls = new ListSelectEvent(
                                         this,
                                         null,
                                         null,
                                         filenames);
                                          // new String[] {filename});
               fireListSelectEvent(ls);
            }
         
      } catch (Exception e){System.out.println(e);}
   }

   protected JTable createFileInfoTable(){
      TableModel dataModel = new AbstractTableModel() {
        public String getColumnName(int column) {
           return  columnNames[column];
        }
        public int getColumnCount() {
           return columnNames.length;
        }
        public int getRowCount() {
           if(fileInfoList == null) return 0;
           return fileInfoList.length;
        }
        public Object getValueAt(int row, int col) {
           if(fileInfoList == null) return null;
           if((row >= fileInfoList.length)||(col >= fileInfoList[0].length)) return null;
           return fileInfoList[row][col];
       }
      };
      JTable table = new JTable(dataModel);
      return new JTable(dataModel);
   }

   protected void clickToLoad(JTable tb){
      final JTable table = tb;
      
      ListSelectionModel lsm = table.getSelectionModel();
      lsm.setSelectionMode(selectionMode);
      /*
      lsm.addListSelectionListener(
             new ListSelectionListener() {
               public void valueChanged(ListSelectionEvent e){
                
                  int[] indices = fileInfoTable.getSelectedRows();
                  if(indices == null) return;
                  String filenames[] = new String[indices.length];
                  for(int i=0;i<indices.length;i++){
                      filenames[i] = fileInfoList[indices[i]][0];
                      System.out.println(filenames[i]);
                  }
                  
                  ListSelectEvent ls = new ListSelectEvent(
                                         this,
                                         null,
                                         currentDir,
                                         filenames);
                  fireListSelectEvent(ls);
               }
            });
      
      */
      table.addMouseListener( 
         new MouseAdapter() {
             public void mousePressed(MouseEvent e) {
                //if (e.getClickCount() == 2) {
                    int[] indices = fileInfoTable.getSelectedRows();
                    if(indices == null) return;
                    String filenames[] = new String[indices.length];
                  
                    for(int i=0;i<indices.length;i++){
                      filenames[i] = fileInfoList[indices[i]][0];
                      //System.out.println(filenames[i]);
                    }
                  
                    ListSelectEvent ls = new ListSelectEvent(
                                         this,
                                         null,
                                         currentDir,
                                         filenames);
                     fireListSelectEvent(ls);
              //}
         }
       });

   }

   /** Lists the filtered files
     * @param dir the current directory.
     * @param filt the array containing list of file extendsions.
     * @return the array containing the list of files. 
     */
   public String[] listFilteredFiles(String dir, String[] filt){
      try {
        File direct = new File(dir);
        File[] flist = direct.listFiles();
        if(flist == null) return null;
        String[] ftlist = filterFileNames(flist, filt);
        return ftlist;
      } catch (Exception e) {return null;}
   }

   protected String[] filterFileNames(File filelist[], String fstr[]){
      Vector fl = new Vector();
      Vector finfo = new Vector();
      int j =0;
      int numfiles = filelist.length;

      for(int i=0; i< filelist.length;i++){
          try {
            if(filelist[i].isDirectory()) {
               fl.add(filelist[i].getName()); j++;
               continue;
            }
          } catch (Exception e) {continue;}
          String fi[] = new String[2];
          if((fstr == null) || (fstr.length <1)){
              fl.add(filelist[i].getName());
              fi = getFileInfo(filelist[i]);
              finfo.add(fi);
          }else{
              for(int k =0; k< fstr.length;k++){
                  if(filelist[i].getName().endsWith(fstr[k])){
                     fl.add(filelist[i].getName());
                     fi = getFileInfo(filelist[i]);
                     finfo.add(fi);
                     j++;
                     break;
                  }
              }
          }
      }
      repaint();
      int size = fl.size();
      if(size == 0) {
         fileInfoList = null;
         return null;
      }
        
      String[] newfl = new String[size];
      size = finfo.size();
      
      fl.copyInto(newfl);
      fileInfoList = new String[size][3];
      finfo.copyInto(fileInfoList);
      fileInfoList = sortFileList(curDir, fileInfoList, currentSortMode);
      //fileInfoTable.setPreferredSize(new Dimension(30, (int)fileInfoTable.getRowHeight()*fileInfoList.length));
      //treeView.revalidate();
      //jsp.revalidate();
      return newfl;
   }

   private String[] getFileInfo(File file) {
      String[] info = new String[3];
      info[0] = file.getName();
      long len = file.length();
      int size = (int)(len/1000);
      info[1] = Integer.toString(size)+"k";
      return info;           
  }

   public static String[] listFiles(String userDir) {
      String fl[] = null;
      try {
         File dir = new File(userDir);
         fl = dir.list();
      } catch (Exception e){System.out.println(e);}
      return fl;
   }

   public static boolean isDirectory(String file){
      try{
        File fl= new File(file);
        if(fl.isDirectory()) return true;
      } catch (Exception e){System.out.println(e);}
      return false;
   }

   private void doubleClick(int selrow, TreePath selpath) {
       if(!tree.isCollapsed(selpath)) tree.collapsePath(selpath);
       else expandNode(selpath);
   }

   private void singleClick(int selrow, TreePath selpath) {
       if(tree.isCollapsed(selpath)) expandNode(selpath);
   }


   public String[][] sortFileList(String curDir, String[][] flist,
                                      int sortMode){
        final int sort = sortMode;
        String fileList[][] = null;
        try{
            String name = null;
            long date;
            Comparator cmp = new Comparator(){
                public int compare(Object aa, Object bb){
                   FileAttributes faa = (FileAttributes) aa;
                   FileAttributes fab = (FileAttributes) bb;
                   if((aa==null)|| (bb==null)) return -1;
                   switch(sort) {
                     case SORT_BY_DATE :
                       if(faa.date > fab.date) return 1;
                       else if(faa.date == fab.date){
                               if(faa.index > fab.index) return 1;
                               else return -1;
                             } else  return -1;

                     case SORT_BY_ALPHA :
                       return faa.name.compareTo(fab.name);
                     default:
                       return -1;
                   }
                }
            };

            TreeSet tset = new TreeSet(cmp);
            int ind =0;
            long lmod;
            for(int i=0; i< flist.length;i++) {
                FileAttributes attr = null;
                try{ 
                  File file = new File(curDir+File.separator+flist[i][0]);
                  name = flist[i][0];
                  lmod = file.lastModified();
                  attr = new FileAttributes(ind++,lmod,name,flist[i][1]);

                } finally{
                  if(attr != null) tset.add(attr);
                }

            }
            if(tset == null) return null;
            Iterator it = tset.iterator();
            fileList= new String[tset.size()][3];
            int i=0;
            while(it.hasNext()){
                 FileAttributes en = (FileAttributes)(it.next());
                 fileList[i][0] = (String)(en.name);
                 fileList[i++][1] = (String)en.size;
            }
        } catch (Exception e){ System.out.println( e);}
            return fileList;
     }


     static class FileAttributes {
        public int index=0;
        public long date=0;
        public String name = null;
        public String size= "0";
        public FileAttributes(int ind, long dat, String nam, String size){
           index =ind;
           date =dat;
           name = nam;
           this.size = size;
        }
     }


     static class DateKey {
        public int index=0;
        public long date=0;
        public DateKey(int ind, long dat){
           index =ind;
           date =dat;
        }
     }


   public void addListSelectListener(ListSelectListener il){
       listSelListeners.addElement(il);
   }

   public void removeListSelectListener(ListSelectListener il){
      if(!(listSelListeners.isEmpty())) listSelListeners.removeElement(il);
   }

   protected void fireListSelectEvent(ListSelectEvent lse){
      if(listSelListeners.isEmpty()) return;
      Vector lstn = new Vector();
      synchronized(this){
          lstn = (Vector)listSelListeners.clone();
      }
      for(Enumeration e=lstn.elements();e.hasMoreElements();){
          ((ListSelectListener)(e.nextElement())).load(lse);
      }
   }

   public static void main(String args[]){
      FileBrowser fc = new FileBrowser();
      JFrame fr = new JFrame();
      fr.addWindowListener(
         new WindowAdapter() {
            public void windowClosing(WindowEvent e){
               System.exit(0);
            }
         } );
      fr.getContentPane().setLayout(new GridLayout(1,1));
      fr.getContentPane().add(fc);
      fr.pack();
      fr.setSize(400,600);
      fr.show();
  }
}
