001package jmri.util.swing; 002 003import java.awt.Container; 004import java.beans.PropertyChangeListener; 005import java.lang.reflect.InvocationTargetException; 006import java.util.ArrayList; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010import javax.swing.Action; 011import javax.swing.ButtonGroup; 012import javax.swing.JMenu; 013import javax.swing.JMenuItem; 014import javax.swing.JRadioButtonMenuItem; 015import javax.swing.UIManager; 016 017import jmri.util.SystemType; 018import jmri.util.jdom.LocaleSelector; 019 020import org.jdom2.Element; 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Common utility methods for working with JMenus. 026 * <p> 027 * Chief among these is the loadMenu method, for creating a set of menus from an 028 * XML definition 029 * 030 * @author Bob Jacobsen Copyright 2003, 2010 031 * @since 2.9.4 032 */ 033public class JMenuUtil extends GuiUtilBase { 034 035 static public JMenu[] loadMenu(String path, WindowInterface wi, Object context) { 036 Element root = rootFromName(path); 037 038 int n = root.getChildren("node").size(); 039 JMenu[] retval = new JMenu[n]; 040 041 int i = 0; 042 ArrayList<Integer> mnemonicList = new ArrayList<Integer>(); 043 for (Object child : root.getChildren("node")) { 044 JMenu menuItem = jMenuFromElement((Element) child, wi, context); 045 retval[i++] = menuItem; 046 if (((Element) child).getChild("mnemonic") != null) { 047 int mnemonic = convertStringToKeyEvent(((Element) child).getChild("mnemonic").getText()); 048 if (mnemonicList.contains(mnemonic)) { 049 log.error("Menu item '{}' Mnemonic '{}' has already been assigned", menuItem.getText(), ((Element) child).getChild("mnemonic").getText()); 050 } else { 051 menuItem.setMnemonic(mnemonic); 052 mnemonicList.add(mnemonic); 053 } 054 } 055 } 056 return retval; 057 } 058 059 static @Nonnull 060 JMenu jMenuFromElement(@CheckForNull Element main, WindowInterface wi, Object context) { 061 boolean addSep = false; 062 String name = "<none>"; 063 if (main == null) { 064 log.warn("Menu from element called without an element"); 065 return new JMenu(name); 066 } 067 name = LocaleSelector.getAttribute(main, "name"); 068 //Next statement left in if the xml file hasn't been converted 069 if ((name == null) || (name.equals(""))) { 070 if (main.getChild("name") != null) { 071 name = main.getChild("name").getText(); 072 } 073 } 074 JMenu menu = new JMenu(name); 075 ArrayList<Integer> mnemonicList = new ArrayList<>(); 076 for (Object item : main.getChildren("node")) { 077 JMenuItem menuItem = null; 078 Element child = (Element) item; 079 if (child.getChildren("node").isEmpty()) { // leaf 080 if ((child.getText().trim()).equals("separator")) { 081 addSep = true; 082 } else { 083 084 boolean optional = true; 085 var instanceManagerOptionalClassElement = child.getChild("instanceManagerOptionalClass"); 086 var optionalFunctionElement = child.getChild("optionalFunction"); 087 088 if (instanceManagerOptionalClassElement != null 089 && optionalFunctionElement != null) { 090 091 var instanceManagerOptional = instanceManagerOptionalClassElement.getText(); 092 var optionalFunction = optionalFunctionElement.getText(); 093 Object o = jmri.InstanceManager.getDefault(instanceManagerOptional); 094 try { 095 var method = o.getClass().getDeclaredMethod(optionalFunction); 096 Object result = method.invoke(o); 097 if (result instanceof Boolean) { 098 optional = (Boolean) result; 099 } 100 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) { 101 log.error("Error during loading of menu", e); 102 } 103 } 104 105 if (!(SystemType.isMacOSX() 106 && UIManager.getLookAndFeel().isNativeLookAndFeel() 107 && ((child.getChild("adapter") != null 108 && child.getChild("adapter").getText().equals("apps.gui3.tabbedpreferences.TabbedPreferencesAction")) 109 || (child.getChild("current") != null 110 && child.getChild("current").getText().equals("quit")))) 111 && optional) { 112 if (addSep) { 113 menu.addSeparator(); 114 addSep = false; 115 } 116 Action act = actionFromNode(child, wi, context); 117 menu.add(menuItem = new JMenuItem(act)); 118 } 119 } 120 } else { 121 if (addSep) { 122 menu.addSeparator(); 123 addSep = false; 124 } 125 if (child.getChild("group") != null && child.getChild("group").getText().equals("yes")) { 126 //A seperate method is required for creating radio button groups 127 menu.add(createMenuGroupFromElement(child, wi, context)); 128 } else { 129 menu.add(menuItem = jMenuFromElement(child, wi, context)); // not leaf 130 } 131 } 132 if (menuItem != null && child.getChild("current") != null) { 133 setMenuItemInterAction(context, child.getChild("current").getText(), menuItem); 134 } 135 if (menuItem != null && child.getChild("mnemonic") != null) { 136 int mnemonic = convertStringToKeyEvent(child.getChild("mnemonic").getText()); 137 if (mnemonicList.contains(mnemonic)) { 138 log.error("Menu Item '{}' Mnemonic '{}' has already been assigned", menuItem.getText(), child.getChild("mnemonic").getText()); 139 } else { 140 menuItem.setMnemonic(mnemonic); 141 mnemonicList.add(mnemonic); 142 } 143 } 144 } 145 return menu; 146 } 147 148 static @Nonnull 149 JMenu createMenuGroupFromElement(@CheckForNull Element main, WindowInterface wi, Object context) { 150 String name = "<none>"; 151 if (main == null) { 152 log.warn("Menu from element called without an element"); 153 return new JMenu(name); 154 } 155 name = LocaleSelector.getAttribute(main, "name"); 156 //Next statement left in if the xml file hasn't been converted 157 if ((name == null) || (name.equals(""))) { 158 if (main.getChild("name") != null) { 159 name = main.getChild("name").getText(); 160 } 161 } 162 JMenu menu = new JMenu(name); 163 ButtonGroup group = new ButtonGroup(); 164 for (Object item : main.getChildren("node")) { 165 Element elem = (Element) item; 166 Action act = actionFromNode(elem, wi, context); 167 JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(act); 168 group.add(menuItem); 169 menu.add(menuItem); 170 if (elem.getChild("current") != null) { 171 setMenuItemInterAction(context, elem.getChild("current").getText(), menuItem); 172 } 173 } 174 175 return menu; 176 } 177 178 static void setMenuItemInterAction(@Nonnull Object context, final String ref, final JMenuItem menuItem) { 179 java.lang.reflect.Method methodListener = null; 180 try { 181 methodListener = context.getClass().getMethod("addPropertyChangeListener", java.beans.PropertyChangeListener.class); 182 } catch (java.lang.NullPointerException e) { 183 log.error("Null object passed"); 184 return; 185 } catch (SecurityException e) { 186 log.error("security exception unable to find remoteCalls for {}", context.getClass().getName()); 187 return; 188 } catch (NoSuchMethodException e) { 189 log.error("No such method remoteCalls for {}", context.getClass().getName()); 190 return; 191 } 192 193 try { 194 methodListener.invoke(context, new PropertyChangeListener() { 195 @Override 196 public void propertyChange(java.beans.PropertyChangeEvent e) { 197 if (e.getPropertyName().equals(ref)) { 198 String method = (String) e.getOldValue(); 199 if (method.equals("setSelected")) { 200 menuItem.setSelected(true); 201 } else if (method.equals("setEnabled")) { 202 menuItem.setEnabled((Boolean) e.getNewValue()); 203 } 204 } 205 } 206 }); 207 } catch (IllegalArgumentException ex) { 208 log.error("IllegalArgument in setMenuItemInterAction ", ex); 209 } catch (IllegalAccessException ex) { 210 log.error("IllegalAccess in setMenuItemInterAction ", ex); 211 } catch (java.lang.reflect.InvocationTargetException ex) { 212 log.error("InvocationTarget {} in setMenuItemInterAction ", ref, ex); 213 } catch (java.lang.NullPointerException ex) { 214 log.error("NPE {} in setMenuItemInterAction ", context.getClass().getName(), ex); 215 } 216 217 } 218 219 static int convertStringToKeyEvent(@Nonnull String st) { 220 char a = (st.toLowerCase()).charAt(0); 221 int kcode = a - 32; 222 return kcode; 223 } 224 225 /** 226 * replace a menu item in its parent with another menu item 227 * <p> 228 * (at the same position in the parent menu) 229 * 230 * @param orginalMenuItem the original menu item to be replaced 231 * @param replacementMenuItem the menu item to replace it with 232 * @return true if the original menu item was found and replaced 233 */ 234 public static boolean replaceMenuItem( 235 @Nonnull JMenuItem orginalMenuItem, 236 @Nonnull JMenuItem replacementMenuItem) { 237 boolean result = false; // assume failure (pessimist!) 238 Container container = orginalMenuItem.getParent(); 239 if (container != null) { 240 int index = container.getComponentZOrder(orginalMenuItem); 241 if (index > -1) { 242 container.remove(orginalMenuItem); 243 container.add(replacementMenuItem, index); 244 result = true; 245 } 246 } 247 return result; 248 } 249 250 private final static Logger log = LoggerFactory.getLogger(JMenuUtil.class); 251} // class JMenuUtil