001package apps.gui3.dp3;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Toolkit;
006import java.awt.event.ActionEvent;
007import java.awt.event.ItemEvent;
008import java.awt.event.WindowAdapter;
009import java.awt.event.WindowEvent;
010import java.beans.PropertyChangeEvent;
011import java.beans.PropertyChangeListener;
012import java.io.File;
013import java.io.IOException;
014import java.text.MessageFormat;
015import java.util.ArrayList;
016import java.util.List;
017import javax.annotation.Nonnull;
018import javax.swing.AbstractButton;
019import javax.swing.BorderFactory;
020import javax.swing.BoxLayout;
021import javax.swing.Icon;
022import javax.swing.JButton;
023import javax.swing.JFrame;
024import javax.swing.JLabel;
025import javax.swing.JMenu;
026import javax.swing.JMenuBar;
027import javax.swing.JPanel;
028import javax.swing.JSeparator;
029import javax.swing.JTextField;
030import javax.swing.SwingConstants;
031import javax.swing.border.TitledBorder;
032import javax.swing.event.TreeSelectionEvent;
033import javax.swing.tree.TreeNode;
034import jmri.GlobalProgrammerManager;
035import jmri.InstanceManager;
036import jmri.JmriException;
037import jmri.ProgListener;
038import jmri.Programmer;
039import jmri.ProgrammerException;
040import jmri.jmrit.XmlFile;
041import jmri.jmrit.decoderdefn.DecoderFile;
042import jmri.jmrit.decoderdefn.DecoderIndexFile;
043import jmri.jmrit.decoderdefn.PrintDecoderListAction;
044import jmri.jmrit.progsupport.ProgModeSelector;
045import jmri.jmrit.progsupport.ProgServiceModeComboBox;
046import jmri.jmrit.roster.Roster;
047import jmri.jmrit.roster.RosterEntry;
048import jmri.jmrit.roster.swing.RosterMenu;
049import jmri.jmrit.symbolicprog.AbstractValue;
050import jmri.jmrit.symbolicprog.CombinedLocoSelTreePane;
051import jmri.jmrit.symbolicprog.CvTableModel;
052import jmri.jmrit.symbolicprog.DccAddressPanel;
053import jmri.jmrit.symbolicprog.DccAddressVarHandler;
054import jmri.jmrit.symbolicprog.EnumVariableValue;
055import jmri.jmrit.symbolicprog.SymbolicProgBundle;
056import jmri.jmrit.symbolicprog.VariableTableModel;
057import jmri.jmrit.symbolicprog.VariableValue;
058import jmri.jmrit.symbolicprog.tabbedframe.PaneContainer;
059import jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame;
060import jmri.jmrit.symbolicprog.tabbedframe.PaneProgPane;
061import jmri.jmrit.symbolicprog.tabbedframe.PaneServiceProgFrame;
062import jmri.util.BusyGlassPane;
063import jmri.util.FileUtil;
064import jmri.util.JmriJFrame;
065import jmri.util.jdom.LocaleSelector;
066import jmri.util.swing.*;
067import org.jdom2.Element;
068import org.jdom2.JDOMException;
069
070/**
071 * Swing action to create and register a frame for selecting the information
072 * needed to open a PaneProgFrame in service mode.
073 * <p>
074 * The class name is a historical accident, and probably should have included
075 * "ServiceMode" or something.
076 *
077 * @see jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgAction
078 *
079 * @author Bob Jacobsen Copyright (C) 2001
080 */
081public class PaneProgDp3Action extends JmriAbstractAction implements ProgListener, PaneContainer {
082
083    Object o1, o2, o3, o4;
084    JLabel statusLabel;
085    final ProgModeSelector modePane = new ProgServiceModeComboBox();
086
087    public PaneProgDp3Action(String s, WindowInterface wi) {
088        super(s, wi);
089        init();
090    }
091
092    public PaneProgDp3Action(String s, Icon i, WindowInterface wi) {
093        super(s, i, wi);
094        init();
095    }
096
097    public PaneProgDp3Action() {
098        this(Bundle.getMessage("Name"));  // NOI18N
099    }
100
101    public PaneProgDp3Action(String s) {
102        super(s);
103        init();
104
105    }
106
107    void init() {
108        statusLabel = new JLabel(SymbolicProgBundle.getMessage("StateIdle")); // NOI18N
109    }
110
111    JmriJFrame f;
112    CombinedLocoSelTreePane combinedLocoSelTree;
113
114    @Override
115    public void actionPerformed(ActionEvent e) {
116
117        log.debug("Pane programmer requested"); // NOI18N
118
119        if (f == null) {
120            log.debug("found f==null");
121            // create the initial frame that steers
122            f = new JmriJFrame(apps.gui3.dp3.Bundle.getMessage("FrameProgrammerSetup")); // NOI18N
123            f.getContentPane().setLayout(new BorderLayout());
124            // ensure status line is cleared on close, so it is normal if re-opened
125            f.addWindowListener(new WindowAdapter() {
126                @Override
127                public void windowClosing(WindowEvent we) {
128                    statusLabel.setText(SymbolicProgBundle.getMessage("StateIdle")); // NOI18N
129                    f.windowClosing(we);
130                }
131            });
132
133            // add the Roster menu
134            JMenuBar menuBar = new JMenuBar();
135            JMenu j = new JMenu(SymbolicProgBundle.getMessage("MenuFile")); // NOI18N
136            j.add(new PrintDecoderListAction(SymbolicProgBundle.getMessage("MenuPrintDecoderDefinitions"), f, false)); // NOI18N
137            j.add(new PrintDecoderListAction(SymbolicProgBundle.getMessage("MenuPrintPreviewDecoderDefinitions"), f, true)); // NOI18N
138            menuBar.add(j);
139            menuBar.add(new RosterMenu(SymbolicProgBundle.getMessage("MenuRoster"), RosterMenu.MAINMENU, f)); // NOI18N
140            f.setJMenuBar(menuBar);
141            final JPanel bottomPanel = new JPanel(new BorderLayout());
142            // new Loco on programming track
143            combinedLocoSelTree = new CombinedLocoSelTreePane(statusLabel, modePane) {
144
145                @Override
146                protected void startProgrammer(DecoderFile decoderFile, @Nonnull RosterEntry re,
147                        @Nonnull String progName) { // progName is ignored here
148                    log.debug("startProgrammer");
149                    String title = MessageFormat.format(SymbolicProgBundle.getMessage("FrameServiceProgrammerTitle"), // NOI18N
150                            re.getId());
151                    JFrame p;
152                    if (!modePane.isSelected() || modePane.getProgrammer() == null) {
153                        p = new PaneProgFrame(decoderFile, re,
154                                title, "programmers" + File.separator + "Comprehensive.xml", // NOI18N
155                                null, false) {
156                            @Override
157                            protected JPanel getModePane() {
158                                return null;
159                            }
160                        };
161                    } else {
162                        p = new PaneServiceProgFrame(decoderFile, re,
163                                title, "programmers" + File.separator + "Comprehensive.xml", // NOI18N
164                                modePane.getProgrammer());
165                    }
166                    p.pack();
167                    p.setVisible(true);
168                }
169
170                @Override
171                protected void openNewLoco() {
172                    log.debug("openNewLoco");
173                    // find the decoderFile object
174                    DecoderFile decoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(selectedDecoderType());
175                    log.debug("decoder file: {}", decoderFile.getFileName()); // NOI18N
176                    if (rosterIdField.getText().equals(SymbolicProgBundle.getMessage("LabelNewDecoder"))) { // NOI18N
177                        re = new RosterEntry();
178                        re.setDecoderFamily(decoderFile.getFamily());
179                        re.setDecoderModel(decoderFile.getModel());
180                        re.setId(SymbolicProgBundle.getMessage("LabelNewDecoder")); // NOI18N
181                        // note that we're leaving the filename null
182                        // add the new roster entry to the in-memory roster
183                        Roster.getDefault().addEntry(re);
184                    } else {
185                        try {
186                            saveRosterEntry();
187                        } catch (JmriException ex) {
188                            log.warn("Exception while saving roster entry", ex); // NOI18N
189                            return;
190                        }
191                    }
192                    // create a dummy RosterEntry with the decoder info
193                    startProgrammer(decoderFile, re, ""); // no programmer name in this case
194                    //Set our roster entry back to null so that a fresh roster entry can be created if needed
195                    re = null;
196                }
197
198                @Override
199                protected JPanel layoutRosterSelection() {
200                    log.debug("layoutRosterSelection");
201                    return null;
202                }
203
204                @Override
205                protected JPanel layoutDecoderSelection() {
206                    log.debug("layoutDecoderSelection");
207                    JPanel pan = super.layoutDecoderSelection();
208                    dTree.removeTreeSelectionListener(dListener);
209                    dListener = (TreeSelectionEvent e1) -> {
210                        if (!dTree.isSelectionEmpty() && dTree.getSelectionPath() != null
211                                && // check that this isn't just a model
212                                ((TreeNode) dTree.getSelectionPath().getLastPathComponent()).isLeaf()
213                                && // can't be just a mfg, has to be at least a family
214                                dTree.getSelectionPath().getPathCount() > 2
215                                && // can't be a multiple decoder selection
216                                dTree.getSelectionCount() < 2) {
217                            log.debug("Selection event with {}", dTree.getSelectionPath());
218                            //if (locoBox != null) locoBox.setSelectedIndex(0);
219                            go2.setEnabled(true);
220                            go2.setRequestFocusEnabled(true);
221                            go2.requestFocus();
222                            go2.setToolTipText(SymbolicProgBundle.getMessage("TipClickToOpen")); // NOI18N
223                            decoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(selectedDecoderType());
224                            setUpRosterPanel();
225                        } else {
226                            decoderFile = null;
227                            // decoder not selected - require one
228                            go2.setEnabled(false);
229                            go2.setToolTipText(SymbolicProgBundle.getMessage("TipSelectLoco")); // NOI18N
230                            setUpRosterPanel();
231                        }
232                    };
233                    dTree.addTreeSelectionListener(dListener);
234                    return pan;
235                }
236
237                @Override
238                protected void selectDecoder(int mfgID, int modelID, int productID) {
239                    log.debug("selectDecoder");
240                    //On selecting a new decoder start a fresh with a new roster entry
241                    super.selectDecoder(mfgID, modelID, productID);
242                    findDecoderAddress();
243                }
244
245                @Override
246                protected JPanel createProgrammerSelection() {
247                    log.debug("createProgrammerSelection");
248
249                    JPanel pane3a = new JPanel();
250                    pane3a.setLayout(new BoxLayout(pane3a, BoxLayout.Y_AXIS));
251
252                    go2 = new JButton(Bundle.getMessage("OpenProgrammer")); // NOI18N
253                    go2.getAccessibleContext().setAccessibleName(Bundle.getMessage("OpenProgrammer"));
254                    go2.addActionListener((ActionEvent e1) -> {
255                        log.debug("Open programmer pressed"); // NOI18N
256                        openButton();
257                        // close this window to prevent having
258                        // two windows processing at the same time
259                        //
260                        // Alternately, could have just cleared out a
261                        // bunch of stuff to force starting over, but
262                        // that seems to be an even more confusing
263                        // user experience.
264                        log.debug("Closing f {}", f);
265                        WindowEvent wev = new WindowEvent(f, WindowEvent.WINDOW_CLOSING);
266                        Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
267                    });
268                    go2.setAlignmentX(JLabel.RIGHT_ALIGNMENT);
269                    go2.setEnabled(false);
270                    go2.setToolTipText(SymbolicProgBundle.getMessage("TipSelectLoco")); // NOI18N
271                    bottomPanel.add(go2, BorderLayout.EAST);
272
273                    return pane3a;
274                }
275            };
276
277            // load primary frame
278            JPanel topPanel = new JPanel();
279            topPanel.add(modePane);
280            topPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
281            f.getContentPane().add(topPanel, BorderLayout.NORTH);
282            //f.getContentPane().add(modePane);
283            //f.getContentPane().add(new JSeparator(SwingConstants.HORIZONTAL));
284
285            combinedLocoSelTree.setAlignmentX(JLabel.CENTER_ALIGNMENT);
286            f.getContentPane().add(combinedLocoSelTree, BorderLayout.CENTER);
287
288            //f.getContentPane().add(new JSeparator(SwingConstants.HORIZONTAL));
289            //basicRoster.setEnabled(false);
290            statusLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
291            bottomPanel.add(statusLabel, BorderLayout.SOUTH);
292            f.getContentPane().add(bottomPanel, BorderLayout.SOUTH);
293
294            f.pack();
295            log.debug("Tab-Programmer setup created"); // NOI18N
296        } else {
297            re = null;
298            combinedLocoSelTree.resetSelections();
299        }
300        f.setVisible(true);
301    }
302
303    String lastSelectedProgrammer = this.getClass().getName() + ".SelectedProgrammer"; // NOI18N
304
305    // never invoked, because we overrode actionPerformed above
306    @Override
307    public JmriPanel makePanel() {
308        throw new IllegalArgumentException("Should not be invoked"); // NOI18N
309    }
310
311    JTextField rosterIdField = new JTextField(20);
312    JTextField rosterAddressField = new JTextField(10);
313
314    RosterEntry re;
315
316    int teststatus = 0;
317
318    synchronized void findDecoderAddress() {
319        teststatus = 1;
320        readCV("29");
321    }
322
323    DecoderFile decoderFile;
324    boolean shortAddr = false;
325    int cv29 = 0;
326    int cv17 = -1;
327    int cv18 = -1;
328    int cv19 = 0;
329    int cv1 = 0;
330    int longAddress;
331    String address = "3";
332
333    @Override
334    synchronized public void programmingOpReply(int value, int status) {
335        switch (teststatus) {
336            case 1:
337                teststatus = 2;
338                cv29 = value;
339                readCV("1");
340                break;
341            case 2:
342                teststatus = 3;
343                cv1 = value;
344                readCV("17");
345                break;
346            case 3:
347                teststatus = 4;
348                cv17 = value;
349                readCV("18");
350                break;
351            case 4:
352                teststatus = 5;
353                cv18 = value;
354                readCV("19");
355                break;
356            case 5:
357                cv19 = value;
358                finishRead();
359                break;
360            default:
361                log.error("unknown test state {}", teststatus);
362                break;
363        }
364    }
365
366    synchronized void finishRead() {
367        if ((cv29 & 0x20) == 0) {
368            shortAddr = true;
369            address = "" + cv1;
370        }
371        if (cv17 != -1 || cv18 != -1) {
372            longAddress = (cv17 & 0x3f) * 256 + cv18;
373            address = "" + longAddress;
374        }
375        if (progPane != null) {
376            progPane.setVariableValue("Short Address", cv1); // NOI18N
377            progPane.setVariableValue("Long Address", longAddress); // NOI18N
378            progPane.setCVValue("29", cv29);
379            progPane.setCVValue("19", cv19);
380        }
381    }
382
383    protected void readCV(String cv) {
384        Programmer p = InstanceManager.getDefault(GlobalProgrammerManager.class).getGlobalProgrammer();
385        if (p == null) {
386            //statusUpdate("No programmer connected");
387        } else {
388            try {
389                p.readCV(cv, this);
390            } catch (ProgrammerException ex) {
391                //statusUpdate(""+ex);
392            }
393        }
394    }
395    JPanel rosterPanel = null;//new JPanel();
396    Programmer mProgrammer;
397    CvTableModel cvModel = null;
398    VariableTableModel variableModel;
399    DccAddressPanel dccAddressPanel;
400    Element modelElem = null;
401    ThisProgPane progPane = null;
402
403    synchronized void setUpRosterPanel() {
404        re = null;
405        if (rosterPanel == null) {
406            rosterPanel = new JPanel();
407            rosterPanel.setLayout(new BorderLayout());
408            JPanel p = new JPanel();
409            p.add(new JLabel(Bundle.getMessage("RosterId"))); // NOI18N
410            p.add(rosterIdField);
411            rosterPanel.add(p, BorderLayout.NORTH);
412            rosterIdField.setText(SymbolicProgBundle.getMessage("LabelNewDecoder")); // NOI18N
413            saveBasicRoster = new JButton(Bundle.getMessage("Save")); // NOI18N
414            saveBasicRoster.addActionListener((ActionEvent e) -> {
415                try {
416                    log.debug("saveBasicRoster button pressed, calls saveRosterEntry");
417                    saveRosterEntry();
418                } catch (JmriException ex) {
419                    // user has been informed within saveRosterEntry(), so ignore
420                }
421            });
422            TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
423            border.setTitle(Bundle.getMessage("CreateBasicRosterEntry")); // NOI18N
424            rosterPanel.setBorder(border);
425            rosterPanel.setVisible(false);
426            f.getContentPane().add(rosterPanel, BorderLayout.EAST);
427        } else {
428            rosterIdField.setText(SymbolicProgBundle.getMessage("LabelNewDecoder")); // NOI18N
429        }
430        if (progPane != null) {
431            progPane.dispose();
432            rosterPanel.remove(progPane);
433            progPane = null;
434            rosterPanel.revalidate();
435            f.getContentPane().repaint();
436            f.repaint();
437            f.pack();
438        }
439        if (InstanceManager.getNullableDefault(GlobalProgrammerManager.class) != null
440                && InstanceManager.getDefault(GlobalProgrammerManager.class).isGlobalProgrammerAvailable()) {
441            this.mProgrammer = InstanceManager.getDefault(GlobalProgrammerManager.class).getGlobalProgrammer();
442        }
443
444        cvModel = new CvTableModel(statusLabel, mProgrammer);
445
446        variableModel = new VariableTableModel(statusLabel, new String[]{"Name", "Value"}, cvModel);
447        if (decoderFile != null) {
448            Element decoderRoot;
449            try {
450                decoderRoot = decoderFile.rootFromName(DecoderFile.fileLocation + decoderFile.getFileName());
451            } catch (JDOMException | IOException e) {
452                log.error("Exception while loading decoder XML file: {}", decoderFile.getFileName(), e);
453                return;
454            } // NOI18N
455            modelElem = decoderFile.getModelElement();
456            decoderFile.loadVariableModel(decoderRoot.getChild("decoder"), variableModel); // NOI18N
457            rosterPanel.setVisible(true);
458        } else {
459            rosterPanel.setVisible(false);
460            return;
461        }
462        Element programmerRoot;
463        XmlFile pf = new XmlFile() {
464        };  // XmlFile is abstract
465
466        PropertyChangeListener dccNews = (PropertyChangeEvent e) -> updateDccAddress();
467        primaryAddr = variableModel.findVar("Short Address"); // NOI18N
468
469        if (primaryAddr == null) {
470            log.debug("DCC Address monitor didn't find a Short Address variable"); // NOI18N
471        } else {
472            primaryAddr.addPropertyChangeListener(dccNews);
473        }
474        extendAddr = variableModel.findVar("Long Address"); // NOI18N
475        if (extendAddr == null) {
476            log.debug("DCC Address monitor didn't find a Long Address variable"); // NOI18N
477        } else {
478            extendAddr.addPropertyChangeListener(dccNews);
479        }
480        addMode = (EnumVariableValue) variableModel.findVar("Address Format"); // NOI18N
481        if (addMode == null) {
482            log.debug("DCC Address monitor didn't find an Address Format variable"); // NOI18N
483        } else {
484            addMode.addPropertyChangeListener(dccNews);
485        }
486
487        try {
488            programmerRoot = pf.rootFromName("programmers" + File.separator + "Basic.xml"); // NOI18N
489            Element base;
490            if ((base = programmerRoot.getChild("programmer")) == null) { // NOI18N
491                log.error("xml file top element is not programmer"); // NOI18N
492                return;
493            }
494            // for all "pane" elements in the programmer
495            List<Element> paneList = base.getChildren("pane"); // NOI18N
496            log.debug("will process {} pane definitions", paneList.size()); // NOI18N
497            String name = LocaleSelector.getAttribute(paneList.get(0), "name");
498            progPane = new ThisProgPane(this, name, paneList.get(0), cvModel, variableModel, modelElem);
499
500            progPane.setVariableValue("Short Address", cv1); // NOI18N
501            progPane.setVariableValue("Long Address", longAddress); // NOI18N
502            progPane.setCVValue("29", cv29); // NOI18N
503            progPane.setCVValue("19", cv19); // NOI18N
504            rosterPanel.add(progPane, BorderLayout.CENTER);
505            rosterPanel.revalidate();
506            rosterPanel.setVisible(true);
507            f.getContentPane().repaint();
508            f.repaint();
509            f.pack();
510        } catch (JDOMException | IOException e) {
511            log.error("exception reading programmer file: ", e); // NOI18N
512        }
513    }
514
515    boolean longMode = false;
516    String newAddr = null;
517
518    void updateDccAddress() {
519
520        // wrapped in isDebugEnabled test to prevent overhead of assembling message
521        if (log.isDebugEnabled()) {
522            log.debug("updateDccAddress: short {} long {} mode {}",
523                    (primaryAddr == null ? "<null>" : primaryAddr.getValueString()),
524                    (extendAddr == null ? "<null>" : extendAddr.getValueString()),
525                    (addMode == null ? "<null>" : addMode.getValueString()));
526        }
527        new DccAddressVarHandler(primaryAddr, extendAddr, addMode) {
528            @Override
529            protected void doPrimary() {
530                longMode = false;
531                if (primaryAddr != null && !primaryAddr.getValueString().equals("")) {
532                    newAddr = primaryAddr.getValueString();
533                }
534            }
535
536            @Override
537            protected void doExtended() {
538                // long address
539                if (!extendAddr.getValueString().equals("")) {
540                    longMode = true;
541                    newAddr = extendAddr.getValueString();
542                }
543            }
544        };
545        // update if needed
546        if (newAddr != null) {
547            synchronized (this) {
548                // store DCC address, type
549                address = newAddr;
550                shortAddr = !longMode;
551            }
552        }
553    }
554
555    JButton saveBasicRoster;
556
557    /**
558     *
559     * @return true if the value in the id JTextField is a duplicate of some
560     *         other RosterEntry in the roster
561     */
562    boolean checkDuplicate() {
563        // check its not a duplicate
564        List<RosterEntry> l = Roster.getDefault().matchingList(null, null, null, null, null, null, rosterIdField.getText());
565        boolean oops = false;
566        for (RosterEntry rosterEntry : l) {
567            if (re != rosterEntry) {
568                oops = true;
569                break;
570            }
571        }
572        return oops;
573    }
574
575    void saveRosterEntry() throws JmriException {
576        log.debug("saveRosterEntry");
577        if (rosterIdField.getText().equals(SymbolicProgBundle.getMessage("LabelNewDecoder"))) { // NOI18N
578            synchronized (this) {
579                JmriJOptionPane.showMessageDialog(progPane, SymbolicProgBundle.getMessage("PromptFillInID")); // NOI18N
580            }
581            throw new JmriException("No Roster ID"); // NOI18N
582        }
583        if (checkDuplicate()) {
584            synchronized (this) {
585                JmriJOptionPane.showMessageDialog(progPane, SymbolicProgBundle.getMessage("ErrorDuplicateID")); // NOI18N
586            }
587            throw new JmriException("Duplicate ID"); // NOI18N
588        }
589
590        if (re == null) {
591            log.debug("re null, creating RosterEntry");
592            re = new RosterEntry();
593            re.setDecoderFamily(decoderFile.getFamily());
594            re.setDecoderModel(decoderFile.getModel());
595            re.setId(rosterIdField.getText());
596            re.setDeveloperID(decoderFile.getDeveloperID());
597            re.setManufacturerID(decoderFile.getManufacturerID());
598            re.setProductID(decoderFile.getProductID());
599            Roster.getDefault().addEntry(re);
600        }
601
602        updateDccAddress();
603
604        // if there isn't a filename, store using the id
605        re.ensureFilenameExists();
606        String filename = re.getFileName();
607
608        // create the RosterEntry to its file
609        log.debug("setting DCC address {} {}", address, shortAddr);
610        synchronized (this) {
611            re.setDccAddress("" + address);  // NOI18N
612            re.setLongAddress(!shortAddr);
613            re.writeFile(cvModel, variableModel);
614
615            // mark this as a success
616            variableModel.setFileDirty(false);
617        }
618        // and store an updated roster file
619        FileUtil.createDirectory(FileUtil.getUserFilesPath());
620        Roster.getDefault().writeRoster();
621
622        // show OK status
623        statusLabel.setText(MessageFormat.format(
624                SymbolicProgBundle.getMessage("StateSaveOK"), // NOI18N
625                filename));
626    }
627
628    // hold refs to variables to check dccAddress
629    VariableValue primaryAddr = null;
630    VariableValue extendAddr = null;
631    EnumVariableValue addMode = null;
632
633    @Override
634    public boolean isBusy() {
635        return false;
636    }
637
638    @Override
639    public void paneFinished() {
640    }
641
642    /**
643     * Enable the read/write buttons.
644     * <p>
645     * In addition, if a programming mode pane is present, its "set" button is
646     * enabled.
647     *
648     * @param enable Are reads possible? If false, so not enable the read
649     *               buttons.
650     */
651    @Override
652    public void enableButtons(boolean enable) {
653    }
654
655    @Override
656    public void prepGlassPane(AbstractButton activeButton) {
657    }
658
659    @Override
660    synchronized public BusyGlassPane getBusyGlassPane() {
661        return new BusyGlassPane(new ArrayList<>(),
662                new ArrayList<>(),
663                rosterPanel, f);
664    }
665
666    class ThisProgPane extends PaneProgPane {
667
668        public ThisProgPane(PaneContainer parent, String name, Element pane, CvTableModel cvModel, VariableTableModel varModel, Element modelElem) {
669            super(parent, name, pane, cvModel, varModel, modelElem, re);
670            bottom.remove(readChangesButton);
671            bottom.remove(writeChangesButton);
672            writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonWrite")); // NOI18N
673            readAllButton.setText(SymbolicProgBundle.getMessage("ButtonRead")); // NOI18N
674            bottom.add(saveBasicRoster);
675            bottom.revalidate();
676            readAllButton.removeItemListener(l2);
677            readAllButton.addItemListener(l2 = (ItemEvent e) -> {
678                if (e.getStateChange() == ItemEvent.SELECTED) {
679                    readAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopReadSheet")); // NOI18N
680                    if (!container.isBusy()) {
681                        prepReadPane(false);
682                        prepGlassPane(readAllButton);
683                        container.getBusyGlassPane().setVisible(true);
684                        readPaneAll();
685                    }
686                } else {
687                    stopProgramming();
688                    readAllButton.setText(SymbolicProgBundle.getMessage("ButtonRead")); // NOI18N
689                    if (container.isBusy()) {
690                        readAllButton.setEnabled(false);
691                    }
692                }
693            });
694            writeAllButton.removeItemListener(l4);
695            writeAllButton.addItemListener(l4 = (ItemEvent e) -> {
696                if (e.getStateChange() == ItemEvent.SELECTED) {
697                    writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopWriteSheet")); // NOI18N
698                    if (!container.isBusy()) {
699                        prepWritePane(false);
700                        prepGlassPane(writeAllButton);
701                        container.getBusyGlassPane().setVisible(true);
702                        writePaneAll();
703                    }
704                } else {
705                    stopProgramming();
706                    writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonWrite")); // NOI18N
707                    if (container.isBusy()) {
708                        writeAllButton.setEnabled(false);
709                    }
710                }
711            });
712            if (_cvModel.getProgrammer() == null) {
713                bottom.remove(readAllButton);
714                bottom.remove(writeAllButton);
715                bottom.revalidate();
716                add(bottom);
717            }
718        }
719
720        public void setCVValue(String cv, int value) {
721            if (_cvModel.getCvByNumber(cv) != null) {
722                (_cvModel.getCvByNumber(cv)).setValue(value);
723                (_cvModel.getCvByNumber(cv)).setState(AbstractValue.ValueState.READ);
724            }
725        }
726
727        public void setVariableValue(String variable, int value) {
728            if (_varModel.findVar(variable) != null) {
729                _varModel.findVar(variable).setIntValue(value);
730                _varModel.findVar(variable).setState(AbstractValue.ValueState.READ);
731            }
732        }
733
734        @Override
735        public void dispose() {
736            bottom.remove(saveBasicRoster);
737            super.dispose();
738        }
739
740    }
741
742    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PaneProgDp3Action.class);
743
744}