GP-4574 - Added support to create transient symbol tables from the

Symbol Tree, Symbol Table and other transient symbol tables
This commit is contained in:
dragonmacher
2024-05-21 10:34:10 -04:00
parent 9911db9828
commit 2ffb82df82
49 changed files with 2152 additions and 1594 deletions

View File

@@ -563,6 +563,7 @@ src/main/help/help/topics/SymbolTablePlugin/images/Refs_To.png||GHIDRA||||END|
src/main/help/help/topics/SymbolTablePlugin/images/Symbol_Table.png||GHIDRA||||END|
src/main/help/help/topics/SymbolTablePlugin/symbol_references.htm||GHIDRA||||END|
src/main/help/help/topics/SymbolTablePlugin/symbol_table.htm||GHIDRA||||END|
src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/images/CreateExternalLocation.png||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/images/EditExternalLocation.png||GHIDRA||||END|

View File

@@ -175,6 +175,7 @@
<tocdef id="Symbol Table" text="Symbol Table" target="help/topics/SymbolTablePlugin/symbol_table.htm">
<tocdef id="Symbol References" sortgroup="a" text="Symbol References" target="help/topics/SymbolTablePlugin/symbol_references.htm" />
<tocdef id="Symbol Tree" sortgroup="b" text="Symbol Tree" target="help/topics/SymbolTreePlugin/SymbolTree.htm" />
<tocdef id="Temporary Symbol Table" sortgroup="c" text="Temporary Symbol Table" target="help/topics/SymbolTablePlugin/symbol_table_transient.htm" />
</tocdef>
<tocdef id="Relocation Table" text="Relocation Table" target="help/topics/RelocationTablePlugin/relocation_table.htm" />

View File

@@ -0,0 +1,71 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Temporary Symbol Table</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY>
<H1><A name="Temporary_Symbol_Table"></A>Temporary Symbol Table</H1>
<P>The Temporary Symbol Table is a table created by providers that have the <B>Create Table</B>
action, such as the <A href="help/topics/SymbolTreePlugin/SymbolTree.htm">Symbol Tree</A> or
the <A href="help/topics/SymbolTablePlugin/symbol_table.htm">Symbol Table</A>. It contains the
same columns as the Symbol Table.
</P>
<P>This table functions much like the <A HREF="help/topics/Search/Query_Results_Dialog.htm">
Search Results Table</A>. For example, this table supports the ability to remove rows.
</P>
<H2>Drag and Drop</H2>
<BLOCKQUOTE>
<P>This table supports the dragging of symbols into the table. This allows you to add to
the table as you are using the tool. The source of a drag can be a provider that knows how
to drag symbols, such as the Symbol Tree, the Symbol Table and other temporary symbol tables.
</P>
</BLOCKQUOTE>
<H2>Actions</H2>
<BLOCKQUOTE>
<P>
This table supports the general actions of the
<A href="help/topics/Search/Query_Results_Dialog.htm">Search Results Window</A>.
</P>
<H3>Create Table From Selection</H3>
<BLOCKQUOTE>
<P>
This table supports the ability to create a new table with the selected rows.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P class="relatedtopic">Related Topics:</P>
<BLOCKQUOTE>
<UL>
<LI>
<A href="help/topics/Search/Query_Results_Dialog.htm">Search Results Window</A>
</LI>
<LI>
<A href="help/topics/SymbolTablePlugin/symbol_table.htm">Symbol Table</A>
</LI>
<LI>
<A href="help/topics/SymbolTreePlugin/SymbolTree.htm">Symbol Tree</A>
</LI>
</UL>
</BLOCKQUOTE>
<BR>
<BR>
<BR>
</BODY>
</HTML>

View File

@@ -361,6 +361,17 @@
</BLOCKQUOTE>
<H2><A NAME="Create_Table"></A>Create Table From Selection</H2>
<BLOCKQUOTE>
<P>Create a new temporary
<A HREF="help/topics/SymbolTablePlugin/symbol_table_transient.htm">Symbol Table</A> with the symbols
in the current selection. The table's features are similar to the
<A HREF="help/topics/Search/Query_Results_Dialog.htm">search results table.</A>
</P>
</BLOCKQUOTE>
<H2>Symbol Tree Snapshots</H2>

View File

