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