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>
</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;
@ -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

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));
}

View file

@ -786,6 +786,9 @@ public class KeyBindingUtils {
* ctrl Z
* </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)}
*/

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));
}