GP-1619 - Fixed bug that caused some MultiStateDockinActions to get called twice when clicked

This commit is contained in:
dragonmacher 2022-01-05 17:03:56 -05:00
parent 436bb4873e
commit 6934c33aa6
25 changed files with 197 additions and 627 deletions

View file

@ -24,7 +24,6 @@ public interface DebuggerAutoReadMemoryAction extends AutoReadMemoryAction {
static MultiStateActionBuilder<AutoReadMemorySpec> builder(Plugin owner) { static MultiStateActionBuilder<AutoReadMemorySpec> builder(Plugin owner) {
MultiStateActionBuilder<AutoReadMemorySpec> builder = AutoReadMemoryAction.builder(owner); MultiStateActionBuilder<AutoReadMemorySpec> builder = AutoReadMemoryAction.builder(owner);
builder.toolBarGroup(NAME); builder.toolBarGroup(NAME);
builder.performActionOnButtonClick(true);
for (AutoReadMemorySpec spec : AutoReadMemorySpec.allSpecs().values()) { for (AutoReadMemorySpec spec : AutoReadMemorySpec.allSpecs().values()) {
builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec); builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec);
} }

View file

@ -25,7 +25,6 @@ public interface DebuggerTrackLocationAction extends TrackLocationAction {
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) { static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner); MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
builder.toolBarGroup(owner.getName()); builder.toolBarGroup(owner.getName());
builder.performActionOnButtonClick(true);
for (LocationTrackingSpec spec : LocationTrackingSpec.allSpecs().values()) { for (LocationTrackingSpec spec : LocationTrackingSpec.allSpecs().values()) {
builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec); builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec);
} }

View file

