GP-4410 - Version Tracking - Added support for deleting matches; Added table column filters

This commit is contained in:
dragonmacher 2024-07-18 13:54:26 -04:00
parent 76977bd514
commit 9f73d23ee4
36 changed files with 1335 additions and 699 deletions

View file

@ -24,29 +24,21 @@ import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.*;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.jdom.Element;
import docking.DockingWindowManager;
import docking.menu.*;
import docking.widgets.EmptyBorderButton;
import docking.widgets.EventTrigger;
import docking.widgets.filter.*;
import docking.widgets.label.GDLabel;
import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
import docking.widgets.table.columnfilter.ColumnFilterSaveManager;
import docking.widgets.table.constraint.dialog.ColumnFilterDialog;
import generic.theme.GIcon;
import docking.widgets.table.columnfilter.ColumnFilterManager;
import ghidra.framework.options.PreferenceState;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
import help.HelpService;
import resources.Icons;
import utilities.util.reflection.ReflectionUtilities;
import utility.function.Callback;
@ -112,37 +104,26 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
public static final String FILTER_TEXTFIELD_NAME = "filter.panel.textfield";
private static final String FILTER_STATE = "FILTER_STATE";
private static final String FILTER_EXTENSION = ".FilterExtension";
private static final Icon FILTER_ON_ICON = new GIcon("icon.widget.filterpanel.filter.on");
private static final Icon FILTER_OFF_ICON = new GIcon("icon.widget.filterpanel.filter.off");
private static final Icon APPLY_FILTER_ICON = Icons.OPEN_FOLDER_ICON;
private static final Icon CLEAR_FILTER_ICON = Icons.DELETE_ICON;
private JTable table;
private RowObjectFilterModel<ROW_OBJECT> textFilterModel;
private RowObjectFilterModel<ROW_OBJECT> rowObjectFilterModel;
private JLabel searchLabel;
private FilterTextField filterField;
private FilterListener filterListener = new GTableFilterListener();
private WeakSet<Callback> listeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
private FilterOptions filterOptions = new FilterOptions();
private TableTextFilterFactory<ROW_OBJECT> filterFactory =
new DefaultTableTextFilterFactory<>(filterOptions);
private RowFilterTransformer<ROW_OBJECT> transformer;
private TableFilter<ROW_OBJECT> secondaryTableFilter;
private ColumnBasedTableFilter<ROW_OBJECT> columnTableFilter;
private List<ColumnBasedTableFilter<ROW_OBJECT>> savedFilters = new ArrayList<>();
private EmptyBorderButton filterStateButton;
private ColumnFilterManager<ROW_OBJECT> columnFilterManager;
private String uniquePreferenceKey;
private MultiStateDockingAction<ColumnBasedTableFilter<ROW_OBJECT>> columnFilterAction;
private ColumnFilterDialog<ROW_OBJECT> columnFilterDialog;
private ColumnBasedTableFilter<ROW_OBJECT> lastUsedColumnFilter;
private SwingUpdateManager updateManager = new SwingUpdateManager(250, 1000, () -> {
private SwingUpdateManager filterUpdater = new SwingUpdateManager(250, 1000, () -> {
String text = filterField.getText();
TableFilter<ROW_OBJECT> tableFilter = filterFactory.getTableFilter(text, transformer);
@ -151,8 +132,9 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
// result of a filter, the table does not know this and may update the wrong row data.
table.editingCanceled(null);
textFilterModel.setTableFilter(
getCombinedTableFilter(secondaryTableFilter, tableFilter, columnTableFilter));
ColumnBasedTableFilter<ROW_OBJECT> columnFilter = columnFilterManager.getCurrentFilter();
rowObjectFilterModel.setTableFilter(
getCombinedTableFilter(secondaryTableFilter, tableFilter, columnFilter));
});
/** I'm a field so that my weak reference won't go away */
@ -174,12 +156,12 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
@Override
public void columnRemoved(TableColumnModelEvent e) {
updateTableContents();
filterUpdater.updateLater();
}
@Override
public void columnAdded(TableColumnModelEvent e) {
updateTableContents();
filterUpdater.updateLater();
}
};
@ -231,13 +213,16 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
String filterLabel) {
this.table = table;
buildPanel(filterLabel);
uniquePreferenceKey = createUniqueFilterPreferenceKey(table);
transformer = new DefaultRowFilterTransformer<>(tableModel, table.getColumnModel());
textFilterModel = installTableModel(tableModel);
rowObjectFilterModel = installTableModel(tableModel);
columnFilterManager = new ColumnFilterManager<ROW_OBJECT>(table, rowObjectFilterModel,
getPreferenceKey(), filterUpdater::updateLater);
buildPanel(filterLabel);
TableColumnModel columnModel = table.getColumnModel();
columnModel.addColumnModelListener(columnModelListener);
@ -246,12 +231,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
table.addPropertyChangeListener(badProgrammingPropertyChangeListener);
DockingWindowManager.registerComponentLoadedListener(this,
(windowManager, provider) -> initialize(windowManager));
}
private void initialize(DockingWindowManager windowManager) {
loadFilterPreference(windowManager);
initializeSavedFilters();
(windowManager, provider) -> loadFilterPreference(windowManager));
}
private void loadFilterPreference(DockingWindowManager dockingWindowManager) {
@ -283,7 +263,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
if (xmlElement != null) {
this.filterOptions = FilterOptions.restoreFromXML(xmlElement);
updateFilterFactory();
updateTableContents();
filterUpdater.updateLater();
}
}
@ -326,18 +306,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @param newFilter the ColumnTableFilter to use for filtering this table.
*/
public void setColumnTableFilter(ColumnBasedTableFilter<ROW_OBJECT> newFilter) {
if (Objects.equals(newFilter, this.columnTableFilter)) {
return;
}
if (columnTableFilter != null && !columnTableFilter.isSaved()) {
lastUsedColumnFilter = columnTableFilter;
}
columnTableFilter = newFilter;
updateTableContents();
updateColumnFilterButton();
if (columnFilterDialog != null) {
columnFilterDialog.filterChanged(newFilter);
}
columnFilterManager.setFilter(newFilter);
}
/**
@ -351,7 +320,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
*/
public void setFilterRowTransformer(RowFilterTransformer<ROW_OBJECT> transformer) {
this.transformer = transformer;
updateTableContents();
filterUpdater.updateLater();
}
/**
@ -362,7 +331,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
*/
public void setSecondaryFilter(TableFilter<ROW_OBJECT> tableFilter) {
this.secondaryTableFilter = tableFilter;
updateTableContents();
filterUpdater.updateLater();
}
/**
@ -373,7 +342,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
public void setFilterOptions(FilterOptions filterOptions) {
this.filterOptions = filterOptions;
updateFilterFactory();
updateTableContents();
filterUpdater.updateLater();
doSaveState();
}
@ -394,7 +363,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
add(buildFilterStateButton());
if (isTableColumnFilterableModel()) {
add(Box.createHorizontalStrut(5));
add(buildColumnFilterStateButton());
add(columnFilterManager.getConfigureButton());
}
HelpService helpService = DockingWindowManager.getHelpService();
@ -425,107 +394,6 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return table.getModel() instanceof RowObjectFilterModel;
}
@SuppressWarnings("unchecked")
private JComponent buildColumnFilterStateButton() {
RowObjectFilterModel<ROW_OBJECT> tableModel =
(RowObjectFilterModel<ROW_OBJECT>) table.getModel();
columnFilterAction =
new NonToolbarMultiStateAction<>("Column Filter", "GTableFilterPanel") {
@Override
public void actionStateChanged(
ActionState<ColumnBasedTableFilter<ROW_OBJECT>> newActionState,
EventTrigger trigger) {
if (trigger != EventTrigger.GUI_ACTION) {
return;
}
ColumnFilterActionState state = (ColumnFilterActionState) newActionState;
state.performAction();
}
@Override
protected void actionPerformed() {
showFilterDialog(tableModel);
}
};
HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters");
columnFilterAction.setHelpLocation(helpLocation);
updateFilterFactory();
updateColumnFilterButton();
JButton button = columnFilterAction.createButton();
DockingWindowManager.getHelpService().registerHelp(button, helpLocation);
return button;
}
private void initializeSavedFilters() {
TableModel model = table.getModel();
if (!(model instanceof GDynamicColumnTableModel)) {
return;
}
@SuppressWarnings("unchecked")
GDynamicColumnTableModel<ROW_OBJECT, ?> dynamicModel =
(GDynamicColumnTableModel<ROW_OBJECT, ?>) model;
ColumnFilterSaveManager<ROW_OBJECT> saveManager =
new ColumnFilterSaveManager<>(this, table, dynamicModel, dynamicModel.getDataSource());
savedFilters = saveManager.getSavedFilters();
Collections.reverse(savedFilters);
updateColumnFilterButton();
}
private void updateColumnFilterButton() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = getActionStates();
columnFilterAction.setActionStates(list);
}
private List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> getActionStates() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = new ArrayList<>();
if (columnTableFilter == null) {
list.add(new CreateFilterActionState());
}
else {
list.add(new EditFilterActionState(columnTableFilter));
list.add(new ClearFilterActionState());
}
if (lastUsedColumnFilter != null) {
list.add(new ApplyLastUsedActionState(lastUsedColumnFilter));
}
for (ColumnBasedTableFilter<ROW_OBJECT> filter : savedFilters) {
list.add(new ApplyFilterActionState(filter));
}
return list;
}
private void showFilterDialog(RowObjectFilterModel<ROW_OBJECT> tableModel) {
if (columnFilterDialog == null) {
if (ColumnFilterDialog.hasFilterableColumns(table, tableModel)) {
DockingWindowManager dockingWindowManager = DockingWindowManager.getInstance(table);
loadFilterPreference(dockingWindowManager);
columnFilterDialog = new ColumnFilterDialog<>(this, table, tableModel);
}
else {
Msg.showError(this, this, "Column Filter Error",
"This table contains no filterable columns!");
return;
}
}
columnFilterDialog.setCloseCallback(() -> {
doSaveState();
updateFilterFactory();
columnFilterDialog = null;
});
DockingWindowManager.showDialog(GTableFilterPanel.this, columnFilterDialog);
}
private void updateFilterFactory() {
filterStateButton.setIcon(filterOptions.getFilterStateIcon());
filterStateButton.setToolTipText(filterOptions.getFilterDescription());
@ -587,17 +455,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
public RowObjectFilterModel<ROW_OBJECT> getTableFilterModel() {
return textFilterModel;
}
/** Convenience method to refilter the table's contents */
private void updateTableContents() {
updateManager.updateLater();
notifyFilterChanged();
}
private void notifyFilterChanged() {
listeners.forEach(callback -> callback.call());
return rowObjectFilterModel;
}
public void dispose() {
@ -609,13 +467,11 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
columnModel.removeColumnModelListener(columnModelListener);
columnModelListener = null;
if (columnFilterDialog != null) {
columnFilterDialog.dispose();
}
columnFilterManager.dispose();
table.removePropertyChangeListener(badProgrammingPropertyChangeListener);
updateManager.dispose();
filterUpdater.dispose();
if (table instanceof GTable) {
((GTable) table).dispose();
}
@ -696,7 +552,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return viewRow;
}
return textFilterModel.getModelRow(viewRow);
return rowObjectFilterModel.getModelRow(viewRow);
}
/**
@ -710,7 +566,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @return the row in the table for the given model row.
*/
public int getViewRow(int modelRow) {
return textFilterModel.getViewRow(modelRow);
return rowObjectFilterModel.getViewRow(modelRow);
}
/**
@ -720,7 +576,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @return the row object matching the given index
*/
public ROW_OBJECT getRowObject(int viewRow) {
ROW_OBJECT rowObject = textFilterModel.getRowObject(viewRow);
ROW_OBJECT rowObject = rowObjectFilterModel.getRowObject(viewRow);
return rowObject;
}
@ -736,7 +592,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return;
}
int viewRow = textFilterModel.getViewIndex(t);
int viewRow = rowObjectFilterModel.getViewIndex(t);
if (viewRow >= 0) {
table.setRowSelectionInterval(viewRow, viewRow);
scrollToSelectedRow();
@ -785,7 +641,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
if (row < 0) {
return null;
}
return textFilterModel.getRowObject(row);
return rowObjectFilterModel.getRowObject(row);
}
/**
@ -801,7 +657,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
List<ROW_OBJECT> list = new ArrayList<>(rows.length);
for (int row : rows) {
list.add(textFilterModel.getRowObject(row));
list.add(rowObjectFilterModel.getRowObject(row));
}
return list;
}
@ -814,7 +670,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @return true if in the view
*/
public boolean isInView(ROW_OBJECT o) {
int rowIndex = textFilterModel.getRowIndex(o);
int rowIndex = rowObjectFilterModel.getRowIndex(o);
return rowIndex >= 0;
}
@ -823,11 +679,48 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
public int getRowCount() {
return textFilterModel.getRowCount();
return rowObjectFilterModel.getRowCount();
}
public int getUnfilteredRowCount() {
return textFilterModel.getUnfilteredRowCount();
return rowObjectFilterModel.getUnfilteredRowCount();
}
/**
* Generates a key used to store user filter configuration state. You can override this
* method to generate unique keys yourself. You are required to override this method if
* you create multiple versions of a filter panel from the same place in your code, as
* multiple instances created in the same place will cause them all to share the same key and
* thus to have the same filter settings when they are created initially.
* <p>
* As an example, consider a plugin that creates <code>n</code> providers. If each provider uses
* a filter panel, then each provider will share the same filter settings when that provider
* is created. If this is not what you want, then you need to override this method to
* generate a unique key for each provider.
*
* @param jTable the table
* @return a key used to store user filter configuration state.
*/
public String createUniqueFilterPreferenceKey(JTable jTable) {
return generateFilterPreferenceKey(jTable, FILTER_EXTENSION);
}
/**
* Returns the ColumnTableFilter that has been set on this GTableFilterPanel or null if there
* is none.
*
* @return the ColumnTableFilter that has been set.
*/
public ColumnBasedTableFilter<ROW_OBJECT> getColumnTableFilter() {
return columnFilterManager.getCurrentFilter();
}
/**
* Return a unique key that can be used to store preferences for this table.
* @return a unique key that can be used to store preferences for this table.
*/
public String getPreferenceKey() {
return uniquePreferenceKey;
}
//==================================================================================================
@ -900,7 +793,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
private TableModelEvent translateEventForFilter(TableModelEvent event) {
int rowCount = textFilterModel.getUnfilteredRowCount();
int rowCount = rowObjectFilterModel.getUnfilteredRowCount();
if (rowCount == 0) {
return event; // nothing to translate--no data
}
@ -915,14 +808,14 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
if (firstRow == 0 && lastRow == rowCount - 1) {
firstRow = 0;
lastRow = Math.max(0, textFilterModel.getRowCount() - 1);
lastRow = Math.max(0, rowObjectFilterModel.getRowCount() - 1);
}
else {
// translate to the filtered view (from the wrapped model's full universe)
firstRow = getViewRow(firstRow);
lastRow = getViewRow(lastRow);
}
return new TableModelEvent(textFilterModel, firstRow, lastRow, event.getColumn(),
return new TableModelEvent(rowObjectFilterModel, firstRow, lastRow, event.getColumn(),
event.getType());
}
}
@ -937,8 +830,8 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
isUpdatingModel = true;
if (textFilterModel instanceof WrappingTableModel) {
WrappingTableModel tableModelWrapper = (WrappingTableModel) textFilterModel;
if (rowObjectFilterModel instanceof WrappingTableModel) {
WrappingTableModel tableModelWrapper = (WrappingTableModel) rowObjectFilterModel;
tableModelWrapper.wrappedModelChangedFromTableChangedEvent();
}
filterField.alert();
@ -947,75 +840,16 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
private class GTableFilterListener implements FilterListener {
@Override
public void filterChanged(String text) {
updateTableContents();
filterUpdater.updateLater();
}
}
/**
* Generates a key used to store user filter configuration state. You can override this
* method to generate unique keys yourself. You are required to override this method if
* you create multiple versions of a filter panel from the same place in your code, as
* multiple instances created in the same place will cause them all to share the same key and
* thus to have the same filter settings when they are created initially.
* <p>
* As an example, consider a plugin that creates <code>n</code> providers. If each provider uses
* a filter panel, then each provider will share the same filter settings when that provider
* is created. If this is not what you want, then you need to override this method to
* generate a unique key for each provider.
*
* @param jTable the table
* @return a key used to store user filter configuration state.
*/
public String createUniqueFilterPreferenceKey(JTable jTable) {
return generateFilterPreferenceKey(jTable, FILTER_EXTENSION);
}
/**
* Returns the ColumnTableFilter that has been set on this GTableFilterPanel or null if there
* is none.
*
* @return the ColumnTableFilter that has been set.
*/
public ColumnBasedTableFilter<ROW_OBJECT> getColumnTableFilter() {
return columnTableFilter;
}
/**
* Return a unique key that can be used to store preferences for this table.
* @return a unique key that can be used to store preferences for this table.
*/
public String getPreferenceKey() {
return uniquePreferenceKey;
}
/**
* Updates the "quick filter" multistate button.
* @param filter the filter to add or remove.
* @param add if true, the filter is added to the quick list. Otherwise, it is removed.
*/
public void updateSavedFilters(ColumnBasedTableFilter<ROW_OBJECT> filter, boolean add) {
if (add) {
ArrayList<ColumnBasedTableFilter<ROW_OBJECT>> list = new ArrayList<>();
list.add(filter);
list.addAll(savedFilters);
savedFilters = list;
if (filter.isEquivalent(columnTableFilter)) {
setColumnTableFilter(filter);
}
}
else {
savedFilters.remove(filter);
}
updateColumnFilterButton();
}
//==================================================================================================
// Static Methods
//==================================================================================================
private static String generateFilterPreferenceKey(JTable jTable, String extension) {
if (jTable instanceof GTable) {
@ -1039,78 +873,4 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
String clientName = filteredTrace[0].getClassName();
return clientName;
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private abstract class ColumnFilterActionState
extends ActionState<ColumnBasedTableFilter<ROW_OBJECT>> {
ColumnFilterActionState(String name, Icon icon, ColumnBasedTableFilter<ROW_OBJECT> filter) {
super(name, icon, filter);
}
abstract void performAction();
}
String getFilterName(ColumnBasedTableFilter<ROW_OBJECT> filter) {
String filterName = filter.getName();
return filterName == null ? "Unsaved" : filterName;
}
private class ClearFilterActionState extends ColumnFilterActionState {
public ClearFilterActionState() {
super("Clear Filter", CLEAR_FILTER_ICON, null);
}
@Override
void performAction() {
setColumnTableFilter(null);
}
}
private class CreateFilterActionState extends ColumnFilterActionState {
public CreateFilterActionState() {
super("Create Column Filter", FILTER_OFF_ICON, null);
}
@Override
void performAction() {
showFilterDialog(textFilterModel);
}
}
private class EditFilterActionState extends ColumnFilterActionState {
public EditFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Edit: " + getFilterName(filter), FILTER_ON_ICON, filter);
}
@Override
void performAction() {
showFilterDialog(textFilterModel);
}
}
private class ApplyFilterActionState extends ColumnFilterActionState {
public ApplyFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply: " + getFilterName(filter), APPLY_FILTER_ICON, filter);
}
@Override
void performAction() {
setColumnTableFilter(getUserData());
}
}
private class ApplyLastUsedActionState extends ColumnFilterActionState {
public ApplyLastUsedActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply Last Unsaved", FILTER_ON_ICON, filter);
}
@Override
void performAction() {
setColumnTableFilter(getUserData());
}
}
}

