1 /* 2 * Copyright (c) 2011, 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 com.apple.laf; 27 28 import java.beans.*; 29 import java.io.File; 30 import java.util.*; 31 32 import javax.swing.*; 33 import javax.swing.event.ListDataEvent; 34 import javax.swing.filechooser.FileSystemView; 35 import javax.swing.table.AbstractTableModel; 36 37 /** 38 * NavServices-like implementation of a file Table 39 * 40 * Some of it came from BasicDirectoryModel 41 */ 42 class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener { 43 private final JTable fFileList; 44 private LoadFilesThread loadThread = null; 45 private Vector<File> files = null; 46 47 JFileChooser filechooser = null; 48 Vector<SortableFile> fileCache = null; 49 Object fileCacheLock; 50 51 Vector<File> directories = null; 52 int fetchID = 0; 53 54 private final boolean fSortAscending[] = {true, true}; 55 // private boolean fSortAscending = true; 56 private boolean fSortNames = true; 57 private final String[] fColumnNames; 58 public final static String SORT_BY_CHANGED = "sortByChanged"; 59 public final static String SORT_ASCENDING_CHANGED = "sortAscendingChanged"; 60 61 public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) { 62 fileCacheLock = new Object(); 63 this.filechooser = filechooser; 64 fFileList = filelist; 65 fColumnNames = colNames; 66 validateFileCache(); 67 updateSelectionMode(); 68 } 69 70 void updateSelectionMode() { 71 // Save dialog lists can't be multi select, because all we're selecting is the next folder to open 72 final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG; 73 fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); 74 } 75 76 public void propertyChange(final PropertyChangeEvent e) { 77 final String prop = e.getPropertyName(); 78 if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) { 79 invalidateFileCache(); 80 validateFileCache(); 81 } else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) { 82 updateSelectionMode(); 83 } else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) { 84 invalidateFileCache(); 85 validateFileCache(); 86 } 87 if (prop == SORT_BY_CHANGED) {// $ Ought to just resort 88 fSortNames = (((Integer)e.getNewValue()).intValue() == 0); 89 invalidateFileCache(); 90 validateFileCache(); 91 fFileList.repaint(); 92 } 93 if (prop == SORT_ASCENDING_CHANGED) { 94 final int sortColumn = (fSortNames ? 0 : 1); 95 fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue(); 96 invalidateFileCache(); 97 validateFileCache(); 98 fFileList.repaint(); 99 } 100 } 101 102 public void invalidateFileCache() { 103 files = null; 104 directories = null; 105 106 synchronized(fileCacheLock) { 107 if (fileCache != null) { 108 final int lastRow = fileCache.size(); 109 fileCache = null; 110 fireTableRowsDeleted(0, lastRow); 111 } 112 } 113 } 114 115 public Vector<File> getDirectories() { 116 if (directories != null) { return directories; } 117 return directories; 118 } 119 120 public Vector<File> getFiles() { 121 if (files != null) { return files; } 122 files = new Vector<File>(); 123 directories = new Vector<File>(); 124 directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), "..")); 125 126 synchronized(fileCacheLock) { 127 for (int i = 0; i < fileCache.size(); i++) { 128 final SortableFile sf = fileCache.elementAt(i); 129 final File f = sf.fFile; 130 if (filechooser.isTraversable(f)) { 131 directories.addElement(f); 132 } else { 133 files.addElement(f); 134 } 135 } 136 } 137 138 return files; 139 } 140 141 public void runWhenDone(final Runnable runnable){ 142 synchronized (fileCacheLock) { 143 if (loadThread != null) { 144 if (loadThread.isAlive()) { 145 loadThread.queuedTasks.add(runnable); 146 return; 147 } 148 } 149 150 SwingUtilities.invokeLater(runnable); 151 } 152 } 153 154 public void validateFileCache() { 155 final File currentDirectory = filechooser.getCurrentDirectory(); 156 157 if (currentDirectory == null) { 158 invalidateFileCache(); 159 return; 160 } 161 162 if (loadThread != null) { 163 // interrupt 164 loadThread.interrupt(); 165 } 166 167 fetchID++; 168 169 // PENDING(jeff) pick the size more sensibly 170 invalidateFileCache(); 171 synchronized(fileCacheLock) { 172 fileCache = new Vector<SortableFile>(50); 173 } 174 175 loadThread = new LoadFilesThread(currentDirectory, fetchID); 176 loadThread.start(); 177 } 178 179 public int getColumnCount() { 180 return 2; 181 } 182 183 public String getColumnName(final int col) { 184 return fColumnNames[col]; 185 } 186 187 public Class<? extends Object> getColumnClass(final int col) { 188 if (col == 0) return File.class; 189 return Date.class; 190 } 191 192 public int getRowCount() { 193 synchronized(fileCacheLock) { 194 if (fileCache != null) { 195 return fileCache.size(); 196 } 197 return 0; 198 } 199 } 200 201 // SAK: Part of fix for 3168263. The fileCache contains 202 // SortableFiles, so when finding a file in the list we need to 203 // first create a sortable file. 204 public boolean contains(final File o) { 205 synchronized(fileCacheLock) { 206 if (fileCache != null) { 207 return fileCache.contains(new SortableFile(o)); 208 } 209 return false; 210 } 211 } 212 213 public int indexOf(final File o) { 214 synchronized(fileCacheLock) { 215 if (fileCache != null) { 216 final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1]; 217 final int row = fileCache.indexOf(new SortableFile(o)); 218 return isAscending ? row : fileCache.size() - row - 1; 219 } 220 return 0; 221 } 222 } 223 224 // AbstractListModel interface 225 public Object getElementAt(final int row) { 226 return getValueAt(row, 0); 227 } 228 229 // AbstractTableModel interface 230 231 public Object getValueAt(int row, final int col) { 232 if (row < 0 || col < 0) return null; 233 final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1]; 234 synchronized(fileCacheLock) { 235 if (fileCache != null) { 236 if (!isAscending) row = fileCache.size() - row - 1; 237 return fileCache.elementAt(row).getValueAt(col); 238 } 239 return null; 240 } 241 } 242 243 // PENDING(jeff) - implement 244 public void intervalAdded(final ListDataEvent e) { 245 } 246 247 // PENDING(jeff) - implement 248 public void intervalRemoved(final ListDataEvent e) { 249 } 250 251 protected void sort(final Vector<Object> v) { 252 if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1); 253 else sSortDates.quickSort(v, 0, v.size() - 1); 254 } 255 256 // Liberated from the 1.1 SortDemo 257 // 258 // This is a generic version of C.A.R Hoare's Quick Sort 259 // algorithm. This will handle arrays that are already 260 // sorted, and arrays with duplicate keys.<BR> 261 // 262 // If you think of a one dimensional array as going from 263 // the lowest index on the left to the highest index on the right 264 // then the parameters to this function are lowest index or 265 // left and highest index or right. The first time you call 266 // this function it will be with the parameters 0, a.length - 1. 267 // 268 // @param a an integer array 269 // @param lo0 left boundary of array partition 270 // @param hi0 right boundary of array partition 271 abstract class QuickSort { 272 final void quickSort(final Vector<Object> v, final int lo0, final int hi0) { 273 int lo = lo0; 274 int hi = hi0; 275 SortableFile mid; 276 277 if (hi0 > lo0) { 278 // Arbitrarily establishing partition element as the midpoint of 279 // the array. 280 mid = (SortableFile)v.elementAt((lo0 + hi0) / 2); 281 282 // loop through the array until indices cross 283 while (lo <= hi) { 284 // find the first element that is greater than or equal to 285 // the partition element starting from the left Index. 286 // 287 // Nasty to have to cast here. Would it be quicker 288 // to copy the vectors into arrays and sort the arrays? 289 while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) { 290 ++lo; 291 } 292 293 // find an element that is smaller than or equal to 294 // the partition element starting from the right Index. 295 while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) { 296 --hi; 297 } 298 299 // if the indexes have not crossed, swap 300 if (lo <= hi) { 301 swap(v, lo, hi); 302 ++lo; 303 --hi; 304 } 305 } 306 307 // If the right index has not reached the left side of array 308 // must now sort the left partition. 309 if (lo0 < hi) { 310 quickSort(v, lo0, hi); 311 } 312 313 // If the left index has not reached the right side of array 314 // must now sort the right partition. 315 if (lo < hi0) { 316 quickSort(v, lo, hi0); 317 } 318 319 } 320 } 321 322 private final void swap(final Vector<Object> a, final int i, final int j) { 323 final Object T = a.elementAt(i); 324 a.setElementAt(a.elementAt(j), i); 325 a.setElementAt(T, j); 326 } 327 328 protected abstract boolean lt(SortableFile a, SortableFile b); 329 } 330 331 class QuickSortNames extends QuickSort { 332 protected boolean lt(final SortableFile a, final SortableFile b) { 333 final String aLower = a.fName.toLowerCase(); 334 final String bLower = b.fName.toLowerCase(); 335 return aLower.compareTo(bLower) < 0; 336 } 337 } 338 339 class QuickSortDates extends QuickSort { 340 protected boolean lt(final SortableFile a, final SortableFile b) { 341 return a.fDateValue < b.fDateValue; 342 } 343 } 344 345 // for speed in sorting, displaying 346 class SortableFile /* extends FileView */{ 347 File fFile; 348 String fName; 349 long fDateValue; 350 Date fDate; 351 352 SortableFile(final File f) { 353 fFile = f; 354 fName = fFile.getName(); 355 fDateValue = fFile.lastModified(); 356 fDate = new Date(fDateValue); 357 } 358 359 public Object getValueAt(final int col) { 360 if (col == 0) return fFile; 361 return fDate; 362 } 363 364 public boolean equals(final Object other) { 365 final SortableFile otherFile = (SortableFile)other; 366 return otherFile.fFile.equals(fFile); 367 } 368 } 369 370 class LoadFilesThread extends Thread { 371 Vector<Runnable> queuedTasks = new Vector<Runnable>(); 372 File currentDirectory = null; 373 int fid; 374 375 public LoadFilesThread(final File currentDirectory, final int fid) { 376 super("Aqua L&F File Loading Thread"); 377 this.currentDirectory = currentDirectory; 378 this.fid = fid; 379 } 380 381 public void run() { 382 final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10); 383 final FileSystemView fileSystem = filechooser.getFileSystemView(); 384 385 final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled()); 386 387 final Vector<Object> acceptsList = new Vector<Object>(); 388 389 for (final File element : list) { 390 // Return all files to the file chooser. The UI will disable or enable 391 // the file name if the current filter approves. 392 acceptsList.addElement(new SortableFile(element)); 393 } 394 395 // Sort based on settings. 396 sort(acceptsList); 397 398 // Don't separate directories from files 399 Vector<SortableFile> chunk = new Vector<SortableFile>(10); 400 final int listSize = acceptsList.size(); 401 // run through list grabbing file/dirs in chunks of ten 402 for (int i = 0; i < listSize;) { 403 SortableFile f; 404 for (int j = 0; j < 10 && i < listSize; j++, i++) { 405 f = (SortableFile)acceptsList.elementAt(i); 406 chunk.addElement(f); 407 } 408 final DoChangeContents runnable = new DoChangeContents(chunk, fid); 409 runnables.addElement(runnable); 410 SwingUtilities.invokeLater(runnable); 411 chunk = new Vector<SortableFile>(10); 412 if (isInterrupted()) { 413 // interrupted, cancel all runnables 414 cancelRunnables(runnables); 415 return; 416 } 417 } 418 419 synchronized (fileCacheLock) { 420 for (final Runnable r : queuedTasks) { 421 SwingUtilities.invokeLater(r); 422 } 423 } 424 } 425 426 public void cancelRunnables(final Vector<DoChangeContents> runnables) { 427 for (int i = 0; i < runnables.size(); i++) { 428 runnables.elementAt(i).cancel(); 429 } 430 } 431 } 432 433 class DoChangeContents implements Runnable { 434 private Vector<SortableFile> contentFiles; 435 private boolean doFire = true; 436 private final Object lock = new Object(); 437 private final int fid; 438 439 public DoChangeContents(final Vector<SortableFile> files, final int fid) { 440 this.contentFiles = files; 441 this.fid = fid; 442 } 443 444 synchronized void cancel() { 445 synchronized(lock) { 446 doFire = false; 447 } 448 } 449 450 public void run() { 451 if (fetchID == fid) { 452 synchronized(lock) { 453 if (doFire) { 454 synchronized(fileCacheLock) { 455 if (fileCache != null) { 456 for (int i = 0; i < contentFiles.size(); i++) { 457 fileCache.addElement(contentFiles.elementAt(i)); 458 fireTableRowsInserted(i, i); 459 } 460 } 461 } 462 } 463 contentFiles = null; 464 directories = null; 465 } 466 } 467 } 468 } 469 470 final QuickSortNames sSortNames = new QuickSortNames(); 471 final QuickSortDates sSortDates = new QuickSortDates(); 472 }