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