Merge remote-tracking branch

'origin/GP-7144-dragonmacher-front-end-natural-sort--SQUASHED'
(Closes #7093)
This commit is contained in:
Ryan Kurtz
2026-08-20 10:48:25 -04:00
14 changed files with 392 additions and 166 deletions

View File

@@ -464,6 +464,17 @@
<TD valign="top" align="left">This controls whether or not Ghidra will compress
data being sent to the server.</TD>
</TR>
<TR>
<TD valign="top" width="200" align="left">Use Natural Project Sort</TD>
<TD valign="top" align="left">This controls whether to use a natural sort for
project files in the tree and table. When true, runs of digits in filenames will be
considered as a single integer value when sorting.
</TD>
</TR>
<TR>
<TD valign="top" width="200" align="left">Use Notification Animation</TD>

View File

@@ -23,6 +23,16 @@ import java.util.Comparator;
*/
public class AlphaNumericComparator implements Comparator<String> {
private boolean caseSensitive;
public AlphaNumericComparator() {
this.caseSensitive = true;
}
public AlphaNumericComparator(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
@Override
public int compare(String s1, String s2) {
if (s1 == null || s2 == null) {
@@ -66,9 +76,11 @@ public class AlphaNumericComparator implements Comparator<String> {
}
else {
// Compare standard characters alphabetically
if (c1 != c2) {
return Character.compare(c1, c2);
int result = compare(c1, c2);
if (result != 0) {
return result;
}
i++;
j++;
}
@@ -98,6 +110,19 @@ public class AlphaNumericComparator implements Comparator<String> {
return s1.compareTo(s2);
}
private int compare(char c1, char c2) {
if (c1 == c2) {
return 0;
}
if (!caseSensitive) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
}
return Character.compare(c1, c2);
}
private long parseNumber(String s, int i) {
long value = 0;
while (i < s.length()) {

View File

@@ -26,7 +26,7 @@ import generic.test.AbstractGenericTest;
public class AlphaNumericComparatorTest extends AbstractGenericTest {
@Test
public void testIt() {
public void testCaseSensitive() {
List<String> unsorted = Arrays.asList(
@@ -81,4 +81,61 @@ public class AlphaNumericComparatorTest extends AbstractGenericTest {
assertEquals(sorted, copy);
}
@Test
public void testCaseInsensitive() {
List<String> unsorted = Arrays.asList(
" file10", // Leading spaces
"file02 ", // Trailing spaces
"file 2", // Internal spaces
"file1", // Normal
" file002 ", // Leading and trailing spaces
"file 01", // Internal space before digit
"file20",
" apple", // Leading spaces
"Apple ", // Trailing spaces
"ba nana", // Internal spaces
"a",
"alpha ", // Trailing spaces
" alphabet", // Leading spaces
"app",
"ap ple", // Internal spaces
"0.1.0",
"0.1.9",
"1.0",
"0.2.1",
"2.1.0");
List<String> sorted = Arrays.asList(
"0.1.0",
"0.1.9",
"0.2.1",
"1.0",
"2.1.0",
"a",
"alpha ",
" alphabet",
"app",
" apple",
"Apple ",
"ap ple",
"ba nana",
"file1",
"file 01",
"file 2",
"file02 ",
" file002 ",
" file10",
"file20");
Collections.shuffle(unsorted);
List<String> copy = new ArrayList<>(unsorted);
Collections.sort(copy, new AlphaNumericComparator(false));
assertEquals(sorted, copy);
}
}

View File

@@ -193,6 +193,10 @@ public class FrontEndPlugin extends Plugin
createToolSpecificOpenActions();
}
void setUseNaturalSort(boolean b) {
projectDataPanel.setUseNaturalSort(b);
}
protected void createToolSpecificOpenActions() {
for (DockingAction action : openActions) {
tool.removeAction(action);

View File

@@ -88,15 +88,17 @@ import help.HelpService;
* manner.
*/
public class FrontEndTool extends PluginTool implements OptionsChangeListener {
public static final String DEFAULT_TOOL_LAUNCH_MODE = "Default Tool Launch Mode";
public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools";
private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation";
private static final String USE_COMBINED_ALT_GRAPH_OPTION_NAME = "Use Combined Alt Keys";
private static final String SHOW_TOOLTIPS_OPTION_NAME = "Show Tooltips";
private static final String BLINKING_CURSORS_OPTION_NAME = "Allow Blinking Cursors";
private static final String ENABLE_COMPRESSED_DATABUFFER_OUTPUT =
private static final String BLINKING_CURSORS_OPTION_NAME = "Allow Blinking Cursors";
public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools";
public static final String DEFAULT_TOOL_LAUNCH_MODE = "Default Tool Launch Mode";
private static final String SHOW_TOOLTIPS_OPTION_NAME = "Show Tooltips";
private static final String USE_COMPRESSED_DATABUFFER_OUTPUT =
"Use DataBuffer Output Compression";
private static final String USE_NATURAL_SORT = "Use Natural File Sort";
private static final String USE_COMBINED_ALT_GRAPH_OPTION_NAME = "Use Combined Alt Keys";
private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation";
private static final Boolean ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT = true;
private static final String RESTORE_PREVIOUS_PROJECT_NAME = "Restore Previous Project";
@@ -356,7 +358,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
options.registerOption(SHOW_TOOLTIPS_OPTION_NAME, true, help,
"Controls the display of tooltip popup windows.");
options.registerOption(ENABLE_COMPRESSED_DATABUFFER_OUTPUT,
options.registerOption(USE_COMPRESSED_DATABUFFER_OUTPUT,
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT, help,
"When enabled data buffers sent to Ghidra Server are compressed (see server " +
"configuration for other direction)");
@@ -367,6 +369,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
options.registerOption(RESTORE_PREVIOUS_PROJECT_NAME, true, help,
"Restore the previous project when Ghidra starts.");
options.registerOption(USE_NATURAL_SORT, true, help,
"Use a natural sort for program files with numeric digits treated as numeric values.");
defaultLaunchMode = options.getEnum(DEFAULT_TOOL_LAUNCH_MODE, defaultLaunchMode);
boolean autoSave = options.getBoolean(AUTOMATICALLY_SAVE_TOOLS, true);
@@ -382,7 +387,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
DockingUtils.setGlobalTooltipEnabledOption(showToolTips);
boolean compressDataBuffers =
options.getBoolean(ENABLE_COMPRESSED_DATABUFFER_OUTPUT,
options.getBoolean(USE_COMPRESSED_DATABUFFER_OUTPUT,
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT);
DataBuffer.enableCompressedSerializationOutput(compressDataBuffers);
@@ -391,6 +396,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
boolean blink = options.getBoolean(BLINKING_CURSORS_OPTION_NAME, true);
Gui.setBlinkingCursors(blink);
boolean useNaturalSort = options.getBoolean(USE_NATURAL_SORT, true);
plugin.setUseNaturalSort(useNaturalSort);
options.addOptionsChangeListener(this);
}
@@ -412,7 +420,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
else if (SHOW_TOOLTIPS_OPTION_NAME.equals(optionName)) {
DockingUtils.setGlobalTooltipEnabledOption((Boolean) newValue);
}
else if (ENABLE_COMPRESSED_DATABUFFER_OUTPUT.equals(optionName)) {
else if (USE_COMPRESSED_DATABUFFER_OUTPUT.equals(optionName)) {
DataBuffer.enableCompressedSerializationOutput((Boolean) newValue);
}
else if (RESTORE_PREVIOUS_PROJECT_NAME.equals(optionName)) {
@@ -421,6 +429,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
else if (BLINKING_CURSORS_OPTION_NAME.equals(optionName)) {
Gui.setBlinkingCursors((Boolean) newValue);
}
else if (USE_NATURAL_SORT.equals(optionName)) {
plugin.setUseNaturalSort((Boolean) newValue);
}
}
@Override

View File

@@ -19,15 +19,17 @@ import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.*;
import docking.*;
import docking.widgets.tabbedpane.DockingTabRenderer;
import ghidra.framework.client.NotConnectedException;
import ghidra.framework.main.datatable.ProjectDataTableModel;
import ghidra.framework.main.datatable.ProjectDataTablePanel;
import ghidra.framework.main.datatree.DataTreeNode;
import ghidra.framework.main.datatree.ProjectDataTreePanel;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
@@ -212,7 +214,7 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener {
}
try {
// TODO: addProjectView should be done in a model task
// Note: addProjectView should be done in a modal task
ProjectData projectData = activeProject.addProjectView(projectView, true);
if (projectData == null) {
return null; // repository connection may have been cancelled
@@ -365,6 +367,26 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener {
}
void setUseNaturalSort(boolean b) {
// update the view sorting code and then trigger a reload
DataTreeNode.setUseNaturalSort(b);
ProjectDataTableModel.setUseNaturalSort(b);
// the active project
treePanel.reload();
tablePanel.reload();
// read-only views
Set<Entry<ProjectLocator, ProjectDataTreePanel>> entries = readOnlyViews.entrySet();
for (Entry<ProjectLocator, ProjectDataTreePanel> entry : entries) {
ProjectDataTreePanel pdtp = entry.getValue();
if (pdtp != null) {
pdtp.reload();
}
}
}
void setBorder(String projectName) {
projectTab.setBorder(BorderFactory.createTitledBorder(BORDER_PREFIX + projectName));
treePanel.updateProjectName(projectName);

View File

@@ -27,12 +27,15 @@ import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.AlphaNumericComparator;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor;
public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, ProjectData> {
private static boolean useNaturalSort = true;
private ProjectData projectData;
private volatile int modCount;
private boolean editingOn;
@@ -43,6 +46,10 @@ public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, Pr
super("Project Data Table", serviceProvider);
}
public static void setUseNaturalSort(boolean b) {
useNaturalSort = b;
}
boolean loadWasCancelled() {
return loadWasCancelled;
}
@@ -189,6 +196,9 @@ public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, Pr
private class DomainFileNameColumn
extends AbstractDynamicTableColumn<DomainFileInfo, String, ProjectData> {
private static AlphaNumericComparator alphaNumericComparator =
new AlphaNumericComparator(false);
@Override
public String getColumnName() {
return "Name";
@@ -205,6 +215,14 @@ public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, Pr
public int getColumnPreferredWidth() {
return 200;
}
@Override
public Comparator<String> getComparator() {
if (useNaturalSort) {
return alphaNumericComparator;
}
return super.getComparator();
}
}
private class ModificationDateColumn

View File

@@ -255,14 +255,13 @@ public class ProjectDataTablePanel extends JPanel {
}
}
private void reload() {
public void reload() {
checkCapacity();
if (!capacityExceeded) {
model.reload();
}
}
// load the max file count system property

View File

@@ -17,11 +17,14 @@ package ghidra.framework.main.datatree;
import java.util.*;
import javax.swing.Icon;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.GTreeSlowLoadingNode;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.data.LinkHandler.LinkStatus;
import ghidra.framework.model.*;
import ghidra.util.datastruct.AlphaNumericComparator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -36,45 +39,34 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
* sort order is based upon the following comparisons in order of significance:
* <ol>
* <li>Node type weighting. Folder and Folder-Links have equal weighting.</li>
* <li>Node comparison by name (see {@link DataTreeNode#compareNodeNames(String, String)}).</li>
* <li>Node comparison by name.</li>
* <li>Node type ordinal (e.g., ensures that a Folder-Link with the same name as a Folder
* will be placed after the Folder.</li>
* </ol>
*/
enum NodeType {
FOLDER(1), FOLDER_LINK(1), FILE(2), OTHER(3);
FOLDER(1),
FOLDER_LINK(1),
FILE(2);
int weight;
private int weight;
NodeType(int weight) {
this.weight = weight;
}
static NodeType getNodeType(GTreeNode node) {
if (node instanceof DomainFolderNode) {
return FOLDER;
}
if (node instanceof DomainFileNode fileNode) {
return fileNode.isFolderLink() ? FOLDER_LINK : FILE;
}
return OTHER;
}
}
/**
* Sort {@link Comparator} for use with sorting children and node comparison
*/
static final Comparator<GTreeNode> DATA_NODE_SORT_COMPARATOR = new DataNodeSortComparator();
static final Comparator<GTreeNode> DATA_NODE_COMPARATOR = new DataNodeSortComparator();
/**
* Search {@link Comparator} for use by {@link #getChild(String, NodeType)} only
*/
private static final DataNodeSearchComparator DATA_NODE_SEARCH_COMPARATOR =
new DataNodeSearchComparator();
private static boolean useNaturalSort = true;
private volatile boolean isCut; // true if this node is marked as cut
public static void setUseNaturalSort(boolean b) {
useNaturalSort = b;
}
@Override
public final void setIsCut(boolean isCut) {
if (isCut != this.isCut) {
@@ -88,6 +80,8 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
return isCut;
}
protected abstract NodeType getNodeType();
/**
* Get the project data instance to which this file or folder belongs.
* @return project data instance
@@ -113,8 +107,9 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
if (!isLoaded()) {
return;
}
List<GTreeNode> allChildren = getChildren();
int index = Collections.binarySearch(allChildren, newNode, DATA_NODE_SORT_COMPARATOR);
int index = Collections.binarySearch(allChildren, newNode, DATA_NODE_COMPARATOR);
if (index < 0) {
index = -index - 1;
}
@@ -181,138 +176,199 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
* @param type node type
* @return matching tree node or null if not found
*/
@SuppressWarnings("unchecked")
static GTreeNode getChild(List<GTreeNode> children, String name, NodeType type) {
ChildSearchRecord childSearchRecord = new ChildSearchRecord(name, type);
SearchNode key = new SearchNode(name, type);
int index =
Collections.binarySearch(children, childSearchRecord, DATA_NODE_SEARCH_COMPARATOR);
Collections.binarySearch(children, key, DATA_NODE_COMPARATOR);
return index >= 0 ? children.get(index) : null;
}
private record ChildSearchRecord(String name, NodeType type) {
}
@SuppressWarnings("rawtypes")
private static class DataNodeSearchComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
GTreeNode node = (GTreeNode) o1;
ChildSearchRecord childSearchRecord = (ChildSearchRecord) o2;
NodeType type1 = NodeType.getNodeType(node);
NodeType type2 = childSearchRecord.type;
int comp = type1.weight - type2.weight;
if (comp != 0) {
return comp;
}
// NOTE: This name comparison is consistent with the sort order and
// will provide a case-senstive name-match
comp = compareNodeNames(node.getName(), childSearchRecord.name);
if (comp == 0) {
return type1.ordinal() - type2.ordinal();
}
return comp;
}
}
private static class DataNodeSortComparator implements Comparator<GTreeNode> {
@Override
public int compare(GTreeNode o1, GTreeNode o2) {
//
// Goal is to have folders appear before files except for folder-links
// which should be grouped with folders but come after a folder with
// the same name
NodeType type1 = NodeType.getNodeType(o1);
NodeType type2 = NodeType.getNodeType(o2);
int comp = type1.weight - type2.weight;
if (comp != 0) {
return comp;
}
// NOTE: This name comparison is consistent with compareTo implementaions
comp = compareNodeNames(o1.getName(), o2.getName());
if (comp == 0) {
return type1.ordinal() - type2.ordinal();
}
return comp;
}
}
/**
* Name comparison to be used for DataTreeNode comparators and node comparison.
* @param n1 first name
* @param n2 second name
* @return comparison result consistent with {@link String#compareTo(String) n1.compareTo(n2)}
*/
static int compareNodeNames(String n1, String n2) {
int c = n1.compareToIgnoreCase(n2);
if (c == 0) {
// disambiguate for deterministic sort
c = n1.compareTo(n2);
}
return c;
}
/**
* Generate filtered child nodes for a DomainFolder
* @param domainFolder folder
* @param filter filter
* @param monitor load task monitor
* @return list of filtered chidren
* @return list of filtered children
* @throws CancelledException if load task is cancelled
*/
static List<GTreeNode> generateChildren(DomainFolder domainFolder, DomainFileFilter filter,
TaskMonitor monitor) throws CancelledException {
boolean hideFolderLinks = false;
boolean hideBroken = false;
boolean hideExternal = false;
if (filter != null) {
hideFolderLinks = filter.ignoreFolderLinks();
hideBroken = filter.ignoreBrokenLinks();
hideExternal = filter.ignoreExternalLinks();
}
List<GTreeNode> children = new ArrayList<>();
if (domainFolder != null) {
DomainFolder[] folders = domainFolder.getFolders();
for (DomainFolder folder : folders) {
monitor.checkCancelled();
children.add(new DomainFolderNode(folder, filter));
}
DomainFile[] files = domainFolder.getFiles();
for (DomainFile df : files) {
monitor.checkCancelled();
if (filter != null) {
boolean isFolderLink = df.isLink() && df.getLinkInfo().isFolderLink();
if (hideFolderLinks && isFolderLink) {
continue;
}
if ((hideBroken || hideExternal) && df.isLink()) {
LinkStatus linkStatus = LinkHandler.getLinkFileStatus(df, null);
if (hideBroken && linkStatus == LinkStatus.BROKEN) {
continue;
}
if (hideExternal && linkStatus == LinkStatus.EXTERNAL) {
continue;
}
}
if (!isFolderLink && !filter.accept(df)) {
continue;
}
}
children.add(new DomainFileNode(df, filter));
}
if (domainFolder == null) {
return children;
}
Collections.sort(children, DATA_NODE_SORT_COMPARATOR);
DomainFolder[] folders = domainFolder.getFolders();
for (DomainFolder folder : folders) {
monitor.checkCancelled();
children.add(new DomainFolderNode(folder, filter));
}
DomainFile[] files = domainFolder.getFiles();
for (DomainFile df : files) {
monitor.checkCancelled();
if (skip(df, filter)) {
continue;
}
children.add(new DomainFileNode(df, filter));
}
Collections.sort(children, DATA_NODE_COMPARATOR);
return children;
}
private static boolean skip(DomainFile df, DomainFileFilter filter) {
if (filter == null) {
return false;
}
boolean hideFolderLinks = filter.ignoreFolderLinks();
boolean hideBroken = filter.ignoreBrokenLinks();
boolean hideExternal = filter.ignoreExternalLinks();
boolean isFolderLink = df.isLink() && df.getLinkInfo().isFolderLink();
if (hideFolderLinks && isFolderLink) {
return true;
}
if ((hideBroken || hideExternal) && df.isLink()) {
LinkStatus linkStatus = LinkHandler.getLinkFileStatus(df, null);
if (hideBroken && linkStatus == LinkStatus.BROKEN) {
return true;
}
if (hideExternal && linkStatus == LinkStatus.EXTERNAL) {
return true;
}
}
if (!isFolderLink && !filter.accept(df)) {
return true;
}
return false;
}
//=================================================================================================
// Inner Classes
//=================================================================================================
private static class DataNodeSortComparator implements Comparator<GTreeNode> {
private static AlphaNumericComparator alphaNumericComparator =
new AlphaNumericComparator(false);
@Override
public int compare(GTreeNode o1, GTreeNode o2) {
// We want folders appear before files except for folder-links which should be grouped
// with folders but come after a folder with the same name
DataTreeNode dtn1 = (DataTreeNode) o1;
DataTreeNode dtn2 = (DataTreeNode) o2;
NodeType type1 = dtn1.getNodeType();
NodeType type2 = dtn2.getNodeType();
int result = type1.weight - type2.weight;
if (result != 0) {
return result;
}
String n1 = o1.getName();
String n2 = o2.getName();
result = compareNames(n1, n2);
if (result == 0) {
return type1.ordinal() - type2.ordinal();
}
return result;
}
private int compareNames(String n1, String n2) {
if (useNaturalSort) {
return alphaNumericComparator.compare(n1, n2);
}
int result = n1.compareToIgnoreCase(n2);
if (result != 0) {
return result;
}
return n1.compareTo(n2);
}
}
/**
* A dummy search node used to find a child node by the given name and type.
*/
private static class SearchNode extends DataTreeNode {
private String name;
private NodeType nodeType;
SearchNode(String name, NodeType nodeType) {
this.name = name;
this.nodeType = nodeType;
}
@Override
public String getName() {
return name;
}
@Override
protected NodeType getNodeType() {
return nodeType;
}
@Override
public ProjectData getProjectData() {
throw new UnsupportedOperationException();
}
@Override
public String getPathname() {
throw new UnsupportedOperationException();
}
@Override
public int compareTo(GTreeNode node) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
throw new UnsupportedOperationException();
}
@Override
public GTreeNode getChild(String childName, NodeType type) {
throw new UnsupportedOperationException();
}
@Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
throw new UnsupportedOperationException();
}
@Override
public Icon getIcon(boolean expanded) {
throw new UnsupportedOperationException();
}
@Override
public String getToolTip() {
throw new UnsupportedOperationException();
}
@Override
public boolean isLeaf() {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -58,7 +58,7 @@ public class DomainFileNode extends DataTreeNode {
private volatile String toolTipText;
private AtomicInteger refreshCount = new AtomicInteger();
private DomainFileFilter filter; // relavent when expand folder-link which is a file
private DomainFileFilter filter; // relevant when expand folder-link which is a file
private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy MMM dd hh:mm aaa");
@@ -114,6 +114,11 @@ public class DomainFileNode extends DataTreeNode {
return isFolderLink;
}
@Override
protected NodeType getNodeType() {
return isFolderLink ? NodeType.FOLDER_LINK : NodeType.FILE;
}
/**
* Get linked folder which corresponds to this folder-link (see {@link #isFolderLink()}).
* @return linked folder or null if this is not a folder-link
@@ -403,7 +408,7 @@ public class DomainFileNode extends DataTreeNode {
@Override
public int compareTo(GTreeNode node) {
return DATA_NODE_SORT_COMPARATOR.compare(this, node);
return DATA_NODE_COMPARATOR.compare(this, node);
}
@Override

View File

@@ -20,7 +20,6 @@ import java.util.List;
import javax.swing.Icon;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.framework.model.*;
import ghidra.util.*;
@@ -53,13 +52,17 @@ public class DomainFolderNode extends DataTreeNode {
this.domainFolder = domainFolder;
this.filter = filter;
// TODO: how can the folder be null?...doesn't really make sense...I don't think it ever is
if (domainFolder != null) {
setToolTipText();
isEditable = domainFolder.isInWritableProject();
}
}
@Override
protected NodeType getNodeType() {
return NodeType.FOLDER;
}
@Override
public boolean isAutoExpandPermitted() {
// Prevent auto-expansion through linked-folders
@@ -162,7 +165,7 @@ public class DomainFolderNode extends DataTreeNode {
@Override
public int compareTo(GTreeNode node) {
return DATA_NODE_SORT_COMPARATOR.compare(this, node);
return DATA_NODE_COMPARATOR.compare(this, node);
}
@Override

View File

@@ -88,7 +88,7 @@ public class ProjectDataTreePanel extends JPanel {
/**
* Construct an empty data tree panel that is going to be used for the active project tree
* within the frontend tool.
* within the front end tool.
*
* @param plugin front end plugin
*/
@@ -126,6 +126,20 @@ public class ProjectDataTreePanel extends JPanel {
return tree.getSelectionModel();
}
public void reload() {
if (projectData == null) {
return;
}
ProjectLocator locator = projectData.getProjectLocator();
String projectName = locator.getName();
GTreeNode oldRoot = root;
root = createRootNode(projectName);
tree.setRootNode(root);
oldRoot.dispose();
}
/**
* Set the project data for this data tree and populate it with
* nodes for the users in the project.
@@ -528,6 +542,7 @@ public class ProjectDataTreePanel extends JPanel {
* Create the root node for this data tree.
*/
private GTreeNode createRootNode(String projectName) {
if (projectData == null) {
return new NoProjectNode();
}

View File

@@ -143,7 +143,7 @@ public interface Project extends AutoCloseable, Iterable<DomainFile> {
/**
* Allows the user to store data related to the project.
* See {@link #getSaveableData(String)} for future retieval of data.
* See {@link #getSaveableData(String)} for future retrieval of data.
* @param key a value used to store and lookup saved data
* @param saveState a container of data that will be written out when persisted
*/
@@ -212,7 +212,7 @@ public interface Project extends AutoCloseable, Iterable<DomainFile> {
/**
* Return a {@link DomainFile} iterator over all non-link files within this project's data store.
* If links should be followed use an appropropriate static method from {@link ProjectDataUtils}.
* If links should be followed use an appropriate static method from {@link ProjectDataUtils}.
* @return domain file iterator
*/
@Override

View File

@@ -235,7 +235,7 @@ public interface ProjectData extends Iterable<DomainFile> {
* NOTE: The project should be closed and then reopened after this method is called.
* @param newRepository new repository to use
* @param force if true any existing local checkout which is not recognized/valid
* for newRepository will be forceably terminated if offline with old repository.
* for newRepository will be forcibly terminated if offline with old repository.
* @param monitor task monitor
* @throws IOException thrown if files are still checked out, or if there was a problem accessing
* the filesystem
@@ -281,7 +281,7 @@ public interface ProjectData extends Iterable<DomainFile> {
/**
* Return a {@link DomainFile} iterator over all non-link files within this project data store.
* If links should be followed use an appropropriate static method from {@link ProjectDataUtils}.
* If links should be followed use an appropriate static method from {@link ProjectDataUtils}.
* @return domain file iterator
*/
@Override