Source for javax.swing.plaf.basic.BasicDirectoryModel

   1: /* BasicDirectoryModel.java --
   2:    Copyright (C) 2005, 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package javax.swing.plaf.basic;
  39: 
  40: import java.beans.PropertyChangeEvent;
  41: import java.beans.PropertyChangeListener;
  42: import java.io.File;
  43: import java.util.Collections;
  44: import java.util.Comparator;
  45: import java.util.Iterator;
  46: import java.util.List;
  47: import java.util.Vector;
  48: import javax.swing.AbstractListModel;
  49: import javax.swing.JFileChooser;
  50: import javax.swing.SwingUtilities;
  51: import javax.swing.event.ListDataEvent;
  52: import javax.swing.filechooser.FileSystemView;
  53: 
  54: 
  55: /**
  56:  * Implements an AbstractListModel for directories where the source
  57:  * of the files is a JFileChooser object.
  58:  *
  59:  * This class is used for sorting and ordering the file list in
  60:  * a JFileChooser L&F object.
  61:  */
  62: public class BasicDirectoryModel extends AbstractListModel
  63:   implements PropertyChangeListener
  64: {
  65:   /** The list of files itself */
  66:   private Vector contents;
  67: 
  68:   /**
  69:    * The directories in the list.
  70:    */
  71:   private Vector directories;
  72: 
  73:   /**
  74:    * The files in the list.
  75:    */
  76:   private Vector files;
  77: 
  78:   /** The listing mode of the associated JFileChooser,
  79:       either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */
  80:   private int listingMode;
  81: 
  82:   /** The JFileCooser associated with this model */
  83:   private JFileChooser filechooser;
  84: 
  85:   /**
  86:    * The thread that loads the file view.
  87:    */
  88:   private DirectoryLoadThread loadThread;
  89: 
  90:   /**
  91:    * This thread is responsible for loading file lists from the
  92:    * current directory and updating the model.
  93:    */
  94:   private class DirectoryLoadThread extends Thread
  95:   {
  96: 
  97:     /**
  98:      * Updates the Swing list model.
  99:      */
 100:     private class UpdateSwingRequest
 101:       implements Runnable
 102:     {
 103: 
 104:       private List added;
 105:       private int addIndex;
 106:       private List removed;
 107:       private int removeIndex;
 108:       private boolean cancel;
 109: 
 110:       UpdateSwingRequest(List add, int ai, List rem, int ri)
 111:       {
 112:         added = add;
 113:         addIndex = ai;
 114:         removed = rem;
 115:         removeIndex = ri;
 116:         cancel = false;
 117:       }
 118: 
 119:       public void run()
 120:       {
 121:         if (! cancel)
 122:           {
 123:             int numRemoved = removed == null ? 0 : removed.size();
 124:             int numAdded = added == null ? 0 : added.size();
 125:             synchronized (contents)
 126:               {
 127:                 if (numRemoved > 0)
 128:                   contents.removeAll(removed);
 129:                 if (numAdded > 0)
 130:                   contents.addAll(added);
 131: 
 132:                 files = null;
 133:                 directories = null;
 134:               }
 135:             if (numRemoved > 0 && numAdded == 0)
 136:               fireIntervalRemoved(BasicDirectoryModel.this, removeIndex,
 137:                                   removeIndex + numRemoved - 1);
 138:             else if (numRemoved == 0 && numAdded > 0)
 139:               fireIntervalAdded(BasicDirectoryModel.this, addIndex,
 140:                                 addIndex + numAdded - 1);
 141:             else
 142:               fireContentsChanged();
 143:           }
 144:       }
 145: 
 146:       void cancel()
 147:       {
 148:         cancel = true;
 149:       }
 150:     }
 151: 
 152:     /**
 153:      * The directory beeing loaded.
 154:      */
 155:     File directory;
 156: 
 157:     /**
 158:      * Stores all UpdateSwingRequests that are sent to the event queue.
 159:      */
 160:     private UpdateSwingRequest pending;
 161: 
 162:     /**
 163:      * Creates a new DirectoryLoadThread that loads the specified
 164:      * directory.
 165:      *
 166:      * @param dir the directory to load
 167:      */
 168:     DirectoryLoadThread(File dir)
 169:     {
 170:       super("Basic L&F directory loader");
 171:       directory = dir;
 172:     }
 173: 
 174:     public void run()
 175:     {
 176:       FileSystemView fsv = filechooser.getFileSystemView();
 177:       File[] files = fsv.getFiles(directory,
 178:                                   filechooser.isFileHidingEnabled());
 179: 
 180:       // Occasional check if we have been interrupted.
 181:       if (isInterrupted())
 182:         return;
 183: 
 184:       // Check list for accepted files.
 185:       Vector accepted = new Vector();
 186:       for (int i = 0; i < files.length; i++)
 187:         {
 188:           if (filechooser.accept(files[i]))
 189:             accepted.add(files[i]);
 190:         }
 191: 
 192:       // Occasional check if we have been interrupted.
 193:       if (isInterrupted())
 194:         return;
 195: 
 196:       // Sort list.
 197:       sort(accepted);
 198: 
 199:       // Now split up directories from files so that we get the directories
 200:       // listed before the files.
 201:       Vector newFiles = new Vector();
 202:       Vector newDirectories = new Vector();
 203:       for (Iterator i = accepted.iterator(); i.hasNext();)
 204:         {
 205:           File f = (File) i.next();
 206:           boolean traversable = filechooser.isTraversable(f);
 207:           if (traversable)
 208:             newDirectories.add(f);
 209:           else if (! traversable && filechooser.isFileSelectionEnabled())
 210:             newFiles.add(f);
 211: 
 212:           // Occasional check if we have been interrupted.
 213:           if (isInterrupted())
 214:             return;
 215: 
 216:         }
 217: 
 218:       // Build up new file cache. Try to update only the changed elements.
 219:       // This will be important for actions like adding new files or
 220:       // directories inside a large file list.
 221:       Vector newCache = new Vector(newDirectories);
 222:       newCache.addAll(newFiles);
 223: 
 224:       int newSize = newCache.size();
 225:       int oldSize = contents.size();
 226:       if (newSize < oldSize)
 227:         {
 228:           // Check for removed interval.
 229:           int start = -1;
 230:           int end = -1;
 231:           boolean found = false;
 232:           for (int i = 0; i < newSize && !found; i++)
 233:             {
 234:               if (! newCache.get(i).equals(contents.get(i)))
 235:                 {
 236:                   start = i;
 237:                   end = i + oldSize - newSize;
 238:                   found = true;
 239:                 }
 240:             }
 241:           if (start >= 0 && end > start
 242:               && contents.subList(end, oldSize)
 243:                                     .equals(newCache.subList(start, newSize)))
 244:             {
 245:               // Occasional check if we have been interrupted.
 246:               if (isInterrupted())
 247:                 return;
 248: 
 249:               Vector removed = new Vector(contents.subList(start, end));
 250:               UpdateSwingRequest r = new UpdateSwingRequest(null, 0,
 251:                                                             removed, start);
 252:               invokeLater(r);
 253:               newCache = null;
 254:             }
 255:         }
 256:       else if (newSize > oldSize)
 257:         {
 258:           // Check for inserted interval.
 259:           int start = oldSize;
 260:           int end = newSize;
 261:           boolean found = false;
 262:           for (int i = 0; i < oldSize && ! found; i++)
 263:             {
 264:               if (! newCache.get(i).equals(contents.get(i)))
 265:                 {
 266:                   start = i;
 267:                   boolean foundEnd = false;
 268:                   for (int j = i; j < newSize && ! foundEnd; j++)
 269:                     {
 270:                       if (newCache.get(j).equals(contents.get(i)))
 271:                         {
 272:                           end = j;
 273:                           foundEnd = true;
 274:                         }
 275:                     }
 276:                   end = i + oldSize - newSize;
 277:                 }
 278:             }
 279:           if (start >= 0 && end > start
 280:               && newCache.subList(end, newSize)
 281:                                     .equals(contents.subList(start, oldSize)))
 282:             {
 283:               // Occasional check if we have been interrupted.
 284:               if (isInterrupted())
 285:                 return;
 286: 
 287:               List added = newCache.subList(start, end);
 288:               UpdateSwingRequest r = new UpdateSwingRequest(added, start,
 289:                                                             null, 0);
 290:               invokeLater(r);
 291:               newCache = null;
 292:             }
 293:         }
 294: 
 295:       // Handle complete list changes (newCache != null).
 296:       if (newCache != null && ! contents.equals(newCache))
 297:         {
 298:           // Occasional check if we have been interrupted.
 299:           if (isInterrupted())
 300:             return;
 301:           UpdateSwingRequest r = new UpdateSwingRequest(newCache, 0,
 302:                                                         contents, 0);
 303:           invokeLater(r);
 304:         }
 305:     }
 306: 
 307:     /**
 308:      * Wraps SwingUtilities.invokeLater() and stores the request in
 309:      * a Vector so that we can still cancel it later.
 310:      *
 311:      * @param update the request to invoke
 312:      */
 313:     private void invokeLater(UpdateSwingRequest update)
 314:     {
 315:       pending = update;
 316:       SwingUtilities.invokeLater(update);
 317:     }
 318: 
 319:     /**
 320:      * Cancels all pending update requests that might be in the AWT
 321:      * event queue.
 322:      */
 323:     void cancelPending()
 324:     {
 325:       if (pending != null)
 326:         pending.cancel();
 327:     }
 328:   }
 329: 
 330:   /** A Comparator class/object for sorting the file list. */
 331:   private Comparator comparator = new Comparator()
 332:     {
 333:       public int compare(Object o1, Object o2)
 334:       {
 335:         if (lt((File) o1, (File) o2))
 336:           return -1;
 337:         else
 338:           return 1;
 339:       }
 340:     };
 341: 
 342:   /**
 343:    * Creates a new BasicDirectoryModel object.
 344:    *
 345:    * @param filechooser DOCUMENT ME!
 346:    */
 347:   public BasicDirectoryModel(JFileChooser filechooser)
 348:   {
 349:     this.filechooser = filechooser;
 350:     filechooser.addPropertyChangeListener(this);
 351:     listingMode = filechooser.getFileSelectionMode();
 352:     contents = new Vector();
 353:     validateFileCache();
 354:   }
 355: 
 356:   /**
 357:    * Returns whether a given (File) object is included in the list.
 358:    *
 359:    * @param o - The file object to test.
 360:    *
 361:    * @return <code>true</code> if the list contains the given object.
 362:    */
 363:   public boolean contains(Object o)
 364:   {
 365:     return contents.contains(o);
 366:   }
 367: 
 368:   /**
 369:    * Fires a content change event.
 370:    */
 371:   public void fireContentsChanged()
 372:   {
 373:     fireContentsChanged(this, 0, getSize() - 1);
 374:   }
 375: 
 376:   /**
 377:    * Returns a Vector of (java.io.File) objects containing
 378:    * the directories in this list.
 379:    *
 380:    * @return a Vector
 381:    */
 382:   public Vector<File> getDirectories()
 383:   {
 384:     // Synchronize this with the UpdateSwingRequest for the case when
 385:     // contents is modified.
 386:     synchronized (contents)
 387:       {
 388:         Vector dirs = directories;
 389:         if (dirs == null)
 390:           {
 391:             // Initializes this in getFiles().
 392:             getFiles();
 393:             dirs = directories;
 394:           }
 395:         return dirs;
 396:       }
 397:   }
 398: 
 399:   /**
 400:    * Returns the (java.io.File) object at
 401:    * an index in the list.
 402:    *
 403:    * @param index The list index
 404:    * @return a File object
 405:    */
 406:   public Object getElementAt(int index)
 407:   {
 408:     if (index > getSize() - 1)
 409:       return null;
 410:     return contents.elementAt(index);
 411:   }
 412: 
 413:   /**
 414:    * Returns a Vector of (java.io.File) objects containing
 415:    * the files in this list.
 416:    *
 417:    * @return a Vector
 418:    */
 419:   public Vector<File>  getFiles()
 420:   {
 421:     synchronized (contents)
 422:       {
 423:         Vector f = files;
 424:         if (f == null)
 425:           {
 426:             f = new Vector();
 427:             Vector d = new Vector(); // Directories;
 428:             for (Iterator i = contents.iterator(); i.hasNext();)
 429:               {
 430:                 File file = (File) i.next();
 431:                 if (filechooser.isTraversable(file))
 432:                   d.add(file);
 433:                 else
 434:                   f.add(file);
 435:               }
 436:             files = f;
 437:             directories = d;
 438:           }
 439:         return f;
 440:       }
 441:   }
 442: 
 443:   /**
 444:    * Returns the size of the list, which only includes directories
 445:    * if the JFileChooser is set to DIRECTORIES_ONLY.
 446:    *
 447:    * Otherwise, both directories and files are included in the count.
 448:    *
 449:    * @return The size of the list.
 450:    */
 451:   public int getSize()
 452:   {
 453:     return contents.size();
 454:   }
 455: 
 456:   /**
 457:    * Returns the index of an (java.io.File) object in the list.
 458:    *
 459:    * @param o The object - normally a File.
 460:    *
 461:    * @return the index of that object, or -1 if it is not in the list.
 462:    */
 463:   public int indexOf(Object o)
 464:   {
 465:     return contents.indexOf(o);
 466:   }
 467: 
 468:   /**
 469:    * Obsoleted method which does nothing.
 470:    */
 471:   public void intervalAdded(ListDataEvent e)
 472:   {
 473:     // obsoleted
 474:   }
 475: 
 476:   /**
 477:    * Obsoleted method which does nothing.
 478:    */
 479:   public void intervalRemoved(ListDataEvent e)
 480:   {
 481:     // obsoleted
 482:   }
 483: 
 484:   /**
 485:    * Obsoleted method which does nothing.
 486:    */
 487:   public void invalidateFileCache()
 488:   {
 489:     // obsoleted
 490:   }
 491: 
 492:   /**
 493:    * Less than, determine the relative order in the list of two files
 494:    * for sorting purposes.
 495:    *
 496:    * The order is: directories < files, and thereafter alphabetically,
 497:    * using the default locale collation.
 498:    *
 499:    * @param a the first file
 500:    * @param b the second file
 501:    *
 502:    * @return <code>true</code> if a > b, <code>false</code> if a < b.
 503:    */
 504:   protected boolean lt(File a, File b)
 505:   {
 506:     boolean aTrav = filechooser.isTraversable(a);
 507:     boolean bTrav = filechooser.isTraversable(b);
 508: 
 509:     if (aTrav == bTrav)
 510:       {
 511:         String aname = a.getName().toLowerCase();
 512:         String bname = b.getName().toLowerCase();
 513:         return (aname.compareTo(bname) < 0) ? true : false;
 514:       }
 515:     else
 516:       {
 517:         if (aTrav)
 518:           return true;
 519:         else
 520:           return false;
 521:       }
 522:   }
 523: 
 524:   /**
 525:    * Listens for a property change; the change in file selection mode of the
 526:    * associated JFileChooser. Reloads the file cache on that event.
 527:    *
 528:    * @param e - A PropertyChangeEvent.
 529:    */
 530:   public void propertyChange(PropertyChangeEvent e)
 531:   {
 532:     String property = e.getPropertyName();
 533:     if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)
 534:         || property.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)
 535:         || property.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY)
 536:         || property.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)
 537:         || property.equals(JFileChooser.FILE_VIEW_CHANGED_PROPERTY)
 538:         )
 539:       {
 540:         validateFileCache();
 541:       }
 542:   }
 543: 
 544:   /**
 545:    * Renames a file - However, does <I>not</I> re-sort the list
 546:    * or replace the old file with the new one in the list.
 547:    *
 548:    * @param oldFile The old file
 549:    * @param newFile The new file name
 550:    *
 551:    * @return <code>true</code> if the rename succeeded
 552:    */
 553:   public boolean renameFile(File oldFile, File newFile)
 554:   {
 555:     return oldFile.renameTo( newFile );
 556:   }
 557: 
 558:   /**
 559:    * Sorts a Vector of File objects.
 560:    *
 561:    * @param v The Vector to sort.
 562:    */
 563:   protected void sort(Vector<? extends File> v)
 564:   {
 565:     Collections.sort(v, comparator);
 566:   }
 567: 
 568:   /**
 569:    * Re-loads the list of files
 570:    */
 571:   public void validateFileCache()
 572:   {
 573:     File dir = filechooser.getCurrentDirectory();
 574:     if (dir != null)
 575:       {
 576:         // Cancel all pending requests.
 577:         if (loadThread != null)
 578:           {
 579:             loadThread.interrupt();
 580:             loadThread.cancelPending();
 581:           }
 582:         loadThread = new DirectoryLoadThread(dir);
 583:         loadThread.start();
 584:       }
 585:   }
 586: }