From 2ffb82df82c9aef8813915e42df2768a405ff7c0 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 21 May 2024 10:34:10 -0400 Subject: [PATCH] GP-4574 - Added support to create transient symbol tables from the Symbol Tree, Symbol Table and other transient symbol tables --- Ghidra/Features/Base/certification.manifest | 1 + .../Base/src/main/help/help/TOC_Source.xml | 1 + .../symbol_table_transient.htm | 71 ++ .../topics/SymbolTreePlugin/SymbolTree.htm | 11 + .../context/ProgramSymbolContextAction.java | 6 +- .../commentwindow/CommentWindowProvider.java | 5 - .../core/datawindow/DataWindowProvider.java | 5 - .../FunctionWindowProvider.java | 5 - .../LocationReferencesPanel.java | 5 - .../scalartable/ScalarSearchProvider.java | 7 +- .../core/script/DraggableScriptTable.java | 114 --- .../script/GhidraScriptComponentProvider.java | 8 +- .../core/strings/ViewStringsProvider.java | 5 - .../SymbolGTreeDragNDropHandler.java | 68 +- .../symboltree/SymbolTreeActionContext.java | 27 +- .../core/symboltree/SymbolTreeProvider.java | 2 + .../actions/CreateSymbolTableAction.java | 158 ++++ .../symtable/AbstractSymbolTableModel.java | 690 ++++++++++++++++++ .../plugin/core/symtable/SymbolEditor.java | 4 +- .../app/plugin/core/symtable/SymbolPanel.java | 12 +- .../plugin/core/symtable/SymbolProvider.java | 2 +- .../plugin/core/symtable/SymbolRenderer.java | 10 +- .../core/symtable/SymbolTableDnDAdapter.java | 153 ++++ .../symtable/SymbolTableDragProvider.java | 46 ++ .../core/symtable/SymbolTableModel.java | 681 +---------------- .../core/symtable/SymbolTablePlugin.java | 6 +- .../TransientSymbolTableDnDAdapter.java | 56 ++ .../symtable/TransientSymbolTableModel.java | 128 ++++ .../core/symtable/dnd/SymbolDataFlavor.java | 34 + .../core/symtable/dnd/SymbolTransferData.java | 43 ++ .../core/symtable/dnd/SymbolTransferable.java | 62 ++ .../core/table/TableComponentProvider.java | 19 +- .../app/util/SelectionTransferable.java | 143 ++-- .../util/table/GhidraProgramTableModel.java | 6 + .../actions/MakeProgramSelectionAction.java | 14 +- .../AbstractGhidraScriptMgrPluginTest.java | 8 +- .../DragDropTableSelectionMouseListener.java | 139 ++++ .../java/docking/dnd/GTableDragProvider.java | 154 ++++ .../java/docking/dnd/GhidraTransferable.java | 105 --- .../widgets/table/GTableFilterPanel.java | 39 +- .../docking/widgets/table/TableUtils.java | 64 +- .../table/threaded/ThreadedTableModel.java | 11 +- .../ThreadedTableModelListenerAdapter.java | 34 + .../tree/internal/GTreeDragNDropAdapter.java | 14 +- .../tree/support/GTreeDragNDropHandler.java | 46 +- .../tree/support/GTreeTransferHandler.java | 25 +- .../datatable/ProjectDataTableDnDHandler.java | 366 ---------- .../main/datatable/ProjectDataTablePanel.java | 59 +- .../GhidraScriptMgrPluginScreenShots.java | 74 +- 49 files changed, 2152 insertions(+), 1594 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDnDAdapter.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDragProvider.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableDnDAdapter.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolDataFlavor.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferData.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferable.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/dnd/DragDropTableSelectionMouseListener.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/dnd/GTableDragProvider.java delete mode 100644 Ghidra/Framework/Docking/src/main/java/docking/dnd/GhidraTransferable.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelListenerAdapter.java delete mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTableDnDHandler.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 5e3032fd05..35177a917d 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -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| diff --git a/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml b/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml index 217e97a571..b87c1a2b75 100644 --- a/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml @@ -175,6 +175,7 @@ + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm new file mode 100644 index 0000000000..bd5a6b7f8f --- /dev/null +++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm @@ -0,0 +1,71 @@ + + + + + + Temporary Symbol Table + + + + +

Temporary Symbol Table

+ +

