mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Merge remote-tracking branch
'origin/GP-7144-dragonmacher-front-end-natural-sort--SQUASHED' (Closes #7093)
This commit is contained in:
@@ -464,6 +464,17 @@
|
|||||||
<TD valign="top" align="left">This controls whether or not Ghidra will compress
|
<TD valign="top" align="left">This controls whether or not Ghidra will compress
|
||||||
data being sent to the server.</TD>
|
data being sent to the server.</TD>
|
||||||
</TR>
|
</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>
|
<TR>
|
||||||
<TD valign="top" width="200" align="left">Use Notification Animation</TD>
|
<TD valign="top" width="200" align="left">Use Notification Animation</TD>
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ import java.util.Comparator;
|
|||||||
*/
|
*/
|
||||||
public class AlphaNumericComparator implements Comparator<String> {
|
public class AlphaNumericComparator implements Comparator<String> {
|
||||||
|
|
||||||
|
private boolean caseSensitive;
|
||||||
|
|
||||||
|
public AlphaNumericComparator() {
|
||||||
|
this.caseSensitive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlphaNumericComparator(boolean caseSensitive) {
|
||||||
|
this.caseSensitive = caseSensitive;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(String s1, String s2) {
|
public int compare(String s1, String s2) {
|
||||||
if (s1 == null || s2 == null) {
|
if (s1 == null || s2 == null) {
|
||||||
@@ -66,9 +76,11 @@ public class AlphaNumericComparator implements Comparator<String> {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Compare standard characters alphabetically
|
// Compare standard characters alphabetically
|
||||||
if (c1 != c2) {
|
int result = compare(c1, c2);
|
||||||
return Character.compare(c1, c2);
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
@@ -98,6 +110,19 @@ public class AlphaNumericComparator implements Comparator<String> {
|
|||||||
return s1.compareTo(s2);
|
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) {
|
private long parseNumber(String s, int i) {
|
||||||
long value = 0;
|
long value = 0;
|
||||||
while (i < s.length()) {
|
while (i < s.length()) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import generic.test.AbstractGenericTest;
|
|||||||
public class AlphaNumericComparatorTest extends AbstractGenericTest {
|
public class AlphaNumericComparatorTest extends AbstractGenericTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIt() {
|
public void testCaseSensitive() {
|
||||||
|
|
||||||
List<String> unsorted = Arrays.asList(
|
List<String> unsorted = Arrays.asList(
|
||||||
|
|
||||||
@@ -81,4 +81,61 @@ public class AlphaNumericComparatorTest extends AbstractGenericTest {
|
|||||||
|
|
||||||
assertEquals(sorted, copy);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ public class FrontEndPlugin extends Plugin
|
|||||||
createToolSpecificOpenActions();
|
createToolSpecificOpenActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setUseNaturalSort(boolean b) {
|
||||||
|
projectDataPanel.setUseNaturalSort(b);
|
||||||
|
}
|
||||||
|
|
||||||
protected void createToolSpecificOpenActions() {
|
protected void createToolSpecificOpenActions() {
|
||||||
for (DockingAction action : openActions) {
|
for (DockingAction action : openActions) {
|
||||||
tool.removeAction(action);
|
tool.removeAction(action);
|
||||||
|
|||||||
@@ -88,15 +88,17 @@ import help.HelpService;
|
|||||||
* manner.
|
* manner.
|
||||||
*/
|
*/
|
||||||
public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
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";
|
"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 Boolean ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT = true;
|
||||||
|
|
||||||
private static final String RESTORE_PREVIOUS_PROJECT_NAME = "Restore Previous Project";
|
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,
|
options.registerOption(SHOW_TOOLTIPS_OPTION_NAME, true, help,
|
||||||
"Controls the display of tooltip popup windows.");
|
"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,
|
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT, help,
|
||||||
"When enabled data buffers sent to Ghidra Server are compressed (see server " +
|
"When enabled data buffers sent to Ghidra Server are compressed (see server " +
|
||||||
"configuration for other direction)");
|
"configuration for other direction)");
|
||||||
@@ -367,6 +369,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
|||||||
options.registerOption(RESTORE_PREVIOUS_PROJECT_NAME, true, help,
|
options.registerOption(RESTORE_PREVIOUS_PROJECT_NAME, true, help,
|
||||||
"Restore the previous project when Ghidra starts.");
|
"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);
|
defaultLaunchMode = options.getEnum(DEFAULT_TOOL_LAUNCH_MODE, defaultLaunchMode);
|
||||||
|
|
||||||
boolean autoSave = options.getBoolean(AUTOMATICALLY_SAVE_TOOLS, true);
|
boolean autoSave = options.getBoolean(AUTOMATICALLY_SAVE_TOOLS, true);
|
||||||
@@ -382,7 +387,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
|||||||
DockingUtils.setGlobalTooltipEnabledOption(showToolTips);
|
DockingUtils.setGlobalTooltipEnabledOption(showToolTips);
|
||||||
|
|
||||||
boolean compressDataBuffers =
|
boolean compressDataBuffers =
|
||||||
options.getBoolean(ENABLE_COMPRESSED_DATABUFFER_OUTPUT,
|
options.getBoolean(USE_COMPRESSED_DATABUFFER_OUTPUT,
|
||||||
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT);
|
ENABLE_COMPRESSED_DATABUFFER_OUTPUT_DEFAULT);
|
||||||
DataBuffer.enableCompressedSerializationOutput(compressDataBuffers);
|
DataBuffer.enableCompressedSerializationOutput(compressDataBuffers);
|
||||||
|
|
||||||
@@ -391,6 +396,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
|||||||
boolean blink = options.getBoolean(BLINKING_CURSORS_OPTION_NAME, true);
|
boolean blink = options.getBoolean(BLINKING_CURSORS_OPTION_NAME, true);
|
||||||
Gui.setBlinkingCursors(blink);
|
Gui.setBlinkingCursors(blink);
|
||||||
|
|
||||||
|
boolean useNaturalSort = options.getBoolean(USE_NATURAL_SORT, true);
|
||||||
|
plugin.setUseNaturalSort(useNaturalSort);
|
||||||
|
|
||||||
options.addOptionsChangeListener(this);
|
options.addOptionsChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +420,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
|||||||
else if (SHOW_TOOLTIPS_OPTION_NAME.equals(optionName)) {
|
else if (SHOW_TOOLTIPS_OPTION_NAME.equals(optionName)) {
|
||||||
DockingUtils.setGlobalTooltipEnabledOption((Boolean) newValue);
|
DockingUtils.setGlobalTooltipEnabledOption((Boolean) newValue);
|
||||||
}
|
}
|
||||||
else if (ENABLE_COMPRESSED_DATABUFFER_OUTPUT.equals(optionName)) {
|
else if (USE_COMPRESSED_DATABUFFER_OUTPUT.equals(optionName)) {
|
||||||
DataBuffer.enableCompressedSerializationOutput((Boolean) newValue);
|
DataBuffer.enableCompressedSerializationOutput((Boolean) newValue);
|
||||||
}
|
}
|
||||||
else if (RESTORE_PREVIOUS_PROJECT_NAME.equals(optionName)) {
|
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)) {
|
else if (BLINKING_CURSORS_OPTION_NAME.equals(optionName)) {
|
||||||
Gui.setBlinkingCursors((Boolean) newValue);
|
Gui.setBlinkingCursors((Boolean) newValue);
|
||||||
}
|
}
|
||||||
|
else if (USE_NATURAL_SORT.equals(optionName)) {
|
||||||
|
plugin.setUseNaturalSort((Boolean) newValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,15 +19,17 @@ import java.awt.BorderLayout;
|
|||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.widgets.tabbedpane.DockingTabRenderer;
|
import docking.widgets.tabbedpane.DockingTabRenderer;
|
||||||
import ghidra.framework.client.NotConnectedException;
|
import ghidra.framework.client.NotConnectedException;
|
||||||
|
import ghidra.framework.main.datatable.ProjectDataTableModel;
|
||||||
import ghidra.framework.main.datatable.ProjectDataTablePanel;
|
import ghidra.framework.main.datatable.ProjectDataTablePanel;
|
||||||
|
import ghidra.framework.main.datatree.DataTreeNode;
|
||||||
import ghidra.framework.main.datatree.ProjectDataTreePanel;
|
import ghidra.framework.main.datatree.ProjectDataTreePanel;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
@@ -212,7 +214,7 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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);
|
ProjectData projectData = activeProject.addProjectView(projectView, true);
|
||||||
if (projectData == null) {
|
if (projectData == null) {
|
||||||
return null; // repository connection may have been cancelled
|
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) {
|
void setBorder(String projectName) {
|
||||||
projectTab.setBorder(BorderFactory.createTitledBorder(BORDER_PREFIX + projectName));
|
projectTab.setBorder(BorderFactory.createTitledBorder(BORDER_PREFIX + projectName));
|
||||||
treePanel.updateProjectName(projectName);
|
treePanel.updateProjectName(projectName);
|
||||||
|
|||||||
@@ -27,12 +27,15 @@ import ghidra.util.InvalidNameException;
|
|||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
|
import ghidra.util.datastruct.AlphaNumericComparator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.DuplicateFileException;
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, ProjectData> {
|
public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, ProjectData> {
|
||||||
|
|
||||||
|
private static boolean useNaturalSort = true;
|
||||||
|
|
||||||
private ProjectData projectData;
|
private ProjectData projectData;
|
||||||
private volatile int modCount;
|
private volatile int modCount;
|
||||||
private boolean editingOn;
|
private boolean editingOn;
|
||||||
@@ -43,6 +46,10 @@ public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, Pr
|
|||||||
super("Project Data Table", serviceProvider);
|
super("Project Data Table", serviceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setUseNaturalSort(boolean b) {
|
||||||
|
useNaturalSort = b;
|
||||||
|
}
|
||||||
|
|
||||||
boolean loadWasCancelled() {
|
boolean loadWasCancelled() {
|
||||||
return loadWasCancelled;
|
return loadWasCancelled;
|
||||||
}
|
}
|
||||||
@@ -189,6 +196,9 @@ public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, Pr
|
|||||||
private class DomainFileNameColumn
|
private class DomainFileNameColumn
|
||||||
extends AbstractDynamicTableColumn<DomainFileInfo, String, ProjectData> {
|
extends AbstractDynamicTableColumn<DomainFileInfo, String, ProjectData> {
|
||||||
|
|
||||||
|
private static AlphaNumericComparator alphaNumericComparator =
|
||||||
|
new AlphaNumericComparator(false);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Name";
|
return "Name";
|
||||||
@@ -205,6 +215,14 @@ public class ProjectDataTableModel extends ThreadedTableModel<DomainFileInfo, Pr
|
|||||||
public int getColumnPreferredWidth() {
|
public int getColumnPreferredWidth() {
|
||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comparator<String> getComparator() {
|
||||||
|
if (useNaturalSort) {
|
||||||
|
return alphaNumericComparator;
|
||||||
|
}
|
||||||
|
return super.getComparator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ModificationDateColumn
|
private class ModificationDateColumn
|
||||||
|
|||||||
@@ -255,14 +255,13 @@ public class ProjectDataTablePanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reload() {
|
public void reload() {
|
||||||
|
|
||||||
checkCapacity();
|
checkCapacity();
|
||||||
|
|
||||||
if (!capacityExceeded) {
|
if (!capacityExceeded) {
|
||||||
model.reload();
|
model.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the max file count system property
|
// load the max file count system property
|
||||||
|
|||||||
@@ -17,11 +17,14 @@ package ghidra.framework.main.datatree;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import docking.widgets.tree.GTreeSlowLoadingNode;
|
import docking.widgets.tree.GTreeSlowLoadingNode;
|
||||||
import ghidra.framework.data.LinkHandler;
|
import ghidra.framework.data.LinkHandler;
|
||||||
import ghidra.framework.data.LinkHandler.LinkStatus;
|
import ghidra.framework.data.LinkHandler.LinkStatus;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
|
import ghidra.util.datastruct.AlphaNumericComparator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
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:
|
* sort order is based upon the following comparisons in order of significance:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Node type weighting. Folder and Folder-Links have equal weighting.</li>
|
* <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
|
* <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>
|
* will be placed after the Folder.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*/
|
*/
|
||||||
enum NodeType {
|
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) {
|
NodeType(int weight) {
|
||||||
this.weight = 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static final Comparator<GTreeNode> DATA_NODE_COMPARATOR = new DataNodeSortComparator();
|
||||||
* Sort {@link Comparator} for use with sorting children and node comparison
|
|
||||||
*/
|
|
||||||
static final Comparator<GTreeNode> DATA_NODE_SORT_COMPARATOR = new DataNodeSortComparator();
|
|
||||||
|
|
||||||
/**
|
private static boolean useNaturalSort = true;
|
||||||
* Search {@link Comparator} for use by {@link #getChild(String, NodeType)} only
|
|
||||||
*/
|
|
||||||
private static final DataNodeSearchComparator DATA_NODE_SEARCH_COMPARATOR =
|
|
||||||
new DataNodeSearchComparator();
|
|
||||||
|
|
||||||
private volatile boolean isCut; // true if this node is marked as cut
|
private volatile boolean isCut; // true if this node is marked as cut
|
||||||
|
|
||||||
|
public static void setUseNaturalSort(boolean b) {
|
||||||
|
useNaturalSort = b;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setIsCut(boolean isCut) {
|
public final void setIsCut(boolean isCut) {
|
||||||
if (isCut != this.isCut) {
|
if (isCut != this.isCut) {
|
||||||
@@ -88,6 +80,8 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
|
|||||||
return isCut;
|
return isCut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract NodeType getNodeType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the project data instance to which this file or folder belongs.
|
* Get the project data instance to which this file or folder belongs.
|
||||||
* @return project data instance
|
* @return project data instance
|
||||||
@@ -113,8 +107,9 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
|
|||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GTreeNode> allChildren = getChildren();
|
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) {
|
if (index < 0) {
|
||||||
index = -index - 1;
|
index = -index - 1;
|
||||||
}
|
}
|
||||||
@@ -181,138 +176,199 @@ public abstract class DataTreeNode extends GTreeSlowLoadingNode implements Cutta
|
|||||||
* @param type node type
|
* @param type node type
|
||||||
* @return matching tree node or null if not found
|
* @return matching tree node or null if not found
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
static GTreeNode getChild(List<GTreeNode> children, String name, NodeType type) {
|
static GTreeNode getChild(List<GTreeNode> children, String name, NodeType type) {
|
||||||
ChildSearchRecord childSearchRecord = new ChildSearchRecord(name, type);
|
|
||||||
|
SearchNode key = new SearchNode(name, type);
|
||||||
int index =
|
int index =
|
||||||
Collections.binarySearch(children, childSearchRecord, DATA_NODE_SEARCH_COMPARATOR);
|
Collections.binarySearch(children, key, DATA_NODE_COMPARATOR);
|
||||||
return index >= 0 ? children.get(index) : null;
|
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
|
* Generate filtered child nodes for a DomainFolder
|
||||||
* @param domainFolder folder
|
* @param domainFolder folder
|
||||||
* @param filter filter
|
* @param filter filter
|
||||||
* @param monitor load task monitor
|
* @param monitor load task monitor
|
||||||
* @return list of filtered chidren
|
* @return list of filtered children
|
||||||
* @throws CancelledException if load task is cancelled
|
* @throws CancelledException if load task is cancelled
|
||||||
*/
|
*/
|
||||||
static List<GTreeNode> generateChildren(DomainFolder domainFolder, DomainFileFilter filter,
|
static List<GTreeNode> generateChildren(DomainFolder domainFolder, DomainFileFilter filter,
|
||||||
TaskMonitor monitor) throws CancelledException {
|
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<>();
|
List<GTreeNode> children = new ArrayList<>();
|
||||||
if (domainFolder != null) {
|
if (domainFolder == null) {
|
||||||
|
return children;
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class DomainFileNode extends DataTreeNode {
|
|||||||
private volatile String toolTipText;
|
private volatile String toolTipText;
|
||||||
private AtomicInteger refreshCount = new AtomicInteger();
|
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");
|
private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy MMM dd hh:mm aaa");
|
||||||
|
|
||||||
@@ -114,6 +114,11 @@ public class DomainFileNode extends DataTreeNode {
|
|||||||
return isFolderLink;
|
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()}).
|
* Get linked folder which corresponds to this folder-link (see {@link #isFolderLink()}).
|
||||||
* @return linked folder or null if this is not a folder-link
|
* @return linked folder or null if this is not a folder-link
|
||||||
@@ -403,7 +408,7 @@ public class DomainFileNode extends DataTreeNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(GTreeNode node) {
|
public int compareTo(GTreeNode node) {
|
||||||
return DATA_NODE_SORT_COMPARATOR.compare(this, node);
|
return DATA_NODE_COMPARATOR.compare(this, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import docking.widgets.tree.GTree;
|
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
@@ -53,13 +52,17 @@ public class DomainFolderNode extends DataTreeNode {
|
|||||||
this.domainFolder = domainFolder;
|
this.domainFolder = domainFolder;
|
||||||
this.filter = filter;
|
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) {
|
if (domainFolder != null) {
|
||||||
setToolTipText();
|
setToolTipText();
|
||||||
isEditable = domainFolder.isInWritableProject();
|
isEditable = domainFolder.isInWritableProject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeType getNodeType() {
|
||||||
|
return NodeType.FOLDER;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAutoExpandPermitted() {
|
public boolean isAutoExpandPermitted() {
|
||||||
// Prevent auto-expansion through linked-folders
|
// Prevent auto-expansion through linked-folders
|
||||||
@@ -162,7 +165,7 @@ public class DomainFolderNode extends DataTreeNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(GTreeNode node) {
|
public int compareTo(GTreeNode node) {
|
||||||
return DATA_NODE_SORT_COMPARATOR.compare(this, node);
|
return DATA_NODE_COMPARATOR.compare(this, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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
|
* 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
|
* @param plugin front end plugin
|
||||||
*/
|
*/
|
||||||
@@ -126,6 +126,20 @@ public class ProjectDataTreePanel extends JPanel {
|
|||||||
return tree.getSelectionModel();
|
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
|
* Set the project data for this data tree and populate it with
|
||||||
* nodes for the users in the project.
|
* nodes for the users in the project.
|
||||||
@@ -528,6 +542,7 @@ public class ProjectDataTreePanel extends JPanel {
|
|||||||
* Create the root node for this data tree.
|
* Create the root node for this data tree.
|
||||||
*/
|
*/
|
||||||
private GTreeNode createRootNode(String projectName) {
|
private GTreeNode createRootNode(String projectName) {
|
||||||
|
|
||||||
if (projectData == null) {
|
if (projectData == null) {
|
||||||
return new NoProjectNode();
|
return new NoProjectNode();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public interface Project extends AutoCloseable, Iterable<DomainFile> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the user to store data related to the project.
|
* 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 key a value used to store and lookup saved data
|
||||||
* @param saveState a container of data that will be written out when persisted
|
* @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.
|
* 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
|
* @return domain file iterator
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ public interface ProjectData extends Iterable<DomainFile> {
|
|||||||
* NOTE: The project should be closed and then reopened after this method is called.
|
* NOTE: The project should be closed and then reopened after this method is called.
|
||||||
* @param newRepository new repository to use
|
* @param newRepository new repository to use
|
||||||
* @param force if true any existing local checkout which is not recognized/valid
|
* @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
|
* @param monitor task monitor
|
||||||
* @throws IOException thrown if files are still checked out, or if there was a problem accessing
|
* @throws IOException thrown if files are still checked out, or if there was a problem accessing
|
||||||
* the filesystem
|
* 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.
|
* 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
|
* @return domain file iterator
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user