diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java index 05cab82a3f..4886f7e05d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableFilterPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableFilterPanel.java index 6145d6a5d8..acb1bff4c6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableFilterPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableFilterPanel.java @@ -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 extends GTableFilterPanel createTextFilterModel( - RowObjectTableModel model) { + RowObjectTableModel clientModel) { - // NOTE: order is important here, since ThreadedTableModels can also be sorted table - // models. We want to handle those first! - RowObjectFilterModel newModel = super.createTextFilterModel(model); - if (newModel instanceof ThreadedTableModel) { - // we don't wrap Threaded models, as they can do their own filtering - return newModel; + RowObjectFilterModel 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, ProgramTableModel { + /** + * A class to adapt a table model that can filter into a {@link ProgramTableModel}. + */ + private class FilterModelToProgramModelAdapter + implements RowObjectFilterModel, ProgramTableModel, WrappingTableModel { - private final RowObjectFilterModel wrappedFilterModel; - private final RowObjectFilterModel wrappedTableModel; + private final RowObjectFilterModel filterModel; + private final ProgramTableModel programModel; - private ProgramTableModelWrapperWrapper(RowObjectFilterModel tableModel, - RowObjectFilterModel filterModel) { - this.wrappedTableModel = tableModel; - this.wrappedFilterModel = filterModel; + private FilterModelToProgramModelAdapter(RowObjectFilterModel 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 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 tableFilter) { - wrappedFilterModel.setTableFilter(tableFilter); + filterModel.setTableFilter(tableFilter); } @Override public TableFilter 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 getUnfilteredData() { - List 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 tableModelWrapper = - (TableModelWrapper) 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); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java index f9f348ab6e..685c95a49a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java @@ -514,10 +514,10 @@ public class GTableFilterPanel extends JPanel { RowObjectFilterModel 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 wrapper = (TableModelWrapper) newModel; + WrappingTableModel wrapper = (WrappingTableModel) newModel; currentModel.addTableModelListener(new TranslatingTableModelListener(wrapper)); } @@ -527,7 +527,6 @@ public class GTableFilterPanel extends JPanel { return newModel; } - // Cast from ThreadedTableModel... protected RowObjectFilterModel createTextFilterModel( RowObjectTableModel model) { RowObjectFilterModel newModel = null; @@ -894,9 +893,9 @@ public class GTableFilterPanel extends JPanel { */ private class TranslatingTableModelListener implements TableModelListener { - private TableModelWrapper tableModelWrapper; + private WrappingTableModel tableModelWrapper; - TranslatingTableModelListener(TableModelWrapper tableModelWrapper) { + TranslatingTableModelListener(WrappingTableModel tableModelWrapper) { this.tableModelWrapper = tableModelWrapper; } @@ -907,7 +906,7 @@ public class GTableFilterPanel 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 extends JPanel { } isUpdatingModel = true; - if (textFilterModel instanceof TableModelWrapper) { - TableModelWrapper tableModelWrapper = - (TableModelWrapper) textFilterModel; + if (textFilterModel instanceof WrappingTableModel) { + WrappingTableModel tableModelWrapper = (WrappingTableModel) textFilterModel; tableModelWrapper.wrappedModelChangedFromTableChangedEvent(); } filterField.alert(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java index 31d862d09c..17c9693235 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java @@ -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 } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectTableModel.java index 1aa9c60b37..a86166f507 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/RowObjectTableModel.java @@ -31,8 +31,8 @@ public interface RowObjectTableModel 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; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableModelWrapper.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableModelWrapper.java index bc11815bce..5905dea89b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableModelWrapper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableModelWrapper.java @@ -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 the row object type */ public class TableModelWrapper - implements RowObjectFilterModel, SelectionStorage { + implements RowObjectFilterModel, SelectionStorage, + WrappingTableModel { + // The index in the list is the view index; the Integer value at that index is the model index protected List filteredIndexList; private TableFilter tableFilter; @@ -45,7 +50,12 @@ public class TableModelWrapper public TableModelWrapper(RowObjectTableModel 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 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 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 return accepts; } - @Override - public void fireTableDataChanged() { - wrappedModel.fireTableDataChanged(); - } - @Override public boolean isFiltered() { return filteredIndexList.size() != wrappedModel.getRowCount(); @@ -276,6 +306,7 @@ public class TableModelWrapper wrappedModel.setValueAt(value, modelRowIndex, columnIndex); } + @Override public TableModel getWrappedModel() { return wrappedModel; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/WrappingTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/WrappingTableModel.java new file mode 100644 index 0000000000..5667319125 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/WrappingTableModel.java @@ -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); +}