The Temporary Symbol Table is a table created by providers that have the Create Table + action, such as the Symbol Tree or + the Symbol Table. It contains the + same columns as the Symbol Table. +

+ +

This table functions much like the + Search Results Table. For example, this table supports the ability to remove rows. +

+ +

Drag and Drop

+ +
+

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. +

+
+ +

Actions

+ +
+

+ This table supports the general actions of the + Search Results Window. +

+ +

Create Table From Selection

+ +
+

+ This table supports the ability to create a new table with the selected rows. +

+
+ +
+ + +

Related Topics:

+ +
+ +
+ +
+
+
+ + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm index 749b0e0473..29b45691e3 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm @@ -361,6 +361,17 @@ +

Create Table From Selection

+ +
+

Create a new temporary + Symbol Table with the symbols + in the current selection. The table's features are similar to the + search results table. +

+
+ +

Symbol Tree Snapshots

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolContextAction.java index d195803448..c4103e9f76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolContextAction.java @@ -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)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java index 5a65cf2f04..16cf6f6206 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowProvider.java @@ -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()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java index da47837934..a1c740ec9f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowProvider.java @@ -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) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java index 3eb04d225e..27d9244b33 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java @@ -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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesPanel.java index f745bcf4fb..68e71024bb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesPanel.java @@ -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. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java index 34a7c1beea..6aebe42f08 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java deleted file mode 100644 index 4d338e07a4..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/DraggableScriptTable.java +++ /dev/null @@ -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 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) { - // - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index e0b1f6c33c..15fa34b459 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -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; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java index bc9f80b782..c9c1fdc67b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java @@ -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); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTreeDragNDropHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTreeDragNDropHandler.java index 8b0e360ee9..d12c57507c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTreeDragNDropHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolGTreeDragNDropHandler.java @@ -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 transferNodes, DataFlavor flavor) throws UnsupportedFlavorException { - return transferNodes; + + if (SymbolDataFlavor.DATA_FLAVOR.equals(flavor)) { + return getSymbols(transferNodes); + } + + List 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 transferNodes) { + List 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); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java index ad004fae9e..950bdb15f5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeActionContext.java @@ -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 getSymbols(TreePath[] selectionPaths) { if (selectionPaths == null) { - return null; + return Collections.emptyList(); } List 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; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index 2175ed71d2..2563b0893e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -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); } //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java new file mode 100644 index 0000000000..029d32838a --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java @@ -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 rowObjects = new HashSet<>(); + Iterable 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 provider = + service.showTable("Symbols", "Symbols", model, "Symbols", navigatable); + + provider.setActionContextProvider(mouseEvent -> { + + GThreadedTablePanel tablePanel = provider.getThreadedTablePanel(); + GTable table = tablePanel.getTable(); + List 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 tablePanel = provider.getThreadedTablePanel(); + GhidraTable table = tablePanel.getTable(); + + configureSymbolTable(tool, table, model, program); + } + + private void addActions(TableComponentProvider 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 getSelectedSymbols(GTable table, TransientSymbolTableModel model) { + List 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; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java new file mode 100644 index 0000000000..270b5f7ca7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java @@ -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 { + + private static final Comparator 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 createTableColumnDescriptor() { + TableColumnDescriptor 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 rowObjects) { + if (rowObjects == null || rowObjects.isEmpty()) { + return; + } + + tool.setStatusInfo(""); + List deleteList = new LinkedList<>(); + CompoundCmd 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 createSortComparator(int columnIndex) { + DynamicTableColumn 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 { + + @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 { + + 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 getColumnRenderer() { + return renderer; + } + + private class PinnedRenderer extends GBooleanCellRenderer + implements AbstractWrapperTypeColumnRenderer { + // body is handled by parents + } + } + + private class LocationTableColumn + extends AbstractProgramLocationTableColumn { + + @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 { + + @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 { + + @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 { + + @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 { + + private GColumnRenderer 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 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 { + + 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 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 { + // body is handled by parents + } + } + + private class OffcutReferenceCountTableColumn + extends AbstractProgramBasedDynamicTableColumn { + + 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 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 { + // body is handled by parents + } + } + + private class UserTableColumn + extends AbstractProgramBasedDynamicTableColumn { + + @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 { + + @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; + } + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java index 8ae690cc78..c9722b595e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolEditor.java @@ -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()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java index ef24da439b..902629f9a5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java index 0b6d8dd202..9e877a0be4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java @@ -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(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java index 0c05518968..1bf76c16c1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolRenderer.java @@ -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) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDnDAdapter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDnDAdapter.java new file mode 100644 index 0000000000..de5bbf4152 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDnDAdapter.java @@ -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 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 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 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 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 + + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDragProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDragProvider.java new file mode 100644 index 0000000000..95ea74cf99 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableDragProvider.java @@ -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 { + + public SymbolTableDragProvider(GTable table, RowObjectTableModel model) { + super(table, model); + } + + @Override + protected Transferable createDragTransferable(List items) { + List symbols = new ArrayList<>(); + for (SymbolRowObject ro : items) { + symbols.add(ro.getSymbol()); + } + return new SymbolTransferable(table, symbols); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java index 02d3dca934..0b25a47e1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java @@ -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 { +class SymbolTableModel extends AbstractSymbolTableModel { - private static final Comparator 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 createTableColumnDescriptor() { - TableColumnDescriptor 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 { 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 { } } - @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 rowObjects) { - if (rowObjects == null || rowObjects.isEmpty()) { - return; - } - - tool.setStatusInfo(""); - List deleteList = new LinkedList<>(); - CompoundCmd 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 createSortComparator(int columnIndex) { - DynamicTableColumn 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 { - - @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 { - - 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 getColumnRenderer() { - return renderer; - } - - private class PinnedRenderer extends GBooleanCellRenderer - implements AbstractWrapperTypeColumnRenderer { - // body is handled by parents - } - } - - private class LocationTableColumn - extends AbstractProgramLocationTableColumn { - - @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 { - - @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 { - - @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 { - - @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 { - - private GColumnRenderer 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 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 { - - 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 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 { - // body is handled by parents - } - } - - private class OffcutReferenceCountTableColumn - extends AbstractProgramBasedDynamicTableColumn { - - 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 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 { - // body is handled by parents - } - } - - private class UserTableColumn - extends AbstractProgramBasedDynamicTableColumn { - - @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 { - - @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; - } - - } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java index 98bf948ba2..854a3dc15a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java @@ -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() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableDnDAdapter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableDnDAdapter.java new file mode 100644 index 0000000000..9100c5ca2e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableDnDAdapter.java @@ -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 symbols) { + + List 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)); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java new file mode 100644 index 0000000000..60e1a55f37 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java @@ -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 rowObjects; + + private SwingUpdateManager updater = new SwingUpdateManager(this::fireTableDataChanged); + + public TransientSymbolTableModel(PluginTool tool, Program program, + HashSet 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 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 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(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolDataFlavor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolDataFlavor.java new file mode 100644 index 0000000000..6a60987f46 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolDataFlavor.java @@ -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"); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferData.java new file mode 100644 index 0000000000..7e1863d868 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferData.java @@ -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 symbols; + + public SymbolTransferData(Component source, List symbols) { + this.source = source; + this.symbols = symbols; + } + + public Component getSource() { + return source; + } + + public List getSymbols() { + return symbols; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferable.java new file mode 100644 index 0000000000..7f3f74d0d5 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/dnd/SymbolTransferable.java @@ -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 symbols; + private Component source; + + public SymbolTransferable(Component source, List 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; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java index a664345e88..ea338f4d03 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java @@ -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 extends ComponentProviderAdapter private DockingAction selectAction; private DockingAction removeItemsAction; + private Function 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 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 extends ComponentProviderAdapter tableFilterPanel.dispose(); } - public GThreadedTablePanel getThreadedTablePanel() { + public GhidraThreadedTablePanel getThreadedTablePanel() { return threadedPanel; } @@ -405,7 +406,17 @@ public class TableComponentProvider 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 contextProvider) { + this.contextProvider = contextProvider; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/SelectionTransferable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/SelectionTransferable.java index 19291dd8a4..9ad314210d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/SelectionTransferable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/SelectionTransferable.java @@ -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 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 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) { - } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraProgramTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraProgramTableModel.java index 683b37496e..03456993b1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraProgramTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraProgramTableModel.java @@ -70,4 +70,10 @@ public abstract class GhidraProgramTableModel public Address getAddress(int row) { return null; } + + @Override + public void dispose() { + program = null; + super.dispose(); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java index 31810b8b4b..60d060870e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java @@ -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) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 7613257fc0..b425febf24 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -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(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/dnd/DragDropTableSelectionMouseListener.java b/Ghidra/Framework/Docking/src/main/java/docking/dnd/DragDropTableSelectionMouseListener.java new file mode 100644 index 0000000000..b570795864 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/dnd/DragDropTableSelectionMouseListener.java @@ -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; + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/dnd/GTableDragProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/dnd/GTableDragProvider.java new file mode 100644 index 0000000000..618d9e0028 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/dnd/GTableDragProvider.java @@ -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 the row object type + */ +public abstract class GTableDragProvider + implements DragSourceListener, DragGestureListener { + + protected GTable table; + protected RowObjectTableModel rowObjectModel; + + public GTableDragProvider(GTable table, RowObjectTableModel 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 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 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 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 objects = new ArrayList<>(); + for (int row : rows) { + ROW_OBJECT ro = rowObjectModel.getRowObject(row); + objects.add(ro); + } + return objects; + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/dnd/GhidraTransferable.java b/Ghidra/Framework/Docking/src/main/java/docking/dnd/GhidraTransferable.java deleted file mode 100644 index cc8c9d3459..0000000000 --- a/Ghidra/Framework/Docking/src/main/java/docking/dnd/GhidraTransferable.java +++ /dev/null @@ -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 flavorList = Arrays.asList(flavors); - private List 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(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) { - } - -} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java index 3435e1a42b..bec4d6aedd 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java @@ -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 extends JPanel { * @param items the row objects to select */ public void setSelectedItems(List 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 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); } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java index e057e24d6d..dde83d35e4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableUtils.java @@ -15,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 void setSelectedItems(JTable table, List 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 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(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java index e771a70a37..8e226fb3fc 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java @@ -165,8 +165,9 @@ public abstract class ThreadedTableModel } /** - * 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)}. + * *

This exists to handle whether this model should load incrementally. * * @param monitor the monitor @@ -182,11 +183,15 @@ public abstract class ThreadedTableModel } // do the load now - ListAccumulator accumulator = new ListAccumulator<>(); + ListAccumulator accumulator = createAccumulator(); doLoad(accumulator, monitor); return accumulator.asList(); } + protected ListAccumulator createAccumulator() { + return new ListAccumulator(); + } + private void initializeWorker() { if (worker == null) { worker = new Worker("GTable Worker: " + getName(), incrementalMonitor); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelListenerAdapter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelListenerAdapter.java new file mode 100644 index 0000000000..fcfabb2a80 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModelListenerAdapter.java @@ -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 + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/internal/GTreeDragNDropAdapter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/internal/GTreeDragNDropAdapter.java index c62a786aee..cbabf3aa9f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/internal/GTreeDragNDropAdapter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/internal/GTreeDragNDropAdapter.java @@ -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 diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeDragNDropHandler.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeDragNDropHandler.java index d3c5e1b805..1de2ae82c8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeDragNDropHandler.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeDragNDropHandler.java @@ -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 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 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); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeTransferHandler.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeTransferHandler.java index 6949f6ce1a..6f8510f3c8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeTransferHandler.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeTransferHandler.java @@ -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 transferNodes); + public DataFlavor[] getSupportedDataFlavors(List 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 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 transferNodes, DataFlavor flavor) + throws UnsupportedFlavorException; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTableDnDHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTableDnDHandler.java deleted file mode 100644 index 66d5bde123..0000000000 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTableDnDHandler.java +++ /dev/null @@ -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 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 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 records, Graphics g) { - CellRendererPane rendererPane = new CellRendererPane(); - paintCells(records, rendererPane, g); - } - - private void paintCells(List 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 createSelectionList(GTable tableToSelect) { - ArrayList 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 list; - - DomainFileTransferable(List 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 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; - } - } - -} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java index 3ee85c9ffa..aa12a134e6 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java @@ -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 { + + 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 items) { + return new DomainFileTransferable(items); + } + + private class DomainFileTransferable implements Transferable { + private List list; + + DomainFileTransferable(List 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 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 diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java index 9d5c199055..78f20580b1 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java @@ -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 accepted = new ArrayList<>( - Arrays.asList("Examples", "Data Types", "Binary", "Functions", "Import", "Analysis")); - - List toRemove = new ArrayList<>(); - final GTreeNode rootNode = scriptCategoryTree.getViewRoot(); - List 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);