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.setProgrammingModes(decoderFile.getProgrammingModes());
181                        re.setId(SymbolicProgBundle.getMessage("LabelNewDecoder")); // NOI18N
182                        // note that we're leaving the filename null
183                        // add the new roster entry to the in-memory roster
184                        Roster.getDefault().addEntry(re);
185                    } else {
186                        try {
187                            saveRosterEntry();
188                        } catch (JmriException ex) {
189                            log.warn("Exception while saving roster entry", ex); // NOI18N
190                            return;
191                        }
192                    }
193                    // create a dummy RosterEntry with the decoder info
194                    startProgrammer(decoderFile, re, ""); // no programmer name in this case
195                    //Set our roster entry back to null so that a fresh roster entry can be created if needed
196                    re = null;
197                }
198
199                @Override
200                protected JPanel layoutRosterSelection() {
201                    log.debug("layoutRosterSelection");
202                    return null;
203                }
204
205                @Override
206                protected JPanel layoutDecoderSelection() {
207                    log.debug("layoutDecoderSelection");
208                    JPanel pan = super.layoutDecoderSelection();
209                    dTree.removeTreeSelectionListener(dListener);
210                    dListener = (TreeSelectionEvent e1) -> {
211                        if (!dTree.isSelectionEmpty() && dTree.getSelectionPath() != null
212                                && // check that this isn't just a model
213                                ((TreeNode) dTree.getSelectionPath().getLastPathComponent()).isLeaf()
214                                && // can't be just a mfg, has to be at least a family
215                                dTree.getSelectionPath().getPathCount() > 2
216                                && // can't be a multiple decoder selection
217                                dTree.getSelectionCount() < 2) {
218                            log.debug("Selection event with {}", dTree.getSelectionPath());
219                            //if (locoBox != null) locoBox.setSelectedIndex(0);
220                            go2.setEnabled(true);
221                            go2.setRequestFocusEnabled(true);
222                            go2.requestFocus();
223                            go2.setToolTipText(SymbolicProgBundle.getMessage("TipClickToOpen")); // NOI18N
224                            decoderFile = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(selectedDecoderType());
225                            setUpRosterPanel();
226                        } else {
227                            decoderFile = null;
228                            // decoder not selected - require one
229                            go2.setEnabled(false);
230                            go2.setToolTipText(SymbolicProgBundle.getMessage("TipSelectLoco")); // NOI18N
231                            setUpRosterPanel();
232                        }
233                    };
234                    dTree.addTreeSelectionListener(dListener);
235                    return pan;
236                }
237
238                @Override
239                protected void selectDecoder(int mfgID, int modelID, int productID) {
240                    log.debug("selectDecoder");
241                    //On selecting a new decoder start a fresh with a new roster entry
242                    super.selectDecoder(mfgID, modelID, productID);
243                    findDecoderAddress();
244                }
245
246                @Override
247                protected JPanel createProgrammerSelection() {
248                    log.debug("createProgrammerSelection");
249
250                    JPanel pane3a = new JPanel();
251                    pane3a.setLayout(new BoxLayout(pane3a, BoxLayout.Y_AXIS));
252
253                    go2 = new JButton(Bundle.getMessage("OpenProgrammer")); // NOI18N
254                    go2.getAccessibleContext().setAccessibleName(Bundle.getMessage("OpenProgrammer"));
255                    go2.addActionListener((ActionEvent e1) -> {
256                        log.debug("Open programmer pressed"); // NOI18N
257                        openButton();
258                        // close this window to prevent having
259                        // two windows processing at the same time
260                        //
261                        // Alternately, could have just cleared out a
262                        // bunch of stuff to force starting over, but
263                        // that seems to be an even more confusing
264                        // user experience.
265                        log.debug("Closing f {}", f);
266                        WindowEvent wev = new WindowEvent(f, WindowEvent.WINDOW_CLOSING);
267                        Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
268                    });
269                    go2.setAlignmentX(JLabel.RIGHT_ALIGNMENT);
270                    go2.setEnabled(false);
271                    go2.setToolTipText(SymbolicProgBundle.getMessage("TipSelectLoco")); // NOI18N
272                    bottomPanel.add(go2, BorderLayout.EAST);
273
274                    return pane3a;
275                }
276            };
277
278            // load primary frame
279            JPanel topPanel = new JPanel();
280            topPanel.add(modePane);
281            topPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
282            f.getContentPane().add(topPanel, BorderLayout.NORTH);
283            //f.getContentPane().add(modePane);
284            //f.getContentPane().add(new JSeparator(SwingConstants.HORIZONTAL));
285
286            combinedLocoSelTree.setAlignmentX(JLabel.CENTER_ALIGNMENT);
287            f.getContentPane().add(combinedLocoSelTree, BorderLayout.CENTER);
288
289            //f.getContentPane().add(new JSeparator(SwingConstants.HORIZONTAL));
290            //basicRoster.setEnabled(false);
291            statusLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
292            bottomPanel.add(statusLabel, BorderLayout.SOUTH);
293            f.getContentPane().add(bottomPanel, BorderLayout.SOUTH);
294
295            f.pack();
296            log.debug("Tab-Programmer setup created"); // NOI18N
297        } else {
298            re = null;
299            combinedLocoSelTree.resetSelections();
300        }
301        f.setVisible(true);
302    }
303
304    String lastSelectedProgrammer = this.getClass().getName() + ".SelectedProgrammer"; // NOI18N
305
306    // never invoked, because we overrode actionPerformed above
307    @Override
308    public JmriPanel makePanel() {
309        throw new IllegalArgumentException("Should not be invoked"); // NOI18N
310    }
311
312    JTextField rosterIdField = new JTextField(20);
313    JTextField rosterAddressField = new JTextField(10);
314
315    RosterEntry re;
316
317    int teststatus = 0;
318
319    synchronized void findDecoderAddress() {
320        teststatus = 1;
321        readCV("29");
322    }
323
324    DecoderFile decoderFile;
325    boolean shortAddr = false;
326    int cv29 = 0;
327    int cv17 = -1;
328    int cv18 = -1;
329    int cv19 = 0;
330    int cv1 = 0;
331    int longAddress;
332    String address = "3";
333
334    @Override
335    synchronized public void programmingOpReply(int value, int status) {
336        switch (teststatus) {
337            case 1:
338                teststatus = 2;
339                cv29 = value;
340                readCV("1");
341                break;
342            case 2:
343                teststatus = 3;
344                cv1 = value;
345                readCV("17");
346                break;
347            case 3:
348                teststatus = 4;
349                cv17 = value;
350                readCV("18");
351                break;
352            case 4:
353                teststatus = 5;
354                cv18 = value;
355                readCV("19");
356                break;
357            case 5:
358                cv19 = value;
359                finishRead();
360                break;
361            default:
362                log.error("unknown test state {}", teststatus);
363                break;
364        }
365    }
366
367    synchronized void finishRead() {
368        if ((cv29 & 0x20) == 0) {
369            shortAddr = true;
370            address = "" + cv1;
371        }
372        if (cv17 != -1 || cv18 != -1) {
373            longAddress = (cv17 & 0x3f) * 256 + cv18;
374            address = "" + longAddress;
375        }
376        if (progPane != null) {
377            progPane.setVariableValue("Short Address", cv1); // NOI18N
378            progPane.setVariableValue("Long Address", longAddress); // NOI18N
379            progPane.setCVValue("29", cv29);
380            progPane.setCVValue("19", cv19);
381        }
382    }
383
384    protected void readCV(String cv) {
385        Programmer p = InstanceManager.getDefault(GlobalProgrammerManager.class).getGlobalProgrammer();
386        if (p == null) {
387            //statusUpdate("No programmer connected");
388        } else {
389            try {
390                p.readCV(cv, this);
391            } catch (ProgrammerException ex) {
392                //statusUpdate(""+ex);
393            }
394        }
395    }
396    JPanel rosterPanel = null;//new JPanel();
397    Programmer mProgrammer;
398    CvTableModel cvModel = null;
399    VariableTableModel variableModel;
400    DccAddressPanel dccAddressPanel;
401    Element modelElem = null;
402    ThisProgPane progPane = null;
403
404    synchronized void setUpRosterPanel() {
405        re = null;
406        if (rosterPanel == null) {
407            rosterPanel = new JPanel();
408            rosterPanel.setLayout(new BorderLayout());
409            JPanel p = new JPanel();
410            p.add(new JLabel(Bundle.getMessage("RosterId"))); // NOI18N
411            p.add(rosterIdField);
412            rosterPanel.add(p, BorderLayout.NORTH);
413            rosterIdField.setText(SymbolicProgBundle.getMessage("LabelNewDecoder")); // NOI18N
414            saveBasicRoster = new JButton(Bundle.getMessage("Save")); // NOI18N
415            saveBasicRoster.addActionListener((ActionEvent e) -> {
416                try {
417                    log.debug("saveBasicRoster button pressed, calls saveRosterEntry");
418                    saveRosterEntry();
419                } catch (JmriException ex) {
420                    // user has been informed within saveRosterEntry(), so ignore
421                }
422            });
423            TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
424            border.setTitle(Bundle.getMessage("CreateBasicRosterEntry")); // NOI18N
425            rosterPanel.setBorder(border);
426            rosterPanel.setVisible(false);
427            f.getContentPane().add(rosterPanel, BorderLayout.EAST);
428        } else {
429            rosterIdField.setText(SymbolicProgBundle.getMessage("LabelNewDecoder")); // NOI18N
430        }
431        if (progPane != null) {
432            progPane.dispose();
433            rosterPanel.remove(progPane);
434            progPane = null;
435            rosterPanel.revalidate();
436            f.getContentPane().repaint();
437            f.repaint();
438            f.pack();
439        }
440        if (InstanceManager.getNullableDefault(GlobalProgrammerManager.class) != null
441                && InstanceManager.getDefault(GlobalProgrammerManager.class).isGlobalProgrammerAvailable()) {
442            this.mProgrammer = InstanceManager.getDefault(GlobalProgrammerManager.class).getGlobalProgrammer();
443        }
444
445        cvModel = new CvTableModel(statusLabel, mProgrammer);
446
447        variableModel = new VariableTableModel(statusLabel, new String[]{"Name", "Value"}, cvModel);
448        if (decoderFile != null) {
449            Element decoderRoot;
450            try {
451                decoderRoot = decoderFile.rootFromName(DecoderFile.fileLocation + decoderFile.getFileName());
452            } catch (JDOMException | IOException e) {
453                log.error("Exception while loading decoder XML file: {}", decoderFile.getFileName(), e);
454                return;
455            } // NOI18N
456            modelElem = decoderFile.getModelElement();
457            decoderFile.loadVariableModel(decoderRoot.getChild("decoder"), variableModel); // NOI18N
458            rosterPanel.setVisible(true);
459        } else {
460            rosterPanel.setVisible(false);
461            return;
462        }
463        Element programmerRoot;
464        XmlFile pf = new XmlFile() {
465        };  // XmlFile is abstract
466
467        PropertyChangeListener dccNews = (PropertyChangeEvent e) -> updateDccAddress();
468        primaryAddr = variableModel.findVar("Short Address"); // NOI18N
469
470        if (primaryAddr == null) {
471            log.debug("DCC Address monitor didn't find a Short Address variable"); // NOI18N
472        } else {
473            primaryAddr.addPropertyChangeListener(dccNews);
474        }
475        extendAddr = variableModel.findVar("Long Address"); // NOI18N
476        if (extendAddr == null) {
477            log.debug("DCC Address monitor didn't find a Long Address variable"); // NOI18N
478        } else {
479            extendAddr.addPropertyChangeListener(dccNews);
480        }
481        addMode = (EnumVariableValue) variableModel.findVar("Address Format"); // NOI18N
482        if (addMode == null) {
483            log.debug("DCC Address monitor didn't find an Address Format variable"); // NOI18N
484        } else {
485            addMode.addPropertyChangeListener(dccNews);
486        }
487
488        try {
489            programmerRoot = pf.rootFromName("programmers" + File.separator + "Basic.xml"); // NOI18N
490            Element base;
491            if ((base = programmerRoot.getChild("programmer")) == null) { // NOI18N
492                log.error("xml file top element is not programmer"); // NOI18N
493                return;
494            }
495            // for all "pane" elements in the programmer
496            List<Element> paneList = base.getChildren("pane"); // NOI18N
497            log.debug("will process {} pane definitions", paneList.size()); // NOI18N
498            String name = LocaleSelector.getAttribute(paneList.get(0), "name");
499            progPane = new ThisProgPane(this, name, paneList.get(0), cvModel, variableModel, modelElem);
500
501            progPane.setVariableValue("Short Address", cv1); // NOI18N
502            progPane.setVariableValue("Long Address", longAddress); // NOI18N
503            progPane.setCVValue("29", cv29); // NOI18N
504            progPane.setCVValue("19", cv19); // NOI18N
505            rosterPanel.add(progPane, BorderLayout.CENTER);
506            rosterPanel.revalidate();
507            rosterPanel.setVisible(true);
508            f.getContentPane().repaint();
509            f.repaint();
510            f.pack();
511        } catch (JDOMException | IOException e) {
512            log.error("exception reading programmer file: ", e); // NOI18N
513        }
514    }
515
516    boolean longMode = false;
517    String newAddr = null;
518
519    void updateDccAddress() {
520
521        // wrapped in isDebugEnabled test to prevent overhead of assembling message
522        if (log.isDebugEnabled()) {
523            log.debug("updateDccAddress: short {} long {} mode {}",
524                    (primaryAddr == null ? "<null>" : primaryAddr.getValueString()),
525                    (extendAddr == null ? "<null>" : extendAddr.getValueString()),
526                    (addMode == null ? "<null>" : addMode.getValueString()));
527        }
528        new DccAddressVarHandler(primaryAddr, extendAddr, addMode) {
529            @Override
530            protected void doPrimary() {
531                longMode = false;
532                if (primaryAddr != null && !primaryAddr.getValueString().isEmpty()) {
533                    newAddr = primaryAddr.getValueString();
534                }
535            }
536
537            @Override
538            protected void doExtended() {
539                // long address
540                if (!extendAddr.getValueString().isEmpty()) {
541                    longMode = true;
542                    newAddr = extendAddr.getValueString();
543                }
544            }
545        };
546        // update if needed
547        if (newAddr != null) {
548            synchronized (this) {
549                // store DCC address, type
550                address = newAddr;
551                shortAddr = !longMode;
552            }
553        }
554    }
555
556    JButton saveBasicRoster;
557
558    /**
559     *
560     * @return true if the value in the id JTextField is a duplicate of some
561     *         other RosterEntry in the roster
562     */
563    boolean checkDuplicate() {
564        // check it's not a duplicate
565        List<RosterEntry> l = Roster.getDefault().matchingList(null, null, null, null, null, null, rosterIdField.getText());
566        boolean oops = false;
567        for (RosterEntry rosterEntry : l) {
568            if (re != rosterEntry) {
569                oops = true;
570                break;
571            }
572        }
573        return oops;
574    }
575
576    void saveRosterEntry() throws JmriException {
577        log.debug("saveRosterEntry");
578        if (rosterIdField.getText().equals(SymbolicProgBundle.getMessage("LabelNewDecoder"))) { // NOI18N
579            synchronized (this) {
580                JmriJOptionPane.showMessageDialog(progPane, SymbolicProgBundle.getMessage("PromptFillInID")); // NOI18N
581            }
582            throw new JmriException("No Roster ID"); // NOI18N
583        }
584        if (checkDuplicate()) {
585            synchronized (this) {
586                JmriJOptionPane.showMessageDialog(progPane, SymbolicProgBundle.getMessage("ErrorDuplicateID")); // NOI18N
587            }
588            throw new JmriException("Duplicate ID"); // NOI18N
589        }
590
591        if (re == null) {
592            log.debug("re null, creating RosterEntry");
593            re = new RosterEntry();
594            re.setDecoderFamily(decoderFile.getFamily());
595            re.setDecoderModel(decoderFile.getModel());
596            re.setId(rosterIdField.getText());
597            re.setDeveloperID(decoderFile.getDeveloperID());
598            re.setManufacturerID(decoderFile.getManufacturerID());
599            re.setProductID(decoderFile.getProductID());
600            re.setProgrammingModes(decoderFile.getProgrammingModes());
601            Roster.getDefault().addEntry(re);
602        }
603
604        updateDccAddress();
605
606        // if there isn't a filename, store using the id
607        re.ensureFilenameExists();
608        String filename = re.getFileName();
609
610        // create the RosterEntry to its file
611        log.debug("setting DCC address {} {}", address, shortAddr);
612        synchronized (this) {
613            re.setDccAddress(address);  // NOI18N
614            re.setLongAddress(!shortAddr);
615            re.writeFile(cvModel, variableModel);
616
617            // mark this as a success
618            variableModel.setFileDirty(false);
619        }
620        // and store an updated roster file
621        FileUtil.createDirectory(FileUtil.getUserFilesPath());
622        Roster.getDefault().writeRoster();
623
624        // show OK status
625        statusLabel.setText(MessageFormat.format(
626                SymbolicProgBundle.getMessage("StateSaveOK"), // NOI18N
627                filename));
628    }
629
630    // hold refs to variables to check dccAddress
631    VariableValue primaryAddr = null;
632    VariableValue extendAddr = null;
633    EnumVariableValue addMode = null;
634
635    @Override
636    public boolean isBusy() {
637        return false;
638    }
639
640    @Override
641    public void paneFinished() {
642    }
643
644    /**
645     * Enable the read/write buttons.
646     * <p>
647     * In addition, if a programming mode pane is present, its "set" button is
648     * enabled.
649     *
650     * @param enable Are reads possible? If false, so not enable the read
651     *               buttons.
652     */
653    @Override
654    public void enableButtons(boolean enable) {
655    }
656
657    @Override
658    public void prepGlassPane(AbstractButton activeButton) {
659    }
660
661    @Override
662    synchronized public BusyGlassPane getBusyGlassPane() {
663        return new BusyGlassPane(new ArrayList<>(),
664                new ArrayList<>(),
665                rosterPanel, f);
666    }
667
668    class ThisProgPane extends PaneProgPane {
669
670        public ThisProgPane(PaneContainer parent, String name, Element pane, CvTableModel cvModel, VariableTableModel varModel, Element modelElem) {
671            super(parent, name, pane, cvModel, varModel, modelElem, re);
672            bottom.remove(readChangesButton);
673            bottom.remove(writeChangesButton);
674            writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonWrite")); // NOI18N
675            readAllButton.setText(SymbolicProgBundle.getMessage("ButtonRead")); // NOI18N
676            bottom.add(saveBasicRoster);
677            bottom.revalidate();
678            readAllButton.removeItemListener(l2);
679            readAllButton.addItemListener(l2 = (ItemEvent e) -> {
680                if (e.getStateChange() == ItemEvent.SELECTED) {
681                    readAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopReadSheet")); // NOI18N
682                    if (!container.isBusy()) {
683                        prepReadPane(false);
684                        prepGlassPane(readAllButton);
685                        container.getBusyGlassPane().setVisible(true);
686                        readPaneAll();
687                    }
688                } else {
689                    stopProgramming();
690                    readAllButton.setText(SymbolicProgBundle.getMessage("ButtonRead")); // NOI18N
691                    if (container.isBusy()) {
692                        readAllButton.setEnabled(false);
693                    }
694                }
695            });
696            writeAllButton.removeItemListener(l4);
697            writeAllButton.addItemListener(l4 = (ItemEvent e) -> {
698                if (e.getStateChange() == ItemEvent.SELECTED) {
699                    writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopWriteSheet")); // NOI18N
700                    if (!container.isBusy()) {
701                        prepWritePane(false);
702                        prepGlassPane(writeAllButton);
703                        container.getBusyGlassPane().setVisible(true);
704                        writePaneAll();
705                    }
706                } else {
707                    stopProgramming();
708                    writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonWrite")); // NOI18N
709                    if (container.isBusy()) {
710                        writeAllButton.setEnabled(false);
711                    }
712                }
713            });
714            if (_cvModel.getProgrammer() == null) {
715                bottom.remove(readAllButton);
716                bottom.remove(writeAllButton);
717                bottom.revalidate();
718                add(bottom);
719            }
720        }
721
722        public void setCVValue(String cv, int value) {
723            if (_cvModel.getCvByNumber(cv) != null) {
724                (_cvModel.getCvByNumber(cv)).setValue(value);
725                (_cvModel.getCvByNumber(cv)).setState(AbstractValue.ValueState.READ);
726            }
727        }
728
729        public void setVariableValue(String variable, int value) {
730            if (_varModel.findVar(variable) != null) {
731                _varModel.findVar(variable).setIntValue(value);
732                _varModel.findVar(variable).setState(AbstractValue.ValueState.READ);
733            }
734        }
735
736        @Override
737        public void dispose() {
738            bottom.remove(saveBasicRoster);
739            super.dispose();
740        }
741
742    }
743
744    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PaneProgDp3Action.class);
745
746}