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 */