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