GT-3629 - Table Copying - updated tables so that the Copy action will

copy the table's cell text as it is renderered to the user
This commit is contained in:
dragonmacher 2020-07-08 15:27:00 -04:00
parent d648dd3ef8
commit edc19158fd
8 changed files with 193 additions and 92 deletions

View File

@ -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 <T> SelectionManager createSelectionManager(TableModel model) {
protected <T> SelectionManager createSelectionManager() {
return null;
}
}

View File

@ -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");

View File

@ -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();

View File

@ -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<VTMatch>) tableModel);
(AbstractSortedTableModel<VTMatch>) 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) {

View File

@ -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.
*
* <P>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.
* <P>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 {

View File

@ -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<ROW_OBJECT> 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<ROW_OBJECT> implements RowFilterTransfo
return mode == ColumnConstraintFilterMode.ALLOW_CONSTRAINTS_FILTER_ONLY;
}
private String getRenderedColumnValue(Object columnValue, int columnIndex) {
if (!(model instanceof DynamicColumnTableModel)) {
return null;
}
DynamicColumnTableModel<ROW_OBJECT> columnBasedModel =
(DynamicColumnTableModel<ROW_OBJECT>) model;
GColumnRenderer<Object> renderer = getColumnRenderer(columnBasedModel, columnIndex);
if (renderer == null) {
return null;
}
Settings settings = columnBasedModel.getColumnSettings(columnIndex);
String s = renderer.getFilterString(columnValue, settings);
return s;
}
private GColumnRenderer<Object> getColumnRenderer(
DynamicColumnTableModel<ROW_OBJECT> columnBasedModel, int columnIndex) {
DynamicTableColumn<ROW_OBJECT, ?, ?> column = columnBasedModel.getColumn(columnIndex);

View File

@ -225,7 +225,15 @@ public class GTable extends JTable {
initializeRowHeight();
selectionManager = createSelectionManager(dataModel);
selectionManager = createSelectionManager();
}
protected <T> SelectionManager createSelectionManager() {
RowObjectTableModel<Object> 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 <T> SelectionManager createSelectionManager(TableModel model) {
private <T> RowObjectTableModel<T> getRowObjectTableModel() {
TableModel model = getModel();
if (model instanceof RowObjectTableModel) {
return new RowObjectSelectionManager<>(this, (RowObjectTableModel<T>) model);
return (RowObjectTableModel<T>) 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<Object> 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;

View File

@ -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.
*
* <P>This is used to provide a means for filtering on the text that is displayed to the user.
*
* @param <ROW_OBJECT> 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 <ROW_OBJECT> String getTableCellStringValue(RowObjectTableModel<ROW_OBJECT> 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 <ROW_OBJECT> String getRenderedColumnValue(RowObjectTableModel<ROW_OBJECT> model,
Object columnValue, int columnIndex) {
if (!(model instanceof DynamicColumnTableModel)) {
return null;
}
DynamicColumnTableModel<ROW_OBJECT> columnBasedModel =
(DynamicColumnTableModel<ROW_OBJECT>) model;
GColumnRenderer<Object> 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 <ROW_OBJECT> GColumnRenderer<Object> getColumnRenderer(
DynamicColumnTableModel<ROW_OBJECT> columnBasedModel, int columnIndex) {
DynamicTableColumn<ROW_OBJECT, ?, ?> column = columnBasedModel.getColumn(columnIndex);
@SuppressWarnings("unchecked")
GColumnRenderer<Object> columnRenderer =
(GColumnRenderer<Object>) 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.