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

View file

@ -59,7 +59,7 @@
"4">Subroutine</FONT></B><IMG border="0" src="../../shared/arrow.gif"><FONT size= "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> "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> </BLOCKQUOTE>
@ -69,7 +69,7 @@
<P> <P>
This action modularizes the program tree by creating a call tree of the 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 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> </P>
</BLOCKQUOTE> </BLOCKQUOTE>

View file

@ -32,6 +32,17 @@
on a <A href="help/topics/BlockModel/Block_Model.htm">Block Model</A>. The following paragraphs on a <A href="help/topics/BlockModel/Block_Model.htm">Block Model</A>. The following paragraphs
describe features of the Program Tree.</P> 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> <H2><A name="FoldersAndFragments"></A>Folders and Fragments</H2>
<BLOCKQUOTE> <BLOCKQUOTE>

View file

@ -5,7 +5,7 @@
<META name="generator" content= <META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net"> "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"> <META http-equiv="content-type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css"> <LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
<META name="generator" content="Microsoft FrontPage 4.0"> <META name="generator" content="Microsoft FrontPage 4.0">
@ -13,7 +13,7 @@
<BODY> <BODY>
<H1><A name="ProgramTreePlugin"></A><A name="Program_Tree"></A> <H1><A name="ProgramTreePlugin"></A><A name="Program_Tree"></A>
Program Tree Plugin Program Tree View Management
</H1> </H1>
<P>Program Trees are used to organize programs into a tree structure. Nodes within the a <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> <H2><A name="KeyBindings_Option"></A><B>Key Bindings</B></H2>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>You can create a new "hot key" for a Plugin's action or modify the default key binding. <P>You can create a new key binding (<I>accelerator key</I>) for an action or modify the
The hot key (or accelerator) that you add can be used to execute the action with a keystroke default key binding. The key binding that you add can be used to execute the action
combination.</P> using the keyboard. Below we describe the <B>Key Bindings</B> options editor.</P>
<BLOCKQUOTE> <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 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> item. Click <A href="#KeyBindingPopup">here</A> for more info.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<P>The <I>Key Bindings</I> panel has a table containing the <I>Action Name</I>, <I>Key <P>The <B>Key Bindings</B> option editor has a table with the following sortable columns:
Binding</I>, and <I>Plugin Name</I>. You can sort the columns in ascending or descending <I>Action Name</I>, <I>Key Binding</I>, and <I>Plugin Name</I>. To change a value in
order. (By default, the <I>Action Nam</I>e column is sorted in ascending order.) The Plugin the table, select the row and then edit the text field below the table.</P>
name is the name of the plugin supplying the action.</P>
<UL> <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>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 <LI>If an action has a description to explain what it does, it will be displayed below the
text field.</LI> text field.</LI>
</UL> </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 <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> Key Bindings Options panel works the same as for a regular Ghidra Tool.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
@ -130,12 +139,12 @@
</OL> </OL>
</BLOCKQUOTE> </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 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 is enabled), then a dialog is displayed for you to choose what action you want to
perform.</P> 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 action from the dialog, do not map the same key to actions that are applicable in the same
context.</P> context.</P>
@ -185,7 +194,7 @@
<LI>Press <B>OK</B> to import the key bindings.</LI> <LI>Press <B>OK</B> to import the key bindings.</LI>
</OL> </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 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 key bindings</A> before you import so that you may revert to your previous settings if
necessary.</P> 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. 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. 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.action.MultipleKeyAction;
import docking.actions.KeyBindingUtils; import docking.actions.KeyBindingUtils;
import docking.menu.keys.MenuKeyProcessor;
import ghidra.util.bean.GGlassPane; import ghidra.util.bean.GGlassPane;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@ -117,6 +118,10 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
return true; return true;
} }
if (MenuKeyProcessor.processMenuKeyEvent(event)) {
return true;
}
DockingKeyBindingAction action = getDockingKeyBindingActionForEvent(event); DockingKeyBindingAction action = getDockingKeyBindingActionForEvent(event);
if (action == null) { if (action == null) {
return false; // let the normal event flow continue return false; // let the normal event flow continue

View file

@ -20,6 +20,13 @@ import javax.swing.KeyStroke;
import docking.KeyBindingPrecedence; import docking.KeyBindingPrecedence;
import docking.actions.KeyBindingUtils; 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 { public class KeyBindingData {
private KeyStroke keyStroke; private KeyStroke keyStroke;
private KeyBindingPrecedence keyBindingPrecedence; private KeyBindingPrecedence keyBindingPrecedence;
@ -36,6 +43,13 @@ public class KeyBindingData {
this(KeyStroke.getKeyStroke(keyCode, modifiers)); 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) { public KeyBindingData(String keyStrokeString) {
this(parseKeyStrokeString(keyStrokeString)); this(parseKeyStrokeString(keyStrokeString));
} }

View file

@ -786,6 +786,9 @@ public class KeyBindingUtils {
* ctrl 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 * @param keyStroke the key stroke
* @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)} * @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)}
*/ */

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; Element elem = null;
if (value == null) { if (value == null) {
// Handle the null case ourselves, not using the wrapped option (and when // 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 = ss.saveToXml();
elem.addContent(new Element(CLEARED_VALUE_ELEMENT_NAME)); elem.addContent(new Element(CLEARED_VALUE_ELEMENT_NAME));
} }