mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-2059 improve GhidraFileChooser interactivity
Refactor how file system root locations are handled to avoid potential slowdowns and swing thread blocking.
This commit is contained in:
parent
b6501c8283
commit
c99f770b23
8 changed files with 287 additions and 273 deletions
|
@ -16,12 +16,13 @@
|
||||||
package ghidra.plugins.fsbrowser;
|
package ghidra.plugins.fsbrowser;
|
||||||
|
|
||||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||||
import static java.util.Map.*;
|
import static java.util.Map.entry;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
@ -109,9 +110,6 @@ class FSBActionManager {
|
||||||
this.textEditorService = textEditorService;
|
this.textEditorService = textEditorService;
|
||||||
this.gTree = gTree;
|
this.gTree = gTree;
|
||||||
|
|
||||||
chooserExport = new GhidraFileChooser(provider.getComponent());
|
|
||||||
chooserExportAll = new GhidraFileChooser(provider.getComponent());
|
|
||||||
|
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,11 +378,14 @@ class FSBActionManager {
|
||||||
if (fsrl == null) {
|
if (fsrl == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (chooserExport == null) {
|
||||||
|
chooserExport = new GhidraFileChooser(provider.getComponent());
|
||||||
|
chooserExport.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||||
|
chooserExport.setTitle("Select Where To Export File");
|
||||||
|
chooserExport.setApproveButtonText("Export");
|
||||||
|
}
|
||||||
File selectedFile =
|
File selectedFile =
|
||||||
new File(chooserExport.getCurrentDirectory(), fsrl.getName());
|
new File(chooserExport.getCurrentDirectory(), fsrl.getName());
|
||||||
chooserExport.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
|
||||||
chooserExport.setTitle("Select Where To Export File");
|
|
||||||
chooserExport.setApproveButtonText("Export");
|
|
||||||
chooserExport.setSelectedFile(selectedFile);
|
chooserExport.setSelectedFile(selectedFile);
|
||||||
File outputFile = chooserExport.getSelectedFile();
|
File outputFile = chooserExport.getSelectedFile();
|
||||||
if (outputFile == null) {
|
if (outputFile == null) {
|
||||||
|
@ -421,11 +422,13 @@ class FSBActionManager {
|
||||||
if (fsrl instanceof FSRLRoot) {
|
if (fsrl instanceof FSRLRoot) {
|
||||||
fsrl = fsrl.appendPath("/");
|
fsrl = fsrl.appendPath("/");
|
||||||
}
|
}
|
||||||
|
if (chooserExportAll == null) {
|
||||||
chooserExportAll
|
chooserExportAll = new GhidraFileChooser(provider.getComponent());
|
||||||
.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
chooserExportAll
|
||||||
chooserExportAll.setTitle("Select Export Directory");
|
.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
|
||||||
chooserExportAll.setApproveButtonText("Export All");
|
chooserExportAll.setTitle("Select Export Directory");
|
||||||
|
chooserExportAll.setApproveButtonText("Export All");
|
||||||
|
}
|
||||||
chooserExportAll.setSelectedFile(null);
|
chooserExportAll.setSelectedFile(null);
|
||||||
File outputFile = chooserExportAll.getSelectedFile();
|
File outputFile = chooserExportAll.getSelectedFile();
|
||||||
if (outputFile == null) {
|
if (outputFile == null) {
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.filechooser;
|
package docking.widgets.filechooser;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import docking.widgets.table.AbstractSortedTableModel;
|
import docking.widgets.table.AbstractSortedTableModel;
|
||||||
|
|
||||||
class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
|
@ -28,7 +29,7 @@ class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
final static int TIME_COL = 2;
|
final static int TIME_COL = 2;
|
||||||
|
|
||||||
private GhidraFileChooser chooser;
|
private GhidraFileChooser chooser;
|
||||||
private File[] files = new File[0];
|
private List<File> files = new ArrayList<>();
|
||||||
|
|
||||||
DirectoryTableModel(GhidraFileChooser chooser) {
|
DirectoryTableModel(GhidraFileChooser chooser) {
|
||||||
super(FILE_COL);
|
super(FILE_COL);
|
||||||
|
@ -36,31 +37,27 @@ class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void insert(File file) {
|
void insert(File file) {
|
||||||
int len = files.length;
|
int len = files.size();
|
||||||
File[] arr = new File[len + 1];
|
files.add(file);
|
||||||
System.arraycopy(files, 0, arr, 0, len);
|
|
||||||
arr[len] = file;
|
|
||||||
files = arr;
|
|
||||||
fireTableRowsInserted(len, len);
|
fireTableRowsInserted(len, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFiles(List<File> fileList) {
|
void setFiles(List<File> fileList) {
|
||||||
this.files = new File[fileList.size()];
|
files.clear();
|
||||||
files = fileList.toArray(files);
|
files.addAll(fileList);
|
||||||
System.arraycopy(files, 0, this.files, 0, files.length);
|
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
File getFile(int row) {
|
File getFile(int row) {
|
||||||
if (row >= 0 && row < files.length) {
|
if (row >= 0 && row < files.size()) {
|
||||||
return files[row];
|
return files.get(row);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFile(int row, File file) {
|
void setFile(int row, File file) {
|
||||||
if (row >= 0 && row < files.length) {
|
if (row >= 0 && row < files.size()) {
|
||||||
files[row] = file;
|
files.set(row, file);
|
||||||
fireTableRowsUpdated(row, row);
|
fireTableRowsUpdated(row, row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +69,7 @@ class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return files == null ? 0 : files.length;
|
return files.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,7 +126,7 @@ class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<File> getModelData() {
|
public List<File> getModelData() {
|
||||||
return Arrays.asList(files);
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,7 +137,7 @@ class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setValueAt(Object aValue, int row, int column) {
|
public void setValueAt(Object aValue, int row, int column) {
|
||||||
if (row < 0 || row >= files.length) {
|
if (row < 0 || row >= files.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +147,7 @@ class DirectoryTableModel extends AbstractSortedTableModel<File> {
|
||||||
|
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case FILE_COL:
|
case FILE_COL:
|
||||||
files[row] = (File) aValue;
|
files.set(row, (File) aValue);
|
||||||
update();
|
update();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,10 +18,11 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.filechooser;
|
package docking.widgets.filechooser;
|
||||||
|
|
||||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
import java.util.Comparator;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Comparator;
|
|
||||||
|
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||||
|
|
||||||
class FileComparator implements Comparator<File> {
|
class FileComparator implements Comparator<File> {
|
||||||
final static int SORT_BY_NAME = 1111;
|
final static int SORT_BY_NAME = 1111;
|
||||||
|
@ -59,7 +59,7 @@ class FileComparator implements Comparator<File> {
|
||||||
else if (sortBy == SORT_BY_TIME) {
|
else if (sortBy == SORT_BY_TIME) {
|
||||||
if (model.isDirectory(file1)) {
|
if (model.isDirectory(file1)) {
|
||||||
if (model.isDirectory(file2)) {
|
if (model.isDirectory(file2)) {
|
||||||
return compare(file1.lastModified(), file2.lastModified());
|
return Long.compare(file1.lastModified(), file2.lastModified());
|
||||||
}
|
}
|
||||||
return -1; // dirs come before files
|
return -1; // dirs come before files
|
||||||
}
|
}
|
||||||
|
@ -73,24 +73,11 @@ class FileComparator implements Comparator<File> {
|
||||||
value = file1.getName().compareToIgnoreCase(file2.getName());
|
value = file1.getName().compareToIgnoreCase(file2.getName());
|
||||||
}
|
}
|
||||||
else if (sortBy == SORT_BY_SIZE) {
|
else if (sortBy == SORT_BY_SIZE) {
|
||||||
value = compare(file1.length(), file2.length());
|
value = Long.compare(file1.length(), file2.length());
|
||||||
}
|
}
|
||||||
else if (sortBy == SORT_BY_TIME) {
|
else if (sortBy == SORT_BY_TIME) {
|
||||||
value = compare(file1.lastModified(), file2.lastModified());
|
value = Long.compare(file1.lastModified(), file2.lastModified());
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int compare(long l1, long l2) {
|
|
||||||
if (l1 == l2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l1 - l2 > 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,10 @@ import ghidra.framework.Platform;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
import ghidra.util.filechooser.*;
|
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||||
|
import ghidra.util.filechooser.GhidraFileFilter;
|
||||||
import ghidra.util.layout.PairLayout;
|
import ghidra.util.layout.PairLayout;
|
||||||
|
import ghidra.util.task.SwingUpdateManager;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import ghidra.util.worker.Job;
|
import ghidra.util.worker.Job;
|
||||||
import ghidra.util.worker.Worker;
|
import ghidra.util.worker.Worker;
|
||||||
|
@ -68,8 +70,7 @@ import util.HistoryList;
|
||||||
* <li>This class provides shortcut buttons similar to those of the Windows native chooser</li>
|
* <li>This class provides shortcut buttons similar to those of the Windows native chooser</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*/
|
*/
|
||||||
public class GhidraFileChooser extends DialogComponentProvider
|
public class GhidraFileChooser extends DialogComponentProvider implements FileFilter {
|
||||||
implements GhidraFileChooserListener, FileFilter {
|
|
||||||
|
|
||||||
static final String UP_BUTTON_NAME = "UP_BUTTON";
|
static final String UP_BUTTON_NAME = "UP_BUTTON";
|
||||||
private static final Color FOREROUND_COLOR = Color.BLACK;
|
private static final Color FOREROUND_COLOR = Color.BLACK;
|
||||||
|
@ -198,6 +199,7 @@ public class GhidraFileChooser extends DialogComponentProvider
|
||||||
private boolean wasCancelled;
|
private boolean wasCancelled;
|
||||||
private boolean multiSelectionEnabled;
|
private boolean multiSelectionEnabled;
|
||||||
private FileChooserActionManager actionManager;
|
private FileChooserActionManager actionManager;
|
||||||
|
private SwingUpdateManager modelUpdater = new SwingUpdateManager(this::updateDirectoryModels);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last input component to take focus (the text field or file view).
|
* The last input component to take focus (the text field or file view).
|
||||||
|
@ -243,7 +245,7 @@ public class GhidraFileChooser extends DialogComponentProvider
|
||||||
|
|
||||||
private void init(GhidraFileChooserModel newModel) {
|
private void init(GhidraFileChooserModel newModel) {
|
||||||
this.fileChooserModel = newModel;
|
this.fileChooserModel = newModel;
|
||||||
this.fileChooserModel.setListener(this);
|
this.fileChooserModel.setModelUpdateCallback(modelUpdater::update);
|
||||||
|
|
||||||
history.setAllowDuplicates(true);
|
history.setAllowDuplicates(true);
|
||||||
|
|
||||||
|
@ -410,10 +412,9 @@ public class GhidraFileChooser extends DialogComponentProvider
|
||||||
filterCombo = new GComboBox<>();
|
filterCombo = new GComboBox<>();
|
||||||
filterCombo.setRenderer(GListCellRenderer.createDefaultCellTextRenderer(
|
filterCombo.setRenderer(GListCellRenderer.createDefaultCellTextRenderer(
|
||||||
fileFilter -> fileFilter != null ? fileFilter.getDescription() : ""));
|
fileFilter -> fileFilter != null ? fileFilter.getDescription() : ""));
|
||||||
filterCombo.addItemListener(e -> rescanCurrentDirectory());
|
|
||||||
|
|
||||||
filterModel = (DefaultComboBoxModel<GhidraFileFilter>) filterCombo.getModel();
|
filterModel = (DefaultComboBoxModel<GhidraFileFilter>) filterCombo.getModel();
|
||||||
addFileFilter(GhidraFileFilter.ALL);
|
addFileFilter(GhidraFileFilter.ALL);
|
||||||
|
filterCombo.addItemListener(e -> rescanCurrentDirectory());
|
||||||
|
|
||||||
JPanel filenamePanel = new JPanel(new PairLayout(PAD, PAD));
|
JPanel filenamePanel = new JPanel(new PairLayout(PAD, PAD));
|
||||||
filenamePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
filenamePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||||
|
@ -607,12 +608,9 @@ public class GhidraFileChooser extends DialogComponentProvider
|
||||||
// End Setup Methods
|
// End Setup Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
@Override
|
private void updateDirectoryModels() {
|
||||||
public void modelChanged() {
|
directoryListModel.update();
|
||||||
SystemUtilities.runSwingLater(() -> {
|
directoryTableModel.update();
|
||||||
directoryListModel.update();
|
|
||||||
directoryTableModel.update();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2003,8 +2001,8 @@ public class GhidraFileChooser extends DialogComponentProvider
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File[] files = fileChooserModel.getListing(directory, GhidraFileChooser.this);
|
loadedFiles =
|
||||||
loadedFiles = Arrays.asList(files);
|
new ArrayList<>(fileChooserModel.getListing(directory, GhidraFileChooser.this));
|
||||||
Collections.sort(loadedFiles, new FileComparator(fileChooserModel));
|
Collections.sort(loadedFiles, new FileComparator(fileChooserModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2019,26 +2017,30 @@ public class GhidraFileChooser extends DialogComponentProvider
|
||||||
private class UpdateMyComputerJob extends FileChooserJob {
|
private class UpdateMyComputerJob extends FileChooserJob {
|
||||||
|
|
||||||
private final File myComputerFile;
|
private final File myComputerFile;
|
||||||
private final boolean addToHistory;
|
|
||||||
private final boolean forceUpdate;
|
private final boolean forceUpdate;
|
||||||
private List<File> roots;
|
private List<File> roots;
|
||||||
|
|
||||||
public UpdateMyComputerJob(File myComputerFile, boolean forceUpdate, boolean addToHistory) {
|
public UpdateMyComputerJob(File myComputerFile, boolean forceUpdate, boolean addToHistory) {
|
||||||
this.myComputerFile = myComputerFile;
|
this.myComputerFile = myComputerFile;
|
||||||
this.addToHistory = addToHistory;
|
|
||||||
this.forceUpdate = forceUpdate;
|
this.forceUpdate = forceUpdate;
|
||||||
|
setCurrentDirectoryDisplay(myComputerFile, addToHistory);
|
||||||
|
setWaitPanelVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
roots = Arrays.asList(fileChooserModel.getRoots(forceUpdate));
|
if (fileChooserModel == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roots = new ArrayList<>(fileChooserModel.getRoots(forceUpdate));
|
||||||
Collections.sort(roots);
|
Collections.sort(roots);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void runSwing() {
|
public void runSwing() {
|
||||||
setCurrentDirectoryDisplay(myComputerFile, addToHistory);
|
|
||||||
setDirectoryList(myComputerFile, roots);
|
setDirectoryList(myComputerFile, roots);
|
||||||
|
setWaitPanelVisible(false);
|
||||||
|
Swing.runLater(() -> doSetSelectedFileAndUpdateDisplay(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.filechooser;
|
package docking.widgets.filechooser;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
|
@ -25,66 +26,49 @@ import javax.swing.Icon;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.filechooser.FileSystemView;
|
import javax.swing.filechooser.FileSystemView;
|
||||||
|
|
||||||
import ghidra.util.filechooser.GhidraFileChooserListener;
|
|
||||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
import utility.function.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default implementation of the file chooser model
|
* A default implementation of the file chooser model that browses the local file system.
|
||||||
* that browses the local file system.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class LocalFileChooserModel implements GhidraFileChooserModel {
|
public class LocalFileChooserModel implements GhidraFileChooserModel {
|
||||||
private static final ImageIcon PROBLEM_FILE_ICON =
|
private static final ImageIcon PROBLEM_FILE_ICON =
|
||||||
ResourceManager.loadImage("images/unknown.gif");
|
ResourceManager.loadImage("images/unknown.gif");
|
||||||
|
private static final ImageIcon PENDING_ROOT_ICON =
|
||||||
|
ResourceManager.loadImage("images/famfamfam_silk_icons_v013/drive.png");
|
||||||
|
|
||||||
private FileSystemView fsView = FileSystemView.getFileSystemView();
|
private static final FileSystemRootInfo FS_ROOT_INFO = new FileSystemRootInfo();
|
||||||
private Map<File, String> rootDescripMap = new HashMap<>();
|
private static final FileSystemView FS_VIEW = FileSystemView.getFileSystemView();
|
||||||
private Map<File, Icon> rootIconMap = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a cache of file icons, as returned from the OS's file icon service.
|
* This is a cache of file icons, as returned from the OS's file icon service.
|
||||||
* <p>
|
* <p>
|
||||||
* This cache is cleared each time a directory is requested (via {@link #getListing(File, FileFilter)}
|
* This cache is cleared each time a directory is requested (via
|
||||||
* so that any changes to a file's icon are visible the next time the user hits
|
* {@link #getListing(File, FileFilter)} so that any changes to a file's icon are visible the
|
||||||
* refresh or navigates into a directory.
|
* next time the user hits refresh or navigates into a directory.
|
||||||
*/
|
*/
|
||||||
private Map<File, Icon> fileIconMap = new HashMap<>();
|
private Map<File, Icon> fileIconMap = new HashMap<>();
|
||||||
|
|
||||||
private File[] roots = new File[0];
|
private Callback callback;
|
||||||
private GhidraFileChooserListener listener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#getSeparator()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public char getSeparator() {
|
public char getSeparator() {
|
||||||
return File.separatorChar;
|
return File.separatorChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#setListener(ghidra.util.filechooser.GhidraFileChooserListener)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setListener(GhidraFileChooserListener l) {
|
public void setModelUpdateCallback(Callback callback) {
|
||||||
this.listener = l;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#getHomeDirectory()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public File getHomeDirectory() {
|
public File getHomeDirectory() {
|
||||||
return new File(System.getProperty("user.home"));
|
return new File(System.getProperty("user.home"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Probes for a "Desktop" directory under the user's home directory.
|
|
||||||
* <p>
|
|
||||||
* Returns null if the desktop directory is missing.
|
|
||||||
* <p>
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#getDesktopDirectory()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public File getDesktopDirectory() {
|
public File getDesktopDirectory() {
|
||||||
String userHomeProp = System.getProperty("user.home");
|
String userHomeProp = System.getProperty("user.home");
|
||||||
|
@ -99,118 +83,41 @@ public class LocalFileChooserModel implements GhidraFileChooserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File[] getRoots(boolean forceUpdate) {
|
public List<File> getRoots(boolean forceUpdate) {
|
||||||
if (roots.length == 0 || forceUpdate) {
|
if (FS_ROOT_INFO.isEmpty() || forceUpdate) {
|
||||||
roots = File.listRoots();
|
FS_ROOT_INFO.updateRootInfo(callback);
|
||||||
|
|
||||||
// pre-populate root Description cache mapping with placeholder values that will be
|
|
||||||
// overwritten by the background thread.
|
|
||||||
synchronized (rootDescripMap) {
|
|
||||||
for (File r : roots) {
|
|
||||||
rootDescripMap.put(r, getFastRootDescriptionString(r));
|
|
||||||
rootIconMap.put(r, fsView.getSystemIcon(r));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread backgroundRootScanThread = new FileDescriptionThread();
|
|
||||||
backgroundRootScanThread.start();
|
|
||||||
}
|
}
|
||||||
return roots;
|
return FS_ROOT_INFO.getRoots();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a description string for a file system root. Avoid slow calls (such as {@link FileSystemView#getSystemDisplayName(File)}.
|
|
||||||
* <p>
|
|
||||||
* Used when pre-populating the root description map with values before {@link FileDescriptionThread background thread}
|
|
||||||
* finishes.
|
|
||||||
*/
|
|
||||||
protected String getFastRootDescriptionString(File root) {
|
|
||||||
String fsvSTD = "Unknown status";
|
|
||||||
try {
|
|
||||||
fsvSTD = fsView.getSystemTypeDescription(root);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
//Windows expects the A drive to exist; if it does not exist, an exception results. Ignore it.
|
|
||||||
}
|
|
||||||
return String.format("%s (%s)", fsvSTD, formatRootPathForDisplay(root));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a description string for a root location.
|
|
||||||
* <p>
|
|
||||||
* Called from a {@link FileDescriptionThread background thread} to avoid blocking the UI
|
|
||||||
* while waiting for slow file systems.
|
|
||||||
* <p>
|
|
||||||
* @param root
|
|
||||||
* @return string such as "Local Disk (C:)", "Network Drive (R:)"
|
|
||||||
*/
|
|
||||||
protected String getRootDescriptionString(File root) {
|
|
||||||
// Special case the description of the root of a unix filesystem, otherwise it gets marked as removable
|
|
||||||
if ("/".equals(root.getAbsolutePath())) {
|
|
||||||
return "File system root (/)";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case the description of floppies and removable disks, otherwise delegate to fsView's getSystemDisplayName.
|
|
||||||
if (fsView.isFloppyDrive(root)) {
|
|
||||||
return String.format("Floppy (%s)", root.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
String fsvSTD = null;
|
|
||||||
try {
|
|
||||||
fsvSTD = fsView.getSystemTypeDescription(root);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
//Windows expects the A drive to exist; if it does not exist, an exception results. Ignore it
|
|
||||||
}
|
|
||||||
if (fsvSTD == null || fsvSTD.toLowerCase().indexOf("removable") != -1) {
|
|
||||||
return "Removable Disk (" + root.getAbsolutePath() + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
// call the (possibly slow) fsv's getSystemDisplayName
|
|
||||||
return fsView.getSystemDisplayName(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the string path of a file system root, formatted so it doesn't have a trailing backslash in the case
|
|
||||||
* of Windows root drive strings such as "c:\\", which becomes "c:"
|
|
||||||
*/
|
|
||||||
protected String formatRootPathForDisplay(File root) {
|
|
||||||
String s = root.getAbsolutePath();
|
|
||||||
return s.length() > 1 && s.endsWith("\\") ? s.substring(0, s.length() - 1) : s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#getListing(java.io.File, java.io.FileFilter)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public File[] getListing(File directory, final FileFilter filter) {
|
public List<File> getListing(File directory, FileFilter filter) {
|
||||||
// This clears the previously cached icons and avoids issues with modifying the map
|
// This clears the previously cached icons and avoids issues with modifying the map
|
||||||
// while its being used by other methods by throwing away the instance and allocating
|
// while its being used by other methods by throwing away the instance and allocating
|
||||||
// a new one.
|
// a new one.
|
||||||
fileIconMap = new HashMap<>();
|
fileIconMap = new HashMap<>();
|
||||||
|
|
||||||
if (directory == null) {
|
if (directory == null) {
|
||||||
return new File[0];
|
return List.of();
|
||||||
}
|
}
|
||||||
File[] files = directory.listFiles(filter);
|
File[] files = directory.listFiles(filter);
|
||||||
return (files == null) ? new File[0] : files;
|
return (files == null) ? List.of() : List.of(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#getIcon(java.io.File)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Icon getIcon(File file) {
|
public Icon getIcon(File file) {
|
||||||
Icon result = rootIconMap.get(file);
|
if (FS_ROOT_INFO.isRoot(file)) {
|
||||||
if (result == null && file != null && file.exists()) {
|
return FS_ROOT_INFO.getRootIcon(file);
|
||||||
result = fileIconMap.computeIfAbsent(file, this::getSystemIcon);
|
|
||||||
}
|
}
|
||||||
|
Icon result = (file != null && file.exists())
|
||||||
|
? fileIconMap.computeIfAbsent(file, this::getSystemIcon)
|
||||||
|
: null;
|
||||||
return (result != null) ? result : PROBLEM_FILE_ICON;
|
return (result != null) ? result : PROBLEM_FILE_ICON;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Icon getSystemIcon(File file) {
|
private Icon getSystemIcon(File file) {
|
||||||
try {
|
try {
|
||||||
return fsView.getSystemIcon(file);
|
return FS_VIEW.getSystemIcon(file);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
// ignore, return null
|
// ignore, return null
|
||||||
|
@ -218,45 +125,25 @@ public class LocalFileChooserModel implements GhidraFileChooserModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#getDescription(java.io.File)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription(File file) {
|
public String getDescription(File file) {
|
||||||
synchronized (rootDescripMap) {
|
if (FS_ROOT_INFO.isRoot(file)) {
|
||||||
if (rootDescripMap.containsKey(file)) {
|
return FS_ROOT_INFO.getRootDescriptionString(file);
|
||||||
return rootDescripMap.get(file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return fsView.getSystemTypeDescription(file);
|
return FS_VIEW.getSystemTypeDescription(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#createDirectory(java.io.File, java.lang.String)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean createDirectory(File directory, String name) {
|
public boolean createDirectory(File directory, String name) {
|
||||||
File newDir = new File(directory, name);
|
File newDir = new File(directory, name);
|
||||||
return newDir.mkdir();
|
return newDir.mkdir();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#isDirectory(java.io.File)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectory(File file) {
|
public boolean isDirectory(File file) {
|
||||||
File[] localRoots = getRoots(false);
|
return file != null && (FS_ROOT_INFO.isRoot(file) || file.isDirectory());
|
||||||
for (int i = 0; i < localRoots.length; i++) {
|
|
||||||
if (localRoots[i].equals(file)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return file != null && file.isDirectory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#isAbsolute(java.io.File)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAbsolute(File file) {
|
public boolean isAbsolute(File file) {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
|
@ -265,36 +152,195 @@ public class LocalFileChooserModel implements GhidraFileChooserModel {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see ghidra.util.filechooser.GhidraFileChooserModel#renameFile(java.io.File, java.io.File)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean renameFile(File src, File dest) {
|
public boolean renameFile(File src, File dest) {
|
||||||
for (File root : roots) {
|
if (FS_ROOT_INFO.isRoot(src)) {
|
||||||
if (root.equals(src)) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return src.renameTo(dest);
|
return src.renameTo(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FileDescriptionThread extends Thread {
|
//---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
FileDescriptionThread() {
|
/**
|
||||||
super("File Chooser - File Description Thread");
|
* Handles querying / caching information about file system root locations.
|
||||||
|
* <p>
|
||||||
|
* Only a single instance of this class is needed and can be shared statically.
|
||||||
|
*/
|
||||||
|
private static class FileSystemRootInfo {
|
||||||
|
private Map<File, String> descriptionMap = new ConcurrentHashMap<>();
|
||||||
|
private Map<File, Icon> iconMap = new ConcurrentHashMap<>();
|
||||||
|
private List<File> roots = List.of();
|
||||||
|
private AtomicBoolean updatePending = new AtomicBoolean();
|
||||||
|
|
||||||
|
synchronized boolean isEmpty() {
|
||||||
|
return roots.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
synchronized boolean isRoot(File f) {
|
||||||
public void run() {
|
for (File root : roots) {
|
||||||
synchronized (rootDescripMap) {
|
if (root.equals(f)) {
|
||||||
for (File r : roots) {
|
return true;
|
||||||
rootDescripMap.put(r, getRootDescriptionString(r));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (listener != null) {
|
return false;
|
||||||
listener.modelChanged();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently known root locations.
|
||||||
|
*
|
||||||
|
* @return list of currently known root locations
|
||||||
|
*/
|
||||||
|
synchronized List<File> getRoots() {
|
||||||
|
return new ArrayList<>(roots);
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon getRootIcon(File root) {
|
||||||
|
return iconMap.get(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRootDescriptionString(File root) {
|
||||||
|
return descriptionMap.get(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is no pending update, updates information about the root filesystem locations
|
||||||
|
* present on the local computer, in a partially blocking manner. The initial list
|
||||||
|
* of locations is queried directly, and the descriptions and icons for the root
|
||||||
|
* locations are fetched in a background thread.
|
||||||
|
* <p>
|
||||||
|
* When new information is found during the background querying, the listener callback
|
||||||
|
* will be executed so that it can cause UI updates.
|
||||||
|
* <p>
|
||||||
|
* If there is a pending background update, no-op.
|
||||||
|
*
|
||||||
|
* @param callback callback
|
||||||
|
*/
|
||||||
|
void updateRootInfo(Callback callback) {
|
||||||
|
if (updatePending.compareAndSet(false, true)) {
|
||||||
|
File[] localRoots = listRoots(); // possibly sloooow
|
||||||
|
synchronized (this) {
|
||||||
|
roots = List.of(localRoots);
|
||||||
|
}
|
||||||
|
for (File root : localRoots) {
|
||||||
|
descriptionMap.put(root, getInitialRootDescriptionString(root));
|
||||||
|
iconMap.put(root, PENDING_ROOT_ICON);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread updateThread = new Thread(
|
||||||
|
() -> asyncUpdateRootInfo(localRoots, Callback.dummyIfNull(callback)));
|
||||||
|
updateThread.setName("GhidraFileChooser File System Updater");
|
||||||
|
updateThread.start();
|
||||||
|
// updateThread will unset the updatePending flag when done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File[] listRoots() {
|
||||||
|
File[] tmpRoots = File.listRoots(); // possibly sloooow
|
||||||
|
// File.listRoots javadoc says null result possible (but actual jdk code doesn't do it)
|
||||||
|
return tmpRoots != null ? tmpRoots : new File[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void asyncUpdateRootInfo(File[] localRoots, Callback callback) {
|
||||||
|
try {
|
||||||
|
// Populate root description strings with values that are hopefully faster to
|
||||||
|
// get than the full description strings that will be fetched next.
|
||||||
|
for (File root : localRoots) {
|
||||||
|
String fastRootDescriptionString = getFastRootDescriptionString(root);
|
||||||
|
if (fastRootDescriptionString != null) {
|
||||||
|
descriptionMap.put(root, fastRootDescriptionString);
|
||||||
|
callback.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate root description strings with final values, and icons
|
||||||
|
for (File root : localRoots) {
|
||||||
|
String slowRootDescriptionString = getSlowRootDescriptionString(root);
|
||||||
|
if (slowRootDescriptionString != null) {
|
||||||
|
descriptionMap.put(root, slowRootDescriptionString);
|
||||||
|
callback.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon rootIcon = FS_VIEW.getSystemIcon(root); // possibly a slow call
|
||||||
|
iconMap.put(root, rootIcon);
|
||||||
|
callback.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
updatePending.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getInitialRootDescriptionString(File root) {
|
||||||
|
return String.format("Unknown (%s)", formatRootPathForDisplay(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a description string for a file system root. Avoid slow calls (such as
|
||||||
|
* {@link FileSystemView#getSystemDisplayName(File)}.
|
||||||
|
* <p>
|
||||||
|
* @param root file location
|
||||||
|
* @return formatted description string, example "Local Drive (C:)"
|
||||||
|
*/
|
||||||
|
private String getFastRootDescriptionString(File root) {
|
||||||
|
try {
|
||||||
|
String fsvSTD = FS_VIEW.getSystemTypeDescription(root);
|
||||||
|
return String.format("%s (%s)", fsvSTD, formatRootPathForDisplay(root));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
//Windows expects the A drive to exist; if it does not exist, an exception results.
|
||||||
|
//Ignore it.
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the string path of a file system root, formatted so it doesn't have a trailing
|
||||||
|
* backslash in the case of Windows root drive strings such as "c:\\", which becomes "c:"
|
||||||
|
*
|
||||||
|
* @param root file location
|
||||||
|
* @return string path, formatted to not contain unneeded trailing slashes, example "C:"
|
||||||
|
* instead of "C:\\"
|
||||||
|
*/
|
||||||
|
private String formatRootPathForDisplay(File root) {
|
||||||
|
String s = root.getPath();
|
||||||
|
return s.length() > 1 && s.endsWith("\\") ? s.substring(0, s.length() - 1) : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a description string for a root location.
|
||||||
|
* <p>
|
||||||
|
* @param root location to get description string
|
||||||
|
* @return string such as "Local Disk (C:)", "Network Drive (R:)"
|
||||||
|
*/
|
||||||
|
private String getSlowRootDescriptionString(File root) {
|
||||||
|
// Special case the description of the root of a unix filesystem, otherwise it gets
|
||||||
|
// marked as removable
|
||||||
|
if ("/".equals(root.getPath())) {
|
||||||
|
return "File system root (/)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case the description of floppies and removable disks, otherwise delegate to
|
||||||
|
// fsView's getSystemDisplayName.
|
||||||
|
if (FS_VIEW.isFloppyDrive(root)) {
|
||||||
|
return String.format("Floppy (%s)", formatRootPathForDisplay(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
String fsvSTD = null;
|
||||||
|
try {
|
||||||
|
fsvSTD = FS_VIEW.getSystemTypeDescription(root);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
//Windows expects the A drive to exist; if it does not exist, an exception results.
|
||||||
|
//Ignore it
|
||||||
|
}
|
||||||
|
if (fsvSTD == null || fsvSTD.toLowerCase().indexOf("removable") != -1) {
|
||||||
|
return String.format("Removable Disk (%s)", formatRootPathForDisplay(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the (possibly slow) fsv's getSystemDisplayName
|
||||||
|
return FS_VIEW.getSystemDisplayName(root);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1338,8 +1338,8 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||||
|
|
||||||
DirectoryList dirlist = getListView();
|
DirectoryList dirlist = getListView();
|
||||||
DirectoryListModel listModel = (DirectoryListModel) dirlist.getModel();
|
DirectoryListModel listModel = (DirectoryListModel) dirlist.getModel();
|
||||||
File[] roots = chooser.getModel().getRoots(false);
|
List<File> roots = chooser.getModel().getRoots(false);
|
||||||
assertEquals(roots.length, listModel.getSize());
|
assertEquals(roots.size(), listModel.getSize());
|
||||||
for (File element : roots) {
|
for (File element : roots) {
|
||||||
listModel.contains(element);
|
listModel.contains(element);
|
||||||
}
|
}
|
||||||
|
@ -1450,8 +1450,8 @@ public class GhidraFileChooserTest extends AbstractDockingTest {
|
||||||
// check the chooser contents
|
// check the chooser contents
|
||||||
DirectoryList dirlist = getListView();
|
DirectoryList dirlist = getListView();
|
||||||
DirectoryListModel listModel = (DirectoryListModel) dirlist.getModel();
|
DirectoryListModel listModel = (DirectoryListModel) dirlist.getModel();
|
||||||
File[] listing = chooser.getModel().getListing(homeDir, null);
|
List<File> listing = chooser.getModel().getListing(homeDir, null);
|
||||||
assertEquals(listing.length, listModel.getSize());
|
assertEquals(listing.size(), listModel.getSize());
|
||||||
for (File element : listing) {
|
for (File element : listing) {
|
||||||
listModel.contains(element);
|
listModel.contains(element);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.util.filechooser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for notifying when the contents
|
|
||||||
* of the file chooser model have changed.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface GhidraFileChooserListener {
|
|
||||||
/**
|
|
||||||
* Invoked when the contents of the file
|
|
||||||
* chooser model have changed.
|
|
||||||
*/
|
|
||||||
public void modelChanged();
|
|
||||||
}
|
|
|
@ -15,11 +15,15 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.util.filechooser;
|
package ghidra.util.filechooser;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
import utility.function.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for the GhidraFileChooser data model.
|
* Interface for the GhidraFileChooser data model.
|
||||||
* This allows the GhidraFileChooser to operate
|
* This allows the GhidraFileChooser to operate
|
||||||
|
@ -28,13 +32,15 @@ import javax.swing.Icon;
|
||||||
*/
|
*/
|
||||||
public interface GhidraFileChooserModel {
|
public interface GhidraFileChooserModel {
|
||||||
/**
|
/**
|
||||||
* Set the model listener.
|
* Set the model update callback.
|
||||||
* @param l the new model listener
|
*
|
||||||
|
* @param callback the new model update callback handler
|
||||||
*/
|
*/
|
||||||
public void setListener(GhidraFileChooserListener l);
|
public void setModelUpdateCallback(Callback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the home directory.
|
* Returns the home directory.
|
||||||
|
*
|
||||||
* @return the home directory
|
* @return the home directory
|
||||||
*/
|
*/
|
||||||
public File getHomeDirectory();
|
public File getHomeDirectory();
|
||||||
|
@ -43,12 +49,13 @@ public interface GhidraFileChooserModel {
|
||||||
* Returns the user's desktop directory, as defined by their operating system and/or their windowing environment, or
|
* Returns the user's desktop directory, as defined by their operating system and/or their windowing environment, or
|
||||||
* null if there is no desktop directory.<p>
|
* null if there is no desktop directory.<p>
|
||||||
* Example: "/home/the_user/Desktop" or "c:/Users/the_user/Desktop"
|
* Example: "/home/the_user/Desktop" or "c:/Users/the_user/Desktop"
|
||||||
|
*
|
||||||
* @return desktop directory
|
* @return desktop directory
|
||||||
*/
|
*/
|
||||||
public File getDesktopDirectory();
|
public File getDesktopDirectory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the root drives/directories.
|
* Returns a list of the root drives/directories.
|
||||||
* <p>
|
* <p>
|
||||||
* On windows, "C:\", "D:\", etc.
|
* On windows, "C:\", "D:\", etc.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -57,18 +64,20 @@ public interface GhidraFileChooserModel {
|
||||||
* @param forceUpdate if true, request a fresh listing, if false allow a cached result
|
* @param forceUpdate if true, request a fresh listing, if false allow a cached result
|
||||||
* @return the root drives
|
* @return the root drives
|
||||||
*/
|
*/
|
||||||
public File[] getRoots(boolean forceUpdate);
|
public List<File> getRoots(boolean forceUpdate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of the files that
|
* Returns an array of the files that
|
||||||
* exist in the specified directory.
|
* exist in the specified directory.
|
||||||
|
*
|
||||||
* @param directory the directory
|
* @param directory the directory
|
||||||
* @return an array of files
|
* @return list of files
|
||||||
*/
|
*/
|
||||||
public File[] getListing(File directory, FileFilter filter);
|
public List<File> getListing(File directory, FileFilter filter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an icon for the specified file.
|
* Returns an icon for the specified file.
|
||||||
|
*
|
||||||
* @param file the file
|
* @param file the file
|
||||||
* @return an icon for the specified file
|
* @return an icon for the specified file
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue