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> </HEAD>
<BODY> <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 <P>The Listing View is the main windows for displaying and working with a program's instruction
and data.</P> and data.</P>

View file

@ -476,6 +476,17 @@
<P class="providedbyplugin">Provided by: <I>Go To Next-Previous Code Unit</I> plugin</P> <P class="providedbyplugin">Provided by: <I>Go To Next-Previous Code Unit</I> plugin</P>
</BLOCKQUOTE> </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> <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 <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> address less than the current address. The default keybinding is <TT><B>Control-Up&nbsp;Arrow</B></TT>.</P>
</BLOCKQUOTE> </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> <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 <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> src="images/right.png" border="0"> buttons are disabled</P>
</BLOCKQUOTE> </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> <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 completely different instruction sets. To disassemble properly, the mode register must be set
at the address where the disassembly begins.</P> 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> <BLOCKQUOTE>
<P>The <I>Register Manager</I> displays the assigned values of registers at addresses within <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.model.listing.*;
import ghidra.program.util.*; import ghidra.program.util.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Swing;
import resources.ResourceManager; import resources.ResourceManager;
public class CodeViewerProvider extends NavigatableComponentProviderAdapter public class CodeViewerProvider extends NavigatableComponentProviderAdapter
@ -117,17 +118,21 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
private MultiListingLayoutModel multiModel; private MultiListingLayoutModel multiModel;
public CodeViewerProvider(CodeBrowserPluginInterface plugin, FormatManager formatMgr, public CodeViewerProvider(CodeBrowserPluginInterface plugin, FormatManager formatMgr,
boolean connected) { boolean isConnected) {
super(plugin.getTool(), plugin.getName(), plugin.getName(), CodeViewerActionContext.class); super(plugin.getTool(), "Listing", plugin.getName(), CodeViewerActionContext.class);
this.plugin = plugin; this.plugin = plugin;
this.formatMgr = formatMgr; this.formatMgr = formatMgr;
setConnected(connected); setConnected(isConnected);
if (!isConnected) {
setTransient();
}
setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser")); setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser"));
setDefaultWindowPosition(WindowPosition.RIGHT); setDefaultWindowPosition(WindowPosition.RIGHT);
setIcon(ResourceManager.loadImage("images/Browser.gif")); setIcon(ResourceManager.loadImage("images/Browser.gif"), isConnected);
listingPanel = new ListingPanel(formatMgr); listingPanel = new ListingPanel(formatMgr);
listingPanel.enablePropertyBasedColorModel(true); listingPanel.enablePropertyBasedColorModel(true);
decorationPanel = new ListingPanelContainer(listingPanel, connected); decorationPanel = new ListingPanelContainer(listingPanel, isConnected);
ListingHighlightProvider listingHighlighter = ListingHighlightProvider listingHighlighter =
createListingHighlighter(listingPanel, tool, decorationPanel); createListingHighlighter(listingPanel, tool, decorationPanel);
highlighterAdapter = new ProgramHighlighterProvider(listingHighlighter); highlighterAdapter = new ProgramHighlighterProvider(listingHighlighter);
@ -136,7 +141,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
setWindowMenuGroup("Listing"); setWindowMenuGroup("Listing");
setIntraGroupPosition(WindowPosition.RIGHT); setIntraGroupPosition(WindowPosition.RIGHT);
setTitle(connected ? TITLE : "[" + TITLE + "]"); setTitle(isConnected ? TITLE : "[" + TITLE + "]");
fieldNavigator = new FieldNavigator(tool, this); fieldNavigator = new FieldNavigator(tool, this);
listingPanel.addButtonPressedListener(fieldNavigator); listingPanel.addButtonPressedListener(fieldNavigator);
addToTool(); addToTool();
@ -150,6 +155,12 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
tool.addPopupListener(this); 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) * @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() { void updateTitle() {
String subTitle = program == null ? "" : program.getDomainFile().getName(); String subTitle = program == null ? "" : ' ' + program.getDomainFile().getName();
String newTitle = isConnected() ? TITLE : "[" + TITLE + "]"; String newTitle = isConnected() ? TITLE : "[" + TITLE + subTitle + "]";
setTitle(newTitle + subTitle); setTitle(newTitle);
} }
@Override @Override
@ -882,7 +893,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
final ViewerPosition vp = listingPanel.getFieldPanel().getViewerPosition(); final ViewerPosition vp = listingPanel.getFieldPanel().getViewerPosition();
// invoke later to give the window manage a chance to create the new window // invoke later to give the window manage a chance to create the new window
// (its done in an invoke later) // (its done in an invoke later)
SwingUtilities.invokeLater(() -> { Swing.runLater(() -> {
newProvider.doSetProgram(program); newProvider.doSetProgram(program);
newProvider.listingPanel.getFieldPanel().setViewerPosition(vp.getIndex(), newProvider.listingPanel.getFieldPanel().setViewerPosition(vp.getIndex(),
vp.getXOffset(), vp.getYOffset()); vp.getXOffset(), vp.getYOffset());
@ -949,7 +960,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
class ToggleHeaderAction extends ToggleDockingAction { class ToggleHeaderAction extends ToggleDockingAction {
ToggleHeaderAction() { ToggleHeaderAction() {
super("Toggle Header", CodeViewerProvider.this.getName()); super("Toggle Header", plugin.getName());
setEnabled(true); setEnabled(true);
setToolBarData(new ToolBarData(LISTING_FORMAT_EXPAND_ICON, "zzz")); setToolBarData(new ToolBarData(LISTING_FORMAT_EXPAND_ICON, "zzz"));
@ -960,8 +971,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
boolean show = !listingPanel.isHeaderShowing(); boolean show = !listingPanel.isHeaderShowing();
listingPanel.showHeader(show); listingPanel.showHeader(show);
getToolBarData() getToolBarData().setIcon(
.setIcon(show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON); show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON);
} }
} }

View file

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

View file

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

View file

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

View file

@ -170,11 +170,13 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
assertProviderKeyStroke(newKs); assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs); assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs); assertMenuItemHasKeyStroke(newKs);
assertNoToolbarAction();
} }
@Test @Test
public void testSetKeyBinding_ViaOptions_WithToolbarAction() { public void testSetKeyBinding_ViaOptions_WithToolbarAction() {
setToolbarIcon(ICON);
showProvider(); showProvider();
KeyStroke newKs = CONTROL_T; KeyStroke newKs = CONTROL_T;
@ -183,6 +185,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
assertProviderKeyStroke(newKs); assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs); assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs); assertMenuItemHasKeyStroke(newKs);
assertToolbarAction();
} }
@Test @Test
@ -306,6 +309,14 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
waitForSwing(); 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) { private void assertProviderKeyStroke(KeyStroke expectedKs) {
DockingActionIf action = getShowProviderAction(); DockingActionIf action = getShowProviderAction();

View file

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

View file

@ -28,8 +28,10 @@ import javax.swing.event.TableColumnModelEvent;
import org.junit.*; import org.junit.*;
import docking.ActionContext; import docking.*;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.menu.ToolBarItemManager;
import docking.menu.ToolBarManager;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
@ -168,7 +170,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
DataModelInfo info = panel.getDataModelInfo(); DataModelInfo info = panel.getDataModelInfo();
String[] names = info.getNames(); String[] names = info.getNames();
assertEquals(3, names.length); 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("Hex"));
assertTrue(viewNames.contains("Octal")); assertTrue(viewNames.contains("Octal"));
assertTrue(viewNames.contains("Ascii")); 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) { private void goToOperand(String addr) {
goTo(addr(addr), OperandFieldFactory.FIELD_NAME); goTo(addr(addr), OperandFieldFactory.FIELD_NAME);
} }
@ -731,11 +791,6 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals(expectedColumn, location.getCol()); assertEquals(expectedColumn, location.getCol());
} }
private void goTo(String addr) {
GoToService goToService = tool.getService(GoToService.class);
goToService.goTo(addr(addr));
}
private void goToByte(String addr) { private void goToByte(String addr) {
goToByte(addr(addr)); goToByte(addr(addr));
} }

View file

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

View file

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

View file

@ -326,7 +326,7 @@ class ComponentNode extends Node {
DockingTabRenderer tabRenderer) { DockingTabRenderer tabRenderer) {
final ComponentProvider provider = placeholder.getProvider(); 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 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 * Removes an action from this component
* @param action the action to be removed. * @param action the action to be removed.

View file

@ -406,6 +406,10 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.helpLocation = helpLocation; this.helpLocation = helpLocation;
HelpService helpService = DockingWindowManager.getHelpService(); HelpService helpService = DockingWindowManager.getHelpService();
helpService.registerHelp(this, helpLocation); 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 * @return true if transient
*/ */
public boolean isTransient() { 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)); 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 // this action itself is not 'key binding managed', but the system *will* use
// any key binding value we set when connecting 'shared' actions // any key binding value we set when connecting 'shared' actions
setKeyBindingData(defaultKeyBindingData); setKeyBindingData(defaultKeyBindingData);
} }
setDescription("Display " + name); setDescription("Display " + name);
HelpLocation providerHelp = ComponentProvider.this.getHelpLocation();
if (providerHelp != null) {
setHelpLocation(providerHelp);
}
} }
@Override @Override

View file

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

View file

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

View file

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