/*******************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Tiny Look and Feel * * (C) Copyright 2003 - 2007 Hans Bickel * * For
* licensing information and credits, please refer to the * comment in file
* de.muntjak.tinylookandfeel.TinyLookAndFeel * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
package de.muntjak.tinylookandfeel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.List;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.filechooser.FileSystemView;
import javax.swing.plaf.basic.BasicDirectoryModel;
import javax.swing.plaf.basic.BasicFileChooserUI;
import sun.awt.shell.ShellFolder;
/**
* A copy of BasicDirectoryModel with access to the fileCache.
*
* @author Hans Bickel
*/
@SuppressWarnings (
{ "all" } )
public class TinyDirectoryModel extends BasicDirectoryModel
{
private JFileChooser filechooser = null;
// PENDING(jeff) pick the size more sensibly
private Vector fileCache = new Vector ( 50 );
private LoadFilesThread loadThread = null;
private Vector files = null;
private Vector directories = null;
private int fetchID = 0;
private PropertyChangeSupport changeSupport;
private boolean busy = false;
public TinyDirectoryModel ( JFileChooser fc )
{
super ( fc );
this.filechooser = fc;
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 ();
}
}
/**
* This method is used to interrupt file loading thread.
*/
public void invalidateFileCache ()
{
if ( loadThread != null )
{
loadThread.interrupt ();
loadThread.cancelRunnables ();
loadThread = null;
}
}
public Vector getDirectories ()
{
synchronized ( fileCache )
{
if ( directories != null )
{
return directories;
}
Vector fls = getFiles ();
return directories;
}
}
public Vector getFiles ()
{
synchronized ( fileCache )
{
if ( files != null )
{
return files;
}
files = new Vector ();
directories = new Vector ();
directories.addElement ( filechooser.getFileSystemView ()
.createFileObject ( filechooser.getCurrentDirectory (), ".." ) );
for ( int i = 0 ; i < getSize () ; i++ )
{
File f = ( File ) fileCache.get ( i );
if ( filechooser.isTraversable ( f ) )
{
directories.add ( f );
}
else
{
files.add ( f );
}
}
return files;
}
}
public void validateFileCache ()
{
// Note: The super constructor will call this method
// but at this time filechooser is null
if ( filechooser == null )
return;
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 ();
}
/**
* Renames a file in the underlying file system.
*
* @param oldFile a <code>File</code> object representing the existing file
* @param newFile a <code>File</code> object representing the desired new
* file name
* @return <code>true</code> if rename succeeded, otherwise
* <code>false</code>
* @since 1.4
*/
public boolean renameFile ( File oldFile, File newFile )
{
synchronized ( fileCache )
{
if ( oldFile.renameTo ( newFile ) )
{
validateFileCache ();
return true;
}
return false;
}
}
public void fireContentsChanged ()
{
// System.out.println("TinyDirectoryModel: 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 Vector getFileCache ()
{
return fileCache;
}
/**
* Obsolete - not used.
*/
public void intervalAdded ( ListDataEvent e )
{
}
/**
* Obsolete - not used.
*/
public void intervalRemoved ( ListDataEvent e )
{
}
protected void sort ( Vector v )
{
ShellFolder.sortFiles ( v );
}
// Obsolete - not used
protected boolean lt ( File a, File b )
{
// First ignore case when comparing
int diff = a.getName ().toLowerCase ().compareTo (
b.getName ().toLowerCase () );
if ( diff != 0 )
{
return diff < 0;
}
else
{
// May differ in case (e.g. "mail" vs. "Mail")
return a.getName ().compareTo ( b.getName () ) < 0;
}
}
class LoadFilesThread extends Thread
{
File currentDirectory = null;
int fid;
Vector runnables = new Vector ( 10 );
public LoadFilesThread ( File currentDirectory, int fid )
{
super ( "Basic L&F File Loading Thread" );
this.currentDirectory = currentDirectory;
this.fid = fid;
}
private void invokeLater ( Runnable runnable )
{
runnables.addElement ( runnable );
SwingUtilities.invokeLater ( runnable );
}
public void run ()
{
run0 ();
setBusy ( false, fid );
}
public void run0 ()
{
FileSystemView fileSystem = filechooser.getFileSystemView ();
File [] list = fileSystem.getFiles ( currentDirectory, filechooser
.isFileHidingEnabled () );
Vector acceptsList = new Vector ();
if ( isInterrupted () )
{
return;
}
// run through the file list, add directories and selectable files to
// fileCache
for ( int i = 0 ; i < list.length ; i++ )
{
if ( filechooser.accept ( list [ i ] ) )
{
acceptsList.addElement ( list [ i ] );
}
}
if ( isInterrupted () )
{
return;
}
// First sort alphabetically by filename
sort ( acceptsList );
Vector newDirectories = new Vector ( 50 );
Vector newFiles = new Vector ();
// run through list grabbing directories in chunks of ten
for ( int i = 0 ; i < acceptsList.size () ; i++ )
{
File f = ( File ) acceptsList.elementAt ( i );
boolean isTraversable = filechooser.isTraversable ( f );
if ( isTraversable )
{
newDirectories.addElement ( f );
}
else if ( !isTraversable && filechooser.isFileSelectionEnabled () )
{
newFiles.addElement ( f );
}
if ( isInterrupted () )
{
return;
}
}
Vector newFileCache = new Vector ( newDirectories );
newFileCache.addAll ( newFiles );
int newSize = newFileCache.size ();
int oldSize = fileCache.size ();
if ( newSize > oldSize )
{
// see if interval is added
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;
}
invokeLater ( new DoChangeContents ( newFileCache.subList ( start,
end ), start, null, 0, fid ) );
newFileCache = null;
}
}
else if ( newSize < oldSize )
{
// see if interval is removed
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;
}
invokeLater ( new DoChangeContents ( null, 0, new Vector ( fileCache
.subList ( start, end ) ), start, fid ) );
newFileCache = null;
}
}
if ( newFileCache != null && !fileCache.equals ( newFileCache ) )
{
if ( isInterrupted () )
{
cancelRunnables ( runnables );
}
invokeLater ( new DoChangeContents ( newFileCache, 0, fileCache, 0, fid ) );
}
}
public void cancelRunnables ( Vector runnables )
{
for ( int i = 0 ; i < runnables.size () ; i++ )
{
( ( DoChangeContents ) runnables.elementAt ( i ) ).cancel ();
}
}
public void cancelRunnables ()
{
cancelRunnables ( runnables );
}
}
/**
* Adds a PropertyChangeListener to the listener list. The listener is
* registered for all bound properties of this class.
* <p>
* If <code>listener</code> is <code>null</code>, no exception is thrown
* and no action is performed.
*
* @param listener the property change listener to be added
* @see #removePropertyChangeListener
* @see #getPropertyChangeListeners
* @since 1.6
*/
public void addPropertyChangeListener ( PropertyChangeListener listener )
{
if ( changeSupport == null )
{
changeSupport = new PropertyChangeSupport ( this );
}
changeSupport.addPropertyChangeListener ( listener );
}
/**
* Removes a PropertyChangeListener from the listener list.
* <p>
* If listener is null, no exception is thrown and no action is performed.
*
* @param listener the PropertyChangeListener to be removed
* @see #addPropertyChangeListener
* @see #getPropertyChangeListeners
* @since 1.6
*/
public void removePropertyChangeListener ( PropertyChangeListener listener )
{
if ( changeSupport != null )
{
changeSupport.removePropertyChangeListener ( listener );
}
}
/**
* Returns an array of all the property change listeners registered on this
* component.
*
* @return all of this component's <code>PropertyChangeListener</code>s or
* an empty array if no property change listeners are currently
* registered
* @see #addPropertyChangeListener
* @see #removePropertyChangeListener
* @see java.beans.PropertyChangeSupport#getPropertyChangeListeners
* @since 1.6
*/
public PropertyChangeListener [] getPropertyChangeListeners ()
{
if ( changeSupport == null )
{
return new PropertyChangeListener [ 0 ];
}
return changeSupport.getPropertyChangeListeners ();
}
/**
* Support for reporting bound property changes for boolean properties. This
* method can be called when a bound property has changed and it will send the
* appropriate PropertyChangeEvent to any registered PropertyChangeListeners.
*
* @param propertyName the property whose value has changed
* @param oldValue the property's previous value
* @param newValue the property's new value
* @since 1.6
*/
protected void firePropertyChange ( String propertyName, boolean oldValue,
boolean newValue )
{
if ( changeSupport != null )
{
changeSupport.firePropertyChange ( propertyName, oldValue, newValue );
}
}
/**
* Set the busy state for the model. The model is considered busy when it is
* running a separate (interruptable) thread in order to load the contents of
* a directory.
*/
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 addFiles;
private List remFiles;
private boolean doFire = true;
private int fid;
private int addStart = 0;
private int remStart = 0;
private int change;
public DoChangeContents ( List addFiles, int addStart, List 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 ( TinyDirectoryModel.this, remStart, remStart
+ remSize - 1 );
}
else if ( addSize > 0 && remSize == 0 && fileCache.size () > addSize )
{
fireIntervalAdded ( TinyDirectoryModel.this, addStart, addStart
+ addSize - 1 );
}
else
{
fireContentsChanged ();
}
}
}
}
}