001package jmri.jmrix.ecos.utilities; 002 003import java.awt.BorderLayout; 004import java.awt.Component; 005import java.awt.Dimension; 006import java.awt.Label; 007import java.awt.Toolkit; 008import java.awt.event.ActionEvent; 009import java.awt.event.ActionListener; 010import java.awt.event.MouseAdapter; 011import java.awt.event.MouseEvent; 012import java.awt.event.WindowEvent; 013import java.io.File; 014import java.io.IOException; 015import java.util.ArrayList; 016import java.util.Enumeration; 017import java.util.List; 018 019import javax.swing.BorderFactory; 020import javax.swing.BoxLayout; 021import javax.swing.JButton; 022import javax.swing.JCheckBox; 023import javax.swing.JComboBox; 024import javax.swing.JDialog; 025import javax.swing.JFrame; 026import javax.swing.JLabel; 027import javax.swing.JPanel; 028import javax.swing.JScrollPane; 029import javax.swing.JToggleButton; 030import javax.swing.JTree; 031import javax.swing.event.TreeSelectionEvent; 032import javax.swing.event.TreeSelectionListener; 033import javax.swing.tree.DefaultMutableTreeNode; 034import javax.swing.tree.DefaultTreeModel; 035import javax.swing.tree.DefaultTreeSelectionModel; 036import javax.swing.tree.TreeNode; 037import javax.swing.tree.TreePath; 038 039import jmri.InstanceManager; 040import jmri.Programmer; 041import jmri.jmrit.XmlFile; 042import jmri.jmrit.decoderdefn.DecoderFile; 043import jmri.jmrit.decoderdefn.DecoderIndexFile; 044import jmri.jmrit.roster.Roster; 045import jmri.jmrit.roster.RosterConfigManager; 046import jmri.jmrit.roster.RosterEntry; 047import jmri.jmrit.symbolicprog.CvTableModel; 048import jmri.jmrit.symbolicprog.ResetTableModel; 049import jmri.jmrit.symbolicprog.VariableTableModel; 050import jmri.jmrix.ecos.EcosListener; 051import jmri.jmrix.ecos.EcosLocoAddress; 052import jmri.jmrix.ecos.EcosLocoAddressManager; 053import jmri.jmrix.ecos.EcosMessage; 054import jmri.jmrix.ecos.EcosPreferences; 055import jmri.jmrix.ecos.EcosReply; 056import jmri.jmrix.ecos.EcosSystemConnectionMemo; 057import jmri.util.swing.JmriJOptionPane; 058 059import org.jdom2.Element; 060import org.jdom2.JDOMException; 061 062public class EcosLocoToRoster implements EcosListener { 063 064 EcosLocoAddressManager ecosManager; 065 EcosLocoAddress ecosLoco; 066 RosterEntry re; 067 String filename = null; 068 DecoderFile pDecoderFile = null; 069 String _ecosObject; 070 int _ecosObjectInt; 071 Label _statusLabel = null; 072 CvTableModel cvModel = null; 073 Programmer mProgrammer; 074 JLabel progStatus; 075// Programmer pProg; 076 protected JComboBox<?> locoBox = null; 077 protected JToggleButton iddecoder; 078 JFrame frame; 079 EcosSystemConnectionMemo adaptermemo; 080 EcosPreferences p; 081 boolean suppressFurtherAdditions = false; 082 083 public EcosLocoToRoster(EcosSystemConnectionMemo memo) { 084 adaptermemo = memo; 085 p = adaptermemo.getPreferenceManager(); 086 } 087 088 public void addToQueue(EcosLocoAddress ecosObject) { 089 locoList.add(ecosObject); 090 } 091 boolean waitingForComplete = false; 092 boolean inProcess = false; 093 094 public void processQueue() { 095 if (inProcess) { 096 return; 097 } 098 suppressFurtherAdditions = false; 099 inProcess = true; 100 Runnable run = new Runnable() { 101 @Override 102 public void run() { 103 while (locoList.size() != 0) { 104 final EcosLocoAddress tmploco = locoList.get(0); 105 waitingForComplete = false; 106 if (p.getAddLocoToJMRI() == EcosPreferences.YES) { 107 adaptermemo.getLocoAddressManager().setLocoToRoster(); 108 ecosLocoToRoster(tmploco.getEcosObject()); 109 } else if (!suppressFurtherAdditions && tmploco.addToRoster() && (tmploco.getRosterId() == null)) { 110 class WindowMaker implements Runnable { 111 112 EcosLocoAddress ecosObject; 113 114 WindowMaker(EcosLocoAddress o) { 115 ecosObject = o; 116 } 117 118 @Override 119 public void run() { 120 final JDialog dialog = new JDialog(); 121 dialog.setTitle(Bundle.getMessage("AddRosterEntryQuestion")); 122 //dialog.setLocationRelativeTo(null); 123 dialog.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); 124 JPanel container = new JPanel(); 125 container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); 126 container.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 127 128 JLabel question = new JLabel(Bundle.getMessage("LocoAddedJMessage", ecosObject.getEcosDescription(), adaptermemo.getUserName())); 129 question.setAlignmentX(Component.CENTER_ALIGNMENT); 130 container.add(question); 131 132 question = new JLabel(Bundle.getMessage("AddToJMRIQuestion")); 133 question.setAlignmentX(Component.CENTER_ALIGNMENT); 134 container.add(question); 135 final JCheckBox remember = new JCheckBox(Bundle.getMessage("MessageRememberSetting")); 136 remember.setFont(remember.getFont().deriveFont(10f)); 137 remember.setAlignmentX(Component.CENTER_ALIGNMENT); 138 //user preferences do not have the save option, but once complete the following line can be removed 139 //Need to get the method to save connection configuration. 140 remember.setVisible(true); 141 JButton yesButton = new JButton(Bundle.getMessage("ButtonYes")); 142 JButton noButton = new JButton(Bundle.getMessage("ButtonNo")); 143 JPanel button = new JPanel(); 144 button.setAlignmentX(Component.CENTER_ALIGNMENT); 145 button.add(yesButton); 146 button.add(noButton); 147 container.add(button); 148 149 noButton.addActionListener(new ActionListener() { 150 @Override 151 public void actionPerformed(ActionEvent e) { 152 ecosObject.doNotAddToRoster(); 153 waitingForComplete = true; 154 if (remember.isSelected()) { 155 suppressFurtherAdditions = true; 156 p.setAddLocoToJMRI(EcosPreferences.NO); 157 } 158 dialog.dispose(); 159 } 160 }); 161 162 yesButton.addActionListener(new ActionListener() { 163 @Override 164 public void actionPerformed(ActionEvent e) { 165 if (remember.isSelected()) { 166 p.setAddLocoToJMRI(EcosPreferences.YES); 167 } 168 ecosLocoToRoster(ecosObject.getEcosObject()); 169 dialog.dispose(); 170 } 171 }); 172 container.add(remember); 173 container.setAlignmentX(Component.CENTER_ALIGNMENT); 174 container.setAlignmentY(Component.CENTER_ALIGNMENT); 175 dialog.getContentPane().add(container); 176 dialog.pack(); 177 Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); 178 179 int w = dialog.getSize().width; 180 int h = dialog.getSize().height; 181 int x = (dim.width - w) / 2; 182 int y = (dim.height - h) / 2; 183 184 // Move the window 185 dialog.setLocation(x, y); 186 187 dialog.setModal(true); 188 dialog.setVisible(true); 189 } 190 } 191 try { 192 WindowMaker t = new WindowMaker(tmploco); 193 javax.swing.SwingUtilities.invokeAndWait(t); 194 } catch (java.lang.reflect.InvocationTargetException | InterruptedException ex) { 195 log.warn("Exception, ending", ex); 196 return; 197 } 198 } else { 199 waitingForComplete = true; 200 } 201 Runnable r = new Runnable() { 202 @Override 203 public void run() { 204 try { 205 while (!waitingForComplete) { 206 Thread.sleep(500L); 207 } 208 } catch (InterruptedException ex) { 209 Thread.currentThread().interrupt(); 210 } 211 } 212 }; 213 Thread thr = new Thread(r); 214 thr.start(); 215 thr.setName("Ecos Loco To Roster Inner thread"); // NOI18N 216 try { 217 thr.join(); 218 } catch (InterruptedException ex) { 219 Thread.currentThread().interrupt(); 220 } 221 locoList.remove(0); 222 } 223 inProcess = false; 224 } 225 }; 226 Thread thread = new Thread(run); 227 thread.setName("Ecos Loco To Roster"); // NOI18N 228 thread.start(); 229 230 } 231 232 ArrayList<EcosLocoAddress> locoList = new ArrayList<EcosLocoAddress>(); 233 234 //Same Name as the constructor need to sort it out! 235 public void ecosLocoToRoster(String ecosObject) { 236 frame = new JFrame(); 237 238 _ecosObject = ecosObject; 239 _ecosObjectInt = Integer.parseInt(_ecosObject); 240 ecosManager = adaptermemo.getLocoAddressManager(); 241 242 ecosLoco = ecosManager.getByEcosObject(ecosObject); 243 String rosterId = ecosLoco.getEcosDescription(); 244 if (checkDuplicate(rosterId)) { 245 int count = 0; 246 String oldrosterId = rosterId; 247 while (checkDuplicate(rosterId)) { 248 rosterId = oldrosterId + "_" + count; 249 count++; 250 } 251 } 252 re = new RosterEntry(); 253 re.setId(rosterId); 254 List<DecoderFile> decoder = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, ecosLoco.getCVAsString(8), ecosLoco.getCVAsString(7), null, null); 255 if (decoder.size() == 1) { 256 pDecoderFile = decoder.get(0); 257 selectedDecoder(pDecoderFile); 258 259 } else { 260 261 class WindowMaker implements Runnable { 262 263 WindowMaker() { 264 } 265 266 @Override 267 public void run() { 268 comboPanel(); 269 } 270 } 271 WindowMaker t = new WindowMaker(); 272 javax.swing.SwingUtilities.invokeLater(t); 273 274 } 275 } 276 277 @Override 278 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "CF_USELESS_CONTROL_FLOW", 279 justification = "TODO fill out the actions in these clauses") 280 public void reply(EcosReply m) { 281 int startval; 282 int endval; 283 284 String msg = m.toString(); 285 String[] lines = msg.split("\n"); 286 if (m.getResultCode() == 0) { 287 // TODO use this if branch? 288 // 289 // if (lines[0].startsWith("<REPLY get(" + _ecosObject + ", cv[")) { 290 // startval = lines[0].indexOf("(") + 1; 291 // endval = (lines[0].substring(startval)).indexOf(",") + startval; 292 // //The first part of the messages is always the object id. 293 // int object = Integer.parseInt(lines[0].substring(startval, endval)); 294 // if (object == _ecosObjectInt) { 295 // for (int i = 1; i < lines.length - 1; i++) { 296 // if (lines[i].contains("cv[")) { 297 // //int startcvnum = lines[i].indexOf("[")+1; 298 // //int endcvnum = (lines[i].substring(startcvnum)).indexOf(",")+startcvnum; 299 // //int cvnum = Integer.parseInt(lines[i].substring(startcvnum, endcvnum)); 300 // //int startcvval = (lines[i].substring(endcvnum)).indexOf(", ")+endcvnum+2; 301 // //int endcvval = (lines[i].substring(startcvval)).indexOf("]")+startcvval; 302 // //int cvval = Integer.parseInt(lines[i].substring(startcvval, endcvval)); 303 // //String strcvnum = "CV"+cvnum; 304 // } 305 // } 306 // } 307 // } else if (lines[0].startsWith("<REPLY get(" + _ecosObject + ", funcdesc")) { 308 if (lines[0].startsWith("<REPLY get(" + _ecosObject + ", funcdesc")) { 309 int functNo = 0; 310 try { 311 startval = lines[1].indexOf("[") + 1; 312 endval = (lines[1].substring(startval)).indexOf(",") + startval; 313 boolean moment = true; 314 functNo = Integer.parseInt(lines[1].substring(startval, endval)); 315 startval = endval + 1; 316 endval = (lines[1].substring(startval)).indexOf(",");//+startval; 317 if (endval == -1) { 318 endval = (lines[1].substring(startval)).indexOf("]");//+startval; 319 moment = false; 320 } 321 endval = endval + startval; 322 if (lines[1].contains("moment")) { 323 moment = true; 324 } 325 326 int functDesc = Integer.parseInt(lines[1].substring(startval, endval)); 327 328 String functionLabel = ""; 329 switch (functDesc) { 330 //Default descriptions for ESU function icons 331 case 2: 332 functionLabel = "function"; 333 break; 334 case 3: 335 functionLabel = "light"; 336 break; 337 case 4: 338 functionLabel = "light_0"; 339 break; 340 case 5: 341 functionLabel = "light_1"; 342 break; 343 case 7: 344 functionLabel = "sound"; 345 break; 346 case 8: 347 functionLabel = "music"; 348 break; 349 case 9: 350 functionLabel = "announce"; 351 break; 352 case 10: 353 functionLabel = "routing_speed"; 354 break; 355 case 11: 356 functionLabel = "abv"; 357 break; 358 case 32: 359 functionLabel = "coupler"; 360 break; 361 case 33: 362 functionLabel = "steam"; 363 break; 364 case 34: 365 functionLabel = "panto"; 366 break; 367 case 35: 368 functionLabel = "highbeam"; 369 break; 370 case 36: 371 functionLabel = "bell"; 372 break; 373 case 37: 374 functionLabel = "horn"; 375 break; 376 case 38: 377 functionLabel = "whistle"; 378 break; 379 case 39: 380 functionLabel = "door_sound"; 381 break; 382 case 40: 383 functionLabel = "fan"; 384 break; 385 case 42: 386 functionLabel = "shovel_work_sound"; 387 break; 388 case 44: 389 functionLabel = "shift"; 390 break; 391 case 260: 392 functionLabel = "interior_lighting"; 393 break; 394 case 261: 395 functionLabel = "plate_light"; 396 break; 397 case 263: 398 functionLabel = "brakesound"; 399 break; 400 case 299: 401 functionLabel = "crane_raise_lower"; 402 break; 403 case 555: 404 functionLabel = "hook_up_down"; 405 break; 406 case 773: 407 functionLabel = "wheel_light"; 408 break; 409 case 811: 410 functionLabel = "turn"; 411 break; 412 case 1031: 413 functionLabel = "steam-blow"; 414 break; 415 case 1033: 416 functionLabel = "radio_sound"; 417 break; 418 case 1287: 419 functionLabel = "coupler_sound"; 420 break; 421 case 1543: 422 functionLabel = "track_sound"; 423 break; 424 case 1607: 425 functionLabel = "notch_up"; 426 break; 427 case 1608: 428 functionLabel = "notch_down"; 429 break; 430 case 2055: 431 functionLabel = "thunderer_whistle"; 432 break; 433 case 3847: 434 functionLabel = "buffer_sound"; 435 break; 436 default: 437 break; 438 } 439 440 re.setFunctionLabel(functNo, functionLabel); 441 re.setFunctionLockable(functNo, !moment); 442 } catch (RuntimeException e) { 443 log.error("Error occurred while getting the function information : {}", e.toString()); 444 } 445 getFunctionDetails(functNo + 1); 446 } 447 } 448 } 449 450 @Override 451 public void message(EcosMessage m) { 452 453 } 454 455 void storeloco() { 456 Roster.getDefault().addEntry(re); 457 ecosLoco.setRosterId(re.getId()); 458 re.ensureFilenameExists(); 459 460 re.writeFile(null, null); 461 462 Roster.getDefault().writeRoster(); 463 ecosManager.clearLocoToRoster(); 464 } 465 466 public void comboPanel() { 467 frame.setTitle(Bundle.getMessage("DecoderSelectionXTitle", ecosLoco.getEcosDescription())); 468 frame.getContentPane().setLayout(new BorderLayout()); 469 470 JPanel topPanel = new JPanel(); 471 472 JPanel p1 = new JPanel(); 473 JPanel p2 = new JPanel(); 474 JPanel p3 = this.layoutDecoderSelection(); 475 476 // Create a panel to hold all other components 477 topPanel.setLayout(new BorderLayout()); 478 //frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 479 //frame.setDefaultCloseOperation(frameclosed()); 480 JLabel jLabel1 = new JLabel(Bundle.getMessage("DecoderNoIDWarning")); 481 JButton okayButton = new JButton(Bundle.getMessage("ButtonOK")); 482 p1.add(jLabel1); 483 p2.add(okayButton); 484 topPanel.add(p1); 485 topPanel.add(p3); 486 topPanel.add(p2); 487 488 topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS)); 489 490 frame.getContentPane().add(topPanel); 491 frame.pack(); 492 frame.setVisible(true); 493 frame.toFront(); 494 frame.setFocusable(true); 495 frame.setFocusableWindowState(true); 496 frame.requestFocus(); 497 498 frame.setAlwaysOnTop(true); 499 frame.setAlwaysOnTop(false); 500 Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); 501 502 int w = frame.getSize().width; 503 int h = frame.getSize().height; 504 int x = (dim.width - w) / 2; 505 int y = (dim.height - h) / 2; 506 507 frame.setLocation(x, y); 508 frame.setVisible(true); 509 510 frame.addWindowListener(new java.awt.event.WindowAdapter() { 511 @Override 512 public void windowClosing(WindowEvent winEvt) { 513 ecosManager.clearLocoToRoster(); 514 } 515 }); 516 517 ActionListener okayButtonAction = new ActionListener() { 518 @Override 519 public void actionPerformed(ActionEvent actionEvent) { 520 okayButton(); 521 } 522 }; 523 okayButton.addActionListener(okayButtonAction); 524 } 525 526 String selectedDecoderType() { 527 if (!isDecoderSelected()) { 528 return null; 529 } else { 530 return ((DecoderTreeNode) dTree.getLastSelectedPathComponent()).getTitle(); 531 } 532 } 533 534 boolean isDecoderSelected() { 535 return !dTree.isSelectionEmpty(); 536 } 537 538 private void okayButton() { 539 pDecoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(selectedDecoderType()); 540 selectedDecoder(pDecoderFile); 541 frame.dispose(); 542 } 543 544 private void selectedDecoder(DecoderFile pDecoderFile) { 545 //pDecoderFile=InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(selectedDecoderType()); 546 re.setDecoderModel(pDecoderFile.getModel()); 547 re.setDecoderFamily(pDecoderFile.getFamily()); 548 549 if (ecosLoco.getNumber() == 0) { 550 re.setDccAddress(Integer.toString(EcosLocoAddress.MFX_DCCAddressOffset+ecosLoco.getEcosObjectAsInt())); 551 } else { 552 re.setDccAddress(Integer.toString(ecosLoco.getNumber())); 553 } 554 //re.setLongAddress(true); 555 556 re.setRoadName(""); 557 re.setRoadNumber(""); 558 re.setMfg(""); 559 re.setModel(""); 560 re.setOwner(InstanceManager.getDefault(RosterConfigManager.class).getDefaultOwner()); 561 re.setComment(Bundle.getMessage("LocoAutoAdded")); 562 re.setDecoderComment(""); 563 re.putAttribute(adaptermemo.getPreferenceManager().getRosterAttribute(), _ecosObject); 564 re.ensureFilenameExists(); 565 if ((ecosLoco.getECOSProtocol().startsWith("DCC"))) { 566 if (ecosLoco.getNumber() <= 127) { 567 re.setProtocol(jmri.LocoAddress.Protocol.DCC_SHORT); 568 } else { 569 re.setProtocol(jmri.LocoAddress.Protocol.DCC_LONG); 570 } 571 } else if (ecosLoco.getECOSProtocol().equals("MMFKT") || ecosLoco.getECOSProtocol().equals("MFX")) { 572 re.setProtocol(jmri.LocoAddress.Protocol.MFX); 573 } else if (ecosLoco.getECOSProtocol().startsWith("MM")) { 574 re.setProtocol(jmri.LocoAddress.Protocol.MOTOROLA); 575 } else if (ecosLoco.getECOSProtocol().equals("SX32")) { 576 re.setProtocol(jmri.LocoAddress.Protocol.SELECTRIX); 577 } 578 579 mProgrammer = null; 580 cvModel = new CvTableModel(progStatus, mProgrammer); 581 variableModel = new VariableTableModel(progStatus, new String[]{"CV", "Value"}, cvModel); 582 resetModel = new ResetTableModel(progStatus, mProgrammer); 583 storeloco(); 584 filename = "programmers" + File.separator + "Basic.xml"; 585 loadProgrammerFile(re); 586 loadDecoderFile(pDecoderFile, re); 587 588 variableModel.findVar("Speed Step Mode").setIntValue(0); // NOI18N 589 if (ecosLoco.getECOSProtocol().equals("DCC128")) { 590 variableModel.findVar("Speed Step Mode").setIntValue(1); 591 } 592 593 re.writeFile(cvModel, variableModel); 594 getFunctionDetails(0); 595 JmriJOptionPane.showMessageDialog(frame, Bundle.getMessage("LocoAddedJDialog")); 596 waitingForComplete = true; 597 } 598 599 /** 600 * Check for Duplicate roster entry. 601 * @param id Loco ID String. 602 * @return true if the value in the Ecos Description is a duplicate of some 603 * other RosterEntry in the roster 604 */ 605 public boolean checkDuplicate(String id) { 606 // check its not a duplicate 607 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, null, null, null, null, id); 608 boolean oops = false; 609 for (int i = 0; i < l.size(); i++) { 610 if (re != l.get(i)) { 611 oops = true; 612 } 613 } 614 return oops; 615 } 616 617 JTree dTree; 618 DefaultTreeModel dModel; 619 DefaultMutableTreeNode dRoot; 620 TreeSelectionListener dListener; 621 622 //@TODO this could do with being re-written so that it reuses the combined loco select tree code 623 protected JPanel layoutDecoderSelection() { 624 625 JPanel pane1a = new JPanel(); 626 pane1a.setLayout(new BoxLayout(pane1a, BoxLayout.X_AXIS)); 627 // create the list of manufacturers; get the list of decoders, and add elements 628 dRoot = new DefaultMutableTreeNode("Root"); 629 dModel = new DefaultTreeModel(dRoot); 630 dTree = new JTree(dModel) { 631 632 @Override 633 public String getToolTipText(MouseEvent evt) { 634 if (getRowForLocation(evt.getX(), evt.getY()) == -1) { 635 return null; 636 } 637 TreePath curPath = getPathForLocation(evt.getX(), evt.getY()); 638 return ((DecoderTreeNode) curPath.getLastPathComponent()).getToolTipText(); 639 } 640 }; 641 dTree.setToolTipText(""); 642 List<DecoderFile> decoders = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, null, null, null, null); 643 int len = decoders.size(); 644 DefaultMutableTreeNode mfgElement = null; 645 DefaultMutableTreeNode familyElement = null; 646 for (int i = 0; i < len; i++) { 647 DecoderFile decoder = decoders.get(i); 648 String mfg = decoder.getMfg(); 649 String family = decoder.getFamily(); 650 String model = decoder.getModel(); 651 log.debug(" process {}/{}/{} on nodes {}/{}", mfg, family, model, mfgElement == null ? "<null>" : mfgElement.toString() + "(" + mfgElement.getChildCount() + ")", familyElement == null ? "<null>" : familyElement.toString() + "(" + familyElement.getChildCount() + ")"); 652 // build elements 653 if (mfgElement == null || !mfg.equals(mfgElement.toString())) { 654 // need new mfg node 655 mfgElement = new DecoderTreeNode(mfg, 656 "CV8 = " + InstanceManager.getDefault(DecoderIndexFile.class).mfgIdFromName(mfg), ""); 657 dModel.insertNodeInto(mfgElement, dRoot, dRoot.getChildCount()); 658 familyElement = null; 659 } 660 String famComment = decoders.get(i).getFamilyComment(); 661 String verString = decoders.get(i).getVersionsAsString(); // not null 662 String hoverText = ""; 663 if (famComment == null || famComment.isEmpty()) { 664 if (!verString.isEmpty()) { 665 hoverText = "CV7=" + verString; 666 } 667 } else { 668 if (verString.isEmpty()) { 669 hoverText = famComment; 670 } else { 671 hoverText = famComment + " CV7=" + verString; 672 } 673 } 674 if (familyElement == null || !family.equals(familyElement.toString())) { 675 // need new family node - is there only one model? Expect the 676 // family element, plus the model element, so check i+2 677 // to see if its the same, or if a single-decoder family 678 // appears to have decoder names separate from the family name 679 if ((i + 2 >= len) 680 || decoders.get(i + 2).getFamily().equals(family) 681 || !decoders.get(i + 1).getModel().equals(family)) { 682 // normal here; insert the new family element & exit 683 log.debug("normal family update case: {}", family); 684 familyElement = new DecoderTreeNode(family, 685 hoverText, 686 decoders.get(i).titleString()); 687 dModel.insertNodeInto(familyElement, mfgElement, mfgElement.getChildCount()); 688 continue; 689 } else { 690 // this is short case; insert decoder entry (next) here 691 log.debug("short case, i={} family={} next {}", i, family, decoders.get(i + 1).getModel()); 692 if (i + 1 > len) { 693 log.error("Unexpected single entry for family: {}", family); 694 } 695 family = decoders.get(i + 1).getModel(); 696 familyElement = new DecoderTreeNode(family, 697 hoverText, 698 decoders.get(i).titleString()); 699 dModel.insertNodeInto(familyElement, mfgElement, mfgElement.getChildCount()); 700 i = i + 1; 701 continue; 702 } 703 } 704 // insert at the decoder level, except if family name is the same 705 if (!family.equals(model)) { 706 dModel.insertNodeInto(new DecoderTreeNode(model, 707 hoverText, 708 decoders.get(i).titleString()), 709 familyElement, familyElement.getChildCount()); 710 } 711 } // end of loop over decoders 712 713 // build the tree GUI 714 pane1a.add(new JScrollPane(dTree)); 715 dTree.expandPath(new TreePath(dRoot)); 716 dTree.setRootVisible(false); 717 dTree.setShowsRootHandles(true); 718 dTree.setScrollsOnExpand(true); 719 dTree.setExpandsSelectedPaths(true); 720 721 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); 722 // tree listener 723 dTree.addTreeSelectionListener(dListener = new TreeSelectionListener() { 724 @Override 725 public void valueChanged(TreeSelectionEvent e) { 726 if (!dTree.isSelectionEmpty() && dTree.getSelectionPath() != null 727 && // can't be just a mfg, has to be at least a family 728 dTree.getSelectionPath().getPathCount() > 2 729 && // can't be a multiple decoder selection 730 dTree.getSelectionCount() < 2) { 731 // decoder selected - reset and disable loco selection 732 log.debug("Selection event with {}", dTree.getSelectionPath().toString()); 733 if (locoBox != null) { 734 locoBox.setSelectedIndex(0); 735 } 736 } 737 } 738 }); 739 740// Mouselistener for doubleclick activation of proprammer 741 dTree.addMouseListener(new MouseAdapter() { 742 @Override 743 public void mouseClicked(MouseEvent me) { 744 // Clear any status messages and ensure the tree is in single path select mode 745 //if (_statusLabel != null) _statusLabel.setText("StateIdle"); 746 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); 747 748 /* check for both double click and that it's a decoder 749 that is being clicked on. If it's just a Family, the programmer 750 button is enabled by the TreeSelectionListener, but we don't 751 want to automatically open a programmer so a user has the opportunity 752 to select an individual decoder 753 */ 754 if (me.getClickCount() == 2) { 755 if (((TreeNode) dTree.getSelectionPath().getLastPathComponent()).isLeaf()) { 756 okayButton(); 757 } 758 } 759 } 760 }); 761 762 this.selectDecoder(ecosLoco.getCVAsString(8), ecosLoco.getCVAsString(7)); 763 return pane1a; 764 } 765 766 // from http://www.codeguru.com/java/articles/143.shtml 767 static class DecoderTreeNode extends DefaultMutableTreeNode { 768 769 private String toolTipText; 770 private String title; 771 772 public DecoderTreeNode(String str, String toolTipText, String title) { 773 super(str); 774 this.toolTipText = toolTipText; 775 this.title = title; 776 } 777 778 public String getTitle() { 779 return title; 780 } 781 782 public String getToolTipText() { 783 return toolTipText; 784 } 785 } 786 787 protected void selectDecoder(String mfgID, String modelID) { 788 789 // locate a decoder like that. 790 List<DecoderFile> temp = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, mfgID, modelID, null, null); 791 if (log.isDebugEnabled()) { 792 log.debug("selectDecoder found {} matches", temp.size()); 793 } 794 // install all those in the JComboBox in place of the longer, original list 795 if (temp.size() > 0) { 796 updateForDecoderTypeID(temp); 797 } else { 798 String mfg = InstanceManager.getDefault(DecoderIndexFile.class).mfgNameFromID(mfgID); 799 int intMfgID = Integer.parseInt(mfgID); 800 int intModelID = Integer.parseInt(modelID); 801 if (mfg == null) { 802 updateForDecoderNotID(intMfgID, intModelID); 803 } else { 804 updateForDecoderMfgID(mfg, intMfgID, intModelID); 805 } 806 } 807 } 808 809 void updateForDecoderNotID(int pMfgID, int pModelID) { 810 log.warn("Found mfg {} version {}; no such manufacterer defined", pMfgID, pModelID ); 811 dTree.clearSelection(); 812 } 813 814 void updateForDecoderMfgID(String pMfg, int pMfgID, int pModelID) { 815 log.warn("Found mfg {} ({}) version {}; no such decoder defined", pMfgID, pMfg, pModelID ); 816 dTree.clearSelection(); 817 Enumeration<TreeNode> e = dRoot.breadthFirstEnumeration(); 818 while (e.hasMoreElements()) { 819 DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.nextElement(); 820 if (node.toString().equals(pMfg)) { 821 TreePath path = new TreePath(node.getPath()); 822 dTree.expandPath(path); 823 dTree.addSelectionPath(path); 824 dTree.scrollPathToVisible(path); 825 break; 826 } 827 } 828 829 } 830 831 void updateForDecoderTypeID(List<DecoderFile> pList) { 832 // find and select the first item 833 if (log.isDebugEnabled()) { 834 StringBuilder buf = new StringBuilder(); 835 for (int i = 0; i < pList.size(); i++) { 836 buf.append(pList.get(i).getModel()); 837 buf.append(":"); 838 } 839 log.debug("Identified {} matches: {}", pList.size(), buf ); 840 } 841 if (pList.size() <= 0) { 842 log.error("Found empty list in updateForDecoderTypeID, should not happen"); 843 return; 844 } 845 dTree.clearSelection(); 846 // If there are multiple matches change tree to allow multiple selections by the program 847 // and issue a warning instruction in the status bar 848 if (pList.size() > 1) { 849 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); 850 } else { 851 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); 852 } 853 // Select the decoder(s) in the tree 854 for (int i = 0; i < pList.size(); i++) { 855 856 DecoderFile f = pList.get(i); 857 String findMfg = f.getMfg(); 858 String findFamily = f.getFamily(); 859 String findModel = f.getModel(); 860 861 Enumeration<TreeNode> e = dRoot.breadthFirstEnumeration(); 862 while (e.hasMoreElements()) { 863 DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.nextElement(); 864 865 // convert path to comparison string 866 TreeNode[] list = node.getPath(); 867 if (list.length == 3) { 868 // check for match to mfg, model, model 869 if (list[1].toString().equals(findMfg) 870 && list[2].toString().equals(findModel)) { 871 TreePath path = new TreePath(node.getPath()); 872 dTree.expandPath(path); 873 dTree.addSelectionPath(path); 874 dTree.scrollPathToVisible(path); 875 break; 876 } 877 } else if (list.length == 4) { 878 // check for match to mfg, family, model 879 if (list[1].toString().equals(findMfg) 880 && list[2].toString().equals(findFamily) 881 && list[3].toString().equals(findModel)) { 882 TreePath path = new TreePath(node.getPath()); 883 dTree.expandPath(path); 884 dTree.addSelectionPath(path); 885 dTree.scrollPathToVisible(path); 886 break; 887 } 888 } 889 } 890 } 891 } 892 893 Element modelElem = null; 894 895 Element decoderRoot = null; 896 VariableTableModel variableModel; 897 Element programmerRoot = null; 898 ResetTableModel resetModel = null; 899 900 protected void loadDecoderFile(DecoderFile df, RosterEntry re) { 901 if (df == null) { 902 log.error("loadDecoder file invoked with null object"); 903 return; 904 } 905 log.debug("loadDecoderFile from {} {}", DecoderFile.fileLocation, df.getFileName()); 906 907 try { 908 decoderRoot = df.rootFromName(DecoderFile.fileLocation + df.getFileName()); 909 } catch (org.jdom2.JDOMException e) { 910 log.error("JDOM Exception while loading decoder XML file: {}", df.getFileName()); 911 } catch (java.io.IOException e) { 912 log.error("IO Exception while loading decoder XML file: {}", df.getFileName()); 913 } 914 // load variables from decoder tree 915 df.getProductID(); 916 df.loadVariableModel(decoderRoot.getChild("decoder"), variableModel); 917 918 df.loadResetModel(decoderRoot.getChild("decoder"), resetModel); 919 920 // load function names 921 re.loadFunctions(decoderRoot.getChild("decoder").getChild("family").getChild("functionlabels")); 922 923 // get the showEmptyPanes attribute, if yes/no update our state 924 if (decoderRoot.getAttribute("showEmptyPanes") != null) { 925 log.debug("Found in decoder {}", decoderRoot.getAttribute("showEmptyPanes").getValue()); 926 } 927 928 // save the pointer to the model element 929 modelElem = df.getModelElement(); 930 } 931 932 // From PaneProgFrame 933 protected void loadProgrammerFile(RosterEntry r) { 934 // Open and parse programmer file 935 XmlFile pf = new XmlFile() { 936 }; // XmlFile is abstract 937 try { 938 programmerRoot = pf.rootFromName(filename); 939 940 readConfig(programmerRoot, r); 941 942 } catch (IOException | JDOMException e) { 943 log.error("exception reading programmer file: {}", filename, e); 944 } 945 } 946 947 void readConfig(Element root, RosterEntry r) { 948 // check for "programmer" element at start 949 if (root.getChild("programmer") == null) { 950 log.error("xml file top element is not programmer"); 951 return; 952 } 953 } 954 955 boolean getFunctionSupported = true; 956 957 void getFunctionDetails(int func) { 958 //Only gets information for function numbers upto 28 959 if (func >= 29) { 960 return; 961 } 962 String message = "get(" + _ecosObject + ", funcdesc[" + func + "])"; 963 EcosMessage m = new EcosMessage(message); 964 adaptermemo.getTrafficController().sendEcosMessage(m, this); 965 } 966 967 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EcosLocoToRoster.class); 968 969} 970/* 971 cv8 - mfgIdFromName 972 cv7 - Version/family 973 974 tmp1 = jmri.jmrit.decoderdefn.InstanceManager.getDefault(DecoderIndexFile.class) 975 print tmp1.matchingDecoderList(None, None, cv8, None, None, None) 976 977 matchingDecoderList(String mfg, String family, String decoderMfgID, String decoderVersionID, String decoderProductID, String model ) 978 979 tmp1 = jmri.jmrit.decoderdefn.InstanceManager.getDefault(DecoderIndexFile.class) 980 list = tmp1.matchingDecoderList(None, None, "153", "16", None, None 981 returns decoderfile.java 982 print list[0].getMfg() 983 print list[0].getFamily() 984 */