4632N/A/*
4632N/A * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
4632N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4632N/A *
4632N/A * This code is free software; you can redistribute it and/or modify it
4632N/A * under the terms of the GNU General Public License version 2 only, as
4632N/A * published by the Free Software Foundation. Oracle designates this
4632N/A * particular file as subject to the "Classpath" exception as provided
4632N/A * by Oracle in the LICENSE file that accompanied this code.
4632N/A *
4632N/A * This code is distributed in the hope that it will be useful, but WITHOUT
4632N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4632N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
4632N/A * version 2 for more details (a copy is included in the LICENSE file that
4632N/A * accompanied this code).
4632N/A *
4632N/A * You should have received a copy of the GNU General Public License version
4632N/A * 2 along with this work; if not, write to the Free Software Foundation,
4632N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
4632N/A *
4632N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
4632N/A * or visit www.oracle.com if you need additional information or have any
4632N/A * questions.
4632N/A */
4632N/A
4632N/Apackage com.apple.laf;
4632N/A
4632N/Aimport java.beans.*;
4632N/Aimport java.io.File;
4632N/Aimport java.util.*;
4632N/A
4632N/Aimport javax.swing.*;
4632N/Aimport javax.swing.event.ListDataEvent;
4632N/Aimport javax.swing.filechooser.FileSystemView;
4632N/Aimport javax.swing.table.AbstractTableModel;
4632N/A
4632N/A/**
4632N/A * NavServices-like implementation of a file Table
4632N/A *
4632N/A * Some of it came from BasicDirectoryModel
4632N/A */
4632N/Aclass AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener {
4632N/A private final JTable fFileList;
4632N/A private LoadFilesThread loadThread = null;
4632N/A private Vector<File> files = null;
4632N/A
4632N/A JFileChooser filechooser = null;
4632N/A Vector<SortableFile> fileCache = null;
4632N/A Object fileCacheLock;
4632N/A
4632N/A Vector<File> directories = null;
4632N/A int fetchID = 0;
4632N/A
4632N/A private final boolean fSortAscending[] = {true, true};
4632N/A // private boolean fSortAscending = true;
4632N/A private boolean fSortNames = true;
4632N/A private final String[] fColumnNames;
4632N/A public final static String SORT_BY_CHANGED = "sortByChanged";
4632N/A public final static String SORT_ASCENDING_CHANGED = "sortAscendingChanged";
4632N/A
4632N/A public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) {
4632N/A fileCacheLock = new Object();
4632N/A this.filechooser = filechooser;
4632N/A fFileList = filelist;
4632N/A fColumnNames = colNames;
4632N/A validateFileCache();
4632N/A updateSelectionMode();
4632N/A }
4632N/A
4632N/A void updateSelectionMode() {
4632N/A // Save dialog lists can't be multi select, because all we're selecting is the next folder to open
4632N/A final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG;
4632N/A fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION);
4632N/A }
4632N/A
4632N/A public void propertyChange(final PropertyChangeEvent e) {
4632N/A final String prop = e.getPropertyName();
4632N/A if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) {
4632N/A invalidateFileCache();
4632N/A validateFileCache();
4632N/A } else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
4632N/A updateSelectionMode();
4632N/A } else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {
4632N/A invalidateFileCache();
4632N/A validateFileCache();
4632N/A }
4632N/A if (prop == SORT_BY_CHANGED) {// $ Ought to just resort
4632N/A fSortNames = (((Integer)e.getNewValue()).intValue() == 0);
4632N/A invalidateFileCache();
4632N/A validateFileCache();
4632N/A fFileList.repaint();
4632N/A }
4632N/A if (prop == SORT_ASCENDING_CHANGED) {
4632N/A final int sortColumn = (fSortNames ? 0 : 1);
4632N/A fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue();
4632N/A invalidateFileCache();
4632N/A validateFileCache();
4632N/A fFileList.repaint();
4632N/A }
4632N/A }
4632N/A
4632N/A public void invalidateFileCache() {
4632N/A files = null;
4632N/A directories = null;
4632N/A
4632N/A synchronized(fileCacheLock) {
4632N/A if (fileCache != null) {
4632N/A final int lastRow = fileCache.size();
4632N/A fileCache = null;
4632N/A fireTableRowsDeleted(0, lastRow);
4632N/A }
4632N/A }
4632N/A }
4632N/A
4632N/A public Vector<File> getDirectories() {
4632N/A if (directories != null) { return directories; }
4632N/A return directories;
4632N/A }
4632N/A
4632N/A public Vector<File> getFiles() {
4632N/A if (files != null) { return files; }
4632N/A files = new Vector<File>();
4632N/A directories = new Vector<File>();
4632N/A directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), ".."));
4632N/A
4632N/A synchronized(fileCacheLock) {
4632N/A for (int i = 0; i < fileCache.size(); i++) {
4632N/A final SortableFile sf = fileCache.elementAt(i);
4632N/A final File f = sf.fFile;
4632N/A if (filechooser.isTraversable(f)) {
4632N/A directories.addElement(f);
4632N/A } else {
4632N/A files.addElement(f);
4632N/A }
4632N/A }
4632N/A }
4632N/A
4632N/A return files;
4632N/A }
4632N/A
4632N/A public void runWhenDone(final Runnable runnable){
4632N/A synchronized (fileCacheLock) {
4632N/A if (loadThread != null) {
4632N/A if (loadThread.isAlive()) {
4632N/A loadThread.queuedTasks.add(runnable);
4632N/A return;
4632N/A }
4632N/A }
4632N/A
4632N/A SwingUtilities.invokeLater(runnable);
4632N/A }
4632N/A }
4632N/A
4632N/A public void validateFileCache() {
4632N/A final File currentDirectory = filechooser.getCurrentDirectory();
4632N/A
4632N/A if (currentDirectory == null) {
4632N/A invalidateFileCache();
4632N/A return;
4632N/A }
4632N/A
4632N/A if (loadThread != null) {
4632N/A // interrupt
4632N/A loadThread.interrupt();
4632N/A }
4632N/A
4632N/A fetchID++;
4632N/A
4632N/A // PENDING(jeff) pick the size more sensibly
4632N/A invalidateFileCache();
4632N/A synchronized(fileCacheLock) {
4632N/A fileCache = new Vector<SortableFile>(50);
4632N/A }
4632N/A
4632N/A loadThread = new LoadFilesThread(currentDirectory, fetchID);
4632N/A loadThread.start();
4632N/A }
4632N/A
4632N/A public int getColumnCount() {
4632N/A return 2;
4632N/A }
4632N/A
4632N/A public String getColumnName(final int col) {
4632N/A return fColumnNames[col];
4632N/A }
4632N/A
4632N/A public Class<? extends Object> getColumnClass(final int col) {
4632N/A if (col == 0) return File.class;
4632N/A return Date.class;
4632N/A }
4632N/A
4632N/A public int getRowCount() {
4632N/A synchronized(fileCacheLock) {
4632N/A if (fileCache != null) {
4632N/A return fileCache.size();
4632N/A }
4632N/A return 0;
4632N/A }
4632N/A }
4632N/A
4632N/A // SAK: Part of fix for 3168263. The fileCache contains
4632N/A // SortableFiles, so when finding a file in the list we need to
4632N/A // first create a sortable file.
4632N/A public boolean contains(final File o) {
4632N/A synchronized(fileCacheLock) {
4632N/A if (fileCache != null) {
4632N/A return fileCache.contains(new SortableFile(o));
4632N/A }
4632N/A return false;
4632N/A }
4632N/A }
4632N/A
4632N/A public int indexOf(final File o) {
4632N/A synchronized(fileCacheLock) {
4632N/A if (fileCache != null) {
4632N/A final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];
4632N/A final int row = fileCache.indexOf(new SortableFile(o));
4632N/A return isAscending ? row : fileCache.size() - row - 1;
4632N/A }
4632N/A return 0;
4632N/A }
4632N/A }
4632N/A
4632N/A // AbstractListModel interface
4632N/A public Object getElementAt(final int row) {
4632N/A return getValueAt(row, 0);
4632N/A }
4632N/A
4632N/A // AbstractTableModel interface
4632N/A
4632N/A public Object getValueAt(int row, final int col) {
4632N/A if (row < 0 || col < 0) return null;
4632N/A final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];
4632N/A synchronized(fileCacheLock) {
4632N/A if (fileCache != null) {
4632N/A if (!isAscending) row = fileCache.size() - row - 1;
4632N/A return fileCache.elementAt(row).getValueAt(col);
4632N/A }
4632N/A return null;
4632N/A }
4632N/A }
4632N/A
4632N/A // PENDING(jeff) - implement
4632N/A public void intervalAdded(final ListDataEvent e) {
4632N/A }
4632N/A
4632N/A // PENDING(jeff) - implement
4632N/A public void intervalRemoved(final ListDataEvent e) {
4632N/A }
4632N/A
4632N/A protected void sort(final Vector<Object> v) {
4632N/A if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1);
4632N/A else sSortDates.quickSort(v, 0, v.size() - 1);
4632N/A }
4632N/A
4632N/A // Liberated from the 1.1 SortDemo
4632N/A //
4632N/A // This is a generic version of C.A.R Hoare's Quick Sort
4632N/A // algorithm. This will handle arrays that are already
4632N/A // sorted, and arrays with duplicate keys.<BR>
4632N/A //
4632N/A // If you think of a one dimensional array as going from
4632N/A // the lowest index on the left to the highest index on the right
4632N/A // then the parameters to this function are lowest index or
4632N/A // left and highest index or right. The first time you call
4632N/A // this function it will be with the parameters 0, a.length - 1.
4632N/A //
4632N/A // @param a an integer array
4632N/A // @param lo0 left boundary of array partition
4632N/A // @param hi0 right boundary of array partition
4632N/A abstract class QuickSort {
4632N/A final void quickSort(final Vector<Object> v, final int lo0, final int hi0) {
4632N/A int lo = lo0;
4632N/A int hi = hi0;
4632N/A SortableFile mid;
4632N/A
4632N/A if (hi0 > lo0) {
4632N/A // Arbitrarily establishing partition element as the midpoint of
4632N/A // the array.
4632N/A mid = (SortableFile)v.elementAt((lo0 + hi0) / 2);
4632N/A
4632N/A // loop through the array until indices cross
4632N/A while (lo <= hi) {
4632N/A // find the first element that is greater than or equal to
4632N/A // the partition element starting from the left Index.
4632N/A //
4632N/A // Nasty to have to cast here. Would it be quicker
4632N/A // to copy the vectors into arrays and sort the arrays?
4632N/A while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) {
4632N/A ++lo;
4632N/A }
4632N/A
4632N/A // find an element that is smaller than or equal to
4632N/A // the partition element starting from the right Index.
4632N/A while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) {
4632N/A --hi;
4632N/A }
4632N/A
4632N/A // if the indexes have not crossed, swap
4632N/A if (lo <= hi) {
4632N/A swap(v, lo, hi);
4632N/A ++lo;
4632N/A --hi;
4632N/A }
4632N/A }
4632N/A
4632N/A // If the right index has not reached the left side of array
4632N/A // must now sort the left partition.
4632N/A if (lo0 < hi) {
4632N/A quickSort(v, lo0, hi);
4632N/A }
4632N/A
4632N/A // If the left index has not reached the right side of array
4632N/A // must now sort the right partition.
4632N/A if (lo < hi0) {
4632N/A quickSort(v, lo, hi0);
4632N/A }
4632N/A
4632N/A }
4632N/A }
4632N/A
4632N/A private final void swap(final Vector<Object> a, final int i, final int j) {
4632N/A final Object T = a.elementAt(i);
4632N/A a.setElementAt(a.elementAt(j), i);
4632N/A a.setElementAt(T, j);
4632N/A }
4632N/A
4632N/A protected abstract boolean lt(SortableFile a, SortableFile b);
4632N/A }
4632N/A
4632N/A class QuickSortNames extends QuickSort {
4632N/A protected boolean lt(final SortableFile a, final SortableFile b) {
4632N/A final String aLower = a.fName.toLowerCase();
4632N/A final String bLower = b.fName.toLowerCase();
4632N/A return aLower.compareTo(bLower) < 0;
4632N/A }
4632N/A }
4632N/A
4632N/A class QuickSortDates extends QuickSort {
4632N/A protected boolean lt(final SortableFile a, final SortableFile b) {
4632N/A return a.fDateValue < b.fDateValue;
4632N/A }
4632N/A }
4632N/A
4632N/A // for speed in sorting, displaying
4632N/A class SortableFile /* extends FileView */{
4632N/A File fFile;
4632N/A String fName;
4632N/A long fDateValue;
4632N/A Date fDate;
4632N/A
4632N/A SortableFile(final File f) {
4632N/A fFile = f;
4632N/A fName = fFile.getName();
4632N/A fDateValue = fFile.lastModified();
4632N/A fDate = new Date(fDateValue);
4632N/A }
4632N/A
4632N/A public Object getValueAt(final int col) {
4632N/A if (col == 0) return fFile;
4632N/A return fDate;
4632N/A }
4632N/A
4632N/A public boolean equals(final Object other) {
4632N/A final SortableFile otherFile = (SortableFile)other;
4632N/A return otherFile.fFile.equals(fFile);
4632N/A }
4632N/A }
4632N/A
4632N/A class LoadFilesThread extends Thread {
4632N/A Vector<Runnable> queuedTasks = new Vector<Runnable>();
4632N/A File currentDirectory = null;
4632N/A int fid;
4632N/A
4632N/A public LoadFilesThread(final File currentDirectory, final int fid) {
4632N/A super("Aqua L&F File Loading Thread");
4632N/A this.currentDirectory = currentDirectory;
4632N/A this.fid = fid;
4632N/A }
4632N/A
4632N/A public void run() {
4632N/A final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);
4632N/A final FileSystemView fileSystem = filechooser.getFileSystemView();
4632N/A
4632N/A final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
4632N/A
4632N/A final Vector<Object> acceptsList = new Vector<Object>();
4632N/A
4632N/A for (final File element : list) {
4632N/A // Return all files to the file chooser. The UI will disable or enable
4632N/A // the file name if the current filter approves.
4632N/A acceptsList.addElement(new SortableFile(element));
4632N/A }
4632N/A
4632N/A // Sort based on settings.
4632N/A sort(acceptsList);
4632N/A
4632N/A // Don't separate directories from files
4632N/A Vector<SortableFile> chunk = new Vector<SortableFile>(10);
4632N/A final int listSize = acceptsList.size();
4632N/A // run through list grabbing file/dirs in chunks of ten
4632N/A for (int i = 0; i < listSize;) {
4632N/A SortableFile f;
4632N/A for (int j = 0; j < 10 && i < listSize; j++, i++) {
4632N/A f = (SortableFile)acceptsList.elementAt(i);
4632N/A chunk.addElement(f);
4632N/A }
4632N/A final DoChangeContents runnable = new DoChangeContents(chunk, fid);
4632N/A runnables.addElement(runnable);
4632N/A SwingUtilities.invokeLater(runnable);
4632N/A chunk = new Vector<SortableFile>(10);
4632N/A if (isInterrupted()) {
4632N/A // interrupted, cancel all runnables
4632N/A cancelRunnables(runnables);
4632N/A return;
4632N/A }
4632N/A }
4632N/A
4632N/A synchronized (fileCacheLock) {
4632N/A for (final Runnable r : queuedTasks) {
4632N/A SwingUtilities.invokeLater(r);
4632N/A }
4632N/A }
4632N/A }
4632N/A
4632N/A public void cancelRunnables(final Vector<DoChangeContents> runnables) {
4632N/A for (int i = 0; i < runnables.size(); i++) {
4632N/A runnables.elementAt(i).cancel();
4632N/A }
4632N/A }
4632N/A }
4632N/A
4632N/A class DoChangeContents implements Runnable {
4632N/A private Vector<SortableFile> contentFiles;
4632N/A private boolean doFire = true;
4632N/A private final Object lock = new Object();
4632N/A private final int fid;
4632N/A
4632N/A public DoChangeContents(final Vector<SortableFile> files, final int fid) {
4632N/A this.contentFiles = files;
4632N/A this.fid = fid;
4632N/A }
4632N/A
4632N/A synchronized void cancel() {
4632N/A synchronized(lock) {
4632N/A doFire = false;
4632N/A }
4632N/A }
4632N/A
4632N/A public void run() {
4632N/A if (fetchID == fid) {
4632N/A synchronized(lock) {
4632N/A if (doFire) {
4632N/A synchronized(fileCacheLock) {
4632N/A if (fileCache != null) {
4632N/A for (int i = 0; i < contentFiles.size(); i++) {
4632N/A fileCache.addElement(contentFiles.elementAt(i));
4632N/A fireTableRowsInserted(i, i);
4632N/A }
4632N/A }
4632N/A }
4632N/A }
4632N/A contentFiles = null;
4632N/A directories = null;
4632N/A }
4632N/A }
4632N/A }
4632N/A }
4632N/A
4632N/A final QuickSortNames sSortNames = new QuickSortNames();
4632N/A final QuickSortDates sSortDates = new QuickSortDates();
4632N/A}