001package jmri.util.swing;
002
003import java.awt.event.ActionEvent;
004import java.io.IOException;
005import java.lang.reflect.Constructor;
006import java.lang.reflect.InvocationTargetException;
007import java.lang.reflect.Method;
008import java.util.HashMap;
009import javax.swing.AbstractAction;
010import javax.swing.Action;
011import javax.swing.Icon;
012import javax.swing.ImageIcon;
013import jmri.util.FileUtil;
014import jmri.util.jdom.LocaleSelector;
015import org.jdom2.Element;
016import org.jdom2.JDOMException;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Common utility methods for working with GUI items
022 *
023 * @author Bob Jacobsen Copyright 2010
024 */
025public class GuiUtilBase {
026
027    static Action actionFromNode(Element child, WindowInterface wi, Object context) {
028        String name;
029        Icon icon = null;
030
031        HashMap<String, String> parameters = new HashMap<>();
032        if (child == null) {
033            log.warn("Action from node called without child");
034            return createEmptyMenuItem(null, "<none>");
035        }
036        name = LocaleSelector.getAttribute(child, "name");
037        if ((name == null) || (name.isEmpty())) {
038            if (child.getChild("name") != null) {
039                name = child.getChild("name").getText();
040            }
041        }
042
043        Element childUrl = child.getChild("icon"); // NOI18N
044        if ( childUrl != null) {
045            java.net.URL iconUrl =  FileUtil.findURL(childUrl.getText());
046            if (iconUrl!=null) {
047                icon = new ImageIcon(iconUrl);
048            }
049            else {
050                log.warn("Unable to locate icon :{}:",childUrl.getText());
051            }
052        }
053        //This bit does not size very well, but it works for now.
054        if (child.getChild("option") != null) {
055            child.getChildren("option").stream().forEach((item) -> {
056                String setting = item.getAttribute("setting").getValue();
057                String setMethod = item.getText();
058                parameters.put(setMethod, setting);
059            });
060        }
061
062        if (child.getChild("adapter") != null) {
063            String classname = child.getChild("adapter").getText();
064            JmriAbstractAction a;
065            try {
066                Class<?> c = Class.forName(classname);
067                for (Constructor<?> ct : c.getConstructors()) {
068                    // look for one with right arguments
069                    if (icon == null) {
070                        Class<?>[] parms = ct.getParameterTypes();
071                        if (parms.length != 2) {
072                            continue;
073                        }
074                        if (parms[0] != String.class) {
075                            continue;
076                        }
077                        if (parms[1] != WindowInterface.class) {
078                            continue;
079                        }
080                        // found it!
081                        a = (JmriAbstractAction) ct.newInstance(new Object[]{name, wi});
082                        a.setName(name);
083                        a.setContext(context);
084                        setParameters(a, parameters);
085                        return a;
086                    } else {
087                        Class<?>[] parms = ct.getParameterTypes();
088                        if (parms.length != 3) {
089                            continue;
090                        }
091                        if (parms[0] != String.class) {
092                            continue;
093                        }
094                        if (parms[1] != Icon.class) {
095                            continue;
096                        }
097                        if (parms[2] != WindowInterface.class) {
098                            continue;
099                        }
100                        // found it!
101                        a = (JmriAbstractAction) ct.newInstance(new Object[]{name, icon, wi});
102                        a.setName(name);
103                        a.setContext(context);
104                        setParameters(a, parameters);
105                        return a;
106                    }
107                }
108                log.warn("Did not find suitable ctor for {}{} icon", classname, icon != null ? " with" : " without");
109                return createEmptyMenuItem(icon, name);
110            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
111                log.warn("failed to load GUI adapter class: {}", classname, e);
112                return createEmptyMenuItem(icon, name);
113            }
114        } else if (child.getChild("panel") != null) {
115            try {
116                JmriNamedPaneAction act;
117                if (icon == null) {
118                    act = new JmriNamedPaneAction(name, wi, child.getChild("panel").getText());
119                } else {
120                    act = new JmriNamedPaneAction(name, icon, wi, child.getChild("panel").getText());
121                }
122                act.setContext(context);
123                setParameters(act, parameters);
124                return act;
125            } catch (Exception ex) {
126                log.warn("could not load toolbar adapter class: {}", child.getChild("panel").getText(), ex);
127                return createEmptyMenuItem(icon, name);
128            }
129        } else if (child.getChild("help") != null) {
130            String reference = child.getChild("help").getText();
131            return jmri.util.HelpUtil.getHelpAction(name, icon, reference);
132        } else if (child.getChild("current") != null) {
133            String method[] = {child.getChild("current").getText()};
134            return createActionInCallingWindow(context, method, name, icon);
135            //Relates to the instance that has called it
136        } else { // make from icon or text without associated function
137            return createEmptyMenuItem(icon, name);
138        }
139    }
140
141    /**
142     * Create an action against the object that invoked the creation of the
143     * GUIBase, a string array is used so that in the future further options can
144     * be specified to be passed.
145     *
146     * @param obj  the object to create an action for
147     * @param args arguments to passed remoteCalls method of obj
148     * @param name name of the action
149     * @param icon icon for the action
150     * @return the action for obj or an empty action with name and icon
151     */
152    static Action createActionInCallingWindow(Object obj, final String args[], String name, Icon icon) {
153        Method method = null;
154        try {
155            method = obj.getClass().getDeclaredMethod("remoteCalls", String[].class);
156        } catch (NullPointerException e) {
157            log.error("Null object passed");
158            return createEmptyMenuItem(icon, name);
159        } catch (SecurityException e) {
160            log.error("security exception unable to find remoteCalls for {}", obj.getClass().getName());
161            createEmptyMenuItem(icon, name);
162        } catch (NoSuchMethodException e) {
163            log.error("No such method remoteCalls for {}", obj.getClass().getName());
164            return createEmptyMenuItem(icon, name);
165        }
166
167        CallingAbstractAction act = new CallingAbstractAction(name, icon);
168
169        act.setMethod(method);
170        act.setArgs(args);
171        act.setObject(obj);
172        act.setEnabled(true);
173        return act;
174    }
175
176    static class CallingAbstractAction extends javax.swing.AbstractAction {
177
178        public CallingAbstractAction(String name, Icon icon) {
179            super(name, icon);
180        }
181
182        Method method = null;
183        Object obj;
184        Object args;
185
186        public void setArgs(Object args[]) {
187            //args = stringArgs.getClass();
188            this.args = args;
189        }
190
191        public void setMethod(Method method) {
192            this.method = method;
193        }
194
195        public void setObject(Object obj) {
196            this.obj = obj;
197        }
198
199        @Override
200        public void actionPerformed(java.awt.event.ActionEvent e) {
201            try {
202                method.invoke(obj, args);
203            } catch (NullPointerException | IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) {
204                log.error("Exception in actionPerformed", ex);
205            }
206        }
207    }
208
209    static Action createEmptyMenuItem(Icon icon, String name) {
210        if (icon != null) {
211            AbstractAction act = new AbstractAction(name, icon) {
212                @Override
213                public void actionPerformed(ActionEvent e) {
214                }
215
216                @Override
217                public String toString() {
218                    return (String) getValue(Action.NAME);
219                }
220            };
221            act.setEnabled(false);
222            return act;
223        } else { // then name must be present
224            AbstractAction act = new AbstractAction(name) {
225
226                @Override
227                public void actionPerformed(ActionEvent e) {
228                }
229
230                @Override
231                public String toString() {
232                    return (String) getValue(Action.NAME);
233                }
234            };
235            act.setEnabled(false);
236            return act;
237        }
238    }
239
240    static void setParameters(JmriAbstractAction act, HashMap<String, String> parameters) {
241        parameters.entrySet().stream().forEach((map) -> {
242            act.setParameter(map.getKey(), map.getValue());
243        });
244    }
245
246    /**
247     * Get root element from XML file, handling errors locally.
248     *
249     * @param name the name of the XML file
250     * @return the root element or null
251     */
252    static protected Element rootFromName(String name) {
253        try {
254            return new jmri.jmrit.XmlFile() {
255            }.rootFromName(name);
256        } catch (JDOMException | IOException e) {
257            log.error("Could not parse file \"{}\" due to", name, e);
258            return null;
259        }
260    }
261
262    private final static Logger log = LoggerFactory.getLogger(GuiUtilBase.class);
263}