GP-5327 - Fixed sizing issues with the data type and category editors

This commit is contained in:
dragonmacher
2025-05-02 20:13:44 -04:00
parent 8234bfb14a
commit e2d5d30c64
18 changed files with 423 additions and 298 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,8 +15,7 @@
*/
package ghidra.app.cmd.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
@@ -73,9 +72,6 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
structureDataLength = structure.getLength();
}
/*
* @see AbstractCreateStructureCmd#createStructure(Address, Program)
*/
@Override
Structure createStructure(Address address, Program program) {
@@ -87,9 +83,6 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
return structure;
}
/*
* @see AbstractCreateStructureCmd#initializeStructureData(Program, Structure)
*/
@Override
DataType initializeStructureData(Program program, Structure localStructure) {
@@ -101,11 +94,11 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
}
catch (AddressOverflowException e1) {
throw new IllegalArgumentException(
"Can't create structure because length exceeds address " + "space" +
"Can't create structure because length exceeds address space" +
structureDataLength);
}
ReferenceManager refMgr = program.getReferenceManager();
Reference[] refs = findExistingRefs(refMgr, program.getAddressFactory(),
List<Reference> refs = findExistingRefs(refMgr, program.getAddressFactory(),
getStructureAddress(), endAddress);
listing.clearCodeUnits(getStructureAddress(), endAddress, false);
@@ -123,20 +116,20 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
return data.getDataType();
}
private Reference[] findExistingRefs(ReferenceManager refMgr, AddressFactory af, Address start,
private List<Reference> findExistingRefs(ReferenceManager refMgr, AddressFactory af,
Address start,
Address end) {
ArrayList<Reference> list = new ArrayList<Reference>();
List<Reference> list = new ArrayList<Reference>();
AddressIterator it = refMgr.getReferenceSourceIterator(new AddressSet(start, end), true);
while (it.hasNext()) {
Address addr = it.next();
Reference[] refs = refMgr.getReferencesFrom(addr);
Collections.addAll(list, refs);
}
Reference[] refList = new Reference[list.size()];
return list.toArray(refList);
return list;
}
private void addRefs(Program p, ReferenceManager refMgr, Reference[] refs) {
private void addRefs(Program p, ReferenceManager refMgr, List<Reference> refs) {
for (Reference ref : refs) {
refMgr.addReference(ref);
}
@@ -1158,8 +1158,6 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
private int maxLength;
private boolean bitfieldAllowed;
private JPanel editorPanel;
@Override
public Component getTableCellEditorComponent(JTable table1, Object value,
boolean isSelected, int row, int column) {
@@ -1178,7 +1176,7 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
editor.setCellEditorValue(dt);
return editorPanel;
return editor.getEditorComponent();
}
private void init() {
@@ -1193,7 +1191,7 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
editor.setConsumeEnterKeyPress(false); // we want the table to handle Enter key presses
textField = editor.getDropDownTextField();
textField.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
editor.addCellEditorListener(new CellEditorListener() {
@Override
public void editingCanceled(ChangeEvent e) {
@@ -1206,17 +1204,8 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
}
});
// force a small button for the table's cell editor
JButton dataTypeChooserButton = new JButton("...") {
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = 15;
return preferredSize;
}
};
dataTypeChooserButton.addActionListener(e -> Swing.runLater(() -> stopEdit(tool)));
JButton browseButton = editor.getBrowseButton();
browseButton.addActionListener(e -> Swing.runLater(() -> stopEdit(tool)));
textField.addFocusListener(new FocusAdapter() {
@Override
@@ -1226,10 +1215,6 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
}
});
editorPanel = new JPanel();
editorPanel.setLayout(new BorderLayout());
editorPanel.add(textField, BorderLayout.CENTER);
editorPanel.add(dataTypeChooserButton, BorderLayout.EAST);
}
private void stopEdit(PluginTool tool) {
@@ -99,7 +99,7 @@ class CreateStructureAction extends ListingContextAction {
return;
}
CreateStructureDialog dialog = new CreateStructureDialog(plugin.getTool());
CreateStructureDialog dialog = new CreateStructureDialog(plugin.getTool(), program);
Structure userChoice = dialog.showCreateStructureDialog(program, tempStructure);
if (userChoice != null) {
@@ -148,7 +148,7 @@ class CreateStructureAction extends ListingContextAction {
return;
}
CreateStructureDialog dialog = new CreateStructureDialog(plugin.getTool());
CreateStructureDialog dialog = new CreateStructureDialog(plugin.getTool(), program);
Structure userChoice =
dialog.showCreateStructureDialog(program, tempStructure);
@@ -54,7 +54,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
private static final String EXISITING_STRUCTURE_STATUS_PREFIX = "Using existing structure: ";
private static final String STRUCTURE_COLUMN_NAME = "Structure";
private static final String PATH_COLUMN_NAME = "Path";
private static final String CATEGORY_COLUMN_NAME = "Category";
private JTextField nameTextField;
private CategoryPathSelectionEditor categoryPathEditor;
@@ -74,11 +74,13 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
* Creates a new dialog with the given parent.
*
* @param tool The current tool that this dialog needs to access services.
* @param program the current program
*/
public CreateStructureDialog(PluginTool tool) {
public CreateStructureDialog(PluginTool tool, Program program) {
super("Create Structure", true, true, true, false);
pluginTool = tool;
this.pluginTool = tool;
this.currentProgram = program;
setHelpLocation(new HelpLocation("DataPlugin", "Create_Structure_Dialog"));
addWorkPanel(buildMainPanel());
@@ -159,20 +161,15 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
JLabel nameLabel = new JLabel("Name: ");
nameTextField = new JTextField() {
// make sure our height doesn't stretch
@Override
public Dimension getMaximumSize() {
Dimension d = super.getMaximumSize();
d.height = getPreferredSize().height;
return d;
}
};
nameTextField = new JTextField();
nameTextField.setName("StructureName");
nameTextField.getAccessibleContext().setAccessibleName("Name");
// Allow user to click on the text field to re-activate "create new" panel without forcing
// a click on the radio button
nameTextField.addMouseListener(new MouseAdapter() {
nameTextField.addFocusListener(new FocusAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
public void focusGained(FocusEvent e) {
createNewStructButton.setSelected(true);
updateEnablement();
}
@@ -224,45 +221,13 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
private void buildCategoryPathEditor() {
categoryPathEditor = new CategoryPathSelectionEditor(pluginTool);
categoryPathEditor.getEditorComponent()
.getAccessibleContext()
.setAccessibleName("Category");
// make sure the "Category: " text field size matches the "Name: " text field size
categoryPathEditor.getEditorComponent().setMaximumSize(nameTextField.getMaximumSize());
categoryPathEditor.addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent event) {
updateStatus(event.getDocument());
}
JComponent editorComponent = categoryPathEditor.getEditorComponent();
editorComponent.getAccessibleContext().setAccessibleName("Category");
@Override
public void insertUpdate(DocumentEvent event) {
updateStatus(event.getDocument());
}
categoryPathEditor.setCellEditorValue(CategoryPath.ROOT);
@Override
public void removeUpdate(DocumentEvent event) {
updateStatus(event.getDocument());
}
private void updateStatus(Document document) {
try {
String text = document.getText(0, document.getLength());
if (StringUtils.isBlank(text)) {
updateStatusText(true, null);
}
else {
updateStatusText(true, "Using category: " + text);
}
}
catch (BadLocationException ble) {
// nothing we can do here
}
}
});
// Allow the user to re-activate the "new struct" panel without forcing toggle click. Use
// FocusListener because @CategoryPathSelectionEditor.java already contains a mouse listener
// and would override this one.
// Allow user to click on the text field to re-activate "create new" panel without forcing
// a click on the radio button
categoryPathEditor.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
@@ -285,6 +250,16 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
updateEnablement();
}
});
ListSelectionModel selectionModel = table.getSelectionModel();
selectionModel.addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) {
return;
}
if (useExistingStructButton.isSelected()) {
setOkEnabled(table.getSelectedRowCount() > 0);
}
});
filterPanel = new GhidraTableFilterPanel<>(table, structureTableModel) {
// make sure our height doesn't stretch
@@ -527,6 +502,18 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
return false;
}
JTextField getNameField() {
return nameTextField;
}
JTable getTable() {
return matchingStructuresTable;
}
CategoryPathSelectionEditor getCategoryEditor() {
return categoryPathEditor;
}
/**
* Shows a dialog that allows the user to create a new structure.
* <p>
@@ -577,34 +564,58 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
@Override
protected void okCallback() {
if (nameTextField.isEnabled()) {
// just use the name set by the user
String nameText = nameTextField.getText();
if (!setCategoryPath()) {
return;
}
try {
currentStructure.setName(nameText);
}
catch (InvalidNameException ine) {
setStatusText(ine.getMessage());
return;
}
catch (DuplicateNameException dne) {
setStatusText(dne.getMessage());
return;
}
}
else {
if (useExistingStructButton.isSelected()) {
// get the selected object in the table
currentStructure = getSelectedStructure();
close();
return;
}
// just use the name set by the user
String nameText = nameTextField.getText();
try {
currentStructure.setName(nameText);
}
catch (InvalidNameException ine) {
setStatusText(ine.getMessage());
return;
}
catch (DuplicateNameException dne) {
setStatusText(dne.getMessage());
return;
}
if (!setCategoryPath()) {
return;
}
if (!validateName()) {
return;
}
close();
}
private boolean validateName() {
// Use the current name and category path to see if there is already an existing name. This
// allows us to avoid a conflict.
ProgramBasedDataTypeManager dtm = currentProgram.getDataTypeManager();
CategoryPath path = currentStructure.getCategoryPath();
Category category = dtm.getCategory(path);
if (category == null) {
return true;
}
String nameText = currentStructure.getName();
DataType existingDt = category.getDataType(nameText);
if (existingDt != null) {
setStatusText("Name already exists: " + nameText, MessageType.ERROR);
return false;
}
return true;
}
private Structure getSelectedStructure() {
int row = matchingStructuresTable.getSelectedRow();
if (row < 0) {
@@ -616,66 +627,43 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
}
private boolean setCategoryPath() {
try {
doSetCategoryPath();
}
catch (DuplicateNameException e) {
setStatusText(e.getMessage(), MessageType.ERROR);
return false;
}
return true;
}
private void doSetCategoryPath() throws DuplicateNameException {
CategoryPath path = categoryPathEditor.getCellEditorValue();
// First see if a category from the list was chosen and make sure the user didn't modify it.
// If they did, path needs to be parsed separately.
if (path != null && path.getPath().equals(categoryPathEditor.getCellEditorValueAsText())) {
try {
currentStructure.setCategoryPath(path);
}
catch (DuplicateNameException dne) {
setStatusText(dne.getMessage(), MessageType.ERROR);
return false;
}
return true;
String editorValue = categoryPathEditor.getCellEditorValueAsText();
if (path != null && path.getPath().equals(editorValue)) {
currentStructure.setCategoryPath(path);
return;
}
String categoryText = categoryPathEditor.getCellEditorValueAsText();
// Selecting/entering a category is optional; root is default
if (!categoryText.isBlank()) {
try {
CategoryPath parsedPath = parseEnteredCategoryPath(categoryText);
currentStructure.setCategoryPath(parsedPath);
}
catch (DuplicateNameException dne) {
setStatusText(dne.getMessage(), MessageType.ERROR);
return false;
}
if (!editorValue.isBlank()) {
CategoryPath parsedPath = parseEnteredCategoryPath(editorValue);
currentStructure.setCategoryPath(parsedPath);
return;
}
else {
try {
currentStructure.setCategoryPath(CategoryPath.ROOT);
}
catch (DuplicateNameException dne) {
setStatusText(dne.getMessage(), MessageType.ERROR);
return false;
}
}
return true;
currentStructure.setCategoryPath(CategoryPath.ROOT);
}
private CategoryPath parseEnteredCategoryPath(String categoryText) {
// entering a leading slash is optional, path is still generated accordingly
if (categoryText.startsWith(CategoryPath.DELIMITER_STRING)) {
return generateCategoryPath(categoryText.substring(1));
return new CategoryPath(categoryText);
}
return generateCategoryPath(categoryText);
}
private CategoryPath generateCategoryPath(String categoryText) {
if (!categoryText.contains(CategoryPath.DELIMITER_STRING)) {
return new CategoryPath(CategoryPath.ROOT, categoryText);
}
// Additional slashes need parsed as branch(es) and final leaf
List<String> parts = split(categoryText);
return new CategoryPath(CategoryPath.ROOT, parts);
}
private List<String> split(String categoryText) {
List<String> parts = new ArrayList<String>(
Arrays.asList(categoryText.split(CategoryPath.DELIMITER_STRING)));
return parts;
return new CategoryPath(CategoryPath.DELIMITER_STRING + categoryText);
}
// a table model that is used to allow for the easy updating of the table with new List data
@@ -707,7 +695,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
case 0:
return STRUCTURE_COLUMN_NAME;
case 1:
return PATH_COLUMN_NAME;
return CATEGORY_COLUMN_NAME;
}
return null;
}
@@ -735,8 +723,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
case 1: {
Structure structure = t.getStructure();
CategoryPath path = structure.getCategoryPath();
String name = structure.getName();
return path.toString() + '/' + name;
return path.toString();
}
}
return null;
@@ -779,7 +766,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
}
// we need this renderer in order to create nice tool tip text values
class StructureCellRenderer extends GTableCellRenderer {
private class StructureCellRenderer extends GTableCellRenderer {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@@ -799,7 +786,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
renderer.setToolTipText(ToolTipUtils.getToolTipText(structure));
}
}
else if (PATH_COLUMN_NAME.equals(columnName)) {
else if (CATEGORY_COLUMN_NAME.equals(columnName)) {
if (value != null) {
renderer.setToolTipText(value.toString());
}
@@ -598,6 +598,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin
public CategoryPath getCategoryPath(TreePath selectedPath) {
DataTypeChooserDialog dialog = new DataTypeChooserDialog(this);
dialog.setCategorySelectionMode(true);
dialog.setShowProgramArchiveOnly(true);
if (selectedPath != null) {
dialog.setSelectedPath(selectedPath);
@@ -169,10 +169,37 @@ public class DataTypeIndexer {
}
}
private class CaseInsensitiveCategoryComparator implements Comparator<CategoryPath> {
@Override
public int compare(CategoryPath cp1, CategoryPath cp2) {
String name1 = cp1.getName();
String name2 = cp2.getName();
int result = name1.compareToIgnoreCase(name2);
if (result != 0) {
return result;
}
result = name1.compareTo(name2);
if (result != 0) {
// let equivalent names be sorted by case ('-' for lower-case first)
return -result;
}
// if the names are the same, then sort by full path
String p1 = cp1.getPath();
String p2 = cp2.getPath();
return p1.compareToIgnoreCase(p2);
}
}
private class IndexerTask extends Task {
private List<DataType> dataTypes = new ArrayList<>();
private List<CategoryPath> categories;
private List<CategoryPath> categories = new ArrayList<>();
private Set<CategoryPath> categorySet = new HashSet<>();
IndexerTask() {
super("Data Type Indexer Task", false, true, true);
@@ -184,26 +211,32 @@ public class DataTypeIndexer {
monitor.initialize(dataTypeManagers.size());
monitor.setMessage("Preparing to index data types...");
for (DataTypeManager dataTypeManager : dataTypeManagers) {
monitor.setMessage("Searching " + dataTypeManager.getName());
dataTypeManager.getAllDataTypes(dataTypes);
for (DataTypeManager dtm : dataTypeManagers) {
monitor.setMessage("Searching " + dtm.getName());
dtm.getAllDataTypes(dataTypes);
Category root = dtm.getRootCategory();
populateCategories(root);
monitor.incrementProgress(1);
}
Collections.sort(dataTypes, new CaseInsensitiveDataTypeComparator());
populateCategoryList(dataTypes);
categories.addAll(categorySet);
Collections.sort(categories, new CaseInsensitiveCategoryComparator());
}
private void populateCategoryList(List<DataType> dataTypes) {
private void populateCategories(Category parent) {
Set<CategoryPath> set = new HashSet<>();
for (DataType dt : dataTypes) {
CategoryPath path = dt.getCategoryPath();
set.add(path);
categorySet.add(parent.getCategoryPath());
Category[] children = parent.getCategories();
for (Category category : children) {
CategoryPath path = category.getCategoryPath();
categorySet.add(path);
populateCategories(category);
}
categories = new ArrayList<>(set);
Collections.sort(categories);
}
List<DataType> getList() {
@@ -674,6 +674,13 @@ public class DataTypeManagerHandler {
return builtInDataTypesManager;
}
public DataTypeManager getProgramDataTypeManager() {
if (programArchive != null) {
return programArchive.getDataTypeManager();
}
return null;
}
public DataTypeIndexer getDataTypeIndexer() {
return dataTypeIndexer;
}
@@ -1810,4 +1817,5 @@ public class DataTypeManagerHandler {
}
return null;
}
}
@@ -34,8 +34,15 @@ public class ArchiveRootNode extends DataTypeTreeNode {
private DtFilterState dtFilterState = new DtFilterState();
ArchiveRootNode(DataTypeManagerHandler archiveManager) {
private boolean programDtmOnly;
public ArchiveRootNode(DataTypeManagerHandler archiveManager) {
this(archiveManager, false);
}
public ArchiveRootNode(DataTypeManagerHandler archiveManager, boolean programDtmOnly) {
this.archiveManager = archiveManager;
this.programDtmOnly = programDtmOnly;
init();
}
@@ -93,21 +100,29 @@ public class ArchiveRootNode extends DataTypeTreeNode {
}
// a factory method to isolate non-OO inheritance checks
private static final GTreeNode createArchiveNode(Archive archive, DtFilterState dtFilterState) {
private final GTreeNode createArchiveNode(Archive archive, DtFilterState filterState) {
if (programDtmOnly) {
if (archive instanceof ProgramArchive) {
return new ProgramArchiveNode((ProgramArchive) archive, filterState);
}
return null;
}
if (archive instanceof FileArchive) {
return new FileArchiveNode((FileArchive) archive, dtFilterState);
return new FileArchiveNode((FileArchive) archive, filterState);
}
else if (archive instanceof ProjectArchive) {
return new ProjectArchiveNode((ProjectArchive) archive, dtFilterState);
return new ProjectArchiveNode((ProjectArchive) archive, filterState);
}
else if (archive instanceof InvalidFileArchive) {
return new InvalidArchiveNode((InvalidFileArchive) archive);
}
else if (archive instanceof ProgramArchive) {
return new ProgramArchiveNode((ProgramArchive) archive, dtFilterState);
return new ProgramArchiveNode((ProgramArchive) archive, filterState);
}
else if (archive instanceof BuiltInArchive) {
return new BuiltInArchiveNode((BuiltInArchive) archive, dtFilterState);
return new BuiltInArchiveNode((BuiltInArchive) archive, filterState);
}
return null;
}
@@ -178,7 +193,10 @@ public class ArchiveRootNode extends DataTypeTreeNode {
public List<GTreeNode> generateChildren() {
List<GTreeNode> list = new ArrayList<>();
for (Archive element : archiveManager.getAllArchives()) {
list.add(createArchiveNode(element, dtFilterState));
GTreeNode node = createArchiveNode(element, dtFilterState);
if (node != null) {
list.add(node);
}
}
Collections.sort(list);
return list;
@@ -221,15 +239,21 @@ public class ArchiveRootNode extends DataTypeTreeNode {
@Override
public void archiveOpened(Archive archive) {
if (isLoaded()) {
GTreeNode node = createArchiveNode(archive, dtFilterState);
List<GTreeNode> allChildrenList = getChildren();
int index = Collections.binarySearch(allChildrenList, node);
if (index < 0) {
index = -index - 1;
}
addNode(index, node);
if (!isLoaded()) {
return;
}
GTreeNode node = createArchiveNode(archive, dtFilterState);
if (node == null) {
return;
}
List<GTreeNode> allChildrenList = getChildren();
int index = Collections.binarySearch(allChildrenList, node);
if (index < 0) {
index = -index - 1;
}
addNode(index, node);
}
@Override
@@ -31,12 +31,14 @@ import docking.DialogComponentProvider;
import docking.Tool;
import docking.widgets.filter.FilterOptions;
import docking.widgets.filter.TextFilterStrategy;
import docking.widgets.label.GLabel;
import docking.widgets.label.GDLabel;
import docking.widgets.tree.*;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.util.datatype.DataTypeSelectionDialog;
import ghidra.program.model.data.*;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -46,17 +48,20 @@ import ghidra.util.task.TaskMonitor;
* {@link DataTypeSelectionDialog} utility widget.
*/
public class DataTypeChooserDialog extends DialogComponentProvider {
private DataTypeManagerPlugin plugin;
private DataTypeArchiveGTree tree;
private DataType selectedDataType;
private CategoryPath selectedCategoryPath;
private GLabel messageLabel;
private GDLabel messageLabel;
private boolean isFilterEditable;
private boolean categorySelectionMode;
public DataTypeChooserDialog(DataTypeManagerPlugin plugin) {
super("Data Type Chooser", true, true, true, false);
this.plugin = plugin;
tree = new DataTypeArchiveGTree(plugin);
@@ -98,6 +103,8 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
addOKButton();
addCancelButton();
setOkEnabled(false);
setHelpLocation(new HelpLocation("DataTypeEditors", "browse"));
}
/**
@@ -106,6 +113,29 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
*/
public void setCategorySelectionMode(boolean categorySelectionMode) {
this.categorySelectionMode = categorySelectionMode;
if (categorySelectionMode) {
setTitle("Category Chooser");
messageLabel.setText("Choose a category:");
}
else {
setTitle("Data Type Chooser");
messageLabel.setText("Choose a data type:");
}
}
public void setShowProgramArchiveOnly(boolean programOnly) {
DataTypeManagerHandler handler = plugin.getDataTypeManagerHandler();
if (programOnly) {
DataTypeManager programDtm = handler.getProgramDataTypeManager();
if (programDtm != null) {
ArchiveRootNode root = new ArchiveRootNode(handler, true);
tree.setRootNode(root);
return;
}
}
ArchiveRootNode root = new ArchiveRootNode(handler);
tree.setRootNode(root);
}
private boolean isValidNodeSelected() {
@@ -152,7 +182,7 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
private JComponent createWorkPanel() {
JPanel panel = new JPanel(new BorderLayout());
messageLabel = new GLabel("Choose the data type you wish to use.");
messageLabel = new GDLabel("Choose the data type you wish to use.");
messageLabel.setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 2));
messageLabel.getAccessibleContext().setAccessibleName("Message");
panel.add(messageLabel, BorderLayout.NORTH);
@@ -335,4 +365,5 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
}
}
}
}
@@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.function.editor;
import java.awt.*;
import java.awt.Component;
import java.awt.event.*;
import java.util.EventObject;
@@ -32,16 +32,15 @@ import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.util.MessageType;
import ghidra.util.Swing;
import ghidra.util.data.DataTypeParser;
class ParameterDataTypeCellEditor extends AbstractCellEditor
implements TableCellEditor, FocusableEditor {
private DataTypeSelectionEditor editor;
private DropDownSelectionTextField<DataType> textField;
private JButton dataTypeChooserButton;
private DataType dt;
private JPanel editorPanel;
private DataTypeManagerService service;
private DialogComponentProvider dialog;
private DataTypeManager dtm;
@@ -62,7 +61,7 @@ class ParameterDataTypeCellEditor extends AbstractCellEditor
editor.setCellEditorValue(dt);
return editorPanel;
return editor.getEditorComponent();
}
private void init() {
@@ -83,17 +82,8 @@ class ParameterDataTypeCellEditor extends AbstractCellEditor
}
});
// force a small button for the table's cell editor
dataTypeChooserButton = new JButton("...") {
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = 15;
return preferredSize;
}
};
dataTypeChooserButton.addActionListener(e -> SwingUtilities.invokeLater(() -> {
JButton browseButton = editor.getBrowseButton();
browseButton.addActionListener(e -> Swing.runLater(() -> {
DataType dataType = service.getDataType((String) null);
if (dataType != null) {
editor.setCellEditorValue(dataType);
@@ -104,18 +94,13 @@ class ParameterDataTypeCellEditor extends AbstractCellEditor
}
}));
FocusAdapter focusListener = new FocusAdapter() {
textField.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
textField.selectAll();
textField.removeFocusListener(this);
}
};
textField.addFocusListener(focusListener);
editorPanel = new JPanel(new BorderLayout());
editorPanel.add(textField, BorderLayout.CENTER);
editorPanel.add(dataTypeChooserButton, BorderLayout.EAST);
});
}
@Override
@@ -136,7 +121,7 @@ class ParameterDataTypeCellEditor extends AbstractCellEditor
* be returned if getTableCellEditorComponent method has not yet been invoked.
*/
public JButton getChooserButton() {
return dataTypeChooserButton;
return editor.getBrowseButton();
}
@Override
@@ -15,11 +15,14 @@
*/
package ghidra.app.util.datatype;
import java.awt.Component;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.tree.TreePath;
@@ -30,7 +33,6 @@ import docking.widgets.list.GListCellRenderer;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.data.CategoryPath;
import ghidra.util.exception.AssertException;
/**
* An editor that is used to show the {@link DropDownSelectionTextField} for the entering of
@@ -92,6 +94,8 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
private void init() {
selectionField = createDropDownSelectionTextField(
new CategoryPathDropDownSelectionDataModel(dataTypeManagerService));
selectionField.setName("CategoryPath");
selectionField.getAccessibleContext().setAccessibleName("Category");
selectionField.addCellEditorListener(new CellEditorListener() {
@Override
public void editingCanceled(ChangeEvent e) {
@@ -113,16 +117,12 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
selectionField.requestFocus();
}
});
selectionField.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
browseButton = new BrowseButton();
browseButton.setToolTipText("Browse Existing Category Paths");
browseButton.addActionListener(e -> showBrowser());
JPanel browsePanel = buildBrowsePanel();
editorPanel = new JPanel();
editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS));
editorPanel.add(selectionField);
editorPanel.add(Box.createHorizontalStrut(5));
editorPanel.add(browseButton);
editorPanel.add(browsePanel);
keyListener = new KeyAdapter() {
@@ -144,6 +144,60 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
};
}
private JPanel buildBrowsePanel() {
// We override the various sizes to make sure the button does not get too big or too small,
// which changes depending upon the theme being used.
JPanel browsePanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
int width = getBestWidth();
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = Math.min(width, preferredSize.width);
return preferredSize;
}
@Override
public Dimension getMinimumSize() {
int width = getBestWidth();
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = Math.min(width, preferredSize.width);
return preferredSize;
}
@Override
public Dimension getMaximumSize() {
int width = getBestWidth();
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = Math.min(width, preferredSize.width);
return preferredSize;
}
private int getBestWidth() {
Font f = getFont();
FontMetrics fm = getFontMetrics(f);
int width = fm.stringWidth(" . . . ");
return width;
}
};
browsePanel.setLayout(new BorderLayout());
browsePanel.setOpaque(false);
// Space the button so that it pops out visually. This was chosen by trial-and-error and
// looks reasonable on all themes.
Border empty = BorderFactory.createEmptyBorder(2, 2, 1, 1);
browsePanel.setBorder(empty);
browseButton = new BrowseButton();
browseButton.setToolTipText("Browse Existing Category Paths");
browseButton.addActionListener(e -> showBrowser());
browsePanel.add(browseButton);
return browsePanel;
}
/**
* Retrieve the value in the cell.
* @return categoryPath of the selected value from the drop-down
@@ -179,8 +233,8 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
}
/**
* Retrieve the dropdown text field that holds the category path collection.
* @return CategoryPath dropdown selection text field object
* Retrieve the drop-down text field that holds the category path collection.
* @return CategoryPath drop-down selection text field object
*/
public DropDownSelectionTextField<CategoryPath> getDropDownTextField() {
return selectionField;
@@ -333,13 +387,6 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
private List<CategoryPath> data;
private Comparator<Object> searchComparator = new CategoryPathComparator();
/**
* Creates a new instance.
*
* @param dataTypeService {@link DataTypeManagerService}
*/
public CategoryPathDropDownSelectionDataModel(DataTypeManagerService dataTypeService) {
data = dataTypeService.getSortedCategoryPathList();
}
@@ -349,66 +396,32 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
return new CategoryPathDropDownRenderer();
}
/**
* Description of the CategoryPath is the display text of the path as a string.
* @param categoryPath CategoryPath
* @return String representation of the Category Path
*/
@Override
public String getDescription(CategoryPath categoryPath) {
return getDisplayText(categoryPath);
return null;
}
/**
* Retrieve the CategoryPath string representation.
*
* @param categoryPath CategoryPath
* @return String representation of the Category Path
*/
@Override
public String getDisplayText(CategoryPath categoryPath) {
return categoryPath.getPath();
}
/**
* Support for the filtering mechanism on the collection of Category Paths in the Data Manager.
*
* @param searchText String entered text
* @return filtered list of Category Paths
*/
@Override
public List<CategoryPath> getMatchingData(String searchText) {
if (searchText == null || searchText.length() == 0) {
return Collections.emptyList();
}
char END_CHAR = '\uffff';
String searchTextStart = searchText;
String searchTextEnd = searchText + END_CHAR;
int startIndex = Collections.binarySearch(data, searchTextStart, searchComparator);
int endIndex = Collections.binarySearch(data, searchTextEnd, searchComparator);
// the binary search returns a negative, incremented position if there is no match in the
// list for the given search
if (startIndex < 0) {
startIndex = -startIndex - 1;
List<CategoryPath> results = new ArrayList<>();
for (CategoryPath path : data) {
String pathString = path.getPath();
if (pathString.contains(searchText)) {
results.add(path);
}
}
if (endIndex < 0) {
endIndex = -endIndex - 1;
}
return data.subList(startIndex, endIndex);
return results;
}
/**
* Identify index of first matching CategoryPath from entered text string.
* @param dataCollection list of Category Paths
* @param text search string
* @return int index of first match
*/
@Override
public int getIndexOfFirstMatchingEntry(List<CategoryPath> dataCollection, String text) {
int lastPreferredMatchIndex = -1;
@@ -434,18 +447,6 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor {
return -1; // we only get here when the list is empty
}
private class CategoryPathComparator implements Comparator<Object> {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof CategoryPath && o2 instanceof String) {
CategoryPath path = (CategoryPath) o1;
return path.getName().compareToIgnoreCase(((String) o2));
}
throw new AssertException(
"CategoryPathCompartor used to compare files against a String key!");
}
}
private class CategoryPathDropDownRenderer extends GListCellRenderer<CategoryPath> {
@Override
@@ -15,9 +15,11 @@
*/
package ghidra.app.util.datatype;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.tree.TreePath;
@@ -141,15 +143,13 @@ public class DataTypeSelectionEditor extends AbstractCellEditor {
selectionField.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
browseButton = new BrowseButton();
browseButton.setToolTipText("Browse the Data Manager");
browseButton.addActionListener(e -> showDataTypeBrowser());
JPanel browsePanel = buildBrowsePanel();
editorPanel = new JPanel();
editorPanel.setOpaque(false);
editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS));
editorPanel.add(selectionField);
editorPanel.add(Box.createHorizontalStrut(5));
editorPanel.add(browseButton);
editorPanel.add(browsePanel);
keyListener = new KeyAdapter() {
@@ -171,6 +171,61 @@ public class DataTypeSelectionEditor extends AbstractCellEditor {
};
}
private JPanel buildBrowsePanel() {
// We override the various sizes to make sure the button does not get too big or too small,
// which changes depending upon the theme being used.
JPanel browsePanel = new JPanel() {
@Override
public Dimension getPreferredSize() {
int width = getBestWidth();
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = Math.min(width, preferredSize.width);
return preferredSize;
}
@Override
public Dimension getMinimumSize() {
int width = getBestWidth();
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = Math.min(width, preferredSize.width);
return preferredSize;
}
@Override
public Dimension getMaximumSize() {
int width = getBestWidth();
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = Math.min(width, preferredSize.width);
return preferredSize;
}
private int getBestWidth() {
Font f = getFont();
FontMetrics fm = getFontMetrics(f);
int width = fm.stringWidth(" . . . ");
return width;
}
};
browsePanel.setLayout(new BorderLayout());
browsePanel.setOpaque(false);
// Space the button so that it pops out visually. This was chosen by trial-and-error and
// looks reasonable on all themes.
Border empty = BorderFactory.createEmptyBorder(2, 2, 1, 1);
browsePanel.setBorder(empty);
browseButton = new BrowseButton();
browseButton.setToolTipText("Browse the Data Manager");
browseButton.addActionListener(e -> showDataTypeBrowser());
browsePanel.add(browseButton);
return browsePanel;
}
@Override
public Object getCellEditorValue() {
return selectionField.getSelectedValue();
@@ -1001,7 +1001,10 @@ public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIn
public void selectRow(final JTable table, final int rowIndex) {
waitForTable(table);
runSwing(() -> table.setRowSelectionInterval(rowIndex, rowIndex));
runSwing(() -> {
table.setRowSelectionInterval(rowIndex, rowIndex);
table.requestFocus();
});
waitForTable(table);
}
@@ -974,7 +974,7 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
fireUserChoiceMade(selectedItem);
}
class PreviewListener implements ListSelectionListener {
private class PreviewListener implements ListSelectionListener {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -55,5 +55,7 @@ public class BrowseButton extends JButton {
setIcon(ICON);
setName(NAME);
setToolTipText(TOOLTIP_TEXT);
getAccessibleContext().setAccessibleName("Browse");
}
}
@@ -19,6 +19,9 @@ import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.junit.Test;
@@ -66,7 +69,21 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
GhidraTable table = (GhidraTable) getInstanceField("matchingStructuresTable", dialog);
selectRow(table, 2);
captureDialog(500, 400);
shrinkCategoryColumn(table);
captureDialog(600, 500);
}
private void shrinkCategoryColumn(JTable table) {
runSwing(() -> {
TableColumnModel columnModel = table.getColumnModel();
int columnIndex = columnModel.getColumnIndex("Category");
TableColumn column = columnModel.getColumn(columnIndex);
int size = 150;
column.setPreferredWidth(size);
column.setMaxWidth(size);
});
}
@Test