Merge remote-tracking branch

'origin/GP-5327-create-structure-dialog-updates' (Closes #6713)
This commit is contained in:
Ryan Kurtz
2025-05-22 06:09:48 -04:00
25 changed files with 1224 additions and 295 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

View File

@@ -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);
}

View File

@@ -93,6 +93,11 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
throw new UnsupportedOperationException();
}
@Override
public CategoryPath getCategoryPath(TreePath selectedPath) {
throw new UnsupportedOperationException();
}
@Override
public List<DataType> getFavorites() {
throw new UnsupportedOperationException();
@@ -111,6 +116,11 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
return dataTypes;
}
@Override
public List<CategoryPath> getSortedCategoryPathList() {
throw new UnsupportedOperationException();
}
@Override
public void removeDataTypeManagerChangeListener(DataTypeManagerChangeListener listener) {
throw new UnsupportedOperationException();

View File

@@ -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) {

View File

@@ -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.
@@ -40,7 +40,6 @@ class CreateStructureAction extends ListingContextAction {
new String[] { "Data", "Create Structure..." };
private DataPlugin plugin;
private CreateStructureDialog createStructureDialog;
public CreateStructureAction(DataPlugin plugin) {
super("Create Structure", plugin.getName());
@@ -49,25 +48,12 @@ class CreateStructureAction extends ListingContextAction {
setKeyBindingData(new KeyBindingData(KeyEvent.VK_OPEN_BRACKET, InputEvent.SHIFT_DOWN_MASK));
this.plugin = plugin;
setEnabled(true);
createStructureDialog = new CreateStructureDialog(plugin.getTool());
}
@Override
public void dispose() {
super.dispose();
createStructureDialog.dispose();
}
/**
* Method called when the action is invoked.
*/
@Override
public void actionPerformed(ListingActionContext programActionContext) {
Program program = programActionContext.getProgram();
ProgramSelection sel = programActionContext.getSelection();
if (sel != null && !sel.isEmpty()) {
InteriorSelection interiorSel = sel.getInteriorSelection();
if (interiorSel != null) {
@@ -113,8 +99,8 @@ class CreateStructureAction extends ListingContextAction {
return;
}
Structure userChoice =
createStructureDialog.showCreateStructureDialog(program, tempStructure);
CreateStructureDialog dialog = new CreateStructureDialog(plugin.getTool(), program);
Structure userChoice = dialog.showCreateStructureDialog(program, tempStructure);
if (userChoice != null) {
CreateStructureInStructureCmd cmd = new CreateStructureInStructureCmd(userChoice,
@@ -162,8 +148,9 @@ class CreateStructureAction extends ListingContextAction {
return;
}
CreateStructureDialog dialog = new CreateStructureDialog(plugin.getTool(), program);
Structure userChoice =
createStructureDialog.showCreateStructureDialog(program, tempStructure);
dialog.showCreateStructureDialog(program, tempStructure);
// exit if the user cancels the operation
if (userChoice != null) {

View File

@@ -15,10 +15,10 @@
*/
package ghidra.app.plugin.core.data;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.TitledBorder;
@@ -28,42 +28,44 @@ import javax.swing.table.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.apache.commons.lang3.StringUtils;
import docking.ReusableDialogComponentProvider;
import docking.widgets.button.GRadioButton;
import docking.widgets.table.*;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.ToolTipUtils;
import ghidra.app.util.datatype.CategoryPathSelectionEditor;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.layout.PairLayout;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
/**
* A dialog that allows the user to create a new structure based upon providing
* a new name or by using the name of an existing structure.
*
*
*/
public class CreateStructureDialog extends ReusableDialogComponentProvider {
private static final String NEW_STRUCTURE_STATUS_PREFIX = "Creating new structure: ";
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;
private GhidraTable matchingStructuresTable;
private StructureTableModel structureTableModel;
private Structure currentStructure;
private Program currentProgram;
private PluginTool pluginTool;
private TitledBorder nameBorder;
private TitledBorder structureBorder;
private JRadioButton createNewStructButton;
private JRadioButton useExistingStructButton;
private JRadioButton exactMatchButton;
private JRadioButton sizeMatchButton;
private GhidraTableFilterPanel<StructureWrapper> filterPanel;
@@ -72,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());
@@ -97,43 +101,79 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
private JPanel buildMainPanel() {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.add(buildNameTextFieldPanel());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(buildMatchingStructurePanel());
mainPanel.add(createChoicePanel());
setStatusJustification(SwingConstants.LEFT);
setCreateStructureByName(true);
mainPanel.getAccessibleContext().setAccessibleName("Create Structure");
return mainPanel;
}
private JPanel buildNameTextFieldPanel() {
JPanel namePanel = new JPanel();
namePanel.setLayout(new BoxLayout(namePanel, BoxLayout.Y_AXIS));
nameBorder = BorderFactory.createTitledBorder("Create Structure By Name");
namePanel.setBorder(nameBorder);
private JPanel createChoicePanel() {
JPanel radioChoicePanel = new JPanel(new BorderLayout());
nameTextField = new JTextField() {
// make sure our height doesn't stretch
createNewStructButton = new GRadioButton("Create New");
createNewStructButton.getAccessibleContext().setAccessibleName("Create New");
useExistingStructButton = new GRadioButton("Use Existing");
useExistingStructButton.getAccessibleContext().setAccessibleName("Use Existing");
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(createNewStructButton);
buttonGroup.add(useExistingStructButton);
createNewStructButton.setSelected(true);
ItemListener choiceListener = event -> updateEnablement();
createNewStructButton.addItemListener(choiceListener);
useExistingStructButton.addItemListener(choiceListener);
JPanel createNewStructPanel = new JPanel();
createNewStructPanel.setLayout(new BoxLayout(createNewStructPanel, BoxLayout.Y_AXIS));
// force the radio button to the left for clarity
createNewStructPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
// indent everything under the radio button
createNewStructPanel.setBorder(BorderFactory.createEmptyBorder(5, 30, 15, 5));
JPanel useExistingStructPanel = new JPanel();
useExistingStructPanel.setLayout(new BoxLayout(useExistingStructPanel, BoxLayout.Y_AXIS));
useExistingStructPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
useExistingStructPanel.setBorder(BorderFactory.createEmptyBorder(0, 30, 10, 5));
createNewStructPanel.add(buildCreateNewStructPanel());
useExistingStructPanel.add(buildMatchingStructPanel());
JPanel top = new JPanel();
top.setLayout(new BoxLayout(top, BoxLayout.PAGE_AXIS));
top.add(createNewStructButton);
top.add(createNewStructPanel);
JPanel center = new JPanel();
center.setLayout(new BoxLayout(center, BoxLayout.PAGE_AXIS));
center.add(useExistingStructButton);
center.add(useExistingStructPanel);
// we would like the structure table to get all extra space, so put it in the center
radioChoicePanel.add(top, BorderLayout.NORTH);
radioChoicePanel.add(center, BorderLayout.CENTER);
return radioChoicePanel;
}
private JPanel buildCreateNewStructPanel() {
JPanel newStructPanel = new JPanel();
newStructPanel.setLayout(new PairLayout());
newStructPanel.setToolTipText("Enter a name and category (optional)");
JLabel nameLabel = new JLabel("Name: ");
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.addFocusListener(new FocusAdapter() {
@Override
public Dimension getMaximumSize() {
Dimension d = super.getMaximumSize();
d.height = getPreferredSize().height;
return d;
}
};
nameTextField.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
setCreateStructureByName(true);
nameTextField.requestFocus();
public void focusGained(FocusEvent e) {
createNewStructButton.setSelected(true);
updateEnablement();
}
});
nameTextField.getAccessibleContext().setAccessibleName("Name Text");
namePanel.add(nameTextField);
nameTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent event) {
@@ -153,7 +193,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
private void checkText(Document document) {
try {
String text = document.getText(0, document.getLength());
if ((text == null) || (text.trim().length() == 0)) {
if (StringUtils.isBlank(text)) {
okButton.setEnabled(false);
updateStatusText(true, null);
}
@@ -167,17 +207,60 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
}
}
});
namePanel.getAccessibleContext().setAccessibleName("Name Text");
return namePanel;
JLabel categoryLabel = new JLabel("Category: ");
buildCategoryPathEditor();
newStructPanel.add(nameLabel);
newStructPanel.add(nameTextField);
newStructPanel.add(categoryLabel);
newStructPanel.add(categoryPathEditor.getEditorComponent());
return newStructPanel;
}
private JPanel buildMatchingStructurePanel() {
private void buildCategoryPathEditor() {
categoryPathEditor = new CategoryPathSelectionEditor(pluginTool);
JComponent editorComponent = categoryPathEditor.getEditorComponent();
editorComponent.getAccessibleContext().setAccessibleName("Category");
categoryPathEditor.setCellEditorValue(CategoryPath.ROOT);
// 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) {
createNewStructButton.setSelected(true);
updateEnablement();
}
});
}
private JPanel buildMatchingStructPanel() {
JPanel structurePanel = new JPanel();
structurePanel.setLayout(new BoxLayout(structurePanel, BoxLayout.Y_AXIS));
structureBorder = BorderFactory.createTitledBorder("Use Existing Structure");
structurePanel.setBorder(structureBorder);
GTable table = buildMatchingStructuresTable();
// allow user to re-activate the "use existing" panel without forcing a radio button click.
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
useExistingStructButton.setSelected(true);
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
@Override
@@ -190,14 +273,13 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.getAccessibleContext().setAccessibleName("Scroll");
structurePanel.add(scrollPane);
structurePanel.add(Box.createVerticalStrut(10));
filterPanel.getAccessibleContext().setAccessibleName("Structure Filter");
structurePanel.add(filterPanel);
structurePanel.add(Box.createVerticalStrut(10));
structurePanel.add(buildMatchingStyelPanel());
structurePanel.add(Box.createVerticalStrut(10));
structurePanel.getAccessibleContext().setAccessibleName("Matching Structure");
return structurePanel;
}
@@ -216,32 +298,41 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
TableColumn column = columnModel.getColumn(i);
column.setCellRenderer(cellRenderer);
}
matchingStructuresTable.getColumnModel().getColumn(0);
ListSelectionModel lsm = matchingStructuresTable.getSelectionModel();
lsm.addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
ListSelectionModel sourceListSelectionModel = (ListSelectionModel) e.getSource();
if ((sourceListSelectionModel != null) &&
!(sourceListSelectionModel.isSelectionEmpty())) {
// show the user that the structure choice is now
// coming from the list of current structures
Structure structure = ((StructureWrapper) matchingStructuresTable
.getValueAt(matchingStructuresTable.getSelectedRow(), 0))
.getStructure();
updateStatusText(false, structure.getName());
setCreateStructureByName(false);
}
else {
updateStatusText(true, nameTextField.getText());
setCreateStructureByName(true);
}
if (e.getValueIsAdjusting()) {
return;
}
ListSelectionModel selectionModel = (ListSelectionModel) e.getSource();
if (selectionModel != null && !selectionModel.isSelectionEmpty()) {
// Show the user that the structure choice is now coming from the table
useExistingStructButton.setSelected(true);
}
updateEnablement();
});
matchingStructuresTable.getAccessibleContext().setAccessibleName("Matching Structures");
return matchingStructuresTable;
}
private void updateStatus() {
clearStatusText();
if (useExistingStructButton.isSelected()) {
Structure structure = getSelectedStructure();
if (structure != null) {
updateStatusText(false, structure.getName());
}
}
else {
updateStatusText(true, nameTextField.getText());
}
}
private JPanel buildMatchingStyelPanel() {
JPanel matchingStylePanel = new JPanel() {
@Override
@@ -275,51 +366,49 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
matchingStylePanel.add(exactMatchButton);
matchingStylePanel.add(sizeMatchButton);
matchingStylePanel.getAccessibleContext().setAccessibleName("Matching Style");
return matchingStylePanel;
}
// toggles whether the structure being created is new, based upon the name field, or a current
// structure, based upon a structure in the table. This method updates the GUI to reflect the
// current creation state.
private void setCreateStructureByName(boolean createStructureByName) {
if (createStructureByName) {
nameBorder.setTitleColor(Colors.FOREGROUND);
structureBorder.setTitleColor(Colors.FOREGROUND_DISABLED);
}
else {
nameBorder.setTitleColor(Colors.FOREGROUND_DISABLED);
structureBorder.setTitleColor(Colors.FOREGROUND);
}
nameTextField.setEnabled(createStructureByName);
if (createStructureByName) {
// Toggles whether the structure being created is new, based upon the name field, or existing,
// based upon a structure in the table.
private void updateEnablement() {
if (createNewStructButton.isSelected()) {
nameTextField.setEnabled(true);
categoryPathEditor.setEnabled(true);
matchingStructuresTable.setEnabled(false);
exactMatchButton.setEnabled(false);
sizeMatchButton.setEnabled(false);
matchingStructuresTable.clearSelection();
}
else {
nameTextField.setEnabled(false);
categoryPathEditor.setEnabled(false);
matchingStructuresTable.setEnabled(true);
exactMatchButton.setEnabled(true);
sizeMatchButton.setEnabled(true);
}
rootPanel.repaint();
updateStatus();
}
// populates the table with structures that match the one the passed to
// this class in terms of data contained
// Populates the table with structures that match the one the passed to this class in terms of
// data contained
private void searchForMatchingStructures(final Program program, final Structure structure) {
Swing.runLater(() -> {
// Get the structures from the DataTypeManagers of the
// DataTypeManagerService
DataTypeManagerService service = pluginTool.getService(DataTypeManagerService.class);
DataTypeManager[] dataTypeManagers = null;
// Get the structures from the DataTypeManagers of the DataTypeManagerService
DataTypeManagerService service = pluginTool.getService(DataTypeManagerService.class);
if (service != null) {
dataTypeManagers = service.getDataTypeManagers();
}
else {
dataTypeManagers = new DataTypeManager[] { program.getDataTypeManager() };
}
DataTypeManager[] dataTypeManagers = null;
getMatchingStructuresFromDataTypeManagers(structure, dataTypeManagers);
});
if (service != null) {
dataTypeManagers = service.getDataTypeManagers();
}
else {
dataTypeManagers = new DataTypeManager[] { program.getDataTypeManager() };
}
getMatchingStructuresFromDataTypeManagers(structure, dataTypeManagers);
}
private void getMatchingStructuresFromDataTypeManagers(Structure structure,
@@ -330,8 +419,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
Iterator<Structure> structureIterator = dataTypeManager.getAllStructures();
while (structureIterator.hasNext()) {
// only add structures that match the one that was
// passed to this dialog
// only add structures that match the one that was passed to this dialog
Structure nextStructure = structureIterator.next();
if (compareStructures(nextStructure, structure)) {
@@ -343,8 +431,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
structureTableModel.setData(dataList);
}
// compares structures depending upon the type of matching that is being
// used
// compares structures depending upon the type of matching that is being used
private boolean compareStructures(Structure structureA, Structure structureB) {
if (sizeMatchButton.isSelected()) {
return compareStructuresBySize(structureA, structureB);
@@ -358,9 +445,9 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
return (structureA.getLength() == structureB.getLength());
}
// Compares the two structures based upon the data contained. This method
// is used instead of isEquivalent() to avoid the comparison of data field
// names, which is not a concern for this class.
// Compares the two structures based upon the data contained. This method is used instead of
// isEquivalent() to avoid the comparison of data field names, which is not a concern for this
// class.
private boolean compareStructuresByData(Structure structureA, Structure structureB) {
if (structureA.getLength() != structureB.getLength()) {
@@ -382,12 +469,11 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
return false;
}
// called by compareStructures() to compare the data that the structures
// contain
// called by compareStructures() to compare the data that the structures contain
private boolean compareDataTypeComponents(DataTypeComponent dtcA, DataTypeComponent dtcB) {
// be sure to do the easiest comparisons first, those based on
// equality and then do the possibly recursive calls last
// be sure to do the easiest comparisons first, those based on equality and then do the
// possibly recursive calls last
if ((dtcA.getLength() == dtcB.getLength()) && (dtcA.getOffset() == dtcB.getOffset()) &&
(dtcA.getOrdinal() == dtcB.getOrdinal()) &&
compareDataTypes(dtcA.getDataType(), dtcB.getDataType())) {
@@ -397,12 +483,10 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
return false;
}
// called by compareDataTypeComponents() in order to compare the data
// types of the components
// called by compareDataTypeComponents() in order to compare the data types of the components
private boolean compareDataTypes(DataType typeA, DataType typeB) {
// make sure the name and length are the same and then compare
// the data types recursively
// make sure the name and length are the same and then compare the data types recursively
if (typeA instanceof Structure) {
if (typeB instanceof Structure) {
return compareStructuresByData((Structure) typeA, (Structure) typeB);
@@ -418,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>
@@ -445,13 +541,11 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
"Non-null structure is required when showing the Create Structure dialog.");
}
// init the return value, which will be updated if the user presses
// the OK button
// init the return value, which will be updated if the user presses the OK button
currentStructure = structure;
nameTextField.setText(currentStructure.getName());
updateStatusText(true, currentStructure.getName());
searchForMatchingStructures(program, structure);
// modal block
@@ -467,39 +561,113 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
super.cancelCallback();
}
/**
* The callback method for when the "OK" button is pressed.
*/
@Override
protected void okCallback() {
if (nameTextField.isEnabled()) {
// 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;
}
}
else {
if (useExistingStructButton.isSelected()) {
// get the selected object in the table
currentStructure = ((StructureWrapper) matchingStructuresTable
.getValueAt(matchingStructuresTable.getSelectedRow(), 0)).getStructure();
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();
}
// a table model that is used to allow for the easy updating of the
// table with new List data and to disable editing
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) {
return null;
}
Object cellValue = matchingStructuresTable.getValueAt(row, 0);
return ((StructureWrapper) cellValue).getStructure();
}
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.
String editorValue = categoryPathEditor.getCellEditorValueAsText();
if (path != null && path.getPath().equals(editorValue)) {
currentStructure.setCategoryPath(path);
return;
}
// Selecting/entering a category is optional; root is default
if (!editorValue.isBlank()) {
CategoryPath parsedPath = parseEnteredCategoryPath(editorValue);
currentStructure.setCategoryPath(parsedPath);
return;
}
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 new CategoryPath(categoryText);
}
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
// and to disable editing
/*package*/class StructureTableModel extends AbstractSortedTableModel<StructureWrapper> {
private List<StructureWrapper> data = Collections.emptyList();
@@ -527,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;
}
@@ -555,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;
@@ -570,19 +737,17 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider {
return;
}
String message = null;
String prefix = EXISITING_STRUCTURE_STATUS_PREFIX;
if (creatingNew) {
message = NEW_STRUCTURE_STATUS_PREFIX;
}
else {
message = EXISITING_STRUCTURE_STATUS_PREFIX;
prefix = NEW_STRUCTURE_STATUS_PREFIX;
}
setStatusText("<html>" + message + "<BR>\"" + HTMLUtilities.escapeHTML(name) + "\"");
String escapeName = HTMLUtilities.escapeHTML(name);
String message = "<html>%s'%s'".formatted(prefix, escapeName);
setStatusText(message);
}
// this class is used instead of a cell renderer so that sorting will
// work on the table
// this class is used instead of a cell renderer so that sorting will work on the table
/*package*/class StructureWrapper {
private Structure structure;
@@ -601,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) {
@@ -621,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());
}

View File

@@ -594,6 +594,19 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return dialog.getSelectedDataType();
}
@Override
public CategoryPath getCategoryPath(TreePath selectedPath) {
DataTypeChooserDialog dialog = new DataTypeChooserDialog(this);
dialog.setCategorySelectionMode(true);
dialog.setShowProgramArchiveOnly(true);
if (selectedPath != null) {
dialog.setSelectedPath(selectedPath);
}
tool.showDialog(dialog);
return dialog.getSelectedCategoryPath();
}
@Override
public DataTypeManager[] getDataTypeManagers() {
return dataTypeManagerHandler.getDataTypeManagers();
@@ -664,6 +677,11 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return dataTypeManagerHandler.getDataTypeIndexer().getSortedDataTypeList();
}
@Override
public List<CategoryPath> getSortedCategoryPathList() {
return dataTypeManagerHandler.getDataTypeIndexer().getSortedCategoryPathList();
}
@Override
public void setDataTypeSelected(DataType dataType) {
if (provider.isVisible()) {

View File

@@ -23,10 +23,11 @@ import ghidra.program.model.data.*;
import ghidra.util.task.*;
/**
* A class that stores a sorted list of all the {@link DataType} objects in the current data type
* manager plugin. This class does its work lazily such that no work is done until
* {@link #getSortedDataTypeList()} is called. Even when that method is called no work will be
* done if the state of the data types in the system hasn't changed.
* A class that stores a sorted list of all the {@link DataType} and a list of all the
* {@link CategoryPath} objects in the current data type manager plugin. This class does its work
* lazily such that no work is done until {@link #getSortedDataTypeList()} is called. Even when that
* method is called no work will be done if the state of the data types in the system hasn't
* changed.
*/
public class DataTypeIndexer {
private List<DataTypeManager> dataTypeManagers = new ArrayList<>();
@@ -34,6 +35,7 @@ public class DataTypeIndexer {
private DataTypeIndexUpdateListener listener = new DataTypeIndexUpdateListener();
private volatile boolean isStale = true;
private List<CategoryPath> categoryPathList = Collections.emptyList();
// Note: synchronizing here prevents concurrent mod issues with the managers list
public synchronized void addDataTypeManager(DataTypeManager dataTypeManager) {
@@ -79,6 +81,18 @@ public class DataTypeIndexer {
return Collections.unmodifiableList(newList);
}
/**
* Returns a list of the unique Category Paths ({@link CategoryPath}) as utilized by the
* data types open in the current tool.
*
* @return a list of the {@link CategoryPath} associated with the data types open in the
* current tool.
*/
public List<CategoryPath> getSortedCategoryPathList() {
updateDataTypeList(); // the category list is quietly updated in the background
return categoryPathList;
}
private List<DataType> updateDataTypeList() {
if (!isStale) {
return dataTypeList;
@@ -95,8 +109,9 @@ public class DataTypeIndexer {
task.run(TaskMonitor.DUMMY);
}
List<DataType> newList = task.getList();
return newList;
List<DataType> newDataTypeList = task.getList();
categoryPathList = task.getCategoryPathList();
return newDataTypeList;
}
// Note: purposefully not synchronized for speed
@@ -107,6 +122,7 @@ public class DataTypeIndexer {
// is possible that once marked stale, we may never have another request for this data
// again.
dataTypeList = Collections.emptyList();
categoryPathList = Collections.emptyList();
}
//==================================================================================================
@@ -153,9 +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> list = new ArrayList<>();
private List<DataType> dataTypes = new ArrayList<>();
private List<CategoryPath> categories = new ArrayList<>();
private Set<CategoryPath> categorySet = new HashSet<>();
IndexerTask() {
super("Data Type Indexer Task", false, true, true);
@@ -167,17 +211,40 @@ 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(list);
for (DataTypeManager dtm : dataTypeManagers) {
monitor.setMessage("Searching " + dtm.getName());
dtm.getAllDataTypes(dataTypes);
Category root = dtm.getRootCategory();
populateCategories(root);
monitor.incrementProgress(1);
}
Collections.sort(list, new CaseInsensitiveDataTypeComparator());
Collections.sort(dataTypes, new CaseInsensitiveDataTypeComparator());
categories.addAll(categorySet);
Collections.sort(categories, new CaseInsensitiveCategoryComparator());
}
private void populateCategories(Category parent) {
categorySet.add(parent.getCategoryPath());
Category[] children = parent.getCategories();
for (Category category : children) {
CategoryPath path = category.getCategoryPath();
categorySet.add(path);
populateCategories(category);
}
}
List<DataType> getList() {
return list;
return dataTypes;
}
List<CategoryPath> getCategoryPathList() {
return categories;
}
}
@@ -266,4 +333,5 @@ public class DataTypeIndexer {
markStale();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -31,13 +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.tree.DataTypeArchiveGTree;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
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.DataType;
import ghidra.program.model.data.*;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -47,20 +48,27 @@ import ghidra.util.task.TaskMonitor;
* {@link DataTypeSelectionDialog} utility widget.
*/
public class DataTypeChooserDialog extends DialogComponentProvider {
private DataTypeManagerPlugin plugin;
private DataTypeArchiveGTree tree;
private DataType selectedDataType;
private GLabel messageLabel;
boolean isFilterEditable;
private CategoryPath selectedCategoryPath;
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);
tree.setEditable(false);
tree.updateFilterForChoosingDataType();
tree.addGTreeSelectionListener(e -> setOkEnabled(getSelectedNode() != null));
tree.addGTreeSelectionListener(e -> setOkEnabled(isValidNodeSelected()));
tree.addMouseListener(new MouseAdapter() {
@Override
@@ -69,7 +77,18 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
return;
}
DataTypeNode selectedNode = getSelectedNode();
if (categorySelectionMode) {
CategoryPath path = getCurrentCategoryPath();
if (path == null) {
return;
}
selectedCategoryPath = path;
close();
return;
}
DataTypeNode selectedNode = getSelectedDtNode();
if (selectedNode == null) {
return;
}
@@ -84,20 +103,75 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
addOKButton();
addCancelButton();
setOkEnabled(false);
setHelpLocation(new HelpLocation("DataTypeEditors", "browse"));
}
private DataTypeNode getSelectedNode() {
/**
* Signals that this chooser is intended to pick {@link CategoryPath}s instead of data types.
* @param categorySelectionMode true to pick category paths
*/
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() {
TreePath[] selectionPath = tree.getSelectionPaths();
if (selectionPath.length != 1) {
return false;
}
GTreeNode node = (GTreeNode) selectionPath[0].getLastPathComponent();
return node instanceof DataTypeTreeNode;
}
private DataTypeNode getSelectedDtNode() {
TreePath[] selectionPath = tree.getSelectionPaths();
if (selectionPath.length != 1) {
return null;
}
GTreeNode node = (GTreeNode) selectionPath[0].getLastPathComponent();
if (!(node instanceof DataTypeNode)) {
if (node instanceof DataTypeNode dtNode) {
return dtNode;
}
return null;
}
private CategoryNode getSelectedCategoryNode() {
TreePath[] selectionPath = tree.getSelectionPaths();
if (selectionPath.length != 1) {
return null;
}
return (DataTypeNode) node;
GTreeNode node = (GTreeNode) selectionPath[0].getLastPathComponent();
if (node instanceof CategoryNode catNode) {
return catNode;
}
return null;
}
@Override
@@ -108,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);
@@ -119,12 +193,36 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
// can't be null since we control button enablement
DataTypeNode dataTypeNode = getSelectedNode();
selectedDataType = dataTypeNode.getDataType();
if (categorySelectionMode) {
selectedCategoryPath = getCurrentCategoryPath();
}
else {
DataTypeNode dtNode = getSelectedDtNode();
selectedDataType = dtNode.getDataType();
}
close();
}
private CategoryPath getCurrentCategoryPath() {
DataTypeNode dtNode = getSelectedDtNode();
// the user may have picked a data type node or a category node
if (dtNode != null) {
return dtNode.getDataType().getCategoryPath();
}
CategoryNode categoryNode = getSelectedCategoryNode();
if (categoryNode != null) {
Category category = categoryNode.getCategory();
return category.getCategoryPath();
}
return null;
}
/**
* A convenience method to show this dialog with the following configuration:
* <ul>
@@ -207,6 +305,10 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
tree.setFilterProvider(provider);
}
public CategoryPath getSelectedCategoryPath() {
return selectedCategoryPath;
}
public DataType getSelectedDataType() {
return selectedDataType;
}
@@ -263,4 +365,5 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
}
}
}
}

View File

@@ -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

View File

@@ -137,6 +137,15 @@ public interface DataTypeManagerService extends DataTypeQueryService, DataTypeAr
*/
public DataType getDataType(TreePath selectedPath);
/**
* Shows the user a dialog that allows them to choose a category path from a tree of all
* available categories.
*
* @param selectedPath An optional tree path to select in the tree
* @return A category path chosen by the user
*/
public CategoryPath getCategoryPath(TreePath selectedPath);
/**
* Examines all enum dataTypes for items that match the given value. Returns a list of Strings
* that might make sense for the given value.

View File

@@ -40,6 +40,18 @@ public interface DataTypeQueryService {
*/
public List<DataType> getSortedDataTypeList();
/**
* Prompts the user for a data type. The optional filter text will be used to filter the tree
* of available types.
* Gets the sorted list of all category paths known by this service via its owned
* DataTypeManagers. This method can be called frequently, as the underlying data is indexed
* and only updated as changes are made. The sorting of the list is done using the
* natural sort of the {@link CategoryPath} objects.
*
* @return the sorted list of known category paths.
*/
public List<CategoryPath> getSortedCategoryPathList();
/**
* This method simply calls {@link #promptForDataType(String)}
* @deprecated use {@link #promptForDataType(String)}
@@ -49,8 +61,10 @@ public interface DataTypeQueryService {
public DataType getDataType(String filterText);
/**
* Prompts the user for a data type. The optional filter text will be used to filter the tree
* of available types.
* Obtain the preferred datatype which corresponds to the specified
* datatype specified by filterText. A tool-based service provider
* may prompt the user to select a datatype if more than one possibility
* exists.
*
* @param filterText If not null, this text filters the visible data types to only show those
* that start with the given text

View File

@@ -0,0 +1,467 @@
/* ###
* IP: GHIDRA
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.datatype;
import java.awt.*;
import java.awt.event.*;
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;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.DropDownTextFieldDataModel;
import docking.widgets.button.BrowseButton;
import docking.widgets.list.GListCellRenderer;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.data.CategoryPath;
/**
* An editor that is used to show the {@link DropDownSelectionTextField} for the entering of
* category paths by name and offers the user of a completion window. This editor also provides a
* browse button that when pressed will show a data type tree so that the user may browse a tree
* of known category paths.
* <p>
* <u>Stand Alone Usage</u><br>
* In order to use this component directly you need to call {@link #getEditorComponent()}. This
* will give you a Component for editing.
* <p>
* In order to know when changes are made to the component you need to add a DocumentListener
* via the {@link #addDocumentListener(DocumentListener)} method. The added listener will be
* notified as the user enters text into the editor's text field.
*/
public class CategoryPathSelectionEditor extends AbstractCellEditor {
private JPanel editorPanel;
private DropDownSelectionTextField<CategoryPath> selectionField;
private JButton browseButton;
private DataTypeManagerService dataTypeManagerService;
private KeyAdapter keyListener;
private NavigationDirection navigationDirection;
// optional path to initially select in the data type chooser tree
private TreePath initiallySelectedTreePath;
/**
* Creates a new instance.
*
* @param serviceProvider {@link ServiceProvider}
*/
public CategoryPathSelectionEditor(ServiceProvider serviceProvider) {
this.dataTypeManagerService = serviceProvider.getService(DataTypeManagerService.class);
if (this.dataTypeManagerService == null) {
throw new NullPointerException("DataTypeManagerService cannot be null");
}
init();
}
/**
* Sets whether this editor should consumer Enter key presses
* @see DropDownSelectionTextField#setConsumeEnterKeyPress(boolean)
*
* @param consume true to consume
*/
public void setConsumeEnterKeyPress(boolean consume) {
selectionField.setConsumeEnterKeyPress(consume);
}
protected DropDownSelectionTextField<CategoryPath> createDropDownSelectionTextField(
CategoryPathDropDownSelectionDataModel model) {
return new DropDownSelectionTextField<>(model);
}
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) {
fireEditingCanceled();
navigationDirection = null;
}
@Override
public void editingStopped(ChangeEvent e) {
fireEditingStopped();
navigationDirection = null;
}
});
selectionField.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
selectionField.setEnabled(true);
selectionField.requestFocus();
}
});
JPanel browsePanel = buildBrowsePanel();
editorPanel = new JPanel();
editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS));
editorPanel.add(selectionField);
editorPanel.add(browsePanel);
keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_TAB) {
if (e.isShiftDown()) {
navigationDirection = NavigationDirection.BACKWARD;
}
else {
navigationDirection = NavigationDirection.FORWARD;
}
fireEditingStopped();
e.consume();
}
}
};
}
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
*/
@Override
public CategoryPath getCellEditorValue() {
return selectionField.getSelectedValue();
}
/**
* If a path was selected from the drop-down list, it is already
* well-formed and cannot be null.
* @return the selected category path as CategoryPath
*/
public CategoryPath getCellEditorValueAsCategoryPath() {
return selectionField.getSelectedValue();
}
/**
* Returns the text value of the editor's text field.
* @return the text value of the editor's text field.
*/
public String getCellEditorValueAsText() {
return selectionField.getText();
}
/**
* Returns the component that allows the user to edit.
* @return the component that allows the user to edit.
*/
public JComponent getEditorComponent() {
return editorPanel;
}
/**
* 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;
}
/**
* The browse button which opens a menu with the Category Path collection from the data manager.
* @return browseButton
*/
public JButton getBrowseButton() {
return browseButton;
}
/**
* Sets the initially selected node in the data type tree that the user can choose to
* show.
*
* @param path The path to set
*/
public void setDefaultSelectedTreePath(TreePath path) {
this.initiallySelectedTreePath = path;
}
/**
* Place focus on the selectionField.
*/
public void requestFocus() {
selectionField.requestFocus();
}
/**
* Highlights the text of the cell editor.
*/
void selectCellEditorValue() {
selectionField.selectAll();
}
/**
* Sets the cell editor value as the entered String text.
* @param text String input
*/
public void setCellEditorValueAsText(String text) {
selectionField.setText(text);
navigationDirection = null;
}
/**
* Sets the value to be edited on this cell editor.
*
* @param path The data type which is to be edited.
*/
public void setCellEditorValue(CategoryPath path) {
selectionField.setSelectedValue(path);
navigationDirection = null;
}
/**
* Adds a document listener to the text field editing component of this editor so that users
* can be notified when the text contents of the editor change.
* @param listener the listener to add.
*/
public void addDocumentListener(DocumentListener listener) {
selectionField.getDocument().addDocumentListener(listener);
}
/**
* Removes a previously added document listener.
* @param listener the listener to remove.
*/
public void removeDocumentListener(DocumentListener listener) {
selectionField.getDocument().removeDocumentListener(listener);
}
/**
* Add the provided FocusListener to the selectionField.
* @param listener FocusListener
*/
public void addFocusListener(FocusListener listener) {
selectionField.addFocusListener(listener);
}
/**
* Remove the provided FocusListener from the selectionField.
* @param listener FocusListener
*/
public void removeFocusListener(FocusListener listener) {
selectionField.removeFocusListener(listener);
}
/**
* Toggle Tab key commits an edit. Sets the traversal key enabled field of the selectionField.
* @param doesCommit Boolean
*/
public void setTabCommitsEdit(boolean doesCommit) {
selectionField.setFocusTraversalKeysEnabled(!doesCommit);
removeKeyListener(keyListener); // always remove to prevent multiple additions
if (doesCommit) {
addKeyListener(keyListener);
}
}
/**
* Returns the direction of the user triggered navigation; null if the user did not trigger
* navigation out of this component.
* @return the direction
*/
public NavigationDirection getNavigationDirection() {
return navigationDirection;
}
private void addKeyListener(KeyListener listener) {
selectionField.addKeyListener(listener);
}
private void removeKeyListener(KeyListener listener) {
selectionField.removeKeyListener(listener);
}
private void showBrowser() {
CategoryPath path = dataTypeManagerService.getCategoryPath(initiallySelectedTreePath);
if (path != null) {
setCellEditorValue(path);
selectionField.requestFocus();
}
}
/**
* Enable or disable the Category Path Text Field.
* @param createStructureByName Boolean
*/
public void setEnabled(boolean createStructureByName) {
selectionField.setEnabled(createStructureByName);
}
/**
* Determine whether the Category Path Text Field is enabled.
* @return isEnabled boolean
*/
public boolean isEnabled() {
return selectionField.isEnabled();
}
/**
* CategoryPathDropDownSelectionDataModel class handles the display and selection of a
* Category Path.
*/
private class CategoryPathDropDownSelectionDataModel
implements DropDownTextFieldDataModel<CategoryPath> {
private List<CategoryPath> data;
public CategoryPathDropDownSelectionDataModel(DataTypeManagerService dataTypeService) {
data = dataTypeService.getSortedCategoryPathList();
}
@Override
public ListCellRenderer<CategoryPath> getListRenderer() {
return new CategoryPathDropDownRenderer();
}
@Override
public String getDescription(CategoryPath categoryPath) {
return null;
}
@Override
public String getDisplayText(CategoryPath categoryPath) {
return categoryPath.getPath();
}
@Override
public List<CategoryPath> getMatchingData(String searchText) {
if (searchText == null || searchText.length() == 0) {
return Collections.emptyList();
}
List<CategoryPath> results = new ArrayList<>();
for (CategoryPath path : data) {
String pathString = path.getPath();
if (pathString.contains(searchText)) {
results.add(path);
}
}
return results;
}
@Override
public int getIndexOfFirstMatchingEntry(List<CategoryPath> dataCollection, String text) {
int lastPreferredMatchIndex = -1;
for (int i = 0; i < data.size(); i++) {
CategoryPath dataType = data.get(i);
String dataTypeName = dataType.getName();
dataTypeName = dataTypeName.replaceAll(" ", "");
if (dataTypeName.equals(text)) {
// an exact match is the best possible match!
return i;
}
if (dataTypeName.equalsIgnoreCase(text)) {
// keep going, but remember this location, in case we don't find any more matches
lastPreferredMatchIndex = i;
}
else {
// we've encountered a non-matching entry--nothing left to search
return lastPreferredMatchIndex;
}
}
return -1; // we only get here when the list is empty
}
private class CategoryPathDropDownRenderer extends GListCellRenderer<CategoryPath> {
@Override
protected String getItemText(CategoryPath path) {
return path.getPath();
}
@Override
public Component getListCellRendererComponent(JList<? extends CategoryPath> list,
CategoryPath value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
return this;
}
}
}
}

View File

@@ -22,7 +22,7 @@ import javax.swing.JPanel;
import javax.swing.event.*;
import docking.DialogComponentProvider;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.util.HelpLocation;
@@ -36,17 +36,17 @@ import ghidra.util.data.DataTypeParser.AllowedDataTypes;
public class DataTypeSelectionDialog extends DialogComponentProvider {
private DataTypeSelectionEditor editor;
private PluginTool pluginTool;
private ServiceProvider serviceProvider;
private DataType userChoice;
private int maxSize = -1;
private DataTypeManager dtm;
private final AllowedDataTypes allowedTypes;
public DataTypeSelectionDialog(PluginTool pluginTool, DataTypeManager dtm, int maxSize,
DataTypeParser.AllowedDataTypes allowedTypes) {
public DataTypeSelectionDialog(ServiceProvider serviceProvider, DataTypeManager dtm,
int maxSize, DataTypeParser.AllowedDataTypes allowedTypes) {
super("Data Type Chooser Dialog", true, true, true, false);
this.pluginTool = pluginTool;
this.serviceProvider = serviceProvider;
this.dtm = dtm;
this.maxSize = maxSize;
this.allowedTypes = allowedTypes;
@@ -65,7 +65,7 @@ public class DataTypeSelectionDialog extends DialogComponentProvider {
private void buildEditor() {
removeWorkPanel();
editor = createEditor(pluginTool, allowedTypes);
editor = createEditor(serviceProvider, allowedTypes);
editor.setConsumeEnterKeyPress(false); // we want to handle Enter key presses
editor.addCellEditorListener(new CellEditorListener() {
@Override
@@ -108,9 +108,9 @@ public class DataTypeSelectionDialog extends DialogComponentProvider {
rootPanel.validate();
}
protected DataTypeSelectionEditor createEditor(PluginTool tool,
protected DataTypeSelectionEditor createEditor(ServiceProvider sp,
AllowedDataTypes allowedDataTypes) {
return new DataTypeSelectionEditor(dtm, tool, allowedDataTypes);
return new DataTypeSelectionEditor(dtm, sp, allowedDataTypes);
}
protected JComponent createEditorPanel(DataTypeSelectionEditor dtEditor) {

View File

@@ -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();

View File

@@ -352,6 +352,11 @@ public class FunctionSignatureParser {
return service.getSortedDataTypeList();
}
@Override
public List<CategoryPath> getSortedCategoryPathList() {
return service.getSortedCategoryPathList();
}
@Override
public DataType getDataType(String filterText) {
return promptForDataType(filterText);

View File

@@ -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);
}

View File

@@ -56,6 +56,7 @@ import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.ProgramManager;
import ghidra.framework.Application;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.data.*;
@@ -123,9 +124,9 @@ public class DataTypeSelectionDialogTest extends AbstractGhidraHeadedIntegration
dialog = new DataTypeSelectionDialog(tool, program.getDataTypeManager(), -1,
AllowedDataTypes.ALL) {
@Override
protected DataTypeSelectionEditor createEditor(PluginTool pluginTool,
protected DataTypeSelectionEditor createEditor(ServiceProvider sp,
AllowedDataTypes allowedDataTypes) {
return new DataTypeSelectionEditor(null, pluginTool, allowedDataTypes) {
return new DataTypeSelectionEditor(null, sp, allowedDataTypes) {
@Override
protected DropDownSelectionTextField<DataType> createDropDownSelectionTextField(

View File

@@ -50,7 +50,7 @@ public class TestDoubleDataTypeManagerService implements DataTypeManagerService
}
@Override
public DataType getDataType(String filterText) {
public List<CategoryPath> getSortedCategoryPathList() {
throw new UnsupportedOperationException();
}
@@ -161,11 +161,21 @@ public class TestDoubleDataTypeManagerService implements DataTypeManagerService
throw new UnsupportedOperationException();
}
@Override
public DataType getDataType(String filterText) {
throw new UnsupportedOperationException();
}
@Override
public DataType getDataType(TreePath selectedPath) {
throw new UnsupportedOperationException();
}
@Override
public CategoryPath getCategoryPath(TreePath selectedPath) {
throw new UnsupportedOperationException();
}
@Override
public Set<String> getPossibleEquateNames(long value) {
throw new UnsupportedOperationException();

View File

@@ -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()) {

View File

@@ -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");
}
}

View File

@@ -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