mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GT-2925 - Key Bindings - Support Window Menu Provider Key Bindings -
Step 6 - added action to 'Go To Last Provider'; cleanup for review
This commit is contained in:
parent
115243801e
commit
d684ee3ce6
7 changed files with 340 additions and 53 deletions
|
@ -0,0 +1,103 @@
|
|||
/* ###
|
||||
* 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.KeyEvent;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.*;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.plugintool.util.ToolConstants;
|
||||
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.NAVIGATION,
|
||||
shortDescription = "Component Provider Navigation",
|
||||
description = "The plugin provides actions to manage switching between Component Providers."
|
||||
)
|
||||
//@formatter:on
|
||||
public class ProviderNavigationPlugin extends Plugin {
|
||||
|
||||
static final String GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME = "Go To Last Active Component";
|
||||
|
||||
private ComponentProvider previousActiveProvider;
|
||||
private ComponentProvider currentActiveProvider;
|
||||
private Consumer<ComponentProvider> providerActivator =
|
||||
provider -> tool.showComponentProvider(provider, true);
|
||||
|
||||
private DockingContextListener contextListener = context -> {
|
||||
|
||||
ComponentProvider componentProvider = context.getComponentProvider();
|
||||
if (componentProvider != null) {
|
||||
if (componentProvider != currentActiveProvider) {
|
||||
previousActiveProvider = currentActiveProvider;
|
||||
currentActiveProvider = componentProvider;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ProviderNavigationPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
createActions();
|
||||
|
||||
tool.addContextListener(contextListener);
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
|
||||
DockingAction previousProviderAction =
|
||||
new DockingAction(GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME, getName()) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
providerActivator.accept(previousActiveProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return previousActiveProvider != null;
|
||||
}
|
||||
};
|
||||
previousProviderAction.setMenuBarData(new MenuData(
|
||||
new String[] { ToolConstants.MENU_NAVIGATION, GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME },
|
||||
null, ToolConstants.MENU_NAVIGATION_GROUP_WINDOWS, MenuData.NO_MNEMONIC,
|
||||
"xLowInMenuSubGroup"));
|
||||
previousProviderAction.setKeyBindingData(new KeyBindingData(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_F6, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
|
||||
|
||||
tool.addAction(previousProviderAction);
|
||||
}
|
||||
|
||||
// for testing
|
||||
void resetTrackingState() {
|
||||
previousActiveProvider = null;
|
||||
currentActiveProvider = null;
|
||||
}
|
||||
|
||||
// for testing
|
||||
void setProviderActivator(Consumer<ComponentProvider> newActivator) {
|
||||
this.providerActivator = newActivator;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,15 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.progmgr;
|
||||
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.Timer;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingUtils;
|
||||
import docking.action.*;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.events.*;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
|
@ -23,18 +32,10 @@ import ghidra.app.services.ProgramManager;
|
|||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.plugintool.util.ToolConstants;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
import java.awt.event.*;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.Timer;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingUtils;
|
||||
import docking.action.*;
|
||||
|
||||
/**
|
||||
* Plugin to show a "tab" for each open program; the selected tab is the activated program.
|
||||
*/
|
||||
|
@ -58,10 +59,10 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
// DockingUtils calls into Swing code. Further, we don't want Swing code being accessed
|
||||
// when the Plugin classes are loaded, as they get loaded in the headless environment.
|
||||
//
|
||||
private final KeyStroke NEXT_TAB_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F9,
|
||||
DockingUtils.CONTROL_KEY_MODIFIER_MASK);
|
||||
private final KeyStroke PREVIOUS_TAB_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F8,
|
||||
DockingUtils.CONTROL_KEY_MODIFIER_MASK);
|
||||
private final KeyStroke NEXT_TAB_KEYSTROKE =
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_F9, DockingUtils.CONTROL_KEY_MODIFIER_MASK);
|
||||
private final KeyStroke PREVIOUS_TAB_KEYSTROKE =
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_F8, DockingUtils.CONTROL_KEY_MODIFIER_MASK);
|
||||
|
||||
private MultiTabPanel tabPanel;
|
||||
private ProgramManager progService;
|
||||
|
@ -92,14 +93,17 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
showProgramList();
|
||||
}
|
||||
};
|
||||
goToProgramAction.setMenuBarData(new MenuData(new String[] { "Navigation",
|
||||
"Go To Program..." }, null, "GoToProgram", MenuData.NO_MNEMONIC, firstGroup));
|
||||
goToProgramAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F7,
|
||||
InputEvent.CTRL_DOWN_MASK));
|
||||
goToProgramAction.setMenuBarData(
|
||||
new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, "Go To Program..." }, null,
|
||||
ToolConstants.MENU_NAVIGATION_GROUP_WINDOWS, MenuData.NO_MNEMONIC, firstGroup));
|
||||
goToProgramAction.setKeyBindingData(
|
||||
new KeyBindingData(KeyEvent.VK_F7, InputEvent.CTRL_DOWN_MASK));
|
||||
|
||||
goToProgramAction.setEnabled(false);
|
||||
goToProgramAction.setDescription("Shows the program selection dialog with the current program selected");
|
||||
goToProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin", "Go_To_Program"));
|
||||
goToProgramAction.setDescription(
|
||||
"Shows the program selection dialog with the current program selected");
|
||||
goToProgramAction.setHelpLocation(
|
||||
new HelpLocation("ProgramManagerPlugin", "Go_To_Program"));
|
||||
|
||||
goToNextProgramAction = new DockingAction("Go To Next Program", getName()) {
|
||||
@Override
|
||||
|
@ -109,10 +113,11 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
}
|
||||
};
|
||||
goToNextProgramAction.setEnabled(false);
|
||||
goToNextProgramAction.setDescription("Highlights the next program tab and then switches to that program");
|
||||
goToNextProgramAction.setDescription(
|
||||
"Highlights the next program tab and then switches to that program");
|
||||
goToNextProgramAction.setKeyBindingData(new KeyBindingData(NEXT_TAB_KEYSTROKE));
|
||||
goToNextProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin",
|
||||
"Go_To_Next_And_Previous_Program"));
|
||||
goToNextProgramAction.setHelpLocation(
|
||||
new HelpLocation("ProgramManagerPlugin", "Go_To_Next_And_Previous_Program"));
|
||||
|
||||
goToPreviousProgramAction = new DockingAction("Go To Previous Program", getName()) {
|
||||
@Override
|
||||
|
@ -122,20 +127,14 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
}
|
||||
};
|
||||
goToPreviousProgramAction.setEnabled(false);
|
||||
goToPreviousProgramAction.setMenuBarData(new MenuData(new String[] { "Navigation" }, null,
|
||||
null));
|
||||
goToPreviousProgramAction.setKeyBindingData(new KeyBindingData(PREVIOUS_TAB_KEYSTROKE));
|
||||
goToPreviousProgramAction.setDescription("Highlights the previous program tab and then switches to that program");
|
||||
goToPreviousProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin",
|
||||
"Go_To_Next_And_Previous_Program"));
|
||||
goToPreviousProgramAction.setDescription(
|
||||
"Highlights the previous program tab and then switches to that program");
|
||||
goToPreviousProgramAction.setHelpLocation(
|
||||
new HelpLocation("ProgramManagerPlugin", "Go_To_Next_And_Previous_Program"));
|
||||
|
||||
// this timer is to give the user time to select successive programs before activating one
|
||||
selectHighlightedProgramTimer = new Timer(750, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
selectHighlightedProgram();
|
||||
}
|
||||
});
|
||||
selectHighlightedProgramTimer = new Timer(750, e -> selectHighlightedProgram());
|
||||
selectHighlightedProgramTimer.setRepeats(false);
|
||||
|
||||
goToLastActiveProgramAction = new DockingAction("Go To Last Active Program", getName()) {
|
||||
|
@ -144,14 +143,16 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
switchToProgram(lastActiveProgram);
|
||||
}
|
||||
};
|
||||
goToLastActiveProgramAction.setMenuBarData(new MenuData(new String[] { "Navigation",
|
||||
"Go To Last Active Program" }, null, "GoToProgram", MenuData.NO_MNEMONIC, secondGroup));
|
||||
goToLastActiveProgramAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F6,
|
||||
InputEvent.CTRL_DOWN_MASK));
|
||||
goToLastActiveProgramAction.setMenuBarData(new MenuData(
|
||||
new String[] { ToolConstants.MENU_NAVIGATION, "Go To Last Active Program" }, null,
|
||||
ToolConstants.MENU_NAVIGATION_GROUP_WINDOWS, MenuData.NO_MNEMONIC, secondGroup));
|
||||
goToLastActiveProgramAction.setKeyBindingData(
|
||||
new KeyBindingData(KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK));
|
||||
goToLastActiveProgramAction.setEnabled(false);
|
||||
goToLastActiveProgramAction.setDescription("Activates the last program used before the current program");
|
||||
goToLastActiveProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin",
|
||||
"Go_To_Last_Active_Program"));
|
||||
goToLastActiveProgramAction.setDescription(
|
||||
"Activates the last program used before the current program");
|
||||
goToLastActiveProgramAction.setHelpLocation(
|
||||
new HelpLocation("ProgramManagerPlugin", "Go_To_Last_Active_Program"));
|
||||
|
||||
tool.addAction(goToProgramAction);
|
||||
tool.addAction(goToLastActiveProgramAction);
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/* ###
|
||||
* 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 static org.junit.Assert.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.AbstractProgramBasedTest;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
|
||||
public class ProviderNavigationPluginTest extends AbstractProgramBasedTest {
|
||||
|
||||
private ProviderNavigationPlugin plugin;
|
||||
private DockingActionIf previousProviderAction;
|
||||
|
||||
private SpyProviderActivator spyProviderActivator = new SpyProviderActivator();
|
||||
private Set<DockingContextListener> testContextListeners;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
initialize();
|
||||
|
||||
plugin = env.getPlugin(ProviderNavigationPlugin.class);
|
||||
previousProviderAction =
|
||||
getAction(tool, ProviderNavigationPlugin.GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME);
|
||||
|
||||
fakeOutContextNotification();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void fakeOutContextNotification() {
|
||||
//
|
||||
// This is the mechanism the tool uses for notifying clients of Component Provider
|
||||
// activation. This activation is focus-sensitive, which makes it unreliable for testing.
|
||||
// Thus, replace this mechanism with one that we can control.
|
||||
//
|
||||
DockingWindowManager windowManager = tool.getWindowManager();
|
||||
testContextListeners = new HashSet<>();
|
||||
WeakSet<DockingContextListener> contextListeners =
|
||||
(WeakSet<DockingContextListener>) getInstanceField("contextListeners", windowManager);
|
||||
testContextListeners.addAll(contextListeners.values());
|
||||
contextListeners.clear();
|
||||
|
||||
//
|
||||
// Now, install a spy that allows us to know when our action under test triggers and
|
||||
// with which state it does so.
|
||||
//
|
||||
plugin.setProviderActivator(spyProviderActivator);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Program getProgram() throws Exception {
|
||||
return buildProgram();
|
||||
}
|
||||
|
||||
private Program buildProgram() throws Exception {
|
||||
ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY);
|
||||
builder.createMemory(".text", "0x1001000", 0x6600);
|
||||
return builder.getProgram();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoToLastActiveComponent() {
|
||||
|
||||
clearPluginState();
|
||||
assertPreviousProviderActionNotEnabled();
|
||||
|
||||
ComponentProvider bookmarks = activateProvider("Bookmarks");
|
||||
assertPreviousProviderActionNotEnabled(); // first provider; nothing to go back to
|
||||
|
||||
ComponentProvider dataTypes = activateProvider("DataTypes Provider");
|
||||
assertPreviousProviderActionEnabled();
|
||||
|
||||
// active provider : 'data types'; previous: 'bookmarks'
|
||||
performPreviousProviderAction();
|
||||
assertActivated(bookmarks);
|
||||
|
||||
// active provider : 'bookmarks'; previous: 'data types'
|
||||
performPreviousProviderAction();
|
||||
assertActivated(dataTypes);
|
||||
|
||||
activateProvider("Symbol Table");
|
||||
|
||||
// active provider : 'symbol table'; previous: 'data types'
|
||||
performPreviousProviderAction();
|
||||
assertActivated(dataTypes);
|
||||
}
|
||||
|
||||
private void clearPluginState() {
|
||||
waitForSwing();
|
||||
runSwing(() -> plugin.resetTrackingState());
|
||||
}
|
||||
|
||||
private void assertActivated(ComponentProvider bookmarks) {
|
||||
assertEquals("The active provider was not restored correctly", bookmarks,
|
||||
spyProviderActivator.lastActivated);
|
||||
}
|
||||
|
||||
private void performPreviousProviderAction() {
|
||||
performAction(previousProviderAction, true);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void assertPreviousProviderActionEnabled() {
|
||||
assertTrue(
|
||||
"'" + ProviderNavigationPlugin.GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME + "'" +
|
||||
" should be enabled when there is a previous provider set",
|
||||
previousProviderAction.isEnabledForContext(new ActionContext()));
|
||||
}
|
||||
|
||||
private void assertPreviousProviderActionNotEnabled() {
|
||||
assertFalse(
|
||||
"'" + ProviderNavigationPlugin.GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME + "'" +
|
||||
" should not be enabled when there is no previous provider set",
|
||||
previousProviderAction.isEnabledForContext(new ActionContext()));
|
||||
}
|
||||
|
||||
private ComponentProvider activateProvider(String name) {
|
||||
|
||||
waitForSwing();
|
||||
ComponentProvider provider = tool.getComponentProvider(name);
|
||||
assertNotNull(provider);
|
||||
|
||||
tool.showComponentProvider(provider, true);
|
||||
runSwing(() -> forceActivate(provider));
|
||||
waitForSwing();
|
||||
return provider;
|
||||
}
|
||||
|
||||
private void forceActivate(ComponentProvider provider) {
|
||||
ActionContext context = new ActionContext(provider, provider);
|
||||
for (DockingContextListener l : testContextListeners) {
|
||||
l.contextChanged(context);
|
||||
}
|
||||
}
|
||||
|
||||
private class SpyProviderActivator implements Consumer<ComponentProvider> {
|
||||
|
||||
private ComponentProvider lastActivated;
|
||||
|
||||
@Override
|
||||
public void accept(ComponentProvider c) {
|
||||
|
||||
Msg.out("Spy - activated: " + c);
|
||||
|
||||
lastActivated = c;
|
||||
forceActivate(c);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -551,8 +551,6 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
|||
4) Wire default 'close' action to keybinding
|
||||
5) Add global action for (show last provider)
|
||||
--Navigation menu?
|
||||
6) Revisit all uses of the key binding managed constructor
|
||||
7) Update table popup actions to be managed
|
||||
8) Update help locations
|
||||
|
||||
Questions:
|
||||
|
|
|
@ -87,13 +87,12 @@ public class DockableHeader extends GenericHeader
|
|||
private boolean isDocking;
|
||||
|
||||
private Animator focusAnimator;
|
||||
private int focusToggle = -1;
|
||||
|
||||
/**
|
||||
* Constructs a new DockableHeader for the given dockableComponent.
|
||||
*
|
||||
* @param dockableComp
|
||||
* the dockableComponent that this header is for.
|
||||
* @param dockableComp the dockableComponent that this header is for.
|
||||
* @param isDocking true means this widget can be dragged and docked by the user
|
||||
*/
|
||||
DockableHeader(DockableComponent dockableComp, boolean isDocking) {
|
||||
this.dockComp = dockableComp;
|
||||
|
@ -173,8 +172,11 @@ public class DockableHeader extends GenericHeader
|
|||
}
|
||||
|
||||
protected Animator createEmphasizingAnimator(JFrame parentFrame) {
|
||||
focusToggle += 1;
|
||||
switch (focusToggle) {
|
||||
|
||||
double random = Math.random();
|
||||
int choices = 4;
|
||||
int value = (int) (choices * random);
|
||||
switch (value) {
|
||||
case 0:
|
||||
return AnimationUtils.shakeComponent(component);
|
||||
case 1:
|
||||
|
@ -182,7 +184,6 @@ public class DockableHeader extends GenericHeader
|
|||
case 2:
|
||||
return raiseComponent(parentFrame);
|
||||
default:
|
||||
focusToggle = -1;
|
||||
return AnimationUtils.pulseComponent(component);
|
||||
}
|
||||
}
|
||||
|
@ -226,12 +227,12 @@ public class DockableHeader extends GenericHeader
|
|||
if (!isDocking) {
|
||||
return;
|
||||
}
|
||||
// check input event: if any button other than MB1 is pressed,
|
||||
// don't attempt to process the drag and drop event.
|
||||
|
||||
// if any button other than MB1 is pressed, don't attempt to process the drag and drop event
|
||||
InputEvent ie = event.getTriggerEvent();
|
||||
int modifiers = ie.getModifiers();
|
||||
if ((modifiers & InputEvent.BUTTON2_MASK) != 0 ||
|
||||
(modifiers & InputEvent.BUTTON3_MASK) != 0) {
|
||||
int modifiers = ie.getModifiersEx();
|
||||
if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 ||
|
||||
(modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0) {
|
||||
return;
|
||||
}
|
||||
DockableComponent.DROP_CODE = DockableComponent.DropCode.WINDOW;
|
||||
|
|
|
@ -53,6 +53,9 @@ public abstract class WeakSet<T> implements Iterable<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Note: sadly, this code does not work with labmda's, as we cannot get the enclosing
|
||||
// method/constructor
|
||||
|
||||
Class<? extends Object> clazz = t.getClass();
|
||||
if (!clazz.isAnonymousClass()) {
|
||||
return; // O.K.
|
||||
|
|
|
@ -34,6 +34,12 @@ public interface ToolConstants extends DockingToolConstants {
|
|||
* Used when placing a PluginAction in the "Navigation" menu of the tool.
|
||||
*/
|
||||
String MENU_NAVIGATION = "&Navigation";
|
||||
|
||||
/**
|
||||
* Group name for actions to navigate between windows
|
||||
*/
|
||||
String MENU_NAVIGATION_GROUP_WINDOWS = "GoToWindow";
|
||||
|
||||
/**
|
||||
* Used when placing a PluginAction in the "Search" menu of the tool.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue