mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-25 17:00:36 -09:00
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:
@@ -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|
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user