mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-10-01 17:11:44 -09:00
Merge remote-tracking branch
'origin/GP-5327-create-structure-dialog-updates' (Closes #6713)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.cmd.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.*;
|
||||
@@ -73,9 +72,6 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
|
||||
structureDataLength = structure.getLength();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see AbstractCreateStructureCmd#createStructure(Address, Program)
|
||||
*/
|
||||
@Override
|
||||
Structure createStructure(Address address, Program program) {
|
||||
|
||||
@@ -87,9 +83,6 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
|
||||
return structure;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see AbstractCreateStructureCmd#initializeStructureData(Program, Structure)
|
||||
*/
|
||||
@Override
|
||||
DataType initializeStructureData(Program program, Structure localStructure) {
|
||||
|
||||
@@ -101,11 +94,11 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
|
||||
}
|
||||
catch (AddressOverflowException e1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't create structure because length exceeds address " + "space" +
|
||||
"Can't create structure because length exceeds address space" +
|
||||
structureDataLength);
|
||||
}
|
||||
ReferenceManager refMgr = program.getReferenceManager();
|
||||
Reference[] refs = findExistingRefs(refMgr, program.getAddressFactory(),
|
||||
List<Reference> refs = findExistingRefs(refMgr, program.getAddressFactory(),
|
||||
getStructureAddress(), endAddress);
|
||||
listing.clearCodeUnits(getStructureAddress(), endAddress, false);
|
||||
|
||||
@@ -123,20 +116,20 @@ public class CreateStructureCmd extends AbstractCreateStructureCmd {
|
||||
return data.getDataType();
|
||||
}
|
||||
|
||||
private Reference[] findExistingRefs(ReferenceManager refMgr, AddressFactory af, Address start,
|
||||
private List<Reference> findExistingRefs(ReferenceManager refMgr, AddressFactory af,
|
||||
Address start,
|
||||
Address end) {
|
||||
ArrayList<Reference> list = new ArrayList<Reference>();
|
||||
List<Reference> list = new ArrayList<Reference>();
|
||||
AddressIterator it = refMgr.getReferenceSourceIterator(new AddressSet(start, end), true);
|
||||
while (it.hasNext()) {
|
||||
Address addr = it.next();
|
||||
Reference[] refs = refMgr.getReferencesFrom(addr);
|
||||
Collections.addAll(list, refs);
|
||||
}
|
||||
Reference[] refList = new Reference[list.size()];
|
||||
return list.toArray(refList);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void addRefs(Program p, ReferenceManager refMgr, Reference[] refs) {
|
||||
private void addRefs(Program p, ReferenceManager refMgr, List<Reference> refs) {
|
||||
for (Reference ref : refs) {
|
||||
refMgr.addReference(ref);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -674,6 +674,13 @@ public class DataTypeManagerHandler {
|
||||
return builtInDataTypesManager;
|
||||
}
|
||||
|
||||
public DataTypeManager getProgramDataTypeManager() {
|
||||
if (programArchive != null) {
|
||||
return programArchive.getDataTypeManager();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public DataTypeIndexer getDataTypeIndexer() {
|
||||
return dataTypeIndexer;
|
||||
}
|
||||
@@ -1810,4 +1817,5 @@ public class DataTypeManagerHandler {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,8 +34,15 @@ public class ArchiveRootNode extends DataTypeTreeNode {
|
||||
|
||||
private DtFilterState dtFilterState = new DtFilterState();
|
||||
|
||||
ArchiveRootNode(DataTypeManagerHandler archiveManager) {
|
||||
private boolean programDtmOnly;
|
||||
|
||||
public ArchiveRootNode(DataTypeManagerHandler archiveManager) {
|
||||
this(archiveManager, false);
|
||||
}
|
||||
|
||||
public ArchiveRootNode(DataTypeManagerHandler archiveManager, boolean programDtmOnly) {
|
||||
this.archiveManager = archiveManager;
|
||||
this.programDtmOnly = programDtmOnly;
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -93,21 +100,29 @@ public class ArchiveRootNode extends DataTypeTreeNode {
|
||||
}
|
||||
|
||||
// a factory method to isolate non-OO inheritance checks
|
||||
private static final GTreeNode createArchiveNode(Archive archive, DtFilterState dtFilterState) {
|
||||
private final GTreeNode createArchiveNode(Archive archive, DtFilterState filterState) {
|
||||
|
||||
if (programDtmOnly) {
|
||||
if (archive instanceof ProgramArchive) {
|
||||
return new ProgramArchiveNode((ProgramArchive) archive, filterState);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (archive instanceof FileArchive) {
|
||||
return new FileArchiveNode((FileArchive) archive, dtFilterState);
|
||||
return new FileArchiveNode((FileArchive) archive, filterState);
|
||||
}
|
||||
else if (archive instanceof ProjectArchive) {
|
||||
return new ProjectArchiveNode((ProjectArchive) archive, dtFilterState);
|
||||
return new ProjectArchiveNode((ProjectArchive) archive, filterState);
|
||||
}
|
||||
else if (archive instanceof InvalidFileArchive) {
|
||||
return new InvalidArchiveNode((InvalidFileArchive) archive);
|
||||
}
|
||||
else if (archive instanceof ProgramArchive) {
|
||||
return new ProgramArchiveNode((ProgramArchive) archive, dtFilterState);
|
||||
return new ProgramArchiveNode((ProgramArchive) archive, filterState);
|
||||
}
|
||||
else if (archive instanceof BuiltInArchive) {
|
||||
return new BuiltInArchiveNode((BuiltInArchive) archive, dtFilterState);
|
||||
return new BuiltInArchiveNode((BuiltInArchive) archive, filterState);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -178,7 +193,10 @@ public class ArchiveRootNode extends DataTypeTreeNode {
|
||||
public List<GTreeNode> generateChildren() {
|
||||
List<GTreeNode> list = new ArrayList<>();
|
||||
for (Archive element : archiveManager.getAllArchives()) {
|
||||
list.add(createArchiveNode(element, dtFilterState));
|
||||
GTreeNode node = createArchiveNode(element, dtFilterState);
|
||||
if (node != null) {
|
||||
list.add(node);
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
@@ -221,15 +239,21 @@ public class ArchiveRootNode extends DataTypeTreeNode {
|
||||
|
||||
@Override
|
||||
public void archiveOpened(Archive archive) {
|
||||
if (isLoaded()) {
|
||||
GTreeNode node = createArchiveNode(archive, dtFilterState);
|
||||
List<GTreeNode> allChildrenList = getChildren();
|
||||
int index = Collections.binarySearch(allChildrenList, node);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
}
|
||||
addNode(index, node);
|
||||
if (!isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GTreeNode node = createArchiveNode(archive, dtFilterState);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<GTreeNode> allChildrenList = getChildren();
|
||||
int index = Collections.binarySearch(allChildrenList, node);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
}
|
||||
addNode(index, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -974,7 +974,7 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
|
||||
fireUserChoiceMade(selectedItem);
|
||||
}
|
||||
|
||||
class PreviewListener implements ListSelectionListener {
|
||||
private class PreviewListener implements ListSelectionListener {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (e.getValueIsAdjusting()) {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -55,5 +55,7 @@ public class BrowseButton extends JButton {
|
||||
setIcon(ICON);
|
||||
setName(NAME);
|
||||
setToolTipText(TOOLTIP_TEXT);
|
||||
|
||||
getAccessibleContext().setAccessibleName("Browse");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JRadioButton;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -66,7 +69,21 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
GhidraTable table = (GhidraTable) getInstanceField("matchingStructuresTable", dialog);
|
||||
selectRow(table, 2);
|
||||
|
||||
captureDialog(500, 400);
|
||||
shrinkCategoryColumn(table);
|
||||
|
||||
captureDialog(600, 500);
|
||||
}
|
||||
|
||||
private void shrinkCategoryColumn(JTable table) {
|
||||
|
||||
runSwing(() -> {
|
||||
TableColumnModel columnModel = table.getColumnModel();
|
||||
int columnIndex = columnModel.getColumnIndex("Category");
|
||||
TableColumn column = columnModel.getColumn(columnIndex);
|
||||
int size = 150;
|
||||
column.setPreferredWidth(size);
|
||||
column.setMaxWidth(size);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user