/*
 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.apple.laf;

import java.beans.*;
import java.io.File;
import java.util.*;

import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.filechooser.FileSystemView;
import javax.swing.table.AbstractTableModel;

NavServices-like implementation of a file Table Some of it came from BasicDirectoryModel
/** * NavServices-like implementation of a file Table * * Some of it came from BasicDirectoryModel */
class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener { private final JTable fFileList; private LoadFilesThread loadThread = null; private Vector<File> files = null; JFileChooser filechooser = null; Vector<SortableFile> fileCache = null; Object fileCacheLock; Vector<File> directories = null; int fetchID = 0; private final boolean fSortAscending[] = {true, true}; // private boolean fSortAscending = true; private boolean fSortNames = true; private final String[] fColumnNames; public final static String SORT_BY_CHANGED = "sortByChanged"; public final static String SORT_ASCENDING_CHANGED = "sortAscendingChanged"; public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) { fileCacheLock = new Object(); this.filechooser = filechooser; fFileList = filelist; fColumnNames = colNames; validateFileCache(); updateSelectionMode(); } void updateSelectionMode() { // Save dialog lists can't be multi select, because all we're selecting is the next folder to open final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG; fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); } public void propertyChange(final PropertyChangeEvent e) { final String prop = e.getPropertyName(); if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) { invalidateFileCache(); validateFileCache(); } else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) { updateSelectionMode(); } else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) { invalidateFileCache(); validateFileCache(); } if (prop == SORT_BY_CHANGED) {// $ Ought to just resort fSortNames = (((Integer)e.getNewValue()).intValue() == 0); invalidateFileCache(); validateFileCache(); fFileList.repaint(); } if (prop == SORT_ASCENDING_CHANGED) { final int sortColumn = (fSortNames ? 0 : 1); fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue(); invalidateFileCache(); validateFileCache(); fFileList.repaint(); } } public void invalidateFileCache() { files = null; directories = null; synchronized(fileCacheLock) { if (fileCache != null) { final int lastRow = fileCache.size(); fileCache = null; fireTableRowsDeleted(0, lastRow); } } } public Vector<File> getDirectories() { if (directories != null) { return directories; } return directories; } public Vector<File> getFiles() { if (files != null) { return files; } files = new Vector<File>(); directories = new Vector<File>(); directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), "..")); synchronized(fileCacheLock) { for (int i = 0; i < fileCache.size(); i++) { final SortableFile sf = fileCache.elementAt(i); final File f = sf.fFile; if (filechooser.isTraversable(f)) { directories.addElement(f); } else { files.addElement(f); } } } return files; } public void runWhenDone(final Runnable runnable){ synchronized (fileCacheLock) { if (loadThread != null) { if (loadThread.isAlive()) { loadThread.queuedTasks.add(runnable); return; } } SwingUtilities.invokeLater(runnable); } } public void validateFileCache() { final File currentDirectory = filechooser.getCurrentDirectory(); if (currentDirectory == null) { invalidateFileCache(); return; } if (loadThread != null) { // interrupt loadThread.interrupt(); } fetchID++; // PENDING(jeff) pick the size more sensibly invalidateFileCache(); synchronized(fileCacheLock) { fileCache = new Vector<SortableFile>(50); } loadThread = new LoadFilesThread(currentDirectory, fetchID); loadThread.start(); } public int getColumnCount() { return 2; } public String getColumnName(final int col) { return fColumnNames[col]; } public Class<? extends Object> getColumnClass(final int col) { if (col == 0) return File.class; return Date.class; } public int getRowCount() { synchronized(fileCacheLock) { if (fileCache != null) { return fileCache.size(); } return 0; } } // SAK: Part of fix for 3168263. The fileCache contains // SortableFiles, so when finding a file in the list we need to // first create a sortable file. public boolean contains(final File o) { synchronized(fileCacheLock) { if (fileCache != null) { return fileCache.contains(new SortableFile(o)); } return false; } } public int indexOf(final File o) { synchronized(fileCacheLock) { if (fileCache != null) { final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1]; final int row = fileCache.indexOf(new SortableFile(o)); return isAscending ? row : fileCache.size() - row - 1; } return 0; } } // AbstractListModel interface public Object getElementAt(final int row) { return getValueAt(row, 0); } // AbstractTableModel interface public Object getValueAt(int row, final int col) { if (row < 0 || col < 0) return null; final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1]; synchronized(fileCacheLock) { if (fileCache != null) { if (!isAscending) row = fileCache.size() - row - 1; return fileCache.elementAt(row).getValueAt(col); } return null; } } // PENDING(jeff) - implement public void intervalAdded(final ListDataEvent e) { } // PENDING(jeff) - implement public void intervalRemoved(final ListDataEvent e) { } protected void sort(final Vector<Object> v) { if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1); else sSortDates.quickSort(v, 0, v.size() - 1); } // Liberated from the 1.1 SortDemo // // This is a generic version of C.A.R Hoare's Quick Sort // algorithm. This will handle arrays that are already // sorted, and arrays with duplicate keys.<BR> // // If you think of a one dimensional array as going from // the lowest index on the left to the highest index on the right // then the parameters to this function are lowest index or // left and highest index or right. The first time you call // this function it will be with the parameters 0, a.length - 1. // // @param a an integer array // @param lo0 left boundary of array partition // @param hi0 right boundary of array partition abstract class QuickSort { final void quickSort(final Vector<Object> v, final int lo0, final int hi0) { int lo = lo0; int hi = hi0; SortableFile mid; if (hi0 > lo0) { // Arbitrarily establishing partition element as the midpoint of // the array. mid = (SortableFile)v.elementAt((lo0 + hi0) / 2); // loop through the array until indices cross while (lo <= hi) { // find the first element that is greater than or equal to // the partition element starting from the left Index. // // Nasty to have to cast here. Would it be quicker // to copy the vectors into arrays and sort the arrays? while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) { ++lo; } // find an element that is smaller than or equal to // the partition element starting from the right Index. while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) { --hi; } // if the indexes have not crossed, swap if (lo <= hi) { swap(v, lo, hi); ++lo; --hi; } } // If the right index has not reached the left side of array // must now sort the left partition. if (lo0 < hi) { quickSort(v, lo0, hi); } // If the left index has not reached the right side of array // must now sort the right partition. if (lo < hi0) { quickSort(v, lo, hi0); } } } private final void swap(final Vector<Object> a, final int i, final int j) { final Object T = a.elementAt(i); a.setElementAt(a.elementAt(j), i); a.setElementAt(T, j); } protected abstract boolean lt(SortableFile a, SortableFile b); } class QuickSortNames extends QuickSort { protected boolean lt(final SortableFile a, final SortableFile b) { final String aLower = a.fName.toLowerCase(); final String bLower = b.fName.toLowerCase(); return aLower.compareTo(bLower) < 0; } } class QuickSortDates extends QuickSort { protected boolean lt(final SortableFile a, final SortableFile b) { return a.fDateValue < b.fDateValue; } } // for speed in sorting, displaying class SortableFile /* extends FileView */{ File fFile; String fName; long fDateValue; Date fDate; SortableFile(final File f) { fFile = f; fName = fFile.getName(); fDateValue = fFile.lastModified(); fDate = new Date(fDateValue); } public Object getValueAt(final int col) { if (col == 0) return fFile; return fDate; } public boolean equals(final Object other) { final SortableFile otherFile = (SortableFile)other; return otherFile.fFile.equals(fFile); } } class LoadFilesThread extends Thread { Vector<Runnable> queuedTasks = new Vector<Runnable>(); File currentDirectory = null; int fid; public LoadFilesThread(final File currentDirectory, final int fid) { super("Aqua L&F File Loading Thread"); this.currentDirectory = currentDirectory; this.fid = fid; } public void run() { final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10); final FileSystemView fileSystem = filechooser.getFileSystemView(); final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled()); final Vector<Object> acceptsList = new Vector<Object>(); for (final File element : list) { // Return all files to the file chooser. The UI will disable or enable // the file name if the current filter approves. acceptsList.addElement(new SortableFile(element)); } // Sort based on settings. sort(acceptsList); // Don't separate directories from files Vector<SortableFile> chunk = new Vector<SortableFile>(10); final int listSize = acceptsList.size(); // run through list grabbing file/dirs in chunks of ten for (int i = 0; i < listSize;) { SortableFile f; for (int j = 0; j < 10 && i < listSize; j++, i++) { f = (SortableFile)acceptsList.elementAt(i); chunk.addElement(f); } final DoChangeContents runnable = new DoChangeContents(chunk, fid); runnables.addElement(runnable); SwingUtilities.invokeLater(runnable); chunk = new Vector<SortableFile>(10); if (isInterrupted()) { // interrupted, cancel all runnables cancelRunnables(runnables); return; } } synchronized (fileCacheLock) { for (final Runnable r : queuedTasks) { SwingUtilities.invokeLater(r); } } } public void cancelRunnables(final Vector<DoChangeContents> runnables) { for (int i = 0; i < runnables.size(); i++) { runnables.elementAt(i).cancel(); } } } class DoChangeContents implements Runnable { private Vector<SortableFile> contentFiles; private boolean doFire = true; private final Object lock = new Object(); private final int fid; public DoChangeContents(final Vector<SortableFile> files, final int fid) { this.contentFiles = files; this.fid = fid; } synchronized void cancel() { synchronized(lock) { doFire = false; } } public void run() { if (fetchID == fid) { synchronized(lock) { if (doFire) { synchronized(fileCacheLock) { if (fileCache != null) { for (int i = 0; i < contentFiles.size(); i++) { fileCache.addElement(contentFiles.elementAt(i)); fireTableRowsInserted(i, i); } } } } contentFiles = null; directories = null; } } } } final QuickSortNames sSortNames = new QuickSortNames(); final QuickSortDates sSortDates = new QuickSortDates(); }