Merge remote-tracking branch 'origin/Ghidra_12.1'

This commit is contained in:
Ryan Kurtz
2026-04-30 12:10:19 -04:00
5 changed files with 195 additions and 83 deletions
@@ -35,6 +35,7 @@ public class DtFilterDialog extends DialogComponentProvider {
private TypeComponent arraysComponent = new TypeComponent("Arrays");
private TypeComponent enumsComponent = new TypeComponent("Enums");
private TypeComponent functionsComponent = new TypeComponent("Functions");
private TypeComponent otherComponent = new TypeComponent("Other");
private TypeComponent pointersComponent = new TypeComponent("Pointers");
private TypeComponent structuresComponent = new TypeComponent("Structures");
private TypeComponent unionsComponent = new TypeComponent("Unions");
@@ -77,6 +78,7 @@ public class DtFilterDialog extends DialogComponentProvider {
newState.setArraysFilter(arraysComponent.getFilter());
newState.setEnumsFilter(enumsComponent.getFilter());
newState.setFunctionsFilter(functionsComponent.getFilter());
newState.setOtherFilter(otherComponent.getFilter());
newState.setPointersFilter(pointersComponent.getFilter());
newState.setStructuresFilter(structuresComponent.getFilter());
newState.setUnionsFilter(unionsComponent.getFilter());
@@ -108,6 +110,8 @@ public class DtFilterDialog extends DialogComponentProvider {
panel.add(structuresComponent.getRight());
panel.add(unionsComponent.getLeft());
panel.add(unionsComponent.getRight());
panel.add(otherComponent.getLeft());
panel.add(otherComponent.getRight());
GButton selectAll = new GButton("Select All");
GButton deselectAll = new GButton("Deselect All");
@@ -145,6 +149,8 @@ public class DtFilterDialog extends DialogComponentProvider {
return List.of(
arraysComponent.typeCb,
arraysComponent.typeDefCb,
otherComponent.typeCb,
otherComponent.typeDefCb,
enumsComponent.typeCb,
enumsComponent.typeDefCb,
functionsComponent.typeCb,
@@ -161,6 +167,7 @@ public class DtFilterDialog extends DialogComponentProvider {
private void initCheckBoxes() {
arraysComponent.init(filterState.getArraysFilter());
otherComponent.init(filterState.getOtherFilter());
enumsComponent.init(filterState.getEnumsFilter());
functionsComponent.init(filterState.getFunctionsFilter());
pointersComponent.init(filterState.getPointersFilter());
@@ -21,7 +21,6 @@ import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.framework.options.SaveState;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.Function;
/**
* A simple object to store various filter settings for the data type provider.
@@ -34,6 +33,7 @@ public class DtFilterState {
private DtTypeFilter enumsFilter = new DtTypeFilter("Enums");
private DtTypeFilter functionsFilter = new DtTypeFilter("Functions");
private DtTypeFilter structuresFilter = new DtTypeFilter("Structures");
private DtTypeFilter otherFilter = new DtTypeFilter("Other");
private DtTypeFilter pointersFilter = new DtTypeFilter("Pointers");
private DtTypeFilter unionsFilter = new DtTypeFilter("Unions");
@@ -46,6 +46,7 @@ public class DtFilterState {
public DtFilterState copy() {
DtFilterState filterState = new DtFilterState();
filterState.arraysFilter = arraysFilter.copy();
filterState.otherFilter = otherFilter.copy();
filterState.enumsFilter = enumsFilter.copy();
filterState.functionsFilter = functionsFilter.copy();
filterState.structuresFilter = structuresFilter.copy();
@@ -66,6 +67,18 @@ public class DtFilterState {
this.arraysFilter = filter;
}
public boolean isShowOther() {
return otherFilter.isTypeActive();
}
public DtTypeFilter getOtherFilter() {
return otherFilter;
}
public void setOtherFilter(DtTypeFilter filter) {
this.otherFilter = filter;
}
public boolean isShowEnums() {
return enumsFilter.isTypeActive();
}
@@ -155,7 +168,7 @@ public class DtFilterState {
return passes(enumsFilter, dt);
}
if (baseDt instanceof Function) {
if (baseDt instanceof FunctionDefinition) {
return passes(functionsFilter, dt);
}
@@ -167,7 +180,7 @@ public class DtFilterState {
return passes(unionsFilter, dt);
}
return true;
return passes(otherFilter, dt);
}
private boolean passes(DtTypeFilter filter, DataType dt) {
@@ -184,6 +197,7 @@ public class DtFilterState {
ss.putSaveState(arraysFilter.getName(), arraysFilter.save());
ss.putSaveState(enumsFilter.getName(), enumsFilter.save());
ss.putSaveState(functionsFilter.getName(), functionsFilter.save());
ss.putSaveState(otherFilter.getName(), otherFilter.save());
ss.putSaveState(pointersFilter.getName(), pointersFilter.save());
ss.putSaveState(structuresFilter.getName(), structuresFilter.save());
ss.putSaveState(unionsFilter.getName(), unionsFilter.save());
@@ -201,6 +215,7 @@ public class DtFilterState {
arraysFilter = DtTypeFilter.restore("Arrays", ss.getSaveState("Arrays"));
enumsFilter = DtTypeFilter.restore("Enums", ss.getSaveState("Enums"));
functionsFilter = DtTypeFilter.restore("Functions", ss.getSaveState("Functions"));
otherFilter = DtTypeFilter.restore("Other", ss.getSaveState("Other"));
pointersFilter = DtTypeFilter.restore("Pointers", ss.getSaveState("Pointers"));
structuresFilter = DtTypeFilter.restore("Structures", ss.getSaveState("Structures"));
unionsFilter = DtTypeFilter.restore("Unions", ss.getSaveState("Unions"));
@@ -208,8 +223,8 @@ public class DtFilterState {
@Override
public int hashCode() {
return Objects.hash(arraysFilter, enumsFilter, functionsFilter, pointersFilter,
structuresFilter, unionsFilter);
return Objects.hash(arraysFilter, enumsFilter, functionsFilter,
otherFilter, pointersFilter, structuresFilter, unionsFilter);
}
@Override
@@ -227,6 +242,7 @@ public class DtFilterState {
return Objects.equals(arraysFilter, other.arraysFilter) &&
Objects.equals(enumsFilter, other.enumsFilter) &&
Objects.equals(functionsFilter, other.functionsFilter) &&
Objects.equals(otherFilter, other.otherFilter) &&
Objects.equals(pointersFilter, other.pointersFilter) &&
Objects.equals(structuresFilter, other.structuresFilter) &&
Objects.equals(unionsFilter, other.unionsFilter);
@@ -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) {
@@ -189,6 +189,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,
@@ -242,89 +246,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();
@@ -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(() -> {