mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-11 19:49:32 +00:00
587 lines
16 KiB
Java
587 lines
16 KiB
Java
/* 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.Collections;
|
|
import java.util.Comparator;
|
|
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<File> 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<File> 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<? extends File> 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();
|
|
}
|
|
}
|
|
}
|