diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingActionContext.java index c119be23a7..062af26c8e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingActionContext.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,25 +15,26 @@ */ package ghidra.app.context; +import docking.ComponentProvider; import ghidra.app.nav.Navigatable; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import docking.ComponentProvider; public class ListingActionContext extends NavigatableActionContext { - + public ListingActionContext(ComponentProvider provider, Navigatable navigatable) { super(provider, navigatable); } - - public ListingActionContext(ComponentProvider provider, Navigatable navigatable, ProgramLocation location) { + + public ListingActionContext(ComponentProvider provider, Navigatable navigatable, + ProgramLocation location) { super(provider, navigatable, location); } - - public ListingActionContext(ComponentProvider provider, Navigatable navigatable, - Program program, ProgramLocation location, ProgramSelection selection, - ProgramSelection highlight ) { - super( provider, navigatable, program, location, selection, highlight ); + + public ListingActionContext(ComponentProvider provider, Navigatable navigatable, + Program program, ProgramLocation location, ProgramSelection selection, + ProgramSelection highlight) { + super(provider, navigatable, program, location, selection, highlight); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java index 4dbeae78eb..101a980ae2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java @@ -25,45 +25,32 @@ public abstract class NavigatableContextAction extends DockingAction { public NavigatableContextAction(String name, String owner) { super(name, owner); + setSupportsDefaultToolContext(true); } public NavigatableContextAction(String name, String owner, KeyBindingType type) { super(name, owner, type); + setSupportsDefaultToolContext(true); } @Override public boolean isEnabledForContext(ActionContext context) { - NavigatableActionContext appropriateContext = getAppropriateContext(context); - if (appropriateContext == null) { - return false; + if (context instanceof NavigatableActionContext) { + return isEnabledForContext((NavigatableActionContext) context); } - return isEnabledForContext(appropriateContext); + return false; } @Override public void actionPerformed(ActionContext context) { - actionPerformed(getAppropriateContext(context)); - } - - private NavigatableActionContext getAppropriateContext(ActionContext context) { - if (context instanceof NavigatableActionContext && - isValidNavigationContext((NavigatableActionContext) context)) { - return (NavigatableActionContext) context; + if (context instanceof NavigatableActionContext) { + actionPerformed((NavigatableActionContext) context); } - ActionContext globalContext = context.getGlobalContext(); - if (globalContext instanceof NavigatableActionContext) { - return (NavigatableActionContext) globalContext; - } - return null; } @Override public final boolean isValidContext(ActionContext context) { - return true; - } - - protected boolean isValidNavigationContext(NavigatableActionContext context) { - return true; + return context instanceof NavigatableActionContext; } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/NextRangeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/NextRangeAction.java index 5297cd23c4..4c26827ec9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/NextRangeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/NextRangeAction.java @@ -30,23 +30,14 @@ public abstract class NextRangeAction extends NavigatableContextAction { private PluginTool tool; private NavigationOptions navOptions; - public NextRangeAction(PluginTool tool, String name, String owner, NavigationOptions navOptions) { + public NextRangeAction(PluginTool tool, String name, String owner, + NavigationOptions navOptions) { super(name, owner); this.tool = tool; this.navOptions = navOptions; setEnabled(false); } - @Override - protected boolean isValidNavigationContext(NavigatableActionContext context) { - // - // We want the nav actions to work in the current view that supports this, which right - // now is the ListingActionContext. If the current context does not support that, then - // we will be called later with the global context, which does support navigation. - // - return context instanceof ListingActionContext; - } - @Override public boolean isEnabledForContext(NavigatableActionContext context) { Address currentAddress = context.getAddress(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/PreviousRangeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/PreviousRangeAction.java index e5b04096d2..c069d48499 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/nav/PreviousRangeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/nav/PreviousRangeAction.java @@ -37,16 +37,6 @@ public abstract class PreviousRangeAction extends NavigatableContextAction { setEnabled(false); } - @Override - protected boolean isValidNavigationContext(NavigatableActionContext context) { - // - // We want the nav actions to work in the current view that supports this, which right - // now is the ListingActionContext. If the current context does not support that, then - // we will be called later with the global context, which does support navigation. - // - return context instanceof ListingActionContext; - } - @Override public void actionPerformed(NavigatableActionContext context) { Address goToAddress = getGoToAddress(context); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/FindPossibleReferencesPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/FindPossibleReferencesPlugin.java index 9872e7498f..ab30ba4989 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/FindPossibleReferencesPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/FindPossibleReferencesPlugin.java @@ -22,6 +22,7 @@ import docking.ActionContext; import docking.ComponentProvider; import docking.action.DockingAction; import docking.action.MenuData; +import docking.action.builder.ActionBuilder; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; import ghidra.app.context.*; @@ -96,33 +97,28 @@ public class FindPossibleReferencesPlugin extends Plugin { } private void createActions() { - action = new ListingContextAction(SEARCH_DIRECT_REFS_ACTION_NAME, getName()) { - @Override - protected void actionPerformed(ListingActionContext context) { - findReferences(context); - } + action = new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName()) + .menuPath(ToolConstants.MENU_SEARCH, "For Direct References") + .menuGroup("search for") + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME)) + .description(getPluginDescription().getDescription()) + .withContext(ListingActionContext.class) + .onAction(this::findReferences) + .enabledWhen(this::hasCorrectAddressSize) + .buildAndInstall(tool); - @Override - protected boolean isEnabledForContext(ListingActionContext context) { - int size = - context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize(); - if ((size == 64) || (size == 32) || (size == 24) || (size == 16) || (size == 20) || - (size == 21)) { - return true; - } - return false; - } - }; - action.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME)); - action.setMenuBarData( - new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Direct References" }, null, - "search for")); + } - action.setDescription(getPluginDescription().getDescription()); - //enableOnLocation(action); - tool.addAction(action); - - } // end of createActions() + private boolean hasCorrectAddressSize(ListingActionContext context) { + int size = + context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize(); + if ((size == 64) || (size == 32) || (size == 24) || (size == 16) || (size == 20) || + (size == 21)) { + return true; + } + return false; + } private void createLocalActions(ProgramLocationActionContext context, ComponentProvider p, FindReferencesTableModel model) { @@ -196,8 +192,10 @@ public class FindPossibleReferencesPlugin extends Plugin { "Could not find memory associated with " + fromAddr); return; } - if (currentProgram.getMemory().getBlock( - fromAddr).getType() == MemoryBlockType.BIT_MAPPED) { + if (currentProgram.getMemory() + .getBlock( + fromAddr) + .getType() == MemoryBlockType.BIT_MAPPED) { Msg.showWarn(getClass(), null, "Search For Direct References", "Cannot search for direct references on bit memory!"); return; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ClearSelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ClearSelectionAction.java index 24a9e8cc38..342e154ae1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ClearSelectionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ClearSelectionAction.java @@ -30,6 +30,7 @@ public class ClearSelectionAction extends CodeViewerContextAction { public ClearSelectionAction(String owner) { super("Clear Selection", owner); + setSupportsDefaultToolContext(true); setMenuBarData(new MenuData( new String[] { ToolConstants.MENU_SELECTION, "&Clear Selection" }, null, "Select")); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoNextFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoNextFunctionAction.java index 75558fc13c..95181e5fa2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoNextFunctionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoNextFunctionAction.java @@ -23,8 +23,9 @@ import javax.swing.KeyStroke; import docking.action.KeyBindingData; import docking.action.MenuData; import docking.tool.ToolConstants; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.context.NavigatableContextAction; import ghidra.app.nav.Navigatable; -import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; import ghidra.app.services.GoToService; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; @@ -33,7 +34,7 @@ import ghidra.program.model.listing.*; import ghidra.program.util.FunctionSignatureFieldLocation; import ghidra.util.HelpLocation; -public class GotoNextFunctionAction extends CodeViewerContextAction { +public class GotoNextFunctionAction extends NavigatableContextAction { private PluginTool tool; @@ -67,7 +68,7 @@ public class GotoNextFunctionAction extends CodeViewerContextAction { } @Override - protected void actionPerformed(CodeViewerActionContext context) { + protected void actionPerformed(NavigatableActionContext context) { Address address = context.getAddress(); Program program = context.getProgram(); Function function = getNextFunction(program, address); @@ -79,7 +80,7 @@ public class GotoNextFunctionAction extends CodeViewerContextAction { if (service != null) { FunctionSignatureFieldLocation location = new FunctionSignatureFieldLocation(program, function.getEntryPoint(), null, 0, - function.getPrototypeString(false, false)); + function.getPrototypeString(false, false)); Navigatable navigatable = context.getNavigatable(); service.goTo(navigatable, location, navigatable.getProgram()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoPreviousFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoPreviousFunctionAction.java index 64a663510e..a8cae35269 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoPreviousFunctionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/GotoPreviousFunctionAction.java @@ -23,8 +23,9 @@ import javax.swing.KeyStroke; import docking.action.KeyBindingData; import docking.action.MenuData; import docking.tool.ToolConstants; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.context.NavigatableContextAction; import ghidra.app.nav.Navigatable; -import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; import ghidra.app.services.GoToService; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; @@ -33,7 +34,7 @@ import ghidra.program.model.listing.*; import ghidra.program.util.FunctionSignatureFieldLocation; import ghidra.util.HelpLocation; -public class GotoPreviousFunctionAction extends CodeViewerContextAction { +public class GotoPreviousFunctionAction extends NavigatableContextAction { private PluginTool tool; @@ -67,7 +68,7 @@ public class GotoPreviousFunctionAction extends CodeViewerContextAction { } @Override - protected void actionPerformed(CodeViewerActionContext context) { + protected void actionPerformed(NavigatableActionContext context) { Address address = context.getAddress(); Program program = context.getProgram(); Function function = getPreviousFunction(program, address); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectAllAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectAllAction.java index 981eef72eb..e0a40e20a1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectAllAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectAllAction.java @@ -15,10 +15,6 @@ */ package ghidra.app.plugin.core.codebrowser.actions; -import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; -import ghidra.app.util.HelpTopics; -import ghidra.util.HelpLocation; - import java.awt.event.InputEvent; import java.awt.event.KeyEvent; @@ -26,27 +22,32 @@ import docking.ActionContext; import docking.action.KeyBindingData; import docking.action.MenuData; import docking.tool.ToolConstants; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.util.HelpTopics; +import ghidra.util.HelpLocation; /** * Action for adding all fields to the current format. */ public class SelectAllAction extends CodeViewerContextAction { - - public SelectAllAction(String owner) { - super("Select All", owner); - setMenuBarData( - new MenuData( - new String[] {ToolConstants.MENU_SELECTION, "&All in View" },null,"Select" ) ); - setKeyBindingData( new KeyBindingData( - KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK ) ); + public SelectAllAction(String owner) { + super("Select All", owner); + // this is in the main tool menu, so make it a tool action + setSupportsDefaultToolContext(true); + setMenuBarData( + new MenuData( + new String[] { ToolConstants.MENU_SELECTION, "&All in View" }, null, "Select")); + + setKeyBindingData(new KeyBindingData( + KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName())); - } - @Override - public void actionPerformed(ActionContext context) { - CodeViewerProvider provider = (CodeViewerProvider) context.getComponentProvider(); - provider.selectAll(); - } -} + } + @Override + public void actionPerformed(ActionContext context) { + CodeViewerProvider provider = (CodeViewerProvider) context.getComponentProvider(); + provider.selectAll(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectComplementAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectComplementAction.java index 661830ee3b..284594f96f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectComplementAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/SelectComplementAction.java @@ -14,14 +14,15 @@ * limitations under the License. */ package ghidra.app.plugin.core.codebrowser.actions; + +import docking.ActionContext; +import docking.action.MenuData; +import docking.tool.ToolConstants; import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.util.HelpTopics; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; -import docking.ActionContext; -import docking.action.MenuData; -import docking.tool.ToolConstants; /** * Action for changing the selection to the complement of all the currently @@ -29,29 +30,33 @@ import docking.tool.ToolConstants; */ public class SelectComplementAction extends CodeViewerContextAction { - public SelectComplementAction(String owner) { - super("Select Complement", owner); - setMenuBarData( new MenuData( - new String[]{ToolConstants.MENU_SELECTION, "&Complement" }, - null, - "Select" ) ); + public SelectComplementAction(String owner) { + super("Select Complement", owner); - setEnabled(false); + // this is in the main tool menu, so make it a tool action + setSupportsDefaultToolContext(true); + + setMenuBarData(new MenuData( + new String[] { ToolConstants.MENU_SELECTION, "&Complement" }, + null, + "Select")); + + setEnabled(false); setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName())); - } + } - @Override - public void actionPerformed(ActionContext context) { - CodeViewerProvider provider = (CodeViewerProvider) context.getComponentProvider(); - provider.selectComplement(); - } - @Override - public boolean isEnabledForContext(CodeViewerActionContext context) { - ProgramSelection selection = context.getSelection(); - if (selection != null && selection.getInteriorSelection() != null) { - return false; - } - return true; - } + @Override + public void actionPerformed(ActionContext context) { + CodeViewerProvider provider = (CodeViewerProvider) context.getComponentProvider(); + provider.selectComplement(); + } + + @Override + public boolean isEnabledForContext(CodeViewerActionContext context) { + ProgramSelection selection = context.getSelection(); + if (selection != null && selection.getInteriorSelection() != null) { + return false; + } + return true; + } } - 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 7557b2636e..c6497ce602 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 @@ -245,9 +245,10 @@ public class InstructionSearchPlugin extends ProgramPlugin { } @Override - protected boolean isValidNavigationContext(NavigatableActionContext context) { + protected boolean isEnabledForContext(NavigatableActionContext context) { return !(context instanceof RestrictedAddressSetContext); } + }; searchAction.setHelpLocation(new HelpLocation("Search", "Instruction_Pattern_Search")); searchAction.setMenuBarData( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/AbstractNextPreviousAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/AbstractNextPreviousAction.java index bf4d93942b..a4364e4d2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/AbstractNextPreviousAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/AbstractNextPreviousAction.java @@ -17,9 +17,10 @@ package ghidra.app.plugin.core.navigation; import javax.swing.*; -import docking.action.*; +import docking.action.KeyBindingData; +import docking.action.ToolBarData; import docking.tool.ToolConstants; -import ghidra.app.context.ListingActionContext; +import ghidra.app.context.NavigatableActionContext; import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; import ghidra.app.plugin.core.codebrowser.actions.CodeViewerContextAction; @@ -41,16 +42,12 @@ public abstract class AbstractNextPreviousAction extends CodeViewerContextAction public AbstractNextPreviousAction(PluginTool tool, String name, String owner, String subGroup) { super(name, owner); this.tool = tool; + setSupportsDefaultToolContext(true); ToolBarData toolBarData = new ToolBarData(getIcon(), ToolConstants.TOOLBAR_GROUP_FOUR); toolBarData.setToolBarSubGroup(subGroup); setToolBarData(toolBarData); - MenuData menuData = - new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, getMenuName() }, getIcon(), - ToolConstants.MENU_GROUP_NEXT_CODE_UNIT_NAV); - menuData.setMenuSubGroup(subGroup); - setMenuBarData(menuData); setKeyBindingData(new KeyBindingData(getKeyStroke())); setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, name)); setDescription(getDescriptionString()); @@ -90,7 +87,7 @@ public abstract class AbstractNextPreviousAction extends CodeViewerContextAction } } - private void gotoAddress(ListingActionContext actionContext, Address address) { + private void gotoAddress(NavigatableActionContext actionContext, Address address) { if (address == null) { tool.setStatusInfo("Unable to locate another \"" + getNavigationTypeName() + "\" past the current range, in the current direction."); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java index aefadcb6fa..b267fce6cd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevCodeUnitPlugin.java @@ -23,9 +23,9 @@ import javax.swing.Icon; import docking.action.*; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.context.NavigatableContextAction; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; -import ghidra.app.plugin.core.codebrowser.actions.CodeViewerContextAction; import ghidra.app.services.GoToService; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.*; @@ -119,18 +119,13 @@ public class NextPrevCodeUnitPlugin extends Plugin { bookmarkAction.setDirection(searchForward); } - private class ToggleDirectionAction extends CodeViewerContextAction { + private class ToggleDirectionAction extends NavigatableContextAction { Icon forwardIcon = ResourceManager.loadImage("images/down.png"); Icon backwardIcon = ResourceManager.loadImage("images/up.png"); private boolean isForward = true; ToggleDirectionAction(String subGroup) { super("Toggle Search Direction", NextPrevCodeUnitPlugin.this.getName()); - MenuData menuData = - new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, getName() }, - forwardIcon, ToolConstants.MENU_GROUP_NEXT_CODE_UNIT_NAV); - menuData.setMenuSubGroup(subGroup); - setMenuBarData(menuData); setToolBarData(new ToolBarData(forwardIcon, ToolConstants.TOOLBAR_GROUP_FOUR, subGroup)); setKeyBindingData(new KeyBindingData(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK | @@ -144,7 +139,7 @@ public class NextPrevCodeUnitPlugin extends Plugin { } @Override - public void actionPerformed(CodeViewerActionContext context) { + public void actionPerformed(NavigatableActionContext context) { isForward = !isForward; getMenuBarData().setIcon(isForward ? forwardIcon : backwardIcon); getToolBarData().setIcon(isForward ? forwardIcon : backwardIcon); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousBookmarkAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousBookmarkAction.java index aad854689c..78a5e534f1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousBookmarkAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousBookmarkAction.java @@ -29,6 +29,7 @@ import docking.menu.MultiStateDockingAction; import docking.tool.ToolConstants; import docking.widgets.EventTrigger; import ghidra.app.context.ListingActionContext; +import ghidra.app.context.NavigatableActionContext; import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; import ghidra.app.services.GoToService; @@ -56,18 +57,13 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction public NextPreviousBookmarkAction(PluginTool tool, String owner, String subGroup) { super("Next Bookmark", owner); this.tool = tool; + setSupportsDefaultToolContext(true); ToolBarData toolBarData = new ToolBarData(bookmarkIcon, ToolConstants.TOOLBAR_GROUP_FOUR); toolBarData.setToolBarSubGroup(subGroup); setToolBarData(toolBarData); - MenuData menuData = - new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, getMenuName() }, - bookmarkIcon, ToolConstants.MENU_GROUP_NEXT_CODE_UNIT_NAV); - menuData.setMenuSubGroup(subGroup); - setMenuBarData(menuData); - setKeyBindingData(new KeyBindingData(getKeyStroke())); setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, getName())); @@ -93,19 +89,6 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction addActionState(warning); addActionState(custom); - /*BookmarkPlugin bookmarkPlugin = new BookmarkPlugin(tool); - Program program = bookmarkPlugin.getCurrentProgram(); - BookmarkManager bookmarkManager = program.getBookmarkManager(); - BookmarkType[] bookmarkTypes = bookmarkManager.getBookmarkTypes(); - - for (BookmarkType bookmarkType : bookmarkTypes) { - ActionState tempBookmarkType = - new ActionState(bookmarkType.getTypeString(), bookmarkUnknownIcon, - bookmarkType.getTypeString()); - - addActionState(tempBookmarkType); - }*/ - setCurrentActionState(allBookmarks); // default } @@ -119,8 +102,8 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction @Override protected void doActionPerformed(ActionContext context) { - if (context instanceof ListingActionContext) { - gotoNextPrevious((ListingActionContext) context, this.getCurrentUserData()); + if (context instanceof NavigatableActionContext) { + gotoNextPrevious((NavigatableActionContext) context, this.getCurrentUserData()); } } @@ -212,7 +195,8 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction //================================================================================================== // AbstractNextPreviousAction Methods //================================================================================================== - private void gotoNextPrevious(final ListingActionContext context, final String bookmarkType) { + private void gotoNextPrevious(final NavigatableActionContext context, + final String bookmarkType) { final Address address = isForward ? getNextAddress(context.getProgram(), context.getAddress(), bookmarkType) : getPreviousAddress(context.getProgram(), context.getAddress(), bookmarkType); @@ -225,7 +209,7 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction }); } - private void gotoAddress(ListingActionContext listingActionContext, Address address) { + private void gotoAddress(NavigatableActionContext listingActionContext, Address address) { if (address == null) { tool.setStatusInfo("Unable to locate another " + getNavigationTypeName() + " past the current range, in the current direction."); @@ -264,40 +248,14 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction //================================================================================================== // CodeViewerContextAction Methods //================================================================================================== - @Override - public boolean isEnabledForContext(ActionContext context) { - if (!(context instanceof CodeViewerActionContext)) { - return false; - } - return isEnabledForContext((CodeViewerActionContext) context); - } - @Override public boolean isValidContext(ActionContext context) { - if (!(context instanceof CodeViewerActionContext)) { - return false; - } - return isValidContext((CodeViewerActionContext) context); + return context instanceof ListingActionContext; } @Override - public boolean isAddToPopup(ActionContext context) { - if (!(context instanceof CodeViewerActionContext)) { - return false; - } - return isAddToPopup((CodeViewerActionContext) context); - } - - protected boolean isValidContext(CodeViewerActionContext context) { - return true; - } - - protected boolean isEnabledForContext(CodeViewerActionContext context) { - return true; - } - - protected boolean isAddToPopup(CodeViewerActionContext context) { - return isEnabledForContext(context); + public boolean isEnabledForContext(ActionContext context) { + return context instanceof ListingActionContext; } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java index 2bf8a64270..5f56227a88 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java @@ -17,7 +17,6 @@ package ghidra.app.plugin.core.progmgr; import java.awt.Component; import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; import java.beans.PropertyEditor; import java.io.IOException; import java.net.MalformedURLException; @@ -26,16 +25,12 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import javax.swing.Icon; - -import docking.ActionContext; -import docking.DockingUtils; -import docking.action.*; +import docking.action.DockingAction; +import docking.action.builder.ActionBuilder; import docking.options.editor.*; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; import ghidra.app.context.ProgramActionContext; -import ghidra.app.context.ProgramContextAction; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.ProgramManager; @@ -59,7 +54,6 @@ import ghidra.program.util.*; import ghidra.util.*; import ghidra.util.exception.NotFoundException; import ghidra.util.task.TaskLauncher; -import resources.ResourceManager; //@formatter:off @PluginInfo( @@ -86,13 +80,15 @@ import resources.ResourceManager; //@formatter:on public class ProgramManagerPlugin extends Plugin implements ProgramManager { + private static final String SAVE_GROUP = "DomainObjectSave"; + private static final String OPEN_GROUP = "DomainObjectOpen"; private MultiProgramManager programMgr; private ProgramSaveManager programSaveMgr; private DockingAction openAction; private DockingAction saveAllAction; - private ProgramContextAction closeAction; - private ProgramContextAction saveAction; - private ProgramContextAction saveAsAction; + private DockingAction closeAction; + private DockingAction saveAction; + private DockingAction saveAsAction; private DockingAction optionsAction; private DockingAction closeOthersAction; private DockingAction closeAllAction; @@ -101,7 +97,6 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { private boolean locked = false; private UndoAction undoAction; private RedoAction redoAction; - private ProgramActionContext lastProgramContext; private ProgramLocation currentLocation; public ProgramManagerPlugin(PluginTool tool) { @@ -527,206 +522,72 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { int subMenuGroupOrder = 1; - openAction = new DockingAction("Open File", getName()) { - @Override - public void actionPerformed(ActionContext context) { - open(); - } - }; - MenuData menuData = - new MenuData(new String[] { ToolConstants.MENU_FILE, "&Open..." }, "DomainObjectOpen"); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - openAction.setMenuBarData(menuData); - openAction.setKeyBindingData( - new KeyBindingData(KeyEvent.VK_O, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + openAction = new ActionBuilder("Open File", getName()) + .onAction(c -> open()) + .menuPath(ToolConstants.MENU_FILE, "&Open...") + .menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++)) + .keyBinding("ctrl O") + .buildAndInstall(tool); - closeAction = new ProgramContextAction("Close File", getName()) { - @Override - public void actionPerformed(ProgramActionContext programContext) { - closeProgram(programContext.getProgram(), false); - } + closeAction = new ActionBuilder("Close File", getName()) + .menuPath(ToolConstants.MENU_FILE, "&Close") + .menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++)) + .withContext(ProgramActionContext.class) + .onAction(c -> closeProgram(c.getProgram(), false)) + .buildAndInstall(tool); - @Override - public boolean isValidContext(ActionContext context) { - if (!super.isValidContext(context)) { - getMenuBarData().setMenuItemName("&Close"); - setDescription("Close Program"); - return false; - } - return true; - } + closeOthersAction = new ActionBuilder("Close Others", getName()) + .menuPath(ToolConstants.MENU_FILE, "Close &Others") + .menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++)) + .enabled(false) + .onAction(c -> closeOtherPrograms(false)) + .buildAndInstall(tool); - @Override - public boolean isEnabledForContext(ProgramActionContext context) { - Program program = context.getProgram(); - String programName = "'" + program.getDomainFile().getName() + "'"; - getMenuBarData().setMenuItemName("&Close " + programName); - setDescription("Close " + HTMLUtilities.escapeHTML(programName)); - return true; - } - }; - String[] closeActionMenuPath = { ToolConstants.MENU_FILE, "&Close" }; - menuData = new MenuData(closeActionMenuPath, null, "DomainObjectOpen"); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - closeAction.setMenuBarData(menuData); + closeAllAction = new ActionBuilder("Close All", getName()) + .menuPath(ToolConstants.MENU_FILE, "Close &All") + .menuGroup(OPEN_GROUP, Integer.toString(subMenuGroupOrder++)) + .onAction(c -> closeAllPrograms(false)) + .enabled(false) + .buildAndInstall(tool); - closeOthersAction = new DockingAction("Close Others", getName()) { - @Override - public void actionPerformed(ActionContext context) { - closeOtherPrograms(false); - } - }; - closeOthersAction.setEnabled(false); - String[] menuPath = { ToolConstants.MENU_FILE, "Close &Others" }; - menuData = new MenuData(menuPath, null, "DomainObjectOpen"); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - closeOthersAction.setMenuBarData(menuData); + saveAction = new ActionBuilder("Save File", "&Save") + .menuPath(ToolConstants.MENU_FILE, "Close &All") + .description("Save Program") + .menuGroup(SAVE_GROUP, Integer.toString(subMenuGroupOrder++)) + .menuIcon(null) + .toolBarIcon("images/disk.png") + .toolBarGroup(ToolConstants.TOOLBAR_GROUP_ONE) + .keyBinding("ctrl S") + .withContext(ProgramActionContext.class) + .enabledWhen(c -> c.getProgram().isChanged()) + .onAction(c -> programSaveMgr.saveProgram(c.getProgram())) + .buildAndInstall(tool); - closeAllAction = new DockingAction("Close All", getName()) { - @Override - public void actionPerformed(ActionContext context) { - closeAllPrograms(false); - } - }; - closeAllAction.setEnabled(false); - String[] nenuPath = { ToolConstants.MENU_FILE, "Close &All" }; - menuData = new MenuData(nenuPath, null, "DomainObjectOpen"); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - closeAllAction.setMenuBarData(menuData); + saveAsAction = new ActionBuilder("Save As File", getName()) + .menuPath(ToolConstants.MENU_FILE, "Save &As...") + .menuGroup(SAVE_GROUP, Integer.toString(subMenuGroupOrder++)) + .withContext(ProgramActionContext.class) + .onAction(c -> programSaveMgr.saveAs(c.getProgram())) + .buildAndInstall(tool); - saveAction = new ProgramContextAction("Save File", getName()) { - @Override - public void actionPerformed(ProgramActionContext programContext) { - programSaveMgr.saveProgram(programContext.getProgram()); - // setEnabled(false); - } + saveAllAction = new ActionBuilder("Save All Files", getName()) + .menuPath(ToolConstants.MENU_FILE, "Save All") + .description("Save All Programs") + .menuGroup(SAVE_GROUP, Integer.toString(subMenuGroupOrder++)) + .onAction(c -> programSaveMgr.saveChangedPrograms()) + .buildAndInstall(tool); - @Override - public boolean isValidContext(ActionContext context) { - lastProgramContext = null; - if (!super.isValidContext(context)) { - getMenuBarData().setMenuItemName("&Save"); - setDescription("Save Program"); - return false; - } - return true; - } - - @Override - public boolean isEnabledForContext(ProgramActionContext context) { - lastProgramContext = context; - Program program = context.getProgram(); - String programName = "'" + program.getDomainFile().getName() + "'"; - getMenuBarData().setMenuItemName("&Save " + programName); - setDescription("Save " + programName); - return program.isChanged(); - } - - @Override - protected boolean isValidContext(ProgramActionContext context) { - return super.isValidContext(context); - } - }; - String[] saveMenuPath = { ToolConstants.MENU_FILE, "&Save" }; - Icon saveIcon = ResourceManager.loadImage("images/disk.png"); - String saveGroup = ToolConstants.TOOLBAR_GROUP_ONE; - subMenuGroupOrder = 0; - - menuData = new MenuData(saveMenuPath, saveIcon, saveGroup); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - saveAction.setMenuBarData(menuData); - saveAction.setToolBarData(new ToolBarData(saveIcon, saveGroup)); - saveAction - .setKeyBindingData(new KeyBindingData('S', DockingUtils.CONTROL_KEY_MODIFIER_MASK)); - saveAction.setDescription("Save Program"); - - saveAsAction = new ProgramContextAction("Save As File", getName()) { - @Override - public void actionPerformed(ProgramActionContext programContext) { - programSaveMgr.saveAs(programContext.getProgram()); - } - - @Override - public boolean isValidContext(ActionContext context) { - if (!super.isValidContext(context)) { - getMenuBarData().setMenuItemName("Save &As..."); - setDescription("Save &As..."); - return false; - } - return true; - } - - @Override - public boolean isEnabledForContext(ProgramActionContext context) { - Program program = context.getProgram(); - String programName = "'" + program.getDomainFile().getName() + "'"; - String menuName = "Save " + programName + " &As..."; - getMenuBarData().setMenuItemName(menuName); - setDescription(menuName); - return true; - } - }; - String[] saveAsPath = { ToolConstants.MENU_FILE, "Save &As..." }; - menuData = new MenuData(saveAsPath, null, "DomainObjectSave"); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - saveAsAction.setMenuBarData(menuData); - - saveAllAction = new DockingAction("Save All Files", getName()) { - @Override - public void actionPerformed(ActionContext context) { - programSaveMgr.saveChangedPrograms(); - } - }; - menuData = - new MenuData(new String[] { ToolConstants.MENU_FILE, "Save All" }, "DomainObjectSave"); - menuData.setMenuSubGroup(Integer.toString(subMenuGroupOrder++)); - saveAllAction.setMenuBarData(menuData); - saveAllAction.setDescription("Save All Programs"); - - optionsAction = new ProgramContextAction("Program Options", getName()) { - @Override - public void actionPerformed(ProgramActionContext programContext) { - showProgramOptions(programContext.getProgram()); - } - - @Override - public boolean isValidContext(ActionContext context) { - if (!super.isValidContext(context)) { - getMenuBarData().setMenuItemName("Program Options"); - return false; - } - return true; - } - - @Override - public boolean isEnabledForContext(ProgramActionContext context) { - lastProgramContext = context; - Program program = context.getProgram(); - String programName = program.getDomainFile().getName(); - getMenuBarData().setMenuItemName("Options for " + programName + "..."); - return true; - } - }; - String[] optionsPath = { ToolConstants.MENU_EDIT, "P&rogram Options..." }; - menuData = new MenuData(optionsPath, null, ToolConstants.TOOL_OPTIONS_MENU_GROUP); - - // update these options to appear below those for the tool, which we know is defined - // inside of ToolConstants - menuData.setMenuSubGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP + "b"); - optionsAction.setMenuBarData(menuData); - optionsAction.setDescription("Edit Options for current program"); + optionsAction = new ActionBuilder("Program Options", getName()) + .menuPath(ToolConstants.MENU_EDIT, "P&rogram Options...") + .description("Edit Options for current program") + .menuGroup(ToolConstants.TOOL_OPTIONS_MENU_GROUP, + ToolConstants.TOOL_OPTIONS_MENU_GROUP + "b") + .withContext(ProgramActionContext.class) + .onAction(c -> showProgramOptions(c.getProgram())) + .buildAndInstall(tool); undoAction = new UndoAction(tool, getName()); redoAction = new RedoAction(tool, getName()); - - tool.addAction(openAction); - tool.addAction(closeAction); - tool.addAction(closeOthersAction); - tool.addAction(closeAllAction); - tool.addAction(saveAction); - tool.addAction(saveAsAction); - tool.addAction(saveAllAction); - tool.addAction(optionsAction); tool.addAction(undoAction); tool.addAction(redoAction); } @@ -791,6 +652,10 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { private void updateActions() { Program p = programMgr.getCurrentProgram(); + updateCloseAction(p); + updateProgramOptionsAction(p); + updateSaveAction(p); + updateSaveAsAction(p); closeAllAction.setEnabled(p != null); optionsAction.setEnabled(p != null); Program[] programList = programMgr.getAllPrograms(); @@ -805,6 +670,54 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { tool.contextChanged(null); } + private void updateSaveAction(Program p) { + if (p == null) { + saveAction.getMenuBarData().setMenuItemName("&Save"); + saveAction.setDescription("Save Program"); + saveAction.setEnabled(false); + } + else { + String programName = "'" + p.getDomainFile().getName() + "'"; + saveAction.getMenuBarData().setMenuItemName("&Save " + programName); + saveAction.setDescription("Save " + programName); + saveAction.setEnabled(p.isChanged()); + } + } + + private void updateSaveAsAction(Program p) { + if (p == null) { + saveAsAction.getMenuBarData().setMenuItemName("Save &As..."); + } + else { + String programName = "'" + p.getDomainFile().getName() + "'"; + saveAsAction.getMenuBarData().setMenuItemName("Save " + programName + " &As..."); + } + } + + private void updateProgramOptionsAction(Program p) { + if (p == null) { + optionsAction.getMenuBarData().setMenuItemName("Program Options"); + } + else { + String programName = "'" + p.getDomainFile().getName() + "'"; + optionsAction.getMenuBarData().setMenuItemName("Options for " + programName + "..."); + } + optionsAction.setEnabled(p != null); + } + + private void updateCloseAction(Program p) { + if (p == null) { + closeAction.getMenuBarData().setMenuItemName("&Close"); + closeAction.setDescription("Close Program"); + } + else { + String programName = "'" + p.getDomainFile().getName() + "'"; + closeAction.getMenuBarData().setMenuItemName("&Close " + programName); + closeAction.setDescription("Close " + HTMLUtilities.escapeHTML(programName)); + } + closeAction.setEnabled(p != null); + } + private void open() { if (openDialog == null) { ActionListener listener = e -> { @@ -875,17 +788,11 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager { void updateProgramActions() { updateSaveAllAction(); - if (lastProgramContext != null) { - updateProgramAction(undoAction); - updateProgramAction(redoAction); - updateProgramAction(saveAction); - updateProgramAction(saveAsAction); - } - } - - private void updateProgramAction(ProgramContextAction action) { - boolean isEnabled = action.isEnabledForContext(lastProgramContext); - action.setEnabled(isEnabled); + Program p = getCurrentProgram(); + updateSaveAction(getCurrentProgram()); + updateSaveAsAction(getCurrentProgram()); + undoAction.update(p); + redoAction.update(p); } private void updateSaveAllAction() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java index 32de860536..a974062491 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/RedoAction.java @@ -15,12 +15,10 @@ */ package ghidra.app.plugin.core.progmgr; -import java.awt.event.InputEvent; import java.io.IOException; import javax.swing.Icon; -import docking.ActionContext; import docking.action.*; import docking.tool.ToolConstants; import ghidra.app.context.ProgramActionContext; @@ -46,7 +44,7 @@ public class RedoAction extends ProgramContextAction { menuData.setMenuSubGroup("2Redo"); // make this appear below the undo menu item setMenuBarData(menuData); setToolBarData(new ToolBarData(icon, group)); - setKeyBindingData(new KeyBindingData('Z', InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)); + setKeyBindingData(new KeyBindingData("ctrl shift Z")); setDescription("Redo"); } @@ -62,18 +60,41 @@ public class RedoAction extends ProgramContextAction { } } - @Override - protected boolean isEnabledForContext(ProgramActionContext context) { - Program program = context.getProgram(); - if (program.canRedo()) { + /** + * updates the menu name of the action as the undo stack changes + *

+ * NOTE: currently, we must manage the enablement explicitly + * because contextChanged is not called for data changes. Ideally, the enablement + * would be handled by the context, but for now it doesn't work + * + * @param program the program + */ + public void update(Program program) { + + if (program == null) { + getMenuBarData().setMenuItemName("Redo "); + setDescription(""); + setEnabled(false); + } + else if (program.canRedo()) { String programName = program.getDomainFile().getName(); getMenuBarData().setMenuItemName("Redo " + programName); String tip = HTMLUtilities.toWrappedHTML( "Redo " + HTMLUtilities.escapeHTML(program.getRedoName())); setDescription(tip); - return true; + setEnabled(true); } - return false; + else { + setDescription("Redo"); + setEnabled(false); + } + + } + + @Override + protected boolean isEnabledForContext(ProgramActionContext context) { + Program program = context.getProgram(); + return program.canRedo(); } private void saveCurrentLocationToHistory() { @@ -84,13 +105,4 @@ public class RedoAction extends ProgramContextAction { } } - @Override - public boolean isEnabledForContext(ActionContext actionContext) { - if (!super.isEnabledForContext(actionContext)) { - setDescription("Redo"); - getMenuBarData().setMenuItemName("Redo"); - return false; - } - return true; - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java index 84b0e8d939..0c8212f876 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/UndoAction.java @@ -15,12 +15,10 @@ */ package ghidra.app.plugin.core.progmgr; -import java.awt.event.InputEvent; import java.io.IOException; import javax.swing.Icon; -import docking.ActionContext; import docking.action.*; import docking.tool.ToolConstants; import ghidra.app.context.ProgramActionContext; @@ -46,7 +44,7 @@ public class UndoAction extends ProgramContextAction { setMenuBarData(menuData); setToolBarData(new ToolBarData(icon, "Undo")); setDescription("Undo"); - setKeyBindingData(new KeyBindingData('Z', InputEvent.CTRL_MASK)); + setKeyBindingData(new KeyBindingData("ctrl Z")); } @Override @@ -69,27 +67,39 @@ public class UndoAction extends ProgramContextAction { } } - @Override - protected boolean isEnabledForContext(ProgramActionContext context) { - Program program = context.getProgram(); - if (program.canUndo()) { + /** + * updates the menu name of the action as the undo stack changes + *

+ * NOTE: currently, we must manage the enablement explicitly + * because contextChanged is not called for data changes. Ideally, the enablement + * would be handled by the context, but for now it doesn't work + * + * @param program the program + */ + public void update(Program program) { + + if (program == null) { + getMenuBarData().setMenuItemName("Undo "); + setDescription(""); + setEnabled(false); + } + if (program != null && program.canUndo()) { String programName = program.getDomainFile().getName(); getMenuBarData().setMenuItemName("Undo " + programName); String tip = HTMLUtilities.toWrappedHTML( "Undo " + HTMLUtilities.escapeHTML(program.getUndoName())); setDescription(tip); - return true; + setEnabled(true); + } + else { + setDescription("Undo"); + setEnabled(false); } - return false; } @Override - public boolean isEnabledForContext(ActionContext actionContext) { - if (!super.isEnabledForContext(actionContext)) { - setDescription("Undo"); - getMenuBarData().setMenuItemName("Undo"); - return false; - } - return true; + protected boolean isEnabledForContext(ProgramActionContext context) { + Program program = context.getProgram(); + return program.canUndo(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchPlugin.java index 5927f65187..4c43ad5d8a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchPlugin.java @@ -145,7 +145,7 @@ public class ScalarSearchPlugin extends ProgramPlugin implements DomainObjectLis } @Override - protected boolean isValidNavigationContext(NavigatableActionContext context) { + protected boolean isEnabledForContext(NavigatableActionContext context) { return !(context instanceof RestrictedAddressSetContext); } }; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java index 0edd603493..efbe2b37c0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java @@ -350,7 +350,7 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, } @Override - protected boolean isValidNavigationContext(NavigatableActionContext context) { + protected boolean isEnabledForContext(NavigatableActionContext context) { return !(context instanceof RestrictedAddressSetContext); } }; @@ -369,13 +369,8 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, } @Override - public boolean isEnabledForContext(NavigatableActionContext context) { - return searchInfo != null; - } - - @Override - protected boolean isValidNavigationContext(NavigatableActionContext context) { - return !(context instanceof RestrictedAddressSetContext); + protected boolean isEnabledForContext(NavigatableActionContext context) { + return !(context instanceof RestrictedAddressSetContext) && searchInfo != null; } }; searchAgainAction.setHelpLocation( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java index 7478f97a97..8c81e6a588 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java @@ -16,8 +16,6 @@ package ghidra.app.plugin.core.searchtext; import java.awt.*; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -26,13 +24,15 @@ import java.util.regex.Pattern; import javax.swing.ImageIcon; import docking.*; -import docking.action.*; +import docking.action.DockingAction; +import docking.action.builder.ActionBuilder; import docking.tool.ToolConstants; import docking.widgets.fieldpanel.support.Highlight; import docking.widgets.table.threaded.*; import ghidra.GhidraOptions; import ghidra.app.CorePluginPackage; -import ghidra.app.context.*; +import ghidra.app.context.ListingActionContext; +import ghidra.app.context.NavigatableActionContext; import ghidra.app.nav.Navigatable; import ghidra.app.nav.NavigatableRemovalListener; import ghidra.app.plugin.PluginCategoryNames; @@ -379,49 +379,34 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList private void createActions() { String subGroup = getClass().getName(); - searchAction = new ListingContextAction("Search Text", getName()) { - @Override - public void actionPerformed(ListingActionContext context) { - setNavigatable(context.getNavigatable()); - displayDialog(context); - } - }; - searchAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, searchAction.getName())); - String[] menuPath = new String[] { "&Search", "Program &Text..." }; - MenuData menuData = new MenuData(menuPath, "search"); - menuData.setMenuSubGroup(subGroup); - searchAction.setMenuBarData(menuData); - searchAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_E, - InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + searchAction = new ActionBuilder("Search Text", getName()) + .menuPath("&Search", "Program &Text...") + .menuGroup("search", subGroup) + .keyBinding("ctrl shift E") + .description(DESCRIPTION) + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text")) + .withContext(NavigatableActionContext.class) + .supportsDefaultToolContext(true) + .onAction(c -> { + setNavigatable(c.getNavigatable()); + displayDialog(c); + }) + .buildAndInstall(tool); - searchAction.setDescription(DESCRIPTION); - searchAction.setEnabled(false); - //searchAction.setAddToPopup(false); - tool.addAction(searchAction); - - searchAgainAction = new ListingContextAction("Repeat Text Search", getName()) { - @Override - public void actionPerformed(ListingActionContext context) { - setNavigatable(context.getNavigatable()); - searchDialog.repeatSearch(); - } - - @Override - public boolean isEnabledForContext(ListingActionContext context) { - return searchedOnce; - } - }; - searchAgainAction.setHelpLocation( - new HelpLocation(HelpTopics.SEARCH, searchAgainAction.getName())); - menuPath = new String[] { "&Search", "Repeat Text Search" }; - menuData = new MenuData(menuPath, "search"); - menuData.setMenuSubGroup(subGroup); - searchAgainAction.setMenuBarData(menuData); - searchAgainAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F3, - InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); - - searchAgainAction.setDescription(DESCRIPTION); - tool.addAction(searchAgainAction); + searchAgainAction = new ActionBuilder("Repeat Text Search", getName()) + .menuPath("&Search", "Repeat Text Search") + .menuGroup("search", subGroup) + .keyBinding("ctrl shift F3") + .description(DESCRIPTION) + .supportsDefaultToolContext(true) + .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Repeat Text Search")) + .withContext(NavigatableActionContext.class) + .enabledWhen(c -> searchedOnce) + .onAction(c -> { + setNavigatable(c.getNavigatable()); + searchDialog.repeatSearch(); + }) + .buildAndInstall(tool); } protected void updateNavigatable(ActionContext context) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/flow/SelectByFlowAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/flow/SelectByFlowAction.java index fe0321b579..6dbe9ca4f5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/flow/SelectByFlowAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/flow/SelectByFlowAction.java @@ -15,14 +15,14 @@ */ package ghidra.app.plugin.core.select.flow; +import docking.action.MenuData; +import docking.tool.ToolConstants; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; import ghidra.app.util.HelpTopics; import ghidra.program.model.address.Address; import ghidra.program.model.listing.CodeUnit; import ghidra.util.HelpLocation; -import docking.action.MenuData; -import docking.tool.ToolConstants; /** * SelectByFlowAction allows the user to Select Code By Flowing from @@ -63,6 +63,9 @@ class SelectByFlowAction extends ListingContextAction { this.selectByFlowPlugin = plugin; this.selectionType = selectionType; + // this is in the main tool menu, so make it a tool action + setSupportsDefaultToolContext(true); + String[] menuPath = null; if (selectionType == SelectByFlowPlugin.SELECT_FUNCTIONS) { menuPath = new String[] { ToolConstants.MENU_SELECTION, "Function" }; @@ -91,11 +94,6 @@ class SelectByFlowAction extends ListingContextAction { setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName())); } - /** - * Method called when the action is invoked. - * - * @param ActionEvent details regarding the invocation of this action - */ @Override public void actionPerformed(ListingActionContext context) { // Either select by following all flows or all flows except calls diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/reference/SelectForwardRefsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/reference/SelectForwardRefsAction.java index 8eb4601dae..9b533ce472 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/reference/SelectForwardRefsAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/reference/SelectForwardRefsAction.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,8 +15,13 @@ */ package ghidra.app.plugin.core.select.reference; -import ghidra.app.context.ListingContextAction; -import ghidra.app.context.ListingActionContext; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.context.NavigatableContextAction; import ghidra.app.nav.NavigationUtils; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; @@ -27,13 +31,7 @@ import ghidra.program.model.symbol.Reference; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; - -import docking.action.KeyBindingData; -import docking.action.MenuData; - -public class SelectForwardRefsAction extends ListingContextAction { +public class SelectForwardRefsAction extends NavigatableContextAction { private final PluginTool tool; @@ -42,47 +40,47 @@ public class SelectForwardRefsAction extends ListingContextAction { this.tool = tool; String group = "references"; - setMenuBarData( new MenuData( new String[] {"Select", "Forward Refs"}, null, group ) ); - - setKeyBindingData( new KeyBindingData( KeyEvent.VK_PERIOD, InputEvent.CTRL_MASK ) ); + setMenuBarData(new MenuData(new String[] { "Select", "Forward Refs" }, null, group)); + + setKeyBindingData(new KeyBindingData(KeyEvent.VK_PERIOD, InputEvent.CTRL_MASK)); setHelpLocation(new HelpLocation(HelpTopics.SELECTION, "Forward")); // setKeyBindingData( new KeyBindingData(KeyEvent.VK_SEMICOLON, InputEvent.CTRL_MASK ) ); // setHelpLocation(new HelpLocation(HelpTopics.SELECTION, "Backward")); } - + @Override - protected boolean isEnabledForContext(ListingActionContext context) { + protected boolean isEnabledForContext(NavigatableActionContext context) { return context.getAddress() != null || context.hasSelection(); } + /** * Method called when the action is invoked. * @param ActionEvent details regarding the invocation of this action */ @Override - public void actionPerformed(ListingActionContext context) { - - AddressSetView addressSet = context.hasSelection() ? - context.getSelection() : - new AddressSet(context.getAddress()); - + public void actionPerformed(NavigatableActionContext context) { + + AddressSetView addressSet = + context.hasSelection() ? context.getSelection() : new AddressSet(context.getAddress()); + ProgramSelection selection = getSelection(context.getProgram(), addressSet); NavigationUtils.setSelection(tool, context.getNavigatable(), selection); } - private ProgramSelection getSelection(Program program, AddressSetView addressSetView){ + private ProgramSelection getSelection(Program program, AddressSetView addressSetView) { AddressSet addressSet = new AddressSet(); - CodeUnitIterator iter = program.getListing().getCodeUnits(addressSetView,true); + CodeUnitIterator iter = program.getListing().getCodeUnits(addressSetView, true); - while (iter.hasNext()){ - CodeUnit cu=iter.next(); - Reference[] memRef=cu.getReferencesFrom(); - for (int i=0;i list; + private List list; private JList actionList; private DefaultListModel listModel; @@ -42,7 +42,7 @@ public class MultiActionDialog extends DialogComponentProvider { * @param keystrokeName keystroke name * @param list list of actions */ - public MultiActionDialog(String keystrokeName, List list) { + public MultiActionDialog(String keystrokeName, List list) { super("Select Action", true); this.keystrokeName = keystrokeName; init(); @@ -65,7 +65,7 @@ public class MultiActionDialog extends DialogComponentProvider { close(); - ExecutableKeyActionAdapter actionProxy = list.get(index); + ExecutableAction actionProxy = list.get(index); actionProxy.execute(); } @@ -73,12 +73,12 @@ public class MultiActionDialog extends DialogComponentProvider { * Set the list of actions that are enabled * @param list list of actions selected */ - public void setActionList(List list) { + public void setActionList(List list) { okButton.setEnabled(false); this.list = list; listModel.clear(); for (int i = 0; i < list.size(); i++) { - ExecutableKeyActionAdapter actionProxy = list.get(i); + ExecutableAction actionProxy = list.get(i); DockingActionIf action = actionProxy.getAction(); listModel.addElement(action.getName() + " (" + action.getOwnerDescription() + ")"); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/Tool.java b/Ghidra/Framework/Docking/src/main/java/docking/Tool.java index 82d9298534..8965180ac5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/Tool.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/Tool.java @@ -305,11 +305,10 @@ public interface Tool extends ServiceProvider { public void close(); /** - * Returns the global action context for the tool. The global context is the context of - * the default focused component, instead of the normal action context which is the current - * focused component. - * @return the global action context for the tool + * Returns the default {@link ActionContext} for the tool. The default context is the context + * the default ComponentProvider for the tool. + * @return the default {@link ActionContext} for the tool */ - public ActionContext getGlobalActionContext(); + public ActionContext getDefaultToolContext(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java index 71aa801b18..74fb2ff849 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java @@ -120,12 +120,8 @@ public class WindowActionManager { return; } - ComponentProvider provider = placeHolderForScheduledActionUpdate == null ? null - : placeHolderForScheduledActionUpdate.getProvider(); - ActionContext localContext = provider == null ? null : provider.getActionContext(null); - if (localContext == null) { - localContext = new ActionContext(); - } + ActionContext localContext = getContext(); + ActionContext globalContext = winMgr.getDefaultToolContext(); // Update actions - make a copy so that we don't get concurrent modification exceptions List list = new ArrayList<>(actionToProxyMap.values()); @@ -133,6 +129,9 @@ public class WindowActionManager { if (action.isValidContext(localContext)) { action.setEnabled(action.isEnabledForContext(localContext)); } + else if (isValidDefaultToolContext(action, globalContext)) { + action.setEnabled(action.isEnabledForContext(globalContext)); + } else { action.setEnabled(false); } @@ -140,4 +139,21 @@ public class WindowActionManager { // Notify listeners if the context provider is the focused provider winMgr.notifyContextListeners(placeHolderForScheduledActionUpdate, localContext); } + + private boolean isValidDefaultToolContext(DockingActionIf action, ActionContext toolContext) { + return action.supportsDefaultToolContext() && + action.isValidContext(toolContext); + } + + private ActionContext getContext() { + ComponentProvider provider = placeHolderForScheduledActionUpdate == null ? null + : placeHolderForScheduledActionUpdate.getProvider(); + + ActionContext context = provider == null ? null : provider.getActionContext(null); + + if (context == null) { + context = new ActionContext(); + } + return context; + } } 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 4670c3a814..2034c88eda 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java @@ -77,6 +77,8 @@ public abstract class DockingAction implements DockingActionIf { private Predicate popupPredicate; private Predicate validContextPredicate; + private boolean supportsDefaultToolContext; + public DockingAction(String name, String owner) { this.name = name; this.owner = owner; @@ -224,6 +226,16 @@ public abstract class DockingAction implements DockingActionIf { return !isEnabled; } + @Override + public void setSupportsDefaultToolContext(boolean newValue) { + supportsDefaultToolContext = newValue; + } + + @Override + public boolean supportsDefaultToolContext() { + return supportsDefaultToolContext; + } + @Override public final JButton createButton() { JButton button = doCreateButton(); 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 80d92e768c..0d04189a80 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java @@ -33,6 +33,26 @@ import docking.help.HelpDescriptor; * client actions will use the default setting of {@link KeyBindingType#INDIVIDUAL}. To control * the level of key binding support, you can pass the desired {@link KeyBindingType} to the * base implementation of this interface. + * + *

ActionContext is a key concept for tool actions so that they can be context sensitive if + * appropriate. The context provides a + * consistent way for plugins and components to share tool state with actions. Actions can then + * use that context to make decisions, such as if they should be enabled or added to a popup menu. + * The context information is also typically used when the action is invoked. For example, an + * action context from a table element may provide the row in a table component that is selected and + * then a "delete table row" action can use that information to be enabled when a table selection + * exists and then delete that row if the action is invoked. + * + *

To make the overall action experience more convenient for the user, action processing + * supports the concept of a "default tool context". This allows actions to work on a more global + * level than just the component that is focused. The idea is that if an action is not valid for + * the current focused context (and it has be declared to work this way using + * the {@link #setSupportsDefaultToolContext(boolean)}), then it can be validated against the default + * tool context. The "default tool context" is defined to be the action context of the tool's + * primary component. This is primarily intended for tool-level actions which are the ones that appear + * in the tool's main menu bar or toolbar. This allows the tool actions to mostly work on the + * tool's main component context regardless of what has focus, and yet still work on the + * focused component if appropriate (such as a snapshot of the main component). */ public interface DockingActionIf extends HelpDescriptor { public static final String ENABLEMENT_PROPERTY = "enabled"; @@ -97,6 +117,26 @@ public interface DockingActionIf extends HelpDescriptor { */ public boolean setEnabled(boolean newValue); + /** + * Sets whether or not this action should be activated using the default tool context if the + * current focused provider's context is not valid for this action. Typically, this should + * be set on actions that are mostly independent of which component has focus such as those + * on the tool's main toolbar. + * + * @param newValue if true, the action will be activated using the default tool context if the + * local context is not valid for this action. If false, the action will only ever be + * activated using the focused context. + */ + public void setSupportsDefaultToolContext(boolean newValue); + + /** + * Returns true if this action can be activated using the default tool context if the focused + * context is invalid for this action. See {@link #setSupportsDefaultToolContext(boolean)} + * @return true if this action can be activated using the default tool context if the local + * context is invalid for this action. + */ + public boolean supportsDefaultToolContext(); + /** * Returns true if the action is enabled. * @@ -120,7 +160,7 @@ public interface DockingActionIf extends HelpDescriptor { /** * Returns the {@link ToolBarData} to be used to put this action in a toolbar. The ToolBarData will be - * null if the action in not set to be in a tool bar. + * null if the action in not set to be in a toolbar. * @return the {@link ToolBarData} for the popup menu or null if the action is not in a popup menu. */ public ToolBarData getToolBarData(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java index 947e2e39dd..f0cf2a6ad2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java @@ -36,6 +36,18 @@ public class KeyBindingData { this(KeyStroke.getKeyStroke(keyCode, modifiers)); } + public KeyBindingData(String keyStrokeString) { + this(parseKeyStrokeString(keyStrokeString)); + } + + private static KeyStroke parseKeyStrokeString(String keyStrokeString) { + KeyStroke keyStroke = KeyBindingUtils.parseKeyStroke(keyStrokeString); + if (keyStroke == null) { + throw new IllegalArgumentException("Invalid keystroke string: " + keyStrokeString); + } + return keyStroke; + } + public KeyBindingData(KeyStroke keyStroke, KeyBindingPrecedence precedence) { if (precedence == KeyBindingPrecedence.ReservedActionsLevel) { throw new IllegalArgumentException( 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 64a0748462..1749a6a6c9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java @@ -114,7 +114,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { @Override public void actionPerformed(final ActionEvent event) { // Build list of actions which are valid in current context - List list = getActionsForCurrentContext(event.getSource()); + List list = getActionsForCurrentOrDefaultContext(event.getSource()); // If menu active, disable all key bindings if (ignoreActionWhileMenuShowing()) { @@ -137,7 +137,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { Swing.runLater(() -> DockingWindowManager.showDialog(dialog)); } else if (list.size() == 1) { - final ExecutableKeyActionAdapter actionProxy = list.get(0); + ExecutableAction actionProxy = list.get(0); tool.setStatusInfo(""); actionProxy.execute(); } @@ -156,8 +156,9 @@ public class MultipleKeyAction extends DockingKeyBindingAction { return menuManager.getSelectedPath().length != 0; } - private List getValidContextActions(ActionContext localContext) { - List list = new ArrayList<>(); + private List getValidContextActions(ActionContext localContext, + ActionContext globalContext) { + List list = new ArrayList<>(); boolean hasLocalActionsForKeyBinding = false; // @@ -167,14 +168,15 @@ public class MultipleKeyAction extends DockingKeyBindingAction { if (actionData.isMyProvider(localContext)) { hasLocalActionsForKeyBinding = true; if (isValidAndEnabled(actionData, localContext)) { - list.add(new ExecutableKeyActionAdapter(actionData.action, localContext)); + list.add(new ExecutableAction(actionData.action, localContext)); } } } if (hasLocalActionsForKeyBinding) { - // We have locals, ignore the globals. This prevents global actions from processing - // the given keybinding when a local action exits, regardless of enablement. + // At this point, we have local actions that may or may not be enabled. Return here + // so that any component specific actions found below will not interfere with the + // provider's local actions return list; } @@ -191,7 +193,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { if (componentAction.isValidComponentContext(localContext)) { hasLocalActionsForKeyBinding = true; if (isValidAndEnabled(actionData, localContext)) { - list.add(new ExecutableKeyActionAdapter(actionData.action, localContext)); + list.add(new ExecutableAction(actionData.action, localContext)); } } } @@ -211,16 +213,28 @@ public class MultipleKeyAction extends DockingKeyBindingAction { // is a 'global' action. This allows more specific context to be used when // available if (isValidAndEnabled(actionData, localContext)) { - list.add(new ExecutableKeyActionAdapter(actionData.action, localContext)); + list.add(new ExecutableAction(actionData.action, localContext)); + } + else if (isValidAndEnabledGlobally(actionData, globalContext)) { + list.add(new ExecutableAction(actionData.action, globalContext)); } } } return list; } - private boolean isValidAndEnabled(ActionData actionData, ActionContext localContext) { + private boolean isValidAndEnabled(ActionData actionData, ActionContext context) { DockingActionIf a = actionData.action; - return a.isValidContext(localContext) && a.isEnabledForContext(localContext); + return a.isValidContext(context) && a.isEnabledForContext(context); + } + + private boolean isValidAndEnabledGlobally(ActionData actionData, ActionContext context) { + // the context may be null when we don't want global action such as when getting actions + // for a dialog + if (context == null) { + return false; + } + return actionData.supportsDefaultToolContext() && isValidAndEnabled(actionData, context); } @Override @@ -242,7 +256,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { */ public KeyBindingPrecedence geValidKeyBindingPrecedence(Component source) { - List validActions = getActionsForCurrentContext(source); + List validActions = getActionsForCurrentOrDefaultContext(source); if (validActions.isEmpty()) { return null; // a signal that no actions are valid for the current context } @@ -251,31 +265,36 @@ public class MultipleKeyAction extends DockingKeyBindingAction { return KeyBindingPrecedence.DefaultLevel; } - ExecutableKeyActionAdapter actionProxy = validActions.get(0); + ExecutableAction actionProxy = validActions.get(0); DockingActionIf action = actionProxy.getAction(); return action.getKeyBindingData().getKeyBindingPrecedence(); } - private List getActionsForCurrentContext(Object eventSource) { + private List getActionsForCurrentOrDefaultContext(Object eventSource) { DockingWindowManager dwm = tool.getWindowManager(); Window window = getWindow(dwm, eventSource); if (window instanceof DockingDialog) { - DockingDialog dockingDialog = (DockingDialog) window; - DialogComponentProvider provider = dockingDialog.getDialogComponent(); - if (provider == null) { - // this can happen if the dialog is closed during key event processing - return Collections.emptyList(); - } - ActionContext context = provider.getActionContext(null); - List validActions = getValidContextActions(context); - return validActions; + return getDialogActions(window); } ComponentProvider localProvider = getProvider(dwm, eventSource); ActionContext localContext = getLocalContext(localProvider); localContext.setSourceObject(eventSource); - List validActions = getValidContextActions(localContext); + ActionContext globalContext = tool.getDefaultToolContext(); + List validActions = getValidContextActions(localContext, globalContext); + return validActions; + } + + private List getDialogActions(Window window) { + DockingDialog dockingDialog = (DockingDialog) window; + DialogComponentProvider provider = dockingDialog.getDialogComponent(); + if (provider == null) { + // this can happen if the dialog is closed during key event processing + return Collections.emptyList(); + } + ActionContext context = provider.getActionContext(null); + List validActions = getValidContextActions(context, null); return validActions; } @@ -337,10 +356,15 @@ public class MultipleKeyAction extends DockingKeyBindingAction { return provider == otherProvider; } + boolean supportsDefaultToolContext() { + return action.supportsDefaultToolContext(); + } + @Override public String toString() { String providerString = provider == null ? "" : provider.toString() + " - "; return providerString + action; } + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java index 0d472c9d1f..e961eee4d8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java @@ -15,6 +15,7 @@ */ package docking.action.builder; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; @@ -23,6 +24,7 @@ import javax.swing.KeyStroke; import docking.*; import docking.action.*; +import docking.actions.KeyBindingUtils; import ghidra.util.HelpLocation; import ghidra.util.Msg; import resources.ResourceManager; @@ -50,7 +52,7 @@ import resources.ResourceManager; * the {@link #withContext(Class)} call. */ public abstract class AbstractActionBuilder> { - + private final Predicate ALWAYS_TRUE = e -> true; /** * Name for the {@code DockingAction} */ @@ -159,17 +161,22 @@ public abstract class AbstractActionBuilder enabledPredicate; + private Predicate enabledPredicate = ALWAYS_TRUE; /** * Predicate for determining if an action should be included on the pop-up menu */ - private Predicate popupPredicate; + private Predicate popupPredicate = ALWAYS_TRUE; /** * Predicate for determining if an action is applicable for a given context */ - private Predicate validContextPredicate; + private Predicate validContextPredicate = ALWAYS_TRUE; + + /** + * Set to true if the action supports using the default tool context if the local context is invalid + */ + private boolean supportsDefaultToolContext; /** * Builder constructor @@ -484,7 +491,7 @@ public abstract class AbstractActionBuilder predicate) { - enabledPredicate = predicate; + enabledPredicate = Objects.requireNonNull(predicate); return self(); } @@ -540,7 +547,7 @@ public abstract class AbstractActionBuilder predicate) { - popupPredicate = predicate; + popupPredicate = Objects.requireNonNull(predicate); return self(); } @@ -556,7 +563,23 @@ public abstract class AbstractActionBuilder predicate) { - validContextPredicate = predicate; + validContextPredicate = Objects.requireNonNull(predicate); + return self(); + } + + /** + * Sets whether the action will support using the default tool context if the focused provider's + * context is invalid. + *

+ * By default, actions only work on the current focused provider's context. Setting this + * to true will cause the action to be evaluated against the default tool context if the + * focused context is not valid for this action. + * + * @param b the new value + * @return this builder (for chaining) + */ + public B supportsDefaultToolContext(boolean b) { + supportsDefaultToolContext = b; return self(); } @@ -603,6 +626,10 @@ public abstract class AbstractActionBuilder> B2 withContext( Class newActionContextClass) { + if (actionContextClass != ActionContext.class) { + throw new IllegalStateException("Can't set the ActionContext type more than once"); + } + // To make this work, we need to return a builder whose ActionContext is AC2 and not AC // (which is what this builder is now) // @@ -627,6 +654,7 @@ public abstract class AbstractActionBuilder extends DockingAction { private ActionContext getActionContext() { DockingWindowManager manager = DockingWindowManager.getActiveInstance(); - ComponentProvider provider = manager.getActiveComponentProvider(); - ActionContext localContext = provider == null ? null : provider.getActionContext(null); - ActionContext actionContext = localContext == null ? new ActionContext() : localContext; - return actionContext; + + ActionContext context = manager.getActionContext(this); + + if (context == null) { + context = new ActionContext(); + } + return context; } protected List getStateActions() { 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 6888ce1d37..0856d8caf9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java @@ -113,11 +113,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene @Override public void actionPerformed(ActionEvent event) { DockingWindowManager.clearMouseOverHelp(); - ActionContext context = getActionContext(); - - if (!toolBarAction.isValidContext(context)) { - return; - } + ActionContext context = windowManager.getActionContext(toolBarAction); context.setSourceObject(event.getSource()); @@ -139,14 +135,6 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene return toolBarAction.getName(); } - private ActionContext getActionContext() { - ComponentProvider provider = getComponentProvider(); - ActionContext context = provider == null ? null : provider.getActionContext(null); - final ActionContext actionContext = - context == null ? new ActionContext(provider, null) : context; - return actionContext; - } - private ComponentProvider getComponentProvider() { DockingWindowManager manager = windowManager; if (manager == null) { diff --git a/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java b/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java index 2d5703fb6d..81a639860c 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java @@ -80,7 +80,7 @@ public class FakeDockingTool extends AbstractDockingTool { } @Override - public ActionContext getGlobalActionContext() { + public ActionContext getDefaultToolContext() { return null; } } 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 7f9a215d60..6ac65b5e65 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 @@ -1472,8 +1472,8 @@ public abstract class PluginTool extends AbstractDockingTool { } @Override - public ActionContext getGlobalActionContext() { - return winMgr.getGlobalActionContext(); + public ActionContext getDefaultToolContext() { + return winMgr.getDefaultToolContext(); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java index 94db73628a..2f0600f897 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java @@ -95,10 +95,13 @@ class DataComponent extends DataDB { } DataType pdt = parent.getBaseDataType(); if (pdt instanceof Composite) { - DataTypeComponent c = ((Composite) pdt).getComponent(indexInParent); - if (c == null) { + Composite composite = (Composite) pdt; + // if we are deleted, the parent may not have as many components as it used to, + // so if our index is bigger than the number of components, then we are deleted. + if (indexInParent >= composite.getNumComponents()) { return true; } + DataTypeComponent c = composite.getComponent(indexInParent); component = c; dataType = c.getDataType(); offset = component.getOffset();