@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +17,7 @@ package ghidra.app.context;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.KeyBindingType;
public abstract class ProgramSymbolContextAction extends DockingAction {
@@ -25,6 +25,10 @@ public abstract class ProgramSymbolContextAction extends DockingAction {
super(name, owner);
}
public ProgramSymbolContextAction(String name, String owner, KeyBindingType kbType) {
super(name, owner, kbType);
}
@Override
public final boolean isEnabledForContext(ActionContext actionContext) {
if (!(actionContext instanceof ProgramSymbolActionContext)) {

View File

@@ -26,7 +26,6 @@ import docking.ActionContext;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
@@ -148,10 +147,6 @@ class CommentWindowProvider extends ComponentProviderAdapter {
tool.contextChanged(this);
}
ProgramSelection selectComment() {
return commentTable.getProgramSelection();
}
void reload() {
if (isVisible()) {
commentModel.reload(plugin.getProgram());

View File

@@ -27,7 +27,6 @@ import generic.theme.GIcon;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
@@ -153,10 +152,6 @@ class DataWindowProvider extends ComponentProviderAdapter {
tool.contextChanged(this);
}
ProgramSelection selectData() {
return dataTable.getProgramSelection();
}
private void setDataTableRenderer() {
dataTable.getColumnModel()
.getColumn(DataTableModel.LOCATION_COL)

View File

@@ -30,7 +30,6 @@ import generic.theme.GIcon;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
@@ -156,10 +155,6 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
return container;
}
ProgramSelection selectFunctions() {
return functionTable.getProgramSelection();
}
private void setFunctionTableRenderer() {
TableColumnModel columnModel = functionTable.getColumnModel();
TableColumn column = columnModel.getColumn(FunctionTableModel.LOCATION_COL);

View File

@@ -26,7 +26,6 @@ import javax.swing.event.TableModelListener;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramSelection;
import ghidra.util.table.*;
/**
@@ -73,10 +72,6 @@ public class LocationReferencesPanel extends JPanel {
return table;
}
ProgramSelection getSelection() {
return table.getProgramSelection();
}
/**
* Causes the model to update the table. This differs from {@link #reloadModel()} in that if
* there is data cached, then this call will use that cached data.

View File

@@ -117,8 +117,7 @@ public class ScalarSearchProvider extends ComponentProviderAdapter {
buffy.append(" [filter: ")
.append(minValueText)
.append(" - ")
.append(
maxValueText)
.append(maxValueText)
.append(']');
}
@@ -181,10 +180,6 @@ public class ScalarSearchProvider extends ComponentProviderAdapter {
filter.dispose();
}
ProgramSelection getSelection() {
return scalarTable.getProgramSelection();
}
void reload() {
if (isVisible()) {
scalarModel.reload();

View File

@@ -1,114 +0,0 @@
/* ###
* 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 ghidra.app.plugin.core.script;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.util.ArrayList;
import javax.swing.table.TableModel;
import docking.dnd.*;
import generic.jar.ResourceFile;
import ghidra.util.table.GhidraTable;
public class DraggableScriptTable extends GhidraTable implements Draggable {
private DragSrcAdapter dragSourceAdapter;
private DragGestureAdapter dragGestureAdapter;
private DragSource dragSource;
private final GhidraScriptComponentProvider provider;
/**
* Constructs a new DraggableGhidraTable.
* @param provider the provider, from which getTableModel and getScriptAt are used
* @param model provider's table model
*/
public DraggableScriptTable(GhidraScriptComponentProvider provider, TableModel model) {
super(model);
this.provider = provider;
initDragNDrop();
}
private void initDragNDrop() {
// set up drag stuff
dragSource = DragSource.getDefaultDragSource();
dragGestureAdapter = new DragGestureAdapter(this);
dragSourceAdapter = new DragSrcAdapter(this);
dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE,
dragGestureAdapter);
}
/**
* Return true if the location in the event is draggable.
*/
@Override
public boolean isStartDragOk(DragGestureEvent e) {
return true;
}
/**
* Called by the DragGestureAdapter to start the drag.
*/
@Override
public DragSourceListener getDragSourceListener() {
return dragSourceAdapter;
}
/**
* Called by the DragGestureAdapter and the DragSourceAdapter to
* know what actions this component allows.
*/
@Override
public int getDragAction() {
return DnDConstants.ACTION_COPY_OR_MOVE;
}
/**
* Called by the DragGestureAdapter when the drag is about to
* start.
*/
@Override
public Transferable getTransferable(Point p) {
ArrayList<ResourceFile> arrayList = new ArrayList<>();
int[] selectedRows = getSelectedRows();
for (int element : selectedRows) {
arrayList.add(provider.getScriptAt(element));
}
return new GhidraTransferable(arrayList);
}
/**
* Do the move operation. Called from the DragSourceAdapter
* when the drop completes and the user action was a
* DnDConstants.MOVE.
*/
@Override
public void move() {
//
}
/**
* Called from the DragSourceAdapter when the drag operation exits the
* drop target without dropping.
*/
@Override
public void dragCanceled(DragSourceDropEvent event) {
//
}
}

View File

@@ -73,7 +73,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
private JPanel component;
private RootNode scriptRoot;
private GTree scriptCategoryTree;
private DraggableScriptTable scriptTable;
private GTable scriptTable;
private final GhidraScriptInfoManager infoManager;
private GhidraScriptTableModel tableModel;
private BundleStatusComponentProvider bundleStatusComponentProvider;
@@ -185,7 +185,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
tableModel = new GhidraScriptTableModel(this, infoManager);
scriptTable = new DraggableScriptTable(this, tableModel);
scriptTable = new GTable(tableModel);
scriptTable.setAutoLookupColumn(tableModel.getNameColumnIndex());
scriptTable.setRowSelectionAllowed(true);
scriptTable.setAutoCreateColumnsFromModel(false);
@@ -461,11 +461,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
setSelectedScript(renameFile);
}
JTable getTable() {
public GTable getTable() {
return scriptTable;
}
GTree getTree() {
public GTree getTree() {
return scriptCategoryTree;
}

View File

@@ -32,7 +32,6 @@ import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
@@ -171,10 +170,6 @@ public class ViewStringsProvider extends ComponentProviderAdapter {
tool.contextChanged(this);
}
ProgramSelection getProgramSelection() {
return table.getProgramSelection();
}
void add(Data data) {
if (isVisible()) {
stringModel.addDataInstance(currentProgram, data);

View File

@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +15,17 @@
*/
package ghidra.app.plugin.core.symboltree;
import java.awt.datatransfer.*;
import java.awt.dnd.DnDConstants;
import java.io.IOException;
import java.util.*;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeDragNDropHandler;
import ghidra.app.plugin.core.symboltree.nodes.*;
import ghidra.app.plugin.core.symtable.dnd.SymbolDataFlavor;
import ghidra.app.plugin.core.symtable.dnd.SymbolTransferData;
import ghidra.app.util.SelectionTransferData;
import ghidra.app.util.SelectionTransferable;
import ghidra.program.model.address.AddressSetView;
@@ -25,14 +34,11 @@ import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.Msg;
import java.awt.datatransfer.*;
import java.awt.dnd.DnDConstants;
import java.io.IOException;
import java.util.*;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeDragNDropHandler;
/**
* A drag and drop handler for the {@link SymbolTreePlugin}. There is limited support for dragging
* nodes within the tree to move symbols. This class also supports the dragging of symbol nodes
* into transient symbol tables.
*/
public class SymbolGTreeDragNDropHandler implements GTreeDragNDropHandler {
private final SymbolTreePlugin plugin;
@@ -132,7 +138,8 @@ public class SymbolGTreeDragNDropHandler implements GTreeDragNDropHandler {
}
@Override
public boolean isDropSiteOk(GTreeNode destinationUserNode, DataFlavor[] flavors, int dropAction) {
public boolean isDropSiteOk(GTreeNode destinationUserNode, DataFlavor[] flavors,
int dropAction) {
if (dropAction != DnDConstants.ACTION_MOVE) {
return false;
}
@@ -155,15 +162,6 @@ public class SymbolGTreeDragNDropHandler implements GTreeDragNDropHandler {
if (dragAction != DnDConstants.ACTION_MOVE) {
return false;
}
for (GTreeNode node : dragUserData) {
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
DataFlavor dataFlavor = symbolNode.getNodeDataFlavor();
if (dataFlavor == null) {
return false;
}
}
return dragUserData.size() != 0;
}
@@ -176,6 +174,10 @@ public class SymbolGTreeDragNDropHandler implements GTreeDragNDropHandler {
if (flavor != null) {
flavorSet.add(flavor);
}
if (symbolNode instanceof SymbolNode) {
flavorSet.add(SymbolDataFlavor.DATA_FLAVOR);
}
}
return flavorSet.toArray(new DataFlavor[flavorSet.size()]);
}
@@ -183,7 +185,33 @@ public class SymbolGTreeDragNDropHandler implements GTreeDragNDropHandler {
@Override
public Object getTransferData(List<GTreeNode> transferNodes, DataFlavor flavor)
throws UnsupportedFlavorException {
return transferNodes;
if (SymbolDataFlavor.DATA_FLAVOR.equals(flavor)) {
return getSymbols(transferNodes);
}
List<GTreeNode> flavorNodes = new ArrayList<>();
for (GTreeNode node : transferNodes) {
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
DataFlavor nodeFlavor = symbolNode.getNodeDataFlavor();
if (Objects.equals(flavor, nodeFlavor)) {
flavorNodes.add(symbolNode);
}
}
return flavorNodes;
}
private SymbolTransferData getSymbols(List<GTreeNode> transferNodes) {
List<Symbol> symbols = new ArrayList<>();
for (GTreeNode node : transferNodes) {
if (node instanceof SymbolNode symbolNode) {
symbols.add(symbolNode.getSymbol());
}
}
SymbolTreeProvider provider = plugin.getProvider();
GTree tree = provider.getTree();
return new SymbolTransferData(tree, symbols);
}
}

View File

@@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.symboltree;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import javax.swing.tree.TreePath;
@@ -70,24 +69,6 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext {
return null;
}
/**
* Returns true if the tree's current selection contains at least one {@link SymbolNode}.
* @return true if the tree's current selection contains at least one {@link SymbolNode}.
*/
public boolean hasSymbolsSelected() {
if (selectionPaths == null) {
return false;
}
for (TreePath treePath : selectionPaths) {
Object object = treePath.getLastPathComponent();
if (object instanceof SymbolNode) {
return true;
}
}
return false;
}
/**
* Returns all selected {@link SymbolNode}s or an empty list.
* @return all selected {@link SymbolNode}s or an empty list.
@@ -109,7 +90,7 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext {
private static List<Symbol> getSymbols(TreePath[] selectionPaths) {
if (selectionPaths == null) {
return null;
return Collections.emptyList();
}
List<Symbol> symbols = new ArrayList<>();
@@ -119,10 +100,6 @@ public class SymbolTreeActionContext extends ProgramSymbolActionContext {
SymbolNode symbolNode = (SymbolNode) object;
symbols.add(symbolNode.getSymbol());
}
else {
// Do not return symbols if selection contains non-symbolNodes
return null;
}
}
return symbols;
}

View File

@@ -269,6 +269,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
goToExternalAction.setEnabled(false);
CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin);
tool.addLocalAction(this, createImportAction);
tool.addLocalAction(this, setExternalProgramAction);
@@ -286,6 +287,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, selectionAction);
tool.addLocalAction(this, goToExternalAction);
tool.addLocalAction(this, cloneAction);
tool.addLocalAction(this, tableAction);
}
//==================================================================================================

View File

@@ -0,0 +1,158 @@
/* ###
* 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 ghidra.app.plugin.core.symboltree.actions;
import java.util.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.action.KeyBindingType;
import docking.action.MenuData;
import docking.widgets.table.GTable;
import docking.widgets.table.threaded.GThreadedTablePanel;
import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.app.context.ProgramSymbolContextAction;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.symtable.*;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.GoToService;
import ghidra.app.util.SymbolInspector;
import ghidra.app.util.query.TableService;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraThreadedTablePanel;
public class CreateSymbolTableAction extends ProgramSymbolContextAction {
private Plugin plugin;
public CreateSymbolTableAction(Plugin plugin) {
super("Create Table", plugin.getName(), KeyBindingType.SHARED);
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Create Table" },
SymbolTreeContextAction.MIDDLE_MENU_GROUP));
// Note: We need to set the help location instead of using the default behavior, which is to
// use the plugin's name. By doing this we can have all different uses of this action
// point to one help location.
setHelpLocation(new HelpLocation("SymbolTablePlugin", "Temporary_Symbol_Table"));
}
@Override
protected boolean isEnabledForContext(ProgramSymbolActionContext context) {
return context.getSymbolCount() != 0;
}
@Override
protected void actionPerformed(ProgramSymbolActionContext context) {
HashSet<SymbolRowObject> rowObjects = new HashSet<>();
Iterable<Symbol> symbols = context.getSymbols();
for (Symbol symbol : symbols) {
rowObjects.add(new SymbolRowObject(symbol));
}
PluginTool tool = plugin.getTool();
Program program = context.getProgram();
TransientSymbolTableModel model = new TransientSymbolTableModel(tool, program, rowObjects);
Navigatable navigatable = null;
GoToService goToService = tool.getService(GoToService.class);
if (goToService != null) {
navigatable = goToService.getDefaultNavigatable();
}
TableService service = tool.getService(TableService.class);
if (service == null) {
Msg.showError(this, null, "Table Service Not Installed",
"You must have a Table Service installed to create a Symbol Table");
return;
}
TableComponentProvider<SymbolRowObject> provider =
service.showTable("Symbols", "Symbols", model, "Symbols", navigatable);
provider.setActionContextProvider(mouseEvent -> {
GThreadedTablePanel<SymbolRowObject> tablePanel = provider.getThreadedTablePanel();
GTable table = tablePanel.getTable();
List<Symbol> selectedSymbols = getSelectedSymbols(table, model);
return new ProgramSymbolActionContext(provider, program, selectedSymbols, table);
});
// replace the generic provider help with this action's help
provider.setHelpLocation(getHelpLocation());
addActions(provider, model);
GhidraThreadedTablePanel<SymbolRowObject> tablePanel = provider.getThreadedTablePanel();
GhidraTable table = tablePanel.getTable();
configureSymbolTable(tool, table, model, program);
}
private void addActions(TableComponentProvider<SymbolRowObject> provider,
TransientSymbolTableModel model) {
provider.installRemoveItemsAction();
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin);
provider.getTool().addLocalAction(provider, tableAction);
}
private void configureSymbolTable(PluginTool tool, GhidraTable table,
TransientSymbolTableModel model, Program program) {
new TransientSymbolTableDnDAdapter(table, model);
SymbolInspector symbolInspector = new SymbolInspector(tool, table);
symbolInspector.setProgram(program);
SymbolRenderer renderer = model.getSymbolRenderer();
renderer.setSymbolInspector(symbolInspector);
TableColumnModel columnModel = table.getColumnModel();
int n = table.getColumnCount();
for (int i = 0; i < n; i++) {
TableColumn column = columnModel.getColumn(i);
column.setCellRenderer(renderer);
if (column.getModelIndex() == AbstractSymbolTableModel.LABEL_COL) {
column.setCellEditor(new SymbolEditor());
}
}
}
private List<Symbol> getSelectedSymbols(GTable table, TransientSymbolTableModel model) {
List<Symbol> list = new ArrayList<>();
int[] rows = table.getSelectedRows();
for (int row : rows) {
SymbolRowObject rowObject = model.getRowObject(row);
Symbol s = rowObject.getSymbol();
if (s != null) {
list.add(s);
}
}
return list;
}
}

View File

@@ -0,0 +1,690 @@
/* ###
* 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 ghidra.app.plugin.core.symtable;
import java.util.*;
import docking.widgets.table.*;
import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.docking.settings.Settings;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg;
import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.*;
import ghidra.util.table.field.*;
public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
private static final Comparator<SymbolRowObject> NAME_COL_COMPARATOR = (s1, s2) -> {
return s1.toString().compareToIgnoreCase(s2.toString());
};
public static final int LABEL_COL = 0;
public static final int LOCATION_COL = 1;
public static final int TYPE_COL = 2;
public static final int DATA_TYPE_COL = 3;
public static final int NAMESPACE_COL = 4;
public static final int SOURCE_COL = 5;
public static final int REFS_COL = 6;
private PluginTool tool;
protected SymbolTable symbolTable;
protected ReferenceManager refMgr;
protected SymbolRowObject lastSymbol;
protected SymbolFilter filter;
protected SymbolRenderer symbolRenderer = new SymbolRenderer();
AbstractSymbolTableModel(PluginTool tool) {
super("Symbols", tool, null, null);
this.tool = tool;
this.filter = new NewSymbolFilter();
}
@Override
protected TableColumnDescriptor<SymbolRowObject> createTableColumnDescriptor() {
TableColumnDescriptor<SymbolRowObject> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new NameTableColumn());
descriptor.addVisibleColumn(new LocationTableColumn(), 1, true);
descriptor.addVisibleColumn(new SymbolTypeTableColumn());
descriptor.addHiddenColumn(new DataTypeTableColumn());
descriptor.addVisibleColumn(new NamespaceTableColumn());
descriptor.addVisibleColumn(new SourceTableColumn());
descriptor.addVisibleColumn(new ReferenceCountTableColumn());
descriptor.addVisibleColumn(new OffcutReferenceCountTableColumn());
descriptor.addHiddenColumn(new PinnedTableColumn());
descriptor.addHiddenColumn(new UserTableColumn());
descriptor.addHiddenColumn(new OriginalNameColumn());
return descriptor;
}
void setFilter(SymbolFilter filter) {
this.filter = filter;
reload();
}
Symbol getSymbol(long symbolID) {
if (symbolTable != null) {
return symbolTable.getSymbol(symbolID);
}
return null;
}
@Override
public void dispose() {
super.dispose();
symbolTable = null;
refMgr = null;
lastSymbol = null;
}
void reload(Program prog) {
cancelAllUpdates();
this.lastSymbol = null;
if (prog != null) {
this.setProgram(prog);
this.symbolTable = prog.getSymbolTable();
this.refMgr = prog.getReferenceManager();
reload();
}
else {
this.setProgram(null);
this.symbolTable = null;
this.refMgr = null;
}
}
public int getKeyCount() {
if (symbolTable != null) {
int cnt = symbolTable.getNumSymbols();
if (filter.acceptsDefaultLabelSymbols()) {
cnt += refMgr.getReferenceDestinationCount();
}
return cnt;
}
return 0;
}
@Override
public boolean isSortable(int columnIndex) {
return true;
}
@Override
public boolean isCellEditable(int key, int columnIndex) {
return columnIndex == LABEL_COL;
}
@Override
public void setValueAt(Object aValue, int row, int columnIndex) {
if (symbolTable == null || aValue == null) {
return;
}
if (row < 0 || row >= filteredData.size()) {
return;
}
Symbol symbol = filteredData.get(row).getSymbol();
if (symbol == null || symbol.isDeleted()) {
return;
}
if (columnIndex != LABEL_COL) {
return;
}
String newName = aValue.toString();
if (symbol.getName().equals(newName)) {
return;
}
RenameLabelCmd cmd = new RenameLabelCmd(symbol, newName, SourceType.USER_DEFINED);
if (!tool.execute(cmd, getProgram())) {
Msg.showError(getClass(), null, "Error Renaming Symbol", cmd.getStatusMsg());
}
}
@Override
public ProgramLocation getProgramLocation(int row, int column) {
Symbol s = (Symbol) getValueAt(row, LABEL_COL);
if (s == null || s.isDeleted()) {
return null;
}
return s.getProgramLocation();
}
public ProgramLocation getProgramLocation(int row) {
return (ProgramLocation) getValueAt(row, LOCATION_COL);
}
@Override
public ProgramSelection getProgramSelection(int[] rows) {
AddressSet set = new AddressSet();
for (int element : rows) {
AddressBasedLocation symbolLocation =
getSymbolLocation(getRowObject(element).getSymbol());
if (symbolLocation.isMemoryLocation()) {
set.add(symbolLocation.getAddress());
}
}
return new ProgramSelection(set);
}
@Override
public void reload() {
lastSymbol = null;
super.reload();
}
void symbolAdded(Symbol s) {
if (filter.accepts(s, getProgram())) {
lastSymbol = new SymbolRowObject(s);
addObject(lastSymbol);
}
}
void symbolRemoved(long symbolId) {
SymbolRowObject deletedSymbol = new DeletedSymbolRowObject(program, symbolId);
if (deletedSymbol.equals(lastSymbol)) {
lastSymbol = null;
}
removeObject(deletedSymbol);
}
void symbolChanged(Symbol s) {
if (filter.accepts(s, getProgram())) {
updateObject(new SymbolRowObject(s));
}
else {
// the symbol may be in the table, as it could have passed the filter before the change
removeObject(new SymbolRowObject(s));
}
}
void delete(List<Symbol> rowObjects) {
if (rowObjects == null || rowObjects.isEmpty()) {
return;
}
tool.setStatusInfo("");
List<Symbol> deleteList = new LinkedList<>();
CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)");
for (Symbol symbol : rowObjects) {
if (symbol.isDynamic()) {
continue; // can't delete dynamic symbols...
}
deleteList.add(symbol);
String label = symbol.getName();
Address address = symbol.getAddress();
if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject();
boolean ignoreMissingFunction = function.isThunk();
cmd.add(new DeleteFunctionCmd(address, ignoreMissingFunction));
if (symbol.getSource() != SourceType.DEFAULT) {
// remove label which gets created when non-default function is removed
cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
}
}
else {
cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
}
}
if (cmd.size() == 0) {
return;
}
if (tool.execute(cmd, getProgram())) {
for (Symbol s : deleteList) {
removeObject(new SymbolRowObject(s));
}
updateNow();
}
else {
tool.setStatusInfo(cmd.getStatusMsg());
reload();
}
}
public SymbolFilter getFilter() {
return filter;
}
@Override
public Address getAddress(int row) {
SymbolRowObject rowObject = getRowObject(row);
if (rowObject == null) {
return null;
}
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getAddress();
}
private AddressBasedLocation getSymbolLocation(Symbol s) {
if (s == null) {
return new AddressBasedLocation();
}
SymbolType type = s.getSymbolType();
if (type == SymbolType.PARAMETER || type == SymbolType.LOCAL_VAR) {
// Must use special location object for variables which renders variable storage
// location since this can't be obtained from just a variable storage address
Variable object = (Variable) s.getObject();
if (object == null) {
return null;
}
return new VariableSymbolLocation(object);
}
return new AddressBasedLocation(program, s.getAddress());
}
@Override
protected Comparator<SymbolRowObject> createSortComparator(int columnIndex) {
DynamicTableColumn<SymbolRowObject, ?, ?> column = getColumn(columnIndex);
if (column instanceof NameTableColumn) {
// note: we use our own name comparator to increase sorting speed for the name
// column. This works because this comparator is called for each *row object*
// allowing the comparator to compare the Symbols based on name instead of
// having to use the table model's code for getting a column value for the
// row object. The code for retrieving a column value is slower than just
// working with the row object directly. See
// ThreadedTableModel.getCachedColumnValueForRow for more info.
return NAME_COL_COMPARATOR;
}
return super.createSortComparator(columnIndex);
}
public SymbolRenderer getSymbolRenderer() {
return symbolRenderer;
}
//==================================================================================================
// Table Column Classes
//==================================================================================================
private class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Symbol> {
@Override
public String getColumnName() {
return "Name";
}
@Override
public Symbol getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
return rowObject.getSymbol();
}
}
private class PinnedTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Boolean> {
private PinnedRenderer renderer = new PinnedRenderer();
@Override
public String getColumnName() {
return "Pinned";
}
@Override
public Boolean getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.isPinned();
}
@Override
public GColumnRenderer<Boolean> getColumnRenderer() {
return renderer;
}
private class PinnedRenderer extends GBooleanCellRenderer
implements AbstractWrapperTypeColumnRenderer<Boolean> {
// body is handled by parents
}
}
private class LocationTableColumn
extends AbstractProgramLocationTableColumn<SymbolRowObject, AddressBasedLocation> {
@Override
public String getColumnName() {
return "Location";
}
@Override
public AddressBasedLocation getValue(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
return getSymbolLocation(symbol);
}
@Override
public ProgramLocation getProgramLocation(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getProgramLocation();
}
}
private class SymbolTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Type";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
// Note: this call is slow. If we decide that filtering/sorting on this value is
// important, then this should be cached
return SymbolUtilities.getSymbolTypeDisplayName(symbol);
}
}
private class VariableSymbolLocation extends AddressBasedLocation {
VariableSymbolLocation(Variable variable) {
super(variable.getSymbol().getAddress(), variable.getVariableStorage().toString());
}
}
private class DataTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Data Type";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
DataType dt = null;
Object obj = symbol.getObject();
if (obj instanceof Data) {
dt = ((Data) obj).getDataType();
}
else if (obj instanceof Function) {
dt = ((Function) obj).getReturnType();
}
else if (obj instanceof Variable) {
dt = ((Variable) obj).getDataType();
}
else if (obj instanceof ExternalLocation) {
dt = ((ExternalLocation) obj).getDataType();
}
if (dt != null) {
return dt.getDisplayName();
}
return "";
}
}
private class NamespaceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Namespace";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getParentNamespace().getName(true);
}
}
private class SourceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SourceType> {
private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<>() {
@Override
protected String getText(Object value) {
if (value == null) {
return "";
}
return ((SourceType) value).getDisplayString();
}
@Override
public String getFilterString(SourceType t, Settings settings) {
return getText(t);
}
};
@Override
public String getColumnName() {
return "Source";
}
@Override
public GColumnRenderer<SourceType> getColumnRenderer() {
return renderer;
}
@Override
public SourceType getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getSource();
}
}
private class ReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private ReferenceCountRenderer renderer = new ReferenceCountRenderer();
@Override
public String getColumnName() {
return "Reference Count";
}
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return Integer.valueOf(symbol.getReferenceCount());
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class ReferenceCountRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}
private class OffcutReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer();
@Override
public String getColumnName() {
return "Offcut Ref Count";
}
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
Address address = symbol.getAddress();
int count = 0;
if (address.isMemoryAddress()) {
CodeUnit codeUnit = p.getListing().getCodeUnitContaining(address);
if (codeUnit != null) {
AddressSet set =
new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
set.deleteRange(address, address);
ReferenceManager referenceManager = p.getReferenceManager();
AddressIterator it =
referenceManager.getReferenceDestinationIterator(set, true);
while (it.hasNext()) {
it.next();
count++;
}
}
}
return Integer.valueOf(count);
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class OffcutReferenceCountRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}
private class UserTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "User";
}
@Override
public String getColumnDescription() {
return "The user that created or last edited this symbol.";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
SourceType source = symbol.getSource();
if (source != SourceType.USER_DEFINED) {
return null;
}
Address address = symbol.getAddress();
LabelHistory[] labelHistory = symbolTable.getLabelHistory(address);
if (labelHistory.length > 0) {
return labelHistory[0].getUserName();
}
return null;
}
}
private class OriginalNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Original Imported Name";
}
@Override
public String getColumnDescription() {
return "The original (pre-demangled) import name (External Symbols Only)";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
if (!symbol.isExternal()) {
return null;
}
SymbolType symbolType = symbol.getSymbolType();
if (symbolType != SymbolType.FUNCTION && symbolType != SymbolType.LABEL) {
return null;
}
ExternalManager externalManager = p.getExternalManager();
ExternalLocation externalLocation = externalManager.getExternalLocation(symbol);
if (externalLocation != null) {
return externalLocation.getOriginalImportedName();
}
return null;
}
}
}

View File

@@ -23,12 +23,12 @@ import docking.DockingUtils;
import docking.UndoRedoKeeper;
import ghidra.program.model.symbol.Symbol;
class SymbolEditor extends DefaultCellEditor {
public class SymbolEditor extends DefaultCellEditor {
private JTextField symbolField = null;
private UndoRedoKeeper undoRedoKeeper;
SymbolEditor() {
public SymbolEditor() {
super(new JTextField());
symbolField = (JTextField) super.getComponent();
symbolField.setBorder(BorderFactory.createEmptyBorder());

View File

@@ -31,7 +31,6 @@ import docking.widgets.table.RowFilterTransformer;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.ProgramSelection;
import ghidra.util.table.*;
class SymbolPanel extends JPanel {
@@ -61,7 +60,7 @@ class SymbolPanel extends JPanel {
this.listener = e -> symProvider.updateTitle();
symTable = threadedTablePanel.getTable();
symTable.setAutoLookupColumn(SymbolTableModel.LABEL_COL);
symTable.setAutoLookupColumn(AbstractSymbolTableModel.LABEL_COL);
symTable.setRowSelectionAllowed(true);
symTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
symTable.getModel().addTableModelListener(listener);
@@ -79,7 +78,7 @@ class SymbolPanel extends JPanel {
for (int i = 0; i < symTable.getColumnCount(); i++) {
TableColumn column = symTable.getColumnModel().getColumn(i);
column.setCellRenderer(renderer);
if (column.getModelIndex() == SymbolTableModel.LABEL_COL) {
if (column.getModelIndex() == AbstractSymbolTableModel.LABEL_COL) {
column.setCellEditor(new SymbolEditor());
}
}
@@ -88,6 +87,9 @@ class SymbolPanel extends JPanel {
add(createFilterFieldPanel(), BorderLayout.SOUTH);
filterDialog = new FilterDialog(tool);
// enable dragging symbols out of the symbol table
new SymbolTableDragProvider(symTable, model);
}
private JPanel createFilterFieldPanel() {
@@ -124,10 +126,6 @@ class SymbolPanel extends JPanel {
return new DefaultRowFilterTransformer<>(tableModel, symTable.getColumnModel());
}
ProgramSelection getProgramSelection() {
return symTable.getProgramSelection();
}
void dispose() {
symTable.getModel().removeTableModelListener(listener);
symTable.dispose();

View File

@@ -57,7 +57,7 @@ class SymbolProvider extends ComponentProviderAdapter {
setWindowGroup("symbolTable");
renderer = new SymbolRenderer();
symbolKeyModel = new SymbolTableModel(this, plugin.getTool());
symbolKeyModel = new SymbolTableModel(plugin.getTool());
symbolPanel = new SymbolPanel(this, symbolKeyModel, renderer, plugin.getTool());
addToTool();

View File

@@ -30,14 +30,10 @@ import ghidra.program.util.ProgramLocation;
import ghidra.program.util.VariableNameFieldLocation;
import ghidra.util.table.GhidraTableCellRenderer;
class SymbolRenderer extends GhidraTableCellRenderer {
public class SymbolRenderer extends GhidraTableCellRenderer {
private SymbolInspector inspector;
SymbolRenderer() {
super();
}
void setSymbolInspector(SymbolInspector inspector) {
public void setSymbolInspector(SymbolInspector inspector) {
this.inspector = inspector;
}
@@ -50,7 +46,7 @@ class SymbolRenderer extends GhidraTableCellRenderer {
int column = data.getColumnModelIndex();
boolean isSelected = data.isSelected();
if (value == null && column == SymbolTableModel.LABEL_COL) {
if (value == null && column == AbstractSymbolTableModel.LABEL_COL) {
setText("<< REMOVED >>");
}
else if (value instanceof Symbol) {

View File

@@ -0,0 +1,153 @@
/* ###
* 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 ghidra.app.plugin.core.symtable;
import java.awt.Component;
import java.awt.Container;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.io.IOException;
import java.util.List;
import docking.widgets.table.RowObjectTableModel;
import ghidra.app.plugin.core.symtable.dnd.SymbolDataFlavor;
import ghidra.app.plugin.core.symtable.dnd.SymbolTransferData;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.Msg;
import ghidra.util.table.GhidraTable;
/**
* A class to combine Symbol drag and drop operations into one class. Clients will need to
* implement methods for getting the selected symbols and then adding dropped symbols.
*/
public abstract class SymbolTableDnDAdapter extends SymbolTableDragProvider
implements DropTargetListener {
public SymbolTableDnDAdapter(GhidraTable table, RowObjectTableModel<SymbolRowObject> model) {
super(table, model);
int actions = DnDConstants.ACTION_COPY;
new DropTarget(table, actions, this, true);
// Add the drop listener to the table's parent so we can drop on the table's blank area
Container parent = table.getParent();
new DropTarget(parent, actions, this, true);
}
/**
* Called when symbols are dropped onto the component using this adapter.
* @param symbols the dropped symbols
*/
protected abstract void addSymbols(List<Symbol> symbols);
//=================================================================================================
// DropTargetListener methods
//=================================================================================================
@Override
public void dragEnter(DropTargetDragEvent dtde) {
dragOver(dtde);
}
@Override
public void dragExit(DropTargetEvent dte) {
// don't care
}
@Override
public void dragOver(DropTargetDragEvent dtde) {
Transferable transferable = dtde.getTransferable();
if (!transferable.isDataFlavorSupported(SymbolDataFlavor.DATA_FLAVOR)) {
return;
}
try {
SymbolTransferData symbolData =
(SymbolTransferData) transferable.getTransferData(SymbolDataFlavor.DATA_FLAVOR);
Component source = symbolData.getSource();
if (source == table) {
// don't allow dragging within the same component
dtde.rejectDrag();
return;
}
// Ghidra tables only support the concept of one program per table. We can change this
// in the future if we find a need to mix table rows that use more than one program.
if (!hasSameProgram(symbolData)) {
dtde.rejectDrag();
return;
}
}
catch (UnsupportedFlavorException | IOException e) {
// shouldn't happen, since we checked the flavor above
Msg.error(this, "Unable to perform drop operation", e);
}
DataFlavor[] dropFlavors = dtde.getCurrentDataFlavors();
if (!supportsDropFlavor(dropFlavors)) {
dtde.rejectDrag();
return;
}
dtde.acceptDrag(dtde.getDropAction());
}
private boolean hasSameProgram(SymbolTransferData symbolData) {
List<Symbol> symbols = symbolData.getSymbols();
Symbol s = symbols.get(0);
Program p = s.getProgram();
GhidraTable ghidraTable = (GhidraTable) table;
Program myProgram = ghidraTable.getProgram();
return p == myProgram;
}
private boolean supportsDropFlavor(DataFlavor[] dropFlavors) {
for (DataFlavor f : dropFlavors) {
if (f.equals(SymbolDataFlavor.DATA_FLAVOR)) {
return true;
}
}
return false;
}
@Override
public void drop(DropTargetDropEvent dtde) {
dtde.acceptDrop(dtde.getDropAction());
Transferable transferable = dtde.getTransferable();
try {
SymbolTransferData symbolData =
(SymbolTransferData) transferable.getTransferData(SymbolDataFlavor.DATA_FLAVOR);
List<Symbol> symbols = symbolData.getSymbols();
addSymbols(symbols);
}
catch (UnsupportedFlavorException | IOException e) {
Msg.error(this, "Unable to perform drop operation", e);
}
dtde.dropComplete(true);
}
@Override
public void dropActionChanged(DropTargetDragEvent dtde) {
// don't care
}
}

View File

@@ -0,0 +1,46 @@
/* ###
* 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 ghidra.app.plugin.core.symtable;
import java.awt.datatransfer.Transferable;
import java.util.ArrayList;
import java.util.List;
import docking.dnd.GTableDragProvider;
import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel;
import ghidra.app.plugin.core.symtable.dnd.SymbolTransferable;
import ghidra.program.model.symbol.Symbol;
/**
* A class that provides the ability to start the dragging of {@link Symbol}s.
*/
public class SymbolTableDragProvider extends GTableDragProvider<SymbolRowObject> {
public SymbolTableDragProvider(GTable table, RowObjectTableModel<SymbolRowObject> model) {
super(table, model);
}
@Override
protected Transferable createDragTransferable(List<SymbolRowObject> items) {
List<Symbol> symbols = new ArrayList<>();
for (SymbolRowObject ro : items) {
symbols.add(ro.getSymbol());
}
return new SymbolTransferable(table, symbols);
}
}

View File

@@ -15,124 +15,19 @@
*/
package ghidra.app.plugin.core.symtable;
import java.util.*;
import docking.widgets.table.*;
import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.docking.settings.Settings;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressIterator;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolIterator;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.*;
import ghidra.util.table.field.*;
import ghidra.util.task.TaskMonitor;
class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
class SymbolTableModel extends AbstractSymbolTableModel {
private static final Comparator<SymbolRowObject> NAME_COL_COMPARATOR = (s1, s2) -> {
return s1.toString().compareToIgnoreCase(s2.toString());
};
static final int LABEL_COL = 0;
static final int LOCATION_COL = 1;
static final int TYPE_COL = 2;
static final int DATA_TYPE_COL = 3;
static final int NAMESPACE_COL = 4;
static final int SOURCE_COL = 5;
static final int REFS_COL = 6;
private SymbolProvider provider;
private PluginTool tool;
private SymbolTable symbolTable;
private ReferenceManager refMgr;
private SymbolRowObject lastSymbol;
private SymbolFilter filter;
SymbolTableModel(SymbolProvider provider, PluginTool tool) {
super("Symbols", tool, null, null);
this.provider = provider;
this.tool = tool;
this.filter = new NewSymbolFilter();
}
@Override
protected TableColumnDescriptor<SymbolRowObject> createTableColumnDescriptor() {
TableColumnDescriptor<SymbolRowObject> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new NameTableColumn());
descriptor.addVisibleColumn(new LocationTableColumn(), 1, true);
descriptor.addVisibleColumn(new SymbolTypeTableColumn());
descriptor.addHiddenColumn(new DataTypeTableColumn());
descriptor.addVisibleColumn(new NamespaceTableColumn());
descriptor.addVisibleColumn(new SourceTableColumn());
descriptor.addVisibleColumn(new ReferenceCountTableColumn());
descriptor.addVisibleColumn(new OffcutReferenceCountTableColumn());
descriptor.addHiddenColumn(new PinnedTableColumn());
descriptor.addHiddenColumn(new UserTableColumn());
descriptor.addHiddenColumn(new OriginalNameColumn());
return descriptor;
}
void setFilter(SymbolFilter filter) {
this.filter = filter;
reload();
}
Symbol getSymbol(long symbolID) {
if (symbolTable != null) {
return symbolTable.getSymbol(symbolID);
}
return null;
}
@Override
public void dispose() {
super.dispose();
symbolTable = null;
refMgr = null;
lastSymbol = null;
provider = null;
}
void reload(Program prog) {
cancelAllUpdates();
this.lastSymbol = null;
if (prog != null) {
this.setProgram(prog);
this.symbolTable = prog.getSymbolTable();
this.refMgr = prog.getReferenceManager();
reload();
}
else {
this.setProgram(null);
this.symbolTable = null;
this.refMgr = null;
}
}
public int getKeyCount() {
if (symbolTable != null) {
int cnt = symbolTable.getNumSymbols();
if (filter.acceptsDefaultLabelSymbols()) {
cnt += refMgr.getReferenceDestinationCount();
}
return cnt;
}
return 0;
SymbolTableModel(PluginTool tool) {
super(tool);
}
@Override
@@ -141,13 +36,8 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
if (symbolTable == null) {
return;
}
SymbolIterator it;
// if (filter.acceptsOnlyCodeSymbols()) {
// it = symbolTable.getSymbolIterator(true);
// }
// else {
it = symbolTable.getDefinedSymbols();
// }
SymbolIterator it = symbolTable.getDefinedSymbols();
monitor.initialize(getKeyCount());
int value = 0;
@@ -174,557 +64,4 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
}
@Override
public boolean isSortable(int columnIndex) {
return true;
}
@Override
public boolean isCellEditable(int key, int columnIndex) {
return columnIndex == LABEL_COL;
}
@Override
public void setValueAt(Object aValue, int row, int columnIndex) {
if (provider == null || symbolTable == null || aValue == null) {
return;
}
if (row < 0 || row >= filteredData.size()) {
return;
}
Symbol symbol = filteredData.get(row).getSymbol();
if (symbol == null || symbol.isDeleted()) {
return;
}
if (columnIndex != LABEL_COL) {
return;
}
String newName = aValue.toString();
if (symbol.getName().equals(newName)) {
return;
}
RenameLabelCmd renameCmd = new RenameLabelCmd(symbol, newName, SourceType.USER_DEFINED);
if (!tool.execute(renameCmd, getProgram())) {
Msg.showError(getClass(), provider.getComponent(), "Error Renaming Symbol",
renameCmd.getStatusMsg());
}
}
@Override
public ProgramLocation getProgramLocation(int row, int column) {
Symbol s = (Symbol) getValueAt(row, LABEL_COL);
if (s == null || s.isDeleted()) {
return null;
}
return s.getProgramLocation();
}
public ProgramLocation getProgramLocation(int row) {
return (ProgramLocation) getValueAt(row, LOCATION_COL);
}
@Override
public ProgramSelection getProgramSelection(int[] rows) {
AddressSet set = new AddressSet();
for (int element : rows) {
AddressBasedLocation symbolLocation =
getSymbolLocation(getRowObject(element).getSymbol());
if (symbolLocation.isMemoryLocation()) {
set.add(symbolLocation.getAddress());
}
}
return new ProgramSelection(set);
}
@Override
public void reload() {
lastSymbol = null;
super.reload();
}
void symbolAdded(Symbol s) {
if (filter.accepts(s, getProgram())) {
lastSymbol = new SymbolRowObject(s);
addObject(lastSymbol);
}
}
void symbolRemoved(long symbolId) {
SymbolRowObject deletedSymbol = new DeletedSymbolRowObject(program, symbolId);
if (deletedSymbol.equals(lastSymbol)) {
lastSymbol = null;
}
removeObject(deletedSymbol);
}
void symbolChanged(Symbol s) {
if (filter.accepts(s, getProgram())) {
updateObject(new SymbolRowObject(s));
}
else {
// the symbol may be in the table, as it could have passed the filter before the change
removeObject(new SymbolRowObject(s));
}
}
void delete(List<Symbol> rowObjects) {
if (rowObjects == null || rowObjects.isEmpty()) {
return;
}
tool.setStatusInfo("");
List<Symbol> deleteList = new LinkedList<>();
CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)");
for (Symbol symbol : rowObjects) {
if (symbol.isDynamic()) {
continue; // can't delete dynamic symbols...
}
deleteList.add(symbol);
String label = symbol.getName();
Address address = symbol.getAddress();
if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject();
boolean ignoreMissingFunction = function.isThunk();
cmd.add(new DeleteFunctionCmd(address, ignoreMissingFunction));
if (symbol.getSource() != SourceType.DEFAULT) {
// remove label which gets created when non-default function is removed
cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
}
}
else {
cmd.add(new DeleteLabelCmd(address, label, symbol.getParentNamespace()));
}
}
if (cmd.size() == 0) {
return;
}
if (tool.execute(cmd, getProgram())) {
for (Symbol s : deleteList) {
removeObject(new SymbolRowObject(s));
}
updateNow();
}
else {
tool.setStatusInfo(cmd.getStatusMsg());
reload();
}
}
public SymbolFilter getFilter() {
return filter;
}
@Override
public Address getAddress(int row) {
SymbolRowObject rowObject = getRowObject(row);
if (rowObject == null) {
return null;
}
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getAddress();
}
private AddressBasedLocation getSymbolLocation(Symbol s) {
if (s == null) {
return new AddressBasedLocation();
}
SymbolType type = s.getSymbolType();
if (type == SymbolType.PARAMETER || type == SymbolType.LOCAL_VAR) {
// Must use special location object for variables which renders variable storage
// location since this can't be obtained from just a variable storage address
Variable object = (Variable) s.getObject();
if (object == null) {
return null;
}
return new VariableSymbolLocation(object);
}
return new AddressBasedLocation(program, s.getAddress());
}
@Override
protected Comparator<SymbolRowObject> createSortComparator(int columnIndex) {
DynamicTableColumn<SymbolRowObject, ?, ?> column = getColumn(columnIndex);
if (column instanceof NameTableColumn) {
// note: we use our own name comparator to increase sorting speed for the name
// column. This works because this comparator is called for each *row object*
// allowing the comparator to compare the Symbols based on name instead of
// having to use the table model's code for getting a column value for the
// row object. The code for retrieving a column value is slower than just
// working with the row object directly. See
// ThreadedTableModel.getCachedColumnValueForRow for more info.
return NAME_COL_COMPARATOR;
}
return super.createSortComparator(columnIndex);
}
//==================================================================================================
// Table Column Classes
//==================================================================================================
private class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Symbol> {
@Override
public String getColumnName() {
return "Name";
}
@Override
public Symbol getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
return rowObject.getSymbol();
}
}
private class PinnedTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Boolean> {
private PinnedRenderer renderer = new PinnedRenderer();
@Override
public String getColumnName() {
return "Pinned";
}
@Override
public Boolean getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.isPinned();
}
@Override
public GColumnRenderer<Boolean> getColumnRenderer() {
return renderer;
}
private class PinnedRenderer extends GBooleanCellRenderer
implements AbstractWrapperTypeColumnRenderer<Boolean> {
// body is handled by parents
}
}
private class LocationTableColumn
extends AbstractProgramLocationTableColumn<SymbolRowObject, AddressBasedLocation> {
@Override
public String getColumnName() {
return "Location";
}
@Override
public AddressBasedLocation getValue(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
return getSymbolLocation(symbol);
}
@Override
public ProgramLocation getProgramLocation(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getProgramLocation();
}
}
private class SymbolTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Type";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
// Note: this call is slow. If we decide that filtering/sorting on this value is
// important, then this should be cached
return SymbolUtilities.getSymbolTypeDisplayName(symbol);
}
}
private class VariableSymbolLocation extends AddressBasedLocation {
VariableSymbolLocation(Variable variable) {
super(variable.getSymbol().getAddress(), variable.getVariableStorage().toString());
}
}
private class DataTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Data Type";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
DataType dt = null;
Object obj = symbol.getObject();
if (obj instanceof Data) {
dt = ((Data) obj).getDataType();
}
else if (obj instanceof Function) {
dt = ((Function) obj).getReturnType();
}
else if (obj instanceof Variable) {
dt = ((Variable) obj).getDataType();
}
else if (obj instanceof ExternalLocation) {
dt = ((ExternalLocation) obj).getDataType();
}
if (dt != null) {
return dt.getDisplayName();
}
return "";
}
}
private class NamespaceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Namespace";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getParentNamespace().getName(true);
}
}
private class SourceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SourceType> {
private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<>() {
@Override
protected String getText(Object value) {
if (value == null) {
return "";
}
return ((SourceType) value).getDisplayString();
}
@Override
public String getFilterString(SourceType t, Settings settings) {
return getText(t);
}
};
@Override
public String getColumnName() {
return "Source";
}
@Override
public GColumnRenderer<SourceType> getColumnRenderer() {
return renderer;
}
@Override
public SourceType getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return symbol.getSource();
}
}
private class ReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private ReferenceCountRenderer renderer = new ReferenceCountRenderer();
@Override
public String getColumnName() {
return "Reference Count";
}
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
return Integer.valueOf(symbol.getReferenceCount());
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class ReferenceCountRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}
private class OffcutReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer();
@Override
public String getColumnName() {
return "Offcut Ref Count";
}
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
Address address = symbol.getAddress();
int count = 0;
if (address.isMemoryAddress()) {
CodeUnit codeUnit = p.getListing().getCodeUnitContaining(address);
if (codeUnit != null) {
AddressSet set =
new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress());
set.deleteRange(address, address);
ReferenceManager referenceManager = p.getReferenceManager();
AddressIterator it =
referenceManager.getReferenceDestinationIterator(set, true);
while (it.hasNext()) {
it.next();
count++;
}
}
}
return Integer.valueOf(count);
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class OffcutReferenceCountRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}
private class UserTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "User";
}
@Override
public String getColumnDescription() {
return "The user that created or last edited this symbol.";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
SourceType source = symbol.getSource();
if (source != SourceType.USER_DEFINED) {
return null;
}
Address address = symbol.getAddress();
LabelHistory[] labelHistory = symbolTable.getLabelHistory(address);
if (labelHistory.length > 0) {
return labelHistory[0].getUserName();
}
return null;
}
}
private class OriginalNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Original Imported Name";
}
@Override
public String getColumnDescription() {
return "The original (pre-demangled) import name (External Symbols Only)";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
}
if (!symbol.isExternal()) {
return null;
}
SymbolType symbolType = symbol.getSymbolType();
if (symbolType != SymbolType.FUNCTION && symbolType != SymbolType.LABEL) {
return null;
}
ExternalManager externalManager = p.getExternalManager();
ExternalLocation externalLocation = externalManager.getExternalLocation(symbol);
if (externalLocation != null) {
return externalLocation.getOriginalImportedName();
}
return null;
}
}
}

