001package apps; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.Color; 006import java.awt.Dimension; 007import java.awt.FlowLayout; 008import java.beans.PropertyChangeEvent; 009import java.beans.PropertyChangeListener; 010import java.io.File; 011import java.net.URI; 012import java.util.Locale; 013 014import javax.swing.Box; 015import javax.swing.BoxLayout; 016import javax.swing.ImageIcon; 017import javax.swing.JComponent; 018import javax.swing.JLabel; 019import javax.swing.JPanel; 020 021import jmri.InstanceManager; 022import jmri.jmrit.jython.Jynstrument; 023import jmri.jmrit.jython.JynstrumentFactory; 024import jmri.jmrix.ConnectionConfig; 025import jmri.jmrix.ConnectionConfigManager; 026import jmri.jmrix.ConnectionStatus; 027import jmri.jmrix.JmrixConfigPane; 028import jmri.util.FileUtil; 029import jmri.util.iharder.dnd.URIDrop; 030 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034/** 035 * Base class for pane filling main frame (window) of traditional-style JMRI 036 * applications 037 * <p> 038 * This is for launching after the system is initialized, so it does none of 039 * that. 040 * 041 * @author Bob Jacobsen Copyright 2003, 2007, 2008, 2010, 2014 042 * @author Dennis Miller Copyright 2005 043 * @author Giorgio Terdina Copyright 2008 044 * @author Matthew Harris Copyright (C) 2011 045 */ 046public abstract class AppsLaunchPane extends JPanel implements PropertyChangeListener { 047 048 static String profileFilename; 049 050 public AppsLaunchPane() { 051 052 super(true); 053 054 setButtonSpace(); 055 setJynstrumentSpace(); 056 057 jmri.Application.setLogo(logo()); 058 jmri.Application.setURL(line2()); 059 060 // populate GUI 061 log.debug("Start UI"); 062 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 063 064 add(statusPanel()); 065 log.debug("Done with statusPanel, start buttonSpace"); 066 add(buttonSpace()); 067 add(_jynstrumentSpace); 068 069 } 070 071 /** 072 * Prepare the JPanel to contain buttons in the startup GUI. Since it's 073 * possible to add buttons via the preferences, this space may have 074 * additional buttons appended to it later. The default implementation here 075 * just creates an empty space for these to be added to. 076 */ 077 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 078 justification = "only one application at a time") 079 protected void setButtonSpace() { 080 _buttonSpace = new JPanel(); 081 _buttonSpace.setLayout(new FlowLayout()); 082 } 083 static JComponent _jynstrumentSpace = null; 084 085 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 086 justification = "only one application at a time") 087 protected void setJynstrumentSpace() { 088 _jynstrumentSpace = new JPanel(); 089 _jynstrumentSpace.setLayout(new FlowLayout()); 090 new URIDrop(_jynstrumentSpace, (URI[] uris) -> { 091 for (URI uri : uris ) { 092 ynstrument(new File(uri).getPath()); 093 } 094 }); 095 } 096 097 public static void ynstrument(String path) { 098 Jynstrument it = JynstrumentFactory.createInstrument(path, _jynstrumentSpace); 099 if (it == null) { 100 log.error("Error while creating Jynstrument {}", path); 101 return; 102 } 103 jmri.jmrit.throttle.ThrottleFrame.setTransparent(it); 104 it.setVisible(true); 105 _jynstrumentSpace.setVisible(true); 106 _jynstrumentSpace.add(it); 107 } 108 109 protected String line1() { 110 return Bundle.getMessage("DefaultVersionCredit", jmri.Version.name()); 111 } 112 113 protected String line2() { 114 return "http://jmri.org/"; 115 } 116 117 protected String line3() { 118 return " "; 119 } 120 // line 4 121 JLabel cs4 = new JLabel(); 122 123 protected void buildLine4(JPanel pane) { 124 if (connection[0] != null) { 125 buildLine(connection[0], cs4, pane); 126 } 127 } 128 // line 5 optional 129 JLabel cs5 = new JLabel(); 130 131 protected void buildLine5(JPanel pane) { 132 if (connection[1] != null) { 133 buildLine(connection[1], cs5, pane); 134 } 135 } 136 // line 6 optional 137 JLabel cs6 = new JLabel(); 138 139 protected void buildLine6(JPanel pane) { 140 if (connection[2] != null) { 141 buildLine(connection[2], cs6, pane); 142 } 143 } 144 // line 7 optional 145 JLabel cs7 = new JLabel(); 146 147 protected void buildLine7(JPanel pane) { 148 if (connection[3] != null) { 149 buildLine(connection[3], cs7, pane); 150 } 151 } 152 153 protected void buildLine(ConnectionConfig conn, JLabel cs, JPanel pane) { 154 if (conn.name().equals(JmrixConfigPane.NONE)) { 155 cs.setText(" "); 156 return; 157 } 158 ConnectionStatus.instance().addConnection(conn.name(), conn.getInfo()); 159 cs.setFont(pane.getFont()); 160 updateLine(conn, cs); 161 pane.add(cs); 162 } 163 164 protected void updateLine(ConnectionConfig conn, JLabel cs) { 165 if (conn.getDisabled()) { 166 return; 167 } 168 String name = conn.getConnectionName(); 169 if (name == null) { 170 name = conn.getManufacturer(); 171 } 172 if (ConnectionStatus.instance().isConnectionOk(null, conn.getInfo())) { 173 cs.setForeground(Color.black); 174 String cf = Bundle.getMessage("ConnectionSucceeded", name, conn.name(), conn.getInfo()); 175 cs.setText(cf); 176 } else { 177 cs.setForeground(Color.red); 178 String cf = Bundle.getMessage("ConnectionFailed", name, conn.name(), conn.getInfo()); 179 cs.setText(cf); 180 } 181 182 this.revalidate(); 183 } 184 185 protected String line8() { 186 return " "; 187 } 188 189 protected String line9() { 190 return Bundle.getMessage("JavaVersionCredit", 191 System.getProperty("java.version", "<unknown>"), 192 Locale.getDefault().toString()); 193 } 194 195 protected String logo() { 196 return "resources/logo.gif"; 197 } 198 199 /** 200 * Fill in the logo and status panel 201 * 202 * @return Properly-filled out JPanel 203 */ 204 protected JPanel statusPanel() { 205 JPanel pane1 = new JPanel(); 206 pane1.setLayout(new BoxLayout(pane1, BoxLayout.X_AXIS)); 207 log.debug("Fetch main logo: {}", logo()); 208 pane1.add(new JLabel(new ImageIcon(getToolkit().getImage(FileUtil.findURL(logo(), FileUtil.Location.INSTALLED)), "JMRI logo"), JLabel.LEFT)); 209 pane1.add(Box.createRigidArea(new Dimension(15, 0))); // Some spacing between logo and status panel 210 211 log.debug("start labels"); 212 JPanel pane2 = new JPanel(); 213 214 pane2.setLayout(new BoxLayout(pane2, BoxLayout.Y_AXIS)); 215 pane2.add(new JLabel(line1())); 216 pane2.add(new JLabel(line2())); 217 pane2.add(new JLabel(line3())); 218 219 // add listerner for Com port updates 220 ConnectionStatus.instance().addPropertyChangeListener(this); 221 int i = 0; 222 for (ConnectionConfig conn : InstanceManager.getDefault(ConnectionConfigManager.class)) { 223 if (!conn.getDisabled()) { 224 connection[i] = conn; 225 i++; 226 } 227 if (i > 3) { 228 break; 229 } 230 } 231 buildLine4(pane2); 232 buildLine5(pane2); 233 buildLine6(pane2); 234 buildLine7(pane2); 235 236 pane2.add(new JLabel(line8())); 237 pane2.add(new JLabel(line9())); 238 pane1.add(pane2); 239 return pane1; 240 } 241 //int[] connection = {-1,-1,-1,-1}; 242 ConnectionConfig[] connection = {null, null, null, null}; 243 244 static protected void setJmriSystemProperty(String key, String value) { 245 try { 246 String current = System.getProperty("org.jmri.Apps." + key); 247 if (current == null) { 248 System.setProperty("org.jmri.Apps." + key, value); 249 } else if (!current.equals(value)) { 250 log.warn("JMRI property {} already set to {}, skipping reset to {}", key, current, value); 251 } 252 } catch (Exception e) { 253 log.error("Unable to set JMRI property {} to {} due to exception", key, value, e); 254 } 255 } 256 257 /** 258 * Provide access to a place where applications can expect the configuration 259 * code to build run-time buttons. 260 * 261 * @see apps.startup.CreateButtonModelFactory 262 * @return null if no such space exists 263 */ 264 static public JComponent buttonSpace() { 265 return _buttonSpace; 266 } 267 static JComponent _buttonSpace = null; 268 269 /** 270 * Set up the configuration file name at startup. 271 * <p> 272 * The Configuration File name variable holds the name used to load the 273 * configuration file during later startup processing. Applications invoke 274 * this method to handle the usual startup hierarchy: 275 * <ul> 276 * <li>If an absolute filename was provided on the command line, use it 277 * <li>If a filename was provided that's not absolute, consider it to be in 278 * the preferences directory 279 * <li>If no filename provided, use a default name (that's application 280 * specific) 281 * </ul> 282 * This name will be used for reading and writing the preferences. It need 283 * not exist when the program first starts up. This name may be proceeded 284 * with <em>config=</em> and may not contain the equals sign (=). 285 * 286 * @param def Default value if no other is provided 287 * @param args Argument array from the main routine 288 */ 289 static protected void setConfigFilename(String def, String[] args) { 290 // if the property org.jmri.Apps.configFilename was set, skip 291 if (System.getProperty("org.jmri.Apps.configFilename") != null) { 292 return; 293 } 294 // save the configuration filename if present on the command line 295 if (args.length >= 1 && args[0] != null && !args[0].contains("=")) { 296 def = args[0]; 297 log.debug("Config file was specified as: {}", args[0]); 298 } 299 for (String arg : args) { 300 String[] split = arg.split("=", 2); 301 if (split[0].equalsIgnoreCase("config")) { 302 def = split[1]; 303 log.debug("Config file was specified as: {}", arg); 304 } 305 } 306 Apps.configFilename = def; 307 setJmriSystemProperty("configFilename", def); 308 } 309 310 static public String getConfigFileName() { 311 log.debug("getConfigFileName() called, shouldn't have been", new Exception("bad call traceback")); 312 return null; 313 // was hopefully set by setJmriSystemProperty("configFilename", def) earlier, recover 314 } 315 316 @Override 317 public void propertyChange(PropertyChangeEvent ev) { 318 log.debug("property change: comm port status update"); 319 if (connection[0] != null) { 320 updateLine(connection[0], cs4); 321 } 322 323 if (connection[1] != null) { 324 updateLine(connection[1], cs5); 325 } 326 327 if (connection[2] != null) { 328 updateLine(connection[2], cs6); 329 } 330 331 if (connection[3] != null) { 332 updateLine(connection[3], cs7); 333 } 334 335 } 336 337 /** 338 * Returns the ID for the window's help, which is application specific 339 * 340 * @return the Java Help reference or null if no help is available 341 */ 342 protected abstract String windowHelpID(); 343 344 private final static Logger log = LoggerFactory.getLogger(AppsLaunchPane.class); 345}