From 74b67be5ede2e5c3e773de748b366c85424f6bb7 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Mon, 30 Dec 2019 12:02:28 -0500 Subject: [PATCH] GT-3430 - Key Bindings - Updated the Function Call Trees Plugin's actions key bindings so users can set the bindings separately --- Ghidra/Features/Base/certification.manifest | 1 - .../VersionControl/project_repository.htm | 2 +- .../plugin/core/calltree/CallTreePlugin.java | 9 ++- .../core/calltree/CallTreeProvider.java | 4 +- .../RemoveAllSecondaryHighlightsAction.java | 3 +- .../RemoveSecondaryHighlightAction.java | 3 +- .../actions/SharedStubKeyBindingAction.java | 14 +++++ .../Framework/Generic/certification.manifest | 1 + .../src/main/java/resources/Icons.java | 5 +- .../main/resources/images/help-browser.png | Bin .../plugintool/dialog/KeyBindingsPanel.java | 59 ++++++++++++++---- 11 files changed, 78 insertions(+), 23 deletions(-) rename Ghidra/{Features/Base => Framework/Generic}/src/main/resources/images/help-browser.png (100%) diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 8f7812b215..09122d462b 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -1049,7 +1049,6 @@ src/main/resources/images/functions.gif||GHIDRA||||END| src/main/resources/images/go-down.tango.16.png||Tango Icons - Public Domain|||tango icon set|END| src/main/resources/images/go-home.png||Tango Icons - Public Domain|||Tango|END| src/main/resources/images/go-up.tango.16.png||Tango Icons - Public Domain|||tango icon set|END| -src/main/resources/images/help-browser.png||Tango Icons - Public Domain|||tango icon set|END| src/main/resources/images/help-hint.png||Oxygen Icons - LGPL 3.0|||renamed from help-hing.png|END| src/main/resources/images/hexData.png||GHIDRA||||END| src/main/resources/images/house.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm b/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm index 55e21e2f12..4458e8aad3 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm @@ -374,7 +374,7 @@ -

View Checkouts

+

View Checkouts

