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}