@ -63,22 +63,14 @@ import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@PluginInfo( @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 = {
shortDescription = "Debugger models manager service (proxy to front-end)", ProgramActivatedPluginEvent.class,
description = "Manage debug sessions, connections, and trace recording", ProgramClosedPluginEvent.class,
category = PluginCategoryNames.DEBUGGER, }, servicesRequired = {
packageName = DebuggerPluginPackage.NAME, DebuggerTraceManagerService.class,
status = PluginStatus.RELEASED, }, servicesProvided = {
eventsConsumed = { DebuggerModelService.class,
ProgramActivatedPluginEvent.class, })
ProgramClosedPluginEvent.class,
},
servicesRequired = {
DebuggerTraceManagerService.class,
},
servicesProvided = {
DebuggerModelService.class,
})
public class DebuggerModelServiceProxyPlugin extends Plugin public class DebuggerModelServiceProxyPlugin extends Plugin
implements DebuggerModelServiceInternal { implements DebuggerModelServiceInternal {
@ -250,7 +242,6 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
.enabledWhen(ctx -> currentProgram != null) .enabledWhen(ctx -> currentProgram != null)
.onAction(this::debugProgramButtonActivated) .onAction(this::debugProgramButtonActivated)
.onActionStateChanged(this::debugProgramStateActivated) .onActionStateChanged(this::debugProgramStateActivated)
.performActionOnButtonClick(true)
.addState(DUMMY_LAUNCH_STATE) .addState(DUMMY_LAUNCH_STATE)
.buildAndInstall(tool); .buildAndInstall(tool);
actionDisconnectAll = DisconnectAllAction.builder(this, delegate) actionDisconnectAll = DisconnectAllAction.builder(this, delegate)
@ -383,7 +374,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
List<DebuggerProgramLaunchOffer> offers = program == null ? List.of() List<DebuggerProgramLaunchOffer> offers = program == null ? List.of()
: getProgramLaunchOffers(program).collect(Collectors.toList()); : getProgramLaunchOffers(program).collect(Collectors.toList());
List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream() List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream()
.map(o -> new ActionState<DebuggerProgramLaunchOffer>(o.getButtonTitle(), .map(o -> new ActionState<>(o.getButtonTitle(),
o.getIcon(), o)) o.getIcon(), o))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!states.isEmpty()) { if (!states.isEmpty()) {

View file

@ -348,7 +348,7 @@ public class SampleGraphProvider extends ComponentProviderAdapter {
new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) {
@Override @Override
protected void doActionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
// this callback is when the user clicks the button // this callback is when the user clicks the button
LayoutProvider<SampleVertex, SampleEdge, SampleGraph> currentUserData = LayoutProvider<SampleVertex, SampleEdge, SampleGraph> currentUserData =
getCurrentUserData(); getCurrentUserData();

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; 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.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.program.model.data.DataTypeConflictHandler; import ghidra.program.model.data.DataTypeConflictHandler;
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResolutionPolicy; import ghidra.program.model.data.DataTypeConflictHandler.ConflictResolutionPolicy;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import javax.swing.Icon;
import resources.ResourceManager; import resources.ResourceManager;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
public class ConflictHandlerModesAction extends public class ConflictHandlerModesAction extends
MultiStateDockingAction<DataTypeConflictHandler.ConflictResolutionPolicy> { MultiStateDockingAction<DataTypeConflictHandler.ConflictResolutionPolicy> {
@ -43,33 +41,31 @@ public class ConflictHandlerModesAction extends
new HelpLocation(plugin.getName(), "conflict_mode"); new HelpLocation(plugin.getName(), "conflict_mode");
setHelpLocation(conflictModesHelpLocation); setHelpLocation(conflictModesHelpLocation);
setPerformActionOnPrimaryButtonClick(false);
Icon renameAndAddIcon = ResourceManager.loadImage("images/conflictRename.png"); Icon renameAndAddIcon = ResourceManager.loadImage("images/conflictRename.png");
Icon useExistingIcon = ResourceManager.loadImage("images/conflictKeep.png"); Icon useExistingIcon = ResourceManager.loadImage("images/conflictKeep.png");
Icon replaceExistingIcon = ResourceManager.loadImage("images/conflictReplace.png"); Icon replaceExistingIcon = ResourceManager.loadImage("images/conflictReplace.png");
Icon replaceDefaultIcon = ResourceManager.loadImage("images/conflictReplaceOrRename.png"); Icon replaceDefaultIcon = ResourceManager.loadImage("images/conflictReplaceOrRename.png");
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> renameAndAddState = ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> renameAndAddState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>( new ActionState<>(
"Rename New or Moved Data Type", renameAndAddIcon, "Rename New or Moved Data Type", renameAndAddIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.RENAME_AND_ADD); DataTypeConflictHandler.ConflictResolutionPolicy.RENAME_AND_ADD);
renameAndAddState.setHelpLocation(conflictModesHelpLocation); renameAndAddState.setHelpLocation(conflictModesHelpLocation);
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> useExistingState = ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> useExistingState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>( new ActionState<>(
"Use Existing Data Type", useExistingIcon, "Use Existing Data Type", useExistingIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.USE_EXISTING); DataTypeConflictHandler.ConflictResolutionPolicy.USE_EXISTING);
useExistingState.setHelpLocation(conflictModesHelpLocation); useExistingState.setHelpLocation(conflictModesHelpLocation);
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> replaceExistingState = ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> replaceExistingState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>( new ActionState<>(
"Replace Existing Data Type", replaceExistingIcon, "Replace Existing Data Type", replaceExistingIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EXISTING); DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EXISTING);
replaceExistingState.setHelpLocation(conflictModesHelpLocation); replaceExistingState.setHelpLocation(conflictModesHelpLocation);
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> replaceDefaultState = ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> replaceDefaultState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>( new ActionState<>(
"Replace Empty Structures else Rename", "Replace Empty Structures else Rename",
replaceDefaultIcon, replaceDefaultIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD); DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD);

View file

@ -15,9 +15,8 @@
*/ */
package ghidra.app.plugin.core.navigation; package ghidra.app.plugin.core.navigation;
import java.util.Iterator;
import java.awt.event.*; import java.awt.event.*;
import java.util.Iterator;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
@ -103,7 +102,7 @@ public class NextPreviousBookmarkAction extends MultiStateDockingAction<String>
} }
@Override @Override
protected void doActionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
if (context instanceof NavigatableActionContext) { if (context instanceof NavigatableActionContext) {
gotoNextPrevious((NavigatableActionContext) context, this.getCurrentUserData()); gotoNextPrevious((NavigatableActionContext) context, this.getCurrentUserData());
} }

View file

@ -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<String> {
//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<String> allMarkers =
new ActionState<String>("All Types", markerIcon, "All Types");
ActionState<String> analysis =
new ActionState<String>("Analysis Marker", markerAnalysisBookmarkIcon,
BookmarkType.ANALYSIS);
ActionState<String> conflictingChanges =
new ActionState<String>("Conflicting Changes", markerAnalysisBookmarkIcon,
"Conflicting Changes");
ActionState<String> latestVersionChanges =
new ActionState<String>("Latest Version Changes", markerAnalysisBookmarkIcon,
"Latest Version Changes");
ActionState<String> notCheckedInChanges =
new ActionState<String>("Not Checked In Changes", markerAnalysisBookmarkIcon,
"Not Checked In Changes");
ActionState<String> unsavedChanges =
new ActionState<String>("Unsaved Changes", markerIcon, "Unsaved Changes");
ActionState<String> cursor = new ActionState<String>("Cursor", markerIcon, "Cursor");
ActionState<String> error =
new ActionState<String>("Error Marker", markerIcon, BookmarkType.ERROR);
ActionState<String> highlight =
new ActionState<String>("Highlight", markerIcon, "Highlight");
ActionState<String> info =
new ActionState<String>("Info Marker", markerIcon, BookmarkType.INFO);
ActionState<String> note =
new ActionState<String>("Note Marker", markerIcon, BookmarkType.NOTE);
ActionState<String> selection =
new ActionState<String>("Selection", markerIcon, "Selection");
ActionState<String> warning =
new ActionState<String>("Warning Marker", markerIcon, BookmarkType.WARNING);
ActionState<String> custom =
new ActionState<String>("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<String> 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<Marker> 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<MarkerSet> 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);
}
}

View file

@ -128,15 +128,8 @@ public class OverviewColorPlugin extends ProgramPlugin {
actionMap.put(overviewColorService, actionMap.put(overviewColorService,
new OverviewToggleAction(getName(), overviewColorService)); new OverviewToggleAction(getName(), overviewColorService));
} }
multiAction = new MultiActionDockingAction("Overview", getName()) {
@Override multiAction = new MultiActionDockingAction("Overview", getName());
public void actionPerformed(ActionContext context) {
// do nothing - the following setPerformActionOnButtonClick(false) will ensure
// this never gets called.
}
};
multiAction.setPerformActionOnButtonClick(false);
multiAction.setActions(new ArrayList<DockingActionIf>(actionMap.values())); multiAction.setActions(new ArrayList<DockingActionIf>(actionMap.values()));
multiAction.setToolBarData( multiAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png"))); new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png")));

View file

@ -668,8 +668,6 @@ public class ListingCodeComparisonPanel
setHelpLocation(helpLocation); setHelpLocation(helpLocation);
setDescription("Set Navigate Next/Previous Area Marker options"); setDescription("Set Navigate Next/Previous Area Marker options");
setPerformActionOnPrimaryButtonClick(false);
ActionState<String> allAreaMarkers = ActionState<String> allAreaMarkers =
new ActionState<>(ALL_AREA_MARKERS, bothIcon, ALL_AREA_MARKERS); new ActionState<>(ALL_AREA_MARKERS, bothIcon, ALL_AREA_MARKERS);
allAreaMarkers.setHelpLocation(helpLocation); allAreaMarkers.setHelpLocation(helpLocation);

View file

@ -177,7 +177,8 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest {
public void testClearActionEnablement() throws Exception { public void testClearActionEnablement() throws Exception {
closeProgram(); closeProgram();
assertTrue(!clearAction.isEnabledForContext(new ActionContext())); ActionContext context = cb.getProvider().getActionContext(null);
assertFalse(clearAction.isEnabledForContext(context));
showTool(tool); showTool(tool);
loadProgram("notepad"); loadProgram("notepad");
@ -185,10 +186,12 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest {
waitForSwing(); waitForSwing();
assertTrue(cb.goToField(addr("0x10026f0"), "Address", 0, 0)); assertTrue(cb.goToField(addr("0x10026f0"), "Address", 0, 0));
assertTrue(clearAction.isEnabled()); context = cb.getProvider().getActionContext(null);
assertTrue(clearAction.isEnabledForContext(context));
closeProgram(); closeProgram();
assertTrue(!clearAction.isEnabledForContext(new ActionContext())); context = cb.getProvider().getActionContext(null);
assertFalse(clearAction.isEnabledForContext(context));
} }
@Test @Test
@ -453,7 +456,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest {
Symbol[] symbols = program.getSymbolTable().getSymbols(addr("0x01001010")); Symbol[] symbols = program.getSymbolTable().getSymbols(addr("0x01001010"));
assertEquals(1, symbols.length); assertEquals(1, symbols.length);
assertTrue(!symbols[0].isDynamic()); assertFalse(symbols[0].isDynamic());
int id = program.startTransaction("Anchor"); int id = program.startTransaction("Anchor");
symbols[0].setPinned(true); symbols[0].setPinned(true);
program.endTransaction(id, true); program.endTransaction(id, true);
@ -467,7 +470,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest {
symbols = program.getSymbolTable().getSymbols(addr("0x01001010")); symbols = program.getSymbolTable().getSymbols(addr("0x01001010"));
assertEquals(1, symbols.length); assertEquals(1, symbols.length);
assertTrue(!symbols[0].isDynamic()); assertFalse(symbols[0].isDynamic());
} }
@Test @Test
@ -639,7 +642,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(l.getNumInstructions() > 0); assertTrue(l.getNumInstructions() > 0);
assertTrue(l.getNumDefinedData() > 0); assertTrue(l.getNumDefinedData() > 0);
assertTrue(!program.getListing().getFunctions(true).hasNext()); assertFalse(program.getListing().getFunctions(true).hasNext());
assertTrue(program.getSymbolTable().getNumSymbols() > 0); assertTrue(program.getSymbolTable().getNumSymbols() > 0);
undo(program); undo(program);
@ -674,7 +677,7 @@ public class ClearTest extends AbstractGhidraHeadedIntegrationTest {
okOnClearDialog(); okOnClearDialog();
assertTrue(!context.hasValueOverRange(ax, BigInteger.valueOf(5), assertFalse(context.hasValueOverRange(ax, BigInteger.valueOf(5),
new AddressSet(addr("0x10022cc")))); new AddressSet(addr("0x10022cc"))));
undo(program); undo(program);
assertTrue(context.hasValueOverRange(ax, BigInteger.valueOf(5), assertTrue(context.hasValueOverRange(ax, BigInteger.valueOf(5),

View file

@ -304,13 +304,6 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
// Private Methods // Private Methods
//================================================================================================== //==================================================================================================
private ComponentProvider showDecompiler() {
ComponentProvider cp = tool.getComponentProvider("Decompiler");
tool.showComponentProvider(cp, true);
cp.requestFocus();
return cp;
}
private void assertCurrentAddress(Address expected) { private void assertCurrentAddress(Address expected) {
assertEquals(expected, currentAddress()); assertEquals(expected, currentAddress());
} }
@ -415,8 +408,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
// //
// clear the popup // clear the popup
Object mouseAdapter = getInstanceField("popupListener", button); setInstanceField("popupMenu", button, null);
setInstanceField("popupMenu", mouseAdapter, null);
// trigger the popup // trigger the popup
Shape popupTriggerArea = (Shape) TestUtils.getInstanceField("popupContext", button); Shape popupTriggerArea = (Shape) TestUtils.getInstanceField("popupContext", button);
@ -426,7 +418,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe
clickMouse(button, MouseEvent.BUTTON1, x, y, 1, 0); clickMouse(button, MouseEvent.BUTTON1, x, y, 1, 0);
// get the popup // get the popup
JPopupMenu menu = (JPopupMenu) getInstanceField("popupMenu", mouseAdapter); JPopupMenu menu = (JPopupMenu) getInstanceField("popupMenu", button);
assertNotNull(menu); assertNotNull(menu);
// Note: calling clickMouse() seems to work for now. If this is not consistent, then // Note: calling clickMouse() seems to work for now. If this is not consistent, then

View file

@ -43,18 +43,17 @@ import ghidra.framework.main.datatree.DataTree;
import ghidra.framework.main.datatree.ProjectDataTreePanel; import ghidra.framework.main.datatree.ProjectDataTreePanel;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.store.LockException;
import ghidra.program.database.symbol.LibrarySymbol; 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.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryConflictException;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv; import ghidra.test.TestEnv;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.exception.*; import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
@ -158,7 +157,7 @@ public abstract class AbstractSymbolTreePluginExternalsTest
protected ExternalLocation setupExternalLocation(String library, String label, Address address, protected ExternalLocation setupExternalLocation(String library, String label, Address address,
SourceType sourceType, boolean isFunction) SourceType sourceType, boolean isFunction)
throws InvalidInputException, DuplicateNameException { throws Exception {
boolean success = false; boolean success = false;
int transactionID = int transactionID =
program.startTransaction("Setting Up External Location " + library + "::" + label); 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, 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); return setupExternalLocation(library, label, address, sourceType, false);
} }
protected ExternalLocation setupExternalFunction(String library, String label, Address address, 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); return setupExternalLocation(library, label, address, sourceType, true);
} }
@ -448,8 +447,7 @@ public abstract class AbstractSymbolTreePluginExternalsTest
} }
protected void addOverlayBlock(String name, String startAddress, long length) protected void addOverlayBlock(String name, String startAddress, long length)
throws LockException, DuplicateNameException, MemoryConflictException, throws Exception {
AddressOverflowException, CancelledException {
int transactionID = program.startTransaction("Add Overlay Block to test"); int transactionID = program.startTransaction("Add Overlay Block to test");
Address address = program.getAddressFactory().getAddress(startAddress); Address address = program.getAddressFactory().getAddress(startAddress);
Memory memory = program.getMemory(); Memory memory = program.getMemory();

View file

@ -87,7 +87,7 @@ public class CloseToolTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(closeOthersAction); assertNotNull(closeOthersAction);
ProgramActionContext context = new ProgramActionContext(null, program1); ProgramActionContext context = new ProgramActionContext(null, program1);
assertEquals(true, closeOthersAction.isEnabledForContext(context)); assertEquals(true, closeOthersAction.isEnabledForContext(context));
performAction(closeOthersAction, true); performAction(closeOthersAction, context, true);
allOpenPrograms = pm.getAllOpenPrograms(); allOpenPrograms = pm.getAllOpenPrograms();
assertEquals(1, allOpenPrograms.length); assertEquals(1, allOpenPrograms.length);

View file

@ -859,10 +859,10 @@ class FGActionManager {
HelpLocation layoutHelpLocation = HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName(), true) { layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) {
@Override @Override
protected void doActionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
// this callback is when the user clicks the button // this callback is when the user clicks the button
FGLayoutProvider currentUserData = getCurrentUserData(); FGLayoutProvider currentUserData = getCurrentUserData();
changeLayout(currentUserData); changeLayout(currentUserData);
@ -994,7 +994,6 @@ class FGActionManager {
}; };
vertexHoverModeAction.setGroup(group); vertexHoverModeAction.setGroup(group);
vertexHoverModeAction.setHelpLocation(pathHelpLocation); vertexHoverModeAction.setHelpLocation(pathHelpLocation);
vertexHoverModeAction.setPerformActionOnPrimaryButtonClick(false);
vertexHoverModeAction.addActionState(offState); vertexHoverModeAction.addActionState(offState);
vertexHoverModeAction.addActionState(pathsForwardScopedFlow); vertexHoverModeAction.addActionState(pathsForwardScopedFlow);
@ -1063,7 +1062,6 @@ class FGActionManager {
}; };
vertexFocusModeAction.setGroup(group); vertexFocusModeAction.setGroup(group);
vertexFocusModeAction.setHelpLocation(pathHelpLocation); vertexFocusModeAction.setHelpLocation(pathHelpLocation);
vertexFocusModeAction.setPerformActionOnPrimaryButtonClick(false);
vertexFocusModeAction.addActionState(offState); vertexFocusModeAction.addActionState(offState);
vertexFocusModeAction.addActionState(pathsForwardScopedFlow); vertexFocusModeAction.addActionState(pathsForwardScopedFlow);

View file

@ -526,7 +526,7 @@ public class FcgProvider
RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) {
@Override @Override
protected void doActionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
// this callback is when the user clicks the button // this callback is when the user clicks the button
LayoutProvider<FcgVertex, FcgEdge, FunctionCallGraph> currentUserData = LayoutProvider<FcgVertex, FcgEdge, FunctionCallGraph> currentUserData =
getCurrentUserData(); getCurrentUserData();

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,19 +16,20 @@
package ghidra.feature.vt.gui.actions; package ghidra.feature.vt.gui.actions;
import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*; 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 javax.swing.Icon;
import resources.ResourceManager;
import docking.action.ToolBarData; import docking.action.ToolBarData;
import docking.menu.ActionState; import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction; import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger; 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<TableSelectionTrackingState> { public class MatchTableSelectionAction
extends MultiStateDockingAction<TableSelectionTrackingState> {
private static final String MENU_GROUP = VTPlugin.VT_SETTINGS_MENU_GROUP; private static final String MENU_GROUP = VTPlugin.VT_SETTINGS_MENU_GROUP;
@ -44,7 +44,6 @@ public class MatchTableSelectionAction extends MultiStateDockingAction<TableSele
setToolBarData(new ToolBarData(null, MENU_GROUP)); setToolBarData(new ToolBarData(null, MENU_GROUP));
setDescription("Adjust the Apply Mark-up Settings for Applying Matches"); setDescription("Adjust the Apply Mark-up Settings for Applying Matches");
setEnabled(true); setEnabled(true);
setPerformActionOnPrimaryButtonClick(false); // pressing button shows drop-down
HelpLocation helpLocation = HelpLocation helpLocation =
new HelpLocation("VersionTrackingPlugin", "Match_Table_Selection"); new HelpLocation("VersionTrackingPlugin", "Match_Table_Selection");
@ -55,17 +54,17 @@ public class MatchTableSelectionAction extends MultiStateDockingAction<TableSele
Icon trackRowIndexSelectionIcon = ResourceManager.loadImage("images/table_gear.png"); Icon trackRowIndexSelectionIcon = ResourceManager.loadImage("images/table_gear.png");
ActionState<TableSelectionTrackingState> trackSelectedIndexActionState = ActionState<TableSelectionTrackingState> trackSelectedIndexActionState =
new ActionState<TableSelectionTrackingState>("Track Selected Index", new ActionState<>("Track Selected Index",
trackRowIndexSelectionIcon, MAINTAIN_SELECTED_ROW_INDEX); trackRowIndexSelectionIcon, MAINTAIN_SELECTED_ROW_INDEX);
trackSelectedIndexActionState.setHelpLocation(helpLocation); trackSelectedIndexActionState.setHelpLocation(helpLocation);
ActionState<TableSelectionTrackingState> trackMatchSelectionActionState = ActionState<TableSelectionTrackingState> trackMatchSelectionActionState =
new ActionState<TableSelectionTrackingState>("Track Selected Match", new ActionState<>("Track Selected Match",
trackMatchSelectionIcon, MAINTAIN_SELECTED_ROW_VALUE); trackMatchSelectionIcon, MAINTAIN_SELECTED_ROW_VALUE);
trackMatchSelectionActionState.setHelpLocation(helpLocation); trackMatchSelectionActionState.setHelpLocation(helpLocation);
ActionState<TableSelectionTrackingState> noSelectionTrackingActionState = ActionState<TableSelectionTrackingState> noSelectionTrackingActionState =
new ActionState<TableSelectionTrackingState>("No Selection Tracking", new ActionState<>("No Selection Tracking",
noSelectionTrackingIcon, NO_SELECTION_TRACKING); noSelectionTrackingIcon, NO_SELECTION_TRACKING);
noSelectionTrackingActionState.setHelpLocation(helpLocation); noSelectionTrackingActionState.setHelpLocation(helpLocation);

View file

@ -15,12 +15,8 @@
*/ */
package ghidra.feature.vt.gui.provider.functionassociation; 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.*;
import static ghidra.feature.vt.api.impl.VTChangeManager.DOCR_VT_MATCH_ADDED; import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.*;
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 java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
@ -156,6 +152,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
destinationFunctionsModel.setFilterSettings(filterSettings); destinationFunctionsModel.setFilterSettings(filterSettings);
} }
}; };
filterAction.setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Functions_Filter")); filterAction.setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Functions_Filter"));
Icon allFunctionsIcon = ResourceManager.loadImage("images/function.png"); Icon allFunctionsIcon = ResourceManager.loadImage("images/function.png");

View file

@ -31,10 +31,6 @@ public class MultiActionBuilder
* List of actions for the the MultActionDockingAction * List of actions for the the MultActionDockingAction
*/ */
private List<DockingActionIf> actionList = Collections.emptyList(); private List<DockingActionIf> actionList = Collections.emptyList();
/**
* determines if the the main action is invokable
*/
private boolean performActionOnButtonClick = true;
/** /**
* Builder constructor * Builder constructor
@ -61,7 +57,6 @@ public class MultiActionBuilder
}; };
decorateAction(action); decorateAction(action);
action.setActions(actionList); action.setActions(actionList);
action.setPerformActionOnButtonClick(performActionOnButtonClick);
return action; return action;
} }
@ -81,26 +76,8 @@ public class MultiActionBuilder
return self(); 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 @Override
protected void validate() { 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) { if (actionList == null) {
throw new IllegalStateException("No ActionList has been set"); throw new IllegalStateException("No ActionList has been set");
} }

View file

@ -22,7 +22,8 @@ import java.util.function.BiConsumer;
import javax.swing.Icon; import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
import docking.menu.*; import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
/** /**
@ -35,7 +36,6 @@ public class MultiStateActionBuilder<T> extends
private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback; private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback;
private boolean useCheckboxForIcons; private boolean useCheckboxForIcons;
private boolean performActionOnButtonClick = false;
private List<ActionState<T>> states = new ArrayList<>(); private List<ActionState<T>> states = new ArrayList<>();
@ -68,18 +68,6 @@ public class MultiStateActionBuilder<T> extends
return self(); 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<T> performActionOnButtonClick(boolean b) {
this.performActionOnButtonClick = b;
return self();
}
/** /**
* Overrides the default icons for actions shown in popup menu of the multi-state action. By * 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}. * default, the popup menu items will use the icons as provided by the {@link ActionState}.
@ -103,7 +91,7 @@ public class MultiStateActionBuilder<T> extends
* @return this MultiActionDockingActionBuilder (for chaining) * @return this MultiActionDockingActionBuilder (for chaining)
*/ */
public MultiStateActionBuilder<T> addState(String displayName, Icon icon, T userData) { public MultiStateActionBuilder<T> addState(String displayName, Icon icon, T userData) {
states.add(new ActionState<T>(displayName, icon, userData)); states.add(new ActionState<>(displayName, icon, userData));
return self(); return self();
} }
@ -133,7 +121,7 @@ public class MultiStateActionBuilder<T> extends
public MultiStateDockingAction<T> build() { public MultiStateDockingAction<T> build() {
validate(); validate();
MultiStateDockingAction<T> action = MultiStateDockingAction<T> action =
new MultiStateDockingAction<>(name, owner, isToolbarAction()) { new MultiStateDockingAction<>(name, owner) {
@Override @Override
public void actionStateChanged(ActionState<T> newActionState, public void actionStateChanged(ActionState<T> newActionState,
@ -142,10 +130,13 @@ public class MultiStateActionBuilder<T> extends
} }
@Override @Override
protected void doActionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
if (actionCallback != null) { if (actionCallback != null) {
actionCallback.accept(context); actionCallback.accept(context);
} }
else {
super.actionPerformed(context);
}
} }
}; };
@ -154,16 +145,12 @@ public class MultiStateActionBuilder<T> extends
} }
decorateAction(action); decorateAction(action);
action.setPerformActionOnPrimaryButtonClick(performActionOnButtonClick);
action.setUseCheckboxForIcons(useCheckboxForIcons); action.setUseCheckboxForIcons(useCheckboxForIcons);
return action; return action;
} }
@Override @Override
protected void validate() { protected void validate() {
if (performActionOnButtonClick) {
super.validate(); // require an action callback has been defined
}
if (actionStateChangedCallback == null) { if (actionStateChangedCallback == null) {
throw new IllegalStateException( throw new IllegalStateException(
"Can't build a MultiStateDockingAction without an action state changed callback"); "Can't build a MultiStateDockingAction without an action state changed callback");

View file

@ -22,13 +22,14 @@ import javax.swing.JButton;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import ghidra.util.Swing;
/** /**
* A class that supports multiple sub-actions, as well as a primary action. This is useful for * A class that supports multiple sub-actions, as well as a primary action. This is useful for
* actions that perform navigation operations. * actions that perform navigation operations.
* <p> * <p>
* Clients may add actions to this class with the intention that they will be accessible * Clients may add actions to this class with the intention that they will be accessible to the
* to the user via a GUI; for example, from a popup menu. * user via a GUI; for example, from a popup menu.
* <p> * <p>
* Actions added must have menu bar data set. * Actions added must have menu bar data set.
* *
@ -36,18 +37,20 @@ import docking.action.*;
* the user to execute. * the user to execute.
* *
* <p> * <p>
* If the user executes this action directly, then * If the user executes this action directly (by clicking the non-popup section of the button),
* {@link #actionPerformed(ActionContext)} will be called. Otherwise, the * then {@link #actionPerformed(ActionContext)} will be called. By default, when the button is
* {@link DockingAction#actionPerformed(ActionContext)} method of the sub-action * clicked, the popup menu is shown. To change this behavior, override
* that was executed will be called. * {@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 * @see MultiStateDockingAction
*/ */
public abstract class MultiActionDockingAction extends DockingAction public class MultiActionDockingAction extends DockingAction
implements MultiActionDockingActionIf { implements MultiActionDockingActionIf {
private List<DockingActionIf> actionList = Collections.emptyList(); private List<DockingActionIf> actionList = Collections.emptyList();
private boolean performActionOnButtonClick = true; private MultipleActionDockingToolbarButton multipleButton;
public MultiActionDockingAction(String name, String owner) { public MultiActionDockingAction(String name, String owner) {
super(name, owner); super(name, owner);
@ -67,24 +70,23 @@ public abstract class MultiActionDockingAction extends DockingAction
return actionList; return actionList;
} }
/**
* This method is called when the user clicks the button <B>when this action is used as part of
* the default {@link DockingAction} framework.</B>
*
* 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 @Override
public JButton doCreateButton() { public void actionPerformed(ActionContext context) {
MultipleActionDockingToolbarButton button = new MultipleActionDockingToolbarButton(this); Swing.runLater(() -> multipleButton.showPopup());
button.setPerformActionOnButtonClick(performActionOnButtonClick);
return button;
} }
/** @Override
* By default a click on this action will trigger <code>actionPerformed()</code> to be called. public JButton doCreateButton() {
* You can call this method to disable that feature. When called with <code>false</code>, this multipleButton = new MultipleActionDockingToolbarButton(this);
* method will effectively let the user click anywhere on the button or its drop-down arrow return multipleButton;
* 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;
} }
public static DockingActionIf createSeparator() { public static DockingActionIf createSeparator() {

View file

@ -15,7 +15,6 @@
*/ */
package docking.menu; package docking.menu;
import java.awt.event.ActionListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -23,25 +22,24 @@ import javax.swing.Icon;
import javax.swing.JButton; import javax.swing.JButton;
import docking.ActionContext; import docking.ActionContext;
import docking.DockingWindowManager;
import docking.action.*; import docking.action.*;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities; import ghidra.util.Swing;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import resources.icons.EmptyIcon; import resources.icons.EmptyIcon;
/** /**
* An action that can be in one of multiple states. The button of this action has a * 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 * drop-down icon that allows users to change the state of the button. As the user changes the
* the user presses the button, it will execute the action corresponding to the current * state of this action, {@link #actionStateChanged(ActionState, EventTrigger)} will be called.
* state. * Clients may also use the button of this action to respond to button presses by overriding
* * {@link #actionPerformed(ActionContext)}.
* <p>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.
* *
* <p>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 <T> the type of the user data * @param <T> the type of the user data
* @see MultiActionDockingAction * @see MultiActionDockingAction
*/ */
@ -54,16 +52,9 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
private MultiActionDockingActionIf multiActionGenerator; private MultiActionDockingActionIf multiActionGenerator;
private MultipleActionDockingToolbarButton multipleButton; private MultipleActionDockingToolbarButton multipleButton;
private boolean performActionOnPrimaryButtonClick = true;
private Icon defaultIcon; private Icon defaultIcon;
private boolean useCheckboxForIcons; 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 * Call this constructor with this action will not be added to a toolbar
* *
@ -72,7 +63,11 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* @see #MultiStateDockingAction(String, String, boolean) * @see #MultiStateDockingAction(String, String, boolean)
*/ */
public MultiStateDockingAction(String name, String owner) { 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<T> extends DockingAction {
* @param name the action name * @param name the action name
* @param owner the owner * @param owner the owner
* @param isToolbarAction true if this action is a toolbar action * @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) { protected MultiStateDockingAction(String name, String owner, boolean isToolbarAction) {
super(name, owner); this(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 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<T> newActionState, EventTrigger trigger); public abstract void actionStateChanged(ActionState<T> newActionState, EventTrigger trigger);
/** /**
* If <code>doPerformAction</code> is <code>true</code>, then, when the user clicks the * This method is called when the user clicks the button <B>when this action is used as part of
* button and not the drop-down arrow, the {@link #doActionPerformed(ActionContext)} * the default {@link DockingAction} framework.</B>
* method will be called. If <code>doPerformAction</code> is <code>false</code>, then, when *
* the user clicks the button and not the drop-down arrow, the popup menu will be shown, just * This is the callback to be overridden when the child wishes to respond to user button
* as if the user had clicked the drop-down arrow. * presses that are on the button and not the drop-down. The default behavior is to show the
* <p> * popup menu when the button is clicked.
* 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)
*/ */
public void setPerformActionOnPrimaryButtonClick(boolean doPerformAction) { @Override
performActionOnPrimaryButtonClick = doPerformAction; public void actionPerformed(ActionContext context) {
if (multipleButton == null) { Swing.runLater(() -> multipleButton.showPopup());
return;
}
multipleButton.setPerformActionOnButtonClick(performActionOnPrimaryButtonClick);
multipleButton.removeActionListener(clickListener);
if (performActionOnPrimaryButtonClick) {
multipleButton.addActionListener(clickListener);
}
} }
/** /**
@ -151,38 +127,6 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
this.defaultIcon = icon; 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<DockingActionIf> getStateActions() { protected List<DockingActionIf> getStateActions() {
ActionState<T> selectedState = actionStates.get(currentStateIndex); ActionState<T> selectedState = actionStates.get(currentStateIndex);
List<DockingActionIf> actions = new ArrayList<>(actionStates.size()); List<DockingActionIf> actions = new ArrayList<>(actionStates.size());
@ -295,15 +239,6 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
@Override @Override
public JButton doCreateButton() { public JButton doCreateButton() {
multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator); multipleButton = new MultipleActionDockingToolbarButton(multiActionGenerator);
multipleButton.setPerformActionOnButtonClick(performActionOnPrimaryButtonClick);
if (performActionOnPrimaryButtonClick) {
multipleButton.addActionListener(clickListener);
}
else {
multipleButton.removeActionListener(clickListener);
}
if (currentStateIndex >= 0) { if (currentStateIndex >= 0) {
ActionState<T> actionState = actionStates.get(currentStateIndex); ActionState<T> actionState = actionStates.get(currentStateIndex);
setButtonState(actionState); setButtonState(actionState);
@ -350,6 +285,10 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
return getName() + ": " + getCurrentState().getName(); return getName() + ": " + getCurrentState().getName();
} }
protected void showPopup() {
multipleButton.showPopup();
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================

View file

@ -43,11 +43,11 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
private static int ARROW_PADDING = 4; private static int ARROW_PADDING = 4;
private PopupMouseListener popupListener; private PopupMouseListener popupListener;
private JPopupMenu popupMenu;
private Shape popupContext; private Shape popupContext;
private long popupLastClosedTime;
private final MultiActionDockingActionIf multipleAction; private final MultiActionDockingActionIf multipleAction;
private boolean iconBorderEnabled = true;
private boolean entireButtonShowsPopupMenu;
public MultipleActionDockingToolbarButton(MultiActionDockingActionIf action) { public MultipleActionDockingToolbarButton(MultiActionDockingActionIf action) {
multipleAction = action; multipleAction = action;
@ -74,21 +74,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
return disabledIcon; return disabledIcon;
} }
/**
* By default a click on this button will trigger <code>actionPerformed()</code> to be called.
* You can call this method to disable that feature. When called with <code>false</code>, 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 @Override
protected void paintBorder(Graphics g) { protected void paintBorder(Graphics g) {
Border buttonBorder = getBorder(); Border buttonBorder = getBorder();
@ -98,10 +83,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
Insets borderInsets = buttonBorder.getBorderInsets(this); Insets borderInsets = buttonBorder.getBorderInsets(this);
int leftIconWidth = primaryIcon.getIconWidth() + (borderInsets.left + borderInsets.right); 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 = int rightButtonWidth =
ARROW_WIDTH + ARROW_PADDING + (borderInsets.left + borderInsets.right); ARROW_WIDTH + ARROW_PADDING + (borderInsets.left + borderInsets.right);
buttonBorder.paintBorder(this, g, leftIconWidth, 0, rightButtonWidth, getHeight()); buttonBorder.paintBorder(this, g, leftIconWidth, 0, rightButtonWidth, getHeight());
@ -132,10 +114,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
} }
private Shape createPopupContext() { private Shape createPopupContext() {
if (entireButtonShowsPopupMenu) {
return new Rectangle(0, 0, getWidth(), getHeight());
}
Border buttonBorder = getBorder(); Border buttonBorder = getBorder();
Insets borderInsets = Insets borderInsets =
buttonBorder == null ? new Insets(0, 0, 0, 0) : buttonBorder.getBorderInsets(this); 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 * Show a popup containing all the actions below the button
* @param listener for the created popup menu
* @return the popup menu that was shown * @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(); JPopupMenu menu = new JPopupMenu();
List<DockingActionIf> actionList = multipleAction.getActionList(getActionContext()); List<DockingActionIf> actionList = multipleAction.getActionList(getActionContext());
for (DockingActionIf dockingAction : actionList) { for (DockingActionIf dockingAction : actionList) {
@ -202,9 +201,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
menu.add(item); menu.add(item);
} }
if (listener != null) { menu.addPopupMenuListener(popupListener);
menu.addPopupMenuListener(listener);
}
Point p = getPopupPoint(); Point p = getPopupPoint();
menu.show(this, p.x, p.y); menu.show(this, p.x, p.y);
return menu; return menu;
@ -215,6 +212,10 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
return new Point(0, bounds.y + bounds.height); return new Point(0, bounds.y + bounds.height);
} }
private boolean popupIsShowing() {
return (popupMenu != null) && popupMenu.isVisible();
}
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
@ -285,8 +286,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
private class PopupMouseListener extends MouseAdapter implements PopupMenuListener { private class PopupMouseListener extends MouseAdapter implements PopupMenuListener {
private final MouseListener[] parentListeners; 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) { public PopupMouseListener(MouseListener[] parentListeners) {
this.parentListeners = parentListeners; this.parentListeners = parentListeners;
@ -294,16 +293,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
@Override @Override
public void mousePressed(MouseEvent e) { 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(); Point clickPoint = e.getPoint();
if (isEnabled() && popupContext.contains(clickPoint)) { 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 // 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 // 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 // correct before we show, since our menu is built with actions based upon the
// focused dude. // focused component.
Swing.runLater(() -> popupMenu = showPopup(PopupMouseListener.this)); Swing.runLater(() -> popupMenu = showPopup());
e.consume(); e.consume();
model.setPressed(false); model.setPressed(false);
@ -372,10 +361,6 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
} }
} }
private boolean popupIsShowing() {
return (popupMenu != null) && popupMenu.isVisible();
}
@Override @Override
public void popupMenuCanceled(PopupMenuEvent e) { public void popupMenuCanceled(PopupMenuEvent e) {
// no-op // no-op
@ -383,7 +368,7 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
@Override @Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
actionID = System.currentTimeMillis(); // hacktastic! popupLastClosedTime = System.currentTimeMillis();
} }
@Override @Override

View file

@ -15,7 +15,13 @@
*/ */
package docking.menu; package docking.menu;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import docking.action.DockingAction;
import docking.widgets.EventTrigger; 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 * 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 #actionStateChanged(ActionState, EventTrigger)} callback. Call
* {@link #createButton()} and add the return value to your UI. * {@link #createButton()} and add the return value to your UI.
* *
* @param <T> * @param <T> the type
* @see MultiStateDockingAction * @see MultiStateDockingAction
*/ */
public abstract class NonToolbarMultiStateAction<T> extends MultiStateDockingAction<T> { public abstract class NonToolbarMultiStateAction<T> extends MultiStateDockingAction<T> {
// 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) { public NonToolbarMultiStateAction(String name, String owner) {
super(name, 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 <B>when this action is used as a
* custom button provider and not installed into the default {@link DockingAction} framework.
* </B>
*
* 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());
}
} }

View file

@ -28,7 +28,6 @@ import javax.swing.table.TableModel;
import org.jdom.Element; import org.jdom.Element;
import docking.ActionContext;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.help.HelpService; import docking.help.HelpService;
import docking.menu.*; import docking.menu.*;
@ -413,12 +412,12 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
} }
@Override @Override
protected void doActionPerformed(ActionContext context) { protected void actionPerformed() {
showFilterDialog(tableModel); showFilterDialog(tableModel);
} }
}; };
columnFilterAction.setPerformActionOnPrimaryButtonClick(true);
HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters"); HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters");
columnFilterAction.setHelpLocation(helpLocation); columnFilterAction.setHelpLocation(helpLocation);

View file

@ -32,7 +32,7 @@ public interface DataType {
/** /**
* WARNING: do not add <code>default</code> method implementations to this interface. Doing so * WARNING: do not add <code>default</code> 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. * {@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 * The datatypes must be of the same "type" (i.e. structure can only be replacedWith another
* structure. * 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 UnsupportedOperationException if the datatype does not support change.
* @throws IllegalArgumentException if the given datatype is not the same type as this datatype. * @throws IllegalArgumentException if the given datatype is not the same type as this datatype.
*/ */