To view a list of who has a file checked out, right mouse click on the file in the Ghidra diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java index 4ccb8adc70..056dbdd006 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java @@ -21,7 +21,8 @@ import java.util.List; import javax.swing.Icon; import docking.ActionContext; -import docking.action.*; +import docking.action.DockingAction; +import docking.action.MenuData; import ghidra.app.CorePluginPackage; import ghidra.app.context.ListingActionContext; import ghidra.app.plugin.PluginCategoryNames; @@ -125,9 +126,9 @@ public class CallTreePlugin extends ProgramPlugin { private void createActions() { // use the name of the provider so that the shared key binding data will get used - String actionName = CallTreeProvider.TITLE; + String actionName = "Static Function Call Trees"; showCallTreeFromMenuAction = - new DockingAction(actionName, getName(), KeyBindingType.SHARED) { + new DockingAction(actionName, getName()) { @Override public void actionPerformed(ActionContext context) { showOrCreateNewCallTree(currentLocation); @@ -143,6 +144,8 @@ public class CallTreePlugin extends ProgramPlugin { new String[] { "References", "Show Call Trees" }, PROVIDER_ICON, "ShowReferencesTo")); showCallTreeFromMenuAction.setHelpLocation( new HelpLocation("CallTreePlugin", "Call_Tree_Plugin")); + showCallTreeFromMenuAction.setDescription("Shows the Function Call Trees window for the " + + "item under the cursor. The new window will not change along with the Listing cursor."); tool.addAction(showCallTreeFromMenuAction); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java index 889c67e606..ffd6ea4d96 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java @@ -604,7 +604,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain // Show new call tree action // DockingAction newCallTree = - new DockingAction("Show Call Tree For Function", plugin.getName()) { + new DockingAction("Show Call Trees For Function", plugin.getName()) { @Override public void actionPerformed(ActionContext context) { GTree gTree = (GTree) context.getContextObject(); @@ -671,6 +671,8 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain "Call_Tree_Context_Action_Show_Call_Tree_For_Function")); newCallTree.setPopupMenuData(new MenuData(new String[] { "Show Call Tree For Function" }, CallTreePlugin.PROVIDER_ICON, newTreeMenu)); + newCallTree.setDescription("Show the Function Call Tree window for the function " + + "selected in the call tree"); tool.addLocalAction(this, newCallTree); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java index 89f9adca10..cfc2194981 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveAllSecondaryHighlightsAction.java @@ -18,7 +18,6 @@ package ghidra.app.plugin.core.decompile.actions; import docking.action.MenuData; import ghidra.app.decompiler.component.*; import ghidra.app.plugin.core.decompile.DecompilerActionContext; -import ghidra.app.util.HelpTopics; import ghidra.util.HelpLocation; /** @@ -35,7 +34,7 @@ public class RemoveAllSecondaryHighlightsAction extends AbstractDecompilerAction setPopupMenuData(new MenuData( new String[] { "Secondary Highlight", "Remove All Highlights" }, "Decompile")); - setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName())); + setHelpLocation(new HelpLocation("DecompilePlugin", getName())); } @Override diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java index 02512ae94a..22ad295c9a 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/RemoveSecondaryHighlightAction.java @@ -19,7 +19,6 @@ import docking.action.MenuData; import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.component.*; import ghidra.app.plugin.core.decompile.DecompilerActionContext; -import ghidra.app.util.HelpTopics; import ghidra.util.HelpLocation; /** @@ -36,7 +35,7 @@ public class RemoveSecondaryHighlightAction extends AbstractDecompilerAction { setPopupMenuData( new MenuData(new String[] { "Secondary Highlight", "Remove Highlight" }, "Decompile")); - setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName())); + setHelpLocation(new HelpLocation("DecompilePlugin", getName())); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java index bf0c5e487a..aed3256835 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java @@ -145,6 +145,20 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options return StringUtils.join(owners, ", "); } + @Override + public String getDescription() { + + Set actions = clientActions.keySet(); + for (DockingActionIf action : actions) { + String description = action.getDescription(); + if (!StringUtils.isBlank(description)) { + return description; + } + } + + return super.getDescription(); + } + private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) { // this value may be null diff --git a/Ghidra/Framework/Generic/certification.manifest b/Ghidra/Framework/Generic/certification.manifest index 916a3aad14..a876cd6b97 100644 --- a/Ghidra/Framework/Generic/certification.manifest +++ b/Ghidra/Framework/Generic/certification.manifest @@ -49,6 +49,7 @@ src/main/resources/images/face-monkey16.png||Tango Icons - Public Domain|||origi src/main/resources/images/flag.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/greenDragon16.png||GHIDRA||||END| src/main/resources/images/greenDragon24.png||GHIDRA||||END| +src/main/resources/images/help-browser.png||Tango Icons - Public Domain|||tango icon set|END| src/main/resources/images/internet-web-browser16.png||Tango Icons - Public Domain|||originally internet-web-browser.png from tango|END| src/main/resources/images/kgpg.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/mergemgr16.gif||GHIDRA||||END| diff --git a/Ghidra/Framework/Generic/src/main/java/resources/Icons.java b/Ghidra/Framework/Generic/src/main/java/resources/Icons.java index 9cb48c5a39..c1582a42be 100644 --- a/Ghidra/Framework/Generic/src/main/java/resources/Icons.java +++ b/Ghidra/Framework/Generic/src/main/java/resources/Icons.java @@ -35,6 +35,9 @@ public class Icons { public static final ImageIcon EMPTY_ICON = ResourceManager.loadImage("images/EmptyIcon16.gif"); + public static final ImageIcon HELP_ICON = + ResourceManager.loadImage("images/help-browser.png"); + public static final ImageIcon ADD_ICON = ResourceManager.loadImage("images/Plus2.png"); public static final ImageIcon COLLAPSE_ALL_ICON = @@ -82,7 +85,7 @@ public class Icons { new DotDotDotIcon(ResourceManager.loadImage("images/Disk.png"))); public static final ImageIcon MAKE_SELECTION_ICON = - ResourceManager.getImageIcon(ResourceManager.loadImage("images/text_align_justify.png")); + ResourceManager.loadImage("images/text_align_justify.png"); // Not necessarily re-usable, but this is needed for the help system; these should // probably be moved to the client that uses them, while updating the diff --git a/Ghidra/Features/Base/src/main/resources/images/help-browser.png b/Ghidra/Framework/Generic/src/main/resources/images/help-browser.png similarity index 100% rename from Ghidra/Features/Base/src/main/resources/images/help-browser.png rename to Ghidra/Framework/Generic/src/main/resources/images/help-browser.png diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java index 92b4ebb86d..1f2356d92e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java @@ -32,9 +32,10 @@ import docking.KeyEntryTextField; import docking.action.DockingActionIf; import docking.action.KeyBindingData; import docking.actions.KeyBindingUtils; +import docking.help.Help; +import docking.help.HelpService; import docking.tool.util.DockingToolConstants; -import docking.widgets.MultiLineLabel; -import docking.widgets.OptionDialog; +import docking.widgets.*; import docking.widgets.label.GIconLabel; import docking.widgets.table.*; import ghidra.framework.options.Options; @@ -44,6 +45,7 @@ import ghidra.util.*; import ghidra.util.exception.AssertException; import ghidra.util.layout.PairLayout; import ghidra.util.layout.VerticalLayout; +import resources.Icons; import resources.ResourceManager; /** @@ -56,6 +58,7 @@ public class KeyBindingsPanel extends JPanel { private final static int ACTION_NAME = 0; private final static int KEY_BINDING = 1; private final static int PLUGIN_NAME = 2; + private static final int FONT_SIZE = 11; private JTextPane statusLabel; @@ -79,6 +82,7 @@ public class KeyBindingsPanel extends JPanel { private boolean firingTableDataChanged; private PropertyChangeListener propertyChangeListener; private GTableFilterPanel tableFilterPanel; + private EmptyBorderButton helpButton; public KeyBindingsPanel(PluginTool tool, Options options) { this.tool = tool; @@ -203,6 +207,7 @@ public class KeyBindingsPanel extends JPanel { JScrollPane sp = new JScrollPane(actionTable); actionTable.setPreferredScrollableViewportSize(new Dimension(400, 100)); actionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + actionTable.setHTMLRenderingEnabled(true); adjustTableColumns(); @@ -231,7 +236,7 @@ public class KeyBindingsPanel extends JPanel { statusLabel = new JTextPane(); statusLabel.setEnabled(false); DockingUtils.setTransparent(statusLabel); - statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); + statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 5)); statusLabel.setContentType("text/html"); // render any HTML we find in descriptions // make sure the label gets enough space @@ -240,9 +245,27 @@ public class KeyBindingsPanel extends JPanel { Font f = new Font("SansSerif", Font.PLAIN, FONT_SIZE); statusLabel.setFont(f); + helpButton = new EmptyBorderButton(Icons.HELP_ICON); + helpButton.setEnabled(false); + helpButton.addActionListener(e -> { + DockingActionIf action = getSelectedAction(); + HelpService hs = Help.getHelpService(); + hs.showHelp(action, false, KeyBindingsPanel.this); + }); + + JPanel helpButtonPanel = new JPanel(); + helpButtonPanel.setLayout(new BoxLayout(helpButtonPanel, BoxLayout.PAGE_AXIS)); + helpButtonPanel.add(helpButton); + helpButtonPanel.add(Box.createVerticalGlue()); + + JPanel lowerStatusPanel = new JPanel(); + lowerStatusPanel.setLayout(new BoxLayout(lowerStatusPanel, BoxLayout.X_AXIS)); + lowerStatusPanel.add(helpButtonPanel); + lowerStatusPanel.add(statusLabel); + JPanel panel = new JPanel(new VerticalLayout(5)); panel.add(keyPanel); - panel.add(statusLabel); + panel.add(lowerStatusPanel); return panel; } @@ -260,9 +283,9 @@ public class KeyBindingsPanel extends JPanel { // the content of the left-hand side label MultiLineLabel mlabel = new MultiLineLabel("To add or change a key binding, select an action\n" + - " and type any key combination.\n" + + "and type any key combination\n \n" + "To remove a key binding, select an action and\n" + - "press or ."); + "press or "); JPanel labelPanel = new JPanel(); labelPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0)); BoxLayout bl = new BoxLayout(labelPanel, BoxLayout.X_AXIS); @@ -451,13 +474,21 @@ public class KeyBindingsPanel extends JPanel { unappliedChanges = changes; } - private String getSelectedAction() { + private DockingActionIf getSelectedAction() { if (selectionModel.isSelectionEmpty()) { return null; } int selectedRow = actionTable.getSelectedRow(); int modelRow = tableFilterPanel.getModelRow(selectedRow); - return tableActions.get(modelRow).getFullName(); + return tableActions.get(modelRow); + } + + private String getSelectedActionName() { + DockingActionIf action = getSelectedAction(); + if (action == null) { + return null; + } + return action.getFullName(); } private void addToKeyMap(KeyStroke ks, String actionName) { @@ -580,7 +611,7 @@ public class KeyBindingsPanel extends JPanel { return; } - String selectedActionName = getSelectedAction(); + String selectedActionName = getSelectedActionName(); if (selectedActionName != null) { if (processKeyStroke(selectedActionName, ks)) { String keyStrokeText = KeyEntryTextField.parseKeyStroke(ks); @@ -645,11 +676,14 @@ public class KeyBindingsPanel extends JPanel { return; } - String fullActionName = getSelectedAction(); + helpButton.setEnabled(false); + String fullActionName = getSelectedActionName(); if (fullActionName == null) { + statusLabel.setText(""); return; } + helpButton.setEnabled(true); KeyStroke ks = keyStrokesByFullName.get(fullActionName); String ksName = ""; clearInfoPanel(); @@ -672,12 +706,14 @@ public class KeyBindingsPanel extends JPanel { if (description == null || description.trim().isEmpty()) { description = action.getName(); } + statusLabel.setText("" + HTMLUtilities.escapeHTML(description)); } } private class KeyBindingsTableModel extends AbstractSortedTableModel { - private final String[] columnNames = { "Action Name", "KeyBinding", "Plugin Name" }; + private final String[] columnNames = + { "Action Name", "KeyBinding", "Plugin Name" }; KeyBindingsTableModel() { super(0); @@ -694,7 +730,6 @@ public class KeyBindingsPanel extends JPanel { switch (columnIndex) { case ACTION_NAME: return action.getName(); - case KEY_BINDING: KeyStroke ks = keyStrokesByFullName.get(action.getFullName()); if (ks != null) {