Merge remote-tracking branch 'origin/GP-1-dragonmacher-table-model-filter-fix--SQUASHED'

This commit is contained in:
Ryan Kurtz
2023-08-28 13:10:50 -04:00
7 changed files with 161 additions and 103 deletions
@@ -128,6 +128,7 @@ class MemoryMapProvider extends ComponentProviderAdapter {
table = new MemoryMapTable(tableModel);
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
table.installNavigation(tool);
table.setAutoCreateColumnsFromModel(false);
GTableCellRenderer monoRenderer = new GTableCellRenderer();
@@ -15,15 +15,14 @@
*/
package ghidra.util.table;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
@@ -49,210 +48,201 @@ public class GhidraTableFilterPanel<ROW_OBJECT> extends GTableFilterPanel<ROW_OB
super(table, tableModel, filterLabel);
}
@Override
// overridden because of our knowledge of threaded models and program table models
@Override // overridden because of our knowledge of program table models
protected RowObjectFilterModel<ROW_OBJECT> createTextFilterModel(
RowObjectTableModel<ROW_OBJECT> model) {
RowObjectTableModel<ROW_OBJECT> clientModel) {
// NOTE: order is important here, since ThreadedTableModels can also be sorted table
// models. We want to handle those first!
RowObjectFilterModel<ROW_OBJECT> newModel = super.createTextFilterModel(model);
if (newModel instanceof ThreadedTableModel<?, ?>) {
// we don't wrap Threaded models, as they can do their own filtering
return newModel;
RowObjectFilterModel<ROW_OBJECT> filterModel = super.createTextFilterModel(clientModel);
if (!(clientModel instanceof ProgramTableModel)) {
return filterModel; // nothing to do
}
// Next, see if we need to create a wrapper to handle ProgramTableModel implementations
if (!(model instanceof ProgramTableModel)) {
return newModel; // nope, the given model is not a ProgramTableModel; no new
if (filterModel instanceof ProgramTableModel) {
// the model is already filterable and a ProgramTableModel (e.g., a ThreadedTableModel)
return filterModel;
}
return new ProgramTableModelWrapperWrapper(newModel, newModel);
return new FilterModelToProgramModelAdapter(filterModel, (ProgramTableModel) clientModel);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class ProgramTableModelWrapperWrapper
implements RowObjectFilterModel<ROW_OBJECT>, ProgramTableModel {
/**
* A class to adapt a table model that can filter into a {@link ProgramTableModel}.
*/
private class FilterModelToProgramModelAdapter
implements RowObjectFilterModel<ROW_OBJECT>, ProgramTableModel, WrappingTableModel {
private final RowObjectFilterModel<ROW_OBJECT> wrappedFilterModel;
private final RowObjectFilterModel<ROW_OBJECT> wrappedTableModel;
private final RowObjectFilterModel<ROW_OBJECT> filterModel;
private final ProgramTableModel programModel;
private ProgramTableModelWrapperWrapper(RowObjectFilterModel<ROW_OBJECT> tableModel,
RowObjectFilterModel<ROW_OBJECT> filterModel) {
this.wrappedTableModel = tableModel;
this.wrappedFilterModel = filterModel;
private FilterModelToProgramModelAdapter(RowObjectFilterModel<ROW_OBJECT> tableModel,
ProgramTableModel programModel) {
this.filterModel = tableModel;
this.programModel = programModel;
}
@Override
public String getName() {
return wrappedTableModel.getName();
return filterModel.getName();
}
@Override
public void fireTableChanged(TableModelEvent event) {
if (filterModel instanceof WrappingTableModel) {
((WrappingTableModel) filterModel).fireTableChanged(event);
}
}
@Override
public void wrappedModelChangedFromTableChangedEvent() {
if (filterModel instanceof WrappingTableModel) {
((WrappingTableModel) filterModel).wrappedModelChangedFromTableChangedEvent();
}
}
@Override
public ROW_OBJECT getRowObject(int row) {
return wrappedTableModel.getRowObject(row);
return filterModel.getRowObject(row);
}
@Override
public List<ROW_OBJECT> getModelData() {
return wrappedTableModel.getModelData();
return filterModel.getModelData();
}
@Override
public int getModelRow(int viewRow) {
return wrappedFilterModel.getModelRow(viewRow);
return filterModel.getModelRow(viewRow);
}
@Override
public int getRowIndex(ROW_OBJECT t) {
return wrappedFilterModel.getRowIndex(t);
return filterModel.getRowIndex(t);
}
@Override
public int getViewIndex(ROW_OBJECT t) {
return wrappedFilterModel.getViewIndex(t);
return filterModel.getViewIndex(t);
}
@Override
public int getModelIndex(ROW_OBJECT t) {
return wrappedFilterModel.getModelIndex(t);
return filterModel.getModelIndex(t);
}
@Override
public int getUnfilteredRowCount() {
return wrappedFilterModel.getUnfilteredRowCount();
return filterModel.getUnfilteredRowCount();
}
@Override
public int getViewRow(int modelRow) {
return wrappedFilterModel.getViewRow(modelRow);
return filterModel.getViewRow(modelRow);
}
@Override
public boolean isFiltered() {
return wrappedFilterModel.isFiltered();
return filterModel.isFiltered();
}
@Override
public void setTableFilter(TableFilter<ROW_OBJECT> tableFilter) {
wrappedFilterModel.setTableFilter(tableFilter);
filterModel.setTableFilter(tableFilter);
}
@Override
public TableFilter<ROW_OBJECT> getTableFilter() {
return wrappedFilterModel.getTableFilter();
return filterModel.getTableFilter();
}
@Override
public void fireTableDataChanged() {
wrappedTableModel.fireTableDataChanged();
filterModel.fireTableDataChanged();
}
@Override
public void addTableModelListener(TableModelListener l) {
wrappedTableModel.addTableModelListener(l);
filterModel.addTableModelListener(l);
}
@Override
public void removeTableModelListener(TableModelListener l) {
wrappedTableModel.removeTableModelListener(l);
filterModel.removeTableModelListener(l);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return wrappedTableModel.getColumnClass(columnIndex);
return filterModel.getColumnClass(columnIndex);
}
@Override
public int getColumnCount() {
return wrappedTableModel.getColumnCount();
return filterModel.getColumnCount();
}
@Override
public String getColumnName(int columnIndex) {
return wrappedTableModel.getColumnName(columnIndex);
return filterModel.getColumnName(columnIndex);
}
@Override
public int getRowCount() {
return wrappedTableModel.getRowCount();
return filterModel.getRowCount();
}
@Override
public List<ROW_OBJECT> getUnfilteredData() {
List<ROW_OBJECT> list = new ArrayList<>();
int rowCount = getUnfilteredRowCount();
for (int i = 0; i < rowCount; i++) {
list.add(wrappedTableModel.getRowObject(i));
}
return list;
return filterModel.getUnfilteredData();
}
@Override
public Object getColumnValueForRow(ROW_OBJECT t, int columnIndex) {
return wrappedTableModel.getColumnValueForRow(t, columnIndex);
return filterModel.getColumnValueForRow(t, columnIndex);
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return wrappedTableModel.getValueAt(rowIndex, columnIndex);
return filterModel.getValueAt(rowIndex, columnIndex);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return wrappedTableModel.isCellEditable(rowIndex, columnIndex);
return filterModel.isCellEditable(rowIndex, columnIndex);
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
wrappedTableModel.setValueAt(value, rowIndex, columnIndex);
filterModel.setValueAt(value, rowIndex, columnIndex);
}
private TableModel getBaseTableModel() {
if (wrappedTableModel instanceof TableModelWrapper<?>) {
TableModelWrapper<ROW_OBJECT> tableModelWrapper =
(TableModelWrapper<ROW_OBJECT>) wrappedTableModel;
return tableModelWrapper.getWrappedModel();
}
return wrappedTableModel;
}
private ProgramTableModel getProgramTableModel() {
TableModel baseTableModel = getBaseTableModel();
if (baseTableModel instanceof ProgramTableModel) {
return (ProgramTableModel) baseTableModel;
}
return null;
@Override
public TableModel getWrappedModel() {
return filterModel;
}
@Override
public Program getProgram() {
ProgramTableModel programTableModel = getProgramTableModel();
if (programTableModel != null) {
return programTableModel.getProgram();
}
return null;
return programModel.getProgram();
}
@Override
public ProgramLocation getProgramLocation(int row, int column) {
ProgramTableModel programTableModel = getProgramTableModel();
if (programTableModel != null) {
return programTableModel.getProgramLocation(row, column);
}
return null;
// Note: the given row is the filtered row index
int unfilteredRow = getModelRow(row);
return programModel.getProgramLocation(unfilteredRow, column);
}
@Override
public ProgramSelection getProgramSelection(int[] rows) {
ProgramTableModel programTableModel = getProgramTableModel();
if (programTableModel != null) {
return programTableModel.getProgramSelection(rows);
// Note: the given rows are the filtered row indexes
int[] unfilteredRows = new int[rows.length];
for (int i = 0; i < rows.length; i++) {
unfilteredRows[i] = getModelRow(rows[i]);
}
return null;
return programModel.getProgramSelection(unfilteredRows);
}
}
@@ -514,10 +514,10 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
RowObjectFilterModel<ROW_OBJECT> newModel = createTextFilterModel(currentModel);
// only wrapped models are set on tables, since they have to replace the original
if (newModel instanceof TableModelWrapper) {
if (newModel instanceof WrappingTableModel) {
table.setModel(newModel);
TableModelWrapper<ROW_OBJECT> wrapper = (TableModelWrapper<ROW_OBJECT>) newModel;
WrappingTableModel wrapper = (WrappingTableModel) newModel;
currentModel.addTableModelListener(new TranslatingTableModelListener(wrapper));
}
@@ -527,7 +527,6 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return newModel;
}
// Cast from ThreadedTableModel...
protected RowObjectFilterModel<ROW_OBJECT> createTextFilterModel(
RowObjectTableModel<ROW_OBJECT> model) {
RowObjectFilterModel<ROW_OBJECT> newModel = null;
@@ -894,9 +893,9 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
*/
private class TranslatingTableModelListener implements TableModelListener {
private TableModelWrapper<ROW_OBJECT> tableModelWrapper;
private WrappingTableModel tableModelWrapper;
TranslatingTableModelListener(TableModelWrapper<ROW_OBJECT> tableModelWrapper) {
TranslatingTableModelListener(WrappingTableModel tableModelWrapper) {
this.tableModelWrapper = tableModelWrapper;
}
@@ -907,7 +906,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
// so that the indices used in the event are correct for the filtered state of the
// view.
//
tableModelWrapper.fireTableDataChanged(translateEventForFilter(e));
tableModelWrapper.fireTableChanged(translateEventForFilter(e));
}
private TableModelEvent translateEventForFilter(TableModelEvent event) {
@@ -948,9 +947,8 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
isUpdatingModel = true;
if (textFilterModel instanceof TableModelWrapper) {
TableModelWrapper<ROW_OBJECT> tableModelWrapper =
(TableModelWrapper<ROW_OBJECT>) textFilterModel;
if (textFilterModel instanceof WrappingTableModel) {
WrappingTableModel tableModelWrapper = (WrappingTableModel) textFilterModel;
tableModelWrapper.wrappedModelChangedFromTableChangedEvent();
}
filterField.alert();
@@ -258,10 +258,6 @@ public final class GTableToCSV {
RowObjectFilterModel<?> threadedModel = (RowObjectFilterModel<?>) model;
return threadedModel.getModelRow(viewRow);
}
else if (model instanceof TableModelWrapper) {
TableModelWrapper<?> wrapper = (TableModelWrapper<?>) model;
return wrapper.getModelRow(viewRow);
}
return viewRow; // assume no filtering, as we don't know how to handle it anyway
}
@@ -31,8 +31,8 @@ public interface RowObjectTableModel<T> extends TableModel {
public static TableModel unwrap(TableModel m) {
TableModel model = m;
while (model instanceof TableModelWrapper) {
model = ((TableModelWrapper<?>) model).getWrappedModel();
while (model instanceof WrappingTableModel) {
model = ((WrappingTableModel) model).getWrappedModel();
}
return model;
}
@@ -20,8 +20,11 @@ import java.util.List;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@@ -32,8 +35,10 @@ import ghidra.util.datastruct.WeakSet;
* @param <ROW_OBJECT> the row object type
*/
public class TableModelWrapper<ROW_OBJECT>
implements RowObjectFilterModel<ROW_OBJECT>, SelectionStorage<ROW_OBJECT> {
implements RowObjectFilterModel<ROW_OBJECT>, SelectionStorage<ROW_OBJECT>,
WrappingTableModel {
// The index in the list is the view index; the Integer value at that index is the model index
protected List<Integer> filteredIndexList;
private TableFilter<ROW_OBJECT> tableFilter;
@@ -45,7 +50,12 @@ public class TableModelWrapper<ROW_OBJECT>
public TableModelWrapper(RowObjectTableModel<ROW_OBJECT> wrappedModel) {
this.wrappedModel = wrappedModel;
SystemUtilities.assertTrue(!(wrappedModel instanceof WrappingTableModel),
"Attempted to wrap a table model that has already been wrapped");
filteredIndexList = getMatchingFilteredIndices();
copyListeners();
}
@Override
@@ -96,13 +106,23 @@ public class TableModelWrapper<ROW_OBJECT>
return -1; // asking for a row that has been filtered out
}
@Override
public void wrappedModelChangedFromTableChangedEvent() {
// note: don't call notifyTableModelChanged(), as we get this callback during a
// tableChanged() event and we do not want to trigger another event.
updateFilterIndices();
}
public void fireTableDataChanged(TableModelEvent event) {
@Override
public void fireTableChanged(TableModelEvent event) {
for (TableModelListener listener : listeners) {
listener.tableChanged(event);
}
}
@Override
public void fireTableDataChanged() {
TableModelEvent event = new TableModelEvent(this);
for (TableModelListener listener : listeners) {
listener.tableChanged(event);
}
@@ -115,6 +135,21 @@ public class TableModelWrapper<ROW_OBJECT>
fireTableDataChanged();
}
private void copyListeners() {
if (!(wrappedModel instanceof AbstractTableModel)) {
Msg.warn(this, "Wrapping a table model that does not give access to listeners. You " +
"should change your base table model implement a known class");
return;
}
AbstractTableModel abstractModel = (AbstractTableModel) wrappedModel;
TableModelListener[] wrappedListeners =
abstractModel.getListeners(TableModelListener.class);
for (TableModelListener listener : wrappedListeners) {
addTableModelListener(listener);
}
}
private void updateFilterIndices() {
filteredIndexList = getMatchingFilteredIndices();
}
@@ -141,11 +176,6 @@ public class TableModelWrapper<ROW_OBJECT>
return accepts;
}
@Override
public void fireTableDataChanged() {
wrappedModel.fireTableDataChanged();
}
@Override
public boolean isFiltered() {
return filteredIndexList.size() != wrappedModel.getRowCount();
@@ -276,6 +306,7 @@ public class TableModelWrapper<ROW_OBJECT>
wrappedModel.setValueAt(value, modelRowIndex, columnIndex);
}
@Override
public TableModel getWrappedModel() {
return wrappedModel;
}
@@ -0,0 +1,42 @@
/* ###
* 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;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableModel;
/**
* Signals that the implementing table model is wrapping another table model.
*/
public interface WrappingTableModel extends TableModel {
/**
* Returns the wrapped model
* @return the model
*/
public TableModel getWrappedModel();
/**
* Allows this wrapping model to get update notifications directly from the filtering framework
*/
public void wrappedModelChangedFromTableChangedEvent();
/**
* This method allows us to call the delegate model with a translated event
* @param e the event
*/
public void fireTableChanged(TableModelEvent e);
}