View File

@@ -238,7 +238,7 @@ public class SymbolTablePlugin extends Plugin {
}
private void codeAddedRemoved(ProgramChangeRecord rec) {
if (rec.getNewValue() instanceof Data data) {
if (rec.getNewValue() instanceof Data) {
domainObjectWorker.schedule(new CodeAddedRemoveJob(currentProgram, rec.getStart()));
}
}
@@ -411,6 +411,10 @@ public class SymbolTablePlugin extends Plugin {
DockingAction clearPinnedAction = new ClearPinSymbolAction(getName(), pinnedPopupGroup);
tool.addAction(clearPinnedAction);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(this);
tableAction.getPopupMenuData().setMenuGroup(popupGroup);
tool.addLocalAction(symProvider, tableAction);
}
private void createRefActions() {

View File

@@ -0,0 +1,56 @@
/* ###
* 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 ghidra.app.plugin.core.symtable;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.table.TableUtils;
import docking.widgets.table.threaded.ThreadedTableModelListenerAdapter;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.table.GhidraTable;
import ghidra.util.task.TaskLauncher;
/**
* A class to enable drag and drop for temporary symbol tables.
*/
public class TransientSymbolTableDnDAdapter extends SymbolTableDnDAdapter {
private TransientSymbolTableModel model;
public TransientSymbolTableDnDAdapter(GhidraTable table, TransientSymbolTableModel model) {
super(table, model);
this.model = model;
}
@Override
protected void addSymbols(List<Symbol> symbols) {
List<SymbolRowObject> rowObjects = new ArrayList<>();
for (Symbol s : symbols) {
rowObjects.add(new SymbolRowObject(s));
}
model.addInitialLoadListener(new ThreadedTableModelListenerAdapter() {
@Override
public void loadingFinished(boolean wasCancelled) {
TableUtils.setSelectedItems(table, rowObjects);
}
});
TaskLauncher.launchModal("Copying Symbols", () -> model.addSymbols(rowObjects));
}
}

View File

@@ -0,0 +1,128 @@
/* ###
* 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 ghidra.app.plugin.core.symtable;
import static ghidra.framework.model.DomainObjectEvent.*;
import static ghidra.program.util.ProgramEvent.*;
import java.util.HashSet;
import java.util.List;
import ghidra.framework.model.DomainObjectListenerBuilder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.SwingUpdateManager;
import ghidra.util.task.TaskMonitor;
/**
* A symbol table model meant to show a temporary table of symbols. The symbols in the table can
* be removed from the table by the user.
*/
public class TransientSymbolTableModel extends AbstractSymbolTableModel {
private HashSet<SymbolRowObject> rowObjects;
private SwingUpdateManager updater = new SwingUpdateManager(this::fireTableDataChanged);
public TransientSymbolTableModel(PluginTool tool, Program program,
HashSet<SymbolRowObject> rowObjects) {
super(tool);
this.rowObjects = rowObjects;
setProgram(program);
symbolTable = program.getSymbolTable();
//@formatter:off
program.addListener(
new DomainObjectListenerBuilder(this)
.any(RESTORED, MEMORY_BLOCK_ADDED, MEMORY_BLOCK_REMOVED)
.terminate(this::handleRemovedSymbols)
.with(ProgramChangeRecord.class)
/*
.each(SYMBOL_REMOVED)
.call(this::symbolRemoved)
*/
.any(SYMBOL_REMOVED)
.call(() -> symbolChanged())
.any(CODE_ADDED, CODE_REMOVED,
SYMBOL_RENAMED, SYMBOL_SCOPE_CHANGED, SYMBOL_DATA_CHANGED,
SYMBOL_SOURCE_CHANGED,
SYMBOL_PRIMARY_STATE_CHANGED,
REFERENCE_ADDED, REFERENCE_REMOVED,
EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED)
.call(() -> symbolChanged())
.build());
//@formatter:on
}
private void handleRemovedSymbols() {
// Note: we could remove symbols from this model when they are removed from the program.
// But, by leaving them in the table, if the users presses undo, they will still be in the
// table. For now, leave the deleted symbols in the table. The user can remove them if
// they choose.
updater.update();
}
private void symbolChanged() {
updater.update();
}
@Override
protected void doLoad(Accumulator<SymbolRowObject> accumulator, TaskMonitor monitor)
throws CancelledException {
for (SymbolRowObject ro : rowObjects) {
monitor.checkCancelled();
accumulator.add(ro);
}
}
/**
* Adds the given rows to this table
* @param symbolRowObjects the rows to add
*/
public void addSymbols(List<SymbolRowObject> symbolRowObjects) {
for (SymbolRowObject ro : symbolRowObjects) {
addObject(ro);
}
super.reload();
}
@Override
public void addObject(SymbolRowObject obj) {
rowObjects.add(obj);
super.addObject(obj);
}
@Override
public void removeObject(SymbolRowObject obj) {
rowObjects.remove(obj);
super.removeObject(obj);
}
@Override
protected void clearData() {
// don't allow; this will erase all table data
}
@Override
public void dispose() {
super.dispose();
rowObjects.clear();
}
}

View File

@@ -0,0 +1,34 @@
/* ###
* 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 ghidra.app.plugin.core.symtable.dnd;
import java.awt.datatransfer.DataFlavor;
import docking.dnd.GenericDataFlavor;
import ghidra.program.model.symbol.Symbol;
/**
* A simple data flavor for {@link Symbol} objects.
*/
public class SymbolDataFlavor extends GenericDataFlavor {
private static final String JAVA_CLASS_NAME = SymbolDataFlavor.class.getName();
public static final DataFlavor DATA_FLAVOR = new SymbolDataFlavor();
public SymbolDataFlavor() {
super(DataFlavor.javaJVMLocalObjectMimeType + "; class=" + JAVA_CLASS_NAME, "Symbol");
}
}

View File

@@ -0,0 +1,43 @@
/* ###
* 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 ghidra.app.plugin.core.symtable.dnd;
import java.awt.Component;
import java.util.List;
import ghidra.program.model.symbol.Symbol;
/**
* A simple object to transfer a list of symbols along with the source of the transfer.
*/
public class SymbolTransferData {
private Component source;
private List<Symbol> symbols;
public SymbolTransferData(Component source, List<Symbol> symbols) {
this.source = source;
this.symbols = symbols;
}
public Component getSource() {
return source;
}
public List<Symbol> getSymbols() {
return symbols;
}
}

View File

@@ -0,0 +1,62 @@
/* ###
* 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 ghidra.app.plugin.core.symtable.dnd;
import java.awt.Component;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import ghidra.program.model.symbol.Symbol;
public class SymbolTransferable implements Transferable {
private static final DataFlavor[] FLAVORS = new DataFlavor[] { SymbolDataFlavor.DATA_FLAVOR };
private List<Symbol> symbols;
private Component source;
public SymbolTransferable(Component source, List<Symbol> symbols) {
this.source = Objects.requireNonNull(source);
this.symbols = Objects.requireNonNull(symbols);
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (!SymbolDataFlavor.DATA_FLAVOR.equals(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return new SymbolTransferData(source, symbols);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return FLAVORS;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
for (DataFlavor f : FLAVORS) {
if (f.equals(flavor)) {
return true;
}
}
return false;
}
}

View File

@@ -20,6 +20,7 @@ import java.awt.Color;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
@@ -30,7 +31,6 @@ import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.table.AbstractSortedTableModel;
import docking.widgets.table.GTable;
import docking.widgets.table.threaded.GThreadedTablePanel;
import generic.theme.GIcon;
import ghidra.app.nav.Navigatable;
import ghidra.app.nav.NavigatableRemovalListener;
@@ -66,6 +66,8 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
private DockingAction selectAction;
private DockingAction removeItemsAction;
private Function<MouseEvent, ActionContext> contextProvider = null;
private HelpLocation helpLoc = new HelpLocation(HelpTopics.SEARCH, "Query_Results");
TableComponentProvider(TableServicePlugin plugin, String title, String name,
@@ -159,8 +161,7 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
new MakeProgramSelectionAction(navigatable, tableServicePlugin.getName(), table);
}
else {
selectAction =
new MakeProgramSelectionAction(tableServicePlugin, table);
selectAction = new MakeProgramSelectionAction(tableServicePlugin, table);
}
selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection"));
@@ -309,7 +310,7 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
tableFilterPanel.dispose();
}
public GThreadedTablePanel<T> getThreadedTablePanel() {
public GhidraThreadedTablePanel<T> getThreadedTablePanel() {
return threadedPanel;
}
@@ -405,7 +406,17 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
@Override
public ActionContext getActionContext(MouseEvent event) {
if (contextProvider != null) {
return contextProvider.apply(event);
}
return new DefaultActionContext(this, threadedPanel.getTable());
}
/**
* Sets a function that provides context for this component provider.
* @param contextProvider a function that provides context for this component provider.
*/
public void setActionContextProvider(Function<MouseEvent, ActionContext> contextProvider) {
this.contextProvider = contextProvider;
}
}

View File

@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +21,6 @@ import java.util.Arrays;
import java.util.List;
import docking.dnd.GenericDataFlavor;
import ghidra.util.Msg;
/**
@@ -30,80 +28,77 @@ import ghidra.util.Msg;
* The data is an AddressSetView.
*/
public class SelectionTransferable implements Transferable, ClipboardOwner {
/**
* DataFlavor for program selection.
*/
public static DataFlavor localProgramSelectionFlavor = createLocalProgramSelectionFlavor();
// create a data flavor that is an AddressSetView and a program pathname
private static DataFlavor createLocalProgramSelectionFlavor() {
try {
// note: The class specified cannot be an interface,
// contrary to what the javadocs specify.
return new GenericDataFlavor(
DataFlavor.javaJVMLocalObjectMimeType+
"; class="+ SelectionTransferData.class.getName(),
"Local Transfer Data for Program Selections");
}catch (Exception e) {
Msg.showError(SelectionTransferable.class, null, null, null, e);
}
return null;
}
private static DataFlavor []flavors=
{localProgramSelectionFlavor};
private static List<DataFlavor> flavorList = Arrays.asList(flavors);
private SelectionTransferData selectionData;
/**
* Construct a new SelectionTransferable.
* @param selectionData the data indicating the selection for the selection transferable
*/
public SelectionTransferable(SelectionTransferData selectionData) {
this.selectionData = selectionData;
}
/**
* Return all data flavors that this class supports.
*/
public synchronized DataFlavor []getTransferDataFlavors() {
return flavors;
}
/**
* Return whether the specifed data flavor is supported.
*/
public boolean isDataFlavorSupported(DataFlavor f) {
return flavorList.contains(f);
}
public static DataFlavor localProgramSelectionFlavor = createLocalProgramSelectionFlavor();
// create a data flavor that is an AddressSetView and a program pathname
private static DataFlavor createLocalProgramSelectionFlavor() {
try {
return new GenericDataFlavor(
DataFlavor.javaJVMLocalObjectMimeType + "; class=" +
SelectionTransferData.class.getName(),
"Local Transfer Data for Program Selections");
}
catch (Exception e) {
Msg.showError(SelectionTransferable.class, null, null, null, e);
}
return null;
}
private static DataFlavor[] flavors = { localProgramSelectionFlavor };
private static List<DataFlavor> flavorList = Arrays.asList(flavors);
private SelectionTransferData selectionData;
/**
* Construct a new SelectionTransferable.
* @param selectionData the data indicating the selection for the selection transferable
*/
public SelectionTransferable(SelectionTransferData selectionData) {
this.selectionData = selectionData;
}
/**
* Return all data flavors that this class supports.
*/
@Override
public synchronized DataFlavor[] getTransferDataFlavors() {
return flavors;
}
/**
* Return whether the specified data flavor is supported.
*/
@Override
public boolean isDataFlavorSupported(DataFlavor f) {
return flavorList.contains(f);
}
/**
* Return the transfer data with the given data flavor.
*/
@Override
public synchronized Object getTransferData(DataFlavor f)
throws UnsupportedFlavorException, IOException {
if (f.equals(localProgramSelectionFlavor)) {
return selectionData;
}
throw new UnsupportedFlavorException(f);
}
@Override
public String toString() {
return "SelectionTransferable";
}
@Override
public void lostOwnership(Clipboard clipboard, Transferable contents) {
// nothing to do
}
/**
* Return the transfer data with the given data flavor.
*/
public synchronized Object getTransferData(DataFlavor f)
throws UnsupportedFlavorException, IOException {
if (f.equals(localProgramSelectionFlavor)) {
return selectionData;
}
throw new UnsupportedFlavorException(f);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "SelectionTransferable";
}
/*
* (non-Javadoc)
* @see java.awt.datatransfer.ClipboardOwner#lostOwnership(java.awt.datatransfer.Clipboard, java.awt.datatransfer.Transferable)
*/
public void lostOwnership(Clipboard clipboard, Transferable contents) {
}
}

View File

@@ -70,4 +70,10 @@ public abstract class GhidraProgramTableModel<ROW_TYPE>
public Address getAddress(int row) {
return null;
}
@Override
public void dispose() {
program = null;
super.dispose();
}
}

