Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]

    1   /*
    2    * Copyright (c) 1998, 2009, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package javax.swing.plaf.basic;
   27   
   28   import java.io.File;
   29   import java.util;
   30   import java.util.concurrent.Callable;
   31   import javax.swing;
   32   import javax.swing.filechooser;
   33   import javax.swing.event;
   34   import java.beans;
   35   
   36   import sun.awt.shell.ShellFolder;
   37   
   38   /**
   39    * Basic implementation of a file list.
   40    *
   41    * @author Jeff Dinkins
   42    */
   43   public class BasicDirectoryModel extends AbstractListModel<Object> implements PropertyChangeListener {
   44   
   45       private JFileChooser filechooser = null;
   46       // PENDING(jeff) pick the size more sensibly
   47       private Vector<File> fileCache = new Vector<File>(50);
   48       private LoadFilesThread loadThread = null;
   49       private Vector<File> files = null;
   50       private Vector<File> directories = null;
   51       private int fetchID = 0;
   52   
   53       private PropertyChangeSupport changeSupport;
   54   
   55       private boolean busy = false;
   56   
   57       public BasicDirectoryModel(JFileChooser filechooser) {
   58           this.filechooser = filechooser;
   59           validateFileCache();
   60       }
   61   
   62       public void propertyChange(PropertyChangeEvent e) {
   63           String prop = e.getPropertyName();
   64           if(prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY ||
   65              prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY ||
   66              prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY ||
   67              prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY ||
   68              prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {
   69               validateFileCache();
   70           } else if ("UI".equals(prop)) {
   71               Object old = e.getOldValue();
   72               if (old instanceof BasicFileChooserUI) {
   73                   BasicFileChooserUI ui = (BasicFileChooserUI) old;
   74                   BasicDirectoryModel model = ui.getModel();
   75                   if (model != null) {
   76                       model.invalidateFileCache();
   77                   }
   78               }
   79           } else if ("JFileChooserDialogIsClosingProperty".equals(prop)) {
   80               invalidateFileCache();
   81           }
   82       }
   83   
   84       /**
   85        * This method is used to interrupt file loading thread.
   86        */
   87       public void invalidateFileCache() {
   88           if (loadThread != null) {
   89               loadThread.interrupt();
   90               loadThread.cancelRunnables();
   91               loadThread = null;
   92           }
   93       }
   94   
   95       public Vector<File> getDirectories() {
   96           synchronized(fileCache) {
   97               if (directories != null) {
   98                   return directories;
   99               }
  100               Vector fls = getFiles();
  101               return directories;
  102           }
  103       }
  104   
  105       public Vector<File> getFiles() {
  106           synchronized(fileCache) {
  107               if (files != null) {
  108                   return files;
  109               }
  110               files = new Vector<File>();
  111               directories = new Vector<File>();
  112               directories.addElement(filechooser.getFileSystemView().createFileObject(
  113                   filechooser.getCurrentDirectory(), "..")
  114               );
  115   
  116               for (int i = 0; i < getSize(); i++) {
  117                   File f = fileCache.get(i);
  118                   if (filechooser.isTraversable(f)) {
  119                       directories.add(f);
  120                   } else {
  121                       files.add(f);
  122                   }
  123               }
  124               return files;
  125           }
  126       }
  127   
  128       public void validateFileCache() {
  129           File currentDirectory = filechooser.getCurrentDirectory();
  130           if (currentDirectory == null) {
  131               return;
  132           }
  133           if (loadThread != null) {
  134               loadThread.interrupt();
  135               loadThread.cancelRunnables();
  136           }
  137   
  138           setBusy(true, ++fetchID);
  139   
  140           loadThread = new LoadFilesThread(currentDirectory, fetchID);
  141           loadThread.start();
  142       }
  143   
  144       /**
  145        * Renames a file in the underlying file system.
  146        *
  147        * @param oldFile a <code>File</code> object representing
  148        *        the existing file
  149        * @param newFile a <code>File</code> object representing
  150        *        the desired new file name
  151        * @return <code>true</code> if rename succeeded,
  152        *        otherwise <code>false</code>
  153        * @since 1.4
  154        */
  155       public boolean renameFile(File oldFile, File newFile) {
  156           synchronized(fileCache) {
  157               if (oldFile.renameTo(newFile)) {
  158                   validateFileCache();
  159                   return true;
  160               }
  161               return false;
  162           }
  163       }
  164   
  165   
  166       public void fireContentsChanged() {
  167           // System.out.println("BasicDirectoryModel: firecontentschanged");
  168           fireContentsChanged(this, 0, getSize()-1);
  169       }
  170   
  171       public int getSize() {
  172           return fileCache.size();
  173       }
  174   
  175       public boolean contains(Object o) {
  176           return fileCache.contains(o);
  177       }
  178   
  179       public int indexOf(Object o) {
  180           return fileCache.indexOf(o);
  181       }
  182   
  183       public Object getElementAt(int index) {
  184           return fileCache.get(index);
  185       }
  186   
  187       /**
  188        * Obsolete - not used.
  189        */
  190       public void intervalAdded(ListDataEvent e) {
  191       }
  192   
  193       /**
  194        * Obsolete - not used.
  195        */
  196       public void intervalRemoved(ListDataEvent e) {
  197       }
  198   
  199       protected void sort(Vector<? extends File> v){
  200           ShellFolder.sort(v);
  201       }
  202   
  203       // Obsolete - not used
  204       protected boolean lt(File a, File b) {
  205           // First ignore case when comparing
  206           int diff = a.getName().toLowerCase().compareTo(b.getName().toLowerCase());
  207           if (diff != 0) {
  208               return diff < 0;
  209           } else {
  210               // May differ in case (e.g. "mail" vs. "Mail")
  211               return a.getName().compareTo(b.getName()) < 0;
  212           }
  213       }
  214   
  215   
  216       class LoadFilesThread extends Thread {
  217           File currentDirectory = null;
  218           int fid;
  219           Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);
  220   
  221           public LoadFilesThread(File currentDirectory, int fid) {
  222               super("Basic L&F File Loading Thread");
  223               this.currentDirectory = currentDirectory;
  224               this.fid = fid;
  225           }
  226   
  227           public void run() {
  228               run0();
  229               setBusy(false, fid);
  230           }
  231   
  232           public void run0() {
  233               FileSystemView fileSystem = filechooser.getFileSystemView();
  234   
  235               if (isInterrupted()) {
  236                   return;
  237               }
  238   
  239               File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
  240   
  241               if (isInterrupted()) {
  242                   return;
  243               }
  244   
  245               final Vector<File> newFileCache = new Vector<File>();
  246               Vector<File> newFiles = new Vector<File>();
  247   
  248               // run through the file list, add directories and selectable files to fileCache
  249               // Note that this block must be OUTSIDE of Invoker thread because of
  250               // deadlock possibility with custom synchronized FileSystemView
  251               for (File file : list) {
  252                   if (filechooser.accept(file)) {
  253                       boolean isTraversable = filechooser.isTraversable(file);
  254   
  255                       if (isTraversable) {
  256                           newFileCache.addElement(file);
  257                       } else if (filechooser.isFileSelectionEnabled()) {
  258                           newFiles.addElement(file);
  259                       }
  260   
  261                       if (isInterrupted()) {
  262                           return;
  263                       }
  264                   }
  265               }
  266   
  267               // First sort alphabetically by filename
  268               sort(newFileCache);
  269               sort(newFiles);
  270   
  271               newFileCache.addAll(newFiles);
  272   
  273               // To avoid loads of synchronizations with Invoker and improve performance we
  274               // execute the whole block on the COM thread
  275               DoChangeContents doChangeContents = ShellFolder.invoke(new Callable<DoChangeContents>() {
  276                   public DoChangeContents call() {
  277                       int newSize = newFileCache.size();
  278                       int oldSize = fileCache.size();
  279   
  280                       if (newSize > oldSize) {
  281                           //see if interval is added
  282                           int start = oldSize;
  283                           int end = newSize;
  284                           for (int i = 0; i < oldSize; i++) {
  285                               if (!newFileCache.get(i).equals(fileCache.get(i))) {
  286                                   start = i;
  287                                   for (int j = i; j < newSize; j++) {
  288                                       if (newFileCache.get(j).equals(fileCache.get(i))) {
  289                                           end = j;
  290                                           break;
  291                                       }
  292                                   }
  293                                   break;
  294                               }
  295                           }
  296                           if (start >= 0 && end > start
  297                               && newFileCache.subList(end, newSize).equals(fileCache.subList(start, oldSize))) {
  298                               if (isInterrupted()) {
  299                                   return null;
  300                               }
  301                               return new DoChangeContents(newFileCache.subList(start, end), start, null, 0, fid);
  302                           }
  303                       } else if (newSize < oldSize) {
  304                           //see if interval is removed
  305                           int start = -1;
  306                           int end = -1;
  307                           for (int i = 0; i < newSize; i++) {
  308                               if (!newFileCache.get(i).equals(fileCache.get(i))) {
  309                                   start = i;
  310                                   end = i + oldSize - newSize;
  311                                   break;
  312                               }
  313                           }
  314                           if (start >= 0 && end > start
  315                               && fileCache.subList(end, oldSize).equals(newFileCache.subList(start, newSize))) {
  316                               if (isInterrupted()) {
  317                                   return null;
  318                               }
  319                               return new DoChangeContents(null, 0, new Vector(fileCache.subList(start, end)), start, fid);
  320                           }
  321                       }
  322                       if (!fileCache.equals(newFileCache)) {
  323                           if (isInterrupted()) {
  324                               cancelRunnables(runnables);
  325                           }
  326                           return new DoChangeContents(newFileCache, 0, fileCache, 0, fid);
  327                       }
  328                       return null;
  329                   }
  330               });
  331   
  332               if (doChangeContents != null) {
  333                   runnables.addElement(doChangeContents);
  334                   SwingUtilities.invokeLater(doChangeContents);
  335               }
  336           }
  337   
  338   
  339           public void cancelRunnables(Vector<DoChangeContents> runnables) {
  340               for (DoChangeContents runnable : runnables) {
  341                   runnable.cancel();
  342               }
  343           }
  344   
  345           public void cancelRunnables() {
  346               cancelRunnables(runnables);
  347           }
  348      }
  349   
  350   
  351       /**
  352        * Adds a PropertyChangeListener to the listener list. The listener is
  353        * registered for all bound properties of this class.
  354        * <p>
  355        * If <code>listener</code> is <code>null</code>,
  356        * no exception is thrown and no action is performed.
  357        *
  358        * @param    listener  the property change listener to be added
  359        *
  360        * @see #removePropertyChangeListener
  361        * @see #getPropertyChangeListeners
  362        *
  363        * @since 1.6
  364        */
  365       public void addPropertyChangeListener(PropertyChangeListener listener) {
  366           if (changeSupport == null) {
  367               changeSupport = new PropertyChangeSupport(this);
  368           }
  369           changeSupport.addPropertyChangeListener(listener);
  370       }
  371   
  372       /**
  373        * Removes a PropertyChangeListener from the listener list.
  374        * <p>
  375        * If listener is null, no exception is thrown and no action is performed.
  376        *
  377        * @param listener the PropertyChangeListener to be removed
  378        *
  379        * @see #addPropertyChangeListener
  380        * @see #getPropertyChangeListeners
  381        *
  382        * @since 1.6
  383        */
  384       public void removePropertyChangeListener(PropertyChangeListener listener) {
  385           if (changeSupport != null) {
  386               changeSupport.removePropertyChangeListener(listener);
  387           }
  388       }
  389   
  390       /**
  391        * Returns an array of all the property change listeners
  392        * registered on this component.
  393        *
  394        * @return all of this component's <code>PropertyChangeListener</code>s
  395        *         or an empty array if no property change
  396        *         listeners are currently registered
  397        *
  398        * @see      #addPropertyChangeListener
  399        * @see      #removePropertyChangeListener
  400        * @see      java.beans.PropertyChangeSupport#getPropertyChangeListeners
  401        *
  402        * @since 1.6
  403        */
  404       public PropertyChangeListener[] getPropertyChangeListeners() {
  405           if (changeSupport == null) {
  406               return new PropertyChangeListener[0];
  407           }
  408           return changeSupport.getPropertyChangeListeners();
  409       }
  410   
  411       /**
  412        * Support for reporting bound property changes for boolean properties.
  413        * This method can be called when a bound property has changed and it will
  414        * send the appropriate PropertyChangeEvent to any registered
  415        * PropertyChangeListeners.
  416        *
  417        * @param propertyName the property whose value has changed
  418        * @param oldValue the property's previous value
  419        * @param newValue the property's new value
  420        *
  421        * @since 1.6
  422        */
  423       protected void firePropertyChange(String propertyName,
  424                                         Object oldValue, Object newValue) {
  425           if (changeSupport != null) {
  426               changeSupport.firePropertyChange(propertyName,
  427                                                oldValue, newValue);
  428           }
  429       }
  430   
  431   
  432       /**
  433        * Set the busy state for the model. The model is considered
  434        * busy when it is running a separate (interruptable)
  435        * thread in order to load the contents of a directory.
  436        */
  437       private synchronized void setBusy(final boolean busy, int fid) {
  438           if (fid == fetchID) {
  439               boolean oldValue = this.busy;
  440               this.busy = busy;
  441   
  442               if (changeSupport != null && busy != oldValue) {
  443                   SwingUtilities.invokeLater(new Runnable() {
  444                       public void run() {
  445                           firePropertyChange("busy", !busy, busy);
  446                       }
  447                   });
  448               }
  449           }
  450       }
  451   
  452   
  453       class DoChangeContents implements Runnable {
  454           private List<File> addFiles;
  455           private List<File> remFiles;
  456           private boolean doFire = true;
  457           private int fid;
  458           private int addStart = 0;
  459           private int remStart = 0;
  460   
  461           public DoChangeContents(List<File> addFiles, int addStart, List<File> remFiles, int remStart, int fid) {
  462               this.addFiles = addFiles;
  463               this.addStart = addStart;
  464               this.remFiles = remFiles;
  465               this.remStart = remStart;
  466               this.fid = fid;
  467           }
  468   
  469           synchronized void cancel() {
  470                   doFire = false;
  471           }
  472   
  473           public synchronized void run() {
  474               if (fetchID == fid && doFire) {
  475                   int remSize = (remFiles == null) ? 0 : remFiles.size();
  476                   int addSize = (addFiles == null) ? 0 : addFiles.size();
  477                   synchronized(fileCache) {
  478                       if (remSize > 0) {
  479                           fileCache.removeAll(remFiles);
  480                       }
  481                       if (addSize > 0) {
  482                           fileCache.addAll(addStart, addFiles);
  483                       }
  484                       files = null;
  485                       directories = null;
  486                   }
  487                   if (remSize > 0 && addSize == 0) {
  488                       fireIntervalRemoved(BasicDirectoryModel.this, remStart, remStart + remSize - 1);
  489                   } else if (addSize > 0 && remSize == 0 && addStart + addSize <= fileCache.size()) {
  490                       fireIntervalAdded(BasicDirectoryModel.this, addStart, addStart + addSize - 1);
  491                   } else {
  492                       fireContentsChanged();
  493                   }
  494               }
  495           }
  496       }
  497   }

Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]