Merge remote-tracking branch 'origin/GP-5599-dragonmacher-shared-key-binding-fix--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-04-18 06:14:45 -04:00
commit bf1659f11d
7 changed files with 146 additions and 80 deletions

View file

@ -112,7 +112,7 @@ public class LocationReferencesPlugin extends Plugin
// only so the user can bind a key binding to it if they wish. // only so the user can bind a key binding to it if they wish.
new ActionBuilder("Show Xrefs", getName()) new ActionBuilder("Show Xrefs", getName())
.description("Show the Xrefs to the code unit containing the cursor") .description("Show the Xrefs to the code unit containing the cursor")
.validContextWhen(context -> context instanceof ListingActionContext) .withContext(ListingActionContext.class)
.helpLocation(new HelpLocation("CodeBrowserPlugin", "Show_Xrefs")) .helpLocation(new HelpLocation("CodeBrowserPlugin", "Show_Xrefs"))
.onAction(context -> showXrefs(context)) .onAction(context -> showXrefs(context))
.buildAndInstall(tool); .buildAndInstall(tool);

View file

@ -394,7 +394,7 @@ public class ListingCodeComparisonPanel
.toolBarIcon(NEXT_DIFF_ICON) .toolBarIcon(NEXT_DIFF_ICON)
.toolBarGroup(DIFF_NAVIGATE_GROUP) .toolBarGroup(DIFF_NAVIGATE_GROUP)
.keyBinding("ctrl alt N") .keyBinding("ctrl alt N")
.validContextWhen(c -> isValidPanelContext(c)) .validWhen(c -> isValidPanelContext(c))
.enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation())
.onAction(c -> nextAreaDiff(true)) .onAction(c -> nextAreaDiff(true))
.build(); .build();
@ -409,7 +409,7 @@ public class ListingCodeComparisonPanel
.toolBarIcon(PREVIOUS_DIFF_ICON) .toolBarIcon(PREVIOUS_DIFF_ICON)
.toolBarGroup(DIFF_NAVIGATE_GROUP) .toolBarGroup(DIFF_NAVIGATE_GROUP)
.keyBinding("ctrl alt P") .keyBinding("ctrl alt P")
.validContextWhen(c -> isValidPanelContext(c)) .validWhen(c -> isValidPanelContext(c))
.enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation())
.onAction(c -> nextAreaDiff(false)) .onAction(c -> nextAreaDiff(false))
.build(); .build();

View file

@ -584,5 +584,10 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
tool.setStatusInfo(message, true); tool.setStatusInfo(message, true);
Toolkit.getDefaultToolkit().beep(); Toolkit.getDefaultToolkit().beep();
} }
@Override
public String toString() {
return getClass().getSimpleName() + ": " + validActions;
}
} }
} }

View file

