mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 20:18:48 +00:00
502 lines
17 KiB
Java
502 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2011, 2025, 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.awt.ComponentOrientation;
|
|
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
|
|
*/
|
|
@SuppressWarnings("serial") // Superclass is not serializable across versions
|
|
final class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener {
|
|
private final JTable fFileList;
|
|
private FilesLoader filesLoader = null;
|
|
private Vector<File> files = null;
|
|
|
|
JFileChooser filechooser = null;
|
|
ArrayList<SortableFile> fileCache = null;
|
|
final 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 static final String SORT_BY_CHANGED = "sortByChanged";
|
|
public static final 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);
|
|
}
|
|
|
|
@Override
|
|
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();
|
|
} else if (prop.equals("componentOrientation")) {
|
|
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
|
|
JFileChooser cc = (JFileChooser) e.getSource();
|
|
if (o != e.getOldValue()) {
|
|
cc.applyComponentOrientation(o);
|
|
}
|
|
fFileList.setComponentOrientation(o);
|
|
fFileList.getParent().getParent().setComponentOrientation(o);
|
|
|
|
}
|
|
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.get(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 (filesLoader != null) {
|
|
if (filesLoader.loadThread.isAlive()) {
|
|
filesLoader.queuedTasks.add(runnable);
|
|
return;
|
|
}
|
|
}
|
|
|
|
SwingUtilities.invokeLater(runnable);
|
|
}
|
|
}
|
|
|
|
public void validateFileCache() {
|
|
final File currentDirectory = filechooser.getCurrentDirectory();
|
|
|
|
if (currentDirectory == null) {
|
|
invalidateFileCache();
|
|
return;
|
|
}
|
|
|
|
if (filesLoader != null) {
|
|
// interrupt
|
|
filesLoader.loadThread.interrupt();
|
|
}
|
|
|
|
fetchID++;
|
|
|
|
// PENDING(jeff) pick the size more sensibly
|
|
invalidateFileCache();
|
|
synchronized(fileCacheLock) {
|
|
fileCache = new ArrayList<>(50);
|
|
}
|
|
|
|
filesLoader = new FilesLoader(currentDirectory, fetchID);
|
|
}
|
|
|
|
@Override
|
|
public int getColumnCount() {
|
|
return 2;
|
|
}
|
|
|
|
@Override
|
|
public String getColumnName(final int col) {
|
|
return fColumnNames[col];
|
|
}
|
|
|
|
@Override
|
|
public Class<? extends Object> getColumnClass(final int col) {
|
|
if (col == 0) return File.class;
|
|
return Date.class;
|
|
}
|
|
|
|
@Override
|
|
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
|
|
|
|
@Override
|
|
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.get(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 static 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 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);
|
|
}
|
|
|
|
static final class QuickSortNames extends QuickSort {
|
|
@Override
|
|
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;
|
|
}
|
|
}
|
|
|
|
static final class QuickSortDates extends QuickSort {
|
|
@Override
|
|
protected boolean lt(final SortableFile a, final SortableFile b) {
|
|
return a.fDateValue < b.fDateValue;
|
|
}
|
|
}
|
|
|
|
// for speed in sorting, displaying
|
|
static final 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;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(final Object other) {
|
|
final SortableFile otherFile = (SortableFile)other;
|
|
return otherFile.fFile.equals(fFile);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hashCode(fFile);
|
|
}
|
|
}
|
|
|
|
final class FilesLoader implements Runnable {
|
|
ArrayList<Runnable> queuedTasks = new ArrayList<>();
|
|
File currentDirectory = null;
|
|
int fid;
|
|
Thread loadThread;
|
|
|
|
public FilesLoader(final File currentDirectory, final int fid) {
|
|
this.currentDirectory = currentDirectory;
|
|
this.fid = fid;
|
|
String name = "Aqua L&F File Loading Thread";
|
|
this.loadThread = new Thread(null, this, name, 0, false);
|
|
this.loadThread.start();
|
|
}
|
|
|
|
@Override
|
|
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 (loadThread.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();
|
|
}
|
|
}
|
|
}
|
|
|
|
final 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;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
if (fetchID == fid) {
|
|
synchronized(lock) {
|
|
if (doFire) {
|
|
synchronized(fileCacheLock) {
|
|
if (fileCache != null) {
|
|
for (int i = 0; i < contentFiles.size(); i++) {
|
|
fileCache.add(contentFiles.elementAt(i));
|
|
fireTableRowsInserted(i, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
contentFiles = null;
|
|
directories = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final QuickSortNames sSortNames = new QuickSortNames();
|
|
final QuickSortDates sSortDates = new QuickSortDates();
|
|
}
|