|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package javax.swing.plaf.basic; |
|
|
|
import java.io.File; |
|
import java.util.*; |
|
import java.util.concurrent.Callable; |
|
import javax.swing.*; |
|
import javax.swing.filechooser.*; |
|
import javax.swing.event.*; |
|
import java.beans.*; |
|
|
|
import sun.awt.shell.ShellFolder; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class BasicDirectoryModel extends AbstractListModel<Object> implements PropertyChangeListener { |
|
|
|
private JFileChooser filechooser = null; |
|
|
|
private Vector<File> fileCache = new Vector<File>(50); |
|
private LoadFilesThread loadThread = null; |
|
private Vector<File> files = null; |
|
private Vector<File> directories = null; |
|
private int fetchID = 0; |
|
|
|
private PropertyChangeSupport changeSupport; |
|
|
|
private boolean busy = false; |
|
|
|
public BasicDirectoryModel(JFileChooser filechooser) { |
|
this.filechooser = filechooser; |
|
validateFileCache(); |
|
} |
|
|
|
public void propertyChange(PropertyChangeEvent e) { |
|
String prop = e.getPropertyName(); |
|
if(prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || |
|
prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || |
|
prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || |
|
prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY || |
|
prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) { |
|
validateFileCache(); |
|
} else if ("UI".equals(prop)) { |
|
Object old = e.getOldValue(); |
|
if (old instanceof BasicFileChooserUI) { |
|
BasicFileChooserUI ui = (BasicFileChooserUI) old; |
|
BasicDirectoryModel model = ui.getModel(); |
|
if (model != null) { |
|
model.invalidateFileCache(); |
|
} |
|
} |
|
} else if ("JFileChooserDialogIsClosingProperty".equals(prop)) { |
|
invalidateFileCache(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void invalidateFileCache() { |
|
if (loadThread != null) { |
|
loadThread.interrupt(); |
|
loadThread.cancelRunnables(); |
|
loadThread = null; |
|
} |
|
} |
|
|
|
public Vector<File> getDirectories() { |
|
synchronized(fileCache) { |
|
if (directories != null) { |
|
return directories; |
|
} |
|
Vector fls = getFiles(); |
|
return directories; |
|
} |
|
} |
|
|
|
public Vector<File> getFiles() { |
|
synchronized(fileCache) { |
|
if (files != null) { |
|
return files; |
|
} |
|
files = new Vector<File>(); |
|
directories = new Vector<File>(); |
|
directories.addElement(filechooser.getFileSystemView().createFileObject( |
|
filechooser.getCurrentDirectory(), "..") |
|
); |
|
|
|
for (int i = 0; i < getSize(); i++) { |
|
File f = fileCache.get(i); |
|
if (filechooser.isTraversable(f)) { |
|
directories.add(f); |
|
} else { |
|
files.add(f); |
|
} |
|
} |
|
return files; |
|
} |
|
} |
|
|
|
public void validateFileCache() { |
|
File currentDirectory = filechooser.getCurrentDirectory(); |
|
if (currentDirectory == null) { |
|
return; |
|
} |
|
if (loadThread != null) { |
|
loadThread.interrupt(); |
|
loadThread.cancelRunnables(); |
|
} |
|
|
|
setBusy(true, ++fetchID); |
|
|
|
loadThread = new LoadFilesThread(currentDirectory, fetchID); |
|
loadThread.start(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean renameFile(File oldFile, File newFile) { |
|
synchronized(fileCache) { |
|
if (oldFile.renameTo(newFile)) { |
|
validateFileCache(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
} |
|
|
|
|
|
public void fireContentsChanged() { |
|
|
|
fireContentsChanged(this, 0, getSize()-1); |
|
} |
|
|
|
public int getSize() { |
|
return fileCache.size(); |
|
} |
|
|
|
public boolean contains(Object o) { |
|
return fileCache.contains(o); |
|
} |
|
|
|
public int indexOf(Object o) { |
|
return fileCache.indexOf(o); |
|
} |
|
|
|
public Object getElementAt(int index) { |
|
return fileCache.get(index); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void intervalAdded(ListDataEvent e) { |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void intervalRemoved(ListDataEvent e) { |
|
} |
|
|
|
protected void sort(Vector<? extends File> v){ |
|
ShellFolder.sort(v); |
|
} |
|
|
|
|
|
protected boolean lt(File a, File b) { |
|
|
|
int diff = a.getName().toLowerCase().compareTo(b.getName().toLowerCase()); |
|
if (diff != 0) { |
|
return diff < 0; |
|
} else { |
|
|
|
return a.getName().compareTo(b.getName()) < 0; |
|
} |
|
} |
|
|
|
|
|
class LoadFilesThread extends Thread { |
|
File currentDirectory = null; |
|
int fid; |
|
Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10); |
|
|
|
public LoadFilesThread(File currentDirectory, int fid) { |
|
super("Basic L&F File Loading Thread"); |
|
this.currentDirectory = currentDirectory; |
|
this.fid = fid; |
|
} |
|
|
|
public void run() { |
|
run0(); |
|
setBusy(false, fid); |
|
} |
|
|
|
public void run0() { |
|
FileSystemView fileSystem = filechooser.getFileSystemView(); |
|
|
|
if (isInterrupted()) { |
|
return; |
|
} |
|
|
|
File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled()); |
|
|
|
if (isInterrupted()) { |
|
return; |
|
} |
|
|
|
final Vector<File> newFileCache = new Vector<File>(); |
|
Vector<File> newFiles = new Vector<File>(); |
|
|
|
// run through the file list, add directories and selectable files to fileCache |
|
// Note that this block must be OUTSIDE of Invoker thread because of |
|
|
|
for (File file : list) { |
|
if (filechooser.accept(file)) { |
|
boolean isTraversable = filechooser.isTraversable(file); |
|
|
|
if (isTraversable) { |
|
newFileCache.addElement(file); |
|
} else if (filechooser.isFileSelectionEnabled()) { |
|
newFiles.addElement(file); |
|
} |
|
|
|
if (isInterrupted()) { |
|
return; |
|
} |
|
} |
|
} |
|
|
|
|
|
sort(newFileCache); |
|
sort(newFiles); |
|
|
|
newFileCache.addAll(newFiles); |
|
|
|
// To avoid loads of synchronizations with Invoker and improve performance we |
|
|
|
DoChangeContents doChangeContents = ShellFolder.invoke(new Callable<DoChangeContents>() { |
|
public DoChangeContents call() { |
|
int newSize = newFileCache.size(); |
|
int oldSize = fileCache.size(); |
|
|
|
if (newSize > oldSize) { |
|
|
|
int start = oldSize; |
|
int end = newSize; |
|
for (int i = 0; i < oldSize; i++) { |
|
if (!newFileCache.get(i).equals(fileCache.get(i))) { |
|
start = i; |
|
for (int j = i; j < newSize; j++) { |
|
if (newFileCache.get(j).equals(fileCache.get(i))) { |
|
end = j; |
|
break; |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
if (start >= 0 && end > start |
|
&& newFileCache.subList(end, newSize).equals(fileCache.subList(start, oldSize))) { |
|
if (isInterrupted()) { |
|
return null; |
|
} |
|
return new DoChangeContents(newFileCache.subList(start, end), start, null, 0, fid); |
|
} |
|
} else if (newSize < oldSize) { |
|
|
|
int start = -1; |
|
int end = -1; |
|
for (int i = 0; i < newSize; i++) { |
|
if (!newFileCache.get(i).equals(fileCache.get(i))) { |
|
start = i; |
|
end = i + oldSize - newSize; |
|
break; |
|
} |
|
} |
|
if (start >= 0 && end > start |
|
&& fileCache.subList(end, oldSize).equals(newFileCache.subList(start, newSize))) { |
|
if (isInterrupted()) { |
|
return null; |
|
} |
|
return new DoChangeContents(null, 0, new Vector(fileCache.subList(start, end)), start, fid); |
|
} |
|
} |
|
if (!fileCache.equals(newFileCache)) { |
|
if (isInterrupted()) { |
|
cancelRunnables(runnables); |
|
} |
|
return new DoChangeContents(newFileCache, 0, fileCache, 0, fid); |
|
} |
|
return null; |
|
} |
|
}); |
|
|
|
if (doChangeContents != null) { |
|
runnables.addElement(doChangeContents); |
|
SwingUtilities.invokeLater(doChangeContents); |
|
} |
|
} |
|
|
|
|
|
public void cancelRunnables(Vector<DoChangeContents> runnables) { |
|
for (DoChangeContents runnable : runnables) { |
|
runnable.cancel(); |
|
} |
|
} |
|
|
|
public void cancelRunnables() { |
|
cancelRunnables(runnables); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void addPropertyChangeListener(PropertyChangeListener listener) { |
|
if (changeSupport == null) { |
|
changeSupport = new PropertyChangeSupport(this); |
|
} |
|
changeSupport.addPropertyChangeListener(listener); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void removePropertyChangeListener(PropertyChangeListener listener) { |
|
if (changeSupport != null) { |
|
changeSupport.removePropertyChangeListener(listener); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public PropertyChangeListener[] getPropertyChangeListeners() { |
|
if (changeSupport == null) { |
|
return new PropertyChangeListener[0]; |
|
} |
|
return changeSupport.getPropertyChangeListeners(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected void firePropertyChange(String propertyName, |
|
Object oldValue, Object newValue) { |
|
if (changeSupport != null) { |
|
changeSupport.firePropertyChange(propertyName, |
|
oldValue, newValue); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private synchronized void setBusy(final boolean busy, int fid) { |
|
if (fid == fetchID) { |
|
boolean oldValue = this.busy; |
|
this.busy = busy; |
|
|
|
if (changeSupport != null && busy != oldValue) { |
|
SwingUtilities.invokeLater(new Runnable() { |
|
public void run() { |
|
firePropertyChange("busy", !busy, busy); |
|
} |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
class DoChangeContents implements Runnable { |
|
private List<File> addFiles; |
|
private List<File> remFiles; |
|
private boolean doFire = true; |
|
private int fid; |
|
private int addStart = 0; |
|
private int remStart = 0; |
|
|
|
public DoChangeContents(List<File> addFiles, int addStart, List<File> remFiles, int remStart, int fid) { |
|
this.addFiles = addFiles; |
|
this.addStart = addStart; |
|
this.remFiles = remFiles; |
|
this.remStart = remStart; |
|
this.fid = fid; |
|
} |
|
|
|
synchronized void cancel() { |
|
doFire = false; |
|
} |
|
|
|
public synchronized void run() { |
|
if (fetchID == fid && doFire) { |
|
int remSize = (remFiles == null) ? 0 : remFiles.size(); |
|
int addSize = (addFiles == null) ? 0 : addFiles.size(); |
|
synchronized(fileCache) { |
|
if (remSize > 0) { |
|
fileCache.removeAll(remFiles); |
|
} |
|
if (addSize > 0) { |
|
fileCache.addAll(addStart, addFiles); |
|
} |
|
files = null; |
|
directories = null; |
|
} |
|
if (remSize > 0 && addSize == 0) { |
|
fireIntervalRemoved(BasicDirectoryModel.this, remStart, remStart + remSize - 1); |
|
} else if (addSize > 0 && remSize == 0 && addStart + addSize <= fileCache.size()) { |
|
fireIntervalAdded(BasicDirectoryModel.this, addStart, addStart + addSize - 1); |
|
} else { |
|
fireContentsChanged(); |
|
} |
|
} |
|
} |
|
} |
|
} |