| /* BasicDirectoryModel.java -- |
| Copyright (C) 2005, 2006 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.basic; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Vector; |
| import javax.swing.AbstractListModel; |
| import javax.swing.JFileChooser; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.ListDataEvent; |
| import javax.swing.filechooser.FileSystemView; |
| |
| |
| /** |
| * Implements an AbstractListModel for directories where the source |
| * of the files is a JFileChooser object. |
| * |
| * This class is used for sorting and ordering the file list in |
| * a JFileChooser L&F object. |
| */ |
| public class BasicDirectoryModel extends AbstractListModel |
| implements PropertyChangeListener |
| { |
| /** The list of files itself */ |
| private Vector contents; |
| |
| /** |
| * The directories in the list. |
| */ |
| private Vector directories; |
| |
| /** |
| * The files in the list. |
| */ |
| private Vector files; |
| |
| /** The listing mode of the associated JFileChooser, |
| either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */ |
| private int listingMode; |
| |
| /** The JFileCooser associated with this model */ |
| private JFileChooser filechooser; |
| |
| /** |
| * The thread that loads the file view. |
| */ |
| private DirectoryLoadThread loadThread; |
| |
| /** |
| * This thread is responsible for loading file lists from the |
| * current directory and updating the model. |
| */ |
| private class DirectoryLoadThread extends Thread |
| { |
| |
| /** |
| * Updates the Swing list model. |
| */ |
| private class UpdateSwingRequest |
| implements Runnable |
| { |
| |
| private List added; |
| private int addIndex; |
| private List removed; |
| private int removeIndex; |
| private boolean cancel; |
| |
| UpdateSwingRequest(List add, int ai, List rem, int ri) |
| { |
| added = add; |
| addIndex = ai; |
| removed = rem; |
| removeIndex = ri; |
| cancel = false; |
| } |
| |
| public void run() |
| { |
| if (! cancel) |
| { |
| int numRemoved = removed == null ? 0 : removed.size(); |
| int numAdded = added == null ? 0 : added.size(); |
| synchronized (contents) |
| { |
| if (numRemoved > 0) |
| contents.removeAll(removed); |
| if (numAdded > 0) |
| contents.addAll(added); |
| |
| files = null; |
| directories = null; |
| } |
| if (numRemoved > 0 && numAdded == 0) |
| fireIntervalRemoved(BasicDirectoryModel.this, removeIndex, |
| removeIndex + numRemoved - 1); |
| else if (numRemoved == 0 && numAdded > 0) |
| fireIntervalAdded(BasicDirectoryModel.this, addIndex, |
| addIndex + numAdded - 1); |
| else |
| fireContentsChanged(); |
| } |
| } |
| |
| void cancel() |
| { |
| cancel = true; |
| } |
| } |
| |
| /** |
| * The directory beeing loaded. |
| */ |
| File directory; |
| |
| /** |
| * Stores all UpdateSwingRequests that are sent to the event queue. |
| */ |
| private UpdateSwingRequest pending; |
| |
| /** |
| * Creates a new DirectoryLoadThread that loads the specified |
| * directory. |
| * |
| * @param dir the directory to load |
| */ |
| DirectoryLoadThread(File dir) |
| { |
| super("Basic L&F directory loader"); |
| directory = dir; |
| } |
| |
| public void run() |
| { |
| FileSystemView fsv = filechooser.getFileSystemView(); |
| File[] files = fsv.getFiles(directory, |
| filechooser.isFileHidingEnabled()); |
| |
| // Occasional check if we have been interrupted. |
| if (isInterrupted()) |
| return; |
| |
| // Check list for accepted files. |
| Vector accepted = new Vector(); |
| for (int i = 0; i < files.length; i++) |
| { |
| if (filechooser.accept(files[i])) |
| accepted.add(files[i]); |
| } |
| |
| // Occasional check if we have been interrupted. |
| if (isInterrupted()) |
| return; |
| |
| // Sort list. |
| sort(accepted); |
| |
| // Now split up directories from files so that we get the directories |
| // listed before the files. |
| Vector newFiles = new Vector(); |
| Vector newDirectories = new Vector(); |
| for (Iterator i = accepted.iterator(); i.hasNext();) |
| { |
| File f = (File) i.next(); |
| boolean traversable = filechooser.isTraversable(f); |
| if (traversable) |
| newDirectories.add(f); |
| else if (! traversable && filechooser.isFileSelectionEnabled()) |
| newFiles.add(f); |
| |
| // Occasional check if we have been interrupted. |
| if (isInterrupted()) |
| return; |
| |
| } |
| |
| // Build up new file cache. Try to update only the changed elements. |
| // This will be important for actions like adding new files or |
| // directories inside a large file list. |
| Vector newCache = new Vector(newDirectories); |
| newCache.addAll(newFiles); |
| |
| int newSize = newCache.size(); |
| int oldSize = contents.size(); |
| if (newSize < oldSize) |
| { |
| // Check for removed interval. |
| int start = -1; |
| int end = -1; |
| boolean found = false; |
| for (int i = 0; i < newSize && !found; i++) |
| { |
| if (! newCache.get(i).equals(contents.get(i))) |
| { |
| start = i; |
| end = i + oldSize - newSize; |
| found = true; |
| } |
| } |
| if (start >= 0 && end > start |
| && contents.subList(end, oldSize) |
| .equals(newCache.subList(start, newSize))) |
| { |
| // Occasional check if we have been interrupted. |
| if (isInterrupted()) |
| return; |
| |
| Vector removed = new Vector(contents.subList(start, end)); |
| UpdateSwingRequest r = new UpdateSwingRequest(null, 0, |
| removed, start); |
| invokeLater(r); |
| newCache = null; |
| } |
| } |
| else if (newSize > oldSize) |
| { |
| // Check for inserted interval. |
| int start = oldSize; |
| int end = newSize; |
| boolean found = false; |
| for (int i = 0; i < oldSize && ! found; i++) |
| { |
| if (! newCache.get(i).equals(contents.get(i))) |
| { |
| start = i; |
| boolean foundEnd = false; |
| for (int j = i; j < newSize && ! foundEnd; j++) |
| { |
| if (newCache.get(j).equals(contents.get(i))) |
| { |
| end = j; |
| foundEnd = true; |
| } |
| } |
| end = i + oldSize - newSize; |
| } |
| } |
| if (start >= 0 && end > start |
| && newCache.subList(end, newSize) |
| .equals(contents.subList(start, oldSize))) |
| { |
| // Occasional check if we have been interrupted. |
| if (isInterrupted()) |
| return; |
| |
| List added = newCache.subList(start, end); |
| UpdateSwingRequest r = new UpdateSwingRequest(added, start, |
| null, 0); |
| invokeLater(r); |
| newCache = null; |
| } |
| } |
| |
| // Handle complete list changes (newCache != null). |
| if (newCache != null && ! contents.equals(newCache)) |
| { |
| // Occasional check if we have been interrupted. |
| if (isInterrupted()) |
| return; |
| UpdateSwingRequest r = new UpdateSwingRequest(newCache, 0, |
| contents, 0); |
| invokeLater(r); |
| } |
| } |
| |
| /** |
| * Wraps SwingUtilities.invokeLater() and stores the request in |
| * a Vector so that we can still cancel it later. |
| * |
| * @param update the request to invoke |
| */ |
| private void invokeLater(UpdateSwingRequest update) |
| { |
| pending = update; |
| SwingUtilities.invokeLater(update); |
| } |
| |
| /** |
| * Cancels all pending update requests that might be in the AWT |
| * event queue. |
| */ |
| void cancelPending() |
| { |
| if (pending != null) |
| pending.cancel(); |
| } |
| } |
| |
| /** A Comparator class/object for sorting the file list. */ |
| private Comparator comparator = new Comparator() |
| { |
| public int compare(Object o1, Object o2) |
| { |
| if (lt((File) o1, (File) o2)) |
| return -1; |
| else |
| return 1; |
| } |
| }; |
| |
| /** |
| * Creates a new BasicDirectoryModel object. |
| * |
| * @param filechooser DOCUMENT ME! |
| */ |
| public BasicDirectoryModel(JFileChooser filechooser) |
| { |
| this.filechooser = filechooser; |
| filechooser.addPropertyChangeListener(this); |
| listingMode = filechooser.getFileSelectionMode(); |
| contents = new Vector(); |
| validateFileCache(); |
| } |
| |
| /** |
| * Returns whether a given (File) object is included in the list. |
| * |
| * @param o - The file object to test. |
| * |
| * @return <code>true</code> if the list contains the given object. |
| */ |
| public boolean contains(Object o) |
| { |
| return contents.contains(o); |
| } |
| |
| /** |
| * Fires a content change event. |
| */ |
| public void fireContentsChanged() |
| { |
| fireContentsChanged(this, 0, getSize() - 1); |
| } |
| |
| /** |
| * Returns a Vector of (java.io.File) objects containing |
| * the directories in this list. |
| * |
| * @return a Vector |
| */ |
| public Vector getDirectories() |
| { |
| // Synchronize this with the UpdateSwingRequest for the case when |
| // contents is modified. |
| synchronized (contents) |
| { |
| Vector dirs = directories; |
| if (dirs == null) |
| { |
| // Initializes this in getFiles(). |
| getFiles(); |
| dirs = directories; |
| } |
| return dirs; |
| } |
| } |
| |
| /** |
| * Returns the (java.io.File) object at |
| * an index in the list. |
| * |
| * @param index The list index |
| * @return a File object |
| */ |
| public Object getElementAt(int index) |
| { |
| if (index > getSize() - 1) |
| return null; |
| return contents.elementAt(index); |
| } |
| |
| /** |
| * Returns a Vector of (java.io.File) objects containing |
| * the files in this list. |
| * |
| * @return a Vector |
| */ |
| public Vector getFiles() |
| { |
| synchronized (contents) |
| { |
| Vector f = files; |
| if (f == null) |
| { |
| f = new Vector(); |
| Vector d = new Vector(); // Directories; |
| for (Iterator i = contents.iterator(); i.hasNext();) |
| { |
| File file = (File) i.next(); |
| if (filechooser.isTraversable(file)) |
| d.add(file); |
| else |
| f.add(file); |
| } |
| files = f; |
| directories = d; |
| } |
| return f; |
| } |
| } |
| |
| /** |
| * Returns the size of the list, which only includes directories |
| * if the JFileChooser is set to DIRECTORIES_ONLY. |
| * |
| * Otherwise, both directories and files are included in the count. |
| * |
| * @return The size of the list. |
| */ |
| public int getSize() |
| { |
| return contents.size(); |
| } |
| |
| /** |
| * Returns the index of an (java.io.File) object in the list. |
| * |
| * @param o The object - normally a File. |
| * |
| * @return the index of that object, or -1 if it is not in the list. |
| */ |
| public int indexOf(Object o) |
| { |
| return contents.indexOf(o); |
| } |
| |
| /** |
| * Obsoleted method which does nothing. |
| */ |
| public void intervalAdded(ListDataEvent e) |
| { |
| // obsoleted |
| } |
| |
| /** |
| * Obsoleted method which does nothing. |
| */ |
| public void intervalRemoved(ListDataEvent e) |
| { |
| // obsoleted |
| } |
| |
| /** |
| * Obsoleted method which does nothing. |
| */ |
| public void invalidateFileCache() |
| { |
| // obsoleted |
| } |
| |
| /** |
| * Less than, determine the relative order in the list of two files |
| * for sorting purposes. |
| * |
| * The order is: directories < files, and thereafter alphabetically, |
| * using the default locale collation. |
| * |
| * @param a the first file |
| * @param b the second file |
| * |
| * @return <code>true</code> if a > b, <code>false</code> if a < b. |
| */ |
| protected boolean lt(File a, File b) |
| { |
| boolean aTrav = filechooser.isTraversable(a); |
| boolean bTrav = filechooser.isTraversable(b); |
| |
| if (aTrav == bTrav) |
| { |
| String aname = a.getName().toLowerCase(); |
| String bname = b.getName().toLowerCase(); |
| return (aname.compareTo(bname) < 0) ? true : false; |
| } |
| else |
| { |
| if (aTrav) |
| return true; |
| else |
| return false; |
| } |
| } |
| |
| /** |
| * Listens for a property change; the change in file selection mode of the |
| * associated JFileChooser. Reloads the file cache on that event. |
| * |
| * @param e - A PropertyChangeEvent. |
| */ |
| public void propertyChange(PropertyChangeEvent e) |
| { |
| String property = e.getPropertyName(); |
| if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY) |
| || property.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY) |
| || property.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY) |
| || property.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) |
| || property.equals(JFileChooser.FILE_VIEW_CHANGED_PROPERTY) |
| ) |
| { |
| validateFileCache(); |
| } |
| } |
| |
| /** |
| * Renames a file - However, does <I>not</I> re-sort the list |
| * or replace the old file with the new one in the list. |
| * |
| * @param oldFile The old file |
| * @param newFile The new file name |
| * |
| * @return <code>true</code> if the rename succeeded |
| */ |
| public boolean renameFile(File oldFile, File newFile) |
| { |
| return oldFile.renameTo( newFile ); |
| } |
| |
| /** |
| * Sorts a Vector of File objects. |
| * |
| * @param v The Vector to sort. |
| */ |
| protected void sort(Vector v) |
| { |
| Collections.sort(v, comparator); |
| } |
| |
| /** |
| * Re-loads the list of files |
| */ |
| public void validateFileCache() |
| { |
| File dir = filechooser.getCurrentDirectory(); |
| if (dir != null) |
| { |
| // Cancel all pending requests. |
| if (loadThread != null) |
| { |
| loadThread.interrupt(); |
| loadThread.cancelPending(); |
| } |
| loadThread = new DirectoryLoadThread(dir); |
| loadThread.start(); |
| } |
| } |
| } |
| |