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