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 e3af5d4c9e..42f5a24804 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 @@ -635,10 +635,7 @@ class MemoryMapProvider extends ComponentProviderAdapter { memManager.mergeBlocks(blocks); } - /** - * @param cursor - */ - public void setCursor(Cursor cursor) { + void setCursor(Cursor cursor) { tool.getToolFrame().setCursor(cursor); } @@ -657,9 +654,9 @@ class MemoryMapProvider extends ComponentProviderAdapter { return plugin.getTool(); } - // ================================================================================================== - // Inner Classes - // ================================================================================================== +// ================================================================================================== +// Inner Classes +// ================================================================================================== private class MemoryMapTable extends GhidraTable { MemoryMapTable(TableModel model) { @@ -670,7 +667,7 @@ class MemoryMapProvider extends ComponentProviderAdapter { } @Override - protected SelectionManager createSelectionManager(TableModel model) { + protected SelectionManager createSelectionManager() { return null; } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java index 3632d94575..33f0b32182 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPluginTest.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.functionwindow; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import javax.swing.*; @@ -24,6 +25,8 @@ import javax.swing.table.*; import org.junit.*; import docking.ComponentProvider; +import docking.action.DockingActionIf; +import docking.tool.ToolConstants; import docking.widgets.combobox.GComboBox; import docking.widgets.dialogs.SettingsDialog; import docking.widgets.table.GTable; @@ -134,6 +137,46 @@ public class FunctionWindowPluginTest extends AbstractGhidraHeadedIntegrationTes assertNotEquals("Changing the format did not change the view", startValue, endValue); } + @Test + public void testCopyingFunctionSignature() throws Exception { + + int row = 0; + int column = getColumnIndex("Function Signature"); + select(row); + + String signatureText = getRenderedTableCellValue(functionTable, row, column); + + DockingActionIf copyAction = getAction(tool, ToolConstants.SHARED_OWNER, "Table Data Copy"); + performAction(copyAction); + + // + // Note: we cannot make this call: + // String clipboardText = getClipboardText(); + // + // The copy action of the table uses Java's built-in copy code. That code uses the system + // clipboard, which we cannot rely on in a testing environment. So, we will just call + // the code under test directly. + // + + // flag to trigger copy code + setInstanceField("copying", functionTable, Boolean.TRUE); + String copyText = getCopyText(row, column); + assertThat(copyText, containsString(signatureText)); + } + + private String getCopyText(int row, int column) { + Object value = runSwing(() -> functionTable.getValueAt(row, column)); + assertNotNull(value); + return value.toString(); + } + + private void select(int row) { + runSwing(() -> { + functionTable.clearSelection(); + functionTable.addRowSelectionInterval(row, row); + }); + } + private int getFormatRow(SettingsDialog dialog) { GTable table = dialog.getTable(); int column = getColumnIndex(table, "Name"); diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java index fd0af22763..2e4e237214 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java @@ -31,6 +31,7 @@ import org.junit.*; import docking.ActionContext; import docking.ComponentProvider; import docking.action.DockingAction; +import docking.dnd.GClipboard; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.visualization.VisualizationModel; import edu.uci.ics.jung.visualization.VisualizationViewer; @@ -393,7 +394,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { // // Initialize the clipboard with known data // - Clipboard systemClipboard = tool.getToolFrame().getToolkit().getSystemClipboard(); + Clipboard systemClipboard = GClipboard.getSystemClipboard(); systemClipboard.setContents(DUMMY_TRANSFERABLE, null); waitForSwing(); @@ -446,7 +447,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { // // Initialize the clipboard with known data // - Clipboard systemClipboard = tool.getToolFrame().getToolkit().getSystemClipboard(); + Clipboard systemClipboard = GClipboard.getSystemClipboard(); systemClipboard.setContents(DUMMY_TRANSFERABLE, null); waitForSwing(); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java index fd37aa2a8d..c10fdc572a 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java @@ -16,8 +16,7 @@ package ghidra.feature.vt.gui.provider.matchtable; import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*; -import static ghidra.feature.vt.gui.plugin.VTPlugin.FILTERED_ICON; -import static ghidra.feature.vt.gui.plugin.VTPlugin.UNFILTERED_ICON; +import static ghidra.feature.vt.gui.plugin.VTPlugin.*; import static ghidra.feature.vt.gui.util.VTOptionDefines.*; import java.awt.*; @@ -695,25 +694,27 @@ public class VTMatchTableProvider extends ComponentProviderAdapter "Markup items that are incomplete (for example, no destination address is specified) " + "should become ignored by applying a match."); - vtOptions.getOptions(APPLY_MARKUP_OPTIONS_NAME).registerOptionsEditor( - new ApplyMarkupPropertyEditor(controller)); - vtOptions.getOptions(DISPLAY_APPLY_MARKUP_OPTIONS).setOptionsHelpLocation( - new HelpLocation("VersionTracking", "Apply Markup Options")); + vtOptions.getOptions(APPLY_MARKUP_OPTIONS_NAME) + .registerOptionsEditor( + new ApplyMarkupPropertyEditor(controller)); + vtOptions.getOptions(DISPLAY_APPLY_MARKUP_OPTIONS) + .setOptionsHelpLocation( + new HelpLocation("VersionTracking", "Apply Markup Options")); HelpLocation applyOptionsHelpLocation = new HelpLocation(VTPlugin.HELP_TOPIC_NAME, "Version_Tracking_Apply_Options"); - HelpLocation acceptMatchOptionsHelpLocation = - new HelpLocation(VTPlugin.HELP_TOPIC_NAME, "Match_Accept_Options"); HelpLocation applyMatchOptionsHelpLocation = new HelpLocation(VTPlugin.HELP_TOPIC_NAME, "Match_Apply_Options"); vtOptions.setOptionsHelpLocation(applyOptionsHelpLocation); - vtOptions.getOptions(ACCEPT_MATCH_OPTIONS_NAME).setOptionsHelpLocation( - applyMatchOptionsHelpLocation); + vtOptions.getOptions(ACCEPT_MATCH_OPTIONS_NAME) + .setOptionsHelpLocation( + applyMatchOptionsHelpLocation); - vtOptions.getOptions(APPLY_MARKUP_OPTIONS_NAME).setOptionsHelpLocation( - applyMatchOptionsHelpLocation); + vtOptions.getOptions(APPLY_MARKUP_OPTIONS_NAME) + .setOptionsHelpLocation( + applyMatchOptionsHelpLocation); } //================================================================================================== @@ -783,9 +784,9 @@ public class VTMatchTableProvider extends ComponentProviderAdapter @SuppressWarnings("unchecked") // this is our table model--we know its real type @Override - protected SelectionManager createSelectionManager(TableModel tableModel) { + protected SelectionManager createSelectionManager() { return new VTMatchTableSelectionManager(this, - (AbstractSortedTableModel) tableModel); + (AbstractSortedTableModel) getModel()); } } } @@ -810,7 +811,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter private final int row; private final VTMatch match; - /** + /* * (see the class header for details) {@link SelectionOverrideMemento} */ SelectionOverrideMemento(int row, VTMatch match) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/dnd/GClipboard.java b/Ghidra/Framework/Docking/src/main/java/docking/dnd/GClipboard.java index 878a1eb4c4..4d3757bcf7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/dnd/GClipboard.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/dnd/GClipboard.java @@ -22,9 +22,10 @@ import java.awt.datatransfer.Clipboard; * Provides a place for clients to retrieve the Clipboard they should be using. This class * provides a level of indirection that allows us to inject clipboards as needed. * - *

Note: if a test needs to check the contents of a native Java action, which will use the - * system clipboard, then that cannot rely on the contents of the system clipboard. That test - * will have to use some other mechanism to know that the native action was executed. + *

Note: if a test needs to check the contents of the native clipboard, such as after + * executing a native Java action that uses the system clipboard, then that test must use some + * other mechanism to know that the native action was executed. This is due to the fact that + * the system clipboard is potentially used by multiple Java test processes at once. */ public class GClipboard { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java index 4ee704490c..dcf2a7203c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DefaultRowFilterTransformer.java @@ -17,10 +17,8 @@ package docking.widgets.table; import java.util.*; -import javax.swing.JLabel; import javax.swing.table.TableColumnModel; -import ghidra.docking.settings.Settings; import ghidra.util.table.column.GColumnRenderer; import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode; @@ -63,44 +61,7 @@ public class DefaultRowFilterTransformer implements RowFilterTransfo return null; } - // note: this call can be slow when columns dynamically calculate values from the database - Object value = model.getColumnValueForRow(rowObject, column); - if (value == null) { - return null; - } - - /* - Methods for turning the cell value into a string to be filtered (in preference order): - 1) Use the dynamic column's renderer (if applicable), as this is the most - direct way for clients to specify the filter value - 2) See if the value is an instance of DisplayStringProvider, which describes how - it should be rendered - 3) See if it is a label (this is uncommon) - 4) Rely on the toString(); this works as intended for Strings. This is the - default way that built-in table cell renderers will generate display text - */ - - // 1) - String renderedString = getRenderedColumnValue(value, column); - if (renderedString != null) { - return renderedString; - } - - // 2) special plug-in point where clients can specify a value object that can return - // its display string - if (value instanceof DisplayStringProvider) { - return ((DisplayStringProvider) value).toString(); - } - - // 3 - if (value instanceof JLabel) { // some models do this odd thing - JLabel label = (JLabel) value; - String valueString = label.getText(); - return valueString == null ? "" : valueString; - } - - // 4) - return value.toString(); + return TableUtils.getTableCellStringValue(model, rowObject, column); } private boolean columnUsesConstraintFilteringOnly(int column) { @@ -119,24 +80,6 @@ public class DefaultRowFilterTransformer implements RowFilterTransfo return mode == ColumnConstraintFilterMode.ALLOW_CONSTRAINTS_FILTER_ONLY; } - private String getRenderedColumnValue(Object columnValue, int columnIndex) { - - if (!(model instanceof DynamicColumnTableModel)) { - return null; - } - - DynamicColumnTableModel columnBasedModel = - (DynamicColumnTableModel) model; - GColumnRenderer renderer = getColumnRenderer(columnBasedModel, columnIndex); - if (renderer == null) { - return null; - } - - Settings settings = columnBasedModel.getColumnSettings(columnIndex); - String s = renderer.getFilterString(columnValue, settings); - return s; - } - private GColumnRenderer getColumnRenderer( DynamicColumnTableModel columnBasedModel, int columnIndex) { DynamicTableColumn column = columnBasedModel.getColumn(columnIndex); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java index 77b8cc83e0..993a20b740 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java @@ -225,7 +225,15 @@ public class GTable extends JTable { initializeRowHeight(); - selectionManager = createSelectionManager(dataModel); + selectionManager = createSelectionManager(); + } + + protected SelectionManager createSelectionManager() { + RowObjectTableModel rowModel = getRowObjectTableModel(); + if (rowModel != null) { + return new RowObjectSelectionManager<>(this, rowModel); + } + return null; } @SuppressWarnings("unchecked") @@ -233,9 +241,10 @@ public class GTable extends JTable { // an arbitrary type T defined here. So, T doesn't really exist and therefore the cast isn't // really casting to anything. The SelectionManager will take on the type of the given model. // The T is just there on the SelectionManager to make its internal methods consistent. - protected SelectionManager createSelectionManager(TableModel model) { + private RowObjectTableModel getRowObjectTableModel() { + TableModel model = getModel(); if (model instanceof RowObjectTableModel) { - return new RowObjectSelectionManager<>(this, (RowObjectTableModel) model); + return (RowObjectTableModel) model; } return null; @@ -994,16 +1003,27 @@ public class GTable extends JTable { */ @Override public Object getValueAt(int row, int column) { - Object value = super.getValueAt(row, column); - if (!copying) { - return value; + return super.getValueAt(row, column); } + Object value = getCellValue(row, column); Object updated = maybeConvertValue(value); return updated; } + private Object getCellValue(int row, int column) { + RowObjectTableModel rowModel = getRowObjectTableModel(); + if (rowModel == null) { + Object value = super.getValueAt(row, column); + return maybeConvertValue(value); + } + + Object rowObject = rowModel.getRowObject(row); + String stringValue = TableUtils.getTableCellStringValue(rowModel, rowObject, column); + return maybeConvertValue(stringValue); + } + private Object maybeConvertValue(Object value) { if (value == null) { return null; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java index c1e4358d13..c4d3373d46 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java @@ -15,14 +15,109 @@ */ package docking.widgets.table; +import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.table.*; +import ghidra.docking.settings.Settings; +import ghidra.util.table.column.GColumnRenderer; +import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode; + /** * A utility class for JTables used in Ghidra. */ public class TableUtils { + /** + * Uses the given row-based table model, row object and column index to determine what the + * String value should be for that cell. + * + *

This is used to provide a means for filtering on the text that is displayed to the user. + * + * @param The model's row object type + * @param model the model + * @param rowObject the row object for the row being queried + * @param column the column index + * @return the string value; null if no value can be fabricated + */ + public static String getTableCellStringValue(RowObjectTableModel model, + ROW_OBJECT rowObject, + int column) { + + // note: this call can be slow when columns dynamically calculate values from the database + Object value = model.getColumnValueForRow(rowObject, column); + if (value == null) { + return null; + } + + /* + Methods for turning the cell value into the display value (in preference order): + 1) Use the dynamic column's renderer (if applicable), as this is the most + direct way for clients to specify the display value + 2) See if the value is an instance of DisplayStringProvider, which describes how + it should be rendered + 3) See if it is a label (this is uncommon) + 4) Rely on the toString(); this works as intended for Strings. This is the + default way that built-in table cell renderers will generate display text + */ + + // 1) + String renderedString = getRenderedColumnValue(model, value, column); + if (renderedString != null) { + return renderedString; + } + + // 2) special plug-in point where clients can specify a value object that can return + // its display string + if (value instanceof DisplayStringProvider) { + return ((DisplayStringProvider) value).toString(); + } + + // 3 + if (value instanceof JLabel) { // some models do this odd thing + JLabel label = (JLabel) value; + String valueString = label.getText(); + return valueString == null ? "" : valueString; + } + + // 4) + return value.toString(); + } + + private static String getRenderedColumnValue(RowObjectTableModel model, + Object columnValue, int columnIndex) { + + if (!(model instanceof DynamicColumnTableModel)) { + return null; + } + + DynamicColumnTableModel columnBasedModel = + (DynamicColumnTableModel) model; + GColumnRenderer renderer = getColumnRenderer(columnBasedModel, columnIndex); + if (renderer == null) { + return null; + } + + ColumnConstraintFilterMode mode = renderer.getColumnConstraintFilterMode(); + if (mode == ColumnConstraintFilterMode.ALLOW_CONSTRAINTS_FILTER_ONLY) { + // this is a renderer that does not know how to create its own display string + return null; + } + + Settings settings = columnBasedModel.getColumnSettings(columnIndex); + String s = renderer.getFilterString(columnValue, settings); + return s; + } + + private static GColumnRenderer getColumnRenderer( + DynamicColumnTableModel columnBasedModel, int columnIndex) { + DynamicTableColumn column = columnBasedModel.getColumn(columnIndex); + @SuppressWarnings("unchecked") + GColumnRenderer columnRenderer = + (GColumnRenderer) column.getColumnRenderer(); + return columnRenderer; + } + /** * Attempts to sort the given table based upon the given column index. If the {@link TableModel} * of the given table is not a {@link SortedTableModel}, then this method will do nothing.