View File

@@ -67,7 +67,7 @@ public class MakeProgramSelectionAction extends DockingAction {
/**
* Special constructor for clients that do not have a plugin. Clients using this
* constructor must override {@link #makeSelection(ActionContext)}.
* constructor must override {@link #makeProgramSelection(ProgramSelection, ActionContext)}.
*
* @param navigatable the navigatable that will be used to make selections; may not be null
* @param owner the action's owner
@@ -143,20 +143,8 @@ public class MakeProgramSelectionAction extends DockingAction {
@Override
public void actionPerformed(ActionContext context) {
makeSelection(context);
}
/**
* Deprecated in favor of {@link #makeProgramSelection(ProgramSelection, ActionContext)}.
* Override that method instead of this one.
* @param context the context
* @return the selection
*/
@Deprecated(forRemoval = true, since = "10.2")
protected ProgramSelection makeSelection(ActionContext context) {
ProgramSelection selection = table.getProgramSelection();
makeProgramSelection(selection, context);
return selection;
}
protected void makeProgramSelection(ProgramSelection selection, ActionContext context) {

View File

@@ -40,8 +40,7 @@ import docking.DefaultActionContext;
import docking.action.DockingActionIf;
import docking.widgets.OptionDialog;
import docking.widgets.filter.FilterTextField;
import docking.widgets.table.GDynamicColumnTableModel;
import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.*;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.jar.ResourceFile;
@@ -78,7 +77,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
protected ConsoleService console;
protected Program program;
protected DraggableScriptTable scriptTable;
protected GTable scriptTable;
protected JTextPane consoleTextPane;
protected GhidraScriptEditorComponentProvider editor;
protected JTextArea editorTextArea;
@@ -125,8 +124,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
(JTextPane) findComponentByName(consoleProvider.getComponent(), "Console Text Pane");
assertNotNull(consoleTextPane);
scriptTable =
(DraggableScriptTable) findComponentByName(provider.getComponent(), "Scripts Table");
scriptTable = (GTable) findComponentByName(provider.getComponent(), "Scripts Table");
assertNotNull(scriptTable);
clearConsole();

View File

@@ -0,0 +1,139 @@
/* ###
* 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.dnd;
import java.awt.event.*;
import docking.widgets.table.GTable;
/**
* A listener for tables that support drag and drop operations. This listener allows the user to
* make a multi-selection in the table and drag that selection.
*/
public class DragDropTableSelectionMouseListener extends MouseAdapter {
private boolean consuming = false;
private boolean didDrag = false;
private GTable table;
public DragDropTableSelectionMouseListener(GTable table) {
this.table = table;
install();
}
private void install() {
//
// Insert our listener into the front of the listeners so that we get a chance to
// handle events first.
//
MouseListener[] oldMouseListeners = table.getMouseListeners();
MouseMotionListener[] oldMouseMotionListeners = table.getMouseMotionListeners();
for (MouseListener l : oldMouseListeners) {
table.removeMouseListener(l);
}
for (MouseMotionListener l : oldMouseMotionListeners) {
table.removeMouseMotionListener(l);
}
table.addMouseListener(this);
table.addMouseMotionListener(this);
for (MouseListener l : oldMouseListeners) {
table.addMouseListener(l);
}
for (MouseMotionListener l : oldMouseMotionListeners) {
table.addMouseMotionListener(l);
}
}
@Override
public void mousePressed(MouseEvent e) {
consuming = maybeConsumeEvent(e);
didDrag = false;
}
@Override
public void mouseReleased(MouseEvent e) {
if (!consuming) {
return;
}
// continue to consume the event that was started during the pressed event, for symmetry
maybeConsumeEvent(e);
consuming = false;
if (!didDrag) {
//
// If we dragged, leave the initial selection, which does not disrupt the user's
// workflow; otherwise, select the clicked row. This allows users to change the
// selection by clicking in the table, which is the default table behavior.
//
table.clearSelection();
int row = table.rowAtPoint(e.getPoint());
table.selectRow(row);
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (!consuming) {
// This can happen when the initial left mouse click was not on a selected row. In that
// case we want to the drag to make a table selection.
return;
}
// always consume the drag so that Java does not change the selection
e.consume();
didDrag = true;
}
private boolean maybeConsumeEvent(MouseEvent e) {
if (!isBasicLeftClick(e)) {
return false;
}
// don't let other listeners process the event if we are 'pressing' the mouse
// button on an already selected row (to prevent de-selecting a multi-selection for
// a drag operation)
int row = table.rowAtPoint(e.getPoint());
if (table.isRowSelected(row)) {
e.consume();
return true;
}
return false;
}
private boolean isBasicLeftClick(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return false;
}
if (e.getClickCount() > 1) {
return false;
}
if (e.isControlDown() || e.isAltDown() || e.isShiftDown() || e.isMetaDown()) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,154 @@
/* ###
* 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.dnd;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.awt.event.InputEvent;
import java.util.*;
import javax.swing.ListSelectionModel;
import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel;
/**
* A class to allow GTables to support drag operations.
*
* @param <ROW_OBJECT> the row object type
*/
public abstract class GTableDragProvider<ROW_OBJECT>
implements DragSourceListener, DragGestureListener {
protected GTable table;
protected RowObjectTableModel<ROW_OBJECT> rowObjectModel;
public GTableDragProvider(GTable table, RowObjectTableModel<ROW_OBJECT> model) {
this.table = table;
this.rowObjectModel = model;
// install table mouse selection fixing listener
new DragDropTableSelectionMouseListener(table);
int actions = DnDConstants.ACTION_COPY;
DragSource dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(table, actions, this);
}
/**
* Creates a transferable for dragging using the given selected row objects.
* @param items the selected row objects
* @return the transferable
*/
protected abstract Transferable createDragTransferable(List<ROW_OBJECT> items);
//=================================================================================================
// DragSourceListener methods
//=================================================================================================
@Override
public void dragDropEnd(DragSourceDropEvent dsde) {
// don't care
}
@Override
public void dragEnter(DragSourceDragEvent dsde) {
// don't care
}
@Override
public void dragExit(DragSourceEvent dse) {
setCursor(DnDConstants.ACTION_NONE, dse.getDragSourceContext());
}
private void setCursor(int action, DragSourceContext dragSourceContext) {
Cursor cursor = DragSource.DefaultCopyNoDrop;
switch (action) {
case DnDConstants.ACTION_COPY:
cursor = DragSource.DefaultCopyDrop;
break;
case DnDConstants.ACTION_MOVE:
cursor = DragSource.DefaultMoveDrop;
break;
case DnDConstants.ACTION_LINK:
cursor = DragSource.DefaultLinkDrop;
}
dragSourceContext.setCursor(cursor);
}
@Override
public void dragOver(DragSourceDragEvent dsde) {
setCursor(dsde.getDropAction(), dsde.getDragSourceContext());
}
@Override
public void dropActionChanged(DragSourceDragEvent dsde) {
// don't care
}
//=================================================================================================
// DragGestureListener methods
//=================================================================================================
@Override
public void dragGestureRecognized(DragGestureEvent dragEvent) {
// check input event: if any button other than MB1 is pressed,
// don't attempt to process the drag and drop event.
InputEvent ie = dragEvent.getTriggerEvent();
int modifiers = ie.getModifiersEx();
if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 ||
(modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0) {
return;
}
List<ROW_OBJECT> items = getSelectedItems();
if (items.isEmpty()) {
return;
}
Transferable transferable = createDragTransferable(items);
if (transferable == null) {
return;
}
try {
dragEvent.startDrag(DragSource.DefaultMoveDrop, null, new Point(0, 0), transferable,
this);
}
catch (InvalidDnDOperationException exc) {
// not sure why, but apparently we don't care
}
}
protected List<ROW_OBJECT> getSelectedItems() {
ListSelectionModel lsm = table.getSelectionModel();
if (lsm.getValueIsAdjusting()) {
// don't allow dragging while a selection is being made
return Collections.emptyList();
}
int[] rows = table.getSelectedRows();
List<ROW_OBJECT> objects = new ArrayList<>();
for (int row : rows) {
ROW_OBJECT ro = rowObjectModel.getRowObject(row);
objects.add(ro);
}
return objects;
}
}

View File

@@ -1,105 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.dnd;
import ghidra.util.Msg;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.*;
public class GhidraTransferable implements Transferable, ClipboardOwner {
/**
* A static instance of the local flavor that is an ArrayList of
* user object objects.
*/
public static DataFlavor localGhidraFlavor = createLocalGhidraFlavor();
// create a data flavor that is an ArrayList of
// user objects
private static DataFlavor createLocalGhidraFlavor() {
try {
return new GenericDataFlavor(
DataFlavor.javaJVMLocalObjectMimeType+
"; class=java.util.ArrayList",
"Local list of Drag/Drop User objects");
}catch (Exception e) {
Msg.showError(GhidraTransferable.class, null, null, null, e);
}
return null;
}
private static DataFlavor []flavors= {localGhidraFlavor};
private static List<DataFlavor> flavorList = Arrays.asList(flavors);
private List<Object> dataList;
/**
* Constructs a new Transferable from the array of DragDropNodes
* @param dataList the array of DragDropNodes being transfered.
*/
public GhidraTransferable(List<?> dataList) {
this.dataList = new ArrayList<Object>(dataList);
}
/**
* Return all data flavors that this class supports.
*/
public synchronized DataFlavor []getTransferDataFlavors() {
return flavors;
}
/**
* Return whether the specifed data flavor is supported.
* @param f the DataFlavor to check if supported.
*/
public boolean isDataFlavorSupported(DataFlavor f) {
return flavorList.contains(f);
}
/**
* Return the transfer data with the given data flavor.
* @param f the DataFlavor for which to get a Transferable.
*/
public synchronized Object getTransferData(DataFlavor f)
throws UnsupportedFlavorException, IOException {
if (f.equals(localGhidraFlavor)) {
return dataList;
}
throw new UnsupportedFlavorException(f);
}
/**
* Get the string representation for this transferable.
*/
@Override
public String toString() {
return "GhidraTransferable";
}
/**
* Notification we have lost ownership of the clipboard because
* something else was put on the clipboard.
* @param clipboard the system clipboard.
* @param contents the Transferable lost in the clipboard.
*/
public void lostOwnership(Clipboard clipboard, Transferable contents) {
}
}

View File

@@ -26,7 +26,6 @@ import javax.swing.event.*;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.apache.commons.collections4.CollectionUtils;
import org.jdom.Element;
import docking.DockingWindowManager;
@@ -751,43 +750,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @param items the row objects to select
*/
public void setSelectedItems(List<ROW_OBJECT> items) {
if (CollectionUtils.isEmpty(items)) {
table.clearSelection();
return;
}
ListSelectionModel selectionModel = table.getSelectionModel();
int mode = selectionModel.getSelectionMode();
if (mode == ListSelectionModel.SINGLE_SELECTION) {
// take the last item to mimic what the selection model does internally
ROW_OBJECT item = items.get(items.size() - 1);
int viewRow = textFilterModel.getViewIndex(item);
table.setRowSelectionInterval(viewRow, viewRow);
return;
}
//
// For ListSelectionModel SINGLE_INTERVAL_SELECTION and MULTIPLE_INTERVAL_SELECTION, the
// model will update any selection given to it to match the current mode.
//
List<Integer> rows = new ArrayList<>();
for (ROW_OBJECT item : items) {
int viewRow = textFilterModel.getViewIndex(item);
if (viewRow >= 0) {
rows.add(viewRow);
}
}
if (rows.isEmpty()) {
return; // items may be filtered out of view
}
selectionModel.setValueIsAdjusting(true);
selectionModel.clearSelection();
for (int row : rows) {
selectionModel.addSelectionInterval(row, row);
}
selectionModel.setValueIsAdjusting(false);
TableUtils.setSelectedItems(table, items);
}
/**

View File

@@ -15,10 +15,14 @@
*/
package docking.widgets.table;
import javax.swing.JLabel;
import javax.swing.JTable;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.table.*;
import org.apache.commons.collections4.CollectionUtils;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.GColumnRenderer;
import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
@@ -28,6 +32,60 @@ import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
*/
public class TableUtils {
/**
* Select the given row objects. No selection will be made if the objects are filtered out of
* view. Passing a {@code null} list or an empty list will clear the selection.
*
* @param table the table in which to select the items
* @param items the row objects to select
*/
public static <ROW_OBJECT> void setSelectedItems(JTable table, List<ROW_OBJECT> items) {
if (CollectionUtils.isEmpty(items)) {
table.clearSelection();
return;
}
TableModel model = table.getModel();
if (!(model instanceof RowObjectTableModel gModel)) {
return;
}
ListSelectionModel selectionModel = table.getSelectionModel();
int mode = selectionModel.getSelectionMode();
if (mode == ListSelectionModel.SINGLE_SELECTION) {
// take the last item to mimic what the selection model does internally
ROW_OBJECT item = items.get(items.size() - 1);
@SuppressWarnings({ "cast", "unchecked" })
int viewRow = gModel.getRowIndex((ROW_OBJECT) item);
table.setRowSelectionInterval(viewRow, viewRow);
return;
}
//
// For ListSelectionModel SINGLE_INTERVAL_SELECTION and MULTIPLE_INTERVAL_SELECTION, the
// model will update any selection given to it to match the current mode.
//
List<Integer> rows = new ArrayList<>();
for (ROW_OBJECT item : items) {
@SuppressWarnings({ "cast", "unchecked" })
int viewRow = gModel.getRowIndex((ROW_OBJECT) item);
if (viewRow >= 0) {
rows.add(viewRow);
}
}
if (rows.isEmpty()) {
return; // items may be filtered out of view
}
selectionModel.setValueIsAdjusting(true);
selectionModel.clearSelection();
for (int row : rows) {
selectionModel.addSelectionInterval(row, row);
}
selectionModel.setValueIsAdjusting(false);
}
/**
* Uses the given row-based table model, row object and column index to determine what the
* String value should be for that cell.
@@ -208,7 +266,7 @@ public class TableUtils {
/*
Note: this code allows us to disable the 'unsorting' of a table via the UI
// remove it. If there is only one, don't remove the last one
if (editor.getSortedColumnCount() == 1) {
Toolkit.getDefaultToolkit().beep();

View File

@@ -165,8 +165,9 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
}
/**
* A package-level method. Subclasses should not call this.
*
* Subclasses should not call this. Loading for subclasses is done inside their implementation
* of {@link #doLoad(Accumulator, TaskMonitor)}.
*
* <p>This exists to handle whether this model should load incrementally.
*
* @param monitor the monitor
@@ -182,11 +183,15 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
}
// do the load now
ListAccumulator<ROW_OBJECT> accumulator = new ListAccumulator<>();
ListAccumulator<ROW_OBJECT> accumulator = createAccumulator();
doLoad(accumulator, monitor);
return accumulator.asList();
}
protected ListAccumulator<ROW_OBJECT> createAccumulator() {
return new ListAccumulator<ROW_OBJECT>();
}
private void initializeWorker() {
if (worker == null) {
worker = new Worker("GTable Worker: " + getName(), incrementalMonitor);

View File

@@ -0,0 +1,34 @@
/* ###
* 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.threaded;
public abstract class ThreadedTableModelListenerAdapter implements ThreadedTableModelListener {
@Override
public void loadPending() {
// stub
}
@Override
public void loadingStarted() {
// stub
}
@Override
public void loadingFinished(boolean wasCancelled) {
// stub
}
}

View File

@@ -39,8 +39,8 @@ import ghidra.framework.Platform;
import ghidra.util.ColorUtils;
import ghidra.util.Msg;
public class GTreeDragNDropAdapter implements DragSourceListener, DragGestureListener,
DropTargetListener {
public class GTreeDragNDropAdapter
implements DragSourceListener, DragGestureListener, DropTargetListener {
private JTree tree;
private GTreeDragNDropHandler dragNDropHandler;
@@ -159,9 +159,8 @@ public class GTreeDragNDropAdapter implements DragSourceListener, DragGestureLis
GradientPaint mask;
Color treeBg = tree.getBackground();
Color transparentTreeBackground = ColorUtils.withAlpha(treeBg, 100);
mask =
new GradientPaint(0, 0, transparentTreeBackground, 0, size.height >> 1,
Palette.NO_COLOR);
mask = new GradientPaint(0, 0, transparentTreeBackground, 0, size.height >> 1,
Palette.NO_COLOR);
g2.setPaint(mask);
// Sets the alpha composite
@@ -195,9 +194,8 @@ public class GTreeDragNDropAdapter implements DragSourceListener, DragGestureLis
return;
}
Component renderer =
cellRenderer.getTreeCellRendererComponent(tree, node, true, true,
node.isLeaf(), row, false);
Component renderer = cellRenderer.getTreeCellRendererComponent(tree, node, true,
true, node.isLeaf(), row, false);
renderer.setSize(renderer.getPreferredSize());
// move down the point in our graphics space into which we will paint

View File

@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,33 +24,34 @@ import docking.widgets.tree.GTreeNode;
public interface GTreeDragNDropHandler extends GTreeTransferHandler {
/**
* Return true if the dragUserData can be dragged.
* @param dragUserData data where user is initiating the drag operation
* @param dragAction user action for the drag operation
*/
public boolean isStartDragOk(List<GTreeNode> dragUserData, int dragAction);
/**
* Return true if the dragUserData can be dragged.
* @param dragUserData data where user is initiating the drag operation
* @param dragAction user action for the drag operation
* @return true if the dragUserData can be dragged
*/
public boolean isStartDragOk(List<GTreeNode> dragUserData, int dragAction);
/**
* Returns the supported Drag actions for this tree. For available actions see
* {@link DnDConstants}.
* @return the supported Drag actions.
*/
public int getSupportedDragActions();
/**
* Return true if the drop site is valid for the given target.
* @param destUserData destination for node being dragged
* @param flavors flavor(s) being dragged
* @param dropAction user action for drop operation
* @return true if the drop site is valid for the given target
*/
public boolean isDropSiteOk(GTreeNode destUserData, DataFlavor[] flavors, int dropAction);
/**
* Return true if the drop site is valid for the given target.
* @param destUserData destination for node being dragged
* @param flavors flavor(s) being dragged
* @param dropAction user action for drop operation
*/
boolean isDropSiteOk(GTreeNode destUserData, DataFlavor[] flavors, int dropAction);
/**
* Add the given transferable's data to the destination user data.
* @param destUserData destination node for the data.
* @param transferable the transferable being dragged whose data will be dropped.
* @param dropAction user action for drop operation
*/
void drop(GTreeNode destUserData, Transferable transferable, int dropAction);
/**
* Add the given transferable's data to the destination user data.
* @param destUserData destination node for the data.
* @param transferable the transferable being dragged whose data will be dropped.
* @param dropAction user action for drop operation
*/
public void drop(GTreeNode destUserData, Transferable transferable, int dropAction);
}

View File

@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +22,7 @@ import java.util.List;
import docking.widgets.tree.GTreeNode;
/**
* A generic transfer handler used by GTrees to handle transfering drag/drop data and clipboard
* A generic transfer handler used by GTrees to handle transferring drag/drop data and clipboard
* data.
*/
public interface GTreeTransferHandler {
@@ -35,16 +34,16 @@ public interface GTreeTransferHandler {
* @return the DataFlavors for the types of data that this transferable supports, based upon
* the given selection.
*/
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> transferNodes);
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> transferNodes);
/**
* Gets the transfer data from the selection based upon the given flavor.
* @param transferNodes The nodes from which to get the data.
* @param flavor The flavor of data to retrieve from the given selection.
* @return the transfer data from the selection based upon the given flavor.
* @throws UnsupportedFlavorException if the given flavor is not one of the supported flavors
* returned by {@link #getSupportedDataFlavors(List)}.
*/
public Object getTransferData(List<GTreeNode> transferNodes,
DataFlavor flavor) throws UnsupportedFlavorException;
/**
* Gets the transfer data from the selection based upon the given flavor.
* @param transferNodes The nodes from which to get the data.
* @param flavor The flavor of data to retrieve from the given selection.
* @return the transfer data from the selection based upon the given flavor.
* @throws UnsupportedFlavorException if the given flavor is not one of the supported flavors
* returned by {@link #getSupportedDataFlavors(List)}.
*/
public Object getTransferData(List<GTreeNode> transferNodes, DataFlavor flavor)
throws UnsupportedFlavorException;
}

View File

@@ -1,366 +0,0 @@
/* ###
* 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 ghidra.framework.main.datatable;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.CellRendererPane;
import javax.swing.table.*;
import docking.widgets.table.GTable;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.framework.main.datatree.DataTreeDragNDropHandler;
import ghidra.framework.model.DomainFile;
import ghidra.util.ColorUtils;
public class ProjectDataTableDnDHandler implements DragSourceListener, DragGestureListener {
private static final DataFlavor DOMAIN_FILE_LIST_FLAVOR =
DataTreeDragNDropHandler.localDomainFileFlavor;
static final DataFlavor[] ROW_DATA_FLAVORS = { DOMAIN_FILE_LIST_FLAVOR };
public static List<DomainFileInfo> selectedData;
private GTable table;
private ProjectDataTableModel model;
ProjectDataTableDnDHandler(GTable table, ProjectDataTableModel model) {
this.table = table;
this.model = model;
performMultiSelectionMouseFix();
DragSource dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(table, DnDConstants.ACTION_COPY_OR_MOVE,
this);
}
private void performMultiSelectionMouseFix() {
//
// Insert our listener into the front of the listeners so that we get a chance to
// handle events first.
//
MouseListener[] oldMouseListeners = table.getMouseListeners();
MouseMotionListener[] oldMouseMotionListeners = table.getMouseMotionListeners();
for (MouseListener l : oldMouseListeners) {
table.removeMouseListener(l);
}
for (MouseMotionListener l : oldMouseMotionListeners) {
table.removeMouseMotionListener(l);
}
DnDMouseListener newListener = new DnDMouseListener();
table.addMouseListener(newListener);
table.addMouseMotionListener(newListener);
for (MouseListener l : oldMouseListeners) {
table.addMouseListener(l);
}
for (MouseMotionListener l : oldMouseMotionListeners) {
table.addMouseMotionListener(l);
}
}
//======================================================
// DragSourceListener method
//======================================================
@Override
public void dragDropEnd(DragSourceDropEvent dsde) {
// renderer.setDropRow(-1);
// oldRow = -1;
table.repaint();
// provider.setIgnoreSelectionChange(false);
}
@Override
public void dragEnter(DragSourceDragEvent dsde) {
// don't care
}
@Override
public void dragExit(DragSourceEvent dse) {
setCursor(DnDConstants.ACTION_NONE, dse.getDragSourceContext());
}
private void setCursor(int action, DragSourceContext dragSourceContext) {
Cursor cursor = DragSource.DefaultCopyNoDrop;
switch (action) {
case DnDConstants.ACTION_COPY:
cursor = DragSource.DefaultCopyDrop;
break;
case DnDConstants.ACTION_MOVE:
cursor = DragSource.DefaultMoveDrop;
break;
case DnDConstants.ACTION_LINK:
cursor = DragSource.DefaultLinkDrop;
}
dragSourceContext.setCursor(cursor);
}
@Override
public void dragOver(DragSourceDragEvent dsde) {
setCursor(dsde.getDropAction(), dsde.getDragSourceContext());
}
@Override
public void dropActionChanged(DragSourceDragEvent dsde) {
// don't care
}
//======================================================
// DragGestureListener method
//======================================================
@Override
public void dragGestureRecognized(DragGestureEvent dragEvent) {
// check input event: if any button other than MB1 is pressed,
// don't attempt to process the drag and drop event.
InputEvent ie = dragEvent.getTriggerEvent();
int modifiers = ie.getModifiersEx();
if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 ||
(modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0) {
return;
}
selectedData = createSelectionList(table);
Transferable transferable = new DomainFileTransferable(selectedData);
Image image = getDragImage(selectedData);
try {
dragEvent.startDrag(DragSource.DefaultMoveDrop, image, new Point(0, 0), transferable,
this);
}
catch (InvalidDnDOperationException exc) {
// not sure why, but apparently we don't care
}
}
private Image getDragImage(List<DomainFileInfo> files) {
Container parent = table.getParent();
Dimension size = parent.getSize(); // assuming this is JViewport
BufferedImage bufferedImage =
new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics graphics = bufferedImage.getGraphics();
graphics.setClip(0, 0, size.width, size.height);
paintRecords(files, graphics);
// now we will create a fade effect using an alpha composite and a gradient
Graphics2D g2 = (Graphics2D) graphics;
GradientPaint mask;
Color treeBackground = table.getBackground();
Color transparentTreeBackground = ColorUtils.withAlpha(treeBackground, 200);
mask = new GradientPaint(0, 0, transparentTreeBackground, 0, size.height >> 1,
Palette.NO_COLOR);
g2.setPaint(mask);
// Sets the alpha composite
g2.setComposite(AlphaComposite.DstIn);
// Paints the mask
g2.fillRect(0, 0, size.width, size.height);
return bufferedImage;
}
/** Paint each of the given records that is inside of the clips */
private void paintRecords(List<DomainFileInfo> records, Graphics g) {
CellRendererPane rendererPane = new CellRendererPane();
paintCells(records, rendererPane, g);
}
private void paintCells(List<DomainFileInfo> domainFileInfos, CellRendererPane rendererPane,
Graphics g) {
TableColumnModel cm = table.getColumnModel();
int columnMargin = cm.getColumnMargin();
Rectangle clip = g.getClipBounds();
int yOffset = clip.y;
int rowCount = domainFileInfos.size();
int columnCount = table.getColumnCount();
int modelRow = model.getRowIndex(domainFileInfos.get(0));
Rectangle cellRect = table.getCellRect(modelRow, 0, false);
int startYOffset = cellRect.y;
TableColumn aColumn;
int columnWidth;
for (int row = 0; row < rowCount; row++) {
if (clip.y + clip.height < yOffset - startYOffset) {
return; // no need to paint past the end of our visible area
}
modelRow = model.getRowIndex(domainFileInfos.get(row));
cellRect = table.getCellRect(modelRow, 0, false);
cellRect.y -= startYOffset; // paint the row at the top of the graphics, not where it really lives
yOffset += cellRect.height;
for (int column = 0; column < columnCount; column++) {
aColumn = cm.getColumn(column);
columnWidth = aColumn.getWidth();
cellRect.width = columnWidth - columnMargin;
paintCell(rendererPane, g, cellRect, modelRow, column);
cellRect.x += columnWidth;
}
}
}
private void paintCell(CellRendererPane rendererPane, Graphics g, Rectangle cellRect, int row,
int column) {
TableCellRenderer tableRenderer = table.getCellRenderer(row, column);
Component component = table.prepareRenderer(tableRenderer, row, column);
rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y, cellRect.width,
cellRect.height, true);
}
private List<DomainFileInfo> createSelectionList(GTable tableToSelect) {
ArrayList<DomainFileInfo> list = new ArrayList<>();
int[] rows = table.getSelectedRows();
if (rows == null) {
return list;
}
for (int row : rows) {
list.add(model.getRowObject(row));
}
return list;
}
class DomainFileTransferable implements Transferable {
private List<DomainFileInfo> list;
DomainFileTransferable(List<DomainFileInfo> list) {
this.list = list;
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (DOMAIN_FILE_LIST_FLAVOR.equals(flavor)) {
return getDomainFileList();
}
throw new UnsupportedFlavorException(flavor);
}
private Object getDomainFileList() {
List<DomainFile> domainFileList = new ArrayList<>();
for (DomainFileInfo domainFileInfo : list) {
domainFileList.add(domainFileInfo.getDomainFile());
}
return domainFileList;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return ROW_DATA_FLAVORS;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DOMAIN_FILE_LIST_FLAVOR.equals(flavor);
}
}
private class DnDMouseListener extends MouseAdapter {
private boolean consuming = false;
private boolean didDrag = false;
@Override
public void mousePressed(MouseEvent e) {
consuming = maybeConsumeEvent(e);
didDrag = false;
}
@Override
public void mouseReleased(MouseEvent e) {
if (!consuming) {
return;
}
// continue to consume the event that was started during the pressed event, for symmetry
maybeConsumeEvent(e);
consuming = false;
if (!didDrag) {
//
// If we dragged, leave the initial selection, which does not disrupt the user's
// workflow; otherwise, select the clicked row. This allows users to change the
// selection by clicking in the table, which is the default table behavior.
//
table.clearSelection();
int row = table.rowAtPoint(e.getPoint());
table.selectRow(row);
}
}
@Override
public void mouseDragged(MouseEvent e) {
// always consume the drag so that Java does not change the selection
e.consume();
didDrag = true;
}
private boolean maybeConsumeEvent(MouseEvent e) {
if (!isBasicLeftClick(e)) {
return false;
}
// don't let other listeners process the event if we are 'pressing' the mouse
// button on an already selected row (to prevent de-selecting a multi-selection for
// a drag operation)
int row = table.rowAtPoint(e.getPoint());
if (table.isRowSelected(row)) {
e.consume();
return true;
}
return false;
}
private boolean isBasicLeftClick(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return false;
}
if (e.getClickCount() > 1) {
return false;
}
if (e.isControlDown() || e.isAltDown() || e.isShiftDown() || e.isMetaDown()) {
return false;
}
return true;
}
}
}