View file

@ -0,0 +1,298 @@
/* ###
* 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 docking.widgets.table.columnfilter;
import java.util.*;
import javax.swing.*;
import javax.swing.table.TableModel;
import docking.DockingWindowManager;
import docking.menu.*;
import docking.widgets.EventTrigger;
import docking.widgets.table.GDynamicColumnTableModel;
import docking.widgets.table.RowObjectFilterModel;
import docking.widgets.table.constraint.dialog.ColumnFilterDialog;
import generic.theme.GIcon;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import resources.Icons;
import utility.function.Callback;
/**
* A class that manages column filters for a table. This includes creating the UI elements that
* allow users to build filters, as well as a means to save and restore filters.
*
* @param <ROW_OBJECT> the row type
*/
public class ColumnFilterManager<ROW_OBJECT> {
public static final String FILTER_EXTENSION = ".FilterExtension";
public static final String FILTER_TEXTFIELD_NAME = "filter.panel.textfield";
private static final Icon FILTER_ON_ICON = new GIcon("icon.widget.filterpanel.filter.on");
private static final Icon FILTER_OFF_ICON = new GIcon("icon.widget.filterpanel.filter.off");
private static final Icon APPLY_FILTER_ICON = Icons.OPEN_FOLDER_ICON;
private static final Icon CLEAR_FILTER_ICON = Icons.DELETE_ICON;
private MultiStateDockingAction<ColumnBasedTableFilter<ROW_OBJECT>> columnFilterAction;
private JButton configureButton;
private ColumnFilterDialog<ROW_OBJECT> columnFilterDialog;
private ColumnBasedTableFilter<ROW_OBJECT> lastUsedFilter;
private ColumnBasedTableFilter<ROW_OBJECT> currentFilter;
private List<ColumnBasedTableFilter<ROW_OBJECT>> savedFilters = new ArrayList<>();
private JTable table;
private RowObjectFilterModel<ROW_OBJECT> rowObjectFilterModel;
private String preferenceKey;
private Callback filterChangedCallback;
public ColumnFilterManager(JTable table, RowObjectFilterModel<ROW_OBJECT> rowObjectFilterModel,
String preferenceKey, Callback filterChangedCallback) {
this.table = Objects.requireNonNull(table);
this.rowObjectFilterModel = Objects.requireNonNull(rowObjectFilterModel);
this.preferenceKey = Objects.requireNonNull(preferenceKey);
this.filterChangedCallback = Objects.requireNonNull(filterChangedCallback);
configureButton = buildColumnFilterStateButton();
DockingWindowManager.registerComponentLoadedListener(table,
(windowManager, provider) -> initializeSavedFilters());
}
private void initializeSavedFilters() {
TableModel model = table.getModel();
if (!(model instanceof GDynamicColumnTableModel)) {
return;
}
@SuppressWarnings("unchecked")
GDynamicColumnTableModel<ROW_OBJECT, ?> dynamicModel =
(GDynamicColumnTableModel<ROW_OBJECT, ?>) model;
ColumnFilterSaveManager<ROW_OBJECT> saveManager = new ColumnFilterSaveManager<>(
preferenceKey, table, dynamicModel, dynamicModel.getDataSource());
savedFilters = saveManager.getSavedFilters();
Collections.reverse(savedFilters);
updateColumnFilterButton();
}
public ColumnBasedTableFilter<ROW_OBJECT> getCurrentFilter() {
return currentFilter;
}
public JButton getConfigureButton() {
return configureButton;
}
public String getPreferenceKey() {
return preferenceKey;
}
public void setFilter(ColumnBasedTableFilter<ROW_OBJECT> newFilter) {
if (Objects.equals(newFilter, this.currentFilter)) {
return;
}
if (currentFilter != null && !currentFilter.isSaved()) {
lastUsedFilter = currentFilter;
}
currentFilter = newFilter;
updateColumnFilterButton();
if (columnFilterDialog != null) {
columnFilterDialog.filterChanged(newFilter);
}
filterChangedCallback.call();
}
public void updateSavedFilters(ColumnBasedTableFilter<ROW_OBJECT> filter, boolean add) {
if (add) {
ArrayList<ColumnBasedTableFilter<ROW_OBJECT>> list = new ArrayList<>();
list.add(filter);
list.addAll(savedFilters);
savedFilters = list;
if (filter.isEquivalent(currentFilter)) {
setFilter(filter);
}
}
else {
savedFilters.remove(filter);
}
updateColumnFilterButton();
filterChangedCallback.call();
}
public void dispose() {
if (columnFilterDialog != null) {
columnFilterDialog.dispose();
columnFilterDialog = null;
}
filterChangedCallback = Callback.dummy();
}
private JButton buildColumnFilterStateButton() {
columnFilterAction =
new NonToolbarMultiStateAction<>("Column Filter", "GTableFilterPanel") {
@Override
public void actionStateChanged(
ActionState<ColumnBasedTableFilter<ROW_OBJECT>> newActionState,
EventTrigger trigger) {
if (trigger != EventTrigger.GUI_ACTION) {
return;
}
ColumnFilterActionState state = (ColumnFilterActionState) newActionState;
state.performAction();
}
@Override
protected void actionPerformed() {
showFilterDialog(rowObjectFilterModel);
}
};
HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters");
columnFilterAction.setHelpLocation(helpLocation);
updateColumnFilterButton();
JButton button = columnFilterAction.createButton();
DockingWindowManager.getHelpService().registerHelp(button, helpLocation);
return button;
}
private void updateColumnFilterButton() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = getActionStates();
columnFilterAction.setActionStates(list);
}
private List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> getActionStates() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = new ArrayList<>();
if (currentFilter == null) {
list.add(new CreateFilterActionState());
}
else {
list.add(new EditFilterActionState(currentFilter));
list.add(new ClearFilterActionState());
}
if (lastUsedFilter != null) {
list.add(new ApplyLastUsedActionState(lastUsedFilter));
}
for (ColumnBasedTableFilter<ROW_OBJECT> filter : savedFilters) {
list.add(new ApplyFilterActionState(filter));
}
return list;
}
private void showFilterDialog(RowObjectFilterModel<ROW_OBJECT> tableModel) {
if (columnFilterDialog == null) {
if (ColumnFilterDialog.hasFilterableColumns(table, tableModel)) {
columnFilterDialog = new ColumnFilterDialog<>(this, table, rowObjectFilterModel);
}
else {
Msg.showError(this, null, "Column Filter Error",
"This table contains no filterable columns!");
return;
}
}
DockingWindowManager.showDialog(table, columnFilterDialog);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private abstract class ColumnFilterActionState
extends ActionState<ColumnBasedTableFilter<ROW_OBJECT>> {
ColumnFilterActionState(String name, Icon icon, ColumnBasedTableFilter<ROW_OBJECT> filter) {
super(name, icon, filter);
}
abstract void performAction();
}
String getFilterName(ColumnBasedTableFilter<ROW_OBJECT> filter) {
String filterName = filter.getName();
return filterName == null ? "Unsaved" : filterName;
}
private class ClearFilterActionState extends ColumnFilterActionState {
public ClearFilterActionState() {
super("Clear Filter", CLEAR_FILTER_ICON, null);
}
@Override
void performAction() {
setFilter(null);
}
}
private class CreateFilterActionState extends ColumnFilterActionState {
public CreateFilterActionState() {
super("Create Column Filter", FILTER_OFF_ICON, null);
}
@Override
void performAction() {
showFilterDialog(rowObjectFilterModel);
}
}
private class EditFilterActionState extends ColumnFilterActionState {
public EditFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Edit: " + getFilterName(filter), FILTER_ON_ICON, filter);
}
@Override
void performAction() {
showFilterDialog(rowObjectFilterModel);
}
}
private class ApplyFilterActionState extends ColumnFilterActionState {
public ApplyFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply: " + getFilterName(filter), APPLY_FILTER_ICON, filter);
}
@Override
void performAction() {
setFilter(getUserData());
}
}
private class ApplyLastUsedActionState extends ColumnFilterActionState {
public ApplyLastUsedActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply Last Unsaved", FILTER_ON_ICON, filter);
}
@Override
void performAction() {
setFilter(getUserData());
}
}
}

