Merge remote-tracking branch

'origin/GP-1081-dragonmacher-menu-mnemonics--SQUASHED' (Closes #2811)
This commit is contained in:
ghidra1 2021-07-13 14:39:01 -04:00
commit f66bad1a9c
18 changed files with 690 additions and 110 deletions

View file

@ -159,9 +159,6 @@
<tocdef id="Control the View" sortgroup="b" text="Control the View" target="help/topics/ProgramTreePlugin/program_tree.htm#ViewControl" />
<tocdef id="Cut/Copy/Paste and Drag and Drop" sortgroup="c" text="Cut/Copy/Paste and Drag and Drop" target="help/topics/ProgramTreePlugin/program_tree.htm#CCPandDnD" />
<tocdef id="Program Organizations" sortgroup="d" text="Program Organizations" target="help/topics/ProgramTreePlugin/Program_Organizations.htm" />
</tocdef>
<tocdef id="Program Tree Manager" text="Program Tree Manager" target="help/topics/ProgramTreePlugin/view_manager.htm" >
<tocdef id="Create view" sortgroup="a" text="Create view" target="help/topics/ProgramTreePlugin/view_manager.htm#Create_Default_Tree_View" />
<tocdef id="Delete view" sortgroup="b" text="Delete view" target="help/topics/ProgramTreePlugin/view_manager.htm#Delete_Tree_View" />
<tocdef id="Rename view" sortgroup="c" text="Rename view" target="help/topics/ProgramTreePlugin/view_manager.htm#Rename_Tree_View" />

View file

@ -259,11 +259,9 @@
<P align="center"><IMG alt="" src="images/Err_Dialog.png" border="0"></P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P align="left">The <B>Details</B> &gt;&gt;&gt; button expands the dialog to show the
details of the java stack trace. (The stack trace is also output to the console.)</P>
</BLOCKQUOTE><BR>
</BLOCKQUOTE>
</BLOCKQUOTE><BR>
</BODY>
</HTML>

View file

@ -59,7 +59,7 @@
"4">Subroutine</FONT></B><IMG border="0" src="../../shared/arrow.gif"><FONT size=
"4"><B><I>&lt;block model name&gt;.</I></B></FONT></P>
<P>Provided By:<I>ModularizeAlgorithmPlugin</I></P>
<P class="providedbyplugin">Provided by: <I>ModularizeAlgorithmPlugin</I></P>
</BLOCKQUOTE>
@ -69,7 +69,7 @@
<P>
This action modularizes the program tree by creating a call tree of the
code blocks and then arranges that tree by dominance such that all blocks only
reachable from a parent <tt>p</tt> are children of <tt>p</tt> in the program tree.
reachable from a parent <tt>'p'</tt> are children of <tt>'p'</tt> in the program tree.
</P>
</BLOCKQUOTE>

View file

@ -32,6 +32,17 @@
on a <A href="help/topics/BlockModel/Block_Model.htm">Block Model</A>. The following paragraphs
describe features of the Program Tree.</P>
<CENTER>
<TABLE border="0" width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG src="images/ViewManager.png" border="0"></TD>
</TR>
</TBODY>
</TABLE>
</CENTER>
<H2><A name="FoldersAndFragments"></A>Folders and Fragments</H2>
<BLOCKQUOTE>

View file

@ -5,7 +5,7 @@
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>View Manager</TITLE>
<TITLE>Program Tree View Management</TITLE>
<META http-equiv="content-type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
<META name="generator" content="Microsoft FrontPage 4.0">
@ -13,7 +13,7 @@
<BODY>
<H1><A name="ProgramTreePlugin"></A><A name="Program_Tree"></A>
Program Tree Plugin
Program Tree View Management
</H1>
<P>Program Trees are used to organize programs into a tree structure. Nodes within the a

View file

@ -56,36 +56,45 @@
<H2><A name="KeyBindings_Option"></A><B>Key Bindings</B></H2>
<BLOCKQUOTE>
<P>You can create a new "hot key" for a Plugin's action or modify the default key binding.
The hot key (or accelerator) that you add can be used to execute the action with a keystroke
combination.</P>
<P>You can create a new key binding (<I>accelerator key</I>) for an action or modify the
default key binding. The key binding that you add can be used to execute the action
using the keyboard. Below we describe the <B>Key Bindings</B> options editor.</P>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="../../shared/note.png"> You can also change key bindings
<P><IMG alt="" border="0" src="../../shared/tip.png">Not all key bindings are changeable
via the tool options. For example, the following keys cannot be changed:
<UL>
<LI>
<TT><B>F1, Help, Ctrl-F1, F4</B></TT> (this bindings are reserved and cannot be used
when assigning key bindings to actions)
</LI>
<LI>
Menu Navigation Actions: <TT><B>1-9, Page Up/Down, End, Home</B></TT> (these key
bindings are usable with a menu or popup menu open and are otherwise available for
assignment to key bindings).
</LI>
</UL>
</P>
</BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG alt="" border="0" src="../../shared/tip.png"> You can also change key bindings
from within Ghidra by pressing <B>F4</B> while the mouse is over any toolbar icon or menu
item. Click <A href="#KeyBindingPopup">here</A> for more info.</P>
</BLOCKQUOTE>
<P>The <I>Key Bindings</I> panel has a table containing the <I>Action Name</I>, <I>Key
Binding</I>, and <I>Plugin Name</I>. You can sort the columns in ascending or descending
order. (By default, the <I>Action Nam</I>e column is sorted in ascending order.) The Plugin
name is the name of the plugin supplying the action.</P>
<P>The <B>Key Bindings</B> option editor has a table with the following sortable columns:
<I>Action Name</I>, <I>Key Binding</I>, and <I>Plugin Name</I>. To change a value in
the table, select the row and then edit the text field below the table.</P>
<UL>
<LI>Click on the category header to change the sort order.</LI>
<LI>Change the order of the columns by dragging the column header to another position in
the table.</LI>
<LI>The text field below the table captures keystroke combinations entered.</LI>
<LI>If an action has a description to explain what it does, it will be displayed below the
text field.</LI>
</UL>
<P><IMG alt="" border="0" src="../../shared/note.png"> The table entries are not
editable.</P>
<P>The display below shows the key bindings panel for the <I>Project Window</I>. Using the
Key Bindings Options panel works the same as for a regular Ghidra Tool.</P>
</BLOCKQUOTE>
@ -130,12 +139,12 @@
</OL>
</BLOCKQUOTE>
<P><IMG alt="" border="0" src="../../shared/note.png"> When a key is mapped to multiple
<P><IMG alt="" border="0" src="../../shared/note.yellow.png"> When a key is mapped to multiple
actions, and more than one of these actions is valid in the current context (i.e., the action
is enabled), then a dialog is displayed for you to choose what action you want to
perform.</P>
<P><IMG alt="" border="0" src="../../shared/tip.png"> To avoid the extra step of choosing the
<P>To avoid the extra step of choosing the
action from the dialog, do not map the same key to actions that are applicable in the same
context.</P>
@ -185,7 +194,7 @@
<LI>Press <B>OK</B> to import the key bindings.</LI>
</OL>
<P><IMG alt="" border="0" src="../../shared/note.png"> Importing key bindings will override
<P><IMG alt="" border="0" src="../../shared/warning.png"> Importing key bindings will override
your current key bindings settings. It is suggested that you <A href="#Export">export your
key bindings</A> before you import so that you may revert to your previous settings if
necessary.</P>

View file

@ -92,4 +92,6 @@ You can reconfigure the browser display by adding / removing / moving / resizing
Did you know that you can run Ghidra from the command line without invoking the user interface? (See analyzeHeadlessREADME.html in the {Install Dir}/support folder.
You can use the following keys with an open menu or popup menu to quickly move through the menu: Page Up/Down, Home, End, and number keys 1-9.
This is the last tip. You can turn them off now.

View file

@ -26,6 +26,7 @@ import javax.swing.text.JTextComponent;
import docking.action.MultipleKeyAction;
import docking.actions.KeyBindingUtils;
import docking.menu.keys.MenuKeyProcessor;
import ghidra.util.bean.GGlassPane;
import ghidra.util.exception.AssertException;
@ -34,7 +35,7 @@ import ghidra.util.exception.AssertException;
* processing. See {@link #dispatchKeyEvent(KeyEvent)} for a more detailed explanation of how
* Ghidra processes key events.
* <p>
* {@link #install()} must be called in order to install this <code>Singleton</code> into Java's
* {@link #install()} must be called in order to install this <code>Singleton</code> into Java's
* key event processing system.
*/
public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher {
@ -45,14 +46,14 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
* We use this action as a signal that we intend to process a key
* binding and that no other Java component should try to handle it (sometimes Java processes
* bindings on key typed, after we have processed a binding on key pressed, which is not
* what we want).
* what we want).
* <p>
* This action is one that is triggered by a key pressed, but will be processed on a
* key released. We need to do this for because on some systems, when we perform the
* action on a key pressed, we do not get the follow-on key events, which we need to reset
* This action is one that is triggered by a key pressed, but will be processed on a
* key released. We need to do this for because on some systems, when we perform the
* action on a key pressed, we do not get the follow-on key events, which we need to reset
* our state (SCR 7040).
* <p>
* <b>Posterity Note:</b> While debugging we will not get a KeyEvent.KEY_RELEASED event if
* <b>Posterity Note:</b> While debugging we will not get a KeyEvent.KEY_RELEASED event if
* the focus changes from the application to the debugger tool.
*/
private DockingKeyBindingAction inProgressAction;
@ -98,9 +99,9 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
* <P>
* There are some exceptions to this processing chain:
* <ol>
* <li>We don't do any processing when the focused component is an instance of
* <li>We don't do any processing when the focused component is an instance of
* <code>JTextComponent</code>.</li>
* <li>We don't do any processing if the active window is an instance of
* <li>We don't do any processing if the active window is an instance of
* <code>DockingDialog</code>.</li>
* </ol>
*
@ -117,6 +118,10 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
return true;
}
if (MenuKeyProcessor.processMenuKeyEvent(event)) {
return true;
}
DockingKeyBindingAction action = getDockingKeyBindingActionForEvent(event);
if (action == null) {
return false; // let the normal event flow continue
@ -147,11 +152,11 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
return false;
}
// Process the key event in precedence order.
// If it processes the event at any given level, the short-circuit operator will kick out.
// Finally, if the exception statement is reached, then someone has added a new level
// Process the key event in precedence order.
// If it processes the event at any given level, the short-circuit operator will kick out.
// Finally, if the exception statement is reached, then someone has added a new level
// of precedence that this algorithm has not taken into account!
// @formatter:off
// @formatter:off
return processKeyListenerPrecedence(action, keyBindingPrecedence, event) ||
processComponentActionMapPrecedence(action, keyBindingPrecedence, event) ||
processActionAtPrecedence(DefaultLevel, keyBindingPrecedence, action, event) ||
@ -180,10 +185,10 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
JRootPane rootPane = SwingUtilities.getRootPane(component);
if (rootPane == null) {
// This can happen when the source component of the key event has been hidden as a
// result of processing the key event earlier, like on a key pressed event; for
// example, when the user presses the ESC key to close a dialog.
return true; // don't let Java process the remaining event chain
// This can happen when the source component of the key event has been hidden as a
// result of processing the key event earlier, like on a key pressed event; for
// example, when the user presses the ESC key to close a dialog.
return true; // don't let Java process the remaining event chain
}
Component glassPane = rootPane.getGlassPane();
@ -192,16 +197,16 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
return true; // out parent's glass pane is blocking..don't let events through
}
}
// else {
// Msg.debug( KeyBindingOverrideKeyEventDispatcher.this,
// "Found a window with a non-standard glass pane--this should be fixed to " +
// else {
// Msg.debug( KeyBindingOverrideKeyEventDispatcher.this,
// "Found a window with a non-standard glass pane--this should be fixed to " +
// "use the Docking windowing system" );
// }
return false;
}
/**
* Used to clear the flag that signals we are in the middle of processing a Ghidra action.
* Used to clear the flag that signals we are in the middle of processing a Ghidra action.
*/
private boolean actionInProgress(KeyEvent event) {
boolean wasInProgress = inProgressAction != null;
@ -219,7 +224,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
}
/**
* A check to see if a given keystroke is something that should not be processed, depending
* A check to see if a given keystroke is something that should not be processed, depending
* upon the current state of the system.
*
* @param keyStroke The keystroke to check.
@ -231,9 +236,9 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
if (activeWindow instanceof DockingDialog) {
// The choice to ignore modal dialogs was made long ago. We cannot remember why the
// choice was made, but speculate that odd things can happen when keybindings are
// processed with modal dialogs open. For now, do not let key bindings get processed
// for modal dialogs. This can be changed in the future if needed.
// choice was made, but speculate that odd things can happen when keybindings are
// processed with modal dialogs open. For now, do not let key bindings get processed
// for modal dialogs. This can be changed in the future if needed.
DockingDialog dialog = (DockingDialog) activeWindow;
return !dialog.isModal();
}
@ -252,8 +257,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
return false; // we only handle text components
}
// Note: don't do this--it breaks key event handling for text components, as they do
// not get to handle key events when they are not editable (they still should
// Note: don't do this--it breaks key event handling for text components, as they do
// not get to handle key events when they are not editable (they still should
// though, so things like built-in copy/paste still work).
// JTextComponent textComponent = (JTextComponent) focusOwner;
// if (!textComponent.isEditable()) {
@ -261,7 +266,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
// }
// We've made the executive decision to allow all keys to go through to the text component
// unless they are modified with the 'Alt'/'Ctrl'/etc keys, unless they directly used
// unless they are modified with the 'Alt'/'Ctrl'/etc keys, unless they directly used
// by the text component
if (!isModified(event)) {
return true; // unmodified keys will be given to the text component
@ -288,8 +293,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
}
/**
* This method should only be called if a programmer adds a new precedence to
* {@link KeyBindingPrecedence} and does not update the algorithm of
* This method should only be called if a programmer adds a new precedence to
* {@link KeyBindingPrecedence} and does not update the algorithm of
* {@link #dispatchKeyEvent(KeyEvent)} to take into account the new precedence.
*/
private boolean throwAssertException() {
@ -318,7 +323,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
return true;
}
// O.K., there is an action for the KeyStroke, but before we process it, we have to
// O.K., there is an action for the KeyStroke, but before we process it, we have to
// check the proper ordering of key events (see method JavaDoc)
if (processComponentKeyListeners(e)) {
return true;
@ -355,7 +360,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
}
inProgressAction = action; // this will be handled on the release
event.consume(); // don't let this event be used later
event.consume(); // don't let this event be used later
return true;
}
@ -387,7 +392,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
// note: this code is taken from the JComponent method:
// protected boolean processKeyBinding(KeyStroke, KeyEvent, int, boolean )
//
//
// returns true if there is a focused component that has an action for the given keystroke
// and it processes that action.
private boolean processInputAndActionMaps(KeyEvent keyEvent, KeyStroke keyStroke) {
@ -406,7 +411,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
}
private Action getJavaActionForComponent(JComponent jComponent, KeyStroke keyStroke) {
// first see if there is a Java key binding for when the component is in the focused
// first see if there is a Java key binding for when the component is in the focused
// window...
Action action = KeyBindingUtils.getAction(jComponent, keyStroke, JComponent.WHEN_FOCUSED);
if (action != null) {
@ -447,7 +452,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
DockingWindowManager activeManager = DockingWindowManager.getActiveInstance();
if (activeManager == null) {
// this can happen if clients use DockingWindows Look and Feel settings or
// this can happen if clients use DockingWindows Look and Feel settings or
// DockingWindows widgets without using the DockingWindows system (like in tests or
// in stand-alone, non-Ghidra apps).
return null;

View file

@ -20,6 +20,13 @@ import javax.swing.KeyStroke;
import docking.KeyBindingPrecedence;
import docking.actions.KeyBindingUtils;
/**
* An object that contains a key stroke and the precedence for when that key stroke should be used.
*
* <p>Note: this class creates key strokes that work on key {@code pressed}. This effectively
* normalizes all client key bindings to work on the same type of key stroke (pressed, typed or
* released).
*/
public class KeyBindingData {
private KeyStroke keyStroke;
private KeyBindingPrecedence keyBindingPrecedence;
@ -36,6 +43,13 @@ public class KeyBindingData {
this(KeyStroke.getKeyStroke(keyCode, modifiers));
}
/**
* Creates a key stroke from the given text. See
* {@link KeyBindingUtils#parseKeyStroke(KeyStroke)}. The key stroke created for this class
* will always be a key {@code pressed} key stroke.
*
* @param keyStrokeString the key stroke string to parse
*/
public KeyBindingData(String keyStrokeString) {
this(parseKeyStrokeString(keyStrokeString));
}
@ -86,7 +100,7 @@ public class KeyBindingData {
}
/**
* Updates the given data with system-independent versions of key modifiers. For example,
* Updates the given data with system-independent versions of key modifiers. For example,
* the <code>control</code> key will be converted to the <code>command</code> key on the Mac.
* @param newKeyBindingData the data to validate
* @return the potentially changed data

View file

@ -90,7 +90,7 @@ public class KeyBindingUtils {
}
public static ToolOptions importKeyBindings() {
// show a filechooser for the user to choose a location
// show a filechooser for the user to choose a location
InputStream inputStream = getInputStreamForFile(getStartingDir());
return createOptionsforKeybindings(inputStream);
}
@ -145,7 +145,7 @@ public class KeyBindingUtils {
* @param keyBindingOptions The options that contains key binding data.
*/
public static void exportKeyBindings(ToolOptions keyBindingOptions) {
// show a filechooser for the user to choose a location
// show a filechooser for the user to choose a location
OutputStream outputStream = getOutputStreamForFile(getStartingDir());
if (outputStream == null) {
@ -175,12 +175,12 @@ public class KeyBindingUtils {
/**
* Changes the given key event to the new source component and then dispatches that event.
* This method is intended for clients that wish to effectively take a key event given to
* one component and give it to another component.
* This method is intended for clients that wish to effectively take a key event given to
* one component and give it to another component.
*
* <p>This method exists to deal with the complicated nature of key event processing and
* <p>This method exists to deal with the complicated nature of key event processing and
* how our (not Java's) framework processes key event bindings to trigger actions. If not
* for our special processing of action key bindings, then this method would not be
* for our special processing of action key bindings, then this method would not be
* necessary.
*
* <p><b>This is seldom-used code; if you don't know when to use this code, then don't.</b>
@ -201,23 +201,23 @@ public class KeyBindingUtils {
Unusual Code Alert!
The KeyboardFocusManager is a complicated beast. Here we use knowledge of one such
complication to correctly route key events. If the client of this method passes
complication to correctly route key events. If the client of this method passes
a component whose 'isShowing()' returns false, then the manager will not send the
event to that component. Almost all clients will pass fully attached/realized
event to that component. Almost all clients will pass fully attached/realized
components to the manager. We, however, will sometimes pass components that are not
attached; for example, when we are using said components with a renderer to perform
our own painting. In the case of non-attached components, we must call the
our own painting. In the case of non-attached components, we must call the
redispatchEvent() method ourselves.
Why don't we just always call redispatchEvent()? Well, that
method will not pass the new cloned event we just created back through the full
key event pipeline. This means that tool-level (our Tool API, not Java)
actions will not work, as tool-level actions are handled at the beginning of the
Why don't we just always call redispatchEvent()? Well, that
method will not pass the new cloned event we just created back through the full
key event pipeline. This means that tool-level (our Tool API, not Java)
actions will not work, as tool-level actions are handled at the beginning of the
key event pipeline, not by the components themselves.
Also, we have here guilty knowledge that the aforementioned tool-level key processing
will check to see if the event was consumed. If consumed, then no further processing
will happen; if not consumed, then the framework will continue to process the event
Also, we have here guilty knowledge that the aforementioned tool-level key processing
will check to see if the event was consumed. If consumed, then no further processing
will happen; if not consumed, then the framework will continue to process the event
passed into this method. Thus, after we send the new event, we will update the
original event to match the consumed state of our new event. This means that the
component passed to this method must, somewhere in its processing, consume the key
@ -316,7 +316,7 @@ public class KeyBindingUtils {
* @param keyStroke the keystroke for to which the action will be bound
* @param action the action to execute when the given keystroke is triggered
* @param focusCondition the focus condition under which to bind the action
* ({@link JComponent#getInputMap(int)}). See {@link JComponent} for more info;
* ({@link JComponent#getInputMap(int)}). See {@link JComponent} for more info;
* the default is usually {@link JComponent#WHEN_FOCUSED}
*/
public static void registerAction(JComponent component, KeyStroke keyStroke, Action action,
@ -348,7 +348,7 @@ public class KeyBindingUtils {
}
/**
* Allows the client to clear Java key bindings when the client is creating a docking
* Allows the client to clear Java key bindings when the client is creating a docking
* action. Without this call, any actions bound to the given component will prevent an
* action with the same key binding from firing. This is useful when your
* application is using tool-level key bindings that share the same
@ -370,8 +370,8 @@ public class KeyBindingUtils {
* application is using tool-level key bindings that share the same
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
* <p>
* Note: this method clears the key binding for the
* {@link JComponent#WHEN_FOCUSED} and
* Note: this method clears the key binding for the
* {@link JComponent#WHEN_FOCUSED} and
* {@link JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT} focus conditions.
*
* @param component the component for which to clear the key binding
@ -455,10 +455,10 @@ public class KeyBindingUtils {
}
/**
* A utility method to get all key binding actions. This method will
* A utility method to get all key binding actions. This method will
* only return actions that support {@link KeyBindingType key bindings}.
*
* <p>The mapping returned provides a list of items because it is possible for there to
* <p>The mapping returned provides a list of items because it is possible for there to
* exists multiple actions with the same name and owner. (This can happen when multiple copies
* of a component provider are shown, each with their own set of actions that share the
* same name.)
@ -474,7 +474,7 @@ public class KeyBindingUtils {
for (DockingActionIf action : actions) {
if (isIgnored(action)) {
// don't bother tracking non-keybinding actions; this would be a mistake due
// to the potential for a shared key binding action overwriting its
// to the potential for a shared key binding action overwriting its
// SharedStubKeyBindingAction
continue;
}
@ -486,8 +486,8 @@ public class KeyBindingUtils {
}
/**
* A utility method to get all key binding actions that have the given owner.
* This method will remove duplicate actions and will only return actions
* A utility method to get all key binding actions that have the given owner.
* This method will remove duplicate actions and will only return actions
* that support {@link KeyBindingType key bindings}.
*
* @param tool the tool containing the actions
@ -502,7 +502,7 @@ public class KeyBindingUtils {
for (DockingActionIf action : actions) {
if (isIgnored(action)) {
// don't bother tracking non-keybinding actions; this would be a mistake due
// to the potential for a shared key binding action overwriting its
// to the potential for a shared key binding action overwriting its
// SharedStubKeyBindingAction
continue;
}
@ -590,10 +590,10 @@ public class KeyBindingUtils {
//@formatter:off
String s = "Shared Key Binding Actions have different default values. These " +
"must be the same." +
"\n\tAction name: '"+existingAction.getName()+"'" +
"\n\tAction name: '"+existingAction.getName()+"'" +
"\n\tAction 1: " + existingAction.getInceptionInformation() +
"\n\t\tKey Binding: " + existingDefaultKs +
"\n\tAction 2: " + newAction.getInceptionInformation() +
"\n\tAction 2: " + newAction.getInceptionInformation() +
"\n\t\tKey Binding: " + newAction.getKeyBinding() +
"\nUsing the " +
"first value set - " + existingDefaultKs;
@ -603,14 +603,14 @@ public class KeyBindingUtils {
}
/**
* Updates the given data with system-independent versions of key modifiers. For example,
* Updates the given data with system-independent versions of key modifiers. For example,
* the <code>control</code> key will be converted to the <code>command</code> key on the Mac.
*
* @param keyStroke the keystroke to validate
* @return the potentially changed keystroke
*/
// TODO ignore the deprecation, as this method is responsible for fixing deprecated usage.
// When all actions no longer user the deprecated modifiers, the deprecated elements
// TODO ignore the deprecation, as this method is responsible for fixing deprecated usage.
// When all actions no longer user the deprecated modifiers, the deprecated elements
// of this method can be removed
@SuppressWarnings("deprecation")
public static KeyStroke validateKeyStroke(KeyStroke keyStroke) {
@ -667,10 +667,10 @@ public class KeyBindingUtils {
* and we want it to look like: "Ctrl-M".
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
* and we want it to look like: "Ctrl-M".
* <br>In Java 11 we have seen toString() values get printed with repeated text, such
* <br>In Java 11 we have seen toString() values get printed with repeated text, such
* as: "shift ctrl pressed SHIFT". We want to trim off the repeated modifiers.
*
* @param keyStroke the key stroke
* @param keyStroke the key stroke
* @return the string value; the empty string if the key stroke is null
*/
public static String parseKeyStroke(KeyStroke keyStroke) {
@ -744,28 +744,28 @@ public class KeyBindingUtils {
return StringUtils.indexOfIgnoreCase(source, search, offset);
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isShift(int mask) {
return (mask & InputEvent.SHIFT_DOWN_MASK) != 0 || (mask & InputEvent.SHIFT_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isAlt(int mask) {
return (mask & InputEvent.ALT_DOWN_MASK) != 0 || (mask & InputEvent.ALT_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isControl(int mask) {
return (mask & InputEvent.CTRL_DOWN_MASK) != 0 || (mask & InputEvent.CTRL_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isMeta(int mask) {
@ -773,18 +773,21 @@ public class KeyBindingUtils {
}
/**
* Parses the given text into a KeyStroke. This method relies upon
* Parses the given text into a KeyStroke. This method relies upon
* {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method
* will perform fixup on the given text for added flexibility. For example, the given
* text may contain spaces or dashes as the separators between parts in the string. Also,
* the text is converted such that it is not case-sensitive. So, the following example
* will perform fixup on the given text for added flexibility. For example, the given
* text may contain spaces or dashes as the separators between parts in the string. Also,
* the text is converted such that it is not case-sensitive. So, the following example
* formats are allowed:
* <pre>
* Alt-F
* alt p
* Ctrl-Alt-Z
* ctrl Z
* </pre>
* </pre>
*
* <p><b>Note:</b> The returned keystroke will always correspond to a {@code pressed} event,
* regardless of the value passed in (pressed, typed or released).
*
* @param keyStroke the key stroke
* @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)}
@ -855,10 +858,10 @@ public class KeyBindingUtils {
//==================================================================================================
// Private Methods
//==================================================================================================
//==================================================================================================
private static boolean isIgnored(DockingActionIf action) {
// a shared keybinding implies that this action should not be in
// a shared keybinding implies that this action should not be in
// the UI, as there will be a single proxy in place of all actions sharing that binding
return !action.getKeyBindingType().isManaged();
}

View file

@ -0,0 +1,43 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import javax.swing.*;
class EndMenuKeyHandler extends MenuKeyHandler {
@Override
void process(MenuSelectionManager manager, MenuElement[] path) {
int popupIndex = getLeafPopupIndex(path);
if (popupIndex == -1) {
return;
}
JPopupMenu popup = (JPopupMenu) path[popupIndex];
int n = popup.getComponentCount();
MenuElement newItem = getPreviousValidItem(popup, n - 1);
int length = path.length - 1 == popupIndex ? path.length + 1 : path.length;
MenuElement[] newPath = new MenuElement[length];
System.arraycopy(path, 0, newPath, 0, popupIndex + 1);
newPath[popupIndex + 1] = newItem;
// replace last path element
manager.setSelectedPath(newPath);
}
}

View file

@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import javax.swing.*;
class HomeMenuKeyHandler extends MenuKeyHandler {
@Override
void process(MenuSelectionManager manager, MenuElement[] path) {
int popupIndex = getLeafPopupIndex(path);
if (popupIndex == -1) {
return;
}
JPopupMenu popup = (JPopupMenu) path[popupIndex];
MenuElement newItem = getNextValidItem(popup, 0);
int length = path.length - 1 == popupIndex ? path.length + 1 : path.length;
MenuElement[] newPath = new MenuElement[length];
System.arraycopy(path, 0, newPath, 0, popupIndex + 1);
newPath[popupIndex + 1] = newItem;
// replace last path element
manager.setSelectedPath(newPath);
}
}

View file

@ -0,0 +1,260 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import java.awt.Component;
import javax.swing.*;
/**
* The interface for work to be done on an open menu.
*/
abstract class MenuKeyHandler {
/**
* The work method of this handler. This method will only be called when a menu or popup
* menu is open.
*
* @param manager the active menu selection manager
* @param path the active menu path
*/
abstract void process(MenuSelectionManager manager, MenuElement[] path);
protected int getLeafPopupIndex(MenuElement[] path) {
if (path != null) {
for (int i = path.length - 1; i >= 0; i--) {
MenuElement menuElement = path[i];
if (menuElement instanceof JPopupMenu) {
return i;
}
}
}
return -1;
}
protected JPopupMenu getLeafPopupMenu(MenuElement[] path) {
int index = getLeafPopupIndex(path);
if (index >= 0) {
return (JPopupMenu) path[index];
}
return null;
}
protected int findActiveMenuItemIndex(MenuSelectionManager manager, MenuElement[] path) {
int popupIndexdex = getLeafPopupIndex(path);
if (popupIndexdex == -1) {
return -1; // not sure if this can happen
}
if (popupIndexdex == path.length - 1) {
// last item is the popup--no selected menu item
return -1;
}
MenuElement activeItem = path[path.length - 1];
JPopupMenu popup = (JPopupMenu) path[popupIndexdex];
int count = 0;
int n = popup.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = popup.getComponent(i);
if (isValidItem(c)) {
if (activeItem == c) {
return count;
}
count++;
}
}
return -1;
}
protected int findActiveMenuItemRawIndex(MenuSelectionManager manager, MenuElement[] path) {
int popupIndexdex = getLeafPopupIndex(path);
if (popupIndexdex == -1) {
return -1; // not sure if this can happen
}
if (popupIndexdex == path.length - 1) {
// last item is the popup--no selected menu item
return -1;
}
MenuElement activeItem = path[path.length - 1];
JPopupMenu popup = (JPopupMenu) path[popupIndexdex];
int count = 0;
int n = popup.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = popup.getComponent(i);
if (activeItem == c) {
return count;
}
count++;
}
return -1;
}
protected int findNextSeparatorIndex(JPopupMenu popup, int startIndex) {
int n = popup.getComponentCount();
for (int i = startIndex; i < n; i++) {
Component c = popup.getComponent(i);
if (c instanceof JSeparator) {
return i;
}
}
return -1;
}
protected int findPreviousSeparatorIndex(JPopupMenu popup, int startIndex) {
for (int i = startIndex; i >= 0; i--) {
Component c = popup.getComponent(i);
if (c instanceof JSeparator) {
return i;
}
}
return -1;
}
protected int findNextValidIndex(JPopupMenu popup, int startIndex) {
int n = popup.getComponentCount();
for (int i = startIndex; i < n; i++) {
Component c = popup.getComponent(i);
if (isValidItem(c)) {
return i;
}
}
return -1;
}
protected int findPreviousValidIndex(JPopupMenu popup, int startIndex) {
for (int i = startIndex; i >= 0; i--) {
Component c = popup.getComponent(i);
if (isValidItem(c)) {
return i;
}
}
return -1;
}
protected int moveForward(MenuSelectionManager manager, MenuElement[] path,
int offset) {
JPopupMenu popup = getLeafPopupMenu(path);
if (popup == null) {
return -1;
}
int itemCount = getItemCount(popup);
// handle wrapping around to the top again
int updatedOffset = offset >= itemCount ? offset % itemCount : offset;
int progress = 0;
int n = popup.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = popup.getComponent(i);
if (isValidItem(c)) {
if (progress == updatedOffset) {
return i;
}
progress++;
}
}
return -1;
}
protected void setNewMenuItemIndex(MenuSelectionManager manager, MenuElement[] path,
int index) {
if (index < 0) {
return;
}
int popupIndex = getLeafPopupIndex(path);
JPopupMenu popup = (JPopupMenu) path[popupIndex];
JMenuItem newItem = (JMenuItem) popup.getComponent(index);
int length = path.length - 1 == popupIndex ? path.length + 1 : path.length;
MenuElement[] newPath = new MenuElement[length];
System.arraycopy(path, 0, newPath, 0, popupIndex + 1);
newPath[popupIndex + 1] = newItem;
// replace last path element
manager.setSelectedPath(newPath);
}
protected MenuElement getNextValidItem(JPopupMenu popup, int start) {
int n = popup.getComponentCount();
for (int i = 0; i < n; i++) {
// handle wrapping
int updated = i + start;
int index = (updated < n) ? updated : Math.abs(i - (n - start));
Component c = popup.getComponent(index);
if (isValidItem(c)) {
return (MenuElement) c;
}
}
return null;
}
protected MenuElement getPreviousValidItem(JPopupMenu popup, int offset) {
int n = popup.getComponentCount();
for (int i = n - 1; i >= 0; i--) {
// handle wrapping
int updated = (n - (i + 1));
int index = (updated > offset) ? updated : offset - updated;
Component c = popup.getComponent(index);
if (isValidItem(c)) {
return (MenuElement) c;
}
}
return null;
}
protected int getItemCount(JPopupMenu popup) {
int count = 0;
int n = popup.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = popup.getComponent(i);
if (isValidItem(c)) {
count++;
}
}
return count;
}
protected boolean isValidItem(Component c) {
return c instanceof JMenuItem && c.isEnabled();
}
}

View file

@ -0,0 +1,74 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
/**
* Handles the processing of key events while menus or popup menus are open.
*/
public class MenuKeyProcessor {
private static Map<KeyStroke, MenuKeyHandler> menuHandlersByKeyStroke = new HashMap<>();
static {
menuHandlersByKeyStroke.put(keyStroke("HOME"), new HomeMenuKeyHandler());
menuHandlersByKeyStroke.put(keyStroke("END"), new EndMenuKeyHandler());
menuHandlersByKeyStroke.put(keyStroke("PAGE_UP"), new PageUpMenuKeyHandler());
menuHandlersByKeyStroke.put(keyStroke("PAGE_DOWN"), new PageDownMenuKeyHandler());
for (int i = 1; i < 10; i++) {
menuHandlersByKeyStroke.put(keyStroke(Integer.toString(i)),
new NumberMenuKeyHandler(i));
}
}
/**
* Checks the given event to see if it has a registered action to perform while a menu is open.
* If a menu is open and a handler exists, the handler will be called.
*
* @param event the event to check
* @return true if the event triggered a handler
*/
public static boolean processMenuKeyEvent(KeyEvent event) {
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
MenuElement[] path = manager.getSelectedPath();
if (path == null || path.length == 0) {
return false; // no menu showing
}
KeyStroke eventStroke = KeyStroke.getKeyStrokeForEvent(event);
MenuKeyHandler Handler = menuHandlersByKeyStroke.get(eventStroke);
if (Handler != null) {
Handler.process(manager, path);
event.consume();
return true;
}
return false;
}
private static KeyStroke keyStroke(String s) {
return KeyStroke.getKeyStroke("pressed " + s);
}
}

View file

@ -0,0 +1,36 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
class NumberMenuKeyHandler extends MenuKeyHandler {
private int number;
NumberMenuKeyHandler(int number) {
this.number = number;
}
@Override
public void process(MenuSelectionManager manager, MenuElement[] path) {
int activeMenuItemIndex = findActiveMenuItemIndex(manager, path);
int amount = activeMenuItemIndex + number;
int nextIndex = moveForward(manager, path, amount);
setNewMenuItemIndex(manager, path, nextIndex);
}
}

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import javax.swing.*;
class PageDownMenuKeyHandler extends MenuKeyHandler {
@Override
void process(MenuSelectionManager manager, MenuElement[] path) {
JPopupMenu popup = getLeafPopupMenu(path);
if (popup == null) {
return;
}
int activeIndex = findActiveMenuItemRawIndex(manager, path);
int separatorIndex = findNextSeparatorIndex(popup, activeIndex + 1);
int nextIndex = findNextValidIndex(popup, separatorIndex + 1);
if (nextIndex < 0) {
separatorIndex = -1; // wrap the search; start at the top
nextIndex = findNextValidIndex(popup, separatorIndex + 1);
}
setNewMenuItemIndex(manager, path, nextIndex);
}
}

View file

@ -0,0 +1,46 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.menu.keys;
import javax.swing.*;
class PageUpMenuKeyHandler extends MenuKeyHandler {
@Override
void process(MenuSelectionManager manager, MenuElement[] path) {
JPopupMenu popup = getLeafPopupMenu(path);
if (popup == null) {
return;
}
int activeIndex = findActiveMenuItemRawIndex(manager, path);
int separatorIndex = -1;
if (activeIndex >= 0) {
// Only search for separator with an active item. This will trigger the search
// to start at the bottom of the menu
separatorIndex = findPreviousSeparatorIndex(popup, activeIndex - 1);
}
int nextIndex = findPreviousValidIndex(popup, separatorIndex - 1);
if (nextIndex < 0) {
separatorIndex = popup.getComponentCount(); // wrap the search; start at the bottom
nextIndex = findPreviousValidIndex(popup, separatorIndex - 1);
}
setNewMenuItemIndex(manager, path, nextIndex);
}
}

View file

@ -199,7 +199,7 @@ public class ToolOptions extends AbstractOptions {
Element elem = null;
if (value == null) {
// Handle the null case ourselves, not using the wrapped option (and when
// reading from xml) so that the logic does not need to in each wrapped option
// reading from xml) so the logic does not need to be in each wrapped option
elem = ss.saveToXml();
elem.addContent(new Element(CLEARED_VALUE_ELEMENT_NAME));
}