View File

@@ -16,8 +16,10 @@
package ghidra.framework.main.datatable;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.*;
import java.util.List;
@@ -25,10 +27,12 @@ import javax.swing.*;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.dnd.GTableDragProvider;
import docking.widgets.label.GHtmlLabel;
import docking.widgets.table.*;
import docking.widgets.table.threaded.*;
import ghidra.framework.main.FrontEndPlugin;
import ghidra.framework.main.datatree.DataTreeDragNDropHandler;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.*;
@@ -100,7 +104,8 @@ public class ProjectDataTablePanel extends JPanel {
gTable.setDefaultRenderer(Date.class, new DateCellRenderer());
gTable.setDefaultRenderer(DomainFileType.class, new TypeCellRenderer());
new ProjectDataTableDnDHandler(gTable, model);
// self-registering drag provider
new ProjectDataTableDragProvider();
}
/**
@@ -285,6 +290,58 @@ public class ProjectDataTablePanel extends JPanel {
// Inner Classes
//==================================================================================================
private class ProjectDataTableDragProvider extends GTableDragProvider<DomainFileInfo> {
private static final DataFlavor DOMAIN_FILE_LIST_FLAVOR =
DataTreeDragNDropHandler.localDomainFileFlavor;
private static final DataFlavor[] ROW_DATA_FLAVORS = { DOMAIN_FILE_LIST_FLAVOR };
ProjectDataTableDragProvider() {
super(gTable, model);
}
@Override
protected Transferable createDragTransferable(List<DomainFileInfo> items) {
return new DomainFileTransferable(items);
}
private class DomainFileTransferable implements Transferable {
private List<DomainFileInfo> list;
DomainFileTransferable(List<DomainFileInfo> list) {
this.list = list;
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (DOMAIN_FILE_LIST_FLAVOR.equals(flavor)) {
return getDomainFileList();
}
throw new UnsupportedFlavorException(flavor);
}
private Object getDomainFileList() {
List<DomainFile> domainFileList = new ArrayList<>();
for (DomainFileInfo domainFileInfo : list) {
domainFileList.add(domainFileInfo.getDomainFile());
}
return domainFileList;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return ROW_DATA_FLAVORS;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DOMAIN_FILE_LIST_FLAVOR.equals(flavor);
}
}
}
private class ProjectDataTableDomainFolderChangeListener implements DomainFolderChangeListener {
// TODO: file and subfolder events are only triggered if the parent folder has been

View File

@@ -24,8 +24,9 @@ import javax.swing.*;
import org.junit.Test;
import docking.ComponentProvider;
import docking.DialogComponentProvider;
import docking.action.DockingActionIf;
import docking.widgets.table.GTable;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.jar.ResourceFile;
@@ -60,6 +61,11 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
d = waitForJDialog("New Script");
pressButtonByText(d, "OK");
waitForSwing();
DialogComponentProvider dialog = waitForDialogComponent("Script Path Added/Enabled");
close(dialog);
captureIsolatedProvider(GhidraScriptEditorComponentProvider.class, 597, 600);
}
@@ -88,10 +94,8 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
@Test
public void testAssign_Key_Binding() throws Exception {
ComponentProvider componentProvider = showProvider(GhidraScriptComponentProvider.class);
JComponent component = componentProvider.getComponent();
DraggableScriptTable scriptTable =
(DraggableScriptTable) findComponentByName(component, "SCRIPT_TABLE");
GhidraScriptComponentProvider provider = showProvider(GhidraScriptComponentProvider.class);
GTable scriptTable = provider.getTable();
selectRow(scriptTable, "HelloWorldScript.java");
performAction("Key Binding", "GhidraScriptMgrPlugin", false);
@@ -101,10 +105,8 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
@Test
public void testSelect_Font() throws Exception {
ComponentProvider componentProvider = showProvider(GhidraScriptComponentProvider.class);
JComponent component = componentProvider.getComponent();
DraggableScriptTable scriptTable =
(DraggableScriptTable) findComponentByName(component, "SCRIPT_TABLE");
GhidraScriptComponentProvider provider = showProvider(GhidraScriptComponentProvider.class);
GTable scriptTable = provider.getTable();
selectRow(scriptTable, "HelloWorldScript.java");
performAction("Edit", "GhidraScriptMgrPlugin", false);
@@ -132,37 +134,26 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
@Test
public void testEdit_Script() throws Exception {
ResourceFile newScript = createHelloWorldScript("MyHelloWorldScript");
ComponentProvider componentProvider = showProvider(GhidraScriptComponentProvider.class);
createHelloWorldScript("MyHelloWorldScript");
GhidraScriptComponentProvider provider = showProvider(GhidraScriptComponentProvider.class);
JComponent component = componentProvider.getComponent();
DraggableScriptTable scriptTable =
(DraggableScriptTable) findComponentByName(component, "SCRIPT_TABLE");
selectRow(scriptTable, newScript.getName());
performAction("Edit", "GhidraScriptMgrPlugin", false);
waitForSwing();
GhidraScriptEditorComponentProvider provider =
getProvider(GhidraScriptEditorComponentProvider.class);
moveProviderToFront(provider, 557, 378);
captureProvider(provider);
}
@Test
public void testScript_Manager() {
ComponentProvider scriptManager = showProvider(GhidraScriptComponentProvider.class);
JComponent component = scriptManager.getComponent();
GhidraScriptComponentProvider provider = showProvider(GhidraScriptComponentProvider.class);
JComponent component = provider.getComponent();
final JSplitPane splitPane =
(JSplitPane) findComponentByName(component, "dataDescriptionSplit");
runSwing(() -> splitPane.setDividerLocation(0.63));
DraggableScriptTable scriptTable =
(DraggableScriptTable) findComponentByName(component, "SCRIPT_TABLE");
GTree scriptCategoryTree = (GTree) findComponentByName(component, "CATEGORY_TREE");
removeSuspectNodes(scriptCategoryTree);
GTable scriptTable = provider.getTable();
GTree scriptCategoryTree = provider.getTree();
selectPath(scriptCategoryTree, "Scripts", "Examples");
collapse(scriptCategoryTree, "Examples");// don't open examples (silly JTree)
@@ -170,8 +161,8 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
selectRow(scriptTable, "HelloWorldScript.java");
scriptTable.scrollToSelectedRow();
moveProviderToFront(scriptManager, 1333, 570);
captureProvider(scriptManager);
moveProviderToFront(provider, 1333, 570);
captureProvider(provider);
}
@Test
@@ -196,10 +187,8 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
public void testDelete_Script_Confirm() throws Exception {
createHelloWorldScript("FooScript");
ComponentProvider componentProvider = showProvider(GhidraScriptComponentProvider.class);
JComponent component = componentProvider.getComponent();
DraggableScriptTable scriptTable =
(DraggableScriptTable) findComponentByName(component, "SCRIPT_TABLE");
GhidraScriptComponentProvider provider = showProvider(GhidraScriptComponentProvider.class);
GTable scriptTable = provider.getTable();
selectRow(scriptTable, "FooScript.java");
performAction("Delete", "GhidraScriptMgrPlugin", false);
captureDialog();
@@ -278,27 +267,6 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
});
}
private void removeSuspectNodes(final GTree scriptCategoryTree) {
List<String> accepted = new ArrayList<>(
Arrays.asList("Examples", "Data Types", "Binary", "Functions", "Import", "Analysis"));
List<GTreeNode> toRemove = new ArrayList<>();
final GTreeNode rootNode = scriptCategoryTree.getViewRoot();
List<GTreeNode> children = rootNode.getChildren();
for (GTreeNode child : children) {
String name = child.getName();
if (!accepted.contains(name)) {
toRemove.add(child);
}
}
for (GTreeNode node : toRemove) {
rootNode.removeNode(node);
}
waitForTree(scriptCategoryTree);
}
private ResourceFile createTempScriptFile(String name) {
File userScriptsDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);