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}