diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Tool/ToolOptions_Dialog.htm b/Ghidra/Features/Base/src/main/help/help/topics/Tool/ToolOptions_Dialog.htm index 574d2d8fc1..d7517cbdd8 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Tool/ToolOptions_Dialog.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Tool/ToolOptions_Dialog.htm @@ -456,6 +456,15 @@ automations to provide visual feedback that something is happening, such as launching a tool. + + + Use Combined Alt Keys + + When selected, any keybding created that uses the Alt + key will work using the left and right Alt keys. When unselected, key bindings + using the Alt key will only work with the left Alt on some systems, such as on + Windows. + diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java index 14b2e6b7a3..9d34be95ae 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java @@ -4,9 +4,9 @@ * 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. @@ -31,6 +31,7 @@ import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.Analyzer; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.model.DomainFile; import ghidra.framework.options.OptionType; import ghidra.framework.options.Options; import ghidra.framework.plugintool.*; @@ -133,13 +134,21 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis } private void updateActionName(ActionContext context) { - String programName = ""; - if (context instanceof ListingActionContext) { - ListingActionContext listingContext = (ListingActionContext) context; - programName = listingContext.getProgram().getDomainFile().getName(); + if (!(context instanceof ListingActionContext lac)) { + return; } + + Program p = lac.getProgram(); + DomainFile df = p.getDomainFile(); + String fileName = df.getName(); + String programName = "'" + fileName + "'"; + MenuData menuBarData = autoAnalyzeAction.getMenuBarData(); - menuBarData.setMenuItemName("&Auto Analyze '" + programName + "'..."); + String currentName = menuBarData.getMenuItemName(); + String newName = "Auto Analyze " + programName + "..."; + if (!currentName.equals(newName)) { + menuBarData.setMenuItemName("&" + newName); + } } private void analyzeCallback(ActionContext context) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java index 46e241a4c2..7bc7757d06 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java @@ -126,6 +126,8 @@ public class DockingUtils { private static boolean globalTooltipsEnabled = true; + private static boolean useCombinedAltKeysEnabled; + public static JSeparator createToolbarSeparator() { Dimension sepDim = new Dimension(2, ICON_SIZE + 2); JSeparator separator = new JSeparator(SwingConstants.VERTICAL); @@ -366,6 +368,16 @@ public class DockingUtils { Swing.runLater(() -> ToolTipManager.sharedInstance().setEnabled(enabled)); } + /** + * Not meant for public consumption. This is for application code to control how key bindings + * that use the Alt key get mapped. When true, a key binding that uses the Alt key will get + * mapped to the left and right alt keys. + * @param enabled true if enabled + */ + public static void setCombinedAltKeysEnabled(boolean enabled) { + useCombinedAltKeysEnabled = enabled; + } + /** * Note: calling this method has no effect * @param enabled true if enabled; false prevents all Java tooltips @@ -389,6 +401,15 @@ public class DockingUtils { return globalTooltipsEnabled; } + /** + * True if the application should map Alt key binding usage to the left and right key. + * @return true if the application should map Alt key binding usage to the left and right key. + * @see #setCombinedAltKeysEnabled(boolean) + */ + public static boolean isCombineAltKeysEnabled() { + return useCombinedAltKeysEnabled; + } + /** Hides any open tooltip window */ public static void hideTipWindow() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java index 4b7b7e00bb..52b0bb1424 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java @@ -153,19 +153,34 @@ public class KeyBindingsManager implements PropertyChangeListener { private void fixupAltGraphKeyStrokeMapping(ComponentProvider provider, DockingActionIf action, KeyStroke keyStroke) { + KeyStroke altGraphKs = maybeGenerateAltGraphKeyStroke(keyStroke); + if (altGraphKs != null) { + doAddKeyBinding(provider, action, altGraphKs, keyStroke); + } + } + + /** + * Some operating systems allow the left and right Alt keys to be mapped separately. Some users + * find it convenient to be able to use both Alt keys for a single key binding. + * @param keyStroke the key stroke + * @return a new key stroke that uses the AltGraph key or null if not appropriate + */ + private KeyStroke maybeGenerateAltGraphKeyStroke(KeyStroke keyStroke) { + if (!DockingUtils.isCombineAltKeysEnabled()) { + return null; + } + // special case int modifiers = keyStroke.getModifiers(); if ((modifiers & InputEvent.ALT_DOWN_MASK) == InputEvent.ALT_DOWN_MASK) { - // - // Also register the 'Alt' binding with the 'Alt Graph' mask. This fixes the but - // on Windows (https://bugs.openjdk.java.net/browse/JDK-8194873) - // that have different key codes for the left and right Alt keys. - // + // Also register the Alt binding with the 'Alt Graph' mask. Some operating systems + // allow the left and right Alt keys to be mapped separately. Some users find it + // convenient to be able to use both Alt keys for a single key binding. modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK; - KeyStroke updateKeyStroke = - KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, false); - doAddKeyBinding(provider, action, updateKeyStroke, keyStroke); + return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, false); } + + return null; } private void doAddKeyBinding(ComponentProvider provider, DockingActionIf action, @@ -230,6 +245,17 @@ public class KeyBindingsManager implements PropertyChangeListener { return; } + removeKeyBindingFromCache(action, keyStroke); + + // also remove any secondarily mapped Alt key stroke + KeyStroke altGraphKs = maybeGenerateAltGraphKeyStroke(keyStroke); + if (altGraphKs != null) { + removeKeyBindingFromCache(action, altGraphKs); + } + } + + private void removeKeyBindingFromCache(DockingActionIf action, KeyStroke keyStroke) { + DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke); if (existingAction == null) { return; @@ -238,9 +264,7 @@ public class KeyBindingsManager implements PropertyChangeListener { if (existingAction instanceof SystemKeyBindingAction) { dockingKeyMap.remove(keyStroke); } - else if (existingAction instanceof MultipleKeyAction) { - - MultipleKeyAction mkAction = (MultipleKeyAction) existingAction; + else if (existingAction instanceof MultipleKeyAction mkAction) { mkAction.removeAction(action); if (mkAction.isEmpty()) { dockingKeyMap.remove(keyStroke); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java index ebc78af2b7..0ea88eca18 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -15,7 +15,7 @@ */ package docking.actions; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.Strings.*; import java.awt.Component; import java.awt.KeyboardFocusManager; @@ -674,19 +674,19 @@ public class KeyBindingUtils { StringBuilder buffy = new StringBuilder(); if (isShift(modifiers)) { buffy.insert(0, SHIFT + MODIFIER_SEPARATOR); - keyString = removeIgnoreCase(keyString, SHIFT); + keyString = remove(keyString, SHIFT); } if (isAlt(modifiers)) { buffy.insert(0, ALT + MODIFIER_SEPARATOR); - keyString = removeIgnoreCase(keyString, ALT); + keyString = remove(keyString, ALT); } if (isControl(modifiers)) { buffy.insert(0, CTRL + MODIFIER_SEPARATOR); - keyString = removeIgnoreCase(keyString, CONTROL); + keyString = remove(keyString, CONTROL); } if (isMeta(modifiers)) { buffy.insert(0, META + MODIFIER_SEPARATOR); - keyString = removeIgnoreCase(keyString, META); + keyString = remove(keyString, META); } buffy.append(keyString); @@ -698,7 +698,15 @@ public class KeyBindingUtils { } private static int indexOf(String source, String search, int offset) { - return StringUtils.indexOfIgnoreCase(source, search, offset); + return CI.indexOf(source, search, offset); + } + + private static int indexOf(String source, String search) { + return CI.indexOf(source, search); + } + + private static String remove(String source, String toRemove) { + return CI.remove(source, toRemove); } // ignore the deprecated; remove when we are confident that all tool actions no longer use the @@ -767,33 +775,33 @@ public class KeyBindingUtils { StringBuilder buffy = new StringBuilder(); for (Iterator iterator = pieces.iterator(); iterator.hasNext();) { String piece = iterator.next(); - if (indexOfIgnoreCase(piece, SHIFT) != -1) { + if (indexOf(piece, SHIFT) != -1) { buffy.append("shift "); iterator.remove(); } - else if (indexOfIgnoreCase(piece, CTRL) != -1) { + else if (indexOf(piece, CTRL) != -1) { buffy.append("ctrl "); iterator.remove(); } - else if (indexOfIgnoreCase(piece, CONTROL) != -1) { + else if (indexOf(piece, CONTROL) != -1) { buffy.append("ctrl "); iterator.remove(); } - else if (indexOfIgnoreCase(piece, ALT) != -1) { + else if (indexOf(piece, ALT) != -1) { buffy.append("alt "); iterator.remove(); } - else if (indexOfIgnoreCase(piece, META) != -1) { + else if (indexOf(piece, META) != -1) { buffy.append("meta "); iterator.remove(); } - else if (indexOfIgnoreCase(piece, PRESSED) != -1) { + else if (indexOf(piece, PRESSED) != -1) { iterator.remove(); } - else if (indexOfIgnoreCase(piece, TYPED) != -1) { + else if (indexOf(piece, TYPED) != -1) { iterator.remove(); } - else if (indexOfIgnoreCase(piece, RELEASED) != -1) { + else if (indexOf(piece, RELEASED) != -1) { iterator.remove(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index d49142f86a..6db967764c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -394,7 +394,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { } /* - * An odd method that really shoulnd't be on the interface. This is a call that allows the + * An odd method that really shouldn't be on the interface. This is a call that allows the * framework to signal that the ToolOptions have been rebuilt, such as when restoring from xml. * During a rebuild, ToolOptions does not send out events, so this class does not get any of the * values from the new options. This method tells us to get the new version of the options from diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java index 5fb3f23df5..37316477e3 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java @@ -91,6 +91,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { public static final String DEFAULT_TOOL_LAUNCH_MODE = "Default Tool Launch Mode"; public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools"; private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation"; + private static final String USE_COMBINED_ALT_GRAPH_OPTION_NAME = "Use Combined Alt Keys"; private static final String SHOW_TOOLTIPS_OPTION_NAME = "Show Tooltips"; private static final String BLINKING_CURSORS_OPTION_NAME = "Allow Blinking Cursors"; @@ -216,7 +217,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { return; } - GhidraToolTemplate template = new GhidraToolTemplate((Element) root.getChildren().get(0), + GhidraToolTemplate template = new GhidraToolTemplate(root.getChildren().get(0), TOOL_FILE.getAbsolutePath()); refresh(template); } @@ -349,6 +350,10 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { options.registerOption(USE_ALERT_ANIMATION_OPTION_NAME, true, help, "Signals that user notifications should be animated. This makes notifications more " + "distinguishable."); + options.registerOption(USE_COMBINED_ALT_GRAPH_OPTION_NAME, true, help, + "Signals to have both right and left Alt keys be usable for key bindings that use the " + + "Alt key."); + options.registerOption(SHOW_TOOLTIPS_OPTION_NAME, true, help, "Controls the display of tooltip popup windows."); options.registerOption(ENABLE_COMPRESSED_DATABUFFER_OUTPUT, false, help, @@ -369,6 +374,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { boolean animationEnabled = options.getBoolean(USE_ALERT_ANIMATION_OPTION_NAME, true); AnimationUtils.setAnimationEnabled(animationEnabled); + boolean combineAltKeys = options.getBoolean(USE_COMBINED_ALT_GRAPH_OPTION_NAME, true); + DockingUtils.setCombinedAltKeysEnabled(combineAltKeys); + boolean showToolTips = options.getBoolean(SHOW_TOOLTIPS_OPTION_NAME, true); DockingUtils.setGlobalTooltipEnabledOption(showToolTips); @@ -396,6 +404,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { else if (USE_ALERT_ANIMATION_OPTION_NAME.equals(optionName)) { AnimationUtils.setAnimationEnabled((Boolean) newValue); } + else if (USE_COMBINED_ALT_GRAPH_OPTION_NAME.equals(optionName)) { + DockingUtils.setCombinedAltKeysEnabled((Boolean) newValue); + } else if (SHOW_TOOLTIPS_OPTION_NAME.equals(optionName)) { DockingUtils.setGlobalTooltipEnabledOption((Boolean) newValue); }