mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-26 06:02:29 +00:00
Changes to ActionBuilder to allow creating actions that work on more
specific ActionContext
This commit is contained in:
parent
15468c7b1a
commit
d181cec070
@ -41,8 +41,15 @@ import resources.ResourceManager;
|
||||
*
|
||||
* @param <T> The type of DockingAction to build
|
||||
* @param <B> the Type of action builder
|
||||
* @param <C> The type of ActionContext. By default, the ActionContext type always starts as
|
||||
* the base ActionContext class. If the client calls the {@link #withContext(Class)} method on
|
||||
* the builder, then that class (which must be a subclass of ActionContext) becomes the ActionContext
|
||||
* type that will be used for future calls to the builder methods that take predicates with
|
||||
* ActionContext (i.e. {@link #enabledWhen(Predicate)} and {@link #validContextWhen(Predicate)}.
|
||||
* This works by substituting a builder with a different ActionContext type when chaining after
|
||||
* the {@link #withContext(Class)} call.
|
||||
*/
|
||||
public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends AbstractActionBuilder<T, B>> {
|
||||
public abstract class AbstractActionBuilder<T extends DockingActionIf, C extends ActionContext, B extends AbstractActionBuilder<T, C, B>> {
|
||||
|
||||
/**
|
||||
* Name for the {@code DockingAction}
|
||||
@ -54,6 +61,11 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
*/
|
||||
protected String owner;
|
||||
|
||||
/**
|
||||
* Specifies the type of ActionContext that the built action works on.
|
||||
*/
|
||||
protected Class<? extends ActionContext> actionContextClass;
|
||||
|
||||
/**
|
||||
* The {@code KeyBindingType} for this {@code DockingAction}
|
||||
*/
|
||||
@ -62,7 +74,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
/**
|
||||
* The callback to perform when the action is invoked
|
||||
*/
|
||||
protected Consumer<ActionContext> actionCallback;
|
||||
protected Consumer<C> actionCallback;
|
||||
|
||||
/**
|
||||
* Description for the {@code DockingAction}. (optional)
|
||||
@ -147,17 +159,17 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
/**
|
||||
* Predicate for determining if an action is enabled for a given context
|
||||
*/
|
||||
private Predicate<ActionContext> enabledPredicate;
|
||||
private Predicate<C> enabledPredicate;
|
||||
|
||||
/**
|
||||
* Predicate for determining if an action should be included on the pop-up menu
|
||||
*/
|
||||
private Predicate<ActionContext> popupPredicate;
|
||||
private Predicate<C> popupPredicate;
|
||||
|
||||
/**
|
||||
* Predicate for determining if an action is applicable for a given context
|
||||
*/
|
||||
private Predicate<ActionContext> validContextPredicate;
|
||||
private Predicate<C> validContextPredicate;
|
||||
|
||||
/**
|
||||
* Builder constructor
|
||||
@ -167,6 +179,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
public AbstractActionBuilder(String name, String owner) {
|
||||
this.name = name;
|
||||
this.owner = owner;
|
||||
this.actionContextClass = ActionContext.class;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -370,7 +383,8 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
* @see #popupMenuGroup(String)
|
||||
*/
|
||||
public B popupMenuGroup(String group, String subGroup) {
|
||||
popupSubGroup = group;
|
||||
popupGroup = group;
|
||||
popupSubGroup = subGroup;
|
||||
return self();
|
||||
}
|
||||
|
||||
@ -446,7 +460,8 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
* @see #toolBarGroup(String)
|
||||
*/
|
||||
public B toolBarGroup(String group, String subGroup) {
|
||||
toolBarSubGroup = group;
|
||||
toolBarGroup = group;
|
||||
toolBarSubGroup = subGroup;
|
||||
return self();
|
||||
}
|
||||
|
||||
@ -484,7 +499,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
* @param action the callback to execute when the action is invoked
|
||||
* @return this builder (for chaining)
|
||||
*/
|
||||
public B onAction(Consumer<ActionContext> action) {
|
||||
public B onAction(Consumer<C> action) {
|
||||
actionCallback = action;
|
||||
return self();
|
||||
}
|
||||
@ -501,7 +516,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
* enabled state
|
||||
* @return this builder (for chaining)
|
||||
*/
|
||||
public B enabledWhen(Predicate<ActionContext> predicate) {
|
||||
public B enabledWhen(Predicate<C> predicate) {
|
||||
enabledPredicate = predicate;
|
||||
return self();
|
||||
}
|
||||
@ -524,7 +539,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
* @return this builder (for chaining)
|
||||
* @see #popupMenuPath(String...)
|
||||
*/
|
||||
public B popupWhen(Predicate<ActionContext> predicate) {
|
||||
public B popupWhen(Predicate<C> predicate) {
|
||||
popupPredicate = predicate;
|
||||
return self();
|
||||
}
|
||||
@ -540,11 +555,68 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
* validity for a given {@link ActionContext}
|
||||
* @return this builder (for chaining)
|
||||
*/
|
||||
public B validContextWhen(Predicate<ActionContext> predicate) {
|
||||
public B validContextWhen(Predicate<C> predicate) {
|
||||
validContextPredicate = predicate;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specific ActionContext type to use for the various predicate calls
|
||||
* ({@link #validContextWhen(Predicate)}, {@link #enabledWhen(Predicate)}, and
|
||||
* {@link #popupWhen(Predicate)}).
|
||||
* <P>
|
||||
* In other words, this allows the client to specify the type of ActionContext that is valid for
|
||||
* the action being built.
|
||||
* <P>
|
||||
* To be effective, this method must be called <b>before</b> setting any of the predicates
|
||||
* such as the {@link #enabledWhen(Predicate)}. Once this method is called you can define your
|
||||
* predicates using the more specific ActionContext and be assured your predicates will only
|
||||
* be called when the current action context is the type (or sub-type) of the context you have
|
||||
* specified here.
|
||||
* <P>
|
||||
* For example, assume you have an action that is only enabled when the context is of type
|
||||
* FooActionContext. If you don't call this method to set the ActionContext type, you would have
|
||||
* to write your predicate something like this:
|
||||
* <pre>
|
||||
* builder.enabledWhen(context -> {
|
||||
* if (!(context instanceof FooContext)) {
|
||||
* return false;
|
||||
* }
|
||||
* return ((FooContext) context).isAwesome();
|
||||
* });
|
||||
* </pre>
|
||||
* But by first calling the builder method <CODE>withContext(FooContext.class)</CODE>, you can
|
||||
* simply write:
|
||||
*
|
||||
* <pre>
|
||||
* builder.enabledWhen(context -> return context.isAwesome() }
|
||||
* </pre>
|
||||
*
|
||||
* @param newActionContextClass the more specific ActionContext type.
|
||||
* @param <AC2> The new ActionContext type (as determined by the newActionContextClass) that
|
||||
* the returned builder will have.
|
||||
* @param <B2> the new builder type.
|
||||
* @return an ActionBuilder whose generic types have been modified to match the new ActionContext.
|
||||
* It still contains all the configuration that has been applied so far.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <AC2 extends ActionContext, B2 extends AbstractActionBuilder<T, AC2, B2>> B2 withContext(
|
||||
Class<AC2> newActionContextClass) {
|
||||
|
||||
// To make this work, we need to return a builder whose ActionContext is AC2 and not AC
|
||||
// (which is what this builder is now)
|
||||
//
|
||||
// Since we "know" that the only thing that matters regarding the ActionContext type is that
|
||||
// the template type (AC) must match the type of actionContextClass instance variable, we
|
||||
// can get away with returning this same builder and casting it to be a builder with type
|
||||
// AC2 instead of AC. We can do this since we set the actionContextClass below
|
||||
|
||||
actionContextClass = newActionContextClass;
|
||||
|
||||
B2 newSelf = (B2) self();
|
||||
return newSelf;
|
||||
}
|
||||
|
||||
protected void validate() {
|
||||
if (actionCallback == null) {
|
||||
throw new IllegalStateException(
|
||||
@ -566,16 +638,36 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
|
||||
}
|
||||
|
||||
if (enabledPredicate != null) {
|
||||
action.enabledWhen(enabledPredicate);
|
||||
action.enabledWhen(adaptPredicate(enabledPredicate));
|
||||
}
|
||||
if (validContextPredicate != null) {
|
||||
action.validContextWhen(validContextPredicate);
|
||||
action.validContextWhen(adaptPredicate(validContextPredicate));
|
||||
}
|
||||
if (popupPredicate != null) {
|
||||
action.popupWhen(enabledPredicate);
|
||||
action.popupWhen(adaptPredicate(popupPredicate));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the built action will need a predicate that handles any action type, this method
|
||||
* creates a predicate that adapts a user supplied predicate for a more specific ActionContext
|
||||
* to a general predicate that can accept any ActionContext.
|
||||
* @param predicate the client supplied predicate that expects a more specific ActionContext
|
||||
* @return a predicate that can handle any ActionContext
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Predicate<ActionContext> adaptPredicate(Predicate<C> predicate) {
|
||||
if (actionContextClass == ActionContext.class) {
|
||||
// don't wrap the predicate if it doesn't need it
|
||||
return (Predicate<ActionContext>) predicate;
|
||||
}
|
||||
// Convert a sub-classed ActionContext predicate to a plain ActionContext predicate
|
||||
Predicate<ActionContext> predicateAdapter = (ac) -> {
|
||||
return actionContextClass.isInstance(ac) && predicate.test((C) ac);
|
||||
};
|
||||
return predicateAdapter;
|
||||
}
|
||||
|
||||
protected boolean isPopupAction() {
|
||||
return popupPath != null;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import docking.action.DockingAction;
|
||||
* Builder for {@link DockingAction}s
|
||||
*/
|
||||
public class ActionBuilder
|
||||
extends AbstractActionBuilder<DockingAction, ActionBuilder> {
|
||||
extends AbstractActionBuilder<DockingAction, ActionContext, ActionBuilder> {
|
||||
|
||||
/**
|
||||
* Builder constructor
|
||||
|
@ -26,7 +26,7 @@ import docking.menu.MultiActionDockingAction;
|
||||
* Builder for {@link MultiActionDockingAction}
|
||||
*/
|
||||
public class MultiActionBuilder
|
||||
extends AbstractActionBuilder<MultiActionDockingAction, MultiActionBuilder> {
|
||||
extends AbstractActionBuilder<MultiActionDockingAction, ActionContext, MultiActionBuilder> {
|
||||
/**
|
||||
* List of actions for the the MultActionDockingAction
|
||||
*/
|
||||
|
@ -27,7 +27,7 @@ import docking.widgets.EventTrigger;
|
||||
* @param <T> The action state type
|
||||
*/
|
||||
public class MultiStateActionBuilder<T> extends
|
||||
AbstractActionBuilder<MultiStateDockingAction<T>, MultiStateActionBuilder<T>> {
|
||||
AbstractActionBuilder<MultiStateDockingAction<T>, ActionContext, MultiStateActionBuilder<T>> {
|
||||
|
||||
private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback;
|
||||
private boolean performActionOnButtonClick;
|
||||
@ -77,7 +77,7 @@ public class MultiStateActionBuilder<T> extends
|
||||
public MultiStateDockingAction<T> build() {
|
||||
validate();
|
||||
MultiStateDockingAction<T> action =
|
||||
new MultiStateDockingAction<T>(name, owner, isToolbarAction()) {
|
||||
new MultiStateDockingAction<>(name, owner, isToolbarAction()) {
|
||||
|
||||
@Override
|
||||
public void actionStateChanged(ActionState<T> newActionState,
|
||||
|
@ -21,7 +21,7 @@ import docking.action.ToggleDockingAction;
|
||||
* Builder for {@link ToggleDockingAction}s
|
||||
*/
|
||||
public class ToggleActionBuilder extends
|
||||
AbstractActionBuilder<ToggleDockingAction, ToggleActionBuilder> {
|
||||
AbstractActionBuilder<ToggleDockingAction, ActionContext, ToggleActionBuilder> {
|
||||
|
||||
/**
|
||||
* The initial toggle state for the action
|
||||
|
@ -0,0 +1,247 @@
|
||||
/* ###
|
||||
* 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 docking.action;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import resources.Icons;
|
||||
|
||||
public class ActionBuilderTest {
|
||||
private int actionCount = 0;
|
||||
|
||||
|
||||
@Test
|
||||
public void testDescription() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.description("foo")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
assertEquals("foo", action.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMenuPath() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.menuPath("foo", "bar")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getMenuBarData();
|
||||
assertEquals("foo->bar", data.getMenuPathAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMenuGroup() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.menuPath("foo", "bar")
|
||||
.menuGroup("A", "B")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getMenuBarData();
|
||||
assertEquals("A", data.getMenuGroup());
|
||||
assertEquals("B", data.getMenuSubGroup());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMenuIcon() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.menuPath("foo", "bar")
|
||||
.menuIcon(Icons.ADD_ICON)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getMenuBarData();
|
||||
assertEquals(Icons.ADD_ICON, data.getMenuIcon());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMenuMnemonic() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.menuPath("foo", "bar")
|
||||
.menuMnemonic(5)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getMenuBarData();
|
||||
assertEquals(5, data.getMnemonic());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopupPath() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.popupMenuPath("foo", "bar")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getPopupMenuData();
|
||||
assertEquals("foo->bar", data.getMenuPathAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopupGroup() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.popupMenuPath("foo", "bar")
|
||||
.popupMenuGroup("A", "B")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getPopupMenuData();
|
||||
assertEquals("A", data.getMenuGroup());
|
||||
assertEquals("B", data.getMenuSubGroup());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopupIcon() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.popupMenuPath("foo", "bar")
|
||||
.popupMenuIcon(Icons.ADD_ICON)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
MenuData data = action.getPopupMenuData();
|
||||
assertEquals(Icons.ADD_ICON, data.getMenuIcon());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToolbarIcon() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.toolBarIcon(Icons.ADD_ICON)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
ToolBarData data = action.getToolBarData();
|
||||
assertEquals(Icons.ADD_ICON, data.getIcon());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToolbarGroup() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.toolBarIcon(Icons.ADD_ICON)
|
||||
.toolBarGroup("A", "B")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
ToolBarData data = action.getToolBarData();
|
||||
assertEquals("A", data.getToolBarGroup());
|
||||
assertEquals("B", data.getToolBarSubGroup());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyBindingKeyStroke() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.keyBinding(KeyStroke.getKeyStroke("A"))
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
assertEquals(KeyStroke.getKeyStroke("A"), action.getKeyBinding());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyBindingKeyString() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.keyBinding("ALT A")
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
assertEquals(KeyStroke.getKeyStroke("ALT A"), action.getKeyBinding());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAction() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.onAction(e -> actionCount = 6)
|
||||
.build();
|
||||
|
||||
assertEquals(0, actionCount);
|
||||
action.actionPerformed(new ActionContext());
|
||||
assertEquals(6, actionCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabled() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.enabled(true)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
assertTrue(action.isEnabled());
|
||||
|
||||
action = new ActionBuilder("Test", "Test")
|
||||
.enabled(false)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
assertFalse(action.isEnabled());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledWhen() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.enabledWhen(c -> c.getContextObject() == this)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
assertTrue(action.isEnabledForContext(new ActionContext(null, this, null)));
|
||||
assertFalse(action.isEnabledForContext(new ActionContext()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidContextWhen() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.validContextWhen(c -> c.getContextObject() == this)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
assertTrue(action.isValidContext(new ActionContext(null, this, null)));
|
||||
assertFalse(action.isValidContext(new ActionContext()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPopupWhen() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.popupWhen(c -> c.getContextObject() == this)
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
assertTrue(action.isAddToPopup(new ActionContext(null, this, null)));
|
||||
assertFalse(action.isAddToPopup(new ActionContext()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithContext() {
|
||||
DockingAction action = new ActionBuilder("Test", "Test")
|
||||
.withContext(FooActionContext.class)
|
||||
.enabledWhen(c -> c.foo())
|
||||
.onAction(e -> actionCount++)
|
||||
.build();
|
||||
|
||||
assertFalse(action.isEnabledForContext(new ActionContext()));
|
||||
assertTrue(action.isEnabledForContext(new FooActionContext()));
|
||||
}
|
||||
|
||||
static class FooActionContext extends ActionContext {
|
||||
public boolean foo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user