mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 14:11:59 +00:00
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:
parent
d648dd3ef8
commit
edc19158fd
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user