mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-06-01 03:26:56 +08:00
GP-5327 - Create Structure Dialog - New text category box & existing
categories chooser
This commit is contained in:
committed by
dragonmacher
parent
795d92cb1a
commit
8234bfb14a
+10
@@ -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();
|
||||
|
||||
+6
-19
@@ -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());
|
||||
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());
|
||||
Structure userChoice =
|
||||
createStructureDialog.showCreateStructureDialog(program, tempStructure);
|
||||
dialog.showCreateStructureDialog(program, tempStructure);
|
||||
|
||||
// exit if the user cancels the operation
|
||||
if (userChoice != null) {
|
||||
|
||||
+294
-116
File diff suppressed because it is too large
Load Diff
+17
@@ -594,6 +594,18 @@ public class DataTypeManagerPlugin extends ProgramPlugin
|
||||
return dialog.getSelectedDataType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CategoryPath getCategoryPath(TreePath selectedPath) {
|
||||
DataTypeChooserDialog dialog = new DataTypeChooserDialog(this);
|
||||
dialog.setCategorySelectionMode(true);
|
||||
|
||||
if (selectedPath != null) {
|
||||
dialog.setSelectedPath(selectedPath);
|
||||
}
|
||||
tool.showDialog(dialog);
|
||||
return dialog.getSelectedCategoryPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataTypeManager[] getDataTypeManagers() {
|
||||
return dataTypeManagerHandler.getDataTypeManagers();
|
||||
@@ -664,6 +676,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()) {
|
||||
|
||||
+45
-10
@@ -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();
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
@@ -155,7 +171,8 @@ public class DataTypeIndexer {
|
||||
|
||||
private class IndexerTask extends Task {
|
||||
|
||||
private List<DataType> list = new ArrayList<>();
|
||||
private List<DataType> dataTypes = new ArrayList<>();
|
||||
private List<CategoryPath> categories;
|
||||
|
||||
IndexerTask() {
|
||||
super("Data Type Indexer Task", false, true, true);
|
||||
@@ -169,15 +186,32 @@ public class DataTypeIndexer {
|
||||
|
||||
for (DataTypeManager dataTypeManager : dataTypeManagers) {
|
||||
monitor.setMessage("Searching " + dataTypeManager.getName());
|
||||
dataTypeManager.getAllDataTypes(list);
|
||||
dataTypeManager.getAllDataTypes(dataTypes);
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
|
||||
Collections.sort(list, new CaseInsensitiveDataTypeComparator());
|
||||
Collections.sort(dataTypes, new CaseInsensitiveDataTypeComparator());
|
||||
populateCategoryList(dataTypes);
|
||||
}
|
||||
|
||||
private void populateCategoryList(List<DataType> dataTypes) {
|
||||
|
||||
Set<CategoryPath> set = new HashSet<>();
|
||||
for (DataType dt : dataTypes) {
|
||||
CategoryPath path = dt.getCategoryPath();
|
||||
set.add(path);
|
||||
}
|
||||
|
||||
categories = new ArrayList<>(set);
|
||||
Collections.sort(categories);
|
||||
}
|
||||
|
||||
List<DataType> getList() {
|
||||
return list;
|
||||
return dataTypes;
|
||||
}
|
||||
|
||||
List<CategoryPath> getCategoryPathList() {
|
||||
return categories;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,4 +300,5 @@ public class DataTypeIndexer {
|
||||
markStale();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+84
-12
@@ -34,10 +34,9 @@ import docking.widgets.filter.TextFilterStrategy;
|
||||
import docking.widgets.label.GLabel;
|
||||
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.tree.*;
|
||||
import ghidra.app.util.datatype.DataTypeSelectionDialog;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@@ -49,8 +48,12 @@ import ghidra.util.task.TaskMonitor;
|
||||
public class DataTypeChooserDialog extends DialogComponentProvider {
|
||||
private DataTypeArchiveGTree tree;
|
||||
private DataType selectedDataType;
|
||||
private CategoryPath selectedCategoryPath;
|
||||
|
||||
private GLabel messageLabel;
|
||||
boolean isFilterEditable;
|
||||
private boolean isFilterEditable;
|
||||
|
||||
private boolean categorySelectionMode;
|
||||
|
||||
public DataTypeChooserDialog(DataTypeManagerPlugin plugin) {
|
||||
super("Data Type Chooser", true, true, true, false);
|
||||
@@ -60,7 +63,7 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
|
||||
tree.setEditable(false);
|
||||
tree.updateFilterForChoosingDataType();
|
||||
|
||||
tree.addGTreeSelectionListener(e -> setOkEnabled(getSelectedNode() != null));
|
||||
tree.addGTreeSelectionListener(e -> setOkEnabled(isValidNodeSelected()));
|
||||
|
||||
tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -69,7 +72,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;
|
||||
}
|
||||
@@ -86,18 +100,48 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
|
||||
setOkEnabled(false);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -119,12 +163,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 +275,10 @@ public class DataTypeChooserDialog extends DialogComponentProvider {
|
||||
tree.setFilterProvider(provider);
|
||||
}
|
||||
|
||||
public CategoryPath getSelectedCategoryPath() {
|
||||
return selectedCategoryPath;
|
||||
}
|
||||
|
||||
public DataType getSelectedDataType() {
|
||||
return selectedDataType;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
/* ###
|
||||
* 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.Component;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
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;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* 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.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();
|
||||
}
|
||||
});
|
||||
selectionField.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
|
||||
browseButton = new BrowseButton();
|
||||
browseButton.setToolTipText("Browse Existing Category Paths");
|
||||
browseButton.addActionListener(e -> showBrowser());
|
||||
|
||||
editorPanel = new JPanel();
|
||||
editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS));
|
||||
editorPanel.add(selectionField);
|
||||
editorPanel.add(Box.createHorizontalStrut(5));
|
||||
editorPanel.add(browseButton);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 dropdown text field that holds the category path collection.
|
||||
* @return CategoryPath dropdown 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;
|
||||
|
||||
private Comparator<Object> searchComparator = new CategoryPathComparator();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param dataTypeService {@link DataTypeManagerService}
|
||||
*/
|
||||
public CategoryPathDropDownSelectionDataModel(DataTypeManagerService dataTypeService) {
|
||||
data = dataTypeService.getSortedCategoryPathList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListCellRenderer<CategoryPath> getListRenderer() {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the CategoryPath string representation.
|
||||
*
|
||||
* @param categoryPath CategoryPath
|
||||
* @return String representation of the Category Path
|
||||
*/
|
||||
@Override
|
||||
public String getDisplayText(CategoryPath categoryPath) {
|
||||
return categoryPath.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for the filtering mechanism on the collection of Category Paths in the Data Manager.
|
||||
*
|
||||
* @param searchText String entered text
|
||||
* @return filtered list of Category Paths
|
||||
*/
|
||||
@Override
|
||||
public List<CategoryPath> getMatchingData(String searchText) {
|
||||
if (searchText == null || searchText.length() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
char END_CHAR = '\uffff';
|
||||
String searchTextStart = searchText;
|
||||
String searchTextEnd = searchText + END_CHAR;
|
||||
|
||||
int startIndex = Collections.binarySearch(data, searchTextStart, searchComparator);
|
||||
int endIndex = Collections.binarySearch(data, searchTextEnd, searchComparator);
|
||||
|
||||
// the binary search returns a negative, incremented position if there is no match in the
|
||||
// list for the given search
|
||||
if (startIndex < 0) {
|
||||
startIndex = -startIndex - 1;
|
||||
}
|
||||
|
||||
if (endIndex < 0) {
|
||||
endIndex = -endIndex - 1;
|
||||
}
|
||||
|
||||
return data.subList(startIndex, endIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify index of first matching CategoryPath from entered text string.
|
||||
* @param dataCollection list of Category Paths
|
||||
* @param text search string
|
||||
* @return int index of first match
|
||||
*/
|
||||
@Override
|
||||
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 CategoryPathComparator implements Comparator<Object> {
|
||||
@Override
|
||||
public int compare(Object o1, Object o2) {
|
||||
if (o1 instanceof CategoryPath && o2 instanceof String) {
|
||||
CategoryPath path = (CategoryPath) o1;
|
||||
return path.getName().compareToIgnoreCase(((String) o2));
|
||||
}
|
||||
throw new AssertException(
|
||||
"CategoryPathCompartor used to compare files against a String key!");
|
||||
}
|
||||
}
|
||||
|
||||
private class CategoryPathDropDownRenderer extends GListCellRenderer<CategoryPath> {
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
+3
-2
@@ -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(
|
||||
|
||||
+11
-1
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user