diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java index 5a6992a38d..70c2925333 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java @@ -214,10 +214,14 @@ class ProgramSaveManager { } try { DataTreeDialog dialog = getSaveDialog(); - String filename = program.getDomainFile().getName(); + DomainFile domainFile = program.getDomainFile(); + String filename = domainFile.getName(); dialog.setTitle("Save As (" + filename + ")"); dialog.setNameText(filename + ".1"); - dialog.setSelectedFolder(program.getDomainFile().getParent()); + + DomainFolder parent = domainFile.getParent(); + dialog.setSelectedFolder(parent); + treeDialogCancelled = true; tool.showDialog(dialog); if (!treeDialogCancelled) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/AbstractDataTreeDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/AbstractDataTreeDialog.java index 389f1bc862..bd9a5dc192 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/AbstractDataTreeDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/AbstractDataTreeDialog.java @@ -193,6 +193,10 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider return nameField.getText(); } + public String getFolderText() { + return folderNameLabel.getText(); + } + public void setNameText(String name) { // We need to run this code in a task since the tree may already be processing other tasks // that would override this setting when they are run. But putting this task in the queue, @@ -246,89 +250,124 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider return domainFolder; } - /** - * TreeSelectionListener method that is called whenever the value of the selection changes. - * @param e the event that characterizes the change. - */ @Override public void valueChanged(GTreeSelectionEvent e) { clearStatusText(); if (type == CHOOSE_FOLDER) { - domainFolder = treePanel.getSelectedDomainFolder(); - if (domainFolder != null) { - DomainFolder folderParent = domainFolder.getParent(); - if (folderParent != null) { - folderNameLabel.setText(folderParent.getPathname()); - } - else { - folderNameLabel.setText(" "); - } - - nameField.setText(domainFolder.getName()); - } - else { - domainFile = treePanel.getSelectedDomainFile(); - if (domainFile != null) { - domainFolder = domainFile.getParent(); - DomainFolder grandParent = domainFolder.getParent(); - if (grandParent != null) { - folderNameLabel.setText(grandParent.getPathname()); - } - else { - folderNameLabel.setText(""); - } - - nameField.setText(domainFolder.getName()); - } - else { - domainFolder = project.getProjectData().getRootFolder(); - folderNameLabel.setText(domainFolder.getPathname()); - nameField.setText(domainFolder.getName()); - } - } + updateFromTreeSelectionInFolderMode(); } else { - domainFile = treePanel.getSelectedDomainFile(); - if (domainFile != null) { - LinkFileInfo linkInfo = domainFile.getLinkInfo(); - if (linkInfo != null && linkInfo.isFolderLink()) { - // Ensure we don't have a folder name conflict - if (domainFile.getParent().getFolder(domainFile.getName()) == null) { - domainFolder = linkInfo.getLinkedFolder(); - domainFile = null; - } - } - else { - folderNameLabel.setText(domainFile.getParent().getPathname()); - nameField.setText(domainFile.getName()); - domainFolder = domainFile.getParent(); - } - } - - if (domainFile == null) { - if (domainFolder == null) { - domainFolder = treePanel.getSelectedDomainFolder(); - if (domainFolder == null) { - domainFolder = project.getProjectData().getRootFolder(); - } - } - folderNameLabel.setText(domainFolder.getPathname()); - if (nameField.isEditable()) { - if (nameField.getText().length() > 0) { - nameField.selectAll(); - } - } - else { - nameField.setText(""); - } - } + updateFromTreeSelectionInFileMode(); } String text = nameField.getText(); setOkEnabled((text != null) && !text.isEmpty()); } + private void updateFromTreeSelectionInFileMode() { + DomainFile newFile = treePanel.getSelectedDomainFile(); + if (isFolderLink(newFile)) { + updateFromFolderLink(newFile); + return; + } + + // not a folder link; see if we have a new file selected + if (newFile != null) { + domainFile = newFile; + domainFolder = domainFile.getParent(); + String pathname = domainFolder.getPathname(); + folderNameLabel.setText(pathname); + String filename = domainFile.getName(); + nameField.setText(filename); + return; + } + + // No selected domain file + domainFile = null; + domainFolder = getSelectedFolder(); + String pathname = domainFolder.getPathname(); + folderNameLabel.setText(pathname); + updateNameFieldTextForNoFileSelected(); + } + + private void updateNameFieldTextForNoFileSelected() { + if (!nameField.isEditable()) { + nameField.setText(""); + return; + } + + if (nameField.getText().length() > 0) { + nameField.selectAll(); + } + } + + private void updateFromFolderLink(DomainFile newFile) { + + LinkFileInfo linkInfo = newFile.getLinkInfo(); + DomainFolder folder = newFile.getParent(); + String filename = newFile.getName(); + + // Ensure we don't have a folder name conflict + if (folder.getFolder(filename) == null) { + domainFolder = linkInfo.getLinkedFolder(); + if (domainFolder == null) { + domainFolder = getSelectedFolder(); + } + + domainFile = null; + folderNameLabel.setText(domainFolder.getPathname()); + updateNameFieldTextForNoFileSelected(); + return; + } + + domainFile = newFile; + folderNameLabel.setText(folder.getPathname()); + nameField.setText(filename); + domainFolder = folder; + } + + private boolean isFolderLink(DomainFile file) { + if (file == null) { + return false; + } + LinkFileInfo linkInfo = file.getLinkInfo(); + return linkInfo != null && linkInfo.isFolderLink(); + } + + private void updateFromTreeSelectionInFolderMode() { + + // The tree selection has changed and we are in FOLDER mode. Update the folder selection + // based on type of node selected. + domainFolder = getSelectedFolder(); + DomainFolder folderParent = domainFolder.getParent(); + if (folderParent == null) { + folderParent = domainFolder; // root folder; no parent + } + + folderNameLabel.setText(folderParent.getPathname()); + nameField.setText(domainFolder.getName()); + + } + + /** + * Returns the selected folder, or the selected file's parent or the root folder. + * @return the folder + */ + private DomainFolder getSelectedFolder() { + DomainFolder folder = treePanel.getSelectedDomainFolder(); + if (folder != null) { + return folder; + } + + DomainFile file = treePanel.getSelectedDomainFile(); + if (file != null) { + return file.getParent(); + } + + return project.getProjectData().getRootFolder(); + } + @Override public void actionPerformed(ActionEvent event) { int index = projectComboBox.getSelectedIndex(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java index 9539770432..20a3552b63 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java @@ -41,6 +41,8 @@ import ghidra.util.task.TaskMonitor; public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { + private static final String TEST_FOLDER_NAME = "TestFolder"; + private TestEnv env; private FrontEndTool frontEndTool; private DataTreeDialog dialog; @@ -70,6 +72,10 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { DomainFolder domainFolder = ProjectDataUtils.createDomainFolderPath(rootFolder, path); result.add(domainFolder.createFile(filename, p, TaskMonitor.DUMMY)); } + + // add a test folder + ProjectDataUtils.createDomainFolderPath(rootFolder, "/" + TEST_FOLDER_NAME); + builder.dispose(); waitForSwing(); return result; @@ -116,7 +122,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertNameHasText(true); // select a folder--text remains; button enabled - selectFolder(); + selectRootFolder(); assertOK(true); assertNameHasText(true); @@ -125,6 +131,16 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertOK(true); assertNameHasText(true); + // select a non-root folder--text remains; button enabled + selectTestFolder(); + assertOK(true); + assertNameHasText(true); + assertFolderText("/" + TEST_FOLDER_NAME); + + deselectFolder(); + assertOK(true); + assertNameHasText(true); + // clear text--disabled clearText(); assertOK(false); @@ -147,7 +163,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertNameHasText(true); // select a folder--text remains; button enabled - selectFolder(); + selectRootFolder(); assertOK(true); assertNameHasText(true); @@ -185,7 +201,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertNameHasText(true); // "/" // select a folder--enabled - selectFolder(); + selectRootFolder(); assertOK(true); assertNameHasText(true); @@ -212,7 +228,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertNameHasText(false); // select a folder--disabled - selectFolder(); + selectRootFolder(); assertOK(false); // de-select a folder--disabled @@ -237,7 +253,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertNameHasText(false); // select a folder--disabled - selectFolder(); + selectRootFolder(); assertOK(false); // de-select a folder--disabled @@ -321,7 +337,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { }); } - private void selectFolder() { + private void selectRootFolder() { final AtomicBoolean result = new AtomicBoolean(false); final GTree gTree = getGTree(); runSwing(() -> { @@ -340,6 +356,31 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { waitForTree(gTree); } + private void selectTestFolder() { + + AtomicBoolean result = new AtomicBoolean(false); + GTree gTree = getGTree(); + runSwing(() -> { + GTreeNode root = gTree.getViewRoot(); + if (root == null) { + return; + } + + GTreeNode folderNode = root.getChild(TEST_FOLDER_NAME); + if (folderNode != null) { + gTree.expandPath(root); + gTree.setSelectedNode(folderNode); + result.set(true); + } + }); + + if (!result.get()) { + Assert.fail("Unable to select root folder"); + } + + waitForTree(gTree); + } + private void deselectFile() { clearSelection(); } @@ -350,6 +391,11 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { waitForTree(gTree); } + private void assertFolderText(String expectedName) { + String actualName = runSwing(() -> dialog.getFolderText()); + assertEquals("Dialog folder path not correct", expectedName, actualName); + } + private void assertNameHasText(boolean hasText) { final AtomicBoolean result = new AtomicBoolean(); runSwing(() -> {