diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java b/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java index f5cc17bb21..d50491de52 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -127,7 +127,7 @@ public class MenuData { StringBuilder buildy = new StringBuilder(); for (int i = 0; i < menuPath.length; i++) { if (i != (menuPath.length - 1)) { - buildy.append(processMenuItemName(menuPath[i])); + buildy.append(stripMnemonicAmp(menuPath[i])); buildy.append("->"); } else { @@ -272,7 +272,7 @@ public class MenuData { * of the characters of the name as the new mnemonic of this item */ public void setMenuItemName(String newMenuItemName) { - String processedMenuItemName = processMenuItemName(newMenuItemName); + String processedMenuItemName = stripMnemonicAmp(newMenuItemName); if (processedMenuItemName.equals(menuPath[menuPath.length - 1])) { return; } @@ -298,22 +298,23 @@ public class MenuData { firePropertyChanged(oldData); } - private static int getMnemonic(String[] menuPath) { + public static int getMnemonic(String[] menuPath) { if (menuPath == null || menuPath.length == 0) { return NO_MNEMONIC; } return getMnemonic(menuPath[menuPath.length - 1]); } - private static int getMnemonic(String string) { - int indexOf; - int fromIndex = 0; - do { - indexOf = string.indexOf('&', fromIndex); - fromIndex = indexOf + 2; - } while (indexOf >= 0 && indexOf < string.length() - 1 && string.charAt(indexOf + 1) == '&'); - if (indexOf >= 0 && indexOf < string.length() - 1) { - return string.charAt(indexOf + 1); + /** + * Parses the mnemonic key from the menu items text. + * @param menuName the menu item text + * @return the mnemonic key for encoded in the actions menu text. Returns 0 if there is none. + */ + public static int getMnemonic(String menuName) { + String cleaned = menuName.replaceAll("&&", ""); + int firstIndex = cleaned.indexOf('&'); + if (firstIndex >= 0 && firstIndex < cleaned.length() - 1) { + return cleaned.charAt(firstIndex + 1); } return NO_MNEMONIC; } @@ -321,27 +322,36 @@ public class MenuData { private static String[] processMenuPath(String[] menuPath) { String[] copy = Arrays.copyOf(menuPath, menuPath.length); if (copy != null && copy.length > 0) { - copy[copy.length - 1] = processMenuItemName(copy[copy.length - 1]); + copy[copy.length - 1] = stripMnemonicAmp(copy[copy.length - 1]); } return copy; } - private static String processMenuItemName(String string) { - int firstAmp = string.indexOf('&'); - if (firstAmp < 0) { - return string; + /** + * Removes any single '&' characters used to set the mnemonic from the menu item name. The + * '&' character can be included in the name by escaping with another '&' character. + * @param menuItemName the name that may include mnemonic information + * @return the menu item name with single '&' characters removed. + */ + public static String stripMnemonicAmp(String menuItemName) { + if (menuItemName.indexOf('&') < 0) { + return menuItemName; } - StringBuilder builder = new StringBuilder(string.substring(0, firstAmp)); - for (int i = firstAmp; i < string.length(); i++) { - char ch = string.charAt(i); - if (ch == '&') { - if (i < string.length() - 1 && string.charAt(i+1) == '&') { - builder.append('&'); - i++; - } - } else { - builder.append(ch); + + StringBuilder builder = new StringBuilder(); + boolean previousWasAmpersand = false; + for (int i = 0; i < menuItemName.length(); i++) { + char c = menuItemName.charAt(i); + if (c != '&') { + builder.append(c); + previousWasAmpersand = false; + continue; } + if (previousWasAmpersand) { + // add in escaped ampersand (double ampersands are replace with one ampersand) + builder.append('&'); + } + previousWasAmpersand = !previousWasAmpersand; } return builder.toString(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java index 8726327506..0edf2d5e44 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -103,8 +103,8 @@ public class MenuBarManager implements MenuGroupListener { */ private MenuManager getMenuManager(String menuName) { - char mk = MenuManager.getMnemonicKey(menuName); - menuName = MenuManager.stripMnemonicAmp(menuName); + int mk = MenuData.getMnemonic(menuName); + menuName = MenuData.stripMnemonicAmp(menuName); MenuManager mgr = menuManagers.get(menuName); if (mgr == null) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java index 4d7fbcfc6d..239015eb32 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,7 +34,7 @@ public class MenuManager implements ManagedMenuItem { private String name; private final String[] menuPath; - private char mnemonicKey = '\0'; + private int mnemonicKey = MenuData.NO_MNEMONIC; private int level; private boolean usePopupPath; private MenuHandler menuHandler; @@ -53,7 +53,7 @@ public class MenuManager implements ManagedMenuItem { * @param menuHandler Listener to be notified of menu behavior. * @param menuGroupMap maps menu groups to menu paths */ - public MenuManager(String name, char mnemonicKey, String group, boolean usePopupPath, + public MenuManager(String name, int mnemonicKey, String group, boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) { this(name, new String[] { name }, mnemonicKey, 0, group, usePopupPath, menuHandler, menuGroupMap); @@ -71,7 +71,7 @@ public class MenuManager implements ManagedMenuItem { * @param menuHandler Listener to be notified of menu behavior. * @param menuGroupMap maps menu groups to menu paths */ - MenuManager(String name, String[] menuPath, char mnemonicKey, int level, String group, + MenuManager(String name, String[] menuPath, int mnemonicKey, int level, String group, boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) { this.name = name; this.menuPath = menuPath; @@ -120,8 +120,8 @@ public class MenuManager implements ManagedMenuItem { String[] fullPath = menuData.getMenuPath(); String displayName = fullPath[level]; - char mnemonic = getMnemonicKey(displayName); - String realName = stripMnemonicAmp(displayName); + int mnemonic = MenuData.getMnemonic(displayName); + String realName = MenuData.stripMnemonicAmp(displayName); MenuManager subMenu = subMenus.get(realName); if (subMenu != null) { return subMenu; @@ -187,37 +187,6 @@ public class MenuManager implements ManagedMenuItem { return null; } - /** - * Parses the mnemonic key from the menu items text. - * @param str the menu item text - * @return the mnemonic key for encoded in the actions menu text. Returns 0 if there is none. - */ - public static char getMnemonicKey(String str) { - int ampLoc = str.indexOf('&'); - char mk = '\0'; - if (ampLoc >= 0 && ampLoc < str.length() - 1) { - mk = str.charAt(ampLoc + 1); - } - return mk; - } - - /*** - * Removes the Mnemonic indicator character (&) from the text - * @param text the text to strip - * @return the stripped mnemonic - */ - public static String stripMnemonicAmp(String text) { - int ampLoc = text.indexOf('&'); - if (ampLoc < 0) { - return text; - } - String s = text.substring(0, ampLoc); - if (ampLoc < (text.length() - 1)) { - s += text.substring(++ampLoc); - } - return s; - } - /** * Tests if this menu is empty. */ @@ -233,7 +202,7 @@ public class MenuManager implements ManagedMenuItem { public JMenu getMenu() { if (menu == null) { menu = new JMenu(name); - if (mnemonicKey != '\0') { + if (mnemonicKey != MenuData.NO_MNEMONIC) { menu.setMnemonic(mnemonicKey); } if (menuHandler != null) { @@ -261,7 +230,7 @@ public class MenuManager implements ManagedMenuItem { @Override public JMenuItem getMenuItem() { JMenu localMenu = getMenu(); - localMenu.setUI((DockingMenuUI) DockingMenuUI.createUI(localMenu)); + localMenu.setUI(DockingMenuUI.createUI(localMenu)); return localMenu; } diff --git a/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java b/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java index 400e2e0bb9..548385bfae 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/action/MenuDataTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -32,16 +32,16 @@ public class MenuDataTest { @Test public void testMenuDataParsesMnemonicFromAmpersand() { MenuData menuData = new MenuData(new String[] { "One", "Two", "&Three" }); - assertEquals(menuData.getMnemonic(), 'T'); + assertEquals('T', menuData.getMnemonic()); } - + /** * There should be no mnemonic, the ampersand is escaped. */ @Test public void testMenuDataMnemonicSkipsEscapedAmpersand() { MenuData menuData = new MenuData(new String[] { "One", "Two", "&&Three" }); - assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); + assertEquals(MenuData.NO_MNEMONIC, menuData.getMnemonic()); } /** @@ -51,7 +51,7 @@ public class MenuDataTest { @Test public void testMenuDataMnemonicEscapesAmpersandLeftToRight() { MenuData menuData = new MenuData(new String[] { "One", "Two", "T&&&hree" }); - assertEquals(menuData.getMnemonic(), 'h'); + assertEquals('h', menuData.getMnemonic()); } /** @@ -61,7 +61,7 @@ public class MenuDataTest { @Test public void testMenuDataMnemonicIgnoresTrailingAmpersand() { MenuData menuData = new MenuData(new String[] { "One", "Two", "Three&" }); - assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); + assertEquals(MenuData.NO_MNEMONIC, menuData.getMnemonic()); } /** @@ -71,7 +71,7 @@ public class MenuDataTest { @Test public void testMenuDataMnemonicParsesLeftToRight() { MenuData menuData = new MenuData(new String[] { "One", "Two", "&T&hree" }); - assertEquals(menuData.getMnemonic(), 'T'); + assertEquals('T', menuData.getMnemonic()); } /** @@ -81,7 +81,7 @@ public class MenuDataTest { public void testMenuDataPassedMnemonicWins() { MenuData menuData = new MenuData( new String[] { "One", "Two", "&Three" }, null, null, 'h', null); - assertEquals(menuData.getMnemonic(), 'h'); + assertEquals('h', menuData.getMnemonic()); } /** @@ -110,7 +110,7 @@ public class MenuDataTest { String newName = "Completely New Name"; menuData.setMenuItemName(newName); - assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); + assertEquals(MenuData.NO_MNEMONIC, menuData.getMnemonic()); } @Test @@ -123,15 +123,15 @@ public class MenuDataTest { String newName = "Completely New Name"; String[] newPath = { "Four", newName }; menuData.setMenuPath(newPath); - assertEquals(menuData.getMnemonic(), MenuData.NO_MNEMONIC); + assertEquals(MenuData.NO_MNEMONIC, menuData.getMnemonic()); } - + @Test public void testGetMenuItemNameEscapesAmpersand() { MenuData menuData = new MenuData(new String[] { "One", "Two", "&&Three" }); - assertEquals(menuData.getMenuItemName(), "&Three"); + assertEquals("&Three", menuData.getMenuItemName()); } - + /** * Ampersands that are not escaped should be ignored regardless of use as * mnemonics. @@ -140,8 +140,8 @@ public class MenuDataTest { public void testGetMenuItemNameIgnoresUnescapedAmpersand() { MenuData menuData = new MenuData(new String[] { "One", "Two", "Three&" }); assertEquals(menuData.getMenuItemName(), "Three"); - + menuData.setMenuItemName("&T&hree"); - assertEquals(menuData.getMenuItemName(), "Three"); + assertEquals("Three", menuData.getMenuItemName()); } }