View file

@ -23,7 +23,6 @@ import javax.swing.JTable;
import org.jdom.Element;
import docking.DockingWindowManager;
import docking.widgets.table.GTableFilterPanel;
import docking.widgets.table.RowObjectTableModel;
import ghidra.framework.options.PreferenceState;
import ghidra.framework.options.SaveState;
@ -35,7 +34,7 @@ import ghidra.util.Msg;
* @param <R> the row type of the table.
*/
public class ColumnFilterSaveManager<R> {
private static final String COLUMN_FILTER_EXTENSION = ".ColumnFilterExtension";
private static final String COLUMN_FILTER_STATE = "COLUMN_FILTER_STATE";
private List<ColumnBasedTableFilter<R>> filters = new ArrayList<>();
@ -46,14 +45,15 @@ public class ColumnFilterSaveManager<R> {
/**
* Constructor
*
* @param panel The GTableFilterPanel for the table.
* @param tablePreferenceKey the key used to save table settings. This is used to make a
* preference key for saving the column filters.
* @param table The JTable that is filterable.
* @param model the TableModel that supports filtering.
* @param dataSource the table's DataSource object.
*/
public ColumnFilterSaveManager(GTableFilterPanel<R> panel, JTable table,
public ColumnFilterSaveManager(String tablePreferenceKey, JTable table,
RowObjectTableModel<R> model, Object dataSource) {
preferenceKey = panel.getPreferenceKey() + COLUMN_FILTER_EXTENSION;
preferenceKey = tablePreferenceKey + ColumnFilterManager.FILTER_EXTENSION;
loadFromPreferences(table, model, dataSource);
}

View file

@ -29,7 +29,6 @@ import docking.action.*;
import docking.widgets.OptionDialog;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.label.GLabel;
import docking.widgets.table.GTableFilterPanel;
import docking.widgets.table.RowObjectFilterModel;
import docking.widgets.table.columnfilter.*;
import docking.widgets.table.constrainteditor.ColumnConstraintEditor;
@ -50,38 +49,38 @@ import utility.function.Callback;
public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
implements TableFilterDialogModelListener {
private final ColumnFilterDialogModel<R> filterModel;
private ColumnFilterManager<R> filterManager;
private ColumnFilterDialogModel<R> dialogModel;
private JTable table;
private RowObjectFilterModel<R> tableModel;
private JPanel filterPanelContainer;
private List<ColumnFilterPanel> filterPanels = new ArrayList<>();
private Callback closeCallback;
private GTableFilterPanel<R> gTableFilterPanel;
private JPanel bottomPanel;
private JTable table;
private RowObjectFilterModel<R> tableModel;
/**
* Constructor
*
* @param gTableFilterPanel the GTableFilterPanel that launched this dialog.
*
* @param filterManager the filter manager
* @param table the table being filtered.
* @param tableModel the table model.
*/
public ColumnFilterDialog(GTableFilterPanel<R> gTableFilterPanel, JTable table,
public ColumnFilterDialog(ColumnFilterManager<R> filterManager, JTable table,
RowObjectFilterModel<R> tableModel) {
super("Table Column Filters", WindowUtilities.areModalDialogsVisible(), true, true, false);
this.gTableFilterPanel = gTableFilterPanel;
this.filterManager = filterManager;
this.table = table;
this.tableModel = tableModel;
ColumnBasedTableFilter<R> columnTableFilter = gTableFilterPanel.getColumnTableFilter();
ColumnBasedTableFilter<R> columnTableFilter = filterManager.getCurrentFilter();
filterModel =
dialogModel =
new ColumnFilterDialogModel<>(tableModel, table.getColumnModel(), columnTableFilter);
filterModel.addListener(this);
dialogModel.addListener(this);
setHelpLocation(new HelpLocation("Trees", "Column_Filters"));
addWorkPanel(buildMainPanel());
@ -97,10 +96,9 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
updateStatus();
}
public static <R> boolean hasFilterableColumns(JTable table,
RowObjectFilterModel<R> model) {
public static <R> boolean hasFilterableColumns(JTable table, RowObjectFilterModel<R> model) {
return !ColumnFilterDialogModel.getAllColumnFilterData(model, table.getColumnModel())
.isEmpty();
.isEmpty();
}
private void addClearFilterButton() {
@ -119,7 +117,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
DockingAction saveAction = new DockingAction("Save", "Filter") {
@Override
public boolean isEnabledForContext(ActionContext context) {
return !filterModel.getFilterRows().isEmpty() && filterModel.isValid();
return !dialogModel.getFilterRows().isEmpty() && dialogModel.isValid();
}
@Override
@ -140,15 +138,15 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
};
loadAction.setDescription("Load Filter");
loadAction.setHelpLocation(new HelpLocation("Trees", "Load_Filter"));
loadAction.setToolBarData(
new ToolBarData(Icons.OPEN_FOLDER_ICON));
loadAction.setToolBarData(new ToolBarData(Icons.OPEN_FOLDER_ICON));
addAction(loadAction);
}
private void saveFilter() {
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(
gTableFilterPanel, table, tableModel, filterModel.getDataSource());
ColumnBasedTableFilter<R> filter = filterModel.getTableColumnFilter();
String preferenceKey = filterManager.getPreferenceKey();
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(preferenceKey,
table, tableModel, dialogModel.getDataSource());
ColumnBasedTableFilter<R> filter = dialogModel.getTableColumnFilter();
String defaultName = new Date().toString();
InputDialog dialog = new InputDialog("Save Filter", "Filter Name: ", defaultName, d -> {
@ -174,13 +172,14 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
filter.setName(filterName);
filterSaveManager.addFilter(filter);
filterSaveManager.save();
gTableFilterPanel.updateSavedFilters(filter, true);
filterModel.setFilter(filter);
filterManager.updateSavedFilters(filter, true);
dialogModel.setFilter(filter);
}
private void loadFilter() {
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(
gTableFilterPanel, table, tableModel, filterModel.getDataSource());
String preferenceKey = filterManager.getPreferenceKey();
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(preferenceKey,
table, tableModel, dialogModel.getDataSource());
List<ColumnBasedTableFilter<R>> savedFilters = filterSaveManager.getSavedFilters();
if (savedFilters.isEmpty()) {
Msg.showInfo(this, getComponent(), "No Saved Filters",
@ -195,7 +194,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
ColumnBasedTableFilter<R> selectedFilter = archiveDialog.getSelectedColumnFilter();
if (selectedFilter != null) {
filterModel.setFilter(selectedFilter);
dialogModel.setFilter(selectedFilter);
}
}
@ -223,14 +222,12 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
bottomPanel = new JPanel(new BorderLayout());
JPanel innerPanel = new JPanel(new VerticalLayout(3));
JButton addAndConditionButton =
new JButton("Add AND condition", Icons.ADD_ICON);
JButton addAndConditionButton = new JButton("Add AND condition", Icons.ADD_ICON);
addAndConditionButton.addActionListener(e -> addFilterCondition(LogicOperation.AND));
addAndConditionButton.setEnabled(true);
JButton addOrConditionButton =
new JButton("Add OR condition", Icons.ADD_ICON);
JButton addOrConditionButton = new JButton("Add OR condition", Icons.ADD_ICON);
addOrConditionButton.setHorizontalAlignment(SwingConstants.LEFT);
addOrConditionButton.addActionListener(e -> addFilterCondition(LogicOperation.OR));
@ -251,7 +248,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
sb.append("Column Filter");
ColumnBasedTableFilter<R> filter = filterModel.getTableColumnFilter();
ColumnBasedTableFilter<R> filter = dialogModel.getTableColumnFilter();
if (filter != null && filter.getName() != null) {
sb.append(": ").append(filter.getName());
}
@ -275,7 +272,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
// * Dialog state is different from applied filter and valid - prompt to apply filter.
// * Dialog state is different from applied filter, but invalid - prompt if should really close
if (!filterModel.hasUnappliedChanges()) {
if (!dialogModel.hasUnappliedChanges()) {
return true;
}
if (dialogHasValidFilter()) {
@ -316,12 +313,12 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
private boolean dialogHasValidFilter() {
return filterModel.getTableColumnFilter() != null;
return dialogModel.getTableColumnFilter() != null;
}
@Override
protected void dialogClosed() {
filterModel.dispose();
dialogModel.dispose();
if (closeCallback != null) {
closeCallback.call();
}
@ -339,28 +336,29 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
private void clearFilter() {
this.gTableFilterPanel.setColumnTableFilter(null);
filterModel.clear();
filterManager.setFilter(null);
dialogModel.clear();
updateStatus();
}
private void applyFilter() {
ColumnBasedTableFilter<R> tableColumnFilter = filterModel.getTableColumnFilter();
filterModel.setCurrentlyAppliedFilter(tableColumnFilter);
this.gTableFilterPanel.setColumnTableFilter(tableColumnFilter);
ColumnBasedTableFilter<R> tableColumnFilter = dialogModel.getTableColumnFilter();
dialogModel.setCurrentlyAppliedFilter(tableColumnFilter);
filterManager.setFilter(tableColumnFilter);
}
private void loadFilterRows() {
filterPanelContainer.removeAll();
filterPanels.clear();
List<DialogFilterRow> filterRows = filterModel.getFilterRows();
List<DialogFilterRow> filterRows = dialogModel.getFilterRows();
for (int i = 0; i < filterRows.size(); i++) {
DialogFilterRow filterRow = filterRows.get(i);
ColumnFilterPanel panel = new ColumnFilterPanel(filterRow);
if (i != 0) {
filterPanelContainer.add(
createLogicalOperationLabel(filterRow.getLogicOperation()));
LogicOperation op = filterRow.getLogicOperation();
GLabel label = createLogicalOperationLabel(op);
filterPanelContainer.add(label);
}
filterPanelContainer.add(panel);
filterPanels.add(panel);
@ -382,14 +380,14 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
headerPanel.add(new GLabel("Filter", SwingConstants.CENTER));
headerPanel.add(new GLabel("Filter Value", SwingConstants.CENTER));
headerPanel.setBorder(new CompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 0, Colors.BORDER),
BorderFactory.createEmptyBorder(4, 0, 4, 0)));
headerPanel.setBorder(
new CompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Colors.BORDER),
BorderFactory.createEmptyBorder(4, 0, 4, 0)));
return headerPanel;
}
private void addFilterCondition(LogicOperation logicalOperation) {
filterModel.createFilterRow(logicalOperation);
dialogModel.createFilterRow(logicalOperation);
scrollFilterPanelToBottom();
}
@ -410,7 +408,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
void updateStatus() {
setStatusText(getStatusMessage());
boolean isValid = filterModel.isValid();
boolean isValid = dialogModel.isValid();
setOkEnabled(isValid);
setApplyEnabled(isValid);
@ -423,11 +421,11 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
public void filterChanged(ColumnBasedTableFilter<R> newFilter) {
if (Objects.equals(newFilter, filterModel.getTableColumnFilter())) {
if (Objects.equals(newFilter, dialogModel.getTableColumnFilter())) {
return;
}
getComponent().requestFocus(); // work around for java parenting bug where dialog appears behind
if (filterModel.hasUnappliedChanges()) {
if (dialogModel.hasUnappliedChanges()) {
int result = OptionDialog.showYesNoDialog(getComponent(), "Filter Changed",
"The filter has been changed externally.\n" +
" Do you want to update this editor and lose your current changes?");
@ -435,14 +433,14 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
return;
}
}
filterModel.setFilter(newFilter);
dialogModel.setFilter(newFilter);
}
private String getStatusMessage() {
if (filterModel.isEmpty()) {
if (dialogModel.isEmpty()) {
return "Please add a filter condition!";
}
if (!filterModel.isValid()) {
if (!dialogModel.isValid()) {
return "One or more filter values are invalid!";
}
return "";
@ -473,8 +471,6 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
void filterRemoved(ColumnBasedTableFilter<R> filter) {
gTableFilterPanel.updateSavedFilters(filter, false);
filterManager.updateSavedFilters(filter, false);
}
}

View file

@ -193,6 +193,12 @@ public class ConcurrentQBuilder<I, R> {
return this;
}
/**
* Builds the final {@link ConcurrentQ}.
*
* @param callback the callback for processing each job
* @return the new queue
*/
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
ConcurrentQ<I, R> concurrentQ = new ConcurrentQ<>(callback, getQueue(), getThreadPool(),

View file

@ -17,7 +17,7 @@ package ghidra.util;
/**
* Ghidra synchronization lock. This class allows creation of named locks for
* synchroniing modification of multiple tables in the Ghidra database.
* synchronizing modification of multiple tables in the Ghidra database.
*/
public class Lock {
private Thread owner;