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 UpdateSwingRequest(List add, int ai, List rem, int ri)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 run()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 cancel()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 */ DirectoryLoadThread(File dir)168 DirectoryLoadThread(File dir) 169 { 170 super("Basic L&F directory loader"); 171 directory = dir; 172 } 173 run()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 */ invokeLater(UpdateSwingRequest update)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 */ cancelPending()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 */ BasicDirectoryModel(JFileChooser filechooser)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 */ contains(Object o)363 public boolean contains(Object o) 364 { 365 return contents.contains(o); 366 } 367 368 /** 369 * Fires a content change event. 370 */ fireContentsChanged()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 */ getDirectories()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 */ getElementAt(int index)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 */ getFiles()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 */ getSize()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 */ indexOf(Object o)463 public int indexOf(Object o) 464 { 465 return contents.indexOf(o); 466 } 467 468 /** 469 * Obsoleted method which does nothing. 470 */ intervalAdded(ListDataEvent e)471 public void intervalAdded(ListDataEvent e) 472 { 473 // obsoleted 474 } 475 476 /** 477 * Obsoleted method which does nothing. 478 */ intervalRemoved(ListDataEvent e)479 public void intervalRemoved(ListDataEvent e) 480 { 481 // obsoleted 482 } 483 484 /** 485 * Obsoleted method which does nothing. 486 */ invalidateFileCache()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 */ lt(File a, File b)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 */ propertyChange(PropertyChangeEvent e)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 */ renameFile(File oldFile, File newFile)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 */ sort(Vector<? extends File> v)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 */ validateFileCache()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 } 587