From 28f6500039b344d9d262a06bc6b99d8fd524d5bf Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 2 Aug 2019 19:41:30 -0400 Subject: [PATCH 1/2] GT-3044 - Table Actions - updated table actions to use the modern action system --- .../app/context/ProgramActionContext.java | 30 ++- .../context/ProgramSymbolActionContext.java | 10 +- .../app/merge/MergeManagerProvider.java | 2 +- .../merge/tool/ListingMergePanelProvider.java | 4 +- .../core/bookmark/BookmarkProvider.java | 2 +- .../core/codebrowser/CodeViewerProvider.java | 10 +- .../core/datamgr/DataTypesProvider.java | 4 +- .../editor/TextEditorComponentProvider.java | 22 +- .../FunctionWindowProvider.java | 6 +- .../ui/InstructionTable.java | 11 +- .../instructionsearch/ui/PreviewTable.java | 26 ++- .../core/programtree/TreeViewProvider.java | 98 +++------ .../ViewManagerComponentProvider.java | 6 +- .../core/programtree/ViewProviderService.java | 77 +++---- .../GhidraScriptEditorComponentProvider.java | 2 +- .../app/plugin/debug/DbViewerProvider.java | 8 - .../PropertyManagerProvider.java | 2 +- .../ListingCodeComparisonPanel.java | 29 ++- .../main/OpenVersionedFileDialog.java | 2 +- .../plugins/fsbrowser/FSBActionContext.java | 2 +- .../ModuleAlgorithmPluginTest.java | 3 +- .../core/bookmark/BookmarkPluginTest.java | 8 +- .../core/codebrowser/HeaderActionsTest.java | 17 +- .../plugin/core/codebrowser/HeaderTest.java | 12 +- .../core/module/AutoRenamePluginTest.java | 36 +--- .../core/module/ModuleSortPluginTest.java | 15 +- .../ProviderNavigationPluginTest.java | 2 +- .../java/ghidra/test/DummyToolActions.java | 5 + .../DecompilerCodeComparisonPanel.java | 8 +- .../DualDecompilerActionContext.java | 8 +- .../SetFormatDialogComponentProvider.java | 2 +- .../AbstractFunctionGraphTest.java | 4 +- .../plugin/context/FcgActionContext.java | 2 +- .../VTFunctionAssociationProvider.java | 2 +- .../VTMarkupItemsTableProvider.java | 2 +- .../src/main/java/docking/ActionContext.java | 138 ++++++++++-- .../java/docking/ComponentLoadedListener.java | 4 +- .../main/java/docking/ComponentProvider.java | 23 +- .../java/docking/DialogComponentProvider.java | 14 +- ...ogComponentProviderPopupActionManager.java | 4 +- .../main/java/docking/DockingActionProxy.java | 5 + .../java/docking/DockingKeyBindingAction.java | 2 +- .../src/main/java/docking/DockingTool.java | 7 +- .../java/docking/DockingWindowManager.java | 29 ++- .../KeyBindingOverrideKeyEventDispatcher.java | 3 +- .../main/java/docking/MenuBarMenuHandler.java | 3 +- .../main/java/docking/PopupActionManager.java | 22 +- .../main/java/docking/PopupMenuHandler.java | 3 +- .../java/docking/action/DockingAction.java | 3 +- .../java/docking/action/DockingActionIf.java | 5 + .../docking/action/MultipleKeyAction.java | 2 +- .../java/docking/actions/ActionAdapter.java | 2 +- .../docking/actions/DockingToolActions.java | 2 + .../java/docking/actions/KeyBindingUtils.java | 31 ++- .../java/docking/actions/ToolActions.java | 19 +- .../java/docking/menu/MenuItemManager.java | 25 +-- .../MultipleActionDockingToolbarButton.java | 2 +- .../java/docking/menu/ToolBarItemManager.java | 2 +- .../docking/test/AbstractDockingTest.java | 23 +- .../filechooser/GhidraFileChooser.java | 15 -- .../java/docking/widgets/table/GTable.java | 198 ++++++++++-------- .../widgets/table/GTableFilterPanel.java | 2 +- .../widgets/table/TableColumnModelState.java | 2 +- .../main/java/docking/widgets/tree/GTree.java | 2 +- .../util/bean/EmptyBorderButtonTest.java | 38 +--- .../framework/main/ToolActionManager.java | 8 +- .../datatable/ProjectDataActionContext.java | 2 +- .../main/datatable/ProjectDataTablePanel.java | 12 +- .../main/datatree/VersionHistoryDialog.java | 2 +- .../framework/plugintool/PluginTool.java | 2 + 70 files changed, 642 insertions(+), 493 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java index 2b6913b212..d9e8761c05 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.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,26 +15,37 @@ */ package ghidra.app.context; -import ghidra.program.model.listing.Program; +import java.awt.Component; +import java.awt.KeyboardFocusManager; + import docking.ActionContext; import docking.ComponentProvider; +import ghidra.program.model.listing.Program; public class ProgramActionContext extends ActionContext { protected final Program program; - public ProgramActionContext(ComponentProvider provider, Program program) { this(provider, program, null); } - - public ProgramActionContext(ComponentProvider provider, Program program, Object contextObject) { - super(provider, contextObject); - this.program = program; + + public ProgramActionContext(ComponentProvider provider, Program program, + Component sourceComponent) { + this(provider, program, sourceComponent, sourceComponent); + } + + public ProgramActionContext(ComponentProvider provider, Program program, + Component sourceComponent, Object contextObject) { + super(provider, contextObject, sourceComponent); + this.program = program; + + // the case where the first constructor is called, which does not specify the component + if (sourceComponent == null) { + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + setSourceObject(kfm.getFocusOwner()); + } } - /** - * @return Returns the program. - */ public Program getProgram() { return program; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java index 4b686bfa12..2bb3913102 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramSymbolActionContext.java @@ -15,6 +15,7 @@ */ package ghidra.app.context; +import java.awt.Component; import java.util.Iterator; import java.util.NoSuchElementException; @@ -27,14 +28,9 @@ public class ProgramSymbolActionContext extends ProgramActionContext { private final long[] symbolIDs; - public ProgramSymbolActionContext(ComponentProvider provider, Program program, - long[] symbolIDs) { - this(provider, program, symbolIDs, null); - } - public ProgramSymbolActionContext(ComponentProvider provider, Program program, long[] symbolIDs, - Object contextObj) { - super(provider, program, contextObj); + Component sourceComponent) { + super(provider, program, sourceComponent); this.symbolIDs = symbolIDs; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java index ce89f780b8..c5ab8722f4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/MergeManagerProvider.java @@ -87,7 +87,7 @@ class MergeManagerProvider extends ComponentProviderAdapter { if (event != null && event.getSource() instanceof FieldHeaderComp) { FieldHeaderComp comp = (FieldHeaderComp) event.getSource(); FieldHeaderLocation fieldHeaderLocation = comp.getFieldHeaderLocation(event.getPoint()); - return new ActionContext(this, fieldHeaderLocation); + return createContext(fieldHeaderLocation); } if (mergeManager instanceof ProgramMultiUserMergeManager) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanelProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanelProvider.java index 66409d518a..ecdc282480 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanelProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanelProvider.java @@ -47,7 +47,7 @@ public class ListingMergePanelProvider extends ComponentProviderAdapter @Override public ActionContext getActionContext(MouseEvent event) { Object obj = mergePanel.getActionContext(event); - return new ActionContext(this, obj); + return createContext(obj); } void dispose() { @@ -55,7 +55,7 @@ public class ListingMergePanelProvider extends ComponentProviderAdapter } @Override - public List getPopupActions(DockingTool tool, ActionContext context) { + public List getPopupActions(DockingTool dt, ActionContext context) { ListingPanel resultPanel = mergePanel.getResultPanel(); if (resultPanel != null) { return resultPanel.getHeaderActions(getName()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java index 9ed8ec667d..289b901b20 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java @@ -105,7 +105,7 @@ public class BookmarkProvider extends ComponentProviderAdapter { if (program == null) { return null; } - return new ProgramActionContext(this, program); + return new ProgramActionContext(this, program, bookmarkTable); } void setGoToService(GoToService goToService) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index 35a28963e2..3f1ae1b5e7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -269,17 +269,17 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter FieldHeader headerPanel = listingPanel.getFieldHeader(); if (headerPanel != null && source instanceof FieldHeaderComp) { FieldHeaderLocation fhLoc = headerPanel.getFieldHeaderLocation(event.getPoint()); - return new ActionContext(this, fhLoc); + return createContext(fhLoc); } if (otherPanel != null && otherPanel.isAncestorOf((Component) source)) { Object obj = getContextForMarginPanels(otherPanel, event); if (obj != null) { - return new ActionContext(this, obj); + return createContext(obj); } return new OtherPanelContext(this, program); } - return new ActionContext(this, getContextForMarginPanels(listingPanel, event)); + return createContext(getContextForMarginPanels(listingPanel, event)); } private Object getContextForMarginPanels(ListingPanel lp, MouseEvent event) { @@ -955,11 +955,11 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } @Override - public List getPopupActions(DockingTool tool, ActionContext context) { + public List getPopupActions(DockingTool dt, ActionContext context) { if (context.getComponentProvider() == this) { return listingPanel.getHeaderActions(getName()); } - return new ArrayList<>(); + return null; } //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index 0d03a6e098..32588ff96d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.datamgr; +import java.awt.Component; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -333,7 +334,8 @@ public class DataTypesProvider extends ComponentProviderAdapter { Object source = event.getSource(); if (source instanceof JTextField || source instanceof JTextPane) { - return new ActionContext(this, source, source); + Component component = (Component) source; + return new ActionContext(this, source, component); } Point point = event.getPoint(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/editor/TextEditorComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/editor/TextEditorComponentProvider.java index c5d706fa3a..39c204ae3e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/editor/TextEditorComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/editor/TextEditorComponentProvider.java @@ -18,7 +18,6 @@ package ghidra.app.plugin.core.editor; import java.awt.Dimension; import java.awt.Font; import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; import java.io.*; import java.util.Iterator; import java.util.List; @@ -91,10 +90,10 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter { return textarea.getText(); } - private void initialize(TextEditorManagerPlugin plugin, String textContents) { - this.plugin = plugin; + private void initialize(TextEditorManagerPlugin p, String textContents) { + this.plugin = p; - setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); + setHelpLocation(new HelpLocation(p.getName(), p.getName())); title = textFileName + (isReadOnly() ? " (Read-Only) " : ""); setTitle(title); @@ -106,7 +105,7 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter { addToTool(); setVisible(true); - plugin.getTool().setStatusInfo("Press F1 for help."); + p.getTool().setStatusInfo("Press F1 for help."); createActions(); } @@ -278,8 +277,7 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter { ActionContextProvider acp = e -> { ComponentProvider p = TextEditorComponentProvider.this; - Object context = TextEditorComponentProvider.this; - return new ActionContext(p, context); + return new ActionContext(p); }; KeyBindingUtils.registerAction(textarea, saveAction, acp); @@ -374,11 +372,6 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter { } } - @Override - public ActionContext getActionContext(MouseEvent event) { - return new ActionContext(this, this); - } - @Override public JComponent getComponent() { return scrollpane; @@ -387,9 +380,10 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter { //================================================================================================== // Inner Classes //================================================================================================== + /** * Special JTextArea that knows how to properly handle it's key events. - * @see {@link #processKeyBinding(KeyStroke, KeyEvent, int, boolean)} + * @see #processKeyBinding(KeyStroke, KeyEvent, int, boolean) */ private class KeyMasterTextArea extends JTextArea { private static final long serialVersionUID = 1L; @@ -455,7 +449,7 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter { return true; } - return SwingUtilities.notifyAction(action, ks, e, this, e.getModifiers()); + return SwingUtilities.notifyAction(action, ks, e, this, e.getModifiersEx()); } } return false; 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 ea1b1cf89a..bf174778d2 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 @@ -71,11 +71,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter { @Override public ActionContext getActionContext(MouseEvent event) { - if (event != null && event.getSource() == functionTable) { - return new ActionContext(this, functionTable); - } - - return null; + return new ActionContext(this, functionTable); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java index 89be3de7d3..1723488baf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java @@ -21,8 +21,7 @@ import java.util.List; import javax.swing.*; -import docking.*; -import docking.action.DockingActionIf; +import docking.DockingWindowManager; import docking.widgets.EmptyBorderButton; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.*; @@ -95,13 +94,9 @@ public class InstructionTable extends AbstractInstructionTable { dialog.getSearchData().registerForGuiUpdates(this); } - /** - * Override from abstract class so we can remove the select and copy options - * (which is all of them). - */ @Override - public List getPopupActions(DockingTool tool, ActionContext context) { - return null; + protected boolean supportsPopupActions() { + return false; } public InsertBytesWidget getInsertBytesWidget() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java index 3908ff59d8..9243081140 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java @@ -135,17 +135,25 @@ public class PreviewTable extends AbstractInstructionTable { * Adds custom context-sensitive menus to the table. This does NOT modify * any existing menus; it simply adds to them. */ - @Override - public List getPopupActions(DockingTool tool, ActionContext context) { - // Invoke the base class method to add default menu options. - List list = super.getPopupActions(tool, context); + // TODO + // TODO + // TODO + // TODO Change the custom menu items to be real Docking Actions + // TODO + // TODO - // And now add our own. - addCustomMenuItems(list); - - return list; - } +// @Override +// public List getPopupActions(DockingTool tool, ActionContext context) { +// +// // Invoke the base class method to add default menu options. +// List list = super.getPopupActions(tool, context); +// +// // And now add our own. +// addCustomMenuItems(list); +// +// return list; +// } /** * Replaces the contents of the preview table at the given row with the diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/TreeViewProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/TreeViewProvider.java index 95bc1e001a..9fdc34bbc7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/TreeViewProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/TreeViewProvider.java @@ -15,6 +15,15 @@ */ package ghidra.app.plugin.core.programtree; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.LinkedList; + +import javax.swing.JComponent; +import javax.swing.event.ChangeEvent; + +import docking.ActionContext; +import docking.action.DockingAction; import ghidra.app.events.ViewChangedPluginEvent; import ghidra.app.services.GoToService; import ghidra.app.services.ViewManagerService; @@ -25,15 +34,6 @@ import ghidra.program.util.*; import ghidra.util.Msg; import ghidra.util.task.*; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.LinkedList; - -import javax.swing.JComponent; -import javax.swing.event.ChangeEvent; - -import docking.action.DockingAction; - /** * Provides a view of the program tree. */ @@ -51,9 +51,6 @@ class TreeViewProvider implements ViewProviderService { private final static int DELAY = 500; - /** - * Constructor for TreeViewProvider. - */ public TreeViewProvider(String treeName, final ProgramTreePlugin plugin) { treePanel = new ProgramTreePanel(treeName, plugin); @@ -72,25 +69,16 @@ class TreeViewProvider implements ViewProviderService { }); } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#getViewComponent() - */ @Override public JComponent getViewComponent() { return treePanel; } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#getViewName() - */ @Override public String getViewName() { return treePanel.getTreeName(); } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#setHasFocus(JComponent, boolean) - */ @Override public void setHasFocus(boolean hasFocus) { treePanel.setHasFocus(hasFocus); @@ -107,33 +95,26 @@ class TreeViewProvider implements ViewProviderService { } } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#getActivePopupObject(MouseEvent) - */ @Override public Object getActivePopupObject(MouseEvent event) { return treePanel.prepareSelectionForPopup(event); } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#getActiveObject() - */ + @Override + public ActionContext getActionContext(MouseEvent event) { + return new ActionContext().setContextObject(getActivePopupObject(event)); + } + @Override public Object getActiveObject() { return treePanel.getSelectedNode(); } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#getToolBarActions() - */ @Override public DockingAction[] getToolBarActions() { return plugin.getToolBarActions(); } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#viewClosed() - */ @Override public boolean viewClosed() { if (program == null) { @@ -146,9 +127,6 @@ class TreeViewProvider implements ViewProviderService { return false; } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#viewDeleted() - */ @Override public boolean viewDeleted() { if (program == null) { @@ -161,9 +139,6 @@ class TreeViewProvider implements ViewProviderService { return false; } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#viewRenamed(String) - */ @Override public boolean viewRenamed(String newName) { if (program == null) { @@ -177,9 +152,6 @@ class TreeViewProvider implements ViewProviderService { return false; } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#addToView(ProgramLocation) - */ @Override public AddressSetView addToView(ProgramLocation loc) { if (program != null && loc != null && loc.getAddress() != null) { @@ -192,9 +164,6 @@ class TreeViewProvider implements ViewProviderService { } - /** - * @see ghidra.app.plugin.core.programtree.ViewProviderService#getCurrentView() - */ @Override public AddressSetView getCurrentView() { return view; @@ -212,14 +181,14 @@ class TreeViewProvider implements ViewProviderService { ProgramModule root = program.getListing().getRootModule(group.getTreeName()); ProgramModule[] parents = group.getParents(); if (parents != null && parents.length > 0) { - for (int i = 0; i < parents.length; i++) { - LinkedList myList = new LinkedList(list); - myList.addFirst(parents[i].getName()); - if (parents[i] == root) { + for (ProgramModule parent : parents) { + LinkedList myList = new LinkedList<>(list); + myList.addFirst(parent.getName()); + if (parent == root) { pathNameList.add(myList); } else { - setAncestorList(parents[i], myList, pathNameList); + setAncestorList(parent, myList, pathNameList); } } } @@ -278,15 +247,12 @@ class TreeViewProvider implements ViewProviderService { /** * Set the tree selection. - * @param groupPaths + * @param paths the paths to select */ void setGroupSelection(GroupPath[] paths) { treePanel.setGroupSelection(paths); } - /** - * Write group paths in the view. - */ void writeDataState(SaveState saveState) { GroupView currentView = treePanel.getGroupView(); String treeName = treePanel.getTreeName(); @@ -299,9 +265,6 @@ class TreeViewProvider implements ViewProviderService { } } - /** - * Read the state from save state object. - */ void readDataState(SaveState saveState) { String treeName = treePanel.getTreeName(); int numGroups = saveState.getInt(NUMBER_OF_GROUPS + treeName, 0); @@ -329,9 +292,6 @@ class TreeViewProvider implements ViewProviderService { return treePanel.getDnDTree(); } - /** - * Get the address set currently being viewed. - */ AddressSet getView() { if (program == null) { return new AddressSet(); @@ -342,8 +302,8 @@ class TreeViewProvider implements ViewProviderService { return set; } String treeName = treePanel.getTreeName(); - for (int i = 0; i < gp.length; i++) { - Group group = gp[i].getGroup(program, treeName); + for (GroupPath element : gp) { + Group group = element.getGroup(program, treeName); if (group == null) { continue; } @@ -365,8 +325,8 @@ class TreeViewProvider implements ViewProviderService { return; } view = getView(); - plugin.firePluginEvent(new ViewChangedPluginEvent(plugin.getName(), - treePanel.getTreeName(), view)); + plugin.firePluginEvent( + new ViewChangedPluginEvent(plugin.getName(), treePanel.getTreeName(), view)); } /** @@ -379,7 +339,7 @@ class TreeViewProvider implements ViewProviderService { if (fragment == null) { return; } - LinkedList list = new LinkedList(); + LinkedList list = new LinkedList<>(); list.add(fragment.getName()); Group group = fragment; while (group != null) { @@ -408,8 +368,8 @@ class TreeViewProvider implements ViewProviderService { } else { Group[] groups = ((ProgramModule) group).getChildren(); - for (int i = 0; i < groups.length; i++) { - getAddressSet(groups[i], set); + for (Group group2 : groups) { + getAddressSet(group2, set); } } } @@ -439,11 +399,11 @@ class TreeViewProvider implements ViewProviderService { if (fragment == null) { return; } - LinkedList list = new LinkedList(); + LinkedList list = new LinkedList<>(); list.add(fragment.getName()); Group group = fragment; - ArrayList> pathNameList = new ArrayList>(); + ArrayList> pathNameList = new ArrayList<>(); // need GroupPath for all occurrences of fragment setAncestorList(group, list, pathNameList); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewManagerComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewManagerComponentProvider.java index 3e855787d9..b863f60d21 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewManagerComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewManagerComponentProvider.java @@ -196,11 +196,13 @@ public class ViewManagerComponentProvider extends ComponentProviderAdapter if (currentProgram == null) { return null; } + if (event != null) { - return new ProgramActionContext(this, currentProgram, getActivePopupObject(event)); + return new ProgramActionContext(this, currentProgram, viewPanel, + getActivePopupObject(event)); } - return new ProgramActionContext(this, currentProgram, getFocusedContext()); + return new ProgramActionContext(this, currentProgram, viewPanel, getFocusedContext()); } private Object getFocusedContext() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewProviderService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewProviderService.java index 1e92ad2120..afc02ae017 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewProviderService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewProviderService.java @@ -15,14 +15,14 @@ */ package ghidra.app.plugin.core.programtree; -import ghidra.app.services.ViewService; -import ghidra.framework.plugintool.ServiceInfo; - import java.awt.event.MouseEvent; import javax.swing.JComponent; +import docking.ActionContext; import docking.action.DockingAction; +import ghidra.app.services.ViewService; +import ghidra.framework.plugintool.ServiceInfo; /** * Define methods for notification of which service becomes active; @@ -30,55 +30,62 @@ import docking.action.DockingAction; */ @ServiceInfo(description = "Provide a view that is managed by the ViewManagerService") public interface ViewProviderService extends ViewService { - - /** - * Get the viewer component. - */ - public JComponent getViewComponent(); - /** - * Get the name of this view. - */ - public String getViewName(); + /** + * Get the viewer component. + */ + public JComponent getViewComponent(); - /** - * Set whether or not the component that is showing has focus. + /** + * Get the name of this view. + */ + public String getViewName(); + + /** + * Set whether or not the component that is showing has focus. * @param hasFocus true if the component has focus - */ - public void setHasFocus(boolean hasFocus); + */ + public void setHasFocus(boolean hasFocus); - /** - * Return the object under the mouse location for the popup - * @param event mouse event generated when the right mouse button is pressed - */ - public Object getActivePopupObject(MouseEvent event); - - /** - * Get the actions that would go on a tool bar. - */ - public DockingAction[] getToolBarActions(); - - /** - * Notification that this view is closed. - * @return true if the view can be closed - */ - public boolean viewClosed(); + /** + * Return the object under the mouse location for the popup + * @param event mouse event generated when the right mouse button is pressed + */ + public Object getActivePopupObject(MouseEvent event); + + /** + * Returns the current action context for this view service + * @param event the mouse event + * @return the context + */ + public ActionContext getActionContext(MouseEvent event); + + /** + * Get the actions that would go on a tool bar. + */ + public DockingAction[] getToolBarActions(); + + /** + * Notification that this view is closed. + * @return true if the view can be closed + */ + public boolean viewClosed(); /** * Notification that this view should be deleted * @return true if the view can be deleted */ public boolean viewDeleted(); - + /** * Notification that this view should be renamed to newName. * @return true if the rename is allowed */ public boolean viewRenamed(String newName); - + /** * Returns the context for the current selection. */ public Object getActiveObject(); - + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java index acc70b91eb..7fc3624725 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java @@ -651,7 +651,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { @Override public ActionContext getActionContext(MouseEvent event) { - return new ActionContext(this, this); + return createContext(this); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java index d8dc8d9b72..8a6111332c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java @@ -15,12 +15,9 @@ */ package ghidra.app.plugin.debug; -import java.awt.event.MouseEvent; - import javax.swing.JComponent; import db.DBHandle; -import docking.ActionContext; import docking.WindowPosition; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.Plugin; @@ -77,11 +74,6 @@ public class DbViewerProvider extends ComponentProviderAdapter { } } - @Override - public ActionContext getActionContext(MouseEvent event) { - return new ActionContext(this, this); - } - @Override public JComponent getComponent() { if (comp == null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/propertymanager/PropertyManagerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/propertymanager/PropertyManagerProvider.java index 85f3856976..8ace64dde9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/propertymanager/PropertyManagerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/propertymanager/PropertyManagerProvider.java @@ -219,7 +219,7 @@ public class PropertyManagerProvider extends ComponentProviderAdapter { Rectangle rowBounds = table.getCellRect(row, PropertyManagerTableModel.PROPERTY_NAME_COLUMN, true); if (rowBounds.contains(event.getPoint())) { - return new ActionContext(this, rowBounds); + return createContext(rowBounds); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java index e808d6774b..b165f5b519 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java @@ -24,8 +24,7 @@ import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import docking.ActionContext; -import docking.ComponentProvider; +import docking.*; import docking.action.*; import docking.help.Help; import docking.help.HelpService; @@ -700,7 +699,8 @@ public class ListingCodeComparisonPanel NextDiffAction() { super("Dual Listing Go To Next Area Marker", owner); setEnabled(true); - setKeyBindingData(new KeyBindingData('N', InputEvent.CTRL_MASK | InputEvent.ALT_MASK)); + setKeyBindingData(new KeyBindingData('N', + DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK)); setDescription("Go to the next highlighted area."); setPopupMenuData(new MenuData(new String[] { "Go To Next Highlighted Area" }, NEXT_DIFF_ICON, DIFF_NAVIGATE_GROUP)); @@ -738,7 +738,8 @@ public class ListingCodeComparisonPanel PreviousDiffAction() { super("Dual Listing Go To Previous Area Marker", owner); setEnabled(true); - setKeyBindingData(new KeyBindingData('P', InputEvent.CTRL_MASK | InputEvent.ALT_MASK)); + setKeyBindingData(new KeyBindingData('P', + DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK)); setDescription("Go to the previous highlighted area."); setPopupMenuData(new MenuData(new String[] { "Go To Previous Highlighted Area" }, PREVIOUS_DIFF_ICON, DIFF_NAVIGATE_GROUP)); @@ -796,9 +797,6 @@ public class ListingCodeComparisonPanel } } - /** - * Returns true if the listings are showing the entire program. - */ public boolean isEntireListingShowing() { return isShowingEntireListing; } @@ -857,9 +855,6 @@ public class ListingCodeComparisonPanel toggleHeaderAction.setSelected(show); } - /** - * Returns true if the listings are being displayed side by side. - */ public boolean isSideBySide() { return isSideBySide; } @@ -2024,18 +2019,18 @@ public class ListingCodeComparisonPanel Object leftMarginContext = getContextForMarginPanels(leftPanel, event); if (leftMarginContext != null) { - return new ActionContext(provider, leftMarginContext); + return new ActionContext(provider).setContextObject(leftMarginContext); } Object rightMarginContext = getContextForMarginPanels(rightPanel, event); if (rightMarginContext != null) { - return new ActionContext(provider, rightMarginContext); + return new ActionContext(provider).setContextObject(rightMarginContext); } Object source = event.getSource(); if (source instanceof FieldHeaderComp) { FieldHeaderLocation fieldHeaderLocation = leftPanel.getFieldHeader().getFieldHeaderLocation(event.getPoint()); - return new ActionContext(provider, fieldHeaderLocation); + return new ActionContext(provider).setContextObject(fieldHeaderLocation); } Navigatable focusedNavigatable = dualListingPanel.getFocusedNavigatable(); @@ -2043,7 +2038,7 @@ public class ListingCodeComparisonPanel new DualListingActionContext(provider, focusedNavigatable); myActionContext.setContextObject(this); myActionContext.setCodeComparisonPanel(this); - myActionContext.setSource(source); + myActionContext.setSourceObject(source); return myActionContext; } @@ -2607,13 +2602,13 @@ public class ListingCodeComparisonPanel // Are we on a marker margin of the left listing? Return that margin's context. Object sourceMarginContextObject = getContextObjectForMarginPanels(sourcePanel, event); if (sourceMarginContextObject != null) { - return new ActionContext(provider, sourceMarginContextObject); + return new ActionContext(provider).setContextObject(sourceMarginContextObject); } // Are we on a marker margin of the right listing? Return that margin's context. Object destinationMarginContextObject = getContextObjectForMarginPanels(destinationPanel, event); if (destinationMarginContextObject != null) { - return new ActionContext(provider, destinationMarginContextObject); + return new ActionContext(provider).setContextObject(destinationMarginContextObject); } // If the action is on the Field Header of the left listing panel return an @@ -2621,7 +2616,7 @@ public class ListingCodeComparisonPanel if (sourceComponent instanceof FieldHeaderComp) { FieldHeaderLocation fieldHeaderLocation = sourcePanel.getFieldHeader().getFieldHeaderLocation(event.getPoint()); - return new ActionContext(provider, fieldHeaderLocation); + return new ActionContext(provider).setContextObject(fieldHeaderLocation); } } return null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java index 7b7befa869..02c66284a2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/OpenVersionedFileDialog.java @@ -259,7 +259,7 @@ public class OpenVersionedFileDialog extends DataTreeDialog { return context; } - ActionContext actionContext = new ActionContext(null, event.getSource(), this); + ActionContext actionContext = new ActionContext(null, this, event.getComponent()); actionContext.setMouseEvent(event); return actionContext; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBActionContext.java index 5474771f9d..9091ea9ae5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FSBActionContext.java @@ -35,7 +35,7 @@ public class FSBActionContext extends ActionContext { * @param gTree {@link FileSystemBrowserPlugin} provider tree. */ public FSBActionContext(ComponentProvider provider, Object contextObject, GTree gTree) { - super(provider, contextObject); + super(provider, contextObject, gTree); this.gTree = gTree; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/algorithmtree/ModuleAlgorithmPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/algorithmtree/ModuleAlgorithmPluginTest.java index cb9e2dab9b..acebe7666b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/algorithmtree/ModuleAlgorithmPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/algorithmtree/ModuleAlgorithmPluginTest.java @@ -22,7 +22,6 @@ import java.util.Set; import org.junit.*; -import docking.ActionContext; import docking.action.DockingActionIf; import ghidra.app.plugin.core.programtree.ProgramTreePlugin; import ghidra.app.plugin.core.programtree.ViewProviderService; @@ -99,7 +98,7 @@ public class ModuleAlgorithmPluginTest extends AbstractGhidraHeadedIntegrationTe getContextObject(vps); - performAction(CollectionUtils.any(actions), new ActionContext(null, context), true); + performAction(CollectionUtils.any(actions), createContext(context), true); waitForTasks(); program.flushEvents(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java index 3ca183b18e..09f6eb1bb2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java @@ -433,7 +433,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest { Address address = bookmark.getAddress(); DeleteBookmarkAction action = new DeleteBookmarkAction(plugin, bookmark, true); MarkerLocation markerLocation = new MarkerLocation(null, address, 0, 0); - performAction(action, new ActionContext(null, markerLocation), true); + performAction(action, createContext(markerLocation), true); list = getBookmarks(program.getBookmarkManager()); assertFalse(list.contains(bookmark)); @@ -441,7 +441,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest { address = bookmark.getAddress(); action = new DeleteBookmarkAction(plugin, bookmark, false); markerLocation = new MarkerLocation(null, address, 0, 0); - performAction(action, new ActionContext(null, markerLocation), true); + performAction(action, createContext(markerLocation), true); list = getBookmarks(program.getBookmarkManager()); assertFalse(list.contains(bookmark)); } @@ -543,8 +543,8 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest { CreateStructureCmd cmd = new CreateStructureCmd(addr("0100b6db"), 20); applyCmd(program, cmd); - List actions = runSwing(() -> plugin.getPopupActions( - null, new ActionContext(null, new MarkerLocation(null, addr("0100b6db"), 0, 0)))); + List actions = runSwing(() -> plugin.getPopupActions(null, + createContext(new MarkerLocation(null, addr("0100b6db"), 0, 0)))); assertEquals(10, actions.size()); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderActionsTest.java index 85387fbb15..dfde82aec4 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderActionsTest.java @@ -131,7 +131,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { DockingAction headerAction = getHeaderAction("Remove All Fields"); FieldHeaderLocation loc = new FieldHeaderLocation(functionFormat, factorys[0], 0, 0); - ActionContext context = new ActionContext(provider, loc); + ActionContext context = createContext(provider, loc); performAction(headerAction, context, false); pressContinueOnResetFormatDialog("Remove All Fields?"); @@ -151,7 +151,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { selectHeaderField(factories[0]); FieldHeaderLocation loc = new FieldHeaderLocation(functionFormat, factories[0], 0, 0); - ActionContext context = new ActionContext(provider, loc); + ActionContext context = createContext(provider, loc); DockingAction headerAction = getHeaderAction("Add Spacer Field"); performAction(headerAction, context, true); @@ -173,7 +173,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { selectHeaderField(factories[0]); FieldHeaderLocation loc = new FieldHeaderLocation(functionFormat, factories[0], 0, 0); - ActionContext context = new ActionContext(provider, loc); + ActionContext context = createContext(provider, loc); DockingAction headerAction = getHeaderAction("SetTextAction"); performAction(headerAction, context, false); @@ -200,7 +200,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(factories[1].isEnabled()); FieldHeaderLocation loc = new FieldHeaderLocation(functionFormat, factories[1], 0, 0); - ActionContext context = new ActionContext(provider, loc); + ActionContext context = createContext(provider, loc); DockingAction headerAction = getHeaderAction("Disable Field"); performAction(headerAction, context, true); @@ -222,7 +222,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(factories[1] instanceof FunctionSignatureFieldFactory); FieldHeaderLocation loc = new FieldHeaderLocation(functionFormat, factories[1], 0, 1); - ActionContext context = new ActionContext(provider, loc); + ActionContext context = createContext(provider, loc); DockingAction headerAction = getHeaderAction("Remove Field"); performAction(headerAction, context, true); @@ -242,7 +242,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { functionFormat.removeAllFactories(); FieldHeaderLocation loc = new FieldHeaderLocation(functionFormat, factories[1], 0, 1); - ActionContext context = new ActionContext(provider, loc); + ActionContext context = createContext(provider, loc); DockingAction headerAction = getHeaderAction("Add All Field"); performAction(headerAction, context, true); @@ -272,8 +272,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { } private void enterTextIntoDialog(String input) { - InputDialog dialog = - waitForDialogComponent(null, InputDialog.class, DEFAULT_WINDOW_TIMEOUT); + InputDialog dialog = waitForDialogComponent(InputDialog.class); assertNotNull("Never found the spacer text input dialog", dialog); JTextComponent textField = (JTextComponent) findComponentByName(dialog, "input.dialog.text.field.0"); @@ -291,7 +290,7 @@ public class HeaderActionsTest extends AbstractGhidraHeadedIntegrationTest { } private void pressContinueOnResetFormatDialog(String title) { - Window window = waitForWindow(title, DEFAULT_WINDOW_TIMEOUT); + Window window = waitForWindow(title); assertNotNull("Never found the dialog: " + title, window); pressButtonByText(window, "Continue"); waitForSwing(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderTest.java index 28dc6db161..8cfb5008fd 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/HeaderTest.java @@ -275,14 +275,14 @@ public class HeaderTest extends AbstractGhidraHeadedIntegrationTest { public void testInsertDeleteRow() { FieldFormatModel model = header.getHeaderTab().getModel(); InsertRowAction act = new InsertRowAction("Test", header); - act.isEnabledForContext( - new ActionContext(null, new FieldHeaderLocation(model, null, 0, 0))); + act.isEnabledForContext(new ActionContext(cb.getProvider()).setContextObject( + new FieldHeaderLocation(model, null, 0, 0))); performAction(act, true); assertEquals(8, model.getNumRows()); assertEquals(0, model.getNumFactorys(0)); RemoveRowAction act2 = new RemoveRowAction("Test", header); - act2.isEnabledForContext( - new ActionContext(null, new FieldHeaderLocation(model, null, 0, 0))); + act2.isEnabledForContext(new ActionContext(cb.getProvider()).setContextObject( + new FieldHeaderLocation(model, null, 0, 0))); performAction(act2, true); assertEquals(7, model.getNumRows()); assertEquals(2, model.getNumFactorys(0)); @@ -294,8 +294,8 @@ public class HeaderTest extends AbstractGhidraHeadedIntegrationTest { ListingField bf = cb.getCurrentField(); int startX = bf.getStartX(); InsertRowAction act = new InsertRowAction("Test", header); - act.isEnabledForContext( - new ActionContext(null, new FieldHeaderLocation(model, null, 0, 0))); + act.isEnabledForContext(new ActionContext(cb.getProvider()).setContextObject( + new FieldHeaderLocation(model, null, 0, 0))); performAction(act, true); int width = bf.getWidth(); int dragX = startX + width / 2; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/AutoRenamePluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/AutoRenamePluginTest.java index 77fec95719..de7dfdc174 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/AutoRenamePluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/AutoRenamePluginTest.java @@ -109,21 +109,21 @@ public class AutoRenamePluginTest extends AbstractGhidraHeadedIntegrationTest { ViewProviderService vps = vmService.getCurrentViewProvider(); Object context = vps.getActivePopupObject(null); - assertTrue(!renameAction.isEnabledForContext(new ActionContext(null, context))); - assertTrue(!labelAction.isEnabledForContext(new ActionContext(null, context))); + + assertTrue(!renameAction.isEnabledForContext(createContext(context))); + assertTrue(!labelAction.isEnabledForContext(createContext(context))); gps = new GroupPath[1]; gps[0] = new GroupPath(new String[] { root.getName(), "DLLs", "USER32.DLL" }); setSelection(gps); context = vps.getActivePopupObject(null); - assertTrue(renameAction.isEnabledForContext(new ActionContext(null, context))); - assertTrue(!labelAction.isEnabledForContext(new ActionContext(null, context))); + assertTrue(renameAction.isEnabledForContext(createContext(context))); + assertTrue(!labelAction.isEnabledForContext(createContext(context))); // fire Label program location Address addr = getAddr(0x10033f6); - LabelFieldLocation loc = - new LabelFieldLocation(program, addr, "SUB_010033f6"); + LabelFieldLocation loc = new LabelFieldLocation(program, addr, "SUB_010033f6"); tool.firePluginEvent(new ProgramLocationPluginEvent("test", loc, program)); ActionContext actionContext = cb.getProvider().getActionContext(null); @@ -147,7 +147,7 @@ public class AutoRenamePluginTest extends AbstractGhidraHeadedIntegrationTest { ViewManagerService vmService = tool.getService(ViewManagerService.class); ViewProviderService vps = vmService.getCurrentViewProvider(); Object context = vps.getActivePopupObject(null); - performAction(renameAction, new ActionContext(null, context), true); + performAction(renameAction, createContext(context), true); program.flushEvents(); assertNotNull(program.getListing().getFragment("Main Tree", s.getName())); @@ -166,12 +166,8 @@ public class AutoRenamePluginTest extends AbstractGhidraHeadedIntegrationTest { new LabelFieldLocation(program, addr, null, "SUB_010033f6", null, 0, 0); cb.goTo(loc); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - labelAction.actionPerformed(cb.getProvider().getActionContext(null)); - } - }); + SwingUtilities.invokeAndWait( + () -> labelAction.actionPerformed(cb.getProvider().getActionContext(null))); program.flushEvents(); assertNull(program.getListing().getFragment("SUB_010033f6", origName)); assertEquals("SUB_010033f6", frag.getName()); @@ -182,22 +178,12 @@ public class AutoRenamePluginTest extends AbstractGhidraHeadedIntegrationTest { } private void setViewToMainTree() { - runSwing(new Runnable() { - @Override - public void run() { - service.setViewedTree("Main Tree"); - } - }); + runSwing(() -> service.setViewedTree("Main Tree")); } private void setSelection(final GroupPath[] gps) { - runSwing(new Runnable() { - @Override - public void run() { - service.setGroupSelection(gps); - } - }); + runSwing(() -> service.setGroupSelection(gps)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/ModuleSortPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/ModuleSortPluginTest.java index e96f571807..e7dfe706fa 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/ModuleSortPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/module/ModuleSortPluginTest.java @@ -22,7 +22,6 @@ import java.util.*; import org.junit.*; -import docking.ActionContext; import docking.action.DockingActionIf; import ghidra.app.plugin.core.programtree.ProgramTreePlugin; import ghidra.app.plugin.core.programtree.ViewProviderService; @@ -113,7 +112,8 @@ public class ModuleSortPluginTest extends AbstractGhidraHeadedIntegrationTest { Object context = vps.getActivePopupObject(null); for (DockingActionIf action : actions) { - assertTrue(action.isAddToPopup(new ActionContext(null, context))); + + assertTrue(action.isAddToPopup(vps.getActionContext(null))); } gps = new GroupPath[1]; @@ -122,7 +122,7 @@ public class ModuleSortPluginTest extends AbstractGhidraHeadedIntegrationTest { context = vps.getActivePopupObject(null); for (DockingActionIf action : actions) { - assertTrue(!action.isAddToPopup(new ActionContext(null, context))); + assertTrue(!action.isAddToPopup(vps.getActionContext(null))); } } @@ -159,11 +159,10 @@ public class ModuleSortPluginTest extends AbstractGhidraHeadedIntegrationTest { ViewManagerService vmService = tool.getService(ViewManagerService.class); ViewProviderService vps = vmService.getCurrentViewProvider(); - Object context = vps.getActivePopupObject(null); - for (DockingActionIf action : actions) { if (action.getName().indexOf("Name") > 0) { - action.actionPerformed(new ActionContext(null, context)); + + action.actionPerformed(vps.getActionContext(null)); break; } } @@ -205,7 +204,7 @@ public class ModuleSortPluginTest extends AbstractGhidraHeadedIntegrationTest { for (DockingActionIf action : actions) { if (action.getName().indexOf("Address") > 0) { - action.actionPerformed(new ActionContext(null, context)); + action.actionPerformed(vps.getActionContext(null)); break; } } @@ -232,7 +231,7 @@ public class ModuleSortPluginTest extends AbstractGhidraHeadedIntegrationTest { Object context = vps.getActivePopupObject(null); for (DockingActionIf action : actions) { - assertTrue(!action.isAddToPopup(new ActionContext(null, context))); + assertTrue(!action.isAddToPopup(vps.getActionContext(null))); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java index ae2ad5ef24..24a2919fee 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java @@ -152,7 +152,7 @@ public class ProviderNavigationPluginTest extends AbstractProgramBasedTest { } private void forceActivate(ComponentProvider provider) { - ActionContext context = new ActionContext(provider, provider); + ActionContext context = new ActionContext(provider); for (DockingContextListener l : testContextListeners) { l.contextChanged(context); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java index 3b4b216106..76c2063efb 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java @@ -67,4 +67,9 @@ public class DummyToolActions implements DockingToolActions { public void removeActions(ComponentProvider provider) { // stub } + + @Override + public boolean containsAction(DockingActionIf action) { + return false; + } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java index 6daad2cb23..337ec743dc 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java @@ -528,13 +528,15 @@ public abstract class DecompilerCodeComparisonPanel fieldIsVisible(provider, actionName)); @@ -728,7 +728,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte assertNotNull(action); FieldHeaderLocation fhLoc = createFieldHeaderLocation(provider); - ActionContext context = new ActionContext(null, fhLoc); + ActionContext context = createContext(fhLoc); performAction(action, context, false); Window dialog = waitForWindow("Reset All Formats?"); diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/context/FcgActionContext.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/context/FcgActionContext.java index b8a6fb829b..39a21de8b3 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/context/FcgActionContext.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/context/FcgActionContext.java @@ -29,6 +29,6 @@ public class FcgActionContext extends ActionContext { } public FcgActionContext(FcgProvider provider, Object contextObject) { - super(provider, contextObject); + super(provider, contextObject, provider.getComponent()); } } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java index 62f1c3bf97..7dd4302983 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java @@ -277,7 +277,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter getExistingMatch(sourceFunction, destinationFunction)); vtListingContext.setCodeComparisonPanel(dualListingPanel); vtListingContext.setContextObject(dualListingPanel); - vtListingContext.setSource(source); + vtListingContext.setSourceObject(source); return vtListingContext; } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java index 62eae749bc..69ce829e07 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java @@ -493,7 +493,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter new VTListingContext(this, vtListingNavigator); vtListingContext.setCodeComparisonPanel(dualListingPanel); vtListingContext.setContextObject(dualListingPanel); - vtListingContext.setSource(source); + vtListingContext.setSourceObject(source); return vtListingContext; } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java index ee5c34b983..897e0146b3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java @@ -15,48 +15,128 @@ */ package docking; +import java.awt.Component; import java.awt.event.MouseEvent; +import docking.action.DockingActionIf; + /** - * ComponentProviders are required to return Objects of this type in their getActionContext() + * Action context is a class that contains state information that is given to + * {@link DockingActionIf}s for them to decide if they are enabled for a given user action. User + * actions are toolbar button presses, menu bar item presses and popup menu item presses. As + * the user changes focus in the system, all actions are queried with the current context. Thus, + * toolbar buttons and menu items will enable and disable as the user interacts with the system. + * When the user executes an action, the current context will be passed to the backing + * {@link DockingActionIf}. Ultimately, context serves to control action enablement and to + * allow plugins to share state with actions without having to store that state information + * in class fields of the plugin. + * + *

ComponentProviders are required to return Objects of this type in their getActionContext() * methods. Generally, ComponentProviders have two ways to use this class. They can either create * an ActionContext instance and pass in a contextObject that will be useful to its actions or, * subclass the ActionContext object to include specific methods to provide the information that * actions will require. + * + *

The data contained by this class has meaning that can change relative to the code that + * created it. The intended purpose for the fields of this class is as follows: + *

    + *
  • provider - the component provider to which this context belongs; the provider that + * contains the component that is the source of the user action + *
  • + *
  • contextObject - client-defined data object. This allows clients to save any + * information desired to be used when the action is performed. + *
  • + *
  • sourceObject - when checking enablement, this is the item that was clicked or + * activated; when performing an action this is either the active + * object or the component that was clicked. This value may change + * between the check for + * {@link DockingActionIf#isEnabledForContext(ActionContext) enablement} + * and {@link DockingActionIf#actionPerformed(ActionContext) execution}. + *
  • + *
  • sourceComponent - this value is the component that is the source of the current + * context. Whereas the sourceObject is the actual + * clicked item, this value is the focused/active component and + * will not change between + * {@link DockingActionIf#isEnabledForContext(ActionContext) enablement} + * and {@link DockingActionIf#actionPerformed(ActionContext) execution}. + *
  • + *
  • mouseEvent - the mouse event that triggered the action; null if the action was + * triggered by a key binding. + *
  • + *
+ * + *

Ultimately, clients can pass any values they wish for the fields of this class, even if + * that changes the meaning of the fields outlined above. */ public class ActionContext { private ComponentProvider provider; + private MouseEvent mouseEvent; private Object contextObject; private Object sourceObject; - private MouseEvent mouseEvent; + + // Note: the setting of this object is delayed. This allows clients to build-up the state + // of this context. This object will be set when getSourceComponent() is called if it + // has not already been set. + private Component sourceComponent; public ActionContext() { this(null, null); } + public ActionContext(ComponentProvider cp) { + this(cp, null); + } + /** * Basic constructor for ActionContext * @param provider the ComponentProvider that generated this context. - * @param contextObject an optional contextObject that the ComponentProvider can provide - * to the action. + * @param sourceComponent an optional source object; this is intended to be the component that + * is the source of the context, usually the focused component */ - public ActionContext(ComponentProvider provider, Object contextObject) { - this.provider = provider; - this.contextObject = contextObject; + public ActionContext(ComponentProvider provider, Component sourceComponent) { + this(provider, sourceComponent, sourceComponent); } /** * Constructor * * @param provider the ComponentProvider that generated this context. - * @param contextObject an optional contextObject that the ComponentProvider can provide - * @param sourceObject an optional source object; this can be anything that actions wish to - * later retrieve + * @param contextObject an optional contextObject that the ComponentProvider can provide; this + * can be anything that actions wish to later retrieve + * @param sourceComponent an optional source object; this is intended to be the component that + * is the source of the context, usually the focused component */ - public ActionContext(ComponentProvider provider, Object contextObject, Object sourceObject) { - this(provider, contextObject); - this.sourceObject = sourceObject; + public ActionContext(ComponentProvider provider, Object contextObject, + Component sourceComponent) { + this.provider = provider; + this.contextObject = contextObject; + this.sourceObject = sourceComponent; + this.sourceComponent = sourceComponent; + } + + private void lazyDeriveSourceComponent() { + + if (sourceComponent != null) { + // Do not allow this to change once set. This prevents the value from getting changed + // when the user clicks a menu item. + return; + } + + // check this in order of preference + if (sourceObject instanceof Component) { + sourceComponent = (Component) sourceObject; + return; + } + + if (mouseEvent != null) { + sourceComponent = mouseEvent.getComponent(); + return; + } + + if (contextObject instanceof Component) { + sourceComponent = (Component) contextObject; + } } /** @@ -83,9 +163,11 @@ public class ActionContext { * choosing that can be provided for later retrieval. * * @param contextObject Sets the context object for this context. + * @return this context */ - public void setContextObject(Object contextObject) { + public ActionContext setContextObject(Object contextObject) { this.contextObject = contextObject; + return this; } /** @@ -98,22 +180,28 @@ public class ActionContext { /** * Sets the sourceObject for this ActionContext. This method is used internally by the - * DockingWindowManager. ComponentProvider and action developers should - * only use this method for testing. + * DockingWindowManager. ComponentProvider and action developers should only use this + * method for testing. + * * @param sourceObject the source object + * @return this context */ - public void setSource(Object sourceObject) { + public ActionContext setSourceObject(Object sourceObject) { this.sourceObject = sourceObject; + return this; } /** * Updates the context's mouse event. Contexts that are based upon key events will have no - * mouse event. + * mouse event. This method is really for the framework to use. Client calls to this + * method will be overridden by the framework when menu items are clicked. * * @param e the event that triggered this context. + * @return this context */ - public void setMouseEvent(MouseEvent e) { + public ActionContext setMouseEvent(MouseEvent e) { this.mouseEvent = e; + return this; } /** @@ -126,6 +214,17 @@ public class ActionContext { return mouseEvent; } + /** + * Returns the component that is the target of this context. This value should not change + * whether the context is triggered by a key binding or mouse event. + * + * @return the component; may be null + */ + public Component getSourceComponent() { + lazyDeriveSourceComponent(); + return sourceComponent; + } + @Override public String toString() { @@ -134,6 +233,7 @@ public class ActionContext { "\tprovider: " + provider + ",\n" + "\tcontextObject: " + contextObject + ",\n" + "\tsourceObject: " + sourceObject + ",\n" + + "\tsourceComponent: " + sourceComponent + ",\n" + "\tmouseEvent: " + mouseEvent + "\n" + "}"; //@formatter:on diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentLoadedListener.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentLoadedListener.java index f5d9828685..ec618d9e3d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentLoadedListener.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentLoadedListener.java @@ -27,6 +27,8 @@ public interface ComponentLoadedListener { * Called when the component is made displayable * * @param windowManager the window manager associated with the loaded component + * @param provider the provider that is the parent of the given component; null if this + * component is not the child of a component provider */ - public void componentLoaded(DockingWindowManager windowManager); + public void componentLoaded(DockingWindowManager windowManager, ComponentProvider provider); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java index 7fd85b6036..75e79fce6d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -387,7 +387,28 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext */ @Override public ActionContext getActionContext(MouseEvent event) { - return new ActionContext(this, getComponent()); + Component c = getComponent(); + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusedComponent = kfm.getFocusOwner(); + if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) { + c = focusedComponent; + } + return createContext(c, null); + } + + // TODO + protected ActionContext createContext() { + return new ActionContext(this); + } + + // TODO + protected ActionContext createContext(Object payload) { + return new ActionContext(this).setContextObject(payload); + } + + // TODO + protected ActionContext createContext(Component source, Object payload) { + return new ActionContext(this, source).setContextObject(payload); } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java index e8629339b4..c18520ec9e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java @@ -1127,7 +1127,19 @@ public class DialogComponentProvider */ @Override public ActionContext getActionContext(MouseEvent event) { - return new ActionContext(null, null); + + Component c = getComponent(); + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusedComponent = kfm.getFocusOwner(); + if (focusedComponent != null && SwingUtilities.isDescendingFrom(focusedComponent, c)) { + c = focusedComponent; + } + + if (event == null) { + return new ActionContext(null, c); + } + + return new ActionContext(null, c).setSourceObject(event.getSource()); } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java index 1fe5f87119..cb789f17d0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java @@ -56,7 +56,7 @@ public class DialogComponentProviderPopupActionManager { // If the source is null, must set it or we won't have // any popups shown. if (actionContext.getSourceObject() == null) { - actionContext.setSource(e.getSource()); + actionContext.setSourceObject(e.getSource()); } MenuHandler popupMenuHandler = new PopupMenuHandler(actionContext); @@ -152,7 +152,7 @@ public class DialogComponentProviderPopupActionManager { public void processMenuAction(final DockingActionIf action, final ActionEvent event) { DockingWindowManager.clearMouseOverHelp(); - actionContext.setSource(event.getSource()); + actionContext.setSourceObject(event.getSource()); // this gives the UI some time to repaint before executing the action SwingUtilities.invokeLater(() -> { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java index f7492b697e..8ff8c19c58 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java @@ -214,6 +214,11 @@ public class DockingActionProxy dockingAction.setUnvalidatedKeyBindingData(newKeyBindingData); } + @Override + public void dispose() { + dockingAction.dispose(); + } + @Override public String getHelpInfo() { return dockingAction.getHelpInfo(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java index 15512a423d..42e9e20b03 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java @@ -62,7 +62,7 @@ public abstract class DockingKeyBindingAction extends AbstractAction { tool.setStatusInfo(""); ComponentProvider provider = tool.getActiveComponentProvider(); ActionContext context = getLocalContext(provider); - context.setSource(e.getSource()); + context.setSourceObject(e.getSource()); docakbleAction.actionPerformed(context); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java index 7f2f2ad98c..eb79345a45 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java @@ -73,7 +73,9 @@ public interface DockingTool { public void addComponentProvider(ComponentProvider componentProvider, boolean show); /** - * Removes the given ComponentProvider from the tool + * Removes the given ComponentProvider from the tool. When a provider has been removed + * from the tool it is considered disposed and should not be reused. + * * @param componentProvider the provider to remove from the tool */ public void removeComponentProvider(ComponentProvider componentProvider); @@ -125,7 +127,8 @@ public interface DockingTool { public void addAction(DockingActionIf action); /** - * Removes the given action from the tool + * Removes the given action from the tool. When an action is removed from the tool it will + * be disposed and should not be reused. * @param action the action to be removed. */ public void removeAction(DockingActionIf action); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java index add2e33395..b00f0a98f0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -63,8 +63,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder private static DockingActionIf actionUnderMouse; private static Object objectUnderMouse; - public static final String TOOL_PREFERENCES_XML_NAME = "PREFERENCES"; + /** + * The owner name for docking windows actions. + *

Warning: Any action with this owner will get removed every time the 'Window' menu is + * rebuilt, with the exception if reserved key bindings. + */ public static final String DOCKING_WINDOWS_OWNER = "DockingWindows"; + public static final String TOOL_PREFERENCES_XML_NAME = "PREFERENCES"; /** * The helpService field should be set to the appropriate help service provider. @@ -560,6 +565,25 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return list; } + /** + * Returns the component provider that is the conceptual parent of the given component. More + * precisely, this will return the component provider whose + * {@link ComponentProvider#getComponent() component} is the parent of the given component. + * + * @param component the component for which to find a provider + * @return the provider; null if the component is not the child of a provider + */ + private ComponentProvider getComponentProvider(Component component) { + Set providers = placeholderManager.getActiveProviders(); + for (ComponentProvider provider : providers) { + JComponent providerComponent = provider.getComponent(); + if (SwingUtilities.isDescendingFrom(component, providerComponent)) { + return provider; + } + } + return null; + } + DockableComponent getDockableComponent(ComponentProvider provider) { ComponentPlaceholder placeholder = placeholderManager.getPlaceholder(provider); return placeholder.getComponent(); @@ -2165,7 +2189,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder component.removeHierarchyListener(this); DockingWindowManager dwm = getInstance(component); if (dwm != null) { - listener.componentLoaded(dwm); + ComponentProvider provider = dwm.getComponentProvider(component); + listener.componentLoaded(dwm, provider); return; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java index f14da83cc4..502d55aa20 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java @@ -408,8 +408,9 @@ class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher { // ...next see if there is a key binding for when the component is the child of the focus // owner - return KeyBindingUtils.getAction(jComponent, keyStroke, + action = KeyBindingUtils.getAction(jComponent, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + return action; } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/MenuBarMenuHandler.java b/Ghidra/Framework/Docking/src/main/java/docking/MenuBarMenuHandler.java index 0e345592b9..8366eb08bd 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/MenuBarMenuHandler.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/MenuBarMenuHandler.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. @@ -63,7 +62,7 @@ public class MenuBarMenuHandler extends MenuHandler { return; // context is not valid, nothing to do } - tempContext.setSource(event.getSource()); + tempContext.setSourceObject(event.getSource()); final ActionContext finalContext = tempContext; // this gives the UI some time to repaint before executing the action diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java index 7b241fa652..2d35c32ea5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java @@ -24,7 +24,6 @@ import java.util.*; import javax.swing.JPopupMenu; import docking.action.*; -import docking.actions.PopupActionProvider; import docking.menu.*; public class PopupActionManager implements PropertyChangeListener { @@ -76,7 +75,7 @@ public class PopupActionManager implements PropertyChangeListener { actionContext = new ActionContext(); } - actionContext.setSource(e.getSource()); + actionContext.setSourceObject(e.getSource()); actionContext.setMouseEvent(e); MenuHandler popupMenuHandler = new PopupMenuHandler(windowManager, actionContext); @@ -145,7 +144,7 @@ public class PopupActionManager implements PropertyChangeListener { Object source = actionContext.getSourceObject(); - // this interface is deprecated in favor of the next block + // this interface is deprecated in favor the code that calls this method; this will be deleted if (source instanceof DockingActionProviderIf) { DockingActionProviderIf actionProvider = (DockingActionProviderIf) source; List dockingActions = actionProvider.getDockingActions(); @@ -158,23 +157,6 @@ public class PopupActionManager implements PropertyChangeListener { } } } - - // note: this is temporary; there is only one client that needs this. This will be - // removed in a future ticket when that client uses the standard tool action system - if (source instanceof PopupActionProvider) { - PopupActionProvider actionProvider = (PopupActionProvider) source; - DockingTool tool = windowManager.getTool(); - List dockingActions = - actionProvider.getPopupActions(tool, actionContext); - for (DockingActionIf action : dockingActions) { - MenuData popupMenuData = action.getPopupMenuData(); - if (popupMenuData != null && action.isValidContext(actionContext) && - action.isAddToPopup(actionContext)) { - action.setEnabled(action.isEnabledForContext(actionContext)); - menuMgr.addAction(action); - } - } - } } private boolean isRemovingFromPopup(MenuData oldData, MenuData newData) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuHandler.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuHandler.java index 26460d2a11..6d402bc98f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuHandler.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuHandler.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. @@ -48,7 +47,7 @@ public class PopupMenuHandler extends MenuHandler { public void processMenuAction(final DockingActionIf action, final ActionEvent event) { DockingWindowManager.clearMouseOverHelp(); - actionContext.setSource(event.getSource()); + actionContext.setSourceObject(event.getSource()); // this gives the UI some time to repaint before executing the action SwingUtilities.invokeLater( new Runnable() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java index c3e3e76e9b..71979c79b2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java @@ -372,9 +372,8 @@ public abstract class DockingAction implements DockingActionIf { /** * Cleans up any resources used by the action. */ + @Override public void dispose() { - // TODO this doesn't seem to be called by the framework. Should't we call this when - // an action is removed from the tool?? propertyListeners.clear(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java index dc35f51912..bc86fcdf8a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java @@ -300,4 +300,9 @@ public interface DockingActionIf extends HelpDescriptor { * @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding */ public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData); + + /** + * Called when the action's owner is removed from the tool + */ + public void dispose(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java index 1be12da4f3..6fcdcfebf3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java @@ -119,7 +119,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { // Build list of actions which are valid in current context ComponentProvider localProvider = tool.getActiveComponentProvider(); ActionContext localContext = getLocalContext(localProvider); - localContext.setSource(event.getSource()); + localContext.setSourceObject(event.getSource()); ActionContext globalContext = tool.getGlobalContext(); List list = getValidContextActions(localContext, globalContext); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ActionAdapter.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ActionAdapter.java index e84079cb6f..bc468347ff 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ActionAdapter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ActionAdapter.java @@ -122,7 +122,7 @@ public class ActionAdapter implements Action, PropertyChangeListener { } if (context == null) { context = new ActionContext(); - context.setSource(e.getSource()); + context.setSourceObject(e.getSource()); } if (dockingAction.isEnabledForContext(context)) { dockingAction.actionPerformed(context); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java index 2de77f96e0..78b8a9f9d8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java @@ -92,4 +92,6 @@ public interface DockingToolActions { */ public Set getAllActions(); + // TODO + public boolean containsAction(DockingActionIf action); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java index 3651117ef5..fafa29f573 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -284,13 +284,32 @@ public class KeyBindingUtils { am.put(keyText, action); } + /** + * Allows the client to clear Java key bindings when the client is creating a docking + * action. Without this call, any actions bound to the given component will prevent an + * action with the same key binding from firing. This is useful when your + * application is using tool-level key bindings that share the same + * keystroke as a built-in Java action, such as Ctrl-C for the copy action. + * + * @param component the component for which to clear the key binding + * @param action the action from which to get the key binding + */ + public static void clearKeyBinding(JComponent component, DockingActionIf action) { + KeyStroke keyBinding = action.getKeyBinding(); + if (keyBinding == null) { + return; + } + clearKeyBinding(component, keyBinding); + } + /** * Allows clients to clear Java key bindings. This is useful when your * application is using tool-level key bindings that share the same * keystroke as a built-in Java action, such as Ctrl-C for the copy action. *

- * Note: this method clears focus for the default - * ({@link JComponent#WHEN_FOCUSED}) focus condition. + * Note: this method clears the key binding for the + * {@link JComponent#WHEN_FOCUSED} and + * {@link JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT} focus conditions. * * @param component the component for which to clear the key binding * @param keyStroke the keystroke of the binding to be cleared @@ -298,6 +317,7 @@ public class KeyBindingUtils { */ public static void clearKeyBinding(JComponent component, KeyStroke keyStroke) { clearKeyBinding(component, keyStroke, JComponent.WHEN_FOCUSED); + clearKeyBinding(component, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); } /** @@ -314,12 +334,9 @@ public class KeyBindingUtils { public static void clearKeyBinding(JComponent component, KeyStroke keyStroke, int focusCondition) { InputMap inputMap = component.getInputMap(focusCondition); - ActionMap actionMap = component.getActionMap(); - if (inputMap == null || actionMap == null) { - return; + if (inputMap != null) { + inputMap.put(keyStroke, "none"); } - - inputMap.put(keyStroke, "none"); } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index 61849594e3..8f16a6a0e0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -32,8 +32,7 @@ import docking.*; import docking.action.*; import docking.tool.util.DockingToolConstants; import ghidra.framework.options.*; -import ghidra.util.ReservedKeyBindings; -import ghidra.util.SystemUtilities; +import ghidra.util.*; import ghidra.util.exception.AssertException; import util.CollectionUtils; @@ -187,6 +186,16 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { action.removePropertyChangeListener(this); removeAction(action); actionGuiHelper.removeToolAction(action); + dispose(action); + } + + private void dispose(DockingActionIf action) { + try { + action.dispose(); + } + catch (Throwable t) { + Msg.error(this, "Exception disposing action '" + action.getFullName() + "'", t); + } } @Override @@ -311,6 +320,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { removeAction(action); keyBindingsManager.removeAction(action); actionGuiHelper.removeProviderAction(provider, action); + dispose(action); } @Override @@ -395,6 +405,11 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { return null; } + @Override + public boolean containsAction(DockingActionIf action) { + return getActionStorage(action).contains(action); + } + public Action getAction(KeyStroke ks) { return keyBindingsManager.getDockingKeyAction(ks); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java index 48ec1c5ba7..13bf1480da 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java @@ -197,21 +197,22 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action public void actionPerformed(ActionEvent e) { if (menuHandler != null) { menuHandler.processMenuAction(action, e); + return; } - else { - try { - ActionContext context = new ActionContext(null, null, e.getSource()); - if (action.isEnabledForContext(context)) { - if (action instanceof ToggleDockingActionIf) { - ToggleDockingActionIf toggleAction = ((ToggleDockingActionIf) action); - toggleAction.setSelected(!toggleAction.isSelected()); - } - action.actionPerformed(context); + + try { + ActionContext context = new ActionContext(); + context.setSourceObject(e.getSource()); + if (action.isEnabledForContext(context)) { + if (action instanceof ToggleDockingActionIf) { + ToggleDockingActionIf toggleAction = ((ToggleDockingActionIf) action); + toggleAction.setSelected(!toggleAction.isSelected()); } + action.actionPerformed(context); } - catch (Throwable t) { - Msg.error(this, "Unexpected Exception: " + t.getMessage(), t); - } + } + catch (Throwable t) { + Msg.error(this, "Unexpected Exception: " + t.getMessage(), t); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultipleActionDockingToolbarButton.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultipleActionDockingToolbarButton.java index 3c9a3f8789..01694e76ae 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultipleActionDockingToolbarButton.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultipleActionDockingToolbarButton.java @@ -184,7 +184,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { final DockingActionIf delegateAction = dockingAction; item.addActionListener(e -> { ActionContext context = getActionContext(); - context.setSource(e.getSource()); + context.setSourceObject(e.getSource()); if (delegateAction instanceof ToggleDockingAction) { ToggleDockingAction toggleAction = (ToggleDockingAction) delegateAction; toggleAction.setSelected(!toggleAction.isSelected()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java index 785c882328..223c91d687 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java @@ -206,7 +206,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene else { return; // context is not valid, nothing to do } - tempContext.setSource(event.getSource()); + tempContext.setSourceObject(event.getSource()); final ActionContext finalContext = tempContext; // this gives the UI some time to repaint before executing the action diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index bff919d5b7..68267c4c6e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -1326,7 +1326,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { } actionContext = newContext; - actionContext.setSource(provider.getComponent()); + actionContext.setSourceObject(provider.getComponent()); return actionContext; }); @@ -1349,7 +1349,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { ActionContext context = runSwing(() -> { ActionContext actionContext = provider.getActionContext(null); if (actionContext != null) { - actionContext.setSource(provider.getComponent()); + actionContext.setSourceObject(provider.getComponent()); } return actionContext; }); @@ -2188,6 +2188,25 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { return ref.get(); } + /** + * Creates a generic action context with no provider, with the given payload + * @param payload the generic object to put in the context + * @return the new context + */ + public ActionContext createContext(Object payload) { + return new ActionContext().setContextObject(payload); + } + + /** + * Creates a generic action context with the given provider, with the given payload + * @param provider the provider + * @param payload the generic object to put in the context + * @return the new context + */ + public ActionContext createContext(ComponentProvider provider, Object payload) { + return new ActionContext(provider).setContextObject(payload); + } + //================================================================================================== // Screen Capture //================================================================================================== diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java index 686546c20b..d322508e3f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java @@ -579,18 +579,6 @@ public class GhidraFileChooser extends DialogComponentProvider //================================================================================================== @Override - public ActionContext getActionContext(MouseEvent event) { - if (event == null) { - return super.getActionContext(event); - } - - return new ActionContext(null, event.getSource()); - } - - /** - * @see ghidra.util.filechooser.GhidraFileChooserListener#modelChanged() - */ - @Override public void modelChanged() { SystemUtilities.runSwingLater(() -> { directoryListModel.update(); @@ -598,9 +586,6 @@ public class GhidraFileChooser extends DialogComponentProvider }); } - /** - * @see java.io.FileFilter#accept(java.io.File) - */ @Override public boolean accept(File file) { if (!showDotFiles) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java index 15cae8e1dd..a8d4af34a6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java @@ -32,7 +32,6 @@ import javax.swing.table.*; import docking.*; import docking.action.*; import docking.actions.KeyBindingUtils; -import docking.actions.PopupActionProvider; import docking.widgets.OptionDialog; import docking.widgets.dialogs.SettingsDialog; import docking.widgets.filechooser.GhidraFileChooser; @@ -70,12 +69,13 @@ import resources.ResourceManager; * * @see GTableFilterPanel */ -public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProvider { +public class GTable extends JTable implements KeyStrokeConsumer { private static final String LAST_EXPORT_FILE = "LAST_EXPORT_DIR"; private int userDefinedRowHeight; + private boolean isInitialized; private boolean allowActions; private KeyListener autoLookupListener; private long lastLookupTime; @@ -96,6 +96,8 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv /** A flag to signal that a copy operation is being performed. */ private boolean copying; + + private static final String actionMenuGroup = "zzzTableGroup"; private DockingAction copyAction; private DockingAction copyColumnsAction; private DockingAction copyCurrentColumnAction; @@ -103,8 +105,6 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv private DockingAction exportAction; private DockingAction exportColumnsAction; - private String actionMenuGroup = "zzzTableGroup"; - private SelectionManager selectionManager; private Integer visibleRowCount; @@ -510,24 +510,6 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv return autoLookupKeyStrokeConsumer.isKeyConsumed(keyStroke); } - @Override - public List getPopupActions(DockingTool tool, ActionContext context) { - - // we want these top-level groups to all appear together, with no separator - tool.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1"); - tool.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2"); - tool.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3"); - - List list = new ArrayList<>(); - list.add(copyAction); - list.add(copyCurrentColumnAction); - list.add(copyColumnsAction); - list.add(selectAllAction); - list.add(exportAction); - list.add(exportColumnsAction); - return list; - } - private void init(boolean allowAutoEdit) { ToolTipManager.sharedInstance().unregisterComponent(this); ToolTipManager.sharedInstance().registerComponent(this); @@ -576,8 +558,27 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv } }); - createPopupActions(); + // updating the row height requires the 'isInitialized' to be set, so do it first + isInitialized = true; initializeRowHeight(); + + DockingWindowManager.registerComponentLoadedListener(this, (dwm, provider) -> { + DockingTool tool = dwm.getTool(); + regiserActions(tool, provider); + }); + } + + private void regiserActions(DockingTool tool, ComponentProvider provider) { + + tool.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1"); + tool.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2"); + tool.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3"); + + String owner = getClass().getSimpleName(); + if (provider != null) { + owner = provider.getOwner(); + } + installTableActions(tool, owner); } private void initializeHeader(JTableHeader header) { @@ -600,7 +601,8 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv } private void adjustRowHeight() { - if (copyAction == null) { // crude test to know if our constructor has finished + + if (!isInitialized) { return; // must be initializing } @@ -1153,12 +1155,20 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv return converted; } - private void createPopupActions() { + /** + * A method that subclasses can override to signal that they wish not to have this table's + * built-in popup actions. Subclasses will almost never need to override this method. + * + * @return true if popup actions are supported + */ + protected boolean supportsPopupActions() { + return true; + } + + private void installTableActions(DockingTool tool, String owner) { int subGroupIndex = 1; // order by insertion - String owner = getClass().getSimpleName(); - owner = "GTable"; - copyAction = new DockingAction("Table Data Copy", owner, KeyBindingType.SHARED) { + copyAction = new GTableAction("Table Data Copy", owner) { @Override public void actionPerformed(ActionContext context) { copying = true; @@ -1188,26 +1198,24 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv copyAction.setHelpLocation(new HelpLocation("Tables", "Copy")); //@formatter:on - copyCurrentColumnAction = - new DockingAction("Table Data Copy Current Column", owner, KeyBindingType.SHARED) { - @Override - public void actionPerformed(ActionContext context) { + copyCurrentColumnAction = new GTableAction("Table Data Copy Current Column", owner) { + @Override + public void actionPerformed(ActionContext context) { - int column = getSelectedColumn(); - - MouseEvent event = context.getMouseEvent(); - if (event != null) { - column = columnAtPoint(event.getPoint()); - } - - if (column < 0) { - Msg.debug(this, "Copy failed--no column selected"); - return; - } - - copyColumns(column); + int column = getSelectedColumn(); + MouseEvent event = context.getMouseEvent(); + if (event != null) { + column = columnAtPoint(event.getPoint()); } - }; + + if (column < 0) { + Msg.debug(this, "Copy failed--no column selected"); + return; + } + + copyColumns(column); + } + }; //@formatter:off copyCurrentColumnAction.setPopupMenuData(new MenuData( new String[] { "Copy", @@ -1226,18 +1234,17 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column")); //@formatter:on - copyColumnsAction = - new DockingAction("Table Data Copy by Columns", owner, KeyBindingType.SHARED) { - @Override - public void actionPerformed(ActionContext context) { - int[] userColumns = promptUserForColumns(); - if (userColumns == null) { - return; // cancelled - } - - copyColumns(userColumns); + copyColumnsAction = new GTableAction("Table Data Copy by Columns", owner) { + @Override + public void actionPerformed(ActionContext context) { + int[] userColumns = promptUserForColumns(); + if (userColumns == null) { + return; // cancelled } - }; + + copyColumns(userColumns); + } + }; //@formatter:off copyColumnsAction.setPopupMenuData(new MenuData( new String[] { "Copy", "Copy Columns..." }, @@ -1250,7 +1257,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns")); //@formatter:on - exportAction = new DockingAction("Table Data CSV Export", owner, KeyBindingType.SHARED) { + exportAction = new GTableAction("Table Data CSV Export", owner) { @Override public void actionPerformed(ActionContext context) { File file = chooseExportFile(); @@ -1271,27 +1278,26 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv exportAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV")); //@formatter:on - exportColumnsAction = - new DockingAction("Table Data CSV Export (by Columns)", owner, KeyBindingType.SHARED) { - @Override - public void actionPerformed(ActionContext context) { - int[] userColumns = promptUserForColumns(); - if (userColumns == null) { - return; // cancelled - } - - File file = chooseExportFile(); - if (file == null) { - return; - } - - List columnList = new ArrayList<>(); - for (int userColumn : userColumns) { - columnList.add(userColumn); - } - GTableToCSV.writeCSVUsingColunns(file, GTable.this, columnList); + exportColumnsAction = new GTableAction("Table Data CSV Export (by Columns)", owner) { + @Override + public void actionPerformed(ActionContext context) { + int[] userColumns = promptUserForColumns(); + if (userColumns == null) { + return; // cancelled } - }; + + File file = chooseExportFile(); + if (file == null) { + return; + } + + List columnList = new ArrayList<>(); + for (int userColumn : userColumns) { + columnList.add(userColumn); + } + GTableToCSV.writeCSVUsingColunns(file, GTable.this, columnList); + } + }; //@formatter:off exportColumnsAction.setPopupMenuData(new MenuData( new String[] { "Export", "Export Columns to CSV..." }, @@ -1304,7 +1310,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns")); //@formatter:on - selectAllAction = new DockingAction("Table Select All", owner, KeyBindingType.SHARED) { + selectAllAction = new GTableAction("Table Select All", owner) { @Override public void actionPerformed(ActionContext context) { selectAll(); @@ -1312,6 +1318,9 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv @Override public boolean isEnabledForContext(ActionContext context) { + if (!super.isEnabledForContext(context)) { + return false; + } return getSelectionModel().getSelectionMode() != ListSelectionModel.SINGLE_SELECTION; } }; @@ -1332,9 +1341,20 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv selectAllAction.setHelpLocation(new HelpLocation("Tables", "SelectAll")); //@formatter:on - KeyBindingUtils.registerAction(this, copyAction); - KeyBindingUtils.registerAction(this, copyCurrentColumnAction); - KeyBindingUtils.registerAction(this, selectAllAction); + // remove any conflicting key bindings that Java has installed on this component + KeyBindingUtils.clearKeyBinding(this, copyAction); + KeyBindingUtils.clearKeyBinding(this, copyCurrentColumnAction); + KeyBindingUtils.clearKeyBinding(this, copyColumnsAction); + KeyBindingUtils.clearKeyBinding(this, selectAllAction); + KeyBindingUtils.clearKeyBinding(this, exportAction); + KeyBindingUtils.clearKeyBinding(this, exportColumnsAction); + + tool.addAction(copyAction); + tool.addAction(copyCurrentColumnAction); + tool.addAction(copyColumnsAction); + tool.addAction(selectAllAction); + tool.addAction(exportAction); + tool.addAction(exportColumnsAction); } private void copyColumns(int... copyColumns) { @@ -1478,4 +1498,18 @@ public class GTable extends JTable implements KeyStrokeConsumer, PopupActionProv // ignored } } + + private abstract class GTableAction extends DockingAction { + + GTableAction(String name, String owner) { + super(name, owner, KeyBindingType.SHARED); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + Component sourceComponent = context.getSourceComponent(); + return sourceComponent == GTable.this; + } + } + } 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 653b4ec57a..9789fe69fd 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 @@ -214,7 +214,7 @@ public class GTableFilterPanel extends JPanel { table.addPropertyChangeListener(badProgrammingPropertyChangeListener); DockingWindowManager.registerComponentLoadedListener(this, - windowManager -> initialize(windowManager)); + (windowManager, provider) -> initialize(windowManager)); } private void initialize(DockingWindowManager windowManager) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnModelState.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnModelState.java index c0f321de7f..caf592dd63 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnModelState.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnModelState.java @@ -106,7 +106,7 @@ public class TableColumnModelState implements SortListener { // We want to load our state after the column model is loaded. We are using this // listener to know when the table has been added to the component hierarchy, as its // model has been loaded by then. - DockingWindowManager.registerComponentLoadedListener(table, windowManager -> { + DockingWindowManager.registerComponentLoadedListener(table, (windowManager, provider) -> { if (!enabled) { setEnabled(true); restoreState(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java index 91704d22ec..fce0d43d08 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java @@ -112,7 +112,7 @@ public class GTree extends JPanel implements BusyListener { init(); DockingWindowManager.registerComponentLoadedListener(this, - windowManager -> filterProvider.loadFilterPreference(windowManager, + (windowManager, provider) -> filterProvider.loadFilterPreference(windowManager, uniquePreferenceKey)); filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> performNodeFiltering()); diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/bean/EmptyBorderButtonTest.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/bean/EmptyBorderButtonTest.java index 1ae6cebaed..ec3dfa06f7 100644 --- a/Ghidra/Framework/Docking/src/test/java/ghidra/util/bean/EmptyBorderButtonTest.java +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/bean/EmptyBorderButtonTest.java @@ -18,8 +18,6 @@ package ghidra.util.bean; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.awt.event.MouseEvent; - import javax.swing.ButtonModel; import org.junit.Before; @@ -44,7 +42,7 @@ public class EmptyBorderButtonTest extends AbstractDockingTest { } @Test - public void testButtonBorderOnRollover() { + public void testButtonBorderOnRollover() { assertEquals(emptyBorderButton.getBorder(), EmptyBorderButton.NO_BUTTON_BORDER); buttonModel.setRollover(true); @@ -61,7 +59,7 @@ public class EmptyBorderButtonTest extends AbstractDockingTest { } @Test - public void testButtonBorderOnPress() { + public void testButtonBorderOnPress() { assertEquals(emptyBorderButton.getBorder(), EmptyBorderButton.NO_BUTTON_BORDER); // just pressing the button does not change the border... @@ -82,30 +80,15 @@ public class EmptyBorderButtonTest extends AbstractDockingTest { } private void setButtonArmed(final boolean armed) { - runSwing(new Runnable() { - @Override - public void run() { - buttonModel.setArmed(armed); - } - }); + runSwing(() -> buttonModel.setArmed(armed)); } private void setButtonPressed(final boolean pressed) { - runSwing(new Runnable() { - @Override - public void run() { - buttonModel.setPressed(pressed); - } - }); + runSwing(() -> buttonModel.setPressed(pressed)); } private void setRollover(final boolean isRollover) { - runSwing(new Runnable() { - @Override - public void run() { - buttonModel.setRollover(isRollover); - } - }); + runSwing(() -> buttonModel.setRollover(isRollover)); } // public void testButtonBorderOnModalDialog() throws InterruptedException { @@ -146,7 +129,7 @@ public class EmptyBorderButtonTest extends AbstractDockingTest { // } @Test - public void testButtonEnablementFromAction() { + public void testButtonEnablementFromAction() { DockingAction action = new DockingAction("name", "owner") { @Override public void actionPerformed(ActionContext context) { @@ -158,13 +141,8 @@ public class EmptyBorderButtonTest extends AbstractDockingTest { return true; } }; - ActionContextProvider contextProvider = new ActionContextProvider() { - - @Override - public ActionContext getActionContext(MouseEvent e) { - return new ActionContext(null, null, e.getSource()); - } - }; + ActionContextProvider contextProvider = + e -> new ActionContext(null, e.getSource(), e.getComponent()); action.setToolBarData(new ToolBarData(ResourceManager.getDefaultIcon())); action.setEnabled(false); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolActionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolActionManager.java index e54a0988a3..f4839571ca 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolActionManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ToolActionManager.java @@ -20,8 +20,6 @@ import java.awt.event.KeyEvent; import java.io.*; import java.util.*; -import javax.swing.JMenuItem; - import org.jdom.Element; import org.jdom.input.SAXBuilder; @@ -511,7 +509,7 @@ class ToolActionManager implements ToolChestChangeListener { ToolAction runAction = new ToolAction(toolName, "Run_Tool") { @Override public void actionPerformed(ActionContext context) { - String name = ((JMenuItem) context.getSourceObject()).getText(); + String name = getName(); Workspace ws = plugin.getActiveWorkspace(); ToolChest toolChest = plugin.getActiveProject().getLocalToolChest(); ws.runTool(toolChest.getToolTemplate(name)); @@ -529,7 +527,7 @@ class ToolActionManager implements ToolChestChangeListener { ToolAction deleteAction = new ToolAction(toolName, "Delete_Tool") { @Override public void actionPerformed(ActionContext context) { - String name = ((JMenuItem) context.getSourceObject()).getText(); + String name = getName(); if (!plugin.confirmDelete(name + " from the project tool chest?")) { return; } @@ -549,7 +547,7 @@ class ToolActionManager implements ToolChestChangeListener { ToolAction exportAction = new ToolAction(toolName, "Export_Tool") { @Override public void actionPerformed(ActionContext context) { - String name = ((JMenuItem) context.getSourceObject()).getText(); + String name = getName(); ToolChest toolChest = plugin.getActiveProject().getLocalToolChest(); plugin.exportToolConfig(toolChest.getToolTemplate(name), "Tool Menu"); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataActionContext.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataActionContext.java index c2acecdda6..320ce6aea4 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataActionContext.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataActionContext.java @@ -35,7 +35,7 @@ public class ProjectDataActionContext extends ActionContext implements DomainFil public ProjectDataActionContext(ComponentProvider provider, ProjectData projectData, Object contextObject, List selectedFolders, List selectedFiles, Component comp, boolean isActiveProject) { - super(provider, contextObject); + super(provider, contextObject, comp); this.projectData = projectData; this.selectedFolders = selectedFolders; this.selectedFiles = selectedFiles; 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 f510994dad..bda0f79e2d 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 @@ -25,8 +25,8 @@ import java.util.List; import javax.swing.*; -import docking.*; -import docking.action.DockingActionIf; +import docking.ActionContext; +import docking.ComponentProvider; import docking.help.Help; import docking.help.HelpService; import docking.widgets.label.GHtmlLabel; @@ -489,12 +489,8 @@ public class ProjectDataTablePanel extends JPanel { } @Override - public List getPopupActions(DockingTool tool, ActionContext context) { - - // TODO we should at least add the 'copy' action - - // the table's default actions aren't that useful in the Front End - return Collections.emptyList(); + protected boolean supportsPopupActions() { + return false; } } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java index a9c1cd9f87..328bd8341e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java @@ -135,7 +135,7 @@ public class VersionHistoryDialog extends DialogComponentProvider implements Pro @Override public ActionContext getActionContext(MouseEvent event) { - ActionContext actionContext = new ActionContext(null, versionPanel.getTable(), this); + ActionContext actionContext = new ActionContext(null, this, versionPanel.getTable()); actionContext.setMouseEvent(event); return actionContext; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index 6f23e542bc..0abe43fc2f 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java @@ -441,6 +441,8 @@ public abstract class PluginTool extends AbstractDockingTool implements Tool, Se winMgr.setVisible(false); eventMgr.clearLastEvents(); pluginMgr.dispose(); + + toolActions.removeActions(ToolConstants.TOOL_OWNER); toolActions.dispose(); if (project != null) { From e9c0921cfe78b071d3fb2166fa5a65e139e2cdfb Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 8 Aug 2019 16:34:09 -0400 Subject: [PATCH 2/2] GT-3044 - Table Actions - test and review fixes --- .../core/datamgr/SizeAlignmentPanel.java | 50 +- .../core/datamgr/editor/EnumEditorPanel.java | 97 ++- .../AutoTableDisassemblerPlugin.java | 5 +- .../InstructionSearchPlugin.java | 2 +- .../ui/AbstractInstructionTable.java | 5 + .../ui/InstructionSearchDialog.java | 7 +- .../instructionsearch/ui/PreviewTable.java | 69 +- .../plugin/core/memory/MemoryMapProvider.java | 3 +- .../java/ghidra/util/table/GhidraTable.java | 56 -- .../actions/MakeProgramSelectionAction.java | 9 +- .../docking/ComponentProviderActionsTest.java | 23 +- .../core/memory/MemoryMapPluginTest.java | 26 +- .../navigation/NextPrevAddressPluginTest.java | 20 - .../core/symtable/SymbolTablePluginTest.java | 216 +++--- .../dialog/KeyBindingUtilsTest.java | 33 +- .../plugintool/dialog/KeyBindingsTest.java | 36 +- .../java/ghidra/test/DummyToolActions.java | 5 - .../gui/AlignmentPanelBuilder.java | 94 ++- .../DecompilerCodeComparisonPanel.java | 6 +- .../decompiler/component/DecompilerUtils.java | 6 - .../src/main/java/docking/ActionContext.java | 6 +- .../main/java/docking/ActionToGuiMapper.java | 4 + .../main/java/docking/ComponentProvider.java | 30 +- .../java/docking/DialogComponentProvider.java | 14 + ...ogComponentProviderPopupActionManager.java | 45 +- .../main/java/docking/PopupActionManager.java | 36 +- .../docking/actions/DockingToolActions.java | 3 - .../docking/actions/SharedActionRegistry.java | 39 ++ .../actions/SharedStubKeyBindingAction.java | 8 +- .../java/docking/actions/ToolActions.java | 20 +- .../java/docking/widgets/table/GTable.java | 623 +++++++++--------- .../filechooser/GhidraFileChooserTest.java | 4 +- .../DockingWindowManagerTestHelper.java | 38 ++ .../main/datatable/ProjectDataTablePanel.java | 2 +- .../main/datatree/VersionHistoryPanel.java | 2 +- .../plugintool/dialog/KeyBindingsPanel.java | 26 +- 36 files changed, 920 insertions(+), 748 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/actions/SharedActionRegistry.java create mode 100644 Ghidra/Framework/Docking/src/test/java/docking/DockingWindowManagerTestHelper.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/SizeAlignmentPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/SizeAlignmentPanel.java index 3cbfbc3919..6efc835b38 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/SizeAlignmentPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/SizeAlignmentPanel.java @@ -30,58 +30,62 @@ import ghidra.util.exception.NoValueException; import ghidra.util.table.GhidraTable; public class SizeAlignmentPanel extends JPanel { - + GhidraTable table; DataOrganizationImpl dataOrganization; public SizeAlignmentPanel() { super(new BorderLayout()); TableModel tableModel = new SizeAlignmentTableModel(); - table = new GhidraTable(tableModel, true); - JScrollPane sp = new JScrollPane(table); - table.setPreferredScrollableViewportSize(new Dimension(200, 80)); + table = new GhidraTable(tableModel); + table.setAutoEditEnabled(true); + JScrollPane sp = new JScrollPane(table); + table.setPreferredScrollableViewportSize(new Dimension(200, 80)); add(sp, BorderLayout.CENTER); } public void setOrganization(DataOrganizationImpl dataOrganization) { this.dataOrganization = dataOrganization; - ((SizeAlignmentTableModel)table.getModel()).fireTableDataChanged(); + ((SizeAlignmentTableModel) table.getModel()).fireTableDataChanged(); } - + class SizeAlignmentTableModel extends AbstractTableModel { - - private final String[] columnNames = new String[] {"Size", "Alignment"}; + + private final String[] columnNames = new String[] { "Size", "Alignment" }; private final int SIZE_COLUMN = 0; private final int ALIGNMENT_COLUMN = 1; - + SizeAlignmentTableModel() { super(); } @Override - public void addTableModelListener(TableModelListener l) { + public void addTableModelListener(TableModelListener l) { // TODO Auto-generated method stub - + } @Override - public Class getColumnClass(int columnIndex) { + public Class getColumnClass(int columnIndex) { return Integer.class; } + @Override public int getColumnCount() { return columnNames.length; } @Override - public String getColumnName(int columnIndex) { + public String getColumnName(int columnIndex) { return columnNames[columnIndex]; } + @Override public int getRowCount() { return dataOrganization.getSizeAlignmentCount() + 1; } + @Override public Object getValueAt(int rowIndex, int columnIndex) { int[] sizes = dataOrganization.getSizes(); if (rowIndex < sizes.length) { @@ -92,7 +96,8 @@ public class SizeAlignmentPanel extends JPanel { else if (columnIndex == ALIGNMENT_COLUMN) { try { return dataOrganization.getSizeAlignment(size); - } catch (NoValueException e) { + } + catch (NoValueException e) { return null; } } @@ -101,7 +106,7 @@ public class SizeAlignmentPanel extends JPanel { } @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { + public boolean isCellEditable(int rowIndex, int columnIndex) { if (rowIndex == dataOrganization.getSizeAlignmentCount()) { return columnIndex == SIZE_COLUMN; } @@ -109,30 +114,31 @@ public class SizeAlignmentPanel extends JPanel { } @Override - public void removeTableModelListener(TableModelListener l) { + public void removeTableModelListener(TableModelListener l) { // TODO Auto-generated method stub - + } @Override - public void setValueAt(Object value, int rowIndex, int columnIndex) { + public void setValueAt(Object value, int rowIndex, int columnIndex) { if (value == null) { return; } int[] sizes = dataOrganization.getSizes(); if (rowIndex < sizes.length) { - int alignment = ((Integer)value).intValue(); + int alignment = ((Integer) value).intValue(); int size = sizes[rowIndex]; dataOrganization.setSizeAlignment(size, alignment); } if (rowIndex == sizes.length) { - int size = ((Integer)value).intValue(); + int size = ((Integer) value).intValue(); // Check that we don't already have this size. try { dataOrganization.getSizeAlignment(size); - setStatusMessage("An alignment is already defined for a size of "+size+"."); + setStatusMessage("An alignment is already defined for a size of " + size + "."); return; - } catch (NoValueException e) { + } + catch (NoValueException e) { // Actually don't want to find a value so we can set one below. } int alignment = size; // Set the alignment to match the size initially. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java index 8d5b494330..48c12b49cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java @@ -113,25 +113,22 @@ class EnumEditorPanel extends JPanel { // invoke later because the key press on the table causes the selection // to change - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - try { - if (table.isEditing()) { - return; // don't change the selection if a new edit is in progress - } + SwingUtilities.invokeLater(() -> { + try { + if (table.isEditing()) { + return; // don't change the selection if a new edit is in progress + } - int row = tableModel.getRow(name); - if (row >= 0 && row < tableModel.getRowCount()) { - table.setRowSelectionInterval(row, row); - Rectangle rect = table.getCellRect(row, 0, false); - table.scrollRectToVisible(rect); - } - } - catch (NoSuchElementException e) { - // ignore + int row = tableModel.getRow(name); + if (row >= 0 && row < tableModel.getRowCount()) { + table.setRowSelectionInterval(row, row); + Rectangle rect = table.getCellRect(row, 0, false); + table.scrollRectToVisible(rect); } } + catch (NoSuchElementException e) { + // ignore + } }); } @@ -232,8 +229,8 @@ class EnumEditorPanel extends JPanel { void deleteSelectedEntries() { EnumDataType enuum = getEnum(); int[] rows = getSelectedRows(); - for (int i = 0; i < rows.length; i++) { - String name = tableModel.getNameAt(rows[i]); + for (int row : rows) { + String name = tableModel.getNameAt(row); enuum.remove(name); } tableModel.setEnum(enuum, true); @@ -262,15 +259,12 @@ class EnumEditorPanel extends JPanel { "All possible Enum values have already been used"); return; } - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - table.setRowSelectionInterval(newRow, newRow); - table.editCellAt(newRow, EnumTableModel.NAME_COL); - Rectangle r = table.getCellRect(newRow, 0, true); - table.scrollRectToVisible(r); - provider.stateChanged(null); - } + SwingUtilities.invokeLater(() -> { + table.setRowSelectionInterval(newRow, newRow); + table.editCellAt(newRow, EnumTableModel.NAME_COL); + Rectangle r = table.getCellRect(newRow, 0, true); + table.scrollRectToVisible(r); + provider.stateChanged(null); }); } @@ -418,18 +412,15 @@ class EnumEditorPanel extends JPanel { sizeComboBox = new GhidraComboBox(new Integer[] { 1, 2, 4, 8 }); sizeComboBox.setName("Size"); - sizeComboBox.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - Integer length = (Integer) sizeComboBox.getSelectedItem(); - if (!validateNewLength(length)) { - return; - } - - setStatusMessage(""); - tableModel.setLength(length); - provider.stateChanged(null); + sizeComboBox.addItemListener(e -> { + Integer length = (Integer) sizeComboBox.getSelectedItem(); + if (!validateNewLength(length)) { + return; } + + setStatusMessage(""); + tableModel.setLength(length); + provider.stateChanged(null); }); JLabel label = new GLabel("Category:", SwingConstants.RIGHT); @@ -465,13 +456,10 @@ class EnumEditorPanel extends JPanel { } private void vetoSizeChange(final int newLength, final int currentLength, final long badValue) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - setStatusMessage("Enum size of " + newLength + " cannot contain the value " + "0x" + - Long.toHexString(badValue)); - sizeComboBox.setSelectedItem(new Integer(currentLength)); - } + SwingUtilities.invokeLater(() -> { + setStatusMessage("Enum size of " + newLength + " cannot contain the value " + "0x" + + Long.toHexString(badValue)); + sizeComboBox.setSelectedItem(new Integer(currentLength)); }); } @@ -495,12 +483,9 @@ class EnumEditorPanel extends JPanel { } private void focus(final JTextField field) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - field.requestFocusInWindow(); - field.selectAll(); - } + SwingUtilities.invokeLater(() -> { + field.requestFocusInWindow(); + field.selectAll(); }); } @@ -510,7 +495,8 @@ class EnumEditorPanel extends JPanel { private class EnumTable extends GhidraTable { EnumTable(TableModel model) { - super(model, true); + super(model); + setAutoEditEnabled(true); } } @@ -534,12 +520,7 @@ class EnumEditorPanel extends JPanel { public EnumCellEditor(JTextField textField) { super(textField); textField.addKeyListener(editingKeyListener); - textField.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - table.editingStopped(null); - } - }); + textField.addActionListener(e -> table.editingStopped(null)); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java index 0f4a953883..6377560a94 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java @@ -148,9 +148,8 @@ public class AutoTableDisassemblerPlugin extends ProgramPlugin implements Domain }; findTableAction.setHelpLocation( new HelpLocation(HelpTopics.SEARCH, findTableAction.getName())); - findTableAction.setMenuBarData( - new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Address Tables..." }, null, - "search for")); + findTableAction.setMenuBarData(new MenuData( + new String[] { ToolConstants.MENU_SEARCH, "For Address Tables" }, null, "search for")); findTableAction.setDescription(getPluginDescription().getDescription()); tool.addAction(findTableAction); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java index ff24c07a60..073ae5fea6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/InstructionSearchPlugin.java @@ -251,7 +251,7 @@ public class InstructionSearchPlugin extends ProgramPlugin { }; searchAction.setHelpLocation(new HelpLocation("Search", "Instruction_Pattern_Search")); searchAction.setMenuBarData( - new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Instruction Patterns..." }, + new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Instruction Patterns" }, null, "search for")); searchAction.setDescription("Construct searches using selected instructions"); tool.addAction(searchAction); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java index f9afdb717b..270620a717 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java @@ -21,6 +21,7 @@ import javax.swing.JToolBar; import javax.swing.table.TableCellRenderer; import docking.widgets.table.GTable; +import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.*; import ghidra.util.table.GhidraTable; @@ -85,6 +86,10 @@ public abstract class AbstractInstructionTable extends GhidraTable { this.setRowHeight(this.getRowHeight() + CELL_HEIGHT_PADDING); } + InstructionSearchPlugin getPlugin() { + return dialog.getPlugin(); + } + /** * Returns the data object at the given cell location. We need to check * first to make sure the row/col values map to a valid cell. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java index f36752b8de..ec79cebaad 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionSearchDialog.java @@ -504,13 +504,12 @@ public class InstructionSearchDialog extends DialogComponentProvider implements SystemUtilities.runSwingLater(runnable); } - /** - * - * @param addr - */ private void goToLocation(Address addr) { GoToService gs = plugin.getTool().getService(GoToService.class); gs.goTo(addr); } + public InstructionSearchPlugin getPlugin() { + return plugin; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java index 9243081140..0a348b0e76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java @@ -25,14 +25,17 @@ import java.util.*; import javax.swing.*; import javax.swing.table.TableCellRenderer; -import docking.*; -import docking.action.*; +import docking.ActionContext; +import docking.EmptyBorderToggleButton; +import docking.action.DockingAction; +import docking.action.MenuData; import docking.dnd.GClipboard; import docking.widgets.EmptyBorderButton; import ghidra.app.plugin.core.instructionsearch.InstructionSearchPlugin; import ghidra.app.plugin.core.instructionsearch.model.*; import ghidra.app.plugin.core.instructionsearch.ui.SelectionModeWidget.InputMode; import ghidra.app.plugin.core.instructionsearch.util.InstructionSearchUtils; +import ghidra.framework.plugintool.PluginTool; import ghidra.util.Msg; import ghidra.util.exception.InvalidInputException; import ghidra.util.task.*; @@ -78,6 +81,7 @@ public class PreviewTable extends AbstractInstructionTable { * * @param numColumns the number of columns in the table * @param plugin the parent plugin + * @param dialog the search dialog */ public PreviewTable(int numColumns, InstructionSearchPlugin plugin, InstructionSearchDialog dialog) { @@ -131,30 +135,6 @@ public class PreviewTable extends AbstractInstructionTable { refreshView(); } - /** - * Adds custom context-sensitive menus to the table. This does NOT modify - * any existing menus; it simply adds to them. - */ - - // TODO - // TODO - // TODO - // TODO Change the custom menu items to be real Docking Actions - // TODO - // TODO - -// @Override -// public List getPopupActions(DockingTool tool, ActionContext context) { -// -// // Invoke the base class method to add default menu options. -// List list = super.getPopupActions(tool, context); -// -// // And now add our own. -// addCustomMenuItems(list); -// -// return list; -// } - /** * Replaces the contents of the preview table at the given row with the * given string. @@ -381,14 +361,6 @@ public class PreviewTable extends AbstractInstructionTable { return binaryBtn; } - private void addCustomMenuItems(List list) { - DockingWindowManager dwm = DockingWindowManager.getInstance(this); - dwm.setMenuGroup(new String[] { "Copy Special" }, actionMenuGroup, "1"); - list.add(copyNoSpacesAction); - list.add(copyInstructionAction); - list.add(copyInstructionWithCommentsAction); - } - /** * Gathers the search strings for each instruction and returns them as a * single string. @@ -470,6 +442,10 @@ public class PreviewTable extends AbstractInstructionTable { private void createContextMenuActions() { String owner = getClass().getSimpleName(); + InstructionSearchPlugin plugin = getPlugin(); + PluginTool tool = plugin.getTool(); + tool.setMenuGroup(new String[] { "Copy Special" }, actionMenuGroup, "1"); + createCopyNoSpacesAction(owner); copyNoSpacesAction.setPopupMenuData( new MenuData(new String[] { "Copy Special", "Selected instructions (no spaces)" }, @@ -487,6 +463,10 @@ public class PreviewTable extends AbstractInstructionTable { new MenuData(new String[] { "Copy Special", "Selected Instructions (with comments)" }, ResourceManager.loadImage("images/page_white_copy.png"), actionMenuGroup, MenuData.NO_MNEMONIC, Integer.toString(1))); + + dialog.addAction(copyNoSpacesAction); + dialog.addAction(copyInstructionAction); + dialog.addAction(copyInstructionWithCommentsAction); } /** @@ -518,7 +498,14 @@ public class PreviewTable extends AbstractInstructionTable { Clipboard clip = GClipboard.getSystemClipboard(); clip.setContents(sel, null); } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return context.getSourceComponent() == PreviewTable.this; + } }; + + copyInstructionWithCommentsAction.setHelpLocation(dialog.getHelpLocatdion()); } /** @@ -539,7 +526,14 @@ public class PreviewTable extends AbstractInstructionTable { Clipboard clip = GClipboard.getSystemClipboard(); clip.setContents(sel, null); } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return context.getSourceComponent() == PreviewTable.this; + } }; + + copyInstructionAction.setHelpLocation(dialog.getHelpLocatdion()); } /** @@ -560,7 +554,14 @@ public class PreviewTable extends AbstractInstructionTable { Clipboard clip = GClipboard.getSystemClipboard(); clip.setContents(sel, null); } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return context.getSourceComponent() == PreviewTable.this; + } }; + + copyNoSpacesAction.setHelpLocation(dialog.getHelpLocatdion()); } private class BinaryAction extends AbstractAction { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java index 058f64a356..693fb74a67 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java @@ -652,7 +652,8 @@ class MemoryMapProvider extends ComponentProviderAdapter { private class MemoryMapTable extends GhidraTable { MemoryMapTable(TableModel model) { - super(model, true); + super(model); + setAutoEditEnabled(true); setActionsEnabled(true); setVisibleRowCount(10); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java index 4d16646936..b8c74054c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java @@ -51,66 +51,10 @@ public class GhidraTable extends GTable { public GhidraTable() { super(); - } public GhidraTable(TableModel model) { super(model); - - } - - /** - * Constructs a new GhidraTable using the specified table model. - * If allowAutoEdit is true, then automatic editing is enabled. - * Auto-editing implies that typing in an editable cell will automatically - * force the cell into edit mode. - * If allowAutoEdit is false, then F2 must be hit before editing may commence. - * @param dm the table model - * @param allowAutoEdit true if auto-editing is allowed - */ - public GhidraTable(TableModel dm, boolean allowAutoEdit) { - super(dm, allowAutoEdit); - - } - - /** - * Constructs a GhidraTable to display the values in the two dimensional array, - * rowData, with column names, columnNames. - * rowData is an array of rows, so the value of the cell at row 1, - * column 5 can be obtained with the following code: - *

- *

 rowData[1][5]; 
- *

- * All rows must be of the same length as columnNames. - *

- * @param rowData the data for the new table - * @param columnNames names of each column - */ - public GhidraTable(Object[][] rowData, Object[] columnNames) { - super(rowData, columnNames); - } - - /** - * Constructs a GhidraTable to display the values in the two dimensional array, - * rowData, with column names, columnNames. - * rowData is an array of rows, so the value of the cell at row 1, - * column 5 can be obtained with the following code: - *

- *

 rowData[1][5]; 
- *

- * All rows must be of the same length as columnNames. - *

- * If allowAutoEdit is true, then automatic editing is enabled. - * Auto-editing implies that typing in an editable cell will automatically - * force the cell into edit mode. - * If allowAutoEdit is false, then F2 must be hit before editing may commence. - * - * @param rowData the data for the new table - * @param columnNames names of each column - * @param allowAutoEdit true if auto-editing is allowed - */ - public GhidraTable(Object[][] rowData, Object[] columnNames, boolean allowAutoEdit) { - super(rowData, columnNames, allowAutoEdit); } /** 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 da4a2e138e..356f85b23d 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 @@ -15,6 +15,8 @@ */ package ghidra.util.table.actions; +import java.awt.Component; + import javax.swing.KeyStroke; import docking.ActionContext; @@ -36,10 +38,9 @@ import resources.Icons; */ public class MakeProgramSelectionAction extends DockingAction { - private GhidraTable table; - // we will have one of these fields be non-null after construction private Plugin plugin; + private GhidraTable table; /** * Special constructor for clients that do not have a plugin. Clients using this @@ -92,8 +93,8 @@ public class MakeProgramSelectionAction extends DockingAction { @Override public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject != table) { + Component component = context.getSourceComponent(); + if (component != table) { return false; } diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java index 5dc2c58e3e..e2816e4c3e 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java @@ -33,8 +33,7 @@ import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; -import ghidra.util.Msg; -import ghidra.util.SpyErrorLogger; +import ghidra.util.*; import resources.Icons; import resources.ResourceManager; @@ -335,8 +334,11 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, DockingUtils.CONTROL_KEY_MODIFIER_MASK); setKeyBindingViaF4Dialog_FromCloseButton(controlEsc); + // Note: there may be a test focus issue here. If this test fails sporadically due to + // how the action context is generated (it depends on focus). It is only useful to fail + // here in development mode. pressKey(controlEsc); - assertProviderIsHidden(); + assertProviderIsHidden_InNonBatchMode(); } @Test @@ -461,9 +463,18 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio // runSwing(() -> tool.isActive(provider))); } - private void assertProviderIsHidden() { - assertFalse("The test provider is showing, but should be hidden", - runSwing(() -> tool.isVisible(provider))); + private void assertProviderIsHidden_InNonBatchMode() { + + boolean isVisible = runSwing(() -> tool.isVisible(provider)); + if (!SystemUtilities.isInTestingBatchMode()) { + assertFalse("The test provider is showing, but should be hidden", isVisible); + return; + } + + if (isVisible) { + Msg.error(this, + "Provider should not be visible after pressing the 'close provider' key bindings"); + } } private void assertNoToolbarAction() { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java index b035bb9836..e2d2467796 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java @@ -27,6 +27,7 @@ import javax.swing.table.TableModel; import org.junit.*; +import docking.ActionContext; import docking.action.DockingActionIf; import ghidra.app.cmd.memory.*; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; @@ -100,11 +101,12 @@ public class MemoryMapPluginTest extends AbstractGhidraHeadedIntegrationTest { for (DockingActionIf action : actions) { String name = action.getName(); if (name.equals("Add Block") || name.equals("Set Image Base") || - name.equals("Memory Map") || name.equals("Close Window")) { - assertTrue(action.isEnabledForContext(provider.getActionContext(null))); + name.equals("Memory Map") || name.equals("Close Window") || + name.contains("Table")) { + assertActionEnabled(action, getActionContext(), true); } else { - assertFalse(action.isEnabledForContext(provider.getActionContext(null))); + assertActionEnabled(action, getActionContext(), false); } } @@ -121,10 +123,26 @@ public class MemoryMapPluginTest extends AbstractGhidraHeadedIntegrationTest { if (name.equals("Memory Map") || name.equals("Close Window")) { continue; } - assertFalse(action.isEnabledForContext(provider.getActionContext(null))); + assertActionEnabled(action, getActionContext(), false); } } + private void assertActionEnabled(DockingActionIf action, ActionContext context, + boolean shouldBeEnabled) { + + String text = shouldBeEnabled ? "should be enabled" : "should be disabled"; + assertEquals("Action " + text + ", but is not: '" + action.getFullName() + "'", + shouldBeEnabled, action.isEnabledForContext(context)); + } + + private ActionContext getActionContext() { + ActionContext context = provider.getActionContext(null); + if (context == null) { + return new ActionContext(); + } + return context; + } + @Test public void testBlockNameChanged() throws Exception { MemoryBlock block = memory.getBlock(program.getMinAddress()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java index 8794cae194..426f3e3e71 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java @@ -331,26 +331,6 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe clickMouse(component, MouseEvent.BUTTON1, x, y, 1, 0); } - private JPopupMenu getPopupMenu(Container parent) { - Component[] components = parent.getComponents(); - for (Component component : components) { - if (component instanceof JPopupMenu) { - return (JPopupMenu) component; - } - } - - for (Component component : components) { - if (component instanceof Container) { - JPopupMenu popupMenu = getPopupMenu((Container) component); - if (popupMenu != null) { - return popupMenu; - } - } - } - - return null; - } - @SuppressWarnings("unchecked") // let caution fly private JButton findButtonForAction(DockingWindowManager windowManager, DockingAction action) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java index 08416bc884..e79fd67133 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java @@ -31,7 +31,7 @@ import javax.swing.table.*; import org.jdom.Element; import org.junit.*; -import docking.ActionContext; +import docking.*; import docking.action.DockingActionIf; import docking.action.ToggleDockingAction; import docking.widgets.filter.*; @@ -117,7 +117,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testNavigation() throws Exception { - openProgram("notepad"); + openProgram("sample"); int row = findRow("ghidra", "Global"); TableModel model = symbolTable.getModel(); @@ -128,7 +128,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSortingLabelColumn() throws Exception { - openProgram("notepad"); + openProgram("sample"); Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LABEL_COL); @@ -170,7 +170,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { // Note: this is somewhat of a tripwire test--it is designed to catch a major breakage // to the DynamicTableColumn discovery mechanism. // - openProgram("notepad"); + openProgram("sample"); List columnNames = new ArrayList<>(); int columnCount = symbolModel.getColumnCount(); @@ -203,7 +203,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSortingAddressColumn() throws Exception { - openProgram("notepad"); + openProgram("sample"); Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LOCATION_COL); @@ -239,7 +239,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSortingReferenceColumn() throws Exception { - openProgram("notepad"); + openProgram("sample"); sortOnColumn(SymbolTableModel.REFS_COL); @@ -267,7 +267,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testFilter() throws Exception { - openProgram("notepad"); + openProgram("sample"); performAction(setFilterAction, new ActionContext(), false); waitForSwing(); @@ -331,15 +331,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(2, symbolTable.getRowCount()); } - private FilterDialog showFilterDialog() { - - performAction(setFilterAction, false); - - FilterDialog dialog = waitForDialogComponent(FilterDialog.class); - assertNotNull(dialog); - return dialog; - } - @Test public void testFilterPersistence() throws Exception { @@ -362,39 +353,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(savedXml, restoredXml); } - private void changeSomeFilterSettings(NewSymbolFilter filter) { - // - // Change different filter types and values. (This requires some guilty knowledge). - // - // Symbol type name and default state: - // - // Symbol Types: - // Label filters: instruction (active), data (active), function (active) - // Non-label filters: namespaces, classes, params, etc (all inactive) - // - // Advanced filters: externals, globals, entry points, locals, etc (all inactive) - // - // Symbol Source Types: user defined (active), imported (active), - // default label (inactive), default function, analysis (active) - // - - boolean active = true; - boolean inactive = false; - filter.setFilter("User Defined", inactive); - filter.setFilter("Default (Labels)", active); - - filter.setFilter("Function Labels", inactive); - - filter.setFilter("Local Variables", active); - - filter.setFilter("Register Variables", active); - filter.setFilter("Subroutines", active); - filter.setFilter("Non-Primary Labels", active); - } - @Test public void testEditing() throws Exception { - openProgram("notepad"); + openProgram("sample"); waitForNotBusy(symbolTable); @@ -421,7 +382,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testQuickLookup() throws Exception { - openProgram("notepad"); + openProgram("sample"); int id = prog.startTransaction(testName.getMethodName()); try { @@ -440,7 +401,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { waitForNotBusy(symbolTable); - runSwing(() -> symbolTable.setRowSelectionInterval(0, 0)); + selectRow(0); triggerAutoLookup("a"); waitForNotBusy(symbolTable); @@ -462,7 +423,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(findRow("abc1", "Global"), symbolTable.getSelectedRow()); Thread.sleep(GTable.KEY_TIMEOUT); - runSwing(() -> symbolTable.setRowSelectionInterval(0, 0)); + selectRow(0); waitForSwing(); triggerAutoLookup("abc12"); waitForNotBusy(symbolTable); @@ -471,7 +432,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testDeleting() throws Exception { - openProgram("notepad"); + openProgram("sample"); int rowCount = symbolTable.getRowCount(); assertTrue(!deleteAction.isEnabled()); @@ -501,8 +462,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(rowCount, symbolTable.getRowCount()); final int anotherLocal_RowIndex = findRow("AnotherLocal", "Global"); - runSwing(() -> symbolTable.setRowSelectionInterval(anotherLocal_RowIndex, - anotherLocal_RowIndex)); + selectRow(anotherLocal_RowIndex); int selectedRow = symbolTable.getSelectedRow(); assertEquals("Row was not selected!", anotherLocal_RowIndex, selectedRow); @@ -538,29 +498,37 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { setupSymbolTableFilterToShowParameters(); - final int row = getRowForSymbol(param1Symbol); - - // select that row - runSwing(() -> symbolTable.setRowSelectionInterval(row, row)); + int row = getRowForSymbol(param1Symbol); + selectRow(row); // execute the delete action performAction(deleteAction, true); Assert.assertNotEquals(param1Symbol, getUniqueSymbol(prog, "param_1", function)); } + @Test + public void testBuiltInTableActionsAvailable() throws Exception { + openProgram("sample"); + + int row = 0; + selectRow(row); + + JPopupMenu popup = triggerPopup(row); + List popupItems = getPopupMenuItems(popup); + assertMenuContains(popupItems, "Copy"); + assertMenuContains(popupItems, "Export"); + assertMenuContains(popupItems, "Select All"); + } + @Test public void testMakeSelection() throws Exception { - openProgram("notepad"); + openProgram("sample"); assertTrue(!makeSelectionAction.isEnabled()); final int row = findRow("ghidra", "Global"); int rowCount = 3; - runSwing(() -> { - symbolTable.setRowSelectionInterval(row, row + 2); - Rectangle rect = symbolTable.getCellRect(row + 2, 0, true); - symbolTable.scrollRectToVisible(rect); - }); + selectRow(row, row + 2); assertTrue(makeSelectionAction.isEnabled()); @@ -590,14 +558,11 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSetAndClearPinnedAction() throws Exception { - openProgram("notepad"); + openProgram("sample"); + + int row = findRow("ADVAPI32.dll_IsTextUnicode", "Global"); + selectRow(row, row + 2); - final int row = findRow("ADVAPI32.dll_IsTextUnicode", "Global"); - runSwing(() -> { - symbolTable.setRowSelectionInterval(row, row + 2); - Rectangle rect = symbolTable.getCellRect(row + 2, 0, true); - symbolTable.scrollRectToVisible(rect); - }); ActionContext actionContext = provider.getActionContext(null); int[] selectedRows = symbolTable.getSelectedRows(); assertEquals(3, selectedRows.length); @@ -626,14 +591,11 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSetPinnedActionNotEnabledForExternalSymbols() throws Exception { - openProgram("notepad"); + openProgram("sample"); + + int row = findRow("CharLowerW", "USER32.dll"); + selectRow(row, row + 1); - final int row = findRow("CharLowerW", "USER32.dll"); - runSwing(() -> { - symbolTable.setRowSelectionInterval(row, row + 1); - Rectangle rect = symbolTable.getCellRect(row + 1, 0, true); - symbolTable.scrollRectToVisible(rect); - }); ActionContext actionContext = provider.getActionContext(null); int[] selectedRows = symbolTable.getSelectedRows(); @@ -648,7 +610,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testUpdateOnSymbolsAdded() throws Exception { - openProgram("notepad"); + openProgram("sample"); Address sample = prog.getMinAddress(); SymbolTable st = prog.getSymbolTable(); Symbol sym = null; @@ -673,7 +635,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSymbolsAddedWithFilterOn() throws Exception { - openProgram("notepad"); + openProgram("sample"); final JTextField textField = getFilterTextField(); final JCheckBox checkBox = findComponent(filterPanel, JCheckBox.class); @@ -709,7 +671,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testDefaultFunctionToNamedFunctionWithFilterOn() throws Exception { - openProgram("notepad"); + openProgram("sample"); performAction(setFilterAction, new ActionContext(), false); waitForSwing(); @@ -742,7 +704,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testUpdateOnSymbolsRemoved() throws Exception { - openProgram("notepad"); + openProgram("sample"); SymbolTable st = prog.getSymbolTable(); Symbol sym = getUniqueSymbol(prog, "entry"); @@ -765,7 +727,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testUpdateOnReferencesAdded() throws Exception { - openProgram("notepad"); + openProgram("sample"); Address sample = prog.getMinAddress(); Symbol s = getUniqueSymbol(prog, "entry"); @@ -796,7 +758,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testUpdateOnReferencesRemoved() throws Exception { - openProgram("notepad"); + openProgram("sample"); Address sample = prog.getMinAddress(); Symbol s = getUniqueSymbol(prog, "doStuff"); @@ -836,7 +798,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testUpdateOnProgramRestore() throws Exception { - openProgram("notepad"); + openProgram("sample"); int id = prog.startTransaction(testName.getMethodName()); try { @@ -959,7 +921,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testReferences() throws Exception { - openProgram("notepad"); + openProgram("sample"); showReferencesTable(); @@ -1020,7 +982,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testFilterTextField() throws Exception { - openProgram("notepad"); + openProgram("sample"); JTextField textField = getFilterTextField(); @@ -1167,7 +1129,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testFilterTextFieldFindsAllMatches() throws Exception { - openProgram("notepad"); + openProgram("sample"); JTextField textField = getFilterTextField(); @@ -1214,6 +1176,86 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { // Helper methods //================================================================================================== + private void assertMenuContains(List popupItems, String string) { + for (JMenuItem item : popupItems) { + String text = item.getText(); + if (text.equals(string)) { + return; // found it + } + } + fail("'" + string + "' not in the popup menu!"); + } + + private List getPopupMenuItems(JPopupMenu popup) { + List list = new ArrayList<>(); + Component[] children = popup.getComponents(); + for (Component child : children) { + if (child instanceof JMenuItem) { + list.add((JMenuItem) child); + } + } + return list; + } + + private JPopupMenu triggerPopup(int row) { + DockingWindowManager dwm = DockingWindowManager.getInstance(symbolTable); + ActionContext context = provider.getActionContext(null); + JPopupMenu popup = + runSwing(() -> DockingWindowManagerTestHelper.getPopupMenu(dwm, context)); + return popup; + } + + private void selectRow(int row) { + selectRow(row, row); + } + + private void selectRow(int start, int end) { + runSwing(() -> { + symbolTable.setRowSelectionInterval(start, end); + Rectangle rect = symbolTable.getCellRect(end, 0, true); + symbolTable.scrollRectToVisible(rect); + }); + } + + private FilterDialog showFilterDialog() { + + performAction(setFilterAction, false); + + FilterDialog dialog = waitForDialogComponent(FilterDialog.class); + assertNotNull(dialog); + return dialog; + } + + private void changeSomeFilterSettings(NewSymbolFilter filter) { + // + // Change different filter types and values. (This requires some guilty knowledge). + // + // Symbol type name and default state: + // + // Symbol Types: + // Label filters: instruction (active), data (active), function (active) + // Non-label filters: namespaces, classes, params, etc (all inactive) + // + // Advanced filters: externals, globals, entry points, locals, etc (all inactive) + // + // Symbol Source Types: user defined (active), imported (active), + // default label (inactive), default function, analysis (active) + // + + boolean active = true; + boolean inactive = false; + filter.setFilter("User Defined", inactive); + filter.setFilter("Default (Labels)", active); + + filter.setFilter("Function Labels", inactive); + + filter.setFilter("Local Variables", active); + + filter.setFilter("Register Variables", active); + filter.setFilter("Subroutines", active); + filter.setFilter("Non-Primary Labels", active); + } + private void triggerAutoLookup(String text) { KeyListener listener = (KeyListener) getInstanceField("autoLookupListener", symbolTable); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java index 320f3b2e26..7c4076f5d7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java @@ -29,6 +29,7 @@ import javax.swing.tree.TreePath; import org.junit.*; +import docking.ComponentProvider; import docking.action.DockingActionIf; import docking.actions.KeyBindingUtils; import docking.options.editor.OptionsDialog; @@ -86,6 +87,13 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { // Msg.debug(this, "Writing debug data to: " + file); // debug = new FileWriter(file); + // debug to the local console +// debug = new PrintWriter(System.out); + + setUpTool(); + } + + private void setUpTool() throws Exception { debug("setUp()"); env = new TestEnv(); @@ -104,9 +112,21 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { tool.addPlugin(FunctionPlugin.class.getName()); tool.addPlugin(EquateTablePlugin.class.getName()); + // Unusual Code: Some actions don't get created until the table is shown (like GTable + // actions). Show a provider that has a table so that the actions will get correctly + // loaded into the key bindings panel + showTableProvider(); + debug("two"); } + private void showTableProvider() { + EquateTablePlugin eqp = env.getPlugin(EquateTablePlugin.class); + ComponentProvider provider = (ComponentProvider) getInstanceField("provider", eqp); + env.showTool(); + tool.showComponentProvider(provider, true); + } + private void debug(String message) { if (debug == null) { return; @@ -208,7 +228,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { ToolOptions originalOptions = importOptions(saveFile); assertOptionsMatch( - "The Options objects do not contain different data after " + "changes have been made.", + "The Options objects do not contain different data after changes have been made.", toolKeyBindingOptions, originalOptions); debug("c"); @@ -220,7 +240,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { // verify the changes are different than the original values assertOptionsDontMatch( - "The Options objects do not contain different data after " + "changes have been made.", + "The Options objects do not contain different data after changes have been made.", toolKeyBindingOptions, originalOptions); debug("e"); @@ -319,6 +339,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { private void setKeyBindingsUpDialog() throws Exception { env.showTool(); + showTableProvider(); setKeyBindingsUpDialog(tool); } @@ -531,13 +552,13 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { // compares the provided options with the mapping of property names to // keystrokes (the map is obtained from the key bindings panel after an // import is done). - private boolean compareOptionsWithKeyStrokeMap(Options options, + private boolean compareOptionsWithKeyStrokeMap(Options oldOptions, Map panelKeyStrokeMap) { - List propertyNames = options.getOptionNames(); + List propertyNames = oldOptions.getOptionNames(); for (String element : propertyNames) { - boolean match = panelKeyStrokeMap.containsKey(element); - KeyStroke optionsKs = options.getKeyStroke(element, null); + boolean match = panelKeyStrokeMap.containsKey(element); + KeyStroke optionsKs = oldOptions.getKeyStroke(element, null); KeyStroke panelKs = panelKeyStrokeMap.get(element); // if the value is null, then it would not have been placed into the options map diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java index 0cdccd0b8e..c9e4e0ab57 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java @@ -109,12 +109,22 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { public void testManagedKeyBindings() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { - if (action.getKeyBindingType().isManaged()) { - assertTrue(actionInTable(action)); + if (!ignoreAction(action)) { + boolean inTable = actionInKeyBindingsTable(action); + assertTrue("Action should be in the key bindingds table: " + action.getFullName(), + inTable); } } } + private boolean ignoreAction(DockingActionIf action) { + if (!action.getKeyBindingType().isManaged()) { + return true; + } + + return action.getFullName().contains("Table Data"); + } + @Test public void testEditKeyBinding() throws Exception { // find action that has a keystroke assigned @@ -316,14 +326,14 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } private boolean supportsKeyBindings(DockingActionIf action) { - return action.getKeyBindingType().isManaged(); + return ignoreAction(action); } private DockingActionIf getKeyBindingPluginAction() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { KeyStroke ks = action.getKeyBinding(); - if (action.getKeyBindingType().isManaged() && ks != null && + if (ignoreAction(action) && ks != null && ks != KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0)) { return action; } @@ -331,7 +341,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { return null; } - private boolean actionInTable(DockingActionIf action) { + private boolean actionInKeyBindingsTable(DockingActionIf action) { String actionName = action.getName(); KeyStroke ks = action.getKeyBinding(); @@ -373,15 +383,14 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { private void setUpDialog() throws Exception { runSwing(() -> { panel = new KeyBindingsPanel(tool, tool.getOptions(DockingToolConstants.KEY_BINDINGS)); + panel.setOptionsPropertyChangeListener(evt -> { + // stub + }); dialog = new JDialog(tool.getToolFrame(), "Test KeyBindings", false); dialog.getContentPane().add(panel); dialog.pack(); dialog.setVisible(true); - // set the dialog so that the panel can enable the apply button - panel.setOptionsPropertyChangeListener(evt -> { - // stub - }); }); table = findComponent(panel, JTable.class); keyField = findComponent(panel, JTextField.class); @@ -390,21 +399,26 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { model = table.getModel(); } + // find 2 actions that do not have key bindings so that we can add and change the values private void grabActionsWithoutKeybinding() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { - if (!action.getKeyBindingType().isManaged()) { + if (ignoreAction(action)) { continue; } if (action.getKeyBinding() != null) { continue; } - // good action if (action1 == null) { action1 = action; } else { + + if (action.getName().equals(action1.getName())) { + continue; // same name, different owners; these are 'shared' actions--ignore + } + action2 = action; return; // grabbed all actions--we are done } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java index 76c2063efb..3b4b216106 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java @@ -67,9 +67,4 @@ public class DummyToolActions implements DockingToolActions { public void removeActions(ComponentProvider provider) { // stub } - - @Override - public boolean containsAction(DockingActionIf action) { - return false; - } } diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/AlignmentPanelBuilder.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/AlignmentPanelBuilder.java index fc08f0c61e..39ee00e108 100644 --- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/AlignmentPanelBuilder.java +++ b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/AlignmentPanelBuilder.java @@ -16,13 +16,15 @@ package ghidra.bitpatterns.gui; import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.util.ArrayList; import java.util.List; import javax.swing.*; +import org.apache.commons.collections4.list.LazyList; + import docking.widgets.label.GLabel; +import docking.widgets.table.AbstractSortedTableModel; import docking.widgets.table.GTable; import docking.widgets.textfield.IntegerTextField; import ghidra.bitpatterns.info.FileBitPatternInfoReader; @@ -34,8 +36,6 @@ import ghidra.util.layout.PairLayout; */ public class AlignmentPanelBuilder extends ContextRegisterFilterablePanelBuilder { private static final int DEFAULT_MODULUS = 16; - private static final String[] alignmentTableColumnNames = - { "Modulus", "Number of Functions", "Percentage" }; private static final String MODULUS_FIELD_TEXT = " Alignment Modulus "; private static final String RECOMPUTE_BUTTON_TEXT = "Compute Alignment Info"; @@ -75,12 +75,7 @@ public class AlignmentPanelBuilder extends ContextRegisterFilterablePanelBuilder modulusPanel.add(modulusField.getComponent()); JButton recomputeButton = new JButton(RECOMPUTE_BUTTON_TEXT); - recomputeButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - updateAlignmentPanel(); - } - }); + recomputeButton.addActionListener(e -> updateAlignmentPanel()); getButtonPanel().add(recomputeButton); @@ -109,11 +104,13 @@ public class AlignmentPanelBuilder extends ContextRegisterFilterablePanelBuilder } private GTable createAlignmentTable(List startingAddresses, int numFuncs) { - String[][] modulusInfo = new String[modulus][3]; + + List data = LazyList.lazyList(new ArrayList<>(), () -> new ModulusInfo()); if (startingAddresses != null) { for (int i = 0; i < modulus; i++) { - modulusInfo[i][0] = Long.toString(i); + data.get(0).modulus = Long.toString(i); } + long[] countsAsLongs = new long[modulus]; for (Long currentAddress : startingAddresses) { countsAsLongs[(int) (Long.remainderUnsigned(currentAddress, modulus))] = @@ -121,11 +118,13 @@ public class AlignmentPanelBuilder extends ContextRegisterFilterablePanelBuilder } for (int i = 0; i < modulus; i++) { double percent = (100.0 * countsAsLongs[i]) / numFuncs; - modulusInfo[i][2] = Double.toString(Math.round(percent)); - modulusInfo[i][1] = Long.toString(countsAsLongs[i]); + data.get(i).counts = Long.toString(countsAsLongs[i]); + data.get(i).percent = Double.toString(Math.round(percent)); } } - GTable table = new GTable(modulusInfo, alignmentTableColumnNames); + + AlignmentTableModel model = new AlignmentTableModel(data); + GTable table = new GTable(model); return table; } @@ -161,4 +160,69 @@ public class AlignmentPanelBuilder extends ContextRegisterFilterablePanelBuilder updateAlignmentPanel(); } + private class ModulusInfo { + + private String modulus; + private String percent; + private String counts; + + ModulusInfo() { + this.modulus = modulus; + this.percent = percent; + this.counts = counts; + } + } + + private class AlignmentTableModel extends AbstractSortedTableModel { + + private final String[] columnNames = { "Modulus", "Number of Functions", "Percentage" }; + private List data; + + AlignmentTableModel(List data) { + this.data = data; + } + + @Override + public String getColumnName(int column) { + return columnNames[column]; + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public boolean isSortable(int columnIndex) { + return true; + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public String getName() { + return "Function Start Alignment"; + } + + @Override + public List getModelData() { + return data; + } + + @Override + public Object getColumnValueForRow(ModulusInfo t, int columnIndex) { + switch (columnIndex) { + case 0: + return t.modulus; + case 1: + return t.counts; + case 2: + return t.percent; + } + return null; + } + } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java index 337ec743dc..7b8c49459e 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java @@ -529,11 +529,7 @@ public abstract class DecompilerCodeComparisonPanel 0) { collectTokens(tokenList, node, addressSet); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java index 897e0146b3..c561d1f6de 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionContext.java @@ -25,7 +25,11 @@ import docking.action.DockingActionIf; * {@link DockingActionIf}s for them to decide if they are enabled for a given user action. User * actions are toolbar button presses, menu bar item presses and popup menu item presses. As * the user changes focus in the system, all actions are queried with the current context. Thus, - * toolbar buttons and menu items will enable and disable as the user interacts with the system. + * toolbar buttons and menu items will enable and disable as the user interacts with the system. + * Further, popup menu items will not be added to popup menus when they report false for + * {@link DockingActionIf#isAddToPopup(ActionContext)}; they will appear in the popup, but be + * disabled if they report true for the above call, but false for + * {@link DockingActionIf#isEnabledForContext(ActionContext)}. * When the user executes an action, the current context will be passed to the backing * {@link DockingActionIf}. Ultimately, context serves to control action enablement and to * allow plugins to share state with actions without having to store that state information diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java index c707095f3e..b048f0cf42 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java @@ -126,6 +126,10 @@ public class ActionToGuiMapper { menuAndToolBarManager.contextChanged(placeHolder); } + PopupActionManager getPopupActionManager() { + return popupActionManager; + } + public MenuGroupMap getMenuGroupMap() { return menuGroupMap; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java index 75e79fce6d..415b74836b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -396,19 +396,35 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext return createContext(c, null); } - // TODO + /** + * A default method for creating an action context for this provider + * @return the new context + */ protected ActionContext createContext() { return new ActionContext(this); } - // TODO - protected ActionContext createContext(Object payload) { - return new ActionContext(this).setContextObject(payload); + /** + * A default method for creating an action context for this provider, using the given + * {@link ActionContext#getContextObject() context object} + * + * @param contextObject the provider-specific context object + * @return the new context + */ + protected ActionContext createContext(Object contextObject) { + return new ActionContext(this).setContextObject(contextObject); } - // TODO - protected ActionContext createContext(Component source, Object payload) { - return new ActionContext(this, source).setContextObject(payload); + /** + * A default method for creating an action context for this provider, using the given + * {@link ActionContext#getContextObject() context object} and component + * + * @param sourceComponent the component that is the target of the context being created + * @param contextObject the provider-specific context object + * @return the new context + */ + protected ActionContext createContext(Component sourceComponent, Object contextObject) { + return new ActionContext(this, sourceComponent).setContextObject(contextObject); } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java index c18520ec9e..642c4cb8ac 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java @@ -31,6 +31,7 @@ import docking.action.DockingActionIf; import docking.actions.ActionAdapter; import docking.actions.KeyBindingUtils; import docking.event.mouse.GMouseListenerAdapter; +import docking.help.HelpService; import docking.menu.DockingToolbarButton; import docking.util.AnimationUtils; import docking.widgets.label.GDHtmlLabel; @@ -990,6 +991,15 @@ public class DialogComponentProvider DockingWindowManager.setHelpLocation(rootPanel, helpLocation); } + /** + * Returns the help location for this dialog + * @return the help location + */ + public HelpLocation getHelpLocatdion() { + HelpService helpService = DockingWindowManager.getHelpService(); + return helpService.getHelpLocation(rootPanel); + } + /** * Sets the button to make "Default" when the dialog is shown. If no default button is * desired, then pass null as the button value. @@ -1139,6 +1149,10 @@ public class DialogComponentProvider return new ActionContext(null, c); } + Component sourceComponent = event.getComponent(); + if (sourceComponent != null) { + c = sourceComponent; + } return new ActionContext(null, c).setSourceObject(event.getSource()); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java index cb789f17d0..4c6c20e186 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java @@ -88,43 +88,14 @@ public class DialogComponentProviderPopupActionManager { private void populatePopupMenuActions(DockingWindowManager dwm, MenuManager menuMgr, ActionContext actionContext) { - Iterator iter = popupActions.iterator(); - while (iter.hasNext()) { - DockingActionIf action = iter.next(); - MenuData popupMenuData = action.getPopupMenuData(); - if (popupMenuData != null && action.isValidContext(actionContext) && - action.isAddToPopup(actionContext)) { - - action.setEnabled(action.isEnabledForContext(actionContext)); - menuMgr.addAction(action); - } - } - - Object source = actionContext.getSourceObject(); - if (source instanceof DockingActionProviderIf) { - DockingActionProviderIf actionProvider = (DockingActionProviderIf) source; - List dockingActions = actionProvider.getDockingActions(); - for (DockingActionIf action : dockingActions) { - MenuData popupMenuData = action.getPopupMenuData(); - if (popupMenuData != null && action.isValidContext(actionContext) && - action.isAddToPopup(actionContext)) { - action.setEnabled(action.isEnabledForContext(actionContext)); - menuMgr.addAction(action); - } - } - } - - List tempActions = dwm.getTemporaryPopupActions(actionContext); - if (tempActions != null) { - for (DockingActionIf action : tempActions) { - MenuData popupMenuData = action.getPopupMenuData(); - if (popupMenuData != null && action.isValidContext(actionContext) && - action.isAddToPopup(actionContext)) { - action.setEnabled(action.isEnabledForContext(actionContext)); - menuMgr.addAction(action); - } - } - } + // This is a bit of a kludge, but allows us to get generic actions, like 'copy' for + // tables. This can go away if we ever convert DialogComponentProviders to use the + // primary action system (this was something we were going to do once). If that happens, + // then this entire class goes away. + ActionToGuiMapper actionManager = dwm.getActionToGuiMapper(); + PopupActionManager toolPopupManager = actionManager.getPopupActionManager(); + Iterator localActions = popupActions.iterator(); + toolPopupManager.populatePopupMenuActions(localActions, actionContext, menuMgr); } //================================================================================================== diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java index 2d35c32ea5..4a2bb136b9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java @@ -23,6 +23,8 @@ import java.util.*; import javax.swing.JPopupMenu; +import org.apache.commons.collections4.IteratorUtils; + import docking.action.*; import docking.menu.*; @@ -66,6 +68,7 @@ public class PopupActionManager implements PropertyChangeListener { } void popupMenu(ComponentPlaceholder info, MouseEvent e) { + if (e.isConsumed()) { return; } @@ -78,24 +81,38 @@ public class PopupActionManager implements PropertyChangeListener { actionContext.setSourceObject(e.getSource()); actionContext.setMouseEvent(e); - MenuHandler popupMenuHandler = new PopupMenuHandler(windowManager, actionContext); + Iterator localActions = info.getActions(); + JPopupMenu popupMenu = createPopupMenu(localActions, actionContext); + if (popupMenu == null) { + return; // no matching actions + } + Component c = (Component) e.getSource(); + popupMenu.show(c, e.getX(), e.getY()); + } + + JPopupMenu createPopupMenu(Iterator localActions, ActionContext context) { + + if (localActions == null) { + localActions = IteratorUtils.emptyIterator(); + } + + MenuHandler popupMenuHandler = new PopupMenuHandler(windowManager, context); MenuManager menuMgr = new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap); - populatePopupMenuActions(info, actionContext, menuMgr); + populatePopupMenuActions(localActions, context, menuMgr); if (menuMgr.isEmpty()) { - return; + return null; } // Popup menu if items are available JPopupMenu popupMenu = menuMgr.getPopupMenu(); - Component c = (Component) e.getSource(); popupMenu.addPopupMenuListener(popupMenuHandler); - popupMenu.show(c, e.getX(), e.getY()); + return popupMenu; } - private void populatePopupMenuActions(ComponentPlaceholder info, ActionContext actionContext, - MenuManager menuMgr) { + void populatePopupMenuActions(Iterator localActions, + ActionContext actionContext, MenuManager menuMgr) { // Unregistered actions are those used by special-needs components, on-the-fly addUnregisteredActions(actionContext, menuMgr); @@ -129,9 +146,8 @@ public class PopupActionManager implements PropertyChangeListener { } // Include local actions for focused component - iter = info.getActions(); - while (iter.hasNext()) { - DockingActionIf action = iter.next(); + while (localActions.hasNext()) { + DockingActionIf action = localActions.next(); if (action.getPopupMenuData() != null && action.isValidContext(actionContext) && action.isAddToPopup(actionContext)) { action.setEnabled(action.isEnabledForContext(actionContext)); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java index 78b8a9f9d8..6c56552fe3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java @@ -91,7 +91,4 @@ public interface DockingToolActions { * @return the actions */ public Set getAllActions(); - - // TODO - public boolean containsAction(DockingActionIf action); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedActionRegistry.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedActionRegistry.java new file mode 100644 index 0000000000..0e6920a6f0 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedActionRegistry.java @@ -0,0 +1,39 @@ +/* ### + * 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.actions; + +import docking.DockingTool; +import docking.action.DockingActionIf; +import docking.tool.ToolConstants; +import docking.widgets.table.GTable; + +/** + * A place used to hold {@link DockingActionIf}s that are meant to be used by components. Some + * components do not have access to the tool that is required to register their actions. This + * class helps those components by enabling the installation of shared actions for those + * components. + */ +public class SharedActionRegistry { + + /** + * Install all known shared actions into the given tool + * @param tool the tool + * @param toolActions the tool action manager + */ + public static void installSharedActions(DockingTool tool, ToolActions toolActions) { + GTable.createSharedActions(tool, toolActions, ToolConstants.TOOL_OWNER); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java index de88933718..7fa201c994 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java @@ -89,10 +89,16 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options @Override public String getOwnerDescription() { List owners = getDistinctOwners(); - Collections.sort(owners); if (owners.size() == 1) { return owners.get(0); } + + boolean hasTool = owners.remove(ToolConstants.TOOL_OWNER); + Collections.sort(owners); + if (hasTool) { + owners.add(0, ToolConstants.TOOL_OWNER); + } + return StringUtils.join(owners, ", "); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index 8f16a6a0e0..704209c564 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -71,6 +71,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS); createReservedKeyBindings(); + SharedActionRegistry.installSharedActions(tool, this); } private void createReservedKeyBindings() { @@ -160,12 +161,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { SharedStubKeyBindingAction newStub = new SharedStubKeyBindingAction(name, keyBindingOptions); - newStub.addPropertyChangeListener(this); - keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE, - defaultKeyStroke, null, null); - - keyBindingsManager.addAction(provider, newStub); - + registerStub(newStub, defaultKeyStroke); return newStub; }); @@ -177,6 +173,13 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { } } + private void registerStub(SharedStubKeyBindingAction stub, KeyStroke defaultKeyStroke) { + stub.addPropertyChangeListener(this); + keyBindingOptions.registerOption(stub.getFullName(), OptionType.KEYSTROKE_TYPE, + defaultKeyStroke, null, null); + keyBindingsManager.addAction(null, stub); + } + /** * Removes the given action from the tool * @param action the action to be removed. @@ -405,11 +408,6 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { return null; } - @Override - public boolean containsAction(DockingActionIf action) { - return getActionStorage(action).contains(action); - } - public Action getAction(KeyStroke ks) { return keyBindingsManager.getDockingKeyAction(ks); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java index a8d4af34a6..fa5de615a1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java @@ -32,6 +32,7 @@ import javax.swing.table.*; import docking.*; import docking.action.*; import docking.actions.KeyBindingUtils; +import docking.actions.ToolActions; import docking.widgets.OptionDialog; import docking.widgets.dialogs.SettingsDialog; import docking.widgets.filechooser.GhidraFileChooser; @@ -71,6 +72,13 @@ import resources.ResourceManager; */ public class GTable extends JTable implements KeyStrokeConsumer { + private static final KeyStroke COPY_KEY_STROKE = + KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_KEY_MODIFIER_MASK); + private static final KeyStroke COPY_COLUMN_KEY_STROKE = + KeyStroke.getKeyStroke(KeyEvent.VK_C, CONTROL_KEY_MODIFIER_MASK | SHIFT_DOWN_MASK); + private static final KeyStroke SELECT_ALL_KEY_STROKE = + KeyStroke.getKeyStroke(KeyEvent.VK_A, CONTROL_KEY_MODIFIER_MASK); + private static final String LAST_EXPORT_FILE = "LAST_EXPORT_DIR"; private int userDefinedRowHeight; @@ -97,14 +105,6 @@ public class GTable extends JTable implements KeyStrokeConsumer { /** A flag to signal that a copy operation is being performed. */ private boolean copying; - private static final String actionMenuGroup = "zzzTableGroup"; - private DockingAction copyAction; - private DockingAction copyColumnsAction; - private DockingAction copyCurrentColumnAction; - private DockingAction selectAllAction; - private DockingAction exportAction; - private DockingAction exportColumnsAction; - private SelectionManager selectionManager; private Integer visibleRowCount; @@ -116,11 +116,11 @@ public class GTable extends JTable implements KeyStrokeConsumer { private final Map columnRenderingDataMap = new HashMap<>(); /** - * Constructs a new GTable. + * Constructs a new GTable */ public GTable() { super(); - init(false); + init(); } /** @@ -128,44 +128,8 @@ public class GTable extends JTable implements KeyStrokeConsumer { * @param dm the table model */ public GTable(TableModel dm) { - this(dm, false); - } - - /** - * Constructs a new GTable using the specified table model. - * If allowAutoEdit is true, then automatic editing is enabled. - * Auto-editing implies that typing in an editable cell will automatically - * force the cell into edit mode. - * If allowAutoEdit is false, then F2 must be hit before editing may commence. - * @param dm the table model - * @param allowAutoEdit true if auto-editing is allowed - * - */ - public GTable(TableModel dm, boolean allowAutoEdit) { super(dm); - init(allowAutoEdit); - } - - /** - * Constructs a GTable to display the values of the given 2d array of data. - *

- * @param rowData the array of data to display in the table. - * @param columnNames an array of names to use for the column names. - */ - public GTable(Object[][] rowData, Object[] columnNames) { - this(rowData, columnNames, false); - } - - /** - * Constructs a GTable to display the values of the given 2d array of data. - *

- * @param rowData the array of data to display in the table. - * @param columnNames an array of names to use for the column names. - * @param allowAutoEdit true if auto-editing is allowed - */ - public GTable(Object[][] rowData, Object[] columnNames, boolean allowAutoEdit) { - super(rowData, columnNames); - init(allowAutoEdit); + init(); } public void setVisibleRowCount(int visibleRowCount) { @@ -510,30 +474,42 @@ public class GTable extends JTable implements KeyStrokeConsumer { return autoLookupKeyStrokeConsumer.isKeyConsumed(keyStroke); } - private void init(boolean allowAutoEdit) { + /** + * Enables or disables auto-edit. When enabled, the user can start typing to trigger an + * edit of an editable table cell. + * + * @param allowAutoEdit true for auto-editing + */ + public void setAutoEditEnabled(boolean allowAutoEdit) { + putClientProperty("JTable.autoStartsEdit", allowAutoEdit); + } + + private void installEditKeyBinding() { + AbstractAction action = new AbstractAction("StartEdit") { + @Override + public void actionPerformed(ActionEvent ev) { + int row = getSelectedRow(); + int col = getSelectedColumn(); + if (col == -1) { + Toolkit.getDefaultToolkit().beep(); + } + KeyEvent evt = new KeyEvent(GTable.this, 0, 0, 0, KeyEvent.VK_UNDEFINED, + KeyEvent.CHAR_UNDEFINED); + editCellAt(row, col, evt); + } + }; + + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0); + KeyBindingUtils.registerAction(this, ks, action, JComponent.WHEN_FOCUSED); + } + + private void init() { ToolTipManager.sharedInstance().unregisterComponent(this); ToolTipManager.sharedInstance().registerComponent(this); setTableHeader(new GTableHeader(this)); - if (!allowAutoEdit) { - putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); - AbstractAction action = new AbstractAction("StartEdit") { - @Override - public void actionPerformed(ActionEvent ev) { - int row = getSelectedRow(); - int col = getSelectedColumn(); - if (col == -1) { - Toolkit.getDefaultToolkit().beep(); - } - KeyEvent evt = new KeyEvent(GTable.this, 0, 0, 0, KeyEvent.VK_UNDEFINED, - KeyEvent.CHAR_UNDEFINED); - editCellAt(row, col, evt); - } - }; - - KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0); - KeyBindingUtils.registerAction(this, ks, action, JComponent.WHEN_FOCUSED); - } + setAutoEditEnabled(false); // clients can turn this on as needed + installEditKeyBinding(); initDefaultRenderers(); @@ -558,27 +534,26 @@ public class GTable extends JTable implements KeyStrokeConsumer { } }); + removeActionKeyStrokes(); + // updating the row height requires the 'isInitialized' to be set, so do it first isInitialized = true; initializeRowHeight(); - - DockingWindowManager.registerComponentLoadedListener(this, (dwm, provider) -> { - DockingTool tool = dwm.getTool(); - regiserActions(tool, provider); - }); } - private void regiserActions(DockingTool tool, ComponentProvider provider) { - - tool.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1"); - tool.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2"); - tool.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3"); - - String owner = getClass().getSimpleName(); - if (provider != null) { - owner = provider.getOwner(); - } - installTableActions(tool, owner); + private void removeActionKeyStrokes() { + // + // We remove these keybindings as we have replaced Java's version with our own. To be + // thorough, we should really clear all table keybindings, which would ensure that any + // user-provided key stroke would not get blocked by the table. At the time of writing, + // there are alternate key bindings for copy that do not use this table's copy action. + // Also, there are many other built-in keybindings for table navigation, which we do not + // wish to override. For now, just clear these. We can clear others if they become + // a problem. + // + KeyBindingUtils.clearKeyBinding(this, COPY_KEY_STROKE); + KeyBindingUtils.clearKeyBinding(this, COPY_COLUMN_KEY_STROKE); + KeyBindingUtils.clearKeyBinding(this, SELECT_ALL_KEY_STROKE); } private void initializeHeader(JTableHeader header) { @@ -1155,6 +1130,47 @@ public class GTable extends JTable implements KeyStrokeConsumer { return converted; } + /** + * Maintain a {@link docking.widgets.table.GTableCellRenderingData} object + * associated with each column that maintains some state and references to + * useful data. These objects are created as needed, stored by the table for + * convenient re-use and to prevent per-cell creation, and cleared when columns + * are removed from the table. + *

+ * Row and cell state is cleared before returning to the caller to ensure + * consistent state; when the client is done rendering a cell, row and cell + * state should also be cleared to minimize references. + * + * @param viewColumn + * The columns' view index + * @return Data specific to the column. Row state is cleared before returning. + */ + GTableCellRenderingData getRenderingData(int viewColumn) { + + int modelColumn = convertColumnIndexToModel(viewColumn); + + GTableCellRenderingData renderData = columnRenderingDataMap.get(modelColumn); + + if (renderData == null) { + Settings settings = SettingsImpl.NO_SETTINGS; + ConfigurableColumnTableModel configurableModel = getConfigurableColumnTableModel(); + if (configurableModel != null) { + settings = configurableModel.getColumnSettings(modelColumn); + } + + renderData = new GTableCellRenderingData(this, viewColumn, settings); + columnRenderingDataMap.put(modelColumn, renderData); + } + + renderData.resetRowData(); + return renderData; + + } + +//================================================================================================== +// Actions +//================================================================================================== + /** * A method that subclasses can override to signal that they wish not to have this table's * built-in popup actions. Subclasses will almost never need to override this method. @@ -1165,198 +1181,6 @@ public class GTable extends JTable implements KeyStrokeConsumer { return true; } - private void installTableActions(DockingTool tool, String owner) { - - int subGroupIndex = 1; // order by insertion - copyAction = new GTableAction("Table Data Copy", owner) { - @Override - public void actionPerformed(ActionContext context) { - copying = true; - Action builtinCopyAction = TransferHandler.getCopyAction(); - - try { - builtinCopyAction.actionPerformed(new ActionEvent(GTable.this, 0, "copy")); - } - finally { - copying = false; - } - } - }; - //@formatter:off - copyAction.setPopupMenuData(new MenuData( - new String[] { "Copy", "Copy" }, - ResourceManager.loadImage("images/page_white_copy.png"), - actionMenuGroup, NO_MNEMONIC, - Integer.toString(subGroupIndex++) - ) - ); - copyAction.setKeyBindingData(new KeyBindingData( - KeyStroke.getKeyStroke(KeyEvent.VK_C, - CONTROL_KEY_MODIFIER_MASK) - ) - ); - copyAction.setHelpLocation(new HelpLocation("Tables", "Copy")); - //@formatter:on - - copyCurrentColumnAction = new GTableAction("Table Data Copy Current Column", owner) { - @Override - public void actionPerformed(ActionContext context) { - - int column = getSelectedColumn(); - MouseEvent event = context.getMouseEvent(); - if (event != null) { - column = columnAtPoint(event.getPoint()); - } - - if (column < 0) { - Msg.debug(this, "Copy failed--no column selected"); - return; - } - - copyColumns(column); - } - }; - //@formatter:off - copyCurrentColumnAction.setPopupMenuData(new MenuData( - new String[] { "Copy", - "Copy Current Column" }, - ResourceManager.loadImage("images/page_white_copy.png"), - actionMenuGroup, - NO_MNEMONIC, - Integer.toString(subGroupIndex++) - ) - ); - copyCurrentColumnAction.setKeyBindingData(new KeyBindingData( - KeyStroke.getKeyStroke( - KeyEvent.VK_C, CONTROL_KEY_MODIFIER_MASK | SHIFT_DOWN_MASK) - ) - ); - copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column")); - //@formatter:on - - copyColumnsAction = new GTableAction("Table Data Copy by Columns", owner) { - @Override - public void actionPerformed(ActionContext context) { - int[] userColumns = promptUserForColumns(); - if (userColumns == null) { - return; // cancelled - } - - copyColumns(userColumns); - } - }; - //@formatter:off - copyColumnsAction.setPopupMenuData(new MenuData( - new String[] { "Copy", "Copy Columns..." }, - ResourceManager.loadImage("images/page_white_copy.png"), - actionMenuGroup, - NO_MNEMONIC, - Integer.toString(subGroupIndex++) - ) - ); - copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns")); - //@formatter:on - - exportAction = new GTableAction("Table Data CSV Export", owner) { - @Override - public void actionPerformed(ActionContext context) { - File file = chooseExportFile(); - if (file != null) { - GTableToCSV.writeCSV(file, GTable.this); - } - } - }; - //@formatter:off - exportAction.setPopupMenuData(new MenuData( - new String[] { "Export", GTableToCSV.TITLE + "..." }, - ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"), - actionMenuGroup, - NO_MNEMONIC, - Integer.toString(subGroupIndex++) - ) - ); - exportAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV")); - //@formatter:on - - exportColumnsAction = new GTableAction("Table Data CSV Export (by Columns)", owner) { - @Override - public void actionPerformed(ActionContext context) { - int[] userColumns = promptUserForColumns(); - if (userColumns == null) { - return; // cancelled - } - - File file = chooseExportFile(); - if (file == null) { - return; - } - - List columnList = new ArrayList<>(); - for (int userColumn : userColumns) { - columnList.add(userColumn); - } - GTableToCSV.writeCSVUsingColunns(file, GTable.this, columnList); - } - }; - //@formatter:off - exportColumnsAction.setPopupMenuData(new MenuData( - new String[] { "Export", "Export Columns to CSV..." }, - ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"), - actionMenuGroup, - NO_MNEMONIC, - Integer.toString(subGroupIndex++) - ) - ); - exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns")); - //@formatter:on - - selectAllAction = new GTableAction("Table Select All", owner) { - @Override - public void actionPerformed(ActionContext context) { - selectAll(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - if (!super.isEnabledForContext(context)) { - return false; - } - return getSelectionModel().getSelectionMode() != ListSelectionModel.SINGLE_SELECTION; - } - }; - //@formatter:off - selectAllAction.setPopupMenuData(new MenuData( - new String[] { "Select All" }, - null /*icon*/, - actionMenuGroup, - NO_MNEMONIC, - Integer.toString(subGroupIndex++) - ) - ); - selectAllAction.setKeyBindingData(new KeyBindingData( - KeyStroke.getKeyStroke(KeyEvent.VK_A, - CONTROL_KEY_MODIFIER_MASK) - ) - ); - selectAllAction.setHelpLocation(new HelpLocation("Tables", "SelectAll")); - //@formatter:on - - // remove any conflicting key bindings that Java has installed on this component - KeyBindingUtils.clearKeyBinding(this, copyAction); - KeyBindingUtils.clearKeyBinding(this, copyCurrentColumnAction); - KeyBindingUtils.clearKeyBinding(this, copyColumnsAction); - KeyBindingUtils.clearKeyBinding(this, selectAllAction); - KeyBindingUtils.clearKeyBinding(this, exportAction); - KeyBindingUtils.clearKeyBinding(this, exportColumnsAction); - - tool.addAction(copyAction); - tool.addAction(copyCurrentColumnAction); - tool.addAction(copyColumnsAction); - tool.addAction(selectAllAction); - tool.addAction(exportAction); - tool.addAction(exportColumnsAction); - } - private void copyColumns(int... copyColumns) { int[] originalColumns = new int[0]; @@ -1433,43 +1257,216 @@ public class GTable extends JTable implements KeyStrokeConsumer { Preferences.store(); } - /** - * Maintain a {@link docking.widgets.table.GTableCellRenderingData} object - * associated with each column that maintains some state and references to - * useful data. These objects are created as needed, stored by the table for - * convenient re-use and to prevent per-cell creation, and cleared when columns - * are removed from the table. - *

- * Row and cell state is cleared before returning to the caller to ensure - * consistent state; when the client is done rendering a cell, row and cell - * state should also be cleared to minimize references. - * - * @param viewColumn - * The columns' view index - * @return Data specific to the column. Row state is cleared before returning. - */ - GTableCellRenderingData getRenderingData(int viewColumn) { + private void doCopy() { + copying = true; + Action builtinCopyAction = TransferHandler.getCopyAction(); - int modelColumn = convertColumnIndexToModel(viewColumn); + try { + builtinCopyAction.actionPerformed(new ActionEvent(GTable.this, 0, "copy")); + } + finally { + copying = false; + } + } - GTableCellRenderingData renderData = columnRenderingDataMap.get(modelColumn); - - if (renderData == null) { - Settings settings = SettingsImpl.NO_SETTINGS; - ConfigurableColumnTableModel configurableModel = getConfigurableColumnTableModel(); - if (configurableModel != null) { - settings = configurableModel.getColumnSettings(modelColumn); - } - - renderData = new GTableCellRenderingData(this, viewColumn, settings); - columnRenderingDataMap.put(modelColumn, renderData); + private void doCopyCurrentColumn(MouseEvent event) { + int column = getSelectedColumn(); + if (event != null) { + column = columnAtPoint(event.getPoint()); } - renderData.resetRowData(); - return renderData; + if (column < 0) { + Msg.debug(this, "Copy failed--no column selected"); + return; + } + copyColumns(column); } + private void doCopyColumns() { + int[] userColumns = promptUserForColumns(); + if (userColumns == null) { + return; // cancelled + } + + copyColumns(userColumns); + } + + private void doExport() { + File file = chooseExportFile(); + if (file != null) { + GTableToCSV.writeCSV(file, GTable.this); + } + } + + private void doExportColumns() { + int[] userColumns = promptUserForColumns(); + if (userColumns == null) { + return; // cancelled + } + + File file = chooseExportFile(); + if (file == null) { + return; + } + + List columnList = new ArrayList<>(); + for (int userColumn : userColumns) { + columnList.add(userColumn); + } + GTableToCSV.writeCSVUsingColunns(file, GTable.this, columnList); + } + + public static void createSharedActions(DockingTool tool, ToolActions toolActions, + String owner) { + + String actionMenuGroup = "zzzTableGroup"; + tool.setMenuGroup(new String[] { "Copy" }, actionMenuGroup, "1"); + tool.setMenuGroup(new String[] { "Export" }, actionMenuGroup, "2"); + tool.setMenuGroup(new String[] { "Select All" }, actionMenuGroup, "3"); + + int subGroupIndex = 1; // order by insertion + GTableAction copyAction = new GTableAction("Table Data Copy", owner) { + @Override + public void actionPerformed(ActionContext context) { + GTable gTable = (GTable) context.getSourceComponent(); + gTable.doCopy(); + } + }; + //@formatter:off + copyAction.setPopupMenuData(new MenuData( + new String[] { "Copy", "Copy" }, + ResourceManager.loadImage("images/page_white_copy.png"), + actionMenuGroup, NO_MNEMONIC, + Integer.toString(subGroupIndex++) + ) + ); + copyAction.setKeyBindingData(new KeyBindingData(COPY_KEY_STROKE)); + copyAction.setHelpLocation(new HelpLocation("Tables", "Copy")); + //@formatter:on + + GTableAction copyCurrentColumnAction = + new GTableAction("Table Data Copy Current Column", owner) { + @Override + public void actionPerformed(ActionContext context) { + GTable gTable = (GTable) context.getSourceComponent(); + gTable.doCopyCurrentColumn(context.getMouseEvent()); + } + }; + //@formatter:off + copyCurrentColumnAction.setPopupMenuData(new MenuData( + new String[] { "Copy", + "Copy Current Column" }, + ResourceManager.loadImage("images/page_white_copy.png"), + actionMenuGroup, + NO_MNEMONIC, + Integer.toString(subGroupIndex++) + ) + ); + copyCurrentColumnAction.setKeyBindingData(new KeyBindingData(COPY_COLUMN_KEY_STROKE)); + copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column")); + //@formatter:on + + GTableAction copyColumnsAction = new GTableAction("Table Data Copy by Columns", owner) { + @Override + public void actionPerformed(ActionContext context) { + GTable gTable = (GTable) context.getSourceComponent(); + gTable.doCopyColumns(); + } + }; + //@formatter:off + copyColumnsAction.setPopupMenuData(new MenuData( + new String[] { "Copy", "Copy Columns..." }, + ResourceManager.loadImage("images/page_white_copy.png"), + actionMenuGroup, + NO_MNEMONIC, + Integer.toString(subGroupIndex++) + ) + ); + copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns")); + //@formatter:on + + GTableAction exportAction = new GTableAction("Table Data CSV Export", owner) { + @Override + public void actionPerformed(ActionContext context) { + GTable gTable = (GTable) context.getSourceComponent(); + gTable.doExport(); + } + }; + //@formatter:off + exportAction.setPopupMenuData(new MenuData( + new String[] { "Export", GTableToCSV.TITLE + "..." }, + ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"), + actionMenuGroup, + NO_MNEMONIC, + Integer.toString(subGroupIndex++) + ) + ); + exportAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV")); + //@formatter:on + + GTableAction exportColumnsAction = + new GTableAction("Table Data CSV Export (by Columns)", owner) { + @Override + public void actionPerformed(ActionContext context) { + GTable gTable = (GTable) context.getSourceComponent(); + gTable.doExportColumns(); + } + }; + //@formatter:off + exportColumnsAction.setPopupMenuData(new MenuData( + new String[] { "Export", "Export Columns to CSV..." }, + ResourceManager.loadImage("images/application-vnd.oasis.opendocument.spreadsheet-template.png"), + actionMenuGroup, + NO_MNEMONIC, + Integer.toString(subGroupIndex++) + ) + ); + exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns")); + //@formatter:on + + GTableAction selectAllAction = new GTableAction("Table Select All", owner) { + @Override + public void actionPerformed(ActionContext context) { + GTable gTable = (GTable) context.getSourceComponent(); + gTable.selectAll(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!super.isEnabledForContext(context)) { + return false; + } + GTable gTable = (GTable) context.getSourceComponent(); + int mode = gTable.getSelectionModel().getSelectionMode(); + return mode != ListSelectionModel.SINGLE_SELECTION; + } + }; + //@formatter:off + selectAllAction.setPopupMenuData(new MenuData( + new String[] { "Select All" }, + null /*icon*/, + actionMenuGroup, + NO_MNEMONIC, + Integer.toString(subGroupIndex++) + ) + ); + selectAllAction.setKeyBindingData(new KeyBindingData(SELECT_ALL_KEY_STROKE)); + selectAllAction.setHelpLocation(new HelpLocation("Tables", "SelectAll")); + //@formatter:on + + toolActions.addGlobalAction(copyAction); + toolActions.addGlobalAction(copyColumnsAction); + toolActions.addGlobalAction(copyCurrentColumnAction); + toolActions.addGlobalAction(exportAction); + toolActions.addGlobalAction(exportColumnsAction); + toolActions.addGlobalAction(selectAllAction); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + private class MyTableColumnModelListener implements TableColumnModelListener { @Override public void columnSelectionChanged(ListSelectionEvent e) { @@ -1499,17 +1496,25 @@ public class GTable extends JTable implements KeyStrokeConsumer { } } - private abstract class GTableAction extends DockingAction { + private abstract static class GTableAction extends DockingAction { GTableAction(String name, String owner) { - super(name, owner, KeyBindingType.SHARED); + super(name, owner); + } + + @Override + public boolean isAddToPopup(ActionContext context) { + if (!isEnabledForContext(context)) { + return false; + } + GTable gTable = (GTable) context.getSourceComponent(); + return gTable.supportsPopupActions(); } @Override public boolean isEnabledForContext(ActionContext context) { Component sourceComponent = context.getSourceComponent(); - return sourceComponent == GTable.this; + return sourceComponent instanceof GTable; } } - } diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filechooser/GhidraFileChooserTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filechooser/GhidraFileChooserTest.java index 879027b535..1d8738dd63 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filechooser/GhidraFileChooserTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filechooser/GhidraFileChooserTest.java @@ -1571,9 +1571,7 @@ public class GhidraFileChooserTest extends AbstractDockingTest { private ActionContext createDirListContext() { DirectoryList dirlist = getDirectoryListViewOfFileChooser(); - MouseEvent e = new MouseEvent(dirlist, 0, 0, 0, 0, 0, 1, false); - ActionContext context = chooser.getActionContext(e); - return context; + return new ActionContext(null, dirlist); } private boolean isEnabled(DockingAction action, ActionContext context) { diff --git a/Ghidra/Framework/Docking/src/test/java/docking/DockingWindowManagerTestHelper.java b/Ghidra/Framework/Docking/src/test/java/docking/DockingWindowManagerTestHelper.java new file mode 100644 index 0000000000..759c49b8b7 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test/java/docking/DockingWindowManagerTestHelper.java @@ -0,0 +1,38 @@ +/* ### + * 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; + +import javax.swing.JPopupMenu; + +/** + * A class to help during testing to get objects otherwise restricted by package + */ +public class DockingWindowManagerTestHelper { + + /** + * Gets the popup menu for the given context + * @param dwm the window manager + * @param context the action context + * @return the popup menu; null if there are no valid actions for the given context + */ + public static JPopupMenu getPopupMenu(DockingWindowManager dwm, ActionContext context) { + + ActionToGuiMapper mapper = dwm.getActionToGuiMapper(); + PopupActionManager popupManager = mapper.getPopupActionManager(); + JPopupMenu popup = popupManager.createPopupMenu(null, context); + return popup; + } +} 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 bda0f79e2d..88843e5632 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 @@ -288,7 +288,7 @@ public class ProjectDataTablePanel extends JPanel { list.add(info.getDomainFile()); } return new ProjectDataActionContext(provider, projectData, - model.getRowObject(selectedRows[0]), null, list, table, true); + model.getRowObject(selectedRows[0]), null, list, gTable, true); } private void checkOpen(MouseEvent e) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java index 91328086e4..a36a39fa3d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java @@ -426,7 +426,7 @@ public class VersionHistoryPanel extends JPanel implements Draggable { return false; } - if (context.getContextObject() != table) { + if (context.getSourceComponent() != table) { return false; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java index dde093a54b..92b4ebb86d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java @@ -67,10 +67,10 @@ public class KeyBindingsPanel extends JPanel { private Options options; private Map> actionsByFullName; - private Map> actionNamesByKeyStroke; - private Map keyStrokesByFullName; - private Map originalValues; // to know what has been changed - private List tableActions; + private Map> actionNamesByKeyStroke = new HashMap<>(); + private Map keyStrokesByFullName = new HashMap<>(); + private Map originalValues = new HashMap<>(); // to know what has been changed + private List tableActions = new ArrayList<>(); private KeyEntryTextField ksField; private boolean unappliedChanges; @@ -83,8 +83,8 @@ public class KeyBindingsPanel extends JPanel { public KeyBindingsPanel(PluginTool tool, Options options) { this.tool = tool; this.options = options; - tableActions = new ArrayList<>(); - create(); + + createPanelComponents(); createActionMap(); addListeners(); } @@ -97,11 +97,9 @@ public class KeyBindingsPanel extends JPanel { tableFilterPanel.dispose(); tableModel.dispose(); actionTable.dispose(); + propertyChangeListener = null; } - /** - * Apply the changes to the actions. - */ public void apply() { Iterator iter = keyStrokesByFullName.keySet().iterator(); while (iter.hasNext()) { @@ -160,9 +158,7 @@ public class KeyBindingsPanel extends JPanel { } private void createActionMap() { - keyStrokesByFullName = new HashMap<>(); - actionNamesByKeyStroke = new HashMap<>(); - originalValues = new HashMap<>(); + String longestName = ""; actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool); @@ -198,10 +194,7 @@ public class KeyBindingsPanel extends JPanel { tableModel.fireTableDataChanged(); } - /** - * Create the components in this panel. - */ - private void create() { + private void createPanelComponents() { setLayout(new BorderLayout(10, 10)); tableModel = new KeyBindingsTableModel(); @@ -547,6 +540,7 @@ public class KeyBindingsPanel extends JPanel { // add each new key stroke mapping Iterator iterator = keyBindingsMap.keySet().iterator(); while (iterator.hasNext()) { + String name = iterator.next(); KeyStroke keyStroke = keyBindingsMap.get(name); keyStroke = KeyBindingUtils.validateKeyStroke(keyStroke);