001package jmri.jmrit.speedometer;
002
003import java.awt.FlowLayout;
004import java.io.File;
005import java.io.FileNotFoundException;
006import java.io.IOException;
007import java.util.List;
008import javax.swing.BoxLayout;
009import javax.swing.JButton;
010import javax.swing.JLabel;
011import javax.swing.JOptionPane;
012import javax.swing.JPanel;
013import javax.swing.JTextField;
014import jmri.Application;
015import jmri.InstanceManager;
016import jmri.NamedBeanHandle;
017import jmri.Sensor;
018import jmri.SensorManager;
019import jmri.jmrit.XmlFile;
020import jmri.jmrit.catalog.NamedIcon;
021import jmri.jmrit.display.SensorIcon;
022import jmri.util.FileUtil;
023import jmri.util.IntlUtilities;
024import org.jdom2.Document;
025import org.jdom2.Element;
026import org.jdom2.JDOMException;
027import org.jdom2.ProcessingInstruction;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Frame providing access to a speedometer.
033 * <p>
034 * This contains very simple debouncing logic:
035 * <ul>
036 * <li>The clock starts when the "start" sensor makes the correct transition.
037 * <li>When a "stop" sensor makes the correct transition, the speed is computed
038 * and displayed.
039 * </ul>
040 *
041 * @author Bob Jacobsen Copyright (C) 2001, 2004, 2007
042 * @author Adapted for metric system - S.K. Bosch
043 * @author Matthew Harris Copyright (c) 2011
044 */
045public class SpeedometerFrame extends jmri.util.JmriJFrame {
046
047    final String blank = "       ";
048    JTextField startSensor = new JTextField(5);
049    javax.swing.ButtonGroup startGroup = new javax.swing.ButtonGroup();
050    javax.swing.JRadioButton startOnEntry = new javax.swing.JRadioButton(Bundle.getMessage("RadioButtonEntry"));
051    javax.swing.JRadioButton startOnExit = new javax.swing.JRadioButton(Bundle.getMessage("RadioButtonExit"));
052
053    JTextField stopSensor1 = new JTextField(5);
054    javax.swing.ButtonGroup stopGroup1 = new javax.swing.ButtonGroup();
055    javax.swing.JRadioButton stopOnEntry1 = new javax.swing.JRadioButton(Bundle.getMessage("RadioButtonEntry"));
056    javax.swing.JRadioButton stopOnExit1 = new javax.swing.JRadioButton(Bundle.getMessage("RadioButtonExit"));
057
058    public JTextField stopSensor2 = new JTextField(5);
059    javax.swing.ButtonGroup stopGroup2 = new javax.swing.ButtonGroup();
060    javax.swing.JRadioButton stopOnEntry2 = new javax.swing.JRadioButton(Bundle.getMessage("RadioButtonEntry"));
061    javax.swing.JRadioButton stopOnExit2 = new javax.swing.JRadioButton(Bundle.getMessage("RadioButtonExit"));
062
063    JTextField distance1 = new JTextField(5);
064    JTextField distance2 = new JTextField(5);
065
066    JButton dimButton = new JButton("");   // content will be set to English during startup
067    JButton startButton = new JButton(Bundle.getMessage("ButtonStart"));
068
069    JLabel text1 = new JLabel(Bundle.getMessage("Distance1English"));
070    JLabel text2 = new JLabel(Bundle.getMessage("Distance2English"));
071    JLabel text3 = new JLabel(Bundle.getMessage("Speed1English"));
072    JLabel text4 = new JLabel(Bundle.getMessage("Speed2English"));
073
074    JButton clearButton = new JButton(Bundle.getMessage("ButtonClear"));
075
076    JLabel result1 = new JLabel(blank);
077    JLabel time1 = new JLabel(blank);
078    JLabel result2 = new JLabel(blank);
079    JLabel time2 = new JLabel(blank);
080
081    JButton saveButton = new JButton(Bundle.getMessage("ButtonSave"));
082
083    SensorIcon startSensorIcon;
084    SensorIcon stopSensorIcon1;
085    SensorIcon stopSensorIcon2;
086
087    /**
088     * Set Input sensors.
089     * @param start start sensor name.
090     * @param stop1 stop sensor 1.
091     * @param stop2 stop sensor 2.
092     * @param d1 First timer distance in current units. Express with the decimal
093     *           marker in the current Locale.
094     * @param d2 Second timer distance in current units. Express with the
095     *           decimal marker in the current Locale.
096     */
097    public void setInputs(String start, String stop1, String stop2, String d1, String d2) {
098        startSensor.setText(start);
099        stopSensor1.setText(stop1);
100        stopSensor2.setText(stop2);
101        distance1.setText(d1);
102        distance2.setText(d2);
103    }
104
105    public final void setInputBehavior(boolean startOnEntry, boolean stopOnEntry1, boolean stopOnEntry2) {
106        this.startOnEntry.setSelected(startOnEntry);
107        this.startOnExit.setSelected(!startOnEntry);
108        this.stopOnEntry1.setSelected(stopOnEntry1);
109        this.stopOnExit1.setSelected(!stopOnEntry1);
110        this.stopOnEntry2.setSelected(stopOnEntry2);
111        this.stopOnExit2.setSelected(!stopOnEntry2);
112    }
113
114    public final void setUnitsMetric(boolean metric) {
115        if (dim != metric) {
116            dim();
117        }
118    }
119
120    public SpeedometerFrame() {
121        super(false, false);
122
123        setInputBehavior(true, true, true);
124
125        startGroup.add(startOnEntry);
126        startGroup.add(startOnExit);
127        stopGroup1.add(stopOnEntry1);
128        stopGroup1.add(stopOnExit1);
129        stopGroup2.add(stopOnEntry2);
130        stopGroup2.add(stopOnExit2);
131
132        // general GUI config
133        setTitle(Bundle.getMessage("TitleSpeedometer"));
134        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
135
136        // need a captive panel editor for
137        // the sensor icons to work
138        jmri.jmrit.display.panelEditor.PanelEditor editor = new jmri.jmrit.display.panelEditor.PanelEditor();
139        editor.makePrivateWindow();
140        editor.setVisible(false);
141
142        // add items to GUI
143        JPanel pane1 = new JPanel();
144        pane1.setLayout(new FlowLayout());
145        pane1.add(new JLabel(Bundle.getMessage("LabelSensor")));
146        startSensor.setToolTipText(Bundle.getMessage("TooltipStartSensor"));
147        pane1.add(startSensor);
148        JLabel startSensorLabel = new JLabel(Bundle.getMessage("LabelStartSensor"));
149        startSensorLabel.setLabelFor(startSensor);
150        pane1.add(startSensorLabel);
151        pane1.add(startOnEntry);
152        pane1.add(startOnExit);
153        startSensorIcon = new SensorIcon(editor);
154        setupIconMap(startSensorIcon);
155        startSensorIcon.setToolTipText(Bundle.getMessage("TooltipStartSensorIcon"));
156        pane1.add(startSensorIcon);
157        getContentPane().add(pane1);
158
159        JPanel pane2 = new JPanel();
160        pane2.setLayout(new FlowLayout());
161        pane2.add(new JLabel(Bundle.getMessage("LabelSensor")));
162        stopSensor1.setToolTipText(Bundle.getMessage("TooltipStopSensor1"));
163        pane2.add(stopSensor1);
164        JLabel stopSensor1Label = new JLabel(Bundle.getMessage("LabelStopSensor1"));
165        stopSensor1Label.setLabelFor(stopSensor1);
166        pane2.add(stopSensor1Label);
167        pane2.add(stopOnEntry1);
168        pane2.add(stopOnExit1);
169        stopSensorIcon1 = new SensorIcon(editor);
170        setupIconMap(stopSensorIcon1);
171        stopSensorIcon1.setToolTipText(Bundle.getMessage("TooltipStartSensorIcon"));
172        pane2.add(stopSensorIcon1);
173        getContentPane().add(pane2);
174
175        JPanel pane3 = new JPanel();
176        pane3.setLayout(new FlowLayout());
177        pane3.add(new JLabel(Bundle.getMessage("LabelSensor")));
178        stopSensor2.setToolTipText(Bundle.getMessage("TooltipStopSensor2"));
179        pane3.add(stopSensor2);
180        JLabel stopSensor2Label = new JLabel(Bundle.getMessage("LabelStopSensor2"));
181        stopSensor2Label.setLabelFor(stopSensor2);
182        pane3.add(stopSensor2Label);
183        pane3.add(stopOnEntry2);
184        pane3.add(stopOnExit2);
185        stopSensorIcon2 = new SensorIcon(editor);
186        setupIconMap(stopSensorIcon2);
187        stopSensorIcon2.setToolTipText(Bundle.getMessage("TooltipStartSensorIcon"));
188        pane3.add(stopSensorIcon2);
189        getContentPane().add(pane3);
190
191        JPanel pane4 = new JPanel();
192        pane4.setLayout(new FlowLayout());
193        pane4.add(text1);
194        text1.setLabelFor(distance1);
195        pane4.add(distance1);
196        getContentPane().add(pane4);
197
198        JPanel pane5 = new JPanel();
199        pane5.setLayout(new FlowLayout());
200        pane5.add(text2);
201        text2.setLabelFor(distance2);
202        pane5.add(distance2);
203        getContentPane().add(pane5);
204
205        JPanel buttons = new JPanel();
206        buttons.add(dimButton);
207        dimButton.setToolTipText(Bundle.getMessage("TooltipSwitchUnits"));
208        buttons.add(startButton);
209        buttons.add(clearButton);
210        buttons.add(saveButton);
211        getContentPane().add(buttons);
212
213        clearButton.setVisible(false);
214
215        // see if there's a sensor manager, if not disable
216        if (null == InstanceManager.getNullableDefault(SensorManager.class)) {
217            startButton.setEnabled(false);
218            startButton.setToolTipText(Bundle.getMessage("TooltipSensorsNotSupported"));
219        }
220
221        JPanel pane6 = new JPanel();
222        pane6.setLayout(new FlowLayout());
223        pane6.add(text3);
224        pane6.add(result1);
225        text3.setLabelFor(result1);
226        JLabel time1Label = new JLabel(Bundle.getMessage("LabelTime"));
227        pane6.add(time1Label);
228        pane6.add(time1);
229        time1Label.setLabelFor(time1);
230        getContentPane().add(pane6);
231
232        JPanel pane7 = new JPanel();
233        pane7.setLayout(new FlowLayout());
234        pane7.add(text4);
235        pane7.add(result2);
236        text4.setLabelFor(result2);
237        JLabel time2Label = new JLabel(Bundle.getMessage("LabelTime"));
238        pane7.add(time2Label);
239        pane7.add(time2);
240        time2Label.setLabelFor(time2);
241        getContentPane().add(pane7);
242
243        // set the units consistently
244        dim();
245
246        // add the actions to the config button
247        dimButton.addActionListener(new java.awt.event.ActionListener() {
248            @Override
249            public void actionPerformed(java.awt.event.ActionEvent e) {
250                dim();
251            }
252        });
253
254        startButton.addActionListener(new java.awt.event.ActionListener() {
255            @Override
256            public void actionPerformed(java.awt.event.ActionEvent e) {
257                setup();
258            }
259        });
260
261        clearButton.addActionListener(new java.awt.event.ActionListener() {
262            @Override
263            public void actionPerformed(java.awt.event.ActionEvent e) {
264                time1.setText(blank);
265                time2.setText(blank);
266                result1.setText(blank);
267                result2.setText(blank);
268            }
269        });
270
271        saveButton.addActionListener(new java.awt.event.ActionListener() {
272            @Override
273            public void actionPerformed(java.awt.event.ActionEvent e) {
274                doStore();
275            }
276        });
277
278        // start displaying the sensor status when the number is entered
279        startSensor.addActionListener(new java.awt.event.ActionListener() {
280            @Override
281            public void actionPerformed(java.awt.event.ActionEvent e) {
282                startSensorIcon.setSensor(startSensor.getText());
283            }
284        });
285        stopSensor1.addActionListener(new java.awt.event.ActionListener() {
286            @Override
287            public void actionPerformed(java.awt.event.ActionEvent e) {
288                stopSensorIcon1.setSensor(stopSensor1.getText());
289            }
290        });
291
292        stopSensor2.addActionListener(new java.awt.event.ActionListener() {
293            @Override
294            public void actionPerformed(java.awt.event.ActionEvent e) {
295                stopSensorIcon2.setSensor(stopSensor2.getText());
296            }
297        });
298
299        // add help menu to window
300        addHelpMenu("package.jmri.jmrit.speedometer.SpeedometerFrame", true);
301
302        // and get ready to display
303        pack();
304
305        // finally, load any previously saved defaults
306        doLoad();
307    }
308
309    long startTime = 0;
310    long stopTime1 = 0;
311    long stopTime2 = 0;
312
313    /**
314     * "Distance Is Metric": If true, metric distances are being used.
315     */
316    boolean dim;
317
318    // establish whether English or Metric representation is wanted
319    final void dim() {
320        dimButton.setEnabled(true);
321        if (dimButton.getText().equals(Bundle.getMessage("ButtonToMetric"))) {
322            dimButton.setText(Bundle.getMessage("ButtonToEnglish"));
323            dim = true;
324            text1.setText(Bundle.getMessage("Distance1Metric"));
325            text2.setText(Bundle.getMessage("Distance2Metric"));
326            text3.setText(Bundle.getMessage("Speed1Metric"));
327            text4.setText(Bundle.getMessage("Speed2Metric"));
328        } else {
329            dimButton.setText(Bundle.getMessage("ButtonToMetric"));
330            dim = false;
331            text1.setText(Bundle.getMessage("Distance1English"));
332            text2.setText(Bundle.getMessage("Distance2English"));
333            text3.setText(Bundle.getMessage("Speed1English"));
334            text4.setText(Bundle.getMessage("Speed2English"));
335        }
336    }
337
338    public void setup() {
339        //startButton.setToolTipText("You can only configure this once");
340
341        // Check inputs are valid and get the number of valid stop sensors
342        int valid = verifyInputs(true);
343        if (log.isDebugEnabled()) {
344            log.debug("Number of valid stop sensors: {}", valid);
345        }
346        enableConfiguration(valid == 0);
347        if (valid == 0) {
348            return;
349        }
350
351        // set start sensor
352        Sensor s;
353        s = InstanceManager.sensorManagerInstance().
354                provideSensor(startSensor.getText());
355        s.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
356            @Override
357            public void propertyChange(java.beans.PropertyChangeEvent e) {
358                SpeedometerFrame.log.debug("start sensor fired");
359                if (e.getPropertyName().equals("KnownState")) {
360                    int now = ((Integer) e.getNewValue()).intValue();
361                    if ((now == Sensor.ACTIVE && startOnEntry.isSelected())
362                            || (now == Sensor.INACTIVE && startOnExit.isSelected())) {
363                        startTime = System.currentTimeMillis();  // milliseconds
364                        if (log.isDebugEnabled()) {
365                            log.debug("set start {}", startTime);
366                        }
367                    }
368                }
369            }
370        });
371        startSensorIcon.setSensor(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(startSensor.getText(), s));
372
373        // set stop sensor1
374        s = InstanceManager.sensorManagerInstance().
375                provideSensor(stopSensor1.getText());
376        s.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
377            @Override
378            public void propertyChange(java.beans.PropertyChangeEvent e) {
379                SpeedometerFrame.log.debug("stop sensor fired");
380                if (e.getPropertyName().equals("KnownState")) {
381                    int now = ((Integer) e.getNewValue()).intValue();
382                    if ((now == Sensor.ACTIVE && stopOnEntry1.isSelected())
383                            || (now == Sensor.INACTIVE && stopOnExit1.isSelected())) {
384                        stopTime1 = System.currentTimeMillis();  // milliseconds
385                        if (log.isDebugEnabled()) {
386                            log.debug("set stop {}", stopTime1);
387                        }
388                        // calculate and show speed
389                        float secs = (stopTime1 - startTime) / 1000.f;
390                        float feet = 0.0f;
391                        try {
392                            feet = IntlUtilities.floatValue(distance1.getText());
393                        } catch (java.text.ParseException ex) {
394                            log.error("invalid floating point number as input: {}", distance1.getText());
395                        }
396                        float speed;
397                        if (dim == false) {
398                            speed = (feet / 5280.f) * (3600.f / secs);
399                        } else {
400                            speed = (feet / 100000.f) * (3600.f / secs);
401                        }
402                        if (log.isDebugEnabled()) {
403                            log.debug("calc from {},{}:{}", secs, feet, speed);
404                        }
405                        result1.setText(String.valueOf(speed).substring(0, 4));
406                        String time = String.valueOf(secs);
407                        int offset = time.indexOf(".");
408                        if (offset == -1) {
409                            offset = time.length();
410                        }
411                        offset = offset + 2;  // the decimal point, plus tenths digit
412                        if (offset > time.length()) {
413                            offset = time.length();
414                        }
415                        time1.setText(time.substring(0, offset));
416                    }
417                }
418            }
419        });
420        stopSensorIcon1.setSensor(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(stopSensor1.getText(), s));
421
422        if (valid == 1) {
423            return;
424        }
425
426        // set stop sensor2
427        s = InstanceManager.sensorManagerInstance().
428                provideSensor(stopSensor2.getText());
429        s.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
430            // handle change in stop sensor
431            @Override
432            public void propertyChange(java.beans.PropertyChangeEvent e) {
433                SpeedometerFrame.log.debug("stop sensor fired");
434                if (e.getPropertyName().equals("KnownState")) {
435                    int now = ((Integer) e.getNewValue()).intValue();
436                    if ((now == Sensor.ACTIVE && stopOnEntry2.isSelected())
437                            || (now == Sensor.INACTIVE && stopOnExit2.isSelected())) {
438                        stopTime2 = System.currentTimeMillis();  // milliseconds
439                        if (log.isDebugEnabled()) {
440                            log.debug("set stop {}", stopTime2);
441                        }
442                        // calculate and show speed
443                        float secs = (stopTime2 - startTime) / 1000.f;
444                        float feet = 0.0f;
445                        try {
446                            feet = IntlUtilities.floatValue(distance2.getText());
447                        } catch (java.text.ParseException ex) {
448                            log.error("invalid floating point number as input: {}", distance2.getText());
449                        }
450                        float speed;
451                        if (dim == false) {
452                            speed = (feet / 5280.f) * (3600.f / secs);
453                        } else {
454                            speed = (feet / 100000.f) * (3600.f / secs);
455                        }
456                        if (log.isDebugEnabled()) {
457                            log.debug("calc from {},{}:{}", secs, feet, speed);
458                        }
459                        result2.setText(String.valueOf(speed).substring(0, 4));
460                        String time = String.valueOf(secs);
461                        int offset = time.indexOf(".");
462                        if (offset == -1) {
463                            offset = time.length();
464                        }
465                        offset = offset + 2;  // the decimal point, plus tenths digit
466                        if (offset > time.length()) {
467                            offset = time.length();
468                        }
469                        time2.setText(time.substring(0, offset));
470                    }
471                }
472            }
473        });
474        NamedBeanHandle<Sensor> namedSensor2 = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(stopSensor2.getText(), s);
475        stopSensorIcon2.setSensor(namedSensor2);
476    }
477
478    private void enableConfiguration(boolean enable) {
479        // Buttons first
480        startButton.setEnabled(enable);
481        startButton.setVisible(enable);
482        clearButton.setEnabled(!enable);
483        clearButton.setVisible(!enable);
484        saveButton.setEnabled(enable);
485
486        // Now Start sensor
487        startSensor.setEnabled(enable);
488        startOnEntry.setEnabled(enable);
489        startOnExit.setEnabled(enable);
490
491        // Now Stop sensor 1
492        stopSensor1.setEnabled(enable);
493        stopOnEntry1.setEnabled(enable);
494        stopOnExit1.setEnabled(enable);
495
496        // Now Stop sensor 2
497        stopSensor2.setEnabled(enable);
498        stopOnEntry2.setEnabled(enable);
499        stopOnExit2.setEnabled(enable);
500
501        // Finally, distances
502        distance1.setEnabled(enable);
503        distance2.setEnabled(enable);
504        dimButton.setEnabled(enable);
505    }
506
507    /**
508     * Verifies if correct inputs have been made and returns the number of valid
509     * stop sensors.
510     *
511     * @param warn true if warning messages to be displayed
512     * @return 0 if not verified; otherwise the number of valid stop sensors
513     *         defined
514     */
515    private int verifyInputs(boolean warn) {
516
517        // Initially, no stop sensors are valid
518        int verify = 0;
519
520        Sensor s;
521
522        // Check the start sensor
523        try {
524            s = InstanceManager.sensorManagerInstance().
525                    provideSensor(startSensor.getText());
526            if (s == null) {
527                throw new Exception();
528            }
529        } catch (Exception e) {
530            // couldn't locate the sensor, that's an error
531            log.error("Start sensor invalid: {}", startSensor.getText());
532            if (warn) {
533                JOptionPane.showMessageDialog(
534                        this,
535                        Bundle.getMessage("ErrorStartSensor"),
536                        Bundle.getMessage("TitleError"),
537                        JOptionPane.WARNING_MESSAGE);
538            }
539            return verify;
540        }
541
542        // Check stop sensor 1
543        try {
544            s = InstanceManager.sensorManagerInstance().
545                    provideSensor(stopSensor1.getText());
546            if (s == null) {
547                throw new Exception();
548            }
549        } catch (Exception e) {
550            // couldn't locate the sensor, that's an error
551            log.error("Stop 1 sensor invalid : {}", stopSensor1.getText());
552            if (warn) {
553                JOptionPane.showMessageDialog(
554                        this,
555                        Bundle.getMessage("ErrorStopSensor1"),
556                        Bundle.getMessage("TitleError"),
557                        JOptionPane.WARNING_MESSAGE);
558            }
559            return verify;
560        }
561
562        // Check distance1 has been defined
563        if (distance1.getText().equals("")) {
564            log.error("Distance 1 has not been defined");
565            if (warn) {
566                JOptionPane.showMessageDialog(
567                        this,
568                        Bundle.getMessage("ErrorDistance1"),
569                        Bundle.getMessage("TitleError"),
570                        JOptionPane.WARNING_MESSAGE);
571            }
572            return verify;
573        }
574
575        // We've got this far, so at least start and one stop sensor is valid
576        verify = 1;
577
578        // Check stop sensor2 if either sensor 2 and/or distance 2 defined
579        if (!stopSensor2.getText().equals("") || !distance2.getText().equals("")) {
580            try {
581                s = InstanceManager.sensorManagerInstance().
582                        provideSensor(stopSensor2.getText());
583                if (s == null) {
584                    throw new Exception();
585                }
586            } catch (Exception e) {
587                // couldn't locate the sensor, that's an error
588                log.error("Stop 2 sensor invalid: {}", stopSensor2.getText());
589                if (warn) {
590                    JOptionPane.showMessageDialog(
591                            this,
592                            Bundle.getMessage("ErrorStopSensor2"),
593                            Bundle.getMessage("TitleError"),
594                            JOptionPane.WARNING_MESSAGE);
595                }
596                return 0;
597            }
598
599            // Check distance2 has been defined
600            if (distance2.getText().equals("")) {
601                log.error("Distance 2 has not been defined");
602                enableConfiguration(true);
603                if (warn) {
604                    JOptionPane.showMessageDialog(
605                            this,
606                            Bundle.getMessage("ErrorDistance2"),
607                            Bundle.getMessage("TitleError"),
608                            JOptionPane.WARNING_MESSAGE);
609                }
610                return 0;
611            }
612
613            // We've got this far, so stop sensor 2 is valid
614            verify = 2;
615        }
616        return verify;
617    }
618
619    private void doStore() {
620        log.debug("Check if there's anything to store");
621        int verify = verifyInputs(false);
622        if (verify == 0) {
623            if (JOptionPane.showConfirmDialog(
624                    this,
625                    Bundle.getMessage("QuestionNothingToStore"),
626                    Bundle.getMessage("TitleStoreQuestion"),
627                    JOptionPane.YES_NO_OPTION,
628                    JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) {
629                return;
630            }
631        }
632        log.debug("Start storing speedometer settings...");
633
634        SpeedometerXml x = new SpeedometerXml();
635
636        x.makeBackupFile(SpeedometerXml.getDefaultFileName());
637
638        File file = x.getFile(true);
639
640        // Create root element
641        Element root = new Element("speedometer-config");
642        root.setAttribute("noNamespaceSchemaLocation",
643                "http://jmri.org/xml/schema/speedometer-3-9-3.xsd",
644                org.jdom2.Namespace.getNamespace("xsi",
645                        "http://www.w3.org/2001/XMLSchema-instance"));
646        Document doc = new Document(root);
647
648        // add XSLT processing instruction
649        java.util.Map<String, String> m = new java.util.HashMap<String, String>();
650        m.put("type", "text/xsl");
651        m.put("href", SpeedometerXml.xsltLocation + "speedometer.xsl");
652        ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
653        doc.addContent(0, p);
654
655        Element values;
656
657        // Store configuration
658        root.addContent(values = new Element("configuration"));
659        values.addContent(new Element("useMetric").addContent(dim ? "yes" : "no"));
660
661        // Store values
662        if (verify > 0 || startSensor.getText().length() > 0) {
663            // Create sensors element
664            root.addContent(values = new Element("sensors"));
665
666            // Store start sensor
667            Element e = new Element("sensor");
668            e.addContent(new Element("sensorName").addContent(startSensor.getText()));
669            e.addContent(new Element("type").addContent("StartSensor"));
670            e.addContent(new Element("trigger").addContent(startOnEntry.isSelected() ? "entry" : "exit"));
671            values.addContent(e);
672
673            // If valid, store stop sensor 1
674            if (verify > 0) {
675                e = new Element("sensor");
676                e.addContent(new Element("sensorName").addContent(stopSensor1.getText()));
677                e.addContent(new Element("type").addContent("StopSensor1"));
678                e.addContent(new Element("trigger").addContent(stopOnEntry1.isSelected() ? "entry" : "exit"));
679                try {
680                    e.addContent(new Element("distance").addContent(String.valueOf(IntlUtilities.floatValue(distance1.getText()))));
681                } catch (java.text.ParseException ex) {
682                    log.error("Distance isn't a valid floating number: {}", distance1.getText());
683                }
684                values.addContent(e);
685            }
686
687            // If valid, store stop sensor 2
688            if (verify > 1) {
689                e = new Element("sensor");
690                e.addContent(new Element("sensorName").addContent(stopSensor2.getText()));
691                e.addContent(new Element("type").addContent("StopSensor2"));
692                e.addContent(new Element("trigger").addContent(stopOnEntry2.isSelected() ? "entry" : "exit"));
693                try {
694                    e.addContent(new Element("distance").addContent(String.valueOf(IntlUtilities.floatValue(distance2.getText()))));
695                } catch (java.text.ParseException ex) {
696                    log.error("Distance isn't a valid floating number: {}", distance2.getText());
697                }
698                values.addContent(e);
699            }
700        }
701        try {
702            x.writeXML(file, doc);
703        } catch (FileNotFoundException ex) {
704            log.error("File not found when writing", ex);
705        } catch (IOException ex) {
706            log.error("IO Exception when writing", ex);
707        }
708
709        log.debug("...done");
710    }
711
712    private void doLoad() {
713
714        log.debug("Check if there's anything to load");
715        SpeedometerXml x = new SpeedometerXml();
716        File file = x.getFile(false);
717
718        if (file == null) {
719            log.debug("Nothing to load");
720            return;
721        }
722
723        log.debug("Start loading speedometer settings...");
724
725        // Find root
726        Element root;
727        try {
728            root = x.rootFromFile(file);
729            if (root == null) {
730                log.debug("File could not be read");
731                return;
732            }
733
734            // First read configuration
735            if (root.getChild("configuration") != null) {
736                List<Element> l = root.getChild("configuration").getChildren();
737                if (log.isDebugEnabled()) {
738                    log.debug("readFile sees {} configurations", l.size());
739                }
740                for (int i = 0; i < l.size(); i++) {
741                    Element e = l.get(i);
742                    if (log.isDebugEnabled()) {
743                        log.debug("Configuration {} value {}", e.getName(), e.getValue());
744                    }
745                    if (e.getName().equals("useMetric")) {
746                        setUnitsMetric(e.getValue().equals("yes") ? true : false);
747                    }
748                }
749            }
750
751            // Now read sensor information
752            if (root.getChild("sensors") != null) {
753                List<Element> l = root.getChild("sensors").getChildren("sensor");
754                if (log.isDebugEnabled()) {
755                    log.debug("readFile sees {} sensors", l.size());
756                }
757                for (int i = 0; i < l.size(); i++) {
758                    Element e = l.get(i);
759                    String sensorType = e.getChild("type").getText();
760                    if (sensorType.equals("StartSensor")) {
761                        startSensor.setText(e.getChild("sensorName").getText());
762                        boolean trigger = e.getChild("trigger").getValue().equals("entry");
763                        startOnEntry.setSelected(trigger);
764                        startOnExit.setSelected(!trigger);
765                    } else if (sensorType.equals("StopSensor1")) {
766                        stopSensor1.setText(e.getChild("sensorName").getText());
767                        boolean trigger = e.getChild("trigger").getValue().equals("entry");
768                        stopOnEntry1.setSelected(trigger);
769                        stopOnExit1.setSelected(!trigger);
770                        distance1.setText(
771                                IntlUtilities.valueOf(
772                                        Float.parseFloat(
773                                                e.getChild("distance").getText()
774                                        )
775                                )
776                        );
777                    } else if (sensorType.equals("StopSensor2")) {
778                        stopSensor2.setText(e.getChild("sensorName").getText());
779                        boolean trigger = e.getChild("trigger").getValue().equals("entry");
780                        stopOnEntry2.setSelected(trigger);
781                        stopOnExit2.setSelected(!trigger);
782                        distance2.setText(
783                                IntlUtilities.valueOf(
784                                        Float.parseFloat(
785                                                e.getChild("distance").getText()
786                                        )
787                                )
788                        );
789                    } else {
790                        log.warn("Unknown sensor type: {}", sensorType);
791                    }
792                }
793            }
794
795        } catch (JDOMException ex) {
796            log.error("File invalid", ex);
797        } catch (IOException ex) {
798            log.error("Error reading file", ex);
799        }
800
801        log.debug("...done");
802    }
803
804    private void setupIconMap(SensorIcon sensor) {
805        sensor.setIcon("SensorStateActive",
806                new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-occupied.gif",
807                        "resources/icons/smallschematics/tracksegments/circuit-occupied.gif"));
808        sensor.setIcon("SensorStateInactive",
809                new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-empty.gif",
810                        "resources/icons/smallschematics/tracksegments/circuit-empty.gif"));
811        sensor.setIcon("BeanStateInconsistent",
812                new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
813                        "resources/icons/smallschematics/tracksegments/circuit-error.gif"));
814        sensor.setIcon("BeanStateUnknown",
815                new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
816                        "resources/icons/smallschematics/tracksegments/circuit-error.gif"));
817    }
818
819    private static class SpeedometerXml extends XmlFile {
820
821        public static String getDefaultFileName() {
822            return getFileLocation() + getFileName();
823        }
824
825        public File getFile(boolean store) {
826            File file = findFile(getDefaultFileName());
827            if (file == null && store) {
828                file = new File(getDefaultFileName());
829            }
830            return file;
831        }
832
833        private static String baseFileName = "Speedometer.xml";
834
835        public static String getFileName() {
836            return Application.getApplicationName() + baseFileName;
837        }
838
839        /**
840         * Absolute path to location of Speedometer files.
841         *
842         * @return path to location
843         */
844        public static String getFileLocation() {
845            return fileLocation;
846        }
847
848        private static String fileLocation = FileUtil.getUserFilesPath();
849
850    }
851
852    private static final Logger log = LoggerFactory.getLogger(SpeedometerFrame.class);
853}