@ -570,6 +570,24 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, C extends
return self(); return self();
} }
/**
* @param predicate the predicate
* @return this builder (for chaining)
* @deprecated use {@link #validWhen(Predicate)}
*/
@Deprecated(forRemoval = true, since = "11.4")
public B validContextWhen(Predicate<C> predicate) {
validContextPredicate = Objects.requireNonNull(predicate);
// automatic enablement management triggered, make sure there is a existing enablement
// predicate. The default behavior of manual management interferes with automatic management.
if (enabledPredicate == null) {
enabledPredicate = ALWAYS_TRUE;
}
return self();
}
/** /**
* Sets a predicate for dynamically determining if this action is valid for the current * Sets a predicate for dynamically determining if this action is valid for the current
* {@link ActionContext}. See {@link DockingActionIf#isValidContext(ActionContext)}. * {@link ActionContext}. See {@link DockingActionIf#isValidContext(ActionContext)}.
@ -584,7 +602,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, C extends
* validity for a given {@link ActionContext} * validity for a given {@link ActionContext}
* @return this builder (for chaining) * @return this builder (for chaining)
*/ */
public B validContextWhen(Predicate<C> predicate) { public B validWhen(Predicate<C> predicate) {
validContextPredicate = Objects.requireNonNull(predicate); validContextPredicate = Objects.requireNonNull(predicate);
// automatic enablement management triggered, make sure there is a existing enablement // automatic enablement management triggered, make sure there is a existing enablement

View file

@ -211,14 +211,20 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
} }
private void setKeyBindingData(DockingActionIf action, ActionTrigger actionTrigger) { private void setKeyBindingData(DockingActionIf action, ActionTrigger actionTrigger) {
KeyBindingData kbData = null;
KeyBindingData newKbData = null;
if (actionTrigger != null) { if (actionTrigger != null) {
kbData = new KeyBindingData(actionTrigger); newKbData = new KeyBindingData(actionTrigger);
}
KeyBindingData currentKbData = action.getKeyBindingData();
if (Objects.equals(currentKbData, newKbData)) {
return;
} }
// we use the 'unvalidated' call since this value is provided by the user--we assume // we use the 'unvalidated' call since this value is provided by the user--we assume
// that user input is correct; we only validate programmer input // that user input is correct; we only validate programmer input
action.setUnvalidatedKeyBindingData(kbData); action.setUnvalidatedKeyBindingData(newKbData);
} }
private ActionTrigger getActionTriggerFromOptions(ActionTrigger validatedTrigger) { private ActionTrigger getActionTriggerFromOptions(ActionTrigger validatedTrigger) {
@ -232,6 +238,27 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
return data.getActionTrigger(); return data.getActionTrigger();
} }
/*
* This can be called from ToolActions.optionsRebuilt(). In that case, the code only
* loops over KeyBindingType.INDIVIDAL actions types (which this class is), but not
* KeyBindingType.SHARED actions, which the contained actions are. In the case of options
* getting restored, 1) only this action gets updated, and 2), there is no options changed
* event fired. Thus, to handle options being restored, we have to override this method to make
* sure we update out internal client actions.
*/
@Override
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData) {
// update this shared key binding
super.setUnvalidatedKeyBindingData(newKeyBindingData);
// update the client keybindings
ActionTrigger newTrigger = getActionTrigger(newKeyBindingData);
for (DockingActionIf action : clientActions.keySet()) {
setKeyBindingData(action, newTrigger);
}
}
@Override @Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue, public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) { Object newValue) {
@ -240,9 +267,11 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
return; // not my binding return; // not my binding
} }
// update this shared key binding
ActionTrigger newTrigger = (ActionTrigger) newValue; ActionTrigger newTrigger = (ActionTrigger) newValue;
setKeyBindingData(this, newTrigger); setKeyBindingData(this, newTrigger);
// update the client keybindings
for (DockingActionIf action : clientActions.keySet()) { for (DockingActionIf action : clientActions.keySet()) {
setKeyBindingData(action, newTrigger); setKeyBindingData(action, newTrigger);
} }
@ -275,6 +304,11 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
keyBindingOptions.removeOptionsChangeListener(this); keyBindingOptions.removeOptionsChangeListener(this);
} }
@Override
public String toString() {
return "Shared Stub Action: " + super.toString();
}
private static void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction, private static void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction,
DockingActionIf existingAction, ActionTrigger existingDefaultTrigger) { DockingActionIf existingAction, ActionTrigger existingDefaultTrigger) {

View file

@ -31,18 +31,18 @@ public class ActionBuilderTest {
@Test @Test
public void testDescription() { public void testDescription() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.description("foo") .description("foo")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertEquals("foo", action.getDescription()); assertEquals("foo", action.getDescription());
} }
@Test @Test
public void testMenuPath() { public void testMenuPath() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.menuPath("foo", "bar") .menuPath("foo", "bar")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getMenuBarData(); MenuData data = action.getMenuBarData();
assertEquals("foo->bar", data.getMenuPathAsString()); assertEquals("foo->bar", data.getMenuPathAsString());
@ -51,10 +51,10 @@ public class ActionBuilderTest {
@Test @Test
public void testMenuGroup() { public void testMenuGroup() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.menuPath("foo", "bar") .menuPath("foo", "bar")
.menuGroup("A", "B") .menuGroup("A", "B")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getMenuBarData(); MenuData data = action.getMenuBarData();
assertEquals("A", data.getMenuGroup()); assertEquals("A", data.getMenuGroup());
@ -64,10 +64,10 @@ public class ActionBuilderTest {
@Test @Test
public void testMenuIcon() { public void testMenuIcon() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.menuPath("foo", "bar") .menuPath("foo", "bar")
.menuIcon(Icons.ADD_ICON) .menuIcon(Icons.ADD_ICON)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getMenuBarData(); MenuData data = action.getMenuBarData();
assertEquals(Icons.ADD_ICON, data.getMenuIcon()); assertEquals(Icons.ADD_ICON, data.getMenuIcon());
@ -76,10 +76,10 @@ public class ActionBuilderTest {
@Test @Test
public void testMenuMnemonic() { public void testMenuMnemonic() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.menuPath("foo", "bar") .menuPath("foo", "bar")
.menuMnemonic(5) .menuMnemonic(5)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getMenuBarData(); MenuData data = action.getMenuBarData();
assertEquals(5, data.getMnemonic()); assertEquals(5, data.getMnemonic());
@ -88,9 +88,9 @@ public class ActionBuilderTest {
@Test @Test
public void testPopupPath() { public void testPopupPath() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.popupMenuPath("foo", "bar") .popupMenuPath("foo", "bar")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getPopupMenuData(); MenuData data = action.getPopupMenuData();
assertEquals("foo->bar", data.getMenuPathAsString()); assertEquals("foo->bar", data.getMenuPathAsString());
@ -99,10 +99,10 @@ public class ActionBuilderTest {
@Test @Test
public void testPopupGroup() { public void testPopupGroup() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.popupMenuPath("foo", "bar") .popupMenuPath("foo", "bar")
.popupMenuGroup("A", "B") .popupMenuGroup("A", "B")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getPopupMenuData(); MenuData data = action.getPopupMenuData();
assertEquals("A", data.getMenuGroup()); assertEquals("A", data.getMenuGroup());
@ -112,10 +112,10 @@ public class ActionBuilderTest {
@Test @Test
public void testPopupIcon() { public void testPopupIcon() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.popupMenuPath("foo", "bar") .popupMenuPath("foo", "bar")
.popupMenuIcon(Icons.ADD_ICON) .popupMenuIcon(Icons.ADD_ICON)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
MenuData data = action.getPopupMenuData(); MenuData data = action.getPopupMenuData();
assertEquals(Icons.ADD_ICON, data.getMenuIcon()); assertEquals(Icons.ADD_ICON, data.getMenuIcon());
@ -124,9 +124,9 @@ public class ActionBuilderTest {
@Test @Test
public void testToolbarIcon() { public void testToolbarIcon() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.toolBarIcon(Icons.ADD_ICON) .toolBarIcon(Icons.ADD_ICON)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
ToolBarData data = action.getToolBarData(); ToolBarData data = action.getToolBarData();
assertEquals(Icons.ADD_ICON, data.getIcon()); assertEquals(Icons.ADD_ICON, data.getIcon());
@ -135,10 +135,10 @@ public class ActionBuilderTest {
@Test @Test
public void testToolbarGroup() { public void testToolbarGroup() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.toolBarIcon(Icons.ADD_ICON) .toolBarIcon(Icons.ADD_ICON)
.toolBarGroup("A", "B") .toolBarGroup("A", "B")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
ToolBarData data = action.getToolBarData(); ToolBarData data = action.getToolBarData();
assertEquals("A", data.getToolBarGroup()); assertEquals("A", data.getToolBarGroup());
@ -148,9 +148,9 @@ public class ActionBuilderTest {
@Test @Test
public void testKeyBindingKeyStroke() { public void testKeyBindingKeyStroke() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.keyBinding(KeyStroke.getKeyStroke("A")) .keyBinding(KeyStroke.getKeyStroke("A"))
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertEquals(KeyStroke.getKeyStroke("A"), action.getKeyBinding()); assertEquals(KeyStroke.getKeyStroke("A"), action.getKeyBinding());
} }
@ -158,9 +158,9 @@ public class ActionBuilderTest {
@Test @Test
public void testKeyBindingKeyString() { public void testKeyBindingKeyString() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.keyBinding("ALT A") .keyBinding("ALT A")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertEquals(KeyStroke.getKeyStroke("alt pressed A"), action.getKeyBinding()); assertEquals(KeyStroke.getKeyStroke("alt pressed A"), action.getKeyBinding());
} }
@ -168,8 +168,8 @@ public class ActionBuilderTest {
@Test @Test
public void testOnAction() { public void testOnAction() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.onAction(e -> actionCount = 6) .onAction(e -> actionCount = 6)
.build(); .build();
assertEquals(0, actionCount); assertEquals(0, actionCount);
action.actionPerformed(new DefaultActionContext()); action.actionPerformed(new DefaultActionContext());
@ -179,15 +179,15 @@ public class ActionBuilderTest {
@Test @Test
public void testEnabled() { public void testEnabled() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.enabled(true) .enabled(true)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertTrue(action.isEnabled()); assertTrue(action.isEnabled());
action = new ActionBuilder("Test", "Test") action = new ActionBuilder("Test", "Test")
.enabled(false) .enabled(false)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertFalse(action.isEnabled()); assertFalse(action.isEnabled());
} }
@ -195,9 +195,9 @@ public class ActionBuilderTest {
@Test @Test
public void testEnabledWhen() { public void testEnabledWhen() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.enabledWhen(c -> c.getContextObject() == this) .enabledWhen(c -> c.getContextObject() == this)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertTrue(action.isEnabledForContext(new DefaultActionContext(null, this, null))); assertTrue(action.isEnabledForContext(new DefaultActionContext(null, this, null)));
assertFalse(action.isEnabledForContext(new DefaultActionContext())); assertFalse(action.isEnabledForContext(new DefaultActionContext()));
@ -206,9 +206,9 @@ public class ActionBuilderTest {
@Test @Test
public void testValidContextWhen() { public void testValidContextWhen() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.validContextWhen(c -> c.getContextObject() == this) .validWhen(c -> c.getContextObject() == this)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertTrue(action.isValidContext(new DefaultActionContext(null, this, null))); assertTrue(action.isValidContext(new DefaultActionContext(null, this, null)));
assertFalse(action.isValidContext(new DefaultActionContext())); assertFalse(action.isValidContext(new DefaultActionContext()));
@ -217,9 +217,9 @@ public class ActionBuilderTest {
@Test @Test
public void testPopupWhen() { public void testPopupWhen() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.popupWhen(c -> c.getContextObject() == this) .popupWhen(c -> c.getContextObject() == this)
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertTrue(action.isAddToPopup(new DefaultActionContext(null, this, null))); assertTrue(action.isAddToPopup(new DefaultActionContext(null, this, null)));
assertFalse(action.isAddToPopup(new DefaultActionContext())); assertFalse(action.isAddToPopup(new DefaultActionContext()));
@ -228,10 +228,10 @@ public class ActionBuilderTest {
@Test @Test
public void testWithContext() { public void testWithContext() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.withContext(FooActionContext.class) .withContext(FooActionContext.class)
.enabledWhen(c -> c.foo()) .enabledWhen(c -> c.foo())
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.build(); .build();
assertFalse(action.isEnabledForContext(new DefaultActionContext())); assertFalse(action.isEnabledForContext(new DefaultActionContext()));
assertTrue(action.isEnabledForContext(new FooActionContext())); assertTrue(action.isEnabledForContext(new FooActionContext()));
@ -240,9 +240,9 @@ public class ActionBuilderTest {
@Test @Test
public void testManualEnablement() { public void testManualEnablement() {
DockingAction action = new ActionBuilder("Test", "Test") DockingAction action = new ActionBuilder("Test", "Test")
.onAction(e -> actionCount++) .onAction(e -> actionCount++)
.enabled(false) .enabled(false)
.build(); .build();
assertFalse(action.isEnabledForContext(new DefaultActionContext())); assertFalse(action.isEnabledForContext(new DefaultActionContext()));
action.setEnabled(true); action.setEnabled(true);

View file

@ -17,6 +17,7 @@ package ghidra.framework.main.datatree;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import docking.ActionContext;
import docking.action.KeyBindingData; import docking.action.KeyBindingData;
import ghidra.framework.main.datatable.ProjectTreeAction; import ghidra.framework.main.datatable.ProjectTreeAction;
@ -30,10 +31,18 @@ public class ClearCutAction extends ProjectTreeAction {
} }
@Override @Override
public boolean isEnabledForContext(FrontEndProjectTreeContext context) { public boolean isValidContext(ActionContext context) {
return DataTreeClipboardUtils.isCuttablePresent(); return DataTreeClipboardUtils.isCuttablePresent();
} }
@Override
public boolean isEnabledForContext(FrontEndProjectTreeContext context) {
// If we are valid, then we are enabled (see isValidContext()). Most actions are always
// valid, but only sometimes enabled. We use the valid check to remove ourselves completely
// from the workflow. But, if we are valid, then we are also enabled.
return true;
}
@Override @Override
public void actionPerformed(FrontEndProjectTreeContext context) { public void actionPerformed(FrontEndProjectTreeContext context) {
DataTreeClipboardUtils.clearCuttables(); DataTreeClipboardUtils.clearCuttables();