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