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