GT-2925 - Key Bindings - Support Window Menu Provider Key Bindings -

Step  - Added 'snapshot' concept to base Component Provider to fix how
actions are added to the toolbar and menu; fixed bugs and tests
This commit is contained in:
dragonmacher 2019-06-28 18:03:19 -04:00
parent 3946a05ded
commit 10621008e0
18 changed files with 291 additions and 84 deletions

View file

@ -13,7 +13,7 @@
</HEAD>
<BODY>
<H1><A name="Code_Browser"></A>Listing View</H1>
<H1><A name="Code_Browser"></A><A name="Listing"></A>Listing View</H1>
<P>The Listing View is the main windows for displaying and working with a program's instruction
and data.</P>

View file

@ -476,6 +476,17 @@
<P class="providedbyplugin">Provided by: <I>Go To Next-Previous Code Unit</I> plugin</P>
</BLOCKQUOTE>
<!--
This file is different than most, since it has multiple plugins contributing to the
content. Add some space at the bottom of the file to separate this last contribution
-->
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<H2><A name="Next_Previous_Function"></A>Next/Previous Function</H2>
@ -497,8 +508,21 @@
<P> This action navigates the cursor to the closest function entry point that is at an
address less than the current address. The default keybinding is <TT><B>Control-Up&nbsp;Arrow</B></TT>.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>CodeBrowser</I> plugin</P>
<P class="providedbyplugin">Provided by: <I>CodeBrowser</I> plugin</P>
</BLOCKQUOTE>
<!--
This file is different than most, since it has multiple plugins contributing to the
content. Add some space at the bottom of the file to separate this last contribution
-->
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<H2><A name="Navigation_History"></A>Navigation History</H2>
@ -554,9 +578,54 @@
<P>After clearing the history, the <IMG src="images/left.png" border="0">&nbsp;and <IMG
src="images/right.png" border="0"> buttons are disabled</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>Next/Previous</I> plugin</P>
</BLOCKQUOTE>
<!--
This file is different than most, since it has multiple plugins contributing to the
content. Add some space at the bottom of the file to separate this contribution.
-->
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<H2>Component Provider Navigation</H2>
<BLOCKQUOTE>
<P>
This section lists actions that allow the user to navigate between component providers.
</P>
<H3><A name="Navigation_Previous_Provider"></A>Go To Last Active Component</H3>
<BLOCKQUOTE>
<P>
Allows the user to switch focus back to the previously focused component provider.
</P>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>ProviderNavigation</I> plugin</P>
</BLOCKQUOTE>
<!--
This file is different than most, since it has multiple plugins contributing to the
content. Add some space at the bottom of the file to separate this contribution.
-->
<BR>
<BR>
<BR>
<BR>
<BR>
<BR>
<P class="providedbyplugin">Provided by: <I>Next/Previous</I> plugin</P>
<P class="relatedtopic">Related Topics:</P>

View file

@ -30,7 +30,7 @@
completely different instruction sets. To disassemble properly, the mode register must be set
at the address where the disassembly begins.</P>
<H2><A name="RegisterManager"></A>Register Manager</H2>
<H2><A name="Register_Manager"></A>Register Manager</H2>
<BLOCKQUOTE>
<P>The <I>Register Manager</I> displays the assigned values of registers at addresses within

View file

