From 6934c33aa665c9d206510b0de0f56ca5bd780673 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Wed, 5 Jan 2022 17:03:56 -0500 Subject: [PATCH] GP-1619 - Fixed bug that caused some MultiStateDockinActions to get called twice when clicked --- .../action/DebuggerAutoReadMemoryAction.java | 1 - .../action/DebuggerTrackLocationAction.java | 1 - .../DebuggerModelServiceProxyPlugin.java | 27 +- .../examples/graph/SampleGraphProvider.java | 2 +- .../actions/ConflictHandlerModesAction.java | 22 +- .../NextPreviousBookmarkAction.java | 5 +- .../navigation/NextPreviousMarkerAction.java | 314 ------------------ .../core/overview/OverviewColorPlugin.java | 9 +- .../ListingCodeComparisonPanel.java | 2 - .../plugin/core/disassembler/ClearTest.java | 17 +- .../navigation/NextPrevAddressPluginTest.java | 12 +- ...AbstractSymbolTreePluginExternalsTest.java | 16 +- .../framework/project/tool/CloseToolTest.java | 2 +- .../core/functiongraph/FGActionManager.java | 6 +- .../functioncalls/plugin/FcgProvider.java | 2 +- .../actions/MatchTableSelectionAction.java | 19 +- .../VTFunctionAssociationProvider.java | 9 +- .../action/builder/MultiActionBuilder.java | 23 -- .../builder/MultiStateActionBuilder.java | 29 +- .../menu/MultiActionDockingAction.java | 48 +-- .../docking/menu/MultiStateDockingAction.java | 131 ++------ .../MultipleActionDockingToolbarButton.java | 83 ++--- .../menu/NonToolbarMultiStateAction.java | 35 +- .../widgets/table/GTableFilterPanel.java | 5 +- .../ghidra/program/model/data/DataType.java | 4 +- 25 files changed, 197 insertions(+), 627 deletions(-) delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousMarkerAction.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerAutoReadMemoryAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerAutoReadMemoryAction.java index bb23cef13f..61e3a3709c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerAutoReadMemoryAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerAutoReadMemoryAction.java @@ -24,7 +24,6 @@ public interface DebuggerAutoReadMemoryAction extends AutoReadMemoryAction { static MultiStateActionBuilder builder(Plugin owner) { MultiStateActionBuilder builder = AutoReadMemoryAction.builder(owner); builder.toolBarGroup(NAME); - builder.performActionOnButtonClick(true); for (AutoReadMemorySpec spec : AutoReadMemorySpec.allSpecs().values()) { builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java index 1ebde6c315..845db0061d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationAction.java @@ -25,7 +25,6 @@ public interface DebuggerTrackLocationAction extends TrackLocationAction { static MultiStateActionBuilder builder(Plugin owner) { MultiStateActionBuilder builder = TrackLocationAction.builder(owner); builder.toolBarGroup(owner.getName()); - builder.performActionOnButtonClick(true); for (LocationTrackingSpec spec : LocationTrackingSpec.allSpecs().values()) { builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index 29678510b0..b0885b1a87 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -63,22 +63,14 @@ import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -@PluginInfo( - shortDescription = "Debugger models manager service (proxy to front-end)", - description = "Manage debug sessions, connections, and trace recording", - category = PluginCategoryNames.DEBUGGER, - packageName = DebuggerPluginPackage.NAME, - status = PluginStatus.RELEASED, - eventsConsumed = { - ProgramActivatedPluginEvent.class, - ProgramClosedPluginEvent.class, - }, - servicesRequired = { - DebuggerTraceManagerService.class, - }, - servicesProvided = { - DebuggerModelService.class, - }) +@PluginInfo(shortDescription = "Debugger models manager service (proxy to front-end)", description = "Manage debug sessions, connections, and trace recording", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsConsumed = { + ProgramActivatedPluginEvent.class, + ProgramClosedPluginEvent.class, +}, servicesRequired = { + DebuggerTraceManagerService.class, +}, servicesProvided = { + DebuggerModelService.class, +}) public class DebuggerModelServiceProxyPlugin extends Plugin implements DebuggerModelServiceInternal { @@ -250,7 +242,6 @@ public class DebuggerModelServiceProxyPlugin extends Plugin .enabledWhen(ctx -> currentProgram != null) .onAction(this::debugProgramButtonActivated) .onActionStateChanged(this::debugProgramStateActivated) - .performActionOnButtonClick(true) .addState(DUMMY_LAUNCH_STATE) .buildAndInstall(tool); actionDisconnectAll = DisconnectAllAction.builder(this, delegate) @@ -383,7 +374,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin List offers = program == null ? List.of() : getProgramLaunchOffers(program).collect(Collectors.toList()); List> states = offers.stream() - .map(o -> new ActionState(o.getButtonTitle(), + .map(o -> new ActionState<>(o.getButtonTitle(), o.getIcon(), o)) .collect(Collectors.toList()); if (!states.isEmpty()) { diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java index 84afb81ae6..520feae901 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java @@ -348,7 +348,7 @@ public class SampleGraphProvider extends ComponentProviderAdapter { new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { @Override - protected void doActionPerformed(ActionContext context) { + public void actionPerformed(ActionContext context) { // this callback is when the user clicks the button LayoutProvider currentUserData = getCurrentUserData(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ConflictHandlerModesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ConflictHandlerModesAction.java index f44ad4c58e..872813e847 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ConflictHandlerModesAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/ConflictHandlerModesAction.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,17 +15,16 @@ */ package ghidra.app.plugin.core.datamgr.actions; +import javax.swing.Icon; + +import docking.menu.ActionState; +import docking.menu.MultiStateDockingAction; +import docking.widgets.EventTrigger; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.program.model.data.DataTypeConflictHandler; import ghidra.program.model.data.DataTypeConflictHandler.ConflictResolutionPolicy; import ghidra.util.HelpLocation; - -import javax.swing.Icon; - import resources.ResourceManager; -import docking.menu.ActionState; -import docking.menu.MultiStateDockingAction; -import docking.widgets.EventTrigger; public class ConflictHandlerModesAction extends MultiStateDockingAction { @@ -43,33 +41,31 @@ public class ConflictHandlerModesAction extends new HelpLocation(plugin.getName(), "conflict_mode"); setHelpLocation(conflictModesHelpLocation); - setPerformActionOnPrimaryButtonClick(false); - Icon renameAndAddIcon = ResourceManager.loadImage("images/conflictRename.png"); Icon useExistingIcon = ResourceManager.loadImage("images/conflictKeep.png"); Icon replaceExistingIcon = ResourceManager.loadImage("images/conflictReplace.png"); Icon replaceDefaultIcon = ResourceManager.loadImage("images/conflictReplaceOrRename.png"); ActionState renameAndAddState = - new ActionState( + new ActionState<>( "Rename New or Moved Data Type", renameAndAddIcon, DataTypeConflictHandler.ConflictResolutionPolicy.RENAME_AND_ADD); renameAndAddState.setHelpLocation(conflictModesHelpLocation); ActionState useExistingState = - new ActionState( + new ActionState<>( "Use Existing Data Type", useExistingIcon, DataTypeConflictHandler.ConflictResolutionPolicy.USE_EXISTING); useExistingState.setHelpLocation(conflictModesHelpLocation); ActionState replaceExistingState = - new ActionState( + new ActionState<>( "Replace Existing Data Type", replaceExistingIcon, DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EXISTING); replaceExistingState.setHelpLocation(conflictModesHelpLocation); ActionState replaceDefaultState = - new ActionState( + new ActionState<>( "Replace Empty Structures else Rename", replaceDefaultIcon, DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD); 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 871443b3e3..7c2eb6a04c 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 @@ -15,9 +15,8 @@ */ package ghidra.app.plugin.core.navigation; -import java.util.Iterator; - import java.awt.event.*; +import java.util.Iterator; import javax.swing.ImageIcon; import javax.swing.KeyStroke; @@ -103,7 +102,7 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction } @Override - protected void doActionPerformed(ActionContext context) { + public void actionPerformed(ActionContext context) { if (context instanceof NavigatableActionContext) { gotoNextPrevious((NavigatableActionContext) context, this.getCurrentUserData()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousMarkerAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousMarkerAction.java deleted file mode 100644 index e839e98ccb..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPreviousMarkerAction.java +++ /dev/null @@ -1,314 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.navigation; - -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; - -import javax.swing.ImageIcon; -import javax.swing.KeyStroke; - -import docking.ActionContext; -import docking.action.*; -import docking.menu.ActionState; -import docking.menu.MultiStateDockingAction; -import docking.tool.ToolConstants; -import docking.widgets.EventTrigger; -import ghidra.app.context.ListingActionContext; -import ghidra.app.nav.Navigatable; -import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; -import ghidra.app.services.GoToService; -import ghidra.app.services.MarkerSet; -import ghidra.app.util.HelpTopics; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.Address; -import ghidra.program.model.listing.BookmarkType; -import ghidra.util.HelpLocation; -import resources.ResourceManager; - -public class NextPreviousMarkerAction extends MultiStateDockingAction { - //private static final ImageIcon new ImageIcon(); = null; - private boolean isForward = true; - private PluginTool tool; - - private static ImageIcon markerIcon = ResourceManager.loadImage("images/M.gif"); - private static ImageIcon markerAnalysisBookmarkIcon = ResourceManager.loadImage("images/M.gif"); - private static ImageIcon markerConflictingChangesIcon = - ResourceManager.loadImage("images/edit-delete.png"); - private static ImageIcon markerLatestVersionChangesIcon = - ResourceManager.loadImage("images/information.png"); - private static ImageIcon markerNotCheckedInChangesIcon = - ResourceManager.loadImage("images/notes.gif"); - private static ImageIcon markerUnSavedChangesIcon = - ResourceManager.loadImage("images/warning.png"); - private static ImageIcon markerCursorIcon = ResourceManager.loadImage("images/unknown.gif"); - private static ImageIcon markerErrorBookmarkIcon = - ResourceManager.loadImage("images/unknown.gif"); - private static ImageIcon markerHighlightIcon = ResourceManager.loadImage("images/unknown.gif"); - private static ImageIcon markerInfoBookmarkIcon = - ResourceManager.loadImage("images/unknown.gif"); - private static ImageIcon markerNoteBookmarkIcon = - ResourceManager.loadImage("images/unknown.gif"); - private static ImageIcon markerSelectionIcon = ResourceManager.loadImage("images/unknown.gif"); - private static ImageIcon markerWarningBookmarkIcon = - ResourceManager.loadImage("images/unknown.gif"); - - public NextPreviousMarkerAction(PluginTool tool, String owner, String subGroup) { - super("Next Marker", owner); - this.tool = tool; - - ToolBarData toolBarData = - new ToolBarData(markerIcon, ToolConstants.TOOLBAR_GROUP_FOUR); - toolBarData.setToolBarSubGroup(subGroup); - setToolBarData(toolBarData); - - MenuData menuData = - new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, getMenuName() }, markerIcon, - ToolConstants.MENU_GROUP_NEXT_CODE_UNIT_NAV); - menuData.setMenuSubGroup(subGroup); - setMenuBarData(menuData); - - setKeyBindingData(new KeyBindingData(getKeyStroke())); - addToWindowWhen(CodeViewerActionContext.class); - - setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, getName())); - setDescription("Set marker options"); - addToWindowWhen(CodeViewerActionContext.class); - - ActionState allMarkers = - new ActionState("All Types", markerIcon, "All Types"); - ActionState analysis = - new ActionState("Analysis Marker", markerAnalysisBookmarkIcon, - BookmarkType.ANALYSIS); - ActionState conflictingChanges = - new ActionState("Conflicting Changes", markerAnalysisBookmarkIcon, - "Conflicting Changes"); - ActionState latestVersionChanges = - new ActionState("Latest Version Changes", markerAnalysisBookmarkIcon, - "Latest Version Changes"); - ActionState notCheckedInChanges = - new ActionState("Not Checked In Changes", markerAnalysisBookmarkIcon, - "Not Checked In Changes"); - ActionState unsavedChanges = - new ActionState("Unsaved Changes", markerIcon, "Unsaved Changes"); - ActionState cursor = new ActionState("Cursor", markerIcon, "Cursor"); - ActionState error = - new ActionState("Error Marker", markerIcon, BookmarkType.ERROR); - ActionState highlight = - new ActionState("Highlight", markerIcon, "Highlight"); - ActionState info = - new ActionState("Info Marker", markerIcon, BookmarkType.INFO); - ActionState note = - new ActionState("Note Marker", markerIcon, BookmarkType.NOTE); - ActionState selection = - new ActionState("Selection", markerIcon, "Selection"); - ActionState warning = - new ActionState("Warning Marker", markerIcon, BookmarkType.WARNING); - ActionState custom = - new ActionState("Custom Marker", markerIcon, "Custom Marker"); - - addActionState(allMarkers); - addActionState(analysis); - addActionState(conflictingChanges); - addActionState(latestVersionChanges); - addActionState(notCheckedInChanges); - addActionState(unsavedChanges); - addActionState(cursor); - addActionState(error); - addActionState(highlight); - addActionState(info); - addActionState(note); - addActionState(selection); - addActionState(warning); - addActionState(custom); - - setCurrentActionState(allMarkers); // default - } - - @Override - public void setMenuBarData(MenuData newMenuData) { - // - // When we are in the menu we will display our default icon, which is the marker icon. - // - superSetMenuBarData(newMenuData); - } - - @Override - protected void doActionPerformed(ActionContext context) { - gotoNextPrevious((ListingActionContext) context.getContextObject(), - this.getCurrentUserData()); - } - - @Override - public void actionStateChanged(ActionState newActionState, EventTrigger trigger) { - // nothing - } - - // Find the beginning of the next instruction range - /*private Address getNextAddress(Program program, Address address, String markerType) { - MarkerSet nextMarker = getNextMarker(program, address, true, markerType); - return nextMarker == null ? null : nextMarker.getAddressSet().getMinAddress(); - } - */ - /*private Address getPreviousAddress(Program program, Address address, String markerType) { - MarkerManager markerManager = program.getMarkerManager(); - Iterator markerIterator = markerManager.getMarkersIterator(address, false); - if (isMarkerAddressEqualToCurrent(markerIterator.next(), address)) { - - } - MarkerSet nextMarker = getNextMarker(program, address, false, markerType); - return nextMarker == null ? null : nextMarker.getAddressSet().getMinAddress(); - } - - private MarkerSet getNextMarker(Program program, Address address, boolean forward, - String markerType) { - MarkerManager markerManager = program.getMarkerManager(); - Iterator markerIterator = markerManager.getMarkersIterator(address, forward); - while (markerIterator.hasNext()) { - MarkerSet nextMarker = markerIterator.next(); - Address nextAddress = nextMarker.getAddressSet().getMinAddress(); - if (nextAddress.isExternalAddress()) { - continue; - } - - if (markerType.equals(MarkerType.ALL_TYPES) && !nextAddress.equals(address)) { - return nextMarker; - } - else if (markerType.equals("Custom")) { - if (!nextMarker.getTypeString().equals(MarkerType.ANALYSIS) && - !nextMarker.getTypeString().equals(MarkerType.INFO) && - !nextMarker.getTypeString().equals(MarkerType.NOTE) && - !nextMarker.getTypeString().equals(MarkerType.WARNING) && - !nextMarker.getTypeString().equals(MarkerType.ERROR) && - !nextAddress.equals(address)) { - return nextMarker; - } - - } - else if (nextMarker.getTypeString().equals(markerType) && !nextAddress.equals(address)) { - return nextMarker; - } - } - - if (!markerIterator.hasNext()) { - return null; - } - return markerIterator.next(); - } - */ - @SuppressWarnings("unused") - private boolean isMarkerAddressEqualToCurrent(MarkerSet marker, Address address) { - if (marker == null) { - return false; - } - return !address.equals(marker.getMinAddress()); - } - - private void gotoAddress(GoToService service, Navigatable navigatable, Address address) { - service.goTo(navigatable, address); - } - -//================================================================================================== -// AbstractNextPreviousAction Methods -//================================================================================================== - void gotoNextPrevious(final ListingActionContext context, final String markerType) { - /*final Address address = - isForward ? getNextAddress(context.getProgram(), context.getAddress(), markerType) - : getPreviousAddress(context.getProgram(), context.getAddress(), markerType); - *//* - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - gotoAddress(context, address); - } - });*/ - } - - private void gotoAddress(ListingActionContext listingActionContext, Address address) { - if (address == null) { - tool.setStatusInfo("Unable to locate another " + getNavigationTypeName() + - " past the current range, in the current direction."); - return; - } - tool.clearStatusInfo(); - GoToService service = tool.getService(GoToService.class); - if (service != null) { - Navigatable navigatable = listingActionContext.getNavigatable(); - gotoAddress(service, navigatable, address); - } - - } - - public void setDirection(boolean isForward) { - this.isForward = isForward; - getMenuBarData().setMenuItemName(getMenuName()); - setDescription(getDescription()); - } - - private String getMenuName() { - String prefix = isForward ? "Next " : "Previous "; - return prefix + getNavigationTypeName(); - } - - private String getNavigationTypeName() { - return "Marker"; - } - - private KeyStroke getKeyStroke() { - return KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_DOWN_MASK | - InputEvent.ALT_DOWN_MASK); - } - -//================================================================================================== -// 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); - } - - @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); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorPlugin.java index 9e840d0248..7410fbaedc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorPlugin.java @@ -128,15 +128,8 @@ public class OverviewColorPlugin extends ProgramPlugin { actionMap.put(overviewColorService, new OverviewToggleAction(getName(), overviewColorService)); } - multiAction = new MultiActionDockingAction("Overview", getName()) { - @Override - public void actionPerformed(ActionContext context) { - // do nothing - the following setPerformActionOnButtonClick(false) will ensure - // this never gets called. - } - }; - multiAction.setPerformActionOnButtonClick(false); + multiAction = new MultiActionDockingAction("Overview", getName()); multiAction.setActions(new ArrayList(actionMap.values())); multiAction.setToolBarData( new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png"))); 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 532db7c46b..73e8af14d3 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 @@ -668,8 +668,6 @@ public class ListingCodeComparisonPanel setHelpLocation(helpLocation); setDescription("Set Navigate Next/Previous Area Marker options"); - setPerformActionOnPrimaryButtonClick(false); - ActionState allAreaMarkers = new ActionState<>(ALL_AREA_MARKERS, bothIcon, ALL_AREA_MARKERS); allAreaMarkers.setHelpLocation(helpLocation); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/ClearTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/ClearTest.java index 86ab2bb8c1..fdbd67a046 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/ClearTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/ClearTest.java @@ -177,7 +177,8 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest { public void testClearActionEnablement() throws Exception { closeProgram(); - assertTrue(!clearAction.isEnabledForContext(new ActionContext())); + ActionContext context = cb.getProvider().getActionContext(null); + assertFalse(clearAction.isEnabledForContext(context)); showTool(tool); loadProgram("notepad"); @@ -185,10 +186,12 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest { waitForSwing(); assertTrue(cb.goToField(addr("0x10026f0"), "Address", 0, 0)); - assertTrue(clearAction.isEnabled()); + context = cb.getProvider().getActionContext(null); + assertTrue(clearAction.isEnabledForContext(context)); closeProgram(); - assertTrue(!clearAction.isEnabledForContext(new ActionContext())); + context = cb.getProvider().getActionContext(null); + assertFalse(clearAction.isEnabledForContext(context)); } @Test @@ -453,7 +456,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest { Symbol[] symbols = program.getSymbolTable().getSymbols(addr("0x01001010")); assertEquals(1, symbols.length); - assertTrue(!symbols[0].isDynamic()); + assertFalse(symbols[0].isDynamic()); int id = program.startTransaction("Anchor"); symbols[0].setPinned(true); program.endTransaction(id, true); @@ -467,7 +470,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest { symbols = program.getSymbolTable().getSymbols(addr("0x01001010")); assertEquals(1, symbols.length); - assertTrue(!symbols[0].isDynamic()); + assertFalse(symbols[0].isDynamic()); } @Test @@ -639,7 +642,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(l.getNumInstructions() > 0); assertTrue(l.getNumDefinedData() > 0); - assertTrue(!program.getListing().getFunctions(true).hasNext()); + assertFalse(program.getListing().getFunctions(true).hasNext()); assertTrue(program.getSymbolTable().getNumSymbols() > 0); undo(program); @@ -674,7 +677,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest { okOnClearDialog(); - assertTrue(!context.hasValueOverRange(ax, BigInteger.valueOf(5), + assertFalse(context.hasValueOverRange(ax, BigInteger.valueOf(5), new AddressSet(addr("0x10022cc")))); undo(program); assertTrue(context.hasValueOverRange(ax, BigInteger.valueOf(5), 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 a702c859b5..c9a187ff91 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 @@ -304,13 +304,6 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe // Private Methods //================================================================================================== - private ComponentProvider showDecompiler() { - ComponentProvider cp = tool.getComponentProvider("Decompiler"); - tool.showComponentProvider(cp, true); - cp.requestFocus(); - return cp; - } - private void assertCurrentAddress(Address expected) { assertEquals(expected, currentAddress()); } @@ -415,8 +408,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe // // clear the popup - Object mouseAdapter = getInstanceField("popupListener", button); - setInstanceField("popupMenu", mouseAdapter, null); + setInstanceField("popupMenu", button, null); // trigger the popup Shape popupTriggerArea = (Shape) TestUtils.getInstanceField("popupContext", button); @@ -426,7 +418,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe clickMouse(button, MouseEvent.BUTTON1, x, y, 1, 0); // get the popup - JPopupMenu menu = (JPopupMenu) getInstanceField("popupMenu", mouseAdapter); + JPopupMenu menu = (JPopupMenu) getInstanceField("popupMenu", button); assertNotNull(menu); // Note: calling clickMouse() seems to work for now. If this is not consistent, then diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/AbstractSymbolTreePluginExternalsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/AbstractSymbolTreePluginExternalsTest.java index f5b6079281..da92c266bb 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/AbstractSymbolTreePluginExternalsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/AbstractSymbolTreePluginExternalsTest.java @@ -43,18 +43,17 @@ import ghidra.framework.main.datatree.DataTree; import ghidra.framework.main.datatree.ProjectDataTreePanel; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; -import ghidra.framework.store.LockException; import ghidra.program.database.symbol.LibrarySymbol; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; -import ghidra.program.model.mem.MemoryConflictException; import ghidra.program.model.symbol.*; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.SystemUtilities; -import ghidra.util.exception.*; +import ghidra.util.exception.InvalidInputException; import ghidra.util.task.TaskMonitor; /** @@ -158,7 +157,7 @@ public abstract class AbstractSymbolTreePluginExternalsTest protected ExternalLocation setupExternalLocation(String library, String label, Address address, SourceType sourceType, boolean isFunction) - throws InvalidInputException, DuplicateNameException { + throws Exception { boolean success = false; int transactionID = program.startTransaction("Setting Up External Location " + library + "::" + label); @@ -181,12 +180,12 @@ public abstract class AbstractSymbolTreePluginExternalsTest } protected ExternalLocation setupExternalLocation(String library, String label, Address address, - SourceType sourceType) throws InvalidInputException, DuplicateNameException { + SourceType sourceType) throws Exception { return setupExternalLocation(library, label, address, sourceType, false); } protected ExternalLocation setupExternalFunction(String library, String label, Address address, - SourceType sourceType) throws InvalidInputException, DuplicateNameException { + SourceType sourceType) throws Exception { return setupExternalLocation(library, label, address, sourceType, true); } @@ -448,8 +447,7 @@ public abstract class AbstractSymbolTreePluginExternalsTest } protected void addOverlayBlock(String name, String startAddress, long length) - throws LockException, DuplicateNameException, MemoryConflictException, - AddressOverflowException, CancelledException { + throws Exception { int transactionID = program.startTransaction("Add Overlay Block to test"); Address address = program.getAddressFactory().getAddress(startAddress); Memory memory = program.getMemory(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/tool/CloseToolTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/tool/CloseToolTest.java index 884818e2ba..534ffb8850 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/tool/CloseToolTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/tool/CloseToolTest.java @@ -87,7 +87,7 @@ public class CloseToolTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(closeOthersAction); ProgramActionContext context = new ProgramActionContext(null, program1); assertEquals(true, closeOthersAction.isEnabledForContext(context)); - performAction(closeOthersAction, true); + performAction(closeOthersAction, context, true); allOpenPrograms = pm.getAllOpenPrograms(); assertEquals(1, allOpenPrograms.length); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java index 3a6b533d09..9aed1f44af 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java @@ -859,10 +859,10 @@ class FGActionManager { HelpLocation layoutHelpLocation = new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); - layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName(), true) { + layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) { @Override - protected void doActionPerformed(ActionContext context) { + public void actionPerformed(ActionContext context) { // this callback is when the user clicks the button FGLayoutProvider currentUserData = getCurrentUserData(); changeLayout(currentUserData); @@ -994,7 +994,6 @@ class FGActionManager { }; vertexHoverModeAction.setGroup(group); vertexHoverModeAction.setHelpLocation(pathHelpLocation); - vertexHoverModeAction.setPerformActionOnPrimaryButtonClick(false); vertexHoverModeAction.addActionState(offState); vertexHoverModeAction.addActionState(pathsForwardScopedFlow); @@ -1063,7 +1062,6 @@ class FGActionManager { }; vertexFocusModeAction.setGroup(group); vertexFocusModeAction.setHelpLocation(pathHelpLocation); - vertexFocusModeAction.setPerformActionOnPrimaryButtonClick(false); vertexFocusModeAction.addActionState(offState); vertexFocusModeAction.addActionState(pathsForwardScopedFlow); diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java index 13b23b6bcb..24a4624223 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java @@ -526,7 +526,7 @@ public class FcgProvider RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { @Override - protected void doActionPerformed(ActionContext context) { + public void actionPerformed(ActionContext context) { // this callback is when the user clicks the button LayoutProvider currentUserData = getCurrentUserData(); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/MatchTableSelectionAction.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/MatchTableSelectionAction.java index 98cc77cb2b..14749bd31b 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/MatchTableSelectionAction.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/MatchTableSelectionAction.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. @@ -17,19 +16,20 @@ package ghidra.feature.vt.gui.actions; import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*; -import ghidra.feature.vt.gui.plugin.VTPlugin; -import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableProvider; -import ghidra.util.HelpLocation; import javax.swing.Icon; -import resources.ResourceManager; import docking.action.ToolBarData; import docking.menu.ActionState; import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; +import ghidra.feature.vt.gui.plugin.VTPlugin; +import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableProvider; +import ghidra.util.HelpLocation; +import resources.ResourceManager; -public class MatchTableSelectionAction extends MultiStateDockingAction { +public class MatchTableSelectionAction + extends MultiStateDockingAction { private static final String MENU_GROUP = VTPlugin.VT_SETTINGS_MENU_GROUP; @@ -44,7 +44,6 @@ public class MatchTableSelectionAction extends MultiStateDockingAction trackSelectedIndexActionState = - new ActionState("Track Selected Index", + new ActionState<>("Track Selected Index", trackRowIndexSelectionIcon, MAINTAIN_SELECTED_ROW_INDEX); trackSelectedIndexActionState.setHelpLocation(helpLocation); ActionState trackMatchSelectionActionState = - new ActionState("Track Selected Match", + new ActionState<>("Track Selected Match", trackMatchSelectionIcon, MAINTAIN_SELECTED_ROW_VALUE); trackMatchSelectionActionState.setHelpLocation(helpLocation); ActionState noSelectionTrackingActionState = - new ActionState("No Selection Tracking", + new ActionState<>("No Selection Tracking", noSelectionTrackingIcon, NO_SELECTION_TRACKING); noSelectionTrackingActionState.setHelpLocation(helpLocation); 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 4f144d81e0..a05c432e0f 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 @@ -15,12 +15,8 @@ */ package ghidra.feature.vt.gui.provider.functionassociation; -import static ghidra.feature.vt.api.impl.VTChangeManager.DOCR_VT_ASSOCIATION_STATUS_CHANGED; -import static ghidra.feature.vt.api.impl.VTChangeManager.DOCR_VT_MATCH_ADDED; -import static ghidra.feature.vt.api.impl.VTChangeManager.DOCR_VT_MATCH_DELETED; -import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.SHOW_ALL; -import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.SHOW_UNACCEPTED; -import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.SHOW_UNMATCHED; +import static ghidra.feature.vt.api.impl.VTChangeManager.*; +import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.*; import java.awt.*; import java.awt.event.*; @@ -156,6 +152,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter destinationFunctionsModel.setFilterSettings(filterSettings); } }; + filterAction.setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Functions_Filter")); Icon allFunctionsIcon = ResourceManager.loadImage("images/function.png"); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiActionBuilder.java b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiActionBuilder.java index bf1f2be1c7..06eefc1153 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiActionBuilder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiActionBuilder.java @@ -31,10 +31,6 @@ public class MultiActionBuilder * List of actions for the the MultActionDockingAction */ private List actionList = Collections.emptyList(); - /** - * determines if the the main action is invokable - */ - private boolean performActionOnButtonClick = true; /** * Builder constructor @@ -61,7 +57,6 @@ public class MultiActionBuilder }; decorateAction(action); action.setActions(actionList); - action.setPerformActionOnButtonClick(performActionOnButtonClick); return action; } @@ -81,26 +76,8 @@ public class MultiActionBuilder return self(); } - /** - * Configure whether to perform actions on a button click. - * See {@link MultiActionDockingAction#setPerformActionOnButtonClick(boolean)} - * - * @param b true if the main action is invokable - * @return this MultiActionDockingActionBuilder (for chaining) - */ - public MultiActionBuilder performActionOnButtonClick(boolean b) { - this.performActionOnButtonClick = b; - return self(); - } - @Override protected void validate() { - // if the MultiAction performs an action when the main button is presseed, make sure that - // an action callback has been defined in before building (which is what super validate - // does). Otherwise, don't force the client to define an action callback if it won't be used. - if (performActionOnButtonClick) { - super.validate(); - } if (actionList == null) { throw new IllegalStateException("No ActionList has been set"); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java index 86d8f9806c..86be342b54 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java @@ -22,7 +22,8 @@ import java.util.function.BiConsumer; import javax.swing.Icon; import docking.ActionContext; -import docking.menu.*; +import docking.menu.ActionState; +import docking.menu.MultiStateDockingAction; import docking.widgets.EventTrigger; /** @@ -35,7 +36,6 @@ public class MultiStateActionBuilder extends private BiConsumer, EventTrigger> actionStateChangedCallback; private boolean useCheckboxForIcons; - private boolean performActionOnButtonClick = false; private List> states = new ArrayList<>(); @@ -68,18 +68,6 @@ public class MultiStateActionBuilder extends return self(); } - /** - * Configure whether to perform actions on a button click. - * See {@link MultiActionDockingAction#setPerformActionOnButtonClick(boolean)} - * - * @param b true if the main action is invokable - * @return this MultiActionDockingActionBuilder (for chaining) - */ - public MultiStateActionBuilder performActionOnButtonClick(boolean b) { - this.performActionOnButtonClick = b; - return self(); - } - /** * Overrides the default icons for actions shown in popup menu of the multi-state action. By * default, the popup menu items will use the icons as provided by the {@link ActionState}. @@ -103,7 +91,7 @@ public class MultiStateActionBuilder extends * @return this MultiActionDockingActionBuilder (for chaining) */ public MultiStateActionBuilder addState(String displayName, Icon icon, T userData) { - states.add(new ActionState(displayName, icon, userData)); + states.add(new ActionState<>(displayName, icon, userData)); return self(); } @@ -133,7 +121,7 @@ public class MultiStateActionBuilder extends public MultiStateDockingAction build() { validate(); MultiStateDockingAction action = - new MultiStateDockingAction<>(name, owner, isToolbarAction()) { + new MultiStateDockingAction<>(name, owner) { @Override public void actionStateChanged(ActionState newActionState, @@ -142,10 +130,13 @@ public class MultiStateActionBuilder extends } @Override - protected void doActionPerformed(ActionContext context) { + public void actionPerformed(ActionContext context) { if (actionCallback != null) { actionCallback.accept(context); } + else { + super.actionPerformed(context); + } } }; @@ -154,16 +145,12 @@ public class MultiStateActionBuilder extends } decorateAction(action); - action.setPerformActionOnPrimaryButtonClick(performActionOnButtonClick); action.setUseCheckboxForIcons(useCheckboxForIcons); return action; } @Override protected void validate() { - if (performActionOnButtonClick) { - super.validate(); // require an action callback has been defined - } if (actionStateChangedCallback == null) { throw new IllegalStateException( "Can't build a MultiStateDockingAction without an action state changed callback"); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiActionDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiActionDockingAction.java index 6b30ae56d4..0af60fc6d4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiActionDockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiActionDockingAction.java @@ -22,13 +22,14 @@ import javax.swing.JButton; import docking.ActionContext; import docking.action.*; +import ghidra.util.Swing; /** * A class that supports multiple sub-actions, as well as a primary action. This is useful for * actions that perform navigation operations. *

- * Clients may add actions to this class with the intention that they will be accessible - * to the user via a GUI; for example, from a popup menu. + * Clients may add actions to this class with the intention that they will be accessible to the + * user via a GUI; for example, from a popup menu. *

* Actions added must have menu bar data set. * @@ -36,18 +37,20 @@ import docking.action.*; * the user to execute. * *

- * If the user executes this action directly, then - * {@link #actionPerformed(ActionContext)} will be called. Otherwise, the - * {@link DockingAction#actionPerformed(ActionContext)} method of the sub-action - * that was executed will be called. + * If the user executes this action directly (by clicking the non-popup section of the button), + * then {@link #actionPerformed(ActionContext)} will be called. By default, when the button is + * clicked, the popup menu is shown. To change this behavior, override + * {@link #actionPerformed(ActionContext)}. If an item of the popup menu is clicked, then the + * {@link DockingAction#actionPerformed(ActionContext)} method of the sub-action that was executed + * will be called. * * @see MultiStateDockingAction */ -public abstract class MultiActionDockingAction extends DockingAction +public class MultiActionDockingAction extends DockingAction implements MultiActionDockingActionIf { private List actionList = Collections.emptyList(); - private boolean performActionOnButtonClick = true; + private MultipleActionDockingToolbarButton multipleButton; public MultiActionDockingAction(String name, String owner) { super(name, owner); @@ -67,24 +70,23 @@ public abstract class MultiActionDockingAction extends DockingAction return actionList; } + /** + * This method is called when the user clicks the button when this action is used as part of + * the default {@link DockingAction} framework. + * + * This is the callback to be overridden when the child wishes to respond to user button + * presses that are on the button and not the drop-down. The default behavior is to show the + * popup menu when the button is clicked. + */ @Override - public JButton doCreateButton() { - MultipleActionDockingToolbarButton button = new MultipleActionDockingToolbarButton(this); - button.setPerformActionOnButtonClick(performActionOnButtonClick); - return button; + public void actionPerformed(ActionContext context) { + Swing.runLater(() -> multipleButton.showPopup()); } - /** - * By default a click on this action will trigger actionPerformed() to be called. - * You can call this method to disable that feature. When called with false, this - * method will effectively let the user click anywhere on the button or its drop-down arrow - * to show the popup menu. During normal operation, the user can only show the popup by - * clicking the drop-down arrow. - * @param performActionOnButtonClick if true, pressing the button calls actionPerformed; - * otherwise it pops up the menu. - */ - public void setPerformActionOnButtonClick(boolean performActionOnButtonClick) { - this.performActionOnButtonClick = performActionOnButtonClick; + @Override + public JButton doCreateButton() { + multipleButton = new MultipleActionDockingToolbarButton(this); + return multipleButton; } public static DockingActionIf createSeparator() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java index 4391a096b4..553efe18d0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java @@ -15,7 +15,6 @@ */ package docking.menu; -import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; @@ -23,25 +22,24 @@ import javax.swing.Icon; import javax.swing.JButton; import docking.ActionContext; -import docking.DockingWindowManager; import docking.action.*; import docking.widgets.EventTrigger; import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.exception.AssertException; import resources.icons.EmptyIcon; /** * An action that can be in one of multiple states. The button of this action has a - * drop-down icon that allows users to change the state of the button. Also, by default, as - * the user presses the button, it will execute the action corresponding to the current - * state. - * - *

Warning: if you use this action in a toolbar, then be sure to call the - * {@link #MultiStateDockingAction(String, String, boolean) correct constructor}. If you call - * another constructor, or pass false for this boolean above, your - * {@link #doActionPerformed(ActionContext)} method will get called twice. + * drop-down icon that allows users to change the state of the button. As the user changes the + * state of this action, {@link #actionStateChanged(ActionState, EventTrigger)} will be called. + * Clients may also use the button of this action to respond to button presses by overriding + * {@link #actionPerformed(ActionContext)}. * + *

This action is intended primarily for use as toolbar actions. Alternatively, some clients + * use this action to add a button to custom widgets. In the custom usage case, clients should use + * {@link NonToolbarMultiStateAction}. + * * @param the type of the user data * @see MultiActionDockingAction */ @@ -54,16 +52,9 @@ public abstract class MultiStateDockingAction extends DockingAction { private MultiActionDockingActionIf multiActionGenerator; private MultipleActionDockingToolbarButton multipleButton; - private boolean performActionOnPrimaryButtonClick = true; private Icon defaultIcon; private boolean useCheckboxForIcons; - // A listener that will get called when the button (not the popup) is clicked. Toolbar - // actions do not use this listener. - private ActionListener clickListener = e -> { - // stub for toolbar actions - }; - /** * Call this constructor with this action will not be added to a toolbar * @@ -72,7 +63,11 @@ public abstract class MultiStateDockingAction extends DockingAction { * @see #MultiStateDockingAction(String, String, boolean) */ public MultiStateDockingAction(String name, String owner) { - this(name, owner, false); + super(name, owner); + multiActionGenerator = context -> getStateActions(); + + // set this here so we don't have to check for null elsewhere + super.setToolBarData(new ToolBarData(null)); } /** @@ -82,50 +77,31 @@ public abstract class MultiStateDockingAction extends DockingAction { * @param name the action name * @param owner the owner * @param isToolbarAction true if this action is a toolbar action + * @deprecated use {@link #MultiStateDockingAction(String, String)} */ + @Deprecated(forRemoval = true, since = "10.2") protected MultiStateDockingAction(String name, String owner, boolean isToolbarAction) { - super(name, owner); - multiActionGenerator = context -> getStateActions(); - - // set this here so we don't have to check for null elsewhere - super.setToolBarData(new ToolBarData(null)); - - if (!isToolbarAction) { - // we need this listener to perform the action when the user click the button; - // toolbar actions have their own listener - clickListener = e -> { - actionPerformed(getActionContext()); - }; - } + this(name, owner); } + /** + * This method will be called as the user changes the selected button state + * @param newActionState the newly selected state + * @param trigger the source of the event + */ public abstract void actionStateChanged(ActionState newActionState, EventTrigger trigger); /** - * If doPerformAction is true, then, when the user clicks the - * button and not the drop-down arrow, the {@link #doActionPerformed(ActionContext)} - * method will be called. If doPerformAction is false, then, when - * the user clicks the button and not the drop-down arrow, the popup menu will be shown, just - * as if the user had clicked the drop-down arrow. - *

- * Also, if the parameter is true, then the button will behave like a button in terms of - * mouse feedback. If false, then the button will behave more like a label. - * - * @param doPerformAction true to call {@link #doActionPerformed(ActionContext)} when the - * user presses the button for this action (not the drop-down menu; see above) + * This method is called when the user clicks the button when this action is used as part of + * the default {@link DockingAction} framework. + * + * This is the callback to be overridden when the child wishes to respond to user button + * presses that are on the button and not the drop-down. The default behavior is to show the + * popup menu when the button is clicked. */ - public void setPerformActionOnPrimaryButtonClick(boolean doPerformAction) { - performActionOnPrimaryButtonClick = doPerformAction; - if (multipleButton == null) { - return; - } - - multipleButton.setPerformActionOnButtonClick(performActionOnPrimaryButtonClick); - - multipleButton.removeActionListener(clickListener); - if (performActionOnPrimaryButtonClick) { - multipleButton.addActionListener(clickListener); - } + @Override + public void actionPerformed(ActionContext context) { + Swing.runLater(() -> multipleButton.showPopup()); } /** @@ -151,38 +127,6 @@ public abstract class MultiStateDockingAction extends DockingAction { this.defaultIcon = icon; } - @Override - public final void actionPerformed(ActionContext context) { - if (!performActionOnPrimaryButtonClick) { - SystemUtilities.runSwingLater(() -> multipleButton.showPopup(null)); - return; - } - - doActionPerformed(context); - } - - /** - * This is the callback to be overridden when the child wishes to respond to user button - * presses that are on the button and not the drop-down. This will only be called if - * {@link #performActionOnPrimaryButtonClick} is true. - * - * @param context the action context - */ - protected void doActionPerformed(ActionContext context) { - // override me to do work - } - - private ActionContext getActionContext() { - DockingWindowManager manager = DockingWindowManager.getActiveInstance(); - - ActionContext context = manager.getActionContext(this); - - if (context == null) { - context = new ActionContext(); - } - return context; - } - protected List getStateActions() { ActionState selectedState = actionStates.get(currentStateIndex); List actions = new ArrayList<>(actionStates.size()); @@ -295,15 +239,6 @@ public abstract class MultiStateDockingAction extends DockingAction { @Override public JButton doCreateButton() { multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator); - multipleButton.setPerformActionOnButtonClick(performActionOnPrimaryButtonClick); - - if (performActionOnPrimaryButtonClick) { - multipleButton.addActionListener(clickListener); - } - else { - multipleButton.removeActionListener(clickListener); - } - if (currentStateIndex >= 0) { ActionState actionState = actionStates.get(currentStateIndex); setButtonState(actionState); @@ -350,6 +285,10 @@ public abstract class MultiStateDockingAction extends DockingAction { return getName() + ": " + getCurrentState().getName(); } + protected void showPopup() { + multipleButton.showPopup(); + } + //================================================================================================== // Inner Classes //================================================================================================== 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 ceef1a3092..8a68fcbd11 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultipleActionDockingToolbarButton.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultipleActionDockingToolbarButton.java @@ -43,11 +43,11 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { private static int ARROW_PADDING = 4; private PopupMouseListener popupListener; + private JPopupMenu popupMenu; private Shape popupContext; + private long popupLastClosedTime; private final MultiActionDockingActionIf multipleAction; - private boolean iconBorderEnabled = true; - private boolean entireButtonShowsPopupMenu; public MultipleActionDockingToolbarButton(MultiActionDockingActionIf action) { multipleAction = action; @@ -74,21 +74,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { return disabledIcon; } - /** - * By default a click on this button will trigger actionPerformed() to be called. - * You can call this method to disable that feature. When called with false, this - * method will effectively let the user click anywhere on the button or its drop-down arrow - * to show the popup menu. During normal operation, the user can only show the popup by - * clicking the drop-down arrow. - * - * @param performActionOnButtonClick true to perform the action when the button is clicked - */ - public void setPerformActionOnButtonClick(boolean performActionOnButtonClick) { - entireButtonShowsPopupMenu = !performActionOnButtonClick; - iconBorderEnabled = performActionOnButtonClick; - popupContext = createPopupContext(); - } - @Override protected void paintBorder(Graphics g) { Border buttonBorder = getBorder(); @@ -98,10 +83,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { Insets borderInsets = buttonBorder.getBorderInsets(this); int leftIconWidth = primaryIcon.getIconWidth() + (borderInsets.left + borderInsets.right); - if (iconBorderEnabled) { - buttonBorder.paintBorder(this, g, 0, 0, leftIconWidth, getHeight()); - } - + buttonBorder.paintBorder(this, g, 0, 0, leftIconWidth, getHeight()); int rightButtonWidth = ARROW_WIDTH + ARROW_PADDING + (borderInsets.left + borderInsets.right); buttonBorder.paintBorder(this, g, leftIconWidth, 0, rightButtonWidth, getHeight()); @@ -132,10 +114,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { } private Shape createPopupContext() { - if (entireButtonShowsPopupMenu) { - return new Rectangle(0, 0, getWidth(), getHeight()); - } - Border buttonBorder = getBorder(); Insets borderInsets = buttonBorder == null ? new Insets(0, 0, 0, 0) : buttonBorder.getBorderInsets(this); @@ -163,10 +141,31 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { /** * Show a popup containing all the actions below the button - * @param listener for the created popup menu * @return the popup menu that was shown */ - JPopupMenu showPopup(PopupMenuListener listener) { + JPopupMenu showPopup() { + + if (popupIsShowing()) { + popupMenu.setVisible(false); + return null; + } + + // + // showPopup() will handled 2 cases when this action's button is clicked: + // 1) show a popup if it was not showing + // 2) hide the popup if it was showing + // + // Case 2 requires timestamps. Java will close the popup as the button is clicked. This + // means that when we are told to show the popup as the result of a click, the popup will + // never be showing. To work around this, we track the elapsed time since last click. If + // the period is too short, then we assume Java closed the popup when the click happened + //and thus we should ignore it. + // + long elapsedTime = System.currentTimeMillis() - popupLastClosedTime; + if (elapsedTime < 500) { // somewhat arbitrary time window + return null; + } + JPopupMenu menu = new JPopupMenu(); List actionList = multipleAction.getActionList(getActionContext()); for (DockingActionIf dockingAction : actionList) { @@ -202,9 +201,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { menu.add(item); } - if (listener != null) { - menu.addPopupMenuListener(listener); - } + menu.addPopupMenuListener(popupListener); Point p = getPopupPoint(); menu.show(this, p.x, p.y); return menu; @@ -215,6 +212,10 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { return new Point(0, bounds.y + bounds.height); } + private boolean popupIsShowing() { + return (popupMenu != null) && popupMenu.isVisible(); + } + //================================================================================================== // Inner Classes //================================================================================================== @@ -285,8 +286,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { private class PopupMouseListener extends MouseAdapter implements PopupMenuListener { private final MouseListener[] parentListeners; - private JPopupMenu popupMenu; - private long actionID = 0; // used to determine when the popup was closed by clicking the button public PopupMouseListener(MouseListener[] parentListeners) { this.parentListeners = parentListeners; @@ -294,16 +293,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { @Override public void mousePressed(MouseEvent e) { - // close the popup if the user clicks the button while the popup is visible - if (popupIsShowing() && e.getClickCount() == 1) { // ignore double-click when the menu is up - popupMenu.setVisible(false); - return; - } - - long eventTime = System.currentTimeMillis(); - if (actionID == eventTime) { - return; - } Point clickPoint = e.getPoint(); if (isEnabled() && popupContext.contains(clickPoint)) { @@ -311,8 +300,8 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { // Unusual Code Alert: we need to put this call in an invoke later, since Java // will update the focused window after we click. We need the focus to be // correct before we show, since our menu is built with actions based upon the - // focused dude. - Swing.runLater(() -> popupMenu = showPopup(PopupMouseListener.this)); + // focused component. + Swing.runLater(() -> popupMenu = showPopup()); e.consume(); model.setPressed(false); @@ -372,10 +361,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { } } - private boolean popupIsShowing() { - return (popupMenu != null) && popupMenu.isVisible(); - } - @Override public void popupMenuCanceled(PopupMenuEvent e) { // no-op @@ -383,7 +368,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton { @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - actionID = System.currentTimeMillis(); // hacktastic! + popupLastClosedTime = System.currentTimeMillis(); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/NonToolbarMultiStateAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/NonToolbarMultiStateAction.java index 9aa455ba26..c9ecbbb1b0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/NonToolbarMultiStateAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/NonToolbarMultiStateAction.java @@ -15,7 +15,13 @@ */ package docking.menu; +import java.awt.event.ActionListener; + +import javax.swing.JButton; + +import docking.action.DockingAction; import docking.widgets.EventTrigger; +import ghidra.util.Swing; /** * A class for clients that wish to create a button that has multiple states, controlled by a @@ -27,12 +33,39 @@ import docking.widgets.EventTrigger; * {@link #actionStateChanged(ActionState, EventTrigger)} callback. Call * {@link #createButton()} and add the return value to your UI. * - * @param + * @param the type * @see MultiStateDockingAction */ public abstract class NonToolbarMultiStateAction extends MultiStateDockingAction { + // A listener that will get called when the button (not the popup) is clicked. Toolbar + // actions do not need this functionality, since the toolbar API will call actionPerfomred(). + private ActionListener clickListener = e -> { + actionPerformed(); + }; + public NonToolbarMultiStateAction(String name, String owner) { super(name, owner); } + + @Override + public JButton doCreateButton() { + JButton button = super.doCreateButton(); + button.addActionListener(clickListener); + return button; + } + + /** + * This method is called when the user clicks the button when this action is used as a + * custom button provider and not installed into the default {@link DockingAction} framework. + * + * + * This is the callback to be overridden when the child wishes to respond to user button + * presses that are on the button and not the drop-down. The default behavior is to show the + * popup menu when the button is clicked. + */ + protected void actionPerformed() { + Swing.runLater(() -> showPopup()); + } + } 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 e2d7a6a936..d72e15d92b 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 @@ -28,7 +28,6 @@ import javax.swing.table.TableModel; import org.jdom.Element; -import docking.ActionContext; import docking.DockingWindowManager; import docking.help.HelpService; import docking.menu.*; @@ -413,12 +412,12 @@ public class GTableFilterPanel extends JPanel { } @Override - protected void doActionPerformed(ActionContext context) { + protected void actionPerformed() { showFilterDialog(tableModel); } }; - columnFilterAction.setPerformActionOnPrimaryButtonClick(true); + HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters"); columnFilterAction.setHelpLocation(helpLocation); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java index 12869f88c6..f3ad09b423 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java @@ -32,7 +32,7 @@ public interface DataType { /** * WARNING: do not add default method implementations to this interface. Doing so - * intereferes with correct initialization of the static instance variables {@link #DEFAULT} and + * interferes with correct initialization of the static instance variables {@link #DEFAULT} and * {@link #VOID} below. */ @@ -559,7 +559,7 @@ public interface DataType { * The datatypes must be of the same "type" (i.e. structure can only be replacedWith another * structure. * - * @param datatype the datatype that contains the internals to upgrade to. + * @param dataType the datatype that contains the internals to upgrade to. * @throws UnsupportedOperationException if the datatype does not support change. * @throws IllegalArgumentException if the given datatype is not the same type as this datatype. */