diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png index c7d030078b..1b542fe2ad 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png and b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png index be32a0fe22..e4bad542cd 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png and b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateStructureCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateStructureCmd.java index 71e210c615..9b5069cc40 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateStructureCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateStructureCmd.java @@ -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 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 findExistingRefs(ReferenceManager refMgr, AddressFactory af, + Address start, Address end) { - ArrayList list = new ArrayList(); + List list = new ArrayList(); 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 refs) { for (Reference ref : refs) { refMgr.addReference(ref); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java index 79a5bdedcb..2abce33b4a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java @@ -1158,8 +1158,6 @@ public abstract class CompositeEditorPanel 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 { + if (e.getValueIsAdjusting()) { + return; + } + + if (useExistingStructButton.isSelected()) { + setOkEnabled(table.getSelectedRowCount() > 0); + } + }); filterPanel = new GhidraTableFilterPanel<>(table, structureTableModel) { // make sure our height doesn't stretch @@ -527,6 +502,18 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { return false; } + JTextField getNameField() { + return nameTextField; + } + + JTable getTable() { + return matchingStructuresTable; + } + + CategoryPathSelectionEditor getCategoryEditor() { + return categoryPathEditor; + } + /** * Shows a dialog that allows the user to create a new structure. *

@@ -577,34 +564,58 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { @Override protected void okCallback() { - if (nameTextField.isEnabled()) { - // just use the name set by the user - String nameText = nameTextField.getText(); - - if (!setCategoryPath()) { - return; - } - - try { - currentStructure.setName(nameText); - } - catch (InvalidNameException ine) { - setStatusText(ine.getMessage()); - return; - } - catch (DuplicateNameException dne) { - setStatusText(dne.getMessage()); - return; - } - } - else { + if (useExistingStructButton.isSelected()) { // get the selected object in the table currentStructure = getSelectedStructure(); + close(); + return; + } + + // just use the name set by the user + String nameText = nameTextField.getText(); + try { + currentStructure.setName(nameText); + } + catch (InvalidNameException ine) { + setStatusText(ine.getMessage()); + return; + } + catch (DuplicateNameException dne) { + setStatusText(dne.getMessage()); + return; + } + + if (!setCategoryPath()) { + return; + } + + if (!validateName()) { + return; } close(); } + private boolean validateName() { + // Use the current name and category path to see if there is already an existing name. This + // allows us to avoid a conflict. + ProgramBasedDataTypeManager dtm = currentProgram.getDataTypeManager(); + CategoryPath path = currentStructure.getCategoryPath(); + Category category = dtm.getCategory(path); + if (category == null) { + return true; + } + + String nameText = currentStructure.getName(); + DataType existingDt = category.getDataType(nameText); + if (existingDt != null) { + setStatusText("Name already exists: " + nameText, MessageType.ERROR); + return false; + } + + return true; + } + private Structure getSelectedStructure() { int row = matchingStructuresTable.getSelectedRow(); if (row < 0) { @@ -616,66 +627,43 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { } private boolean setCategoryPath() { + + try { + doSetCategoryPath(); + } + catch (DuplicateNameException e) { + setStatusText(e.getMessage(), MessageType.ERROR); + return false; + } + return true; + } + + private void doSetCategoryPath() throws DuplicateNameException { CategoryPath path = categoryPathEditor.getCellEditorValue(); // First see if a category from the list was chosen and make sure the user didn't modify it. // If they did, path needs to be parsed separately. - if (path != null && path.getPath().equals(categoryPathEditor.getCellEditorValueAsText())) { - try { - currentStructure.setCategoryPath(path); - } - catch (DuplicateNameException dne) { - setStatusText(dne.getMessage(), MessageType.ERROR); - return false; - } - return true; + String editorValue = categoryPathEditor.getCellEditorValueAsText(); + if (path != null && path.getPath().equals(editorValue)) { + currentStructure.setCategoryPath(path); + return; } - String categoryText = categoryPathEditor.getCellEditorValueAsText(); // Selecting/entering a category is optional; root is default - if (!categoryText.isBlank()) { - try { - CategoryPath parsedPath = parseEnteredCategoryPath(categoryText); - currentStructure.setCategoryPath(parsedPath); - } - catch (DuplicateNameException dne) { - setStatusText(dne.getMessage(), MessageType.ERROR); - return false; - } + if (!editorValue.isBlank()) { + CategoryPath parsedPath = parseEnteredCategoryPath(editorValue); + currentStructure.setCategoryPath(parsedPath); + return; } - else { - try { - currentStructure.setCategoryPath(CategoryPath.ROOT); - } - catch (DuplicateNameException dne) { - setStatusText(dne.getMessage(), MessageType.ERROR); - return false; - } - } - return true; + + currentStructure.setCategoryPath(CategoryPath.ROOT); } private CategoryPath parseEnteredCategoryPath(String categoryText) { // entering a leading slash is optional, path is still generated accordingly if (categoryText.startsWith(CategoryPath.DELIMITER_STRING)) { - return generateCategoryPath(categoryText.substring(1)); + return new CategoryPath(categoryText); } - return generateCategoryPath(categoryText); - } - - private CategoryPath generateCategoryPath(String categoryText) { - if (!categoryText.contains(CategoryPath.DELIMITER_STRING)) { - return new CategoryPath(CategoryPath.ROOT, categoryText); - } - - // Additional slashes need parsed as branch(es) and final leaf - List parts = split(categoryText); - return new CategoryPath(CategoryPath.ROOT, parts); - } - - private List split(String categoryText) { - List parts = new ArrayList( - Arrays.asList(categoryText.split(CategoryPath.DELIMITER_STRING))); - return parts; + return new CategoryPath(CategoryPath.DELIMITER_STRING + categoryText); } // a table model that is used to allow for the easy updating of the table with new List data @@ -707,7 +695,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { case 0: return STRUCTURE_COLUMN_NAME; case 1: - return PATH_COLUMN_NAME; + return CATEGORY_COLUMN_NAME; } return null; } @@ -735,8 +723,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { case 1: { Structure structure = t.getStructure(); CategoryPath path = structure.getCategoryPath(); - String name = structure.getName(); - return path.toString() + '/' + name; + return path.toString(); } } return null; @@ -779,7 +766,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { } // we need this renderer in order to create nice tool tip text values - class StructureCellRenderer extends GTableCellRenderer { + private class StructureCellRenderer extends GTableCellRenderer { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { @@ -799,7 +786,7 @@ public class CreateStructureDialog extends ReusableDialogComponentProvider { renderer.setToolTipText(ToolTipUtils.getToolTipText(structure)); } } - else if (PATH_COLUMN_NAME.equals(columnName)) { + else if (CATEGORY_COLUMN_NAME.equals(columnName)) { if (value != null) { renderer.setToolTipText(value.toString()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java index 8576090598..4d25970811 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java @@ -598,6 +598,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin public CategoryPath getCategoryPath(TreePath selectedPath) { DataTypeChooserDialog dialog = new DataTypeChooserDialog(this); dialog.setCategorySelectionMode(true); + dialog.setShowProgramArchiveOnly(true); if (selectedPath != null) { dialog.setSelectedPath(selectedPath); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java index d9c4ce1c0d..cc17249fce 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java @@ -169,10 +169,37 @@ public class DataTypeIndexer { } } + private class CaseInsensitiveCategoryComparator implements Comparator { + + @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 dataTypes = new ArrayList<>(); - private List categories; + private List categories = new ArrayList<>(); + private Set categorySet = new HashSet<>(); IndexerTask() { super("Data Type Indexer Task", false, true, true); @@ -184,26 +211,32 @@ public class DataTypeIndexer { monitor.initialize(dataTypeManagers.size()); monitor.setMessage("Preparing to index data types..."); - for (DataTypeManager dataTypeManager : dataTypeManagers) { - monitor.setMessage("Searching " + dataTypeManager.getName()); - dataTypeManager.getAllDataTypes(dataTypes); + for (DataTypeManager dtm : dataTypeManagers) { + monitor.setMessage("Searching " + dtm.getName()); + dtm.getAllDataTypes(dataTypes); + + Category root = dtm.getRootCategory(); + populateCategories(root); + monitor.incrementProgress(1); } Collections.sort(dataTypes, new CaseInsensitiveDataTypeComparator()); - populateCategoryList(dataTypes); + + categories.addAll(categorySet); + Collections.sort(categories, new CaseInsensitiveCategoryComparator()); } - private void populateCategoryList(List dataTypes) { + private void populateCategories(Category parent) { - Set set = new HashSet<>(); - for (DataType dt : dataTypes) { - CategoryPath path = dt.getCategoryPath(); - set.add(path); + categorySet.add(parent.getCategoryPath()); + Category[] children = parent.getCategories(); + for (Category category : children) { + CategoryPath path = category.getCategoryPath(); + categorySet.add(path); + + populateCategories(category); } - - categories = new ArrayList<>(set); - Collections.sort(categories); } List getList() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java index 0f2c6ac04a..634ef9bd49 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java @@ -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; } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java index 3d0a4e4537..ede28c2b21 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveRootNode.java @@ -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 generateChildren() { List 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 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 allChildrenList = getChildren(); + int index = Collections.binarySearch(allChildrenList, node); + if (index < 0) { + index = -index - 1; + } + addNode(index, node); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeChooserDialog.java index 4542ea9ef5..873a022744 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeChooserDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeChooserDialog.java @@ -31,12 +31,14 @@ import docking.DialogComponentProvider; import docking.Tool; import docking.widgets.filter.FilterOptions; import docking.widgets.filter.TextFilterStrategy; -import docking.widgets.label.GLabel; +import docking.widgets.label.GDLabel; import docking.widgets.tree.*; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.tree.*; import ghidra.app.util.datatype.DataTypeSelectionDialog; import ghidra.program.model.data.*; +import ghidra.util.HelpLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -46,17 +48,20 @@ import ghidra.util.task.TaskMonitor; * {@link DataTypeSelectionDialog} utility widget. */ public class DataTypeChooserDialog extends DialogComponentProvider { + + private DataTypeManagerPlugin plugin; private DataTypeArchiveGTree tree; private DataType selectedDataType; private CategoryPath selectedCategoryPath; - private GLabel messageLabel; + private GDLabel messageLabel; private boolean isFilterEditable; private boolean categorySelectionMode; public DataTypeChooserDialog(DataTypeManagerPlugin plugin) { super("Data Type Chooser", true, true, true, false); + this.plugin = plugin; tree = new DataTypeArchiveGTree(plugin); @@ -98,6 +103,8 @@ public class DataTypeChooserDialog extends DialogComponentProvider { addOKButton(); addCancelButton(); setOkEnabled(false); + + setHelpLocation(new HelpLocation("DataTypeEditors", "browse")); } /** @@ -106,6 +113,29 @@ public class DataTypeChooserDialog extends DialogComponentProvider { */ public void setCategorySelectionMode(boolean categorySelectionMode) { this.categorySelectionMode = categorySelectionMode; + if (categorySelectionMode) { + setTitle("Category Chooser"); + messageLabel.setText("Choose a category:"); + } + else { + setTitle("Data Type Chooser"); + messageLabel.setText("Choose a data type:"); + } + } + + public void setShowProgramArchiveOnly(boolean programOnly) { + DataTypeManagerHandler handler = plugin.getDataTypeManagerHandler(); + if (programOnly) { + DataTypeManager programDtm = handler.getProgramDataTypeManager(); + if (programDtm != null) { + ArchiveRootNode root = new ArchiveRootNode(handler, true); + tree.setRootNode(root); + return; + } + } + + ArchiveRootNode root = new ArchiveRootNode(handler); + tree.setRootNode(root); } private boolean isValidNodeSelected() { @@ -152,7 +182,7 @@ public class DataTypeChooserDialog extends DialogComponentProvider { private JComponent createWorkPanel() { JPanel panel = new JPanel(new BorderLayout()); - messageLabel = new GLabel("Choose the data type you wish to use."); + messageLabel = new GDLabel("Choose the data type you wish to use."); messageLabel.setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 2)); messageLabel.getAccessibleContext().setAccessibleName("Message"); panel.add(messageLabel, BorderLayout.NORTH); @@ -335,4 +365,5 @@ public class DataTypeChooserDialog extends DialogComponentProvider { } } } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/ParameterDataTypeCellEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/ParameterDataTypeCellEditor.java index e7603d7a6f..96c5581574 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/ParameterDataTypeCellEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/ParameterDataTypeCellEditor.java @@ -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 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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/CategoryPathSelectionEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/CategoryPathSelectionEditor.java index 228014a685..183822a561 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/CategoryPathSelectionEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/CategoryPathSelectionEditor.java @@ -15,11 +15,14 @@ */ package ghidra.app.util.datatype; -import java.awt.Component; +import java.awt.*; import java.awt.event.*; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import javax.swing.*; +import javax.swing.border.Border; import javax.swing.event.*; import javax.swing.tree.TreePath; @@ -30,7 +33,6 @@ import docking.widgets.list.GListCellRenderer; import ghidra.app.services.DataTypeManagerService; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.data.CategoryPath; -import ghidra.util.exception.AssertException; /** * An editor that is used to show the {@link DropDownSelectionTextField} for the entering of @@ -92,6 +94,8 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { private void init() { selectionField = createDropDownSelectionTextField( new CategoryPathDropDownSelectionDataModel(dataTypeManagerService)); + selectionField.setName("CategoryPath"); + selectionField.getAccessibleContext().setAccessibleName("Category"); selectionField.addCellEditorListener(new CellEditorListener() { @Override public void editingCanceled(ChangeEvent e) { @@ -113,16 +117,12 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { selectionField.requestFocus(); } }); - selectionField.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); - browseButton = new BrowseButton(); - browseButton.setToolTipText("Browse Existing Category Paths"); - browseButton.addActionListener(e -> showBrowser()); + JPanel browsePanel = buildBrowsePanel(); editorPanel = new JPanel(); editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS)); editorPanel.add(selectionField); - editorPanel.add(Box.createHorizontalStrut(5)); - editorPanel.add(browseButton); + editorPanel.add(browsePanel); keyListener = new KeyAdapter() { @@ -144,6 +144,60 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { }; } + private JPanel buildBrowsePanel() { + + // We override the various sizes to make sure the button does not get too big or too small, + // which changes depending upon the theme being used. + JPanel browsePanel = new JPanel() { + + @Override + public Dimension getPreferredSize() { + int width = getBestWidth(); + Dimension preferredSize = super.getPreferredSize(); + preferredSize.width = Math.min(width, preferredSize.width); + return preferredSize; + } + + @Override + public Dimension getMinimumSize() { + int width = getBestWidth(); + Dimension preferredSize = super.getPreferredSize(); + preferredSize.width = Math.min(width, preferredSize.width); + return preferredSize; + } + + @Override + public Dimension getMaximumSize() { + int width = getBestWidth(); + Dimension preferredSize = super.getPreferredSize(); + preferredSize.width = Math.min(width, preferredSize.width); + return preferredSize; + } + + private int getBestWidth() { + Font f = getFont(); + FontMetrics fm = getFontMetrics(f); + int width = fm.stringWidth(" . . . "); + return width; + } + }; + + browsePanel.setLayout(new BorderLayout()); + browsePanel.setOpaque(false); + + // Space the button so that it pops out visually. This was chosen by trial-and-error and + // looks reasonable on all themes. + Border empty = BorderFactory.createEmptyBorder(2, 2, 1, 1); + browsePanel.setBorder(empty); + + browseButton = new BrowseButton(); + browseButton.setToolTipText("Browse Existing Category Paths"); + browseButton.addActionListener(e -> showBrowser()); + browsePanel.add(browseButton); + + return browsePanel; + } + /** * Retrieve the value in the cell. * @return categoryPath of the selected value from the drop-down @@ -179,8 +233,8 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { } /** - * Retrieve the dropdown text field that holds the category path collection. - * @return CategoryPath dropdown selection text field object + * Retrieve the drop-down text field that holds the category path collection. + * @return CategoryPath drop-down selection text field object */ public DropDownSelectionTextField getDropDownTextField() { return selectionField; @@ -333,13 +387,6 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { private List data; - private Comparator searchComparator = new CategoryPathComparator(); - - /** - * Creates a new instance. - * - * @param dataTypeService {@link DataTypeManagerService} - */ public CategoryPathDropDownSelectionDataModel(DataTypeManagerService dataTypeService) { data = dataTypeService.getSortedCategoryPathList(); } @@ -349,66 +396,32 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { return new CategoryPathDropDownRenderer(); } - /** - * Description of the CategoryPath is the display text of the path as a string. - - * @param categoryPath CategoryPath - * @return String representation of the Category Path - */ @Override public String getDescription(CategoryPath categoryPath) { - return getDisplayText(categoryPath); + return null; } - /** - * Retrieve the CategoryPath string representation. - * - * @param categoryPath CategoryPath - * @return String representation of the Category Path - */ @Override public String getDisplayText(CategoryPath categoryPath) { return categoryPath.getPath(); } - /** - * Support for the filtering mechanism on the collection of Category Paths in the Data Manager. - * - * @param searchText String entered text - * @return filtered list of Category Paths - */ @Override public List getMatchingData(String searchText) { if (searchText == null || searchText.length() == 0) { return Collections.emptyList(); } - char END_CHAR = '\uffff'; - String searchTextStart = searchText; - String searchTextEnd = searchText + END_CHAR; - - int startIndex = Collections.binarySearch(data, searchTextStart, searchComparator); - int endIndex = Collections.binarySearch(data, searchTextEnd, searchComparator); - - // the binary search returns a negative, incremented position if there is no match in the - // list for the given search - if (startIndex < 0) { - startIndex = -startIndex - 1; + List results = new ArrayList<>(); + for (CategoryPath path : data) { + String pathString = path.getPath(); + if (pathString.contains(searchText)) { + results.add(path); + } } - - if (endIndex < 0) { - endIndex = -endIndex - 1; - } - - return data.subList(startIndex, endIndex); + return results; } - /** - * Identify index of first matching CategoryPath from entered text string. - * @param dataCollection list of Category Paths - * @param text search string - * @return int index of first match - */ @Override public int getIndexOfFirstMatchingEntry(List dataCollection, String text) { int lastPreferredMatchIndex = -1; @@ -434,18 +447,6 @@ public class CategoryPathSelectionEditor extends AbstractCellEditor { return -1; // we only get here when the list is empty } - private class CategoryPathComparator implements Comparator { - @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 { @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/DataTypeSelectionEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/DataTypeSelectionEditor.java index e210d274c1..5dff65e417 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/DataTypeSelectionEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/DataTypeSelectionEditor.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java index 82801196a6..6090e8304b 100644 --- a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java +++ b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java @@ -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); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/DropDownTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/DropDownTextField.java index 9af89a2356..3f9e98234b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/DropDownTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/DropDownTextField.java @@ -974,7 +974,7 @@ public class DropDownTextField extends JTextField implements GComponent { fireUserChoiceMade(selectedItem); } - class PreviewListener implements ListSelectionListener { + private class PreviewListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/button/BrowseButton.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/button/BrowseButton.java index 7b5f424b85..7d58d000dc 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/button/BrowseButton.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/button/BrowseButton.java @@ -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"); } } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java index aa733c0e8c..ff012ae0e0 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java @@ -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