package kiyut.swing.shell.shelllistview; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import javax.swing.ListModel; import javax.swing.event.EventListenerList; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.TableModelEvent; import javax.swing.filechooser.FileSystemView; import javax.swing.table.AbstractTableModel; import kiyut.swing.shell.io.DirectoryFilter; import kiyut.swing.shell.io.FileOnlyFilter; import kiyut.swing.shell.io.ShellFilter; /** The data model for {@code ShellListView} * The default values for fileHidingEnabled is false and sortBy is name. * to know when the refresh / change directory occur listen for {@code ListModel.contentsChanged()} * where {@code ListDataEvent.getIndex0()} and {@code ListDataEvent.getIndex1()} equal with {@code ShellListViewModel.ALL_INDEX} * * @version 1.0 * @author tonny */ public class ShellListViewModel extends AbstractTableModel implements ListModel, Serializable { /** the all index */ public static final int ALL_INDEX = TableModelEvent.HEADER_ROW; /** the name column */ public static final int NAME = 0; /** the size column */ public static final int SIZE = 1; /** the type column */ public static final int TYPE = 2; /** the modified column */ public static final int MODIFIED = 3; /** the browse state */ public static final int BROWSE = 0; /** the edit state */ public static final int EDIT = 1; /** the delete state */ public static final int DELETE = 2; /** the column names for this model */ protected String[] columnNames = {"Name","Size","Type","Modified"}; /** the data for this model */ protected List<File> data; /** list data listener */ protected EventListenerList listDataListeners; /** the <code>FileSystemView</code> for this model */ protected FileSystemView fsv; /** the use file hiding for this model */ protected boolean useFileHiding; /** the sortBy use by this model */ protected int sortBy; /** the state for this model */ protected int state; /** the current directory of this model */ protected File directory; /** Constructs a <code>ShellListViewModel</code> pointing to the user's default directory. */ public ShellListViewModel() { this(FileSystemView.getFileSystemView()); } /** Constructs a <code>ShellListViewModel</code> using the given <code>FileSystemView</code>. * @param fsv FileSystemView */ public ShellListViewModel(FileSystemView fsv) { this(fsv.getHomeDirectory(),fsv,false,NAME); } /** Constructs a <code>ShellListViewModel</code> using the given directory * and <code>FileSystemView</code> and initialize it with <code>showHidden</code> * and <code>sortBy</code> parameter * @param directory directory to point to * @param fsv FileSystemView * @param useFileHiding the boolean value that determines whether file hiding is turned on * @param sortBy sort the model using */ public ShellListViewModel(File directory, FileSystemView fsv, boolean useFileHiding, int sortBy) { this.fsv = fsv; this.directory = directory; this.useFileHiding = useFileHiding; this.sortBy = sortBy; listDataListeners = new EventListenerList(); data = Collections.synchronizedList(new ArrayList<File>()); } /** {@inheritDoc} */ public int getSize() { return getRowCount(); } /** {@inheritDoc} */ public int getColumnCount() { return columnNames.length; } /** {@inheritDoc} */ @Override public String getColumnName(int col) { return columnNames[col]; } /** {@inheritDoc} */ public int getRowCount() { return data.size(); } /** {@inheritDoc} */ public Object getValueAt(int row, int col) { Object object = null; File file = data.get(row); switch(col) { case NAME: object = file; break; case SIZE: if (fsv.isTraversable(file).booleanValue()) { object= new Long(0); } else { object = new Long(file.length()); } break; case TYPE: String str = fsv.getSystemTypeDescription(file); if (str == null) { str = ""; } object = str; break; case MODIFIED: object = new Date(file.lastModified()); break; } return object; } /** * Returns the value at the specified index. * <blockquote> * <b>Note:</b> Although this method is not deprecated, the preferred * method to use is <code>getFile(int)</code> or <code>getValueAt(int,int)</code> * </blockquote> * @param index the index being queried * @return the value at the specified index * @exception IndexOutOfBoundsException if an invalid index was given * @see #getFile(int) * @see #getValueAt(int,int) */ public Object getElementAt(int index) { return getValueAt(index, 0); } /** Return the file at specified index * @param index - the index being queried * @return file at specified index * @throws IndexOutOfBoundsException if an invalid index or column was given * @see #getElementAt(int) * @see #getValueAt(int,int) */ public File getFile(int index) { return (File)getValueAt(index,0); } /** Refresh the data model */ public synchronized void refresh() { data.clear(); // Group the folder & file File[] fileArray = fsv.getFiles(directory,useFileHiding); List<File> dirs = ShellFilter.filter(new DirectoryFilter(fsv),fileArray); List<File> files = ShellFilter.filter(new FileOnlyFilter(fsv),fileArray); switch (sortBy) { case NAME: Collections.sort(dirs); Collections.sort(files); break; case SIZE: Collections.sort(dirs,new FileSizeComparator()); Collections.sort(files,new FileSizeComparator()); break; case TYPE: Collections.sort(dirs,new FileTypeComparator()); Collections.sort(files,new FileTypeComparator()); break; case MODIFIED: Collections.sort(dirs,new FileModifiedComparator()); Collections.sort(files,new FileModifiedComparator()); break; } data.addAll(dirs); data.addAll(files); fireTableDataChanged(); fireContentsChanged(ALL_INDEX,ALL_INDEX); } /** Rename file at specified index with the specied filename. * The filename string will be appended with the current directory * this model point to * @param index the index being queried * @param filename the new filename * @return true if and only if the renaming succeeded; false otherwise * @throws SecurityException If a security manager exists and its SecurityManager.checkDelete(java.lang.String) method denies delete access to the file * @exception IndexOutOfBoundsException if an invalid index was given */ public synchronized boolean renameFile(int index, String filename) throws SecurityException { boolean b = false; File file = getFile(index); File dest = new File(getCurrentDirectory() + File.separator + filename); b = file.renameTo(dest); if (b == true) { data.set(index,dest); fireTableRowsUpdated(index,index); fireContentsChanged(index,index); } return b; } /** Removes the element at the specified position in this model. * @param index the index of the element to removed * @return Returns the element that was removed from the model. */ public Object remove(int index) { Object object = data.remove(index); fireTableRowsDeleted(index,index); fireIntervalRemoved(index, index); return object; } /** Searches for the first occurence of the given argument, testing for equality using the equals method. * @param elem an object. * @return the index of the first occurrence of the argument in this model; returns -1 if the object is not found. */ public int indexOf(Object elem) { return data.indexOf(elem); } /** Returns true if the file (directory) can be visited. Returns false if the directory cannot be traversed. * @param index the index being queried * @return true if the file/directory can be traversed, otherwise false */ public boolean isTraversable(int index) { return fsv.isTraversable(getFile(index)).booleanValue(); } /** Returns true if hidden files are not shown ; otherwise, returns false. * @return the status of the file hiding * * @see #setFileHidingEnabled(boolean) */ public boolean isFileHidingEnabled() { return this.useFileHiding; } /** Sets file hiding on or off. If true, hidden files are not shown. * @param useFileHiding the boolean value that determines whether file hiding is turned on * * @see #isFileHidingEnabled() */ public void setFileHidingEnabled(boolean useFileHiding) { this.useFileHiding = useFileHiding; } /** return the sort by value * @return the status of sort by */ public int getSortBy() { return this.sortBy; } /** Sets the sort by used by this model * @param sortBy the value for sorting this model */ public void setSortBy(int sortBy) { this.sortBy = sortBy; } /** Returns the current directory. * @return the current directory * * @see #setCurrentDirectory(java.io.File) */ public File getCurrentDirectory() { return this.directory; } /** Sets the current directory. Passing in <code>null</code> sets the <code>ShellListView</code> * to point to the user's default directory. * This default depends on the operating system. It is typically the "My Documents" folder on Windows, * and the user's home directory on Unix. * @param directory the current directory to point to * * @see #getCurrentDirectory() */ public void setCurrentDirectory(File directory) { if (directory == null) { this.directory = fsv.getHomeDirectory(); } else { this.directory = directory; } } /** Returns the file system view. * @return the <code>FileSystemView</code> object * * @see #setFileSystemView(javax.swing.filechooser.FileSystemView) */ public FileSystemView getFileSystemView() { return fsv; } /** Sets the file system view that this model uses for accessing and creating file system resources, * such as finding the floppy drive and getting a list of root drives. * @param fsv the new FileSystemView * * @see #getFileSystemView */ public void setFileSystemView(FileSystemView fsv) { this.fsv = fsv; } /** return this model state * @return One of the following constants defined in <code>ShellListViewModel</code>: BROWSE, EDIT, DELETE * * @see #setState(int) */ public int getState() { return this.state; } /** Sets this model state * @param state One of the following constants defined in <code>ShellListViewModel</code>: BROWSE, EDIT, DELETE * * @see #getState() */ public void setState(int state) { this.state = state; } /** * Adds a listener to the list that's notified each time a change * to the data model occurs. * * @param l the <code>ListDataListener</code> to be added */ public void addListDataListener(ListDataListener l) { listDataListeners.add(ListDataListener.class, l); } /** * Removes a listener from the list that's notified each time a * change to the data model occurs. * * @param l the <code>ListDataListener</code> to be removed */ public void removeListDataListener(ListDataListener l) { listDataListeners.remove(ListDataListener.class, l); } /** Notifies all listeners that data in the range [index0, index1], inclusive, have been changed. * @param index0 one end of the new interval * @param index1 the other end of the new interval * @see EventListenerList */ public void fireContentsChanged(int index0, int index1) { Object[] listeners = listDataListeners.getListenerList(); ListDataEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index0, index1); } ((ListDataListener)listeners[i+1]).contentsChanged(e); } } } /** Notifies all listeners that data in the range [index0, index1], inclusive, have been inserted. * @param index0 one end of the new interval * @param index1 the other end of the new interval * @see EventListenerList */ public void fireIntervalAdded(int index0, int index1) { Object[] listeners = listDataListeners.getListenerList(); ListDataEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index0, index1); } ((ListDataListener)listeners[i+1]).intervalAdded(e); } } } /** Notifies all listeners that data in the range [index0, index1], inclusive, have been deleted. * @param index0 one end of the new interval * @param index1 the other end of the new interval * @see EventListenerList */ public void fireIntervalRemoved(int index0, int index1) { Object[] listeners = listDataListeners.getListenerList(); ListDataEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index0, index1); } ((ListDataListener)listeners[i+1]).intervalRemoved(e); } } } /** use to compare file based on size */ private class FileSizeComparator implements Comparator<File> { public int compare(File file1, File file2) { int comp; if (file1.length() < file2.length()) { comp = -1; } else if (file1.length() == file2.length()) { comp = file1.compareTo(file2); } else { comp = 1; } return comp; } } /** use to compare file based on size */ private class FileTypeComparator implements Comparator<File> { public int compare(File file1, File file2) { int comp; String desc1 = fsv.getSystemTypeDescription(file1); String desc2 = fsv.getSystemTypeDescription(file2); if (desc1 == null) { desc1 = ""; } if (desc2 == null) { desc2 = ""; } comp = desc1.compareTo(desc2); if (comp == 0) { comp = file1.compareTo(file2); } return comp; } } /** use to compare file based on last modified */ private class FileModifiedComparator implements Comparator<File> { public int compare(File file1, File file2) { int comp; if (file1.lastModified() < file2.lastModified()) { comp = -1; } else if (file1.lastModified() == file2.lastModified()) { comp = file1.compareTo(file2); } else { comp = 1; } return comp; } } }