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.JPanel; 012import javax.swing.JTextField; 013 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 jmri.util.swing.JmriJOptionPane; 025 026import org.jdom2.Document; 027import org.jdom2.Element; 028import org.jdom2.JDOMException; 029import org.jdom2.ProcessingInstruction; 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 JmriJOptionPane.showMessageDialog( 534 this, 535 Bundle.getMessage("ErrorStartSensor"), 536 Bundle.getMessage("TitleError"), 537 JmriJOptionPane.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 JmriJOptionPane.showMessageDialog( 554 this, 555 Bundle.getMessage("ErrorStopSensor1"), 556 Bundle.getMessage("TitleError"), 557 JmriJOptionPane.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 JmriJOptionPane.showMessageDialog( 567 this, 568 Bundle.getMessage("ErrorDistance1"), 569 Bundle.getMessage("TitleError"), 570 JmriJOptionPane.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 JmriJOptionPane.showMessageDialog( 591 this, 592 Bundle.getMessage("ErrorStopSensor2"), 593 Bundle.getMessage("TitleError"), 594 JmriJOptionPane.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 JmriJOptionPane.showMessageDialog( 605 this, 606 Bundle.getMessage("ErrorDistance2"), 607 Bundle.getMessage("TitleError"), 608 JmriJOptionPane.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 (JmriJOptionPane.showConfirmDialog( 624 this, 625 Bundle.getMessage("QuestionNothingToStore"), 626 Bundle.getMessage("TitleStoreQuestion"), 627 JmriJOptionPane.YES_NO_OPTION, 628 JmriJOptionPane.QUESTION_MESSAGE) != JmriJOptionPane.YES_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 org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SpeedometerFrame.class); 853}