Merge remote-tracking branch 'origin/GP-4179-dragonmacher-data-window-filter-fixes--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-05-29 12:55:16 -04:00
commit 046a0e4e12
8 changed files with 519 additions and 549 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -44,7 +44,7 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
private DataWindowPlugin plugin;
private AddressMapImpl addressMap;
private Listing listing;
private AddressSet addresses;
private AddressSet restrictedAddresses;
DataTableModel(DataWindowPlugin plugin) {
super("Data", plugin.getTool(), null, null);
@ -66,7 +66,7 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
void reload(Program newProgram) {
this.setProgram(newProgram);
addresses = plugin.getLimitedAddresses();
restrictedAddresses = plugin.getLimitedAddresses();
if (newProgram != null) {
addressMap = new AddressMapImpl();
listing = newProgram.getListing();
@ -88,12 +88,14 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
@Override
protected void doLoad(Accumulator<DataRowObject> accumulator, TaskMonitor monitor)
throws CancelledException {
LongIterator it = LongIterator.EMPTY;
if (listing != null) {
it = new DataKeyIterator();
if (listing == null) {
return;
}
monitor.initialize(getKeyCount());
int progress = 0;
LongIterator it = new DataKeyIterator();
while (it.hasNext()) {
monitor.setProgress(progress++);
monitor.checkCancelled();
@ -104,51 +106,18 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
}
}
public boolean filterAccepts(long key) {
private boolean filterAccepts(long key) {
if (listing == null || addressMap == null) {
return false;
}
Data curData = listing.getDataAt(addressMap.decodeAddress(key));
String displayName = curData.getDataType().getDisplayName();
if (addresses != null) {
return plugin.typeEnabled(displayName) && addresses.contains(curData.getMinAddress());
}
return plugin.typeEnabled(displayName);
}
private class DataKeyIterator implements LongIterator {
private DataIterator itr;
DataKeyIterator() {
itr = listing.getDefinedData(getProgram().getMemory(), true);
}
@Override
public boolean hasNext() {
if (itr == null || getProgram() == null)
return false;
return itr.hasNext();
}
@Override
public long next() {
Data data = itr.next();
if (addressMap != null) {
return addressMap.getKey(data.getMinAddress());
}
return 0;
}
@Override
public boolean hasPrevious() {
return false;
}
@Override
public long previous() {
return -1;
Data data = listing.getDataAt(addressMap.decodeAddress(key));
String displayName = data.getDataType().getDisplayName();
if (restrictedAddresses != null) {
Address minAddress = data.getMinAddress();
return plugin.isTypeEnabled(displayName) && restrictedAddresses.contains(minAddress);
}
return plugin.isTypeEnabled(displayName);
}
void dataAdded(Address addr) {
@ -191,6 +160,41 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
// Inner Classes
//==================================================================================================
private class DataKeyIterator implements LongIterator {
private DataIterator it;
DataKeyIterator() {
it = listing.getDefinedData(getProgram().getMemory(), true);
}
@Override
public boolean hasNext() {
if (it == null || getProgram() == null) {
return false;
}
return it.hasNext();
}
@Override
public long next() {
Data data = it.next();
if (addressMap != null) {
return addressMap.getKey(data.getMinAddress());
}
return 0;
}
@Override
public boolean hasPrevious() {
return false;
}
@Override
public long previous() {
return -1;
}
}
private class DataValueTableColumn
extends AbstractProgramBasedDynamicTableColumn<DataRowObject, String> {
@ -200,7 +204,7 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
}
@Override
public String getValue(DataRowObject rowObject, Settings settings, Program program,
public String getValue(DataRowObject rowObject, Settings settings, Program p,
ServiceProvider provider) throws IllegalArgumentException {
Data data = getDataForRowObject(rowObject);
if (data == null) {
@ -222,7 +226,7 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
}
@Override
public String getValue(DataRowObject rowObject, Settings settings, Program program,
public String getValue(DataRowObject rowObject, Settings settings, Program p,
ServiceProvider provider) throws IllegalArgumentException {
Data data = getDataForRowObject(rowObject);
if (data == null) {
@ -243,7 +247,7 @@ class DataTableModel extends AddressBasedTableModel<DataRowObject> {
}
@Override
public Integer getValue(DataRowObject rowObject, Settings settings, Program program,
public Integer getValue(DataRowObject rowObject, Settings settings, Program p,
ServiceProvider provider) throws IllegalArgumentException {
Data data = getDataForRowObject(rowObject);
if (data == null) {

View File

@ -0,0 +1,307 @@
/* ###
* 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.plugin.core.datawindow;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider;
import docking.DockingUtils;
import docking.widgets.button.GRadioButton;
import docking.widgets.checkbox.GHtmlCheckBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.filter.FilterListener;
import docking.widgets.filter.FilterTextField;
import docking.widgets.label.GLabel;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.datawindow.DataWindowPlugin.Coverage;
import ghidra.util.HelpLocation;
import ghidra.util.task.SwingUpdateManager;
class DataWindowFilterDialog extends DialogComponentProvider {
private JPanel mainPanel;
private List<JCheckBox> checkboxes = new ArrayList<>();
private JPanel checkboxPanel;
private JRadioButton enabledButton;
private JRadioButton disabledButton;
private GhidraComboBox<Coverage> coverageCombo;
private JButton selectAllButton;
private JButton selectNoneButton;
private FilterTextField filterField;
private List<String> filteredList = new ArrayList<>();
private SortedMap<String, Boolean> typeEnabledMap;
private boolean isFilterEnabled = true;
private SwingUpdateManager updateManager =
new SwingUpdateManager(250, 1000, () -> filterCheckboxes());
private KeyListener listener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getModifiersEx() == 0) {
e.consume();
okCallback();
}
}
};
private ItemListener itemListener = e -> {
JCheckBox checkBox = (JCheckBox) e.getItem();
typeEnabledMap.put(checkBox.getName(), checkBox.isSelected());
};
private FilterListener filterListener = new FilterActionFilterListener();
private DataWindowPlugin plugin;
DataWindowFilterDialog(DataWindowPlugin plugin) {
super("Set Data Type Filter");
this.plugin = plugin;
typeEnabledMap = new TreeMap<>(plugin.getTypeMap());
addWorkPanel(create());
addOKButton();
addCancelButton();
setSelectionEnabled(plugin.getSelection() != null);
setHelpLocation(new HelpLocation(plugin.getName(), "Filter_Data_Types"));
setPreferredSize(360, 730);
}
private JComponent create() {
mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
JPanel enablePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
ButtonGroup group = new ButtonGroup();
enabledButton = new GRadioButton("Enabled", true);
enabledButton.addKeyListener(listener);
enablePanel.add(enabledButton);
group.add(enabledButton);
disabledButton = new GRadioButton("Disabled", false);
disabledButton.addKeyListener(listener);
enablePanel.add(disabledButton);
group.add(disabledButton);
enablePanel.setBorder(BorderFactory.createTitledBorder("Filter State"));
mainPanel.add(enablePanel);
enabledButton.addChangeListener(e -> {
boolean enabled = enabledButton.isSelected();
isFilterEnabled = enabled;
for (JCheckBox cb : checkboxes) {
cb.setEnabled(enabled);
}
selectAllButton.setEnabled(enabled);
selectNoneButton.setEnabled(enabled);
coverageCombo.setEnabled(enabled);
filterField.setEnabled(enabled);
});
JPanel limitPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
coverageCombo = new GhidraComboBox<>();
coverageCombo.addKeyListener(listener);
coverageCombo.setModel(new DefaultComboBoxModel<>(Coverage.values()));
limitPanel.add(coverageCombo);
limitPanel.setBorder(BorderFactory.createTitledBorder("Limit Data To"));
mainPanel.add(limitPanel);
JPanel typesPanel = new JPanel(new BorderLayout());
JPanel typeButtonPanel = new JPanel(new GridLayout(1, 2, 5, 0));
selectAllButton = new JButton("Check All");
selectAllButton.setMnemonic('A');
selectAllButton.addActionListener(evt -> {
for (JCheckBox element : checkboxes) {
element.setSelected(true);
}
});
typeButtonPanel.add(selectAllButton);
selectNoneButton = new JButton("Check None");
selectNoneButton.setMnemonic('N');
selectNoneButton.addActionListener(evt -> {
for (JCheckBox element : checkboxes) {
element.setSelected(false);
}
});
typeButtonPanel.add(selectNoneButton);
checkboxPanel = new JPanel();
checkboxPanel.setBackground(Colors.BACKGROUND);
checkboxPanel.setLayout(new BoxLayout(checkboxPanel, BoxLayout.Y_AXIS));
buildCheckBoxList();
JScrollPane scroller = new JScrollPane(checkboxPanel);
typesPanel.add(scroller, BorderLayout.CENTER);
typesPanel.setBorder(BorderFactory.createTitledBorder("Enabled Data Types"));
typesPanel.add(typeButtonPanel, BorderLayout.SOUTH);
mainPanel.add(typesPanel);
filterField = new FilterTextField(checkboxPanel);
filterField.addFilterListener(filterListener);
JPanel filterPanel = new JPanel();
filterPanel.setLayout(new BoxLayout(filterPanel, BoxLayout.LINE_AXIS));
filterPanel.add(new GLabel("Filter:"));
filterPanel.add(Box.createHorizontalStrut(10));
filterPanel.add(filterField);
filterPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
mainPanel.add(filterPanel);
return mainPanel;
}
private void buildCheckBoxList() {
checkboxPanel.removeAll();
checkboxes.clear();
if (!filteredList.isEmpty()) {
String filteredText = getFilterText();
for (String type : filteredList) {
Boolean enabled = typeEnabledMap.get(type);
StringBuilder html = new StringBuilder(type);
int firstIndex = StringUtils.indexOfIgnoreCase(type, filteredText, 0);
int lastIndex = firstIndex + filteredText.length();
html.insert(lastIndex, "</b>"); // do before first index (for no math on index)
html.insert(firstIndex, "<b>");
html.insert(0, "<html>");
createCheckBox(html.toString(), type, enabled);
}
}
else {
if (StringUtils.isBlank(getFilterText())) { // no filter text to highlight
for (String type : typeEnabledMap.keySet()) {
Boolean enabled = typeEnabledMap.get(type);
createCheckBox(type, type, enabled);
}
}
}
repaint();
}
private String getFilterText() {
if (filterField != null) {
return filterField.getText().trim();
}
return null;
}
private void repaint() {
checkboxPanel.invalidate();
mainPanel.validate();
mainPanel.repaint();
}
private void createCheckBox(String html, String typeName, Boolean enabled) {
JCheckBox newCb = new GHtmlCheckBox(html, enabled.booleanValue());
newCb.setName(typeName);
newCb.addKeyListener(listener);
newCb.addItemListener(itemListener);
DockingUtils.setTransparent(newCb);
checkboxes.add(newCb);
checkboxPanel.add(newCb);
}
private void filterCheckboxes() {
List<String> checkboxNameList = new ArrayList<>();
String filterText = getFilterText();
if (!StringUtils.isBlank(filterText)) {
Set<Entry<String, Boolean>> entrySet = typeEnabledMap.entrySet();
for (Entry<String, Boolean> entry : entrySet) {
String checkboxName = entry.getKey();
if (StringUtils.containsIgnoreCase(checkboxName, filterText)) {
checkboxNameList.add(checkboxName);
}
}
}
filteredList = checkboxNameList;
buildCheckBoxList();
}
boolean isFilterEnabled() {
return isFilterEnabled;
}
@Override
public void okCallback() {
if (!isFilterEnabled) {
plugin.setFilterEnabled(false);
}
else {
Coverage coverage = (Coverage) coverageCombo.getSelectedItem();
plugin.setFilter(typeEnabledMap, coverage);
}
typeEnabledMap.clear();
close();
}
@Override
public void cancelCallback() {
typeEnabledMap.clear();
close();
}
void setSelectionEnabled(boolean enableSelection) {
if (enableSelection) {
coverageCombo.setModel(new DefaultComboBoxModel<>(Coverage.values()));
}
else {
coverageCombo.setModel(
new DefaultComboBoxModel<>(new Coverage[] { Coverage.PROGRAM, Coverage.VIEW }));
}
}
void setTypeEnabled(String type, boolean b) {
for (JCheckBox cb : checkboxes) {
String name = cb.getName();
if (name.equals(type)) {
cb.setSelected(b);
}
}
}
void setFilterEnabled(boolean b) {
if (b) {
enabledButton.setSelected(true);
}
else {
disabledButton.setSelected(true);
}
}
private class FilterActionFilterListener implements FilterListener {
@Override
public void filterChanged(String text) {
filterCheckboxes();
updateManager.updateLater();
}
}
}

View File

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.datawindow;
import static ghidra.framework.model.DomainObjectEvent.*;
import static ghidra.program.util.ProgramEvent.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
import docking.action.DockingAction;
import ghidra.app.CorePluginPackage;
@ -31,7 +30,6 @@ import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramTreeService;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.model.DomainObjectListenerBuilder;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.AddressRangeIterator;
@ -70,11 +68,16 @@ public class DataWindowPlugin extends ProgramPlugin {
private boolean resetTypesNeeded;
private DomainObjectListener domainObjectListener = createDomainObjectListener();
private SortedMap<String, Boolean> typeEnablementByDisplayName =
new TreeMap<>(new DataTypeNameComparator());
private boolean isFilterEnabled = false;
private Coverage coverage = Coverage.PROGRAM;
public DataWindowPlugin(PluginTool tool) {
super(tool);
resetUpdateMgr = new SwingUpdateManager(100, 60000, () -> doReset());
reloadUpdateMgr = new SwingUpdateManager(100, 60000, () -> doReload());
}
@ -97,7 +100,7 @@ public class DataWindowPlugin extends ProgramPlugin {
super.dispose();
}
DomainObjectListener createDomainObjectListener() {
private DomainObjectListener createDomainObjectListener() {
// @formatter:off
return new DomainObjectListenerBuilder(this)
.any(RESTORED)
@ -119,18 +122,37 @@ public class DataWindowPlugin extends ProgramPlugin {
}
}
void reload() {
void dataWindowShown() {
if (resetTypesNeeded) {
resetTypes();
}
}
void setFilterEnabled(boolean enabled) {
isFilterEnabled = enabled;
reload();
}
void setFilter(SortedMap<String, Boolean> typeEnabledMap, Coverage coverage) {
this.isFilterEnabled = true;
this.typeEnablementByDisplayName = new TreeMap<>(typeEnabledMap);
this.coverage = coverage;
reload();
}
private void reload() {
reloadUpdateMgr.update();
}
void doReload() {
private void doReload() {
provider.reload();
}
@Override
public void processEvent(PluginEvent event) {
if (event instanceof ViewChangedPluginEvent) {
if (filterAction.getViewMode() && provider.isVisible()) {
if (isFilterEnabled && provider.isVisible()) {
reload();
}
}
@ -143,7 +165,7 @@ public class DataWindowPlugin extends ProgramPlugin {
protected void programActivated(Program program) {
program.addListener(domainObjectListener);
provider.programOpened(program);
filterAction.programOpened(program);
filterAction.setEnabled(true);
resetTypes();
}
@ -151,7 +173,7 @@ public class DataWindowPlugin extends ProgramPlugin {
protected void programDeactivated(Program program) {
program.removeListener(domainObjectListener);
provider.programClosed();
filterAction.programClosed();
filterAction.setEnabled(false);
}
Program getProgram() {
@ -162,14 +184,11 @@ public class DataWindowPlugin extends ProgramPlugin {
return currentSelection;
}
// Junit access
// test access
DataWindowProvider getProvider() {
return provider;
}
/**
* Create the action objects for this plugin.
*/
private void createActions() {
selectAction = new MakeProgramSelectionAction(this, provider.getTable());
@ -201,55 +220,85 @@ public class DataWindowPlugin extends ProgramPlugin {
private void doReset() {
resetTypesNeeded = false;
ArrayList<String> selectedList = filterAction.getSelectedTypes();
filterAction.clearTypes();
if (currentProgram != null) {
DataTypeManager typeManager = currentProgram.getDataTypeManager();
Iterator<DataType> itr = typeManager.getAllDataTypes();
while (itr.hasNext()) {
DataType type = itr.next();
filterAction.addType(type.getDisplayName());
}
filterAction.selectTypes(selectedList);
provider.reload();
}
}
public boolean typeEnabled(String type) {
return filterAction.typeEnabled(type);
}
public AddressSet getLimitedAddresses() {
if (filterAction.getSelectionMode()) {
AddressSet ret = new AddressSet();
AddressRangeIterator itr = currentSelection.getAddressRanges();
while (itr.hasNext()) {
ret.add(itr.next());
}
return ret;
typeEnablementByDisplayName.clear();
if (currentProgram == null) {
return;
}
if (filterAction.getViewMode()) {
DataTypeManager dtm = currentProgram.getDataTypeManager();
Iterator<DataType> it = dtm.getAllDataTypes();
while (it.hasNext()) {
DataType type = it.next();
typeEnablementByDisplayName.put(type.getDisplayName(), true);
}
provider.reload();
}
boolean isTypeEnabled(String type) {
if (!isFilterEnabled) {
return true;
}
Boolean enabled = typeEnablementByDisplayName.get(type);
return enabled != null && enabled;
}
SortedMap<String, Boolean> getTypeMap() {
return typeEnablementByDisplayName;
}
AddressSet getLimitedAddresses() {
if (coverage == Coverage.SELECTION) {
AddressSet addrs = new AddressSet();
AddressRangeIterator it = currentSelection.getAddressRanges();
while (it.hasNext()) {
addrs.add(it.next());
}
return addrs;
}
if (coverage == Coverage.VIEW) {
ProgramTreeService service = tool.getService(ProgramTreeService.class);
if (service != null) {
return service.getView();
}
}
return null;
return null; // PROGRAM
}
@Override
public void readConfigState(SaveState saveState) {
filterAction.setSelected(true);
private static class DataTypeNameComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
if (o1 != null) {
if (!o1.equalsIgnoreCase(o2)) {
return o1.compareToIgnoreCase(o2);
}
return o1.compareTo(o2);
}
return -1;
}
}
public void dataWindowShown() {
if (resetTypesNeeded) {
resetTypes();
public enum Coverage {
//@formatter:off
PROGRAM("Entire Program"),
SELECTION("Current Selection"),
VIEW("Current View");
//@formatter:on
private String displayName;
Coverage(String displayName) {
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
}
}

View File

@ -20,7 +20,7 @@ import java.awt.Dimension;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import docking.ActionContext;
import generic.theme.GIcon;
@ -30,9 +30,6 @@ import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
/**
* Provider for the equates table.
*/
class DataWindowProvider extends ComponentProviderAdapter {
public static final Icon ICON = new GIcon("icon.plugin.datawindow.provider");
@ -76,9 +73,6 @@ class DataWindowProvider extends ComponentProviderAdapter {
return mainPanel;
}
/*
* @see ghidra.framework.docking.HelpTopic#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation(plugin.getName(), plugin.getName());
@ -131,8 +125,6 @@ class DataWindowProvider extends ComponentProviderAdapter {
dataTable.installNavigation(tool);
JTableHeader dataHeader = dataTable.getTableHeader();
dataHeader.setUpdateTableInRealTime(true);
setDataTableRenderer();
filterPanel = new GhidraTableFilterPanel<>(dataTable, dataModel);
@ -153,11 +145,10 @@ class DataWindowProvider extends ComponentProviderAdapter {
}
private void setDataTableRenderer() {
dataTable.getColumnModel()
.getColumn(DataTableModel.LOCATION_COL)
TableColumnModel columnModel = dataTable.getColumnModel();
columnModel.getColumn(DataTableModel.LOCATION_COL)
.setPreferredWidth(DataTableModel.ADDRESS_COL_WIDTH);
dataTable.getColumnModel()
.getColumn(DataTableModel.SIZE_COL)
columnModel.getColumn(DataTableModel.SIZE_COL)
.setPreferredWidth(DataTableModel.SIZE_COL_WIDTH);
}
@ -173,7 +164,7 @@ class DataWindowProvider extends ComponentProviderAdapter {
}
}
public GhidraTable getTable() {
GhidraTable getTable() {
return dataTable;
}
}

View File

@ -15,429 +15,30 @@
*/
package ghidra.app.plugin.core.datawindow;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.ActionContext;
import docking.action.ToggleDockingAction;
import docking.action.ToolBarData;
import docking.widgets.button.GRadioButton;
import docking.widgets.checkbox.GHtmlCheckBox;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.filter.FilterListener;
import docking.widgets.filter.FilterTextField;
import docking.widgets.label.GLabel;
import generic.theme.GThemeDefaults.Colors;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.task.SwingUpdateManager;
import resources.Icons;
class FilterAction extends ToggleDockingAction {
private static final String ENTIRE_PROGRAM = "Entire Program";
private static final String CURRENT_VIEW = "Current View";
private static final String SELECTION = "Current Selection";
private DataWindowPlugin plugin;
private boolean filterEnabled = false;
private boolean viewMode = false;
private boolean selectionMode = false;
private static class SortMapComparatorASC implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
if (o1 != null) {
if (!o1.equalsIgnoreCase(o2)) {
return o1.compareToIgnoreCase(o2);
}
return o1.compareTo(o2);
}
return -1;
}
}
private SortMapComparatorASC SortMapComparatorASCObj = new SortMapComparatorASC();
private SortedMap<String, Boolean> typeEnabledMap = new TreeMap<>(SortMapComparatorASCObj);
FilterAction(DataWindowPlugin plugin) {
super("Filter Data Types", plugin.getName());
this.plugin = plugin;
setDescription("Filters table so only specified types are displayed");
setEnabled(true);
setToolBarData(new ToolBarData(Icons.CONFIGURE_FILTER_ICON));
setSelected(false);
setEnabled(false); // action is disabled until a program is open
setSelected(false); // not selected; filter is off until the user turns on
}
@Override
public void actionPerformed(ActionContext context) {
FilterDialog dialog = new FilterDialog();
dialog.setSelectionEnabled(plugin.getSelection() != null);
dialog.updateButtonEnabledState();
DataWindowFilterDialog dialog = new DataWindowFilterDialog(plugin);
plugin.getTool().showDialog(dialog);
}
synchronized void clearTypes() {
typeEnabledMap.clear();
}
synchronized void addType(String type) {
Boolean bool = !filterEnabled;
typeEnabledMap.put(type, bool);
}
synchronized boolean typeEnabled(String type) {
if (!filterEnabled) {
return true;
}
Boolean bool = typeEnabledMap.get(type);
return (bool != null && bool.booleanValue());
}
/**
* Return array list of strings that are the names of the
* data types that are selected.
*/
synchronized ArrayList<String> getSelectedTypes() {
ArrayList<String> list = new ArrayList<>();
Iterator<String> iter = typeEnabledMap.keySet().iterator();
while (iter.hasNext()) {
String type = iter.next();
Boolean lEnabled = typeEnabledMap.get(type);
if (lEnabled != null && typeEnabledMap.get(type).booleanValue()) {
list.add(type);
}
}
return list;
}
synchronized void selectTypes(ArrayList<String> list) {
for (String element : list) {
typeEnabledMap.put(element, Boolean.TRUE);
}
}
boolean getViewMode() {
return viewMode;
}
boolean getSelectionMode() {
return selectionMode;
}
void setTypeEnabled(String type, boolean enabled) {
typeEnabledMap.put(type, enabled);
}
void setFilterEnabled(boolean filterEnabled) {
this.filterEnabled = filterEnabled;
}
void programClosed() {
typeEnabledMap = new TreeMap<>();
filterEnabled = false;
viewMode = false;
selectionMode = false;
setEnabled(false);
clearTypes();
}
void programOpened(Program program) {
setEnabled(true);
}
private class FilterDialog extends DialogComponentProvider {
private JPanel mainPanel;
private List<JCheckBox> checkboxes = new ArrayList<>();
private JPanel checkboxPanel;
private JRadioButton enableButton;
private JRadioButton disableButton;
private GhidraComboBox<String> limitComboBox;
private JButton selectAllButton;
private JButton selectNoneButton;
private FilterTextField filterField;
private List<String> filteredList = new ArrayList<>();
private SwingUpdateManager updateManager =
new SwingUpdateManager(250, 1000, () -> updateCheckBoxListFilter());
private KeyListener listener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getModifiers() == 0) {
e.consume();
okCallback();
}
}
};
private ItemListener itemListener = e -> {
JCheckBox typeCheckBox = (JCheckBox) e.getItem();
setTypeEnabled(typeCheckBox.getName(), typeCheckBox.isSelected());
};
private FilterListener filterListener = new FilterActionFilterListener();
FilterDialog() {
super("Set Data Type Filter");
addWorkPanel(create());
addOKButton();
// addCancelButton();
setHelpLocation(new HelpLocation(plugin.getName(), "Filter_Data_Types"));
setPreferredSize(360, 730);
}
void selectTypes(ArrayList<String> list) {
for (String type : list) {
selectCheckBox(type);
}
}
private void selectCheckBox(String typeName) {
for (JCheckBox cb : checkboxes) {
if (cb.getText().equals(typeName)) {
cb.setSelected(true);
return;
}
}
}
private JComponent create() {
mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
JPanel enablePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
ButtonGroup group = new ButtonGroup();
enableButton = new GRadioButton("Enabled", true);
enableButton.addKeyListener(listener);
enablePanel.add(enableButton);
group.add(enableButton);
disableButton = new GRadioButton("Disabled", false);
disableButton.addKeyListener(listener);
enablePanel.add(disableButton);
group.add(disableButton);
enablePanel.setBorder(BorderFactory.createTitledBorder("Filter Enable"));
mainPanel.add(enablePanel);
enableButton.addChangeListener(e -> {
boolean lenabled = enableButton.isSelected();
Iterator<JCheckBox> itr = checkboxes.iterator();
while (itr.hasNext()) {
JCheckBox curCheckbox = itr.next();
curCheckbox.setEnabled(lenabled);
}
selectAllButton.setEnabled(isEnabled());
selectNoneButton.setEnabled(isEnabled());
limitComboBox.setEnabled(isEnabled());
filterField.setEnabled(lenabled);
});
JPanel limitPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
limitComboBox = new GhidraComboBox<>();
limitComboBox.addKeyListener(listener);
limitComboBox.setModel(new DefaultComboBoxModel<>(
new String[] { ENTIRE_PROGRAM, CURRENT_VIEW, SELECTION }));
limitPanel.add(limitComboBox);
limitPanel.setBorder(BorderFactory.createTitledBorder("Limit Data To"));
mainPanel.add(limitPanel);
JPanel typesPanel = new JPanel(new BorderLayout());
JPanel typeButtonPanel = new JPanel(new GridLayout(1, 2, 5, 0));
selectAllButton = new JButton("Select All");
selectAllButton.setMnemonic('A');
selectAllButton.addActionListener(evt -> {
Iterator<JCheckBox> itr = checkboxes.iterator();
while (itr.hasNext()) {
itr.next().setSelected(true);
}
});
typeButtonPanel.add(selectAllButton);
selectNoneButton = new JButton("Select None");
selectNoneButton.setMnemonic('N');
selectNoneButton.addActionListener(evt -> {
Iterator<JCheckBox> itr = checkboxes.iterator();
while (itr.hasNext()) {
itr.next().setSelected(false);
}
});
typeButtonPanel.add(selectNoneButton);
checkboxPanel = new JPanel();
checkboxPanel.setBackground(Colors.BACKGROUND);
checkboxPanel.setLayout(new BoxLayout(checkboxPanel, BoxLayout.Y_AXIS));
buildCheckBoxList();
JScrollPane scroller = new JScrollPane(checkboxPanel);
scroller.setPreferredSize(new Dimension(checkboxPanel.getPreferredSize().width, 150));
typesPanel.add(scroller, BorderLayout.CENTER);
typesPanel.setBorder(BorderFactory.createTitledBorder("Enabled Data Types"));
typesPanel.add(typeButtonPanel, BorderLayout.SOUTH);
mainPanel.add(typesPanel);
JPanel filterBorderPanel = new JPanel(new GridLayout(1, 2, 5, 0));
filterBorderPanel.setBorder(
BorderFactory.createTitledBorder("Filter Enabled Data Types List Above"));
JPanel filterPanel = new JPanel(new BorderLayout());
filterField = new FilterTextField(checkboxPanel);
filterPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
filterPanel.add(new GLabel("Filter:"), BorderLayout.WEST);
filterPanel.add(filterField, BorderLayout.CENTER);
filterField.addFilterListener(filterListener);
filterBorderPanel.add(filterPanel, BorderLayout.CENTER);
mainPanel.add(filterBorderPanel);
return mainPanel;
}
private void updateButtonEnabledState() {
if (filteredTextExists()) {
selectNoneButton.setEnabled(false);
selectAllButton.setEnabled(false);
}
else {
selectNoneButton.setEnabled(true);
selectAllButton.setEnabled(true);
}
}
private void buildCheckBoxList() {
checkboxPanel.removeAll();
checkboxes.clear();
if (!filteredList.isEmpty()) {
Iterator<String> itr = filteredList.iterator();
String filteredText = getFilteredText();
while (itr.hasNext()) {
String curType = itr.next();
Boolean lEnabled = typeEnabledMap.get(curType);
StringBuffer buildMetaCurTypeBuff = new StringBuffer(curType);
int firstIndex = StringUtils.indexOfIgnoreCase(curType, filteredText, 0);
int lastIndex = firstIndex + filteredText.length();
buildMetaCurTypeBuff.insert(lastIndex, "</b>");//THIS MUST ALWAYS COME BEFORE FIRST INDEX (FOR NO MATH on INDEX)
buildMetaCurTypeBuff.insert(firstIndex, "<b>");
buildMetaCurTypeBuff.insert(0, "<html>");
createCheckBox(buildMetaCurTypeBuff.toString(), curType, lEnabled);
}
}
else {
if (!filteredTextExists()) {//Typed Incorrectly, so show nothing...
Iterator<String> itr = typeEnabledMap.keySet().iterator();
while (itr.hasNext()) {
String curType = itr.next();
Boolean lEnabled = typeEnabledMap.get(curType);
createCheckBox(curType, curType, lEnabled);
}
}
}
repaint();
}
private String getFilteredText() {
if (filterField != null) {
return filterField.getText().trim();
}
return null;
}
private boolean filteredTextExists() {
return (((getFilteredText() != null) && (getFilteredText().length() > 0)) ? true
: false);
}
private void repaint() {
checkboxPanel.invalidate();
mainPanel.validate();
mainPanel.repaint();
}
private void createCheckBox(String curTypeHtml, String curType, Boolean lEnabled) {
JCheckBox newCheckbox = new GHtmlCheckBox(curTypeHtml, lEnabled.booleanValue());
newCheckbox.setName(curType);
newCheckbox.addKeyListener(listener);
newCheckbox.addItemListener(itemListener);
DockingUtils.setTransparent(newCheckbox);
checkboxes.add(newCheckbox);
checkboxPanel.add(newCheckbox);
}
private void updateCheckBoxListFilter() {
ArrayList<String> checkboxNameList = new ArrayList<>();
if (filteredTextExists()) {
String filteredText = getFilteredText();
Set<Entry<String, Boolean>> entrySet = typeEnabledMap.entrySet();
Iterator<Entry<String, Boolean>> iteratorIndex = entrySet.iterator();
while (iteratorIndex.hasNext()) {
Entry<String, Boolean> entry = iteratorIndex.next();
String checkboxName = entry.getKey();
if (StringUtils.containsIgnoreCase(checkboxName, filteredText)) {
checkboxNameList.add(checkboxName);
}
}
}
filteredList = checkboxNameList;
buildCheckBoxList();
}
@Override
public void okCallback() {
filterEnabled = enableButton.isSelected();
viewMode = limitComboBox.getSelectedItem() == CURRENT_VIEW;
selectionMode = limitComboBox.getSelectedItem() == SELECTION;
close();
setSelected(filterEnabled);
plugin.reload();
}
@Override
public void cancelCallback() {
okCallback();
}
private void clearTypes() {
checkboxes.clear();
checkboxPanel.removeAll();
mainPanel.validate();
}
public void setSelectionEnabled(boolean enableSelection) {
int modelSize = limitComboBox.getModel().getSize();
if (enableSelection) {
if (modelSize != 3) {
limitComboBox.setModel(new DefaultComboBoxModel<>(
new String[] { ENTIRE_PROGRAM, CURRENT_VIEW, SELECTION }));
}
}
else if (modelSize != 2) {
limitComboBox.setModel(
new DefaultComboBoxModel<>(new String[] { ENTIRE_PROGRAM, CURRENT_VIEW }));
}
}
private class FilterActionFilterListener implements FilterListener {
@Override
public void filterChanged(String text) {
updateButtonEnabledState();
updateManager.updateLater();
}
}
setSelected(dialog.isFilterEnabled());
}
}

View File

@ -77,7 +77,6 @@ public class DataWindowPluginTest extends AbstractGhidraHeadedIntegrationTest {
}
private void loadProgram(String programName) throws Exception {
ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder();
program = builder.getProgram();
}
@ -89,13 +88,14 @@ public class DataWindowPluginTest extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testNavigation() throws Exception {
int numRows = dataTable.getRowCount();
for (int i = 0; i < numRows; i++) {
int rows = 10; // no need to test them all; a sample will do
for (int i = 0; i < rows; i++) {
clickTableCell(dataTable, i, DataTableModel.LOCATION_COL, 2);
waitForSwing();
Address addr = browser.getCurrentAddress();
Object tableAddr =
addr.getAddress(dataTable.getValueAt(i, DataTableModel.LOCATION_COL).toString());
int row = i;
Object value = runSwing(() -> dataTable.getValueAt(row, DataTableModel.LOCATION_COL));
Object tableAddr = addr.getAddress(value.toString());
assertEquals(addr, tableAddr);
}
}
@ -105,45 +105,36 @@ public class DataWindowPluginTest extends AbstractGhidraHeadedIntegrationTest {
int numData = dataTable.getRowCount();
int id = program.startTransaction(testName.getMethodName());
try {
tx(program, () -> {
program.getListing().clearAll(false, TaskMonitor.DUMMY);
}
finally {
program.endTransaction(id, true);
}
waitForNotBusy(dataTable);
});
waitForNotBusy(dataTable);
assertEquals(0, dataTable.getRowCount());
undo(program);
waitForNotBusy(dataTable);
assertEquals(numData, dataTable.getRowCount());
}
@Test
public void testFilter() throws Exception {
int totalRows = dataTable.getRowCount();
String type = dataTable.getValueAt(0, DataTableModel.TYPE_COL).toString();
filterAction.setTypeEnabled(type, false);
filterAction.setFilterEnabled(true);
plugin.reload();
waitForNotBusy(dataTable);
filterType(type);
int filteredRows = dataTable.getRowCount();
for (int i = 0; i < filteredRows; i++) {
assertEquals(dataTable.getValueAt(i, DataTableModel.TYPE_COL).toString().equals(type),
false);
}
assertEquals(totalRows > filteredRows, true);
filterAction.setFilterEnabled(false);
plugin.reload();
waitForNotBusy(dataTable);
for (int i = 0; i < filteredRows; i++) {
int row = i;
Object value = runSwing(() -> dataTable.getValueAt(row, DataTableModel.TYPE_COL));
assertNotEquals(value.toString(), type);
}
disableFilter();
assertEquals(dataTable.getRowCount(), totalRows);
}
@ -156,4 +147,31 @@ public class DataWindowPluginTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(dataTable.getRowCount(), 0);
loadProgram("notepad");
}
private void disableFilter() {
performAction(filterAction, false);
DataWindowFilterDialog filterDialog = waitForDialogComponent(DataWindowFilterDialog.class);
runSwing(() -> {
filterDialog.setFilterEnabled(false);
});
pressButtonByText(filterDialog, "OK");
waitForNotBusy(dataTable);
}
private void filterType(String type) {
performAction(filterAction, false);
DataWindowFilterDialog filterDialog = waitForDialogComponent(DataWindowFilterDialog.class);
runSwing(() -> {
filterDialog.setTypeEnabled(type, false);
filterDialog.setFilterEnabled(true);
});
pressButtonByText(filterDialog, "OK");
waitForNotBusy(dataTable);
}
}

View File

@ -27,10 +27,10 @@ public class DataWindowPluginScreenShots extends GhidraScreenShotGenerator {
super();
}
@Test
public void testDataWindow() {
@Test
public void testDataWindow() {
performAction("Defined Data", "DockingWindows", true);
performAction("Data Window", "DataWindowPlugin", true);
ComponentProvider provider = getProvider("Data Window");
@ -42,9 +42,9 @@ public class DataWindowPluginScreenShots extends GhidraScreenShotGenerator {
captureIsolatedProviderWindow(provider.getClass(), 500, 300);
}
@Test
public void testDataWindowFilter() {
performAction("Defined Data", "DockingWindows", true);
@Test
public void testDataWindowFilter() {
performAction("Data Window", "DataWindowPlugin", true);
performAction("Filter Data Types", "DataWindowPlugin", false);