Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaFileSystemModel.java
41155 views
/*1* Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;262728import java.awt.ComponentOrientation;29import java.beans.*;30import java.io.File;31import java.util.*;32import javax.swing.*;33import javax.swing.event.ListDataEvent;34import javax.swing.filechooser.FileSystemView;35import javax.swing.table.AbstractTableModel;3637/**38* NavServices-like implementation of a file Table39*40* Some of it came from BasicDirectoryModel41*/42@SuppressWarnings("serial") // Superclass is not serializable across versions43class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener {44private final JTable fFileList;45private FilesLoader filesLoader = null;46private Vector<File> files = null;4748JFileChooser filechooser = null;49Vector<SortableFile> fileCache = null;50Object fileCacheLock;5152Vector<File> directories = null;53int fetchID = 0;5455private final boolean[] fSortAscending = {true, true};56// private boolean fSortAscending = true;57private boolean fSortNames = true;58private final String[] fColumnNames;59public static final String SORT_BY_CHANGED = "sortByChanged";60public static final String SORT_ASCENDING_CHANGED = "sortAscendingChanged";6162public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) {63fileCacheLock = new Object();64this.filechooser = filechooser;65fFileList = filelist;66fColumnNames = colNames;67validateFileCache();68updateSelectionMode();69}7071void updateSelectionMode() {72// Save dialog lists can't be multi select, because all we're selecting is the next folder to open73final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG;74fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION);75}7677public void propertyChange(final PropertyChangeEvent e) {78final String prop = e.getPropertyName();79if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) {80invalidateFileCache();81validateFileCache();82} else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {83updateSelectionMode();84} else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {85invalidateFileCache();86validateFileCache();87} else if (prop.equals("componentOrientation")) {88ComponentOrientation o = (ComponentOrientation) e.getNewValue();89JFileChooser cc = (JFileChooser) e.getSource();90if (o != e.getOldValue()) {91cc.applyComponentOrientation(o);92}93fFileList.setComponentOrientation(o);94fFileList.getParent().getParent().setComponentOrientation(o);9596}97if (prop == SORT_BY_CHANGED) {// $ Ought to just resort98fSortNames = (((Integer)e.getNewValue()).intValue() == 0);99invalidateFileCache();100validateFileCache();101fFileList.repaint();102}103if (prop == SORT_ASCENDING_CHANGED) {104final int sortColumn = (fSortNames ? 0 : 1);105fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue();106invalidateFileCache();107validateFileCache();108fFileList.repaint();109}110}111112public void invalidateFileCache() {113files = null;114directories = null;115116synchronized(fileCacheLock) {117if (fileCache != null) {118final int lastRow = fileCache.size();119fileCache = null;120fireTableRowsDeleted(0, lastRow);121}122}123}124125public Vector<File> getDirectories() {126if (directories != null) { return directories; }127return directories;128}129130public Vector<File> getFiles() {131if (files != null) { return files; }132files = new Vector<File>();133directories = new Vector<File>();134directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), ".."));135136synchronized(fileCacheLock) {137for (int i = 0; i < fileCache.size(); i++) {138final SortableFile sf = fileCache.elementAt(i);139final File f = sf.fFile;140if (filechooser.isTraversable(f)) {141directories.addElement(f);142} else {143files.addElement(f);144}145}146}147148return files;149}150151public void runWhenDone(final Runnable runnable){152synchronized (fileCacheLock) {153if (filesLoader != null) {154if (filesLoader.loadThread.isAlive()) {155filesLoader.queuedTasks.add(runnable);156return;157}158}159160SwingUtilities.invokeLater(runnable);161}162}163164public void validateFileCache() {165final File currentDirectory = filechooser.getCurrentDirectory();166167if (currentDirectory == null) {168invalidateFileCache();169return;170}171172if (filesLoader != null) {173// interrupt174filesLoader.loadThread.interrupt();175}176177fetchID++;178179// PENDING(jeff) pick the size more sensibly180invalidateFileCache();181synchronized(fileCacheLock) {182fileCache = new Vector<SortableFile>(50);183}184185filesLoader = new FilesLoader(currentDirectory, fetchID);186}187188public int getColumnCount() {189return 2;190}191192public String getColumnName(final int col) {193return fColumnNames[col];194}195196public Class<? extends Object> getColumnClass(final int col) {197if (col == 0) return File.class;198return Date.class;199}200201public int getRowCount() {202synchronized(fileCacheLock) {203if (fileCache != null) {204return fileCache.size();205}206return 0;207}208}209210// SAK: Part of fix for 3168263. The fileCache contains211// SortableFiles, so when finding a file in the list we need to212// first create a sortable file.213public boolean contains(final File o) {214synchronized(fileCacheLock) {215if (fileCache != null) {216return fileCache.contains(new SortableFile(o));217}218return false;219}220}221222public int indexOf(final File o) {223synchronized(fileCacheLock) {224if (fileCache != null) {225final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];226final int row = fileCache.indexOf(new SortableFile(o));227return isAscending ? row : fileCache.size() - row - 1;228}229return 0;230}231}232233// AbstractListModel interface234public Object getElementAt(final int row) {235return getValueAt(row, 0);236}237238// AbstractTableModel interface239240public Object getValueAt(int row, final int col) {241if (row < 0 || col < 0) return null;242final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];243synchronized(fileCacheLock) {244if (fileCache != null) {245if (!isAscending) row = fileCache.size() - row - 1;246return fileCache.elementAt(row).getValueAt(col);247}248return null;249}250}251252// PENDING(jeff) - implement253public void intervalAdded(final ListDataEvent e) {254}255256// PENDING(jeff) - implement257public void intervalRemoved(final ListDataEvent e) {258}259260protected void sort(final Vector<Object> v) {261if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1);262else sSortDates.quickSort(v, 0, v.size() - 1);263}264265// Liberated from the 1.1 SortDemo266//267// This is a generic version of C.A.R Hoare's Quick Sort268// algorithm. This will handle arrays that are already269// sorted, and arrays with duplicate keys.<BR>270//271// If you think of a one dimensional array as going from272// the lowest index on the left to the highest index on the right273// then the parameters to this function are lowest index or274// left and highest index or right. The first time you call275// this function it will be with the parameters 0, a.length - 1.276//277// @param a an integer array278// @param lo0 left boundary of array partition279// @param hi0 right boundary of array partition280abstract class QuickSort {281final void quickSort(final Vector<Object> v, final int lo0, final int hi0) {282int lo = lo0;283int hi = hi0;284SortableFile mid;285286if (hi0 > lo0) {287// Arbitrarily establishing partition element as the midpoint of288// the array.289mid = (SortableFile)v.elementAt((lo0 + hi0) / 2);290291// loop through the array until indices cross292while (lo <= hi) {293// find the first element that is greater than or equal to294// the partition element starting from the left Index.295//296// Nasty to have to cast here. Would it be quicker297// to copy the vectors into arrays and sort the arrays?298while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) {299++lo;300}301302// find an element that is smaller than or equal to303// the partition element starting from the right Index.304while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) {305--hi;306}307308// if the indexes have not crossed, swap309if (lo <= hi) {310swap(v, lo, hi);311++lo;312--hi;313}314}315316// If the right index has not reached the left side of array317// must now sort the left partition.318if (lo0 < hi) {319quickSort(v, lo0, hi);320}321322// If the left index has not reached the right side of array323// must now sort the right partition.324if (lo < hi0) {325quickSort(v, lo, hi0);326}327328}329}330331private void swap(final Vector<Object> a, final int i, final int j) {332final Object T = a.elementAt(i);333a.setElementAt(a.elementAt(j), i);334a.setElementAt(T, j);335}336337protected abstract boolean lt(SortableFile a, SortableFile b);338}339340class QuickSortNames extends QuickSort {341protected boolean lt(final SortableFile a, final SortableFile b) {342final String aLower = a.fName.toLowerCase();343final String bLower = b.fName.toLowerCase();344return aLower.compareTo(bLower) < 0;345}346}347348class QuickSortDates extends QuickSort {349protected boolean lt(final SortableFile a, final SortableFile b) {350return a.fDateValue < b.fDateValue;351}352}353354// for speed in sorting, displaying355class SortableFile /* extends FileView */{356File fFile;357String fName;358long fDateValue;359Date fDate;360361SortableFile(final File f) {362fFile = f;363fName = fFile.getName();364fDateValue = fFile.lastModified();365fDate = new Date(fDateValue);366}367368public Object getValueAt(final int col) {369if (col == 0) return fFile;370return fDate;371}372373public boolean equals(final Object other) {374final SortableFile otherFile = (SortableFile)other;375return otherFile.fFile.equals(fFile);376}377378@Override379public int hashCode() {380return Objects.hashCode(fFile);381}382}383384class FilesLoader implements Runnable {385Vector<Runnable> queuedTasks = new Vector<>();386File currentDirectory = null;387int fid;388Thread loadThread;389390public FilesLoader(final File currentDirectory, final int fid) {391this.currentDirectory = currentDirectory;392this.fid = fid;393String name = "Aqua L&F File Loading Thread";394this.loadThread = new Thread(null, this, name, 0, false);395this.loadThread.start();396}397398@Override399public void run() {400final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);401final FileSystemView fileSystem = filechooser.getFileSystemView();402403final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());404405final Vector<Object> acceptsList = new Vector<Object>();406407for (final File element : list) {408// Return all files to the file chooser. The UI will disable or enable409// the file name if the current filter approves.410acceptsList.addElement(new SortableFile(element));411}412413// Sort based on settings.414sort(acceptsList);415416// Don't separate directories from files417Vector<SortableFile> chunk = new Vector<SortableFile>(10);418final int listSize = acceptsList.size();419// run through list grabbing file/dirs in chunks of ten420for (int i = 0; i < listSize;) {421SortableFile f;422for (int j = 0; j < 10 && i < listSize; j++, i++) {423f = (SortableFile)acceptsList.elementAt(i);424chunk.addElement(f);425}426final DoChangeContents runnable = new DoChangeContents(chunk, fid);427runnables.addElement(runnable);428SwingUtilities.invokeLater(runnable);429chunk = new Vector<SortableFile>(10);430if (loadThread.isInterrupted()) {431// interrupted, cancel all runnables432cancelRunnables(runnables);433return;434}435}436437synchronized (fileCacheLock) {438for (final Runnable r : queuedTasks) {439SwingUtilities.invokeLater(r);440}441}442}443444public void cancelRunnables(final Vector<DoChangeContents> runnables) {445for (int i = 0; i < runnables.size(); i++) {446runnables.elementAt(i).cancel();447}448}449}450451class DoChangeContents implements Runnable {452private Vector<SortableFile> contentFiles;453private boolean doFire = true;454private final Object lock = new Object();455private final int fid;456457public DoChangeContents(final Vector<SortableFile> files, final int fid) {458this.contentFiles = files;459this.fid = fid;460}461462synchronized void cancel() {463synchronized(lock) {464doFire = false;465}466}467468public void run() {469if (fetchID == fid) {470synchronized(lock) {471if (doFire) {472synchronized(fileCacheLock) {473if (fileCache != null) {474for (int i = 0; i < contentFiles.size(); i++) {475fileCache.addElement(contentFiles.elementAt(i));476fireTableRowsInserted(i, i);477}478}479}480}481contentFiles = null;482directories = null;483}484}485}486}487488final QuickSortNames sSortNames = new QuickSortNames();489final QuickSortDates sSortDates = new QuickSortDates();490}491492493