@ -54,6 +54,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
import resources.ResourceManager;
public class CodeViewerProvider extends NavigatableComponentProviderAdapter
@ -117,17 +118,21 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
private MultiListingLayoutModel multiModel;
public CodeViewerProvider(CodeBrowserPluginInterface plugin, FormatManager formatMgr,
boolean connected) {
super(plugin.getTool(), plugin.getName(), plugin.getName(), CodeViewerActionContext.class);
boolean isConnected) {
super(plugin.getTool(), "Listing", plugin.getName(), CodeViewerActionContext.class);
this.plugin = plugin;
this.formatMgr = formatMgr;
setConnected(connected);
setConnected(isConnected);
if (!isConnected) {
setTransient();
}
setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser"));
setDefaultWindowPosition(WindowPosition.RIGHT);
setIcon(ResourceManager.loadImage("images/Browser.gif"));
setIcon(ResourceManager.loadImage("images/Browser.gif"), isConnected);
listingPanel = new ListingPanel(formatMgr);
listingPanel.enablePropertyBasedColorModel(true);
decorationPanel = new ListingPanelContainer(listingPanel, connected);
decorationPanel = new ListingPanelContainer(listingPanel, isConnected);
ListingHighlightProvider listingHighlighter =
createListingHighlighter(listingPanel, tool, decorationPanel);
highlighterAdapter = new ProgramHighlighterProvider(listingHighlighter);
@ -136,7 +141,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
setWindowMenuGroup("Listing");
setIntraGroupPosition(WindowPosition.RIGHT);
setTitle(connected ? TITLE : "[" + TITLE + "]");
setTitle(isConnected ? TITLE : "[" + TITLE + "]");
fieldNavigator = new FieldNavigator(tool, this);
listingPanel.addButtonPressedListener(fieldNavigator);
addToTool();
@ -150,6 +155,12 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
tool.addPopupListener(this);
}
@Override
public boolean isSnapshot() {
// we are a snapshot when we are 'disconnected'
return !isConnected();
}
/**
* @return true if this listing is backed by a dynamic data source (e.g., debugger)
*/
@ -399,9 +410,9 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
}
void updateTitle() {
String subTitle = program == null ? "" : program.getDomainFile().getName();
String newTitle = isConnected() ? TITLE : "[" + TITLE + "]";
setTitle(newTitle + subTitle);
String subTitle = program == null ? "" : ' ' + program.getDomainFile().getName();
String newTitle = isConnected() ? TITLE : "[" + TITLE + subTitle + "]";
setTitle(newTitle);
}
@Override
@ -882,7 +893,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
final ViewerPosition vp = listingPanel.getFieldPanel().getViewerPosition();
// invoke later to give the window manage a chance to create the new window
// (its done in an invoke later)
SwingUtilities.invokeLater(() -> {
Swing.runLater(() -> {
newProvider.doSetProgram(program);
newProvider.listingPanel.getFieldPanel().setViewerPosition(vp.getIndex(),
vp.getXOffset(), vp.getYOffset());
@ -949,7 +960,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
class ToggleHeaderAction extends ToggleDockingAction {
ToggleHeaderAction() {
super("Toggle Header", CodeViewerProvider.this.getName());
super("Toggle Header", plugin.getName());
setEnabled(true);
setToolBarData(new ToolBarData(LISTING_FORMAT_EXPAND_ICON, "zzz"));
@ -960,8 +971,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
public void actionPerformed(ActionContext context) {
boolean show = !listingPanel.isHeaderShowing();
listingPanel.showHeader(show);
getToolBarData()
.setIcon(show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON);
getToolBarData().setIcon(
show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON);
}
}

View file

@ -64,11 +64,11 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter
private PrintWriter stdin;
private Program currentProgram;
public ConsoleComponentProvider(PluginTool tool, String name) {
super(tool, "Console", name);
public ConsoleComponentProvider(PluginTool tool, String owner) {
super(tool, "Console", owner);
setDefaultWindowPosition(WindowPosition.BOTTOM);
setHelpLocation(new HelpLocation(getName(), "console"));
setHelpLocation(new HelpLocation(owner, owner));
setIcon(ResourceManager.loadImage(CONSOLE_GIF));
setWindowMenuGroup("Console");
setSubTitle("Scripting");
@ -94,7 +94,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter
private void createOptions() {
ToolOptions options = tool.getOptions("Console");
HelpLocation help = new HelpLocation(getName(), "ConsolePlugin");
HelpLocation help = new HelpLocation(getOwner(), getOwner());
options.registerOption(FONT_OPTION_LABEL, DEFAULT_FONT, help, FONT_DESCRIPTION);
options.setOptionsHelpLocation(help);
font = options.getFont(FONT_OPTION_LABEL, DEFAULT_FONT);
@ -260,7 +260,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter
}
private void createActions() {
clearAction = new DockingAction("Clear Console", getName()) {
clearAction = new DockingAction("Clear Console", getOwner()) {
@Override
public void actionPerformed(ActionContext context) {
@ -273,7 +273,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter
clearAction.setEnabled(true);
scrollAction = new ToggleDockingAction("Scroll Lock", getName()) {
scrollAction = new ToggleDockingAction("Scroll Lock", getOwner()) {
@Override
public void actionPerformed(ActionContext context) {
textPane.setScrollLock(isSelected());

View file

@ -27,6 +27,7 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.plugintool.util.ToolConstants;
import ghidra.util.HelpLocation;
//@formatter:off
@PluginInfo(
@ -86,6 +87,8 @@ public class ProviderNavigationPlugin extends Plugin {
"xLowInMenuSubGroup"));
previousProviderAction.setKeyBindingData(new KeyBindingData(
KeyStroke.getKeyStroke(KeyEvent.VK_F6, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
previousProviderAction.setHelpLocation(
new HelpLocation("Navigation", "Navigation_Previous_Provider"));
tool.addAction(previousProviderAction);
}

View file

@ -65,7 +65,7 @@ public class RegisterManagerProvider extends ComponentProviderAdapter {
super(tool, "Register Manager", owner, ProgramActionContext.class);
buildComponent();
setHelpLocation(new HelpLocation("RegisterPlugin", "RegisterManager"));
setHelpLocation(new HelpLocation("RegisterPlugin", "Register_Manager"));
setIcon(REGISTER_ICON, true);
setDefaultWindowPosition(WindowPosition.WINDOW);

View file

@ -170,11 +170,13 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs);
assertNoToolbarAction();
}
@Test
public void testSetKeyBinding_ViaOptions_WithToolbarAction() {
setToolbarIcon(ICON);
showProvider();
KeyStroke newKs = CONTROL_T;
@ -183,6 +185,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs);
assertToolbarAction();
}
@Test
@ -306,6 +309,14 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
waitForSwing();
}
private void assertNoToolbarAction() {
assertNotNull("No toolbar action found for provider", getToolbarShowProviderAction());
}
private void assertToolbarAction() {
assertNotNull("No toolbar action found for provider", getToolbarShowProviderAction());
}
private void assertProviderKeyStroke(KeyStroke expectedKs) {
DockingActionIf action = getShowProviderAction();

View file

@ -70,9 +70,13 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
public ProgramByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin,
boolean isConnected) {
super(tool, plugin, "Bytes", ByteViewerActionContext.class);
this.isConnected = isConnected;
setIcon(ResourceManager.loadImage("images/binaryData.gif"), true);
this.isConnected = isConnected;
if (!isConnected) {
setTransient();
}
setIcon(ResourceManager.loadImage("images/binaryData.gif"), isConnected);
decorationComponent = new DecoratorPanel(panel, isConnected);
clipboardProvider = new ByteViewerClipboardProvider(this, tool);
@ -88,6 +92,12 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
tool.addLocalAction(this, cloneByteViewerAction);
}
@Override
public boolean isSnapshot() {
// we are a snapshot when we are 'disconnected'
return !isConnected();
}
@Override
public JComponent getComponent() {
return decorationComponent;
@ -132,11 +142,6 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
plugin.closeProvider(this);
}
@Override
public boolean isTransient() {
return false;
}
@Override
public void setSelection(ProgramSelection selection) {
setSelection(selection, true);

View file

@ -28,8 +28,10 @@ import javax.swing.event.TableColumnModelEvent;
import org.junit.*;
import docking.ActionContext;
import docking.*;
import docking.action.DockingActionIf;
import docking.menu.ToolBarItemManager;
import docking.menu.ToolBarManager;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.field.Field;
@ -168,7 +170,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
DataModelInfo info = panel.getDataModelInfo();
String[] names = info.getNames();
assertEquals(3, names.length);
Set<String> viewNames = new HashSet<String>(Arrays.asList(names));
Set<String> viewNames = new HashSet<>(Arrays.asList(names));
assertTrue(viewNames.contains("Hex"));
assertTrue(viewNames.contains("Octal"));
assertTrue(viewNames.contains("Ascii"));
@ -668,6 +670,64 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
}
}
@Test
public void testShowingSnapshotDoesNotAddMultipleToolbarActions() {
DockingActionIf cloneAction = getAction(plugin, "ByteViewer Clone");
performAction(cloneAction);
waitForSwing();
assertOnlyOneProviderToolbarAction();
performAction(cloneAction);
waitForSwing();
assertOnlyOneProviderToolbarAction();
}
//==================================================================================================
// Private Methods
//==================================================================================================
@SuppressWarnings("unchecked")
private void assertOnlyOneProviderToolbarAction() {
DockingWindowManager dwm = tool.getWindowManager();
ActionToGuiMapper guiActions =
(ActionToGuiMapper) getInstanceField("actionToGuiMapper", dwm);
GlobalMenuAndToolBarManager menuManager =
(GlobalMenuAndToolBarManager) getInstanceField("menuAndToolBarManager", guiActions);
Map<WindowNode, WindowActionManager> windowToActionManagerMap =
(Map<WindowNode, WindowActionManager>) getInstanceField("windowToActionManagerMap",
menuManager);
ProgramByteViewerComponentProvider provider = plugin.getProvider();
DockingActionIf showAction =
(DockingActionIf) getInstanceField("showProviderAction", provider);
String actionName = showAction.getName();
List<DockingActionIf> matches = new ArrayList<>();
for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
ToolBarManager toolbarManager =
(ToolBarManager) getInstanceField("toolBarMgr", actionManager);
Map<String, List<ToolBarItemManager>> groupToItems =
(Map<String, List<ToolBarItemManager>>) getInstanceField("groupToItemsMap",
toolbarManager);
Collection<List<ToolBarItemManager>> values = groupToItems.values();
for (List<ToolBarItemManager> list : values) {
for (ToolBarItemManager manager : list) {
DockingActionIf action = manager.getAction();
if (actionName.equals(action.getName())) {
matches.add(action);
}
}
}
}
assertEquals("Should only have 1 action on toolbar to show the provider", 1,
matches.size());
}
private void goToOperand(String addr) {
goTo(addr(addr), OperandFieldFactory.FIELD_NAME);
}
@ -731,11 +791,6 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals(expectedColumn, location.getCol());
}
private void goTo(String addr) {
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(addr(addr));
}
private void goToByte(String addr) {
goToByte(addr(addr));
}

View file

@ -124,9 +124,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
public DecompilerProvider(DecompilePlugin plugin, boolean isConnected) {
super(plugin.getTool(), "Decompiler", plugin.getName(), DecompilerActionContext.class);
this.plugin = plugin;
clipboardProvider = new DecompilerClipboardProvider(plugin, this);
this.plugin = plugin;
this.clipboardProvider = new DecompilerClipboardProvider(plugin, this);
setConnected(isConnected);
decompilerOptions = new DecompileOptions();
@ -137,10 +138,17 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
decompilerPanel.setHighlightController(highlightController);
decorationPanel = new DecoratorPanel(decompilerPanel, isConnected);
if (!isConnected) {
setTransient();
}
else {
setDefaultKeyBinding(
new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
}
setIcon(C_SOURCE_ICON, isConnected);
setTitle("Decompile");
setIcon(C_SOURCE_ICON, true);
setDefaultKeyBinding(
new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
setWindowMenuGroup("Decompile");
setDefaultWindowPosition(WindowPosition.RIGHT);
createActions(isConnected);
@ -155,6 +163,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
//==================================================================================================
// Component Provider methods
//==================================================================================================
@Override
public boolean isSnapshot() {
// we are a snapshot when we are 'disconnected'
return !isConnected();
}
@Override
public void closeComponent() {
controller.clear();

View file

@ -48,8 +48,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
import ghidra.util.*;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
@ -104,13 +103,16 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
controller = new FGController(this, plugin);
setConnected(isConnected);
if (!isConnected) {
setTransient();
}
decorationPanel = new DecoratorPanel(controller.getViewComponent(), isConnected);
setWindowMenuGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
setWindowGroup(FunctionGraphPlugin.FUNCTION_GRAPH_NAME);
setDefaultWindowPosition(WindowPosition.WINDOW);
setIcon(FunctionGraphPlugin.ICON, true);
setIcon(FunctionGraphPlugin.ICON, isConnected);
setHelpLocation(new HelpLocation("FunctionGraphPlugin", "FunctionGraphPlugin"));
addToTool();
@ -129,6 +131,12 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
setClipboardService(service);
}
@Override
public boolean isSnapshot() {
// we are a snapshot when we are 'disconnected'
return !isConnected();
}
public void setClipboardService(ClipboardService service) {
clipboardService = service;
if (clipboardService != null) {
@ -204,7 +212,7 @@ public class FGProvider extends VisualGraphComponentProvider<FGVertex, FGEdge, F
void cloneWindow() {
FGProvider newProvider = plugin.createNewDisconnectedProvider();
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
newProvider.doSetProgram(currentProgram);
FGData currentData = controller.getFunctionGraphData();

View file

@ -326,7 +326,7 @@ class ComponentNode extends Node {
DockingTabRenderer tabRenderer) {
final ComponentProvider provider = placeholder.getProvider();
if (!provider.isTransient()) {
if (!provider.isTransient() || provider.isSnapshot()) {
return; // don't muck with the title of 'real' providers--only transients, like search
}

View file

@ -449,6 +449,16 @@ public class ComponentPlaceholder {
}
}
void removeAllActions() {
if (comp != null) {
for (DockingActionIf action : actions) {
comp.actionRemoved(action);
}
}
actions.clear();
}
/**
* Removes an action from this component
* @param action the action to be removed.

View file

@ -406,6 +406,10 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.helpLocation = helpLocation;
HelpService helpService = DockingWindowManager.getHelpService();
helpService.registerHelp(this, helpLocation);
if (showProviderAction != null) {
showProviderAction.setHelpLocation(helpLocation);
}
}
/**
@ -564,7 +568,17 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
* @return true if transient
*/
public boolean isTransient() {
return isTransient;
return isTransient || isSnapshot();
}
/**
* A special marker that indicates this provider is a snapshot of a primary provider,
* somewhat like a picture of the primary provider.
*
* @return true if a snapshot
*/
public boolean isSnapshot() {
return false;
}
/**
@ -766,13 +780,17 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP));
}
if (defaultKeyBindingData != null) {
if (supportsKeyBindings && defaultKeyBindingData != null) {
// this action itself is not 'key binding managed', but the system *will* use
// any key binding value we set when connecting 'shared' actions
setKeyBindingData(defaultKeyBindingData);
}
setDescription("Display " + name);
HelpLocation providerHelp = ComponentProvider.this.getHelpLocation();
if (providerHelp != null) {
setHelpLocation(providerHelp);
}
}
@Override

View file

@ -26,6 +26,7 @@ import java.util.Map.Entry;
import javax.swing.*;
import org.apache.commons.collections4.map.LazyMap;
import org.jdom.Element;
import docking.action.DockingActionIf;
@ -996,11 +997,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
private void disposePlaceholder(ComponentPlaceholder placeholder, boolean keepAround) {
Iterator<DockingActionIf> iter = placeholder.getActions();
while (iter.hasNext()) {
DockingActionIf action = iter.next();
placeholder.removeAction(action);
}
placeholder.removeAllActions();
ComponentNode node = placeholder.getNode();
if (node == null) {
@ -1092,8 +1089,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
tool.getToolActions().removeActions(DOCKING_WINDOWS_OWNER);
Map<String, List<ComponentPlaceholder>> permanentMap = new HashMap<>();
Map<String, List<ComponentPlaceholder>> transientMap = new HashMap<>();
Map<String, List<ComponentPlaceholder>> permanentMap =
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
Map<String, List<ComponentPlaceholder>> transientMap =
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
Map<ComponentProvider, ComponentPlaceholder> map =
placeholderManager.getActiveProvidersToPlaceholders();
@ -1103,18 +1102,18 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
ComponentPlaceholder placeholder = entry.getValue();
String subMenuName = provider.getWindowSubMenuName();
if (provider.isTransient()) {
addToMap(transientMap, subMenuName, placeholder);
if (provider.isTransient() && !provider.isSnapshot()) {
transientMap.get(subMenuName).add(placeholder);
}
else {
addToMap(permanentMap, subMenuName, placeholder);
permanentMap.get(subMenuName).add(placeholder);
}
}
promoteSingleMenuGroups(permanentMap);
promoteSingleMenuGroups(transientMap);
createActions(transientMap, true);
createActions(permanentMap, false);
createActions(transientMap);
createActions(permanentMap);
createWindowActions();
actionToGuiMapper.update();
@ -1144,14 +1143,17 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return null;
}
private void createActions(Map<String, List<ComponentPlaceholder>> map, boolean isTransient) {
private void createActions(Map<String, List<ComponentPlaceholder>> map) {
List<ShowComponentAction> actionList = new ArrayList<>();
for (String subMenuName : map.keySet()) {
List<ComponentPlaceholder> placeholders = map.get(subMenuName);
for (ComponentPlaceholder placeholder : placeholders) {
ComponentProvider provider = placeholder.getProvider();
boolean isTransient = provider.isTransient();
actionList.add(
new ShowComponentAction(this, placeholder, subMenuName, isTransient));
}
if (subMenuName != null) {
// add an 'add all' action for the sub-menu
actionList.add(new ShowAllComponentsAction(this, placeholders, subMenuName));
@ -1165,28 +1167,21 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
}
private void promoteSingleMenuGroups(Map<String, List<ComponentPlaceholder>> map) {
List<String> lists = new ArrayList<>(map.keySet());
private void promoteSingleMenuGroups(Map<String, List<ComponentPlaceholder>> lazyMap) {
List<String> lists = new ArrayList<>(lazyMap.keySet());
for (String key : lists) {
List<ComponentPlaceholder> list = map.get(key);
if (key != null && list.size() == 1) {
addToMap(map, null, list.get(0));
map.remove(key);
if (key == null) {
continue;
}
List<ComponentPlaceholder> list = lazyMap.get(key);
if (list.size() == 1) {
lazyMap.get(null /*submenu name*/).add(list.get(0));
lazyMap.remove(key);
}
}
}
private void addToMap(Map<String, List<ComponentPlaceholder>> map, String menuGroup,
ComponentPlaceholder placeholder) {
List<ComponentPlaceholder> list = map.get(menuGroup);
if (list == null) {
list = new ArrayList<>();
map.put(menuGroup, list);
}
list.add(placeholder);
}
private void createWindowActions() {
List<DetachedWindowNode> windows = root.getDetachedWindows();
List<ShowWindowAction> actions = new ArrayList<>();

View file

@ -52,8 +52,7 @@ public class KeyBindingAction extends DockingAction {
if (!action.getKeyBindingType().supportsKeyBindings()) {
Component parent = windowManager.getActiveComponent();
Msg.showInfo(getClass(), parent, "Unable to Set Keybinding",
"Action \"" + getActionName(action) + "\" is not keybinding managed and thus a " +
"keybinding cannot be set.");
"Action \"" + getActionName(action) + "\" does not support key bindings");
return;
}

View file

@ -446,9 +446,17 @@ public class KeyBindingUtils {
public static void assertSameDefaultKeyBindings(DockingActionIf newAction,
Collection<DockingActionIf> existingActions) {
if (!newAction.getKeyBindingType().supportsKeyBindings()) {
return;
}
KeyBindingData newDefaultBinding = newAction.getDefaultKeyBindingData();
KeyStroke defaultKs = getKeyStroke(newDefaultBinding);
for (DockingActionIf action : existingActions) {
if (!action.getKeyBindingType().supportsKeyBindings()) {
continue;
}
KeyBindingData existingDefaultBinding = action.getDefaultKeyBindingData();
KeyStroke existingKs = getKeyStroke(existingDefaultBinding);
if (!Objects.equals(defaultKs, existingKs)) {