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) {
MultiStateActionBuilder<AutoReadMemorySpec> builder = AutoReadMemoryAction.builder(owner);
builder.toolBarGroup(NAME);
builder.performActionOnButtonClick(true);
for (AutoReadMemorySpec spec : AutoReadMemorySpec.allSpecs().values()) {
builder.addState(spec.getMenuName(), spec.getMenuIcon(), spec);
}

View file

@ -25,7 +25,6 @@ public interface DebuggerTrackLocationAction extends TrackLocationAction {
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);
builder.toolBarGroup(owner.getName());
builder.performActionOnButtonClick(true);
for (LocationTrackingSpec spec : LocationTrackingSpec.allSpecs().values()) {
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.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<DebuggerProgramLaunchOffer> offers = program == null ? List.of()
: getProgramLaunchOffers(program).collect(Collectors.toList());
List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream()
.map(o -> new ActionState<DebuggerProgramLaunchOffer>(o.getButtonTitle(),
.map(o -> new ActionState<>(o.getButtonTitle(),
o.getIcon(), o))
.collect(Collectors.toList());
if (!states.isEmpty()) {

View file

@ -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<SampleVertex, SampleEdge, SampleGraph> currentUserData =
getCurrentUserData();

View file

@ -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<DataTypeConflictHandler.ConflictResolutionPolicy> {
@ -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<DataTypeConflictHandler.ConflictResolutionPolicy> renameAndAddState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>(
new ActionState<>(
"Rename New or Moved Data Type", renameAndAddIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.RENAME_AND_ADD);
renameAndAddState.setHelpLocation(conflictModesHelpLocation);
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> useExistingState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>(
new ActionState<>(
"Use Existing Data Type", useExistingIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.USE_EXISTING);
useExistingState.setHelpLocation(conflictModesHelpLocation);
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> replaceExistingState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>(
new ActionState<>(
"Replace Existing Data Type", replaceExistingIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EXISTING);
replaceExistingState.setHelpLocation(conflictModesHelpLocation);
ActionState<DataTypeConflictHandler.ConflictResolutionPolicy> replaceDefaultState =
new ActionState<DataTypeConflictHandler.ConflictResolutionPolicy>(
new ActionState<>(
"Replace Empty Structures else Rename",
replaceDefaultIcon,
DataTypeConflictHandler.ConflictResolutionPolicy.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD);

View file

@ -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<String>
}
@Override
protected void doActionPerformed(ActionContext context) {
public void actionPerformed(ActionContext context) {
if (context instanceof NavigatableActionContext) {
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,
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<DockingActionIf>(actionMap.values()));
multiAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png")));

View file

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

View file

@ -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),

View file

@ -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

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -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<FcgVertex, FcgEdge, FunctionCallGraph> currentUserData =
getCurrentUserData();

View file

@ -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<TableSelectionTrackingState> {
public class MatchTableSelectionAction
extends MultiStateDockingAction<TableSelectionTrackingState> {
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));
setDescription("Adjust the Apply Mark-up Settings for Applying Matches");
setEnabled(true);
setPerformActionOnPrimaryButtonClick(false); // pressing button shows drop-down
HelpLocation helpLocation =
new HelpLocation("VersionTrackingPlugin", "Match_Table_Selection");
@ -55,17 +54,17 @@ public class MatchTableSelectionAction extends MultiStateDockingAction<TableSele
Icon trackRowIndexSelectionIcon = ResourceManager.loadImage("images/table_gear.png");
ActionState<TableSelectionTrackingState> trackSelectedIndexActionState =
new ActionState<TableSelectionTrackingState>("Track Selected Index",
new ActionState<>("Track Selected Index",
trackRowIndexSelectionIcon, MAINTAIN_SELECTED_ROW_INDEX);
trackSelectedIndexActionState.setHelpLocation(helpLocation);
ActionState<TableSelectionTrackingState> trackMatchSelectionActionState =
new ActionState<TableSelectionTrackingState>("Track Selected Match",
new ActionState<>("Track Selected Match",
trackMatchSelectionIcon, MAINTAIN_SELECTED_ROW_VALUE);
trackMatchSelectionActionState.setHelpLocation(helpLocation);
ActionState<TableSelectionTrackingState> noSelectionTrackingActionState =
new ActionState<TableSelectionTrackingState>("No Selection Tracking",
new ActionState<>("No Selection Tracking",
noSelectionTrackingIcon, NO_SELECTION_TRACKING);
noSelectionTrackingActionState.setHelpLocation(helpLocation);

View file

@ -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");

View file

@ -31,10 +31,6 @@ public class MultiActionBuilder
* List of actions for the the MultActionDockingAction
*/
private List<DockingActionIf> 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");
}

View file

@ -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<T> extends
private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback;
private boolean useCheckboxForIcons;
private boolean performActionOnButtonClick = false;
private List<ActionState<T>> states = new ArrayList<>();
@ -68,18 +68,6 @@ public class MultiStateActionBuilder<T> 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<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
* 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)
*/
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();
}
@ -133,7 +121,7 @@ public class MultiStateActionBuilder<T> extends
public MultiStateDockingAction<T> build() {
validate();
MultiStateDockingAction<T> action =
new MultiStateDockingAction<>(name, owner, isToolbarAction()) {
new MultiStateDockingAction<>(name, owner) {
@Override
public void actionStateChanged(ActionState<T> newActionState,
@ -142,10 +130,13 @@ public class MultiStateActionBuilder<T> 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<T> 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");

View file

@ -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.
* <p>
* 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.
* <p>
* Actions added must have menu bar data set.
*
@ -36,18 +37,20 @@ import docking.action.*;
* the user to execute.
*
* <p>
* 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<DockingActionIf> 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 <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
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 <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 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() {

View file

@ -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.
*
* <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.
* 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)}.
*
* <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
* @see MultiActionDockingAction
*/
@ -54,16 +52,9 @@ public abstract class MultiStateDockingAction<T> 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<T> 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<T> 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<T> newActionState, EventTrigger trigger);
/**
* If <code>doPerformAction</code> is <code>true</code>, then, when the user clicks the
* button and not the drop-down arrow, the {@link #doActionPerformed(ActionContext)}
* 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
* as if the user had clicked the drop-down arrow.
* <p>
* 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 <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.
*/
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<T> 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<DockingActionIf> getStateActions() {
ActionState<T> selectedState = actionStates.get(currentStateIndex);
List<DockingActionIf> actions = new ArrayList<>(actionStates.size());
@ -295,15 +239,6 @@ public abstract class MultiStateDockingAction<T> 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<T> actionState = actionStates.get(currentStateIndex);
setButtonState(actionState);
@ -350,6 +285,10 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
return getName() + ": " + getCurrentState().getName();
}
protected void showPopup() {
multipleButton.showPopup();
}
//==================================================================================================
// Inner Classes
//==================================================================================================

View file

@ -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 <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
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<DockingActionIf> 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

View file

@ -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 <T>
* @param <T> the type
* @see MultiStateDockingAction
*/
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) {
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 docking.ActionContext;
import docking.DockingWindowManager;
import docking.help.HelpService;
import docking.menu.*;
@ -413,12 +412,12 @@ public class GTableFilterPanel<ROW_OBJECT> 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);

View file

@ -32,7 +32,7 @@ public interface DataType {
/**
* 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.
*/
@ -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.
*/