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}