001package jmri.jmrit.roster.swing;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Component;
006import java.awt.Cursor;
007import java.awt.Dimension;
008import java.awt.FlowLayout;
009import java.awt.GridBagConstraints;
010import java.awt.GridBagLayout;
011import java.awt.GridLayout;
012import java.awt.Insets;
013import java.awt.datatransfer.Transferable;
014import java.awt.event.ActionEvent;
015import java.awt.event.ActionListener;
016import java.awt.event.WindowEvent;
017import java.awt.image.BufferedImage;
018
019import java.beans.PropertyChangeEvent;
020import java.beans.PropertyChangeListener;
021import java.io.File;
022import java.io.IOException;
023import java.text.DateFormat;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.List;
027
028import javax.annotation.CheckForNull;
029import javax.imageio.ImageIO;
030import javax.swing.*;
031import javax.swing.event.ListSelectionEvent;
032
033import jmri.AddressedProgrammerManager;
034import jmri.GlobalProgrammerManager;
035import jmri.InstanceManager;
036import jmri.Programmer;
037import jmri.ShutDownManager;
038import jmri.UserPreferencesManager;
039import jmri.jmrit.decoderdefn.DecoderFile;
040import jmri.jmrit.decoderdefn.DecoderIndexFile;
041import jmri.jmrit.progsupport.ProgModeSelector;
042import jmri.jmrit.progsupport.ProgServiceModeComboBox;
043import jmri.jmrit.roster.CopyRosterItemAction;
044import jmri.jmrit.roster.DeleteRosterItemAction;
045import jmri.jmrit.roster.ExportRosterItemAction;
046import jmri.jmrit.roster.IdentifyLoco;
047import jmri.jmrit.roster.PrintRosterEntry;
048import jmri.jmrit.roster.Roster;
049import jmri.jmrit.roster.RosterEntry;
050import jmri.jmrit.roster.RosterEntrySelector;
051import jmri.jmrit.roster.rostergroup.RosterGroupSelector;
052import jmri.jmrit.symbolicprog.ProgrammerConfigManager;
053import jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgFrame;
054import jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame;
055import jmri.jmrit.symbolicprog.tabbedframe.PaneServiceProgFrame;
056import jmri.jmrit.throttle.*;
057import jmri.jmrix.ActiveSystemsMenu;
058import jmri.jmrix.ConnectionConfig;
059import jmri.jmrix.ConnectionConfigManager;
060import jmri.jmrix.ConnectionStatus;
061import jmri.profile.Profile;
062import jmri.profile.ProfileManager;
063import jmri.swing.JTablePersistenceManager;
064import jmri.swing.RowSorterUtil;
065import jmri.util.FileUtil;
066import jmri.util.HelpUtil;
067import jmri.util.WindowMenu;
068import jmri.util.datatransfer.RosterEntrySelection;
069import jmri.util.swing.JmriAbstractAction;
070import jmri.util.swing.JmriJOptionPane;
071import jmri.util.swing.JmriMouseAdapter;
072import jmri.util.swing.JmriMouseEvent;
073import jmri.util.swing.JmriMouseListener;
074import jmri.util.swing.ResizableImagePanel;
075import jmri.util.swing.WindowInterface;
076import jmri.util.swing.multipane.TwoPaneTBWindow;
077
078/**
079 * A window for Roster management.
080 * <p>
081 * TODO: Several methods are copied from PaneProgFrame and should be refactored
082 * No programmer support yet (dummy object below). Color only covering borders.
083 * No reset toolbar support yet. No glass pane support (See DecoderPro3Panes
084 * class and usage below). Special panes (Roster entry, attributes, graphics)
085 * not included. How do you pick a programmer file? (hardcoded) Initialization
086 * needs partial deferral, too for 1st pane to appear.
087 *
088 * @see jmri.jmrit.symbolicprog.tabbedframe.PaneSet
089 *
090 * @author Bob Jacobsen Copyright (C) 2010, 2016
091 * @author Kevin Dickerson Copyright (C) 2011
092 * @author Randall Wood Copyright (C) 2012
093 */
094public class RosterFrame extends TwoPaneTBWindow implements RosterEntrySelector, RosterGroupSelector {
095
096    static final ArrayList<RosterFrame> frameInstances = new ArrayList<>();
097    protected boolean allowQuit = true;
098    protected String baseTitle = "Roster";
099    protected JmriAbstractAction newWindowAction;
100
101    public RosterFrame() {
102        this(Bundle.getMessage("RosterTitle"));
103    }
104
105    public RosterFrame(String name) {
106        this(name,
107                "xml/config/parts/jmri/jmrit/roster/swing/RosterFrameMenu.xml",
108                "xml/config/parts/jmri/jmrit/roster/swing/RosterFrameToolBar.xml");
109    }
110
111    public RosterFrame(String name, String menubarFile, String toolbarFile) {
112        super(name, menubarFile, toolbarFile);
113        this.allowInFrameServlet = false;
114        this.setBaseTitle(name);
115        this.buildWindow();
116        this.locoSelected(null);
117    }
118
119    final JRadioButtonMenuItem contextEdit = new JRadioButtonMenuItem(Bundle.getMessage("EditOnly"));
120    final JRadioButtonMenuItem contextOps = new JRadioButtonMenuItem(Bundle.getMessage("ProgrammingOnMain"));
121    final JRadioButtonMenuItem contextService = new JRadioButtonMenuItem(Bundle.getMessage("ProgrammingTrack"));
122    final JTextPane dateUpdated = new JTextPane();
123    final JTextPane dccAddress = new JTextPane();
124    final JTextPane decoderFamily = new JTextPane();
125    final JTextPane decoderModel = new JTextPane();
126    final JRadioButton edit = new JRadioButton(Bundle.getMessage("EditOnly"));
127    final JTextPane filename = new JTextPane();
128    JLabel firstHelpLabel;
129    //int firstTimeAddedEntry = 0x00;
130    int groupSplitPaneLocation = 0;
131    RosterGroupsPanel groups;
132    boolean hideGroups = false;
133    boolean hideRosterImage = false;
134    final JTextPane id = new JTextPane();
135    boolean inStartProgrammer = false;
136    ResizableImagePanel locoImage;
137    JTextPane maxSpeed = new JTextPane();
138    final JTextPane mfg = new JTextPane();
139    final ProgModeSelector modePanel = new ProgServiceModeComboBox();
140    final JTextPane model = new JTextPane();
141    final JLabel operationsModeProgrammerLabel = new JLabel();
142    final JRadioButton ops = new JRadioButton(Bundle.getMessage("ProgrammingOnMain"));
143    ConnectionConfig opsModeProCon = null;
144    final JTextPane owner = new JTextPane();
145    UserPreferencesManager prefsMgr;
146    final JButton prog1Button = new JButton(Bundle.getMessage("Program"));
147    final JButton prog2Button = new JButton(Bundle.getMessage("BasicProgrammer"));
148    ActionListener programModeListener;
149
150    // These are the names of the programmer _files_, not what should be displayed to the user
151    String programmer1 = "Comprehensive"; // NOI18N
152    String programmer2 = "Basic"; // NOI18N
153
154    final java.util.ResourceBundle rb = java.util.ResourceBundle.getBundle("apps.AppsBundle");
155    //current selected loco
156    transient RosterEntry re;
157    final JTextPane roadName = new JTextPane();
158    final JTextPane roadNumber = new JTextPane();
159    final JPanel rosterDetailPanel = new JPanel();
160    PropertyChangeListener rosterEntryUpdateListener;
161    JSplitPane rosterGroupSplitPane;
162    final JButton rosterMedia = new JButton(Bundle.getMessage("LabelsAndMedia"));
163    RosterTable rtable;
164    ConnectionConfig serModeProCon = null;
165    final JRadioButton service = new JRadioButton(Bundle.getMessage("ProgrammingTrack"));
166    final JLabel serviceModeProgrammerLabel = new JLabel();
167    final JLabel statusField = new JLabel();
168    final Dimension summaryPaneDim = new Dimension(0, 170);
169    final JButton throttleLabels = new JButton(Bundle.getMessage("ThrottleLabels"));
170    final JButton throttleLaunch = new JButton(Bundle.getMessage("Throttle"));
171
172    protected void additionsToToolBar() {
173        getToolBar().add(new LargePowerManagerButton(true));
174        getToolBar().add(Box.createHorizontalGlue());
175        JPanel p = new JPanel();
176        p.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
177        p.add(modePanel);
178        getToolBar().add(p);
179    }
180
181    /**
182     * For use when the DP3 window is called from another JMRI instance, set
183     * this to prevent the DP3 from shutting down JMRI when the window is
184     * closed.
185     *
186     * @param quitAllowed true if closing window should quit application; false
187     *                    otherwise
188     */
189    protected void allowQuit(boolean quitAllowed) {
190        if (allowQuit != quitAllowed) {
191            newWindowAction = null;
192            allowQuit = quitAllowed;
193            groups.setNewWindowMenuAction(this.getNewWindowAction());
194        }
195
196        firePropertyChange("quit", "setEnabled", allowQuit);
197        //if we are not allowing quit, ie opened from JMRI classic
198        //then we must at least allow the window to be closed
199        if (!allowQuit) {
200            firePropertyChange("closewindow", "setEnabled", true);
201        }
202    }
203
204    JPanel bottomRight() {
205        JPanel panel = new JPanel();
206        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
207        ButtonGroup progMode = new ButtonGroup();
208        progMode.add(service);
209        progMode.add(ops);
210        progMode.add(edit);
211        service.setEnabled(false);
212        ops.setEnabled(false);
213        edit.setEnabled(true);
214        firePropertyChange("setprogservice", "setEnabled", false);
215        firePropertyChange("setprogops", "setEnabled", false);
216        firePropertyChange("setprogedit", "setEnabled", true);
217        ops.setOpaque(false);
218        service.setOpaque(false);
219        edit.setOpaque(false);
220        JPanel progModePanel = new JPanel();
221        GridLayout buttonLayout = new GridLayout(3, 1, 0, 0);
222        progModePanel.setLayout(buttonLayout);
223        progModePanel.add(service);
224        progModePanel.add(ops);
225        progModePanel.add(edit);
226        programModeListener = (ActionEvent e) -> updateProgMode();
227        service.addActionListener(programModeListener);
228        ops.addActionListener(programModeListener);
229        edit.addActionListener(programModeListener);
230        service.setVisible(false);
231        ops.setVisible(false);
232        panel.add(progModePanel);
233        JPanel buttonHolder = new JPanel(new GridBagLayout());
234        GridBagConstraints c = new GridBagConstraints();
235        c.weightx = 1.0;
236        c.fill = GridBagConstraints.HORIZONTAL;
237        c.anchor = GridBagConstraints.NORTH;
238        c.gridx = 0;
239        c.ipady = 20;
240        c.gridwidth = GridBagConstraints.REMAINDER;
241        c.gridy = 0;
242        c.insets = new Insets(2, 2, 2, 2);
243        buttonHolder.add(prog1Button, c);
244        c.weightx = 1;
245        c.fill = GridBagConstraints.NONE;
246        c.gridx = 0;
247        c.gridy = 1;
248        c.gridwidth = 1;
249        c.ipady = 0;
250        buttonHolder.add(rosterMedia, c);
251        c.weightx = 1.0;
252        c.fill = GridBagConstraints.NONE;
253        c.gridx = 1;
254        c.gridy = 1;
255        c.gridwidth = 1;
256        c.ipady = 0;
257        buttonHolder.add(throttleLaunch, c);
258        //buttonHolder.add(throttleLaunch);
259        panel.add(buttonHolder);
260        prog1Button.setEnabled(false);
261        prog1Button.addActionListener((ActionEvent e) -> {
262            log.debug("Open programmer pressed");
263            startProgrammer(null, re, programmer1);
264        });
265
266        rosterMedia.setEnabled(false);
267        rosterMedia.addActionListener((ActionEvent e) -> {
268            log.debug("Open Media pressed");
269            edit.setSelected(true);
270            startProgrammer(null, re, "dp3" + File.separator + "MediaPane");
271        });
272        throttleLaunch.setEnabled(false);
273        throttleLaunch.addActionListener((ActionEvent e) -> {
274            log.debug("Launch Throttle pressed");
275            if (!checkIfEntrySelected()) {
276                return;
277            }
278            ThrottleControllerUI tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleController();
279            tf.toFront();
280            tf.setRosterEntry(re);
281        });
282        return panel;
283    }
284
285    protected final void buildWindow() {
286        //Additions to the toolbar need to be added first otherwise when trying to hide bits up during the initialisation they remain on screen
287        additionsToToolBar();
288        frameInstances.add(this);
289        prefsMgr = InstanceManager.getDefault(UserPreferencesManager.class);
290        getTop().add(createTop());
291        getBottom().setMinimumSize(summaryPaneDim);
292        getBottom().add(createBottom());
293        statusBar();
294        systemsMenu();
295        helpMenu(getMenu(), this);
296        if ((!prefsMgr.getSimplePreferenceState(this.getClass().getName() + ".hideGroups")) && !Roster.getDefault().getRosterGroupList().isEmpty()) {
297            hideGroupsPane(false);
298        } else {
299            hideGroupsPane(true);
300        }
301        if (prefsMgr.getSimplePreferenceState(this.getClass().getName() + ".hideSummary")) {
302            //We have to set it to display first, then we can hide it.
303            hideBottomPane(false);
304            hideBottomPane(true);
305        }
306        PropertyChangeListener propertyChangeListener = (PropertyChangeEvent changeEvent) -> {
307            JSplitPane sourceSplitPane = (JSplitPane) changeEvent.getSource();
308            String propertyName = changeEvent.getPropertyName();
309            if (propertyName.equals(JSplitPane.LAST_DIVIDER_LOCATION_PROPERTY)) {
310                int current = sourceSplitPane.getDividerLocation() + sourceSplitPane.getDividerSize();
311                int panesize = (int) (sourceSplitPane.getSize().getHeight());
312                hideBottomPane = panesize - current <= 1;
313                //p.setSimplePreferenceState(DecoderPro3Window.class.getName()+".hideSummary",hideSummary);
314            }
315        };
316        updateProgrammerStatus(null);
317        ConnectionStatus.instance().addPropertyChangeListener((PropertyChangeEvent e) -> {
318            if ((e.getPropertyName().equals("change")) || (e.getPropertyName().equals("add"))) {
319                log.debug("Received property {} with value {} ", e.getPropertyName(), e.getNewValue());
320                updateProgrammerStatus(e);
321            }
322        });
323        InstanceManager.addPropertyChangeListener(InstanceManager.getListPropertyName(AddressedProgrammerManager.class),
324                evt -> {
325                    log.debug("Received property {} with value {} ", evt.getPropertyName(), evt.getNewValue());
326                    AddressedProgrammerManager m = (AddressedProgrammerManager) evt.getNewValue();
327                    if (m != null) {
328                        m.addPropertyChangeListener(this::updateProgrammerStatus);
329                    }
330                    updateProgrammerStatus(evt);
331                });
332        InstanceManager.getList(AddressedProgrammerManager.class).forEach(m -> m.addPropertyChangeListener(this::updateProgrammerStatus));
333        InstanceManager.addPropertyChangeListener(InstanceManager.getListPropertyName(GlobalProgrammerManager.class),
334                evt -> {
335                    log.debug("Received property {} with value {} ", evt.getPropertyName(), evt.getNewValue());
336                    GlobalProgrammerManager m = (GlobalProgrammerManager) evt.getNewValue();
337                    if (m != null) {
338                        m.addPropertyChangeListener(this::updateProgrammerStatus);
339                    }
340                    updateProgrammerStatus(evt);
341                });
342        InstanceManager.getList(GlobalProgrammerManager.class).forEach(m -> m.addPropertyChangeListener(this::updateProgrammerStatus));
343        getSplitPane().addPropertyChangeListener(propertyChangeListener);
344        if (this.getProgrammerConfigManager().getDefaultFile() != null) {
345            programmer1 = this.getProgrammerConfigManager().getDefaultFile();
346        }
347        this.getProgrammerConfigManager().addPropertyChangeListener(ProgrammerConfigManager.DEFAULT_FILE, (PropertyChangeEvent evt) -> {
348            if (this.getProgrammerConfigManager().getDefaultFile() != null) {
349                programmer1 = this.getProgrammerConfigManager().getDefaultFile();
350            }
351        });
352
353        String lastProg = (String) prefsMgr.getProperty(getWindowFrameRef(), "selectedProgrammer");
354        if (lastProg != null) {
355            if (lastProg.equals("service") && service.isEnabled()) {
356                service.setSelected(true);
357                updateProgMode();
358            } else if (lastProg.equals("ops") && ops.isEnabled()) {
359                ops.setSelected(true);
360                updateProgMode();
361            } else if (lastProg.equals("edit") && edit.isEnabled()) {
362                edit.setSelected(true);
363                updateProgMode();
364            }
365        }
366        if (frameInstances.size() > 1) {
367            firePropertyChange("closewindow", "setEnabled", true);
368            allowQuit(frameInstances.get(0).isAllowQuit());
369        } else {
370            firePropertyChange("closewindow", "setEnabled", false);
371        }
372    }
373
374    boolean checkIfEntrySelected() {
375        return this.checkIfEntrySelected(false);
376    }
377
378    boolean checkIfEntrySelected(boolean allowMultiple) {
379        if ((re == null && !allowMultiple) || (this.getSelectedRosterEntries().length < 1)) {
380            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorNoSelection"));
381            return false;
382        }
383        return true;
384    }
385
386    //@TODO The disabling of the closeWindow menu item doesn't quite work as this in only invoked on the closing window, and not the one that is left
387    void closeWindow(WindowEvent e) {
388        saveWindowDetails();
389        //Save any changes made in the roster entry details
390        Roster.getDefault().writeRoster();
391        if (allowQuit && frameInstances.size() == 1 && !InstanceManager.getDefault(ShutDownManager.class).isShuttingDown()) {
392            handleQuit(e);
393        } else {
394            //As we are not the last window open or we are not allowed to quit the application then we will just close the current window
395            frameInstances.remove(this);
396            super.windowClosing(e);
397            if ((frameInstances.size() == 1) && (allowQuit)) {
398                frameInstances.get(0).firePropertyChange("closewindow", "setEnabled", false);
399            }
400            dispose();
401        }
402    }
403
404    protected void copyLoco() {
405        CopyRosterItem act = new CopyRosterItem("Copy", this, re);
406        act.actionPerformed(null);
407    }
408
409    JComponent createBottom() {
410        locoImage = new ResizableImagePanel(null, 240, 160);
411        locoImage.setBorder(BorderFactory.createLineBorder(Color.blue));
412        locoImage.setOpaque(true);
413        locoImage.setRespectAspectRatio(true);
414        rosterDetailPanel.setLayout(new BorderLayout());
415        rosterDetailPanel.add(locoImage, BorderLayout.WEST);
416        rosterDetailPanel.add(rosterDetails(), BorderLayout.CENTER);
417        rosterDetailPanel.add(bottomRight(), BorderLayout.EAST);
418        if (prefsMgr.getSimplePreferenceState(this.getClass().getName() + ".hideRosterImage")) {
419            locoImage.setVisible(false);
420            hideRosterImage = true;
421        }
422        rosterEntryUpdateListener = (PropertyChangeEvent e) -> updateDetails();
423        return rosterDetailPanel;
424    }
425
426    private boolean isUpdatingSelection = false;
427
428    JComponent createTop() {
429        Object selectedRosterGroup = prefsMgr.getProperty(getWindowFrameRef(), SELECTED_ROSTER_GROUP);
430        groups = new RosterGroupsPanel((selectedRosterGroup != null) ? selectedRosterGroup.toString() : null);
431        groups.setNewWindowMenuAction(this.getNewWindowAction());
432        setTitle(groups.getSelectedRosterGroup());
433        final JPanel rosters = new JPanel();
434        rosters.setLayout(new BorderLayout());
435        // set up roster table
436        rtable = new RosterTable(true, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
437        rtable.setRosterGroup(this.getSelectedRosterGroup());
438        rtable.setRosterGroupSource(groups);
439        rosters.add(rtable, BorderLayout.CENTER);
440        // add selection listener
441        rtable.getTable().getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
442            JTable table = rtable.getTable();
443            if (!e.getValueIsAdjusting()) {
444                if ((rtable.getSelectedRosterEntries().length == 1 ) && (table.getSelectedRow() >= 0)) {
445                    log.debug("Selected row {}", table.getSelectedRow());
446                    locoSelected(rtable.getModel().getValueAt(table.getRowSorter().convertRowIndexToModel(table.getSelectedRow()), RosterTableModel.IDCOL).toString());
447                } else if (rtable.getSelectedRosterEntries().length > 1) {
448                    log.debug("Multiple selection");
449                    locoSelected(null);
450                } else if ( (table.getSelectedRow() < 0) && (!isUpdatingSelection) ) {
451                    isUpdatingSelection = true;
452                    if (re != null) { // can be null with multiple selection
453                        log.debug("Selected roster entry {}", re.getId());
454                        if (!rtable.setSelection(re)) {
455                            re = null; //nothng was found
456                        }
457                    }
458                    updateDetails();
459                    rtable.moveTableViewToSelected();
460                    isUpdatingSelection = false;
461                } // leave last selected item visible if no selection
462            }
463        });
464
465        //Set all the sort and width details of the table first.
466        String rostertableref = getWindowFrameRef() + ":roster";
467        rtable.getTable().setName(rostertableref);
468
469        // Allow only one column to be sorted at a time -
470        // Java allows multiple column sorting, but to effectively persist that, we
471        // need to be intelligent about which columns can be meaningfully sorted
472        // with other columns; this bypasses the problem by only allowing the
473        // last column sorted to affect sorting
474        RowSorterUtil.addSingleSortableColumnListener(rtable.getTable().getRowSorter());
475
476        // Reset and then persist the table's ui state
477        JTablePersistenceManager tpm = InstanceManager.getNullableDefault(JTablePersistenceManager.class);
478        if (tpm != null) {
479            tpm.resetState(rtable.getTable());
480            tpm.persist(rtable.getTable());
481        }
482        rtable.getTable().setDragEnabled(true);
483        rtable.getTable().setTransferHandler(new TransferHandler() {
484
485            @Override
486            public int getSourceActions(JComponent c) {
487                return TransferHandler.COPY;
488            }
489
490            @Override
491            public Transferable createTransferable(JComponent c) {
492                JTable table = rtable.getTable();
493                ArrayList<String> Ids = new ArrayList<>(table.getSelectedRowCount());
494                for (int i = 0; i < table.getSelectedRowCount(); i++) {
495                    Ids.add(rtable.getModel().getValueAt(table.getRowSorter().convertRowIndexToModel(table.getSelectedRows()[i]), RosterTableModel.IDCOL).toString());
496                }
497                return new RosterEntrySelection(Ids);
498            }
499
500            @Override
501            public void exportDone(JComponent c, Transferable t, int action) {
502                // nothing to do
503            }
504        });
505        JmriMouseListener rosterMouseListener = new RosterPopupListener();
506        rtable.getTable().addMouseListener(JmriMouseListener.adapt(rosterMouseListener));
507
508        // assemble roster/groups splitpane
509        rosterGroupSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, groups, rosters);
510        rosterGroupSplitPane.setOneTouchExpandable(true);
511        rosterGroupSplitPane.setResizeWeight(0); // emphasis rosters
512        Object w = prefsMgr.getProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation");
513        if (w != null) {
514            groupSplitPaneLocation = (Integer) w;
515            rosterGroupSplitPane.setDividerLocation(groupSplitPaneLocation);
516        }
517        if (!Roster.getDefault().getRosterGroupList().isEmpty()) {
518            if (prefsMgr.getSimplePreferenceState(this.getClass().getName() + ".hideGroups")) {
519                hideGroupsPane(true);
520            }
521        } else {
522            enableRosterGroupMenuItems(false);
523        }
524        PropertyChangeListener propertyChangeListener = (PropertyChangeEvent changeEvent) -> {
525            JSplitPane sourceSplitPane = (JSplitPane) changeEvent.getSource();
526            String propertyName = changeEvent.getPropertyName();
527            if (propertyName.equals(JSplitPane.LAST_DIVIDER_LOCATION_PROPERTY)) {
528                int current = sourceSplitPane.getDividerLocation();
529                hideGroups = current <= 1;
530                Integer last = (Integer) changeEvent.getNewValue();
531                if (current >= 2) {
532                    groupSplitPaneLocation = current;
533                } else if (last >= 2) {
534                    groupSplitPaneLocation = last;
535                }
536            }
537        };
538        groups.addPropertyChangeListener(SELECTED_ROSTER_GROUP, new PropertyChangeListener() {
539            @Override
540            public void propertyChange(PropertyChangeEvent pce) {
541                prefsMgr.setProperty(this.getClass().getName(), SELECTED_ROSTER_GROUP, pce.getNewValue());
542                setTitle((String) pce.getNewValue());
543            }
544        });
545        rosterGroupSplitPane.addPropertyChangeListener(propertyChangeListener);
546        Roster.getDefault().addPropertyChangeListener((PropertyChangeEvent e) -> {
547            if (e.getPropertyName().equals("RosterGroupAdded") && Roster.getDefault().getRosterGroupList().size() == 1) {
548                // if the pane is hidden, show it when 1st group is created
549                hideGroupsPane(false);
550                enableRosterGroupMenuItems(true);
551            } else if (!rtable.isVisible() && (e.getPropertyName().equals("saved"))) {
552                if (firstHelpLabel != null) {
553                    firstHelpLabel.setVisible(false);
554                }
555                rtable.setVisible(true);
556                rtable.resetColumnWidths();
557            }
558        });
559        if (Roster.getDefault().numEntries() == 0) {
560            try {
561                BufferedImage myPicture = ImageIO.read(FileUtil.findURL(("resources/" + Bundle.getMessage("ThrottleFirstUseImage")), FileUtil.Location.INSTALLED));
562                //rosters.add(new JLabel(new ImageIcon( myPicture )), BorderLayout.CENTER);
563                firstHelpLabel = new JLabel(new ImageIcon(myPicture));
564                rtable.setVisible(false);
565                rosters.add(firstHelpLabel, BorderLayout.NORTH);
566                //tableArea.add(firstHelpLabel);
567                rtable.setVisible(false);
568            } catch (IOException ex) {
569                // handle exception...
570            }
571        }
572        return rosterGroupSplitPane;
573    }
574
575    protected void deleteLoco() {
576        DeleteRosterItemAction act = new DeleteRosterItemAction("Delete", (WindowInterface) this);
577        act.actionPerformed(null);
578    }
579
580    void editMediaButton() {
581        //Because of the way that programmers work, we need to use edit mode for displaying the media pane, so that the read/write buttons do not appear.
582        boolean serviceSelected = service.isSelected();
583        boolean opsSelected = ops.isSelected();
584        edit.setSelected(true);
585        startProgrammer(null, re, "dp3" + File.separator + "MediaPane");
586        service.setSelected(serviceSelected);
587        ops.setSelected(opsSelected);
588    }
589
590    protected void enableRosterGroupMenuItems(boolean enable) {
591        firePropertyChange("groupspane", "setEnabled", enable);
592        firePropertyChange("grouptable", "setEnabled", enable);
593        firePropertyChange("deletegroup", "setEnabled", enable);
594        firePropertyChange("addgroup", "setEnabled", enable);
595    }
596
597    protected void exportLoco() {
598        ExportRosterItem act = new ExportRosterItem(Bundle.getMessage("Export"), this, re);
599        act.actionPerformed(null);
600    }
601
602    void formatTextAreaAsLabel(JTextPane pane) {
603        pane.setOpaque(false);
604        pane.setEditable(false);
605        pane.setBorder(null);
606    }
607
608    /*=============== Getters and Setters for core properties ===============*/
609
610    /**
611     * @return Will closing the window quit JMRI?
612     */
613    public boolean isAllowQuit() {
614        return allowQuit;
615    }
616
617    /**
618     * @param allowQuit Set state to either close JMRI or just the roster window
619     */
620    public void setAllowQuit(boolean allowQuit) {
621        allowQuit(allowQuit);
622    }
623
624    /**
625     * @return the baseTitle
626     */
627    protected String getBaseTitle() {
628        return baseTitle;
629    }
630
631    /**
632     * @param baseTitle the baseTitle to set
633     */
634    protected final void setBaseTitle(String baseTitle) {
635        String title = null;
636        if (this.baseTitle == null) {
637            title = this.getTitle();
638        }
639        this.baseTitle = baseTitle;
640        if (title != null) {
641            this.setTitle(title);
642        }
643    }
644
645    /**
646     * @return the newWindowAction
647     */
648    protected JmriAbstractAction getNewWindowAction() {
649        if (newWindowAction == null) {
650            newWindowAction = new RosterFrameAction("newWindow", this, allowQuit);
651        }
652        return newWindowAction;
653    }
654
655    /**
656     * @param newWindowAction the newWindowAction to set
657     */
658    protected void setNewWindowAction(JmriAbstractAction newWindowAction) {
659        this.newWindowAction = newWindowAction;
660        this.groups.setNewWindowMenuAction(newWindowAction);
661    }
662
663    @Override
664    public void setTitle(String title) {
665        if (title == null || title.isEmpty()) {
666            title = Roster.ALLENTRIES;
667        }
668        if (this.baseTitle != null) {
669            if (!title.equals(this.baseTitle) && !title.startsWith(this.baseTitle)) {
670                super.setTitle(this.baseTitle + ": " + title);
671            }
672        } else {
673            super.setTitle(title);
674        }
675    }
676
677    @Override
678    public Object getProperty(String key) {
679        if (key.equalsIgnoreCase(SELECTED_ROSTER_GROUP)) {
680            return getSelectedRosterGroup();
681        } else if (key.equalsIgnoreCase("hideSummary")) {
682            return hideBottomPane;
683        }
684        // call parent getProperty method to return any properties defined
685        // in the class hierarchy.
686        return super.getProperty(key);
687    }
688
689    public Object getRemoteObject(String value) {
690        return getProperty(value);
691    }
692
693    @Override
694    public RosterEntry[] getSelectedRosterEntries() {
695        RosterEntry[] entries = rtable.getSelectedRosterEntries();
696        return Arrays.copyOf(entries, entries.length);
697    }
698
699    public RosterEntry[] getAllRosterEntries() {
700        RosterEntry[] entries = rtable.getSortedRosterEntries();
701        return Arrays.copyOf(entries, entries.length);
702    }
703
704    @Override
705    public String getSelectedRosterGroup() {
706        return groups.getSelectedRosterGroup();
707    }
708
709    protected ProgrammerConfigManager getProgrammerConfigManager() {
710        return InstanceManager.getDefault(ProgrammerConfigManager.class);
711    }
712
713    void handleQuit(WindowEvent e) {
714        if (e != null && frameInstances.size() == 1) {
715            final String rememberWindowClose = this.getClass().getName() + ".closeDP3prompt";
716            if (!prefsMgr.getSimplePreferenceState(rememberWindowClose)) {
717                JPanel message = new JPanel();
718                JLabel question = new JLabel(rb.getString("MessageLongCloseWarning"));
719                final JCheckBox remember = new JCheckBox(rb.getString("MessageRememberSetting"));
720                remember.setFont(remember.getFont().deriveFont(10.0F));
721                message.setLayout(new BoxLayout(message, BoxLayout.Y_AXIS));
722                message.add(question);
723                message.add(remember);
724                int result = JmriJOptionPane.showConfirmDialog(null,
725                        message,
726                        rb.getString("MessageShortCloseWarning"),
727                        JmriJOptionPane.YES_NO_OPTION);
728                if (remember.isSelected()) {
729                    prefsMgr.setSimplePreferenceState(rememberWindowClose, true);
730                }
731                if (result == JmriJOptionPane.YES_OPTION) {
732                    handleQuit();
733                }
734            } else {
735                handleQuit();
736            }
737        } else if (frameInstances.size() > 1) {
738            final String rememberWindowClose = this.getClass().getName() + ".closeMultipleDP3prompt";
739            if (!prefsMgr.getSimplePreferenceState(rememberWindowClose)) {
740                JPanel message = new JPanel();
741                JLabel question = new JLabel(rb.getString("MessageLongMultipleCloseWarning"));
742                final JCheckBox remember = new JCheckBox(rb.getString("MessageRememberSetting"));
743                remember.setFont(remember.getFont().deriveFont(10.0F));
744                message.setLayout(new BoxLayout(message, BoxLayout.Y_AXIS));
745                message.add(question);
746                message.add(remember);
747                int result = JmriJOptionPane.showConfirmDialog(null,
748                        message,
749                        rb.getString("MessageShortCloseWarning"),
750                        JmriJOptionPane.YES_NO_OPTION);
751                if (remember.isSelected()) {
752                    prefsMgr.setSimplePreferenceState(rememberWindowClose, true);
753                }
754                if (result == JmriJOptionPane.YES_OPTION) {
755                    handleQuit();
756                }
757            } else {
758                handleQuit();
759            }
760            //closeWindow(null);
761        }
762    }
763
764    private void handleQuit(){
765        try {
766            InstanceManager.getDefault(jmri.ShutDownManager.class).shutdown();
767        } catch (Exception e) {
768            log.error("Continuing after error in handleQuit", e);
769        }
770    }
771
772    protected void helpMenu(JMenuBar menuBar, final JFrame frame) {
773        // create menu and standard items
774        JMenu helpMenu = HelpUtil.makeHelpMenu("package.apps.gui3.dp3.DecoderPro3", true);
775        // use as main help menu
776        menuBar.add(helpMenu);
777    }
778
779    protected void hideGroups() {
780        boolean boo = !hideGroups;
781        hideGroupsPane(boo);
782    }
783
784    public void hideGroupsPane(boolean hide) {
785        if (hideGroups == hide) {
786            return;
787        }
788        hideGroups = hide;
789        if (hide) {
790            groupSplitPaneLocation = rosterGroupSplitPane.getDividerLocation();
791            rosterGroupSplitPane.setDividerLocation(1);
792            rosterGroupSplitPane.getLeftComponent().setMinimumSize(new Dimension());
793            if (Roster.getDefault().getRosterGroupList().isEmpty()) {
794                rosterGroupSplitPane.setOneTouchExpandable(false);
795                rosterGroupSplitPane.setDividerSize(0);
796            }
797        } else {
798            rosterGroupSplitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize"));
799            rosterGroupSplitPane.setOneTouchExpandable(true);
800            if (groupSplitPaneLocation >= 2) {
801                rosterGroupSplitPane.setDividerLocation(groupSplitPaneLocation);
802            } else {
803                rosterGroupSplitPane.resetToPreferredSizes();
804            }
805        }
806    }
807
808    protected void hideRosterImage() {
809        hideRosterImage = !hideRosterImage;
810        //p.setSimplePreferenceState(DecoderPro3Window.class.getName()+".hideRosterImage",hideRosterImage);
811        if (hideRosterImage) {
812            locoImage.setVisible(false);
813        } else {
814            locoImage.setVisible(true);
815        }
816    }
817
818    protected void hideSummary() {
819        boolean boo = !hideBottomPane;
820        hideBottomPane(boo);
821    }
822
823    /**
824     * An entry has been selected in the Roster Table, activate the bottom part
825     * of the window.
826     *
827     * @param id ID of the selected roster entry
828     */
829    final void locoSelected(String id) {
830        if (id != null) {
831            log.debug("locoSelected ID {}", id);
832            if (re != null) {
833                // we remove the propertychangelistener if we had a previously selected entry;
834                re.removePropertyChangeListener(rosterEntryUpdateListener);
835            }
836            // convert to roster entry
837            re = Roster.getDefault().entryFromTitle(id);
838            re.addPropertyChangeListener(rosterEntryUpdateListener);
839        } else {
840            log.debug("Multiple selection");
841            re = null;
842        }
843        updateDetails();
844    }
845
846    protected void newWindow() {
847        this.newWindow(this.getNewWindowAction());
848    }
849
850    protected void newWindow(JmriAbstractAction action) {
851        action.setWindowInterface(this);
852        action.actionPerformed(null);
853        firePropertyChange("closewindow", "setEnabled", true);
854    }
855
856    /**
857     * Prepare a roster entry to be printed, and display a selection list.
858     *
859     * @see jmri.jmrit.roster.PrintRosterEntry#printPanes(boolean)
860     * @param preview true if output should go to a Preview pane on screen, false
861     *            to output to a printer (dialog)
862     */
863    protected void printLoco(boolean preview) {
864        log.debug("Selected entry: {}", re.getDisplayName());
865        String programmer = "Basic";
866        if (this.getProgrammerConfigManager().getDefaultFile() != null) {
867            programmer = this.getProgrammerConfigManager().getDefaultFile();
868        } else {
869            log.error("programmer is NULL");
870        }
871        PrintRosterEntry pre = new PrintRosterEntry(re, this, "programmers" + File.separator + programmer + ".xml");
872        // uses programmer set in prefs when printing a selected entry from (this) top Roster frame
873        // compare with: jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame#printPanes(boolean)
874        // as user expects to see more tabs on printout using Comprehensive or just 1 tab for Basic programmer
875        pre.printPanes(preview);
876    }
877
878    /**
879     * Print the displayed table, as displayed.
880     *
881     */
882    protected void printCurrentTable() {
883        try {
884            var cal = java.util.Calendar.getInstance();
885            var sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm");
886            String time = sdf.format(cal.getTime());
887
888            var selectedRosterGroup = getSelectedRosterGroup();
889            String g = (selectedRosterGroup != null) ? selectedRosterGroup : "All Entries";
890            String group = String.format("%-20s",g);  // pad to right to fixed length
891
892            rtable.getTable().print(javax.swing.JTable.PrintMode.FIT_WIDTH,
893                            null,  // no header
894                            new java.text.MessageFormat(group+" - {0} -   "+time)  // spaces for heuristic formatting, don't change
895                            );
896        } catch (java.awt.print.PrinterException ep) {
897            log.error("While printing",ep);
898        }    
899    }
900    
901    /**
902     * Match the first argument in the array against a locally-known method.
903     *
904     * @param args Array of arguments, we take with element 0
905     */
906    @Override
907    public void remoteCalls(String[] args) {
908        args[0] = args[0].toLowerCase();
909        switch (args[0]) {
910            case "identifyloco":
911                startIdentifyLoco();
912                break;
913            case "printcurrenttable":
914                    printCurrentTable();
915                break;
916            case "printloco":
917                if (checkIfEntrySelected()) {
918                    printLoco(false);
919                }
920                break;
921            case "printpreviewloco":
922                if (checkIfEntrySelected()) {
923                    printLoco(true);
924                }
925                break;
926            case "exportloco":
927                if (checkIfEntrySelected()) {
928                    exportLoco();
929                }
930                break;
931            case "basicprogrammer":
932                if (checkIfEntrySelected()) {
933                    startProgrammer(null, re, programmer2);
934                }
935                break;
936            case "comprehensiveprogrammer":
937                if (checkIfEntrySelected()) {
938                    startProgrammer(null, re, programmer1);
939                }
940                break;
941            case "editthrottlelabels":
942                if (checkIfEntrySelected()) {
943                    startProgrammer(null, re, "dp3" + File.separator + "ThrottleLabels");
944                }
945                break;
946            case "editrostermedia":
947                if (checkIfEntrySelected()) {
948                    startProgrammer(null, re, "dp3" + File.separator + "MediaPane");
949                }
950                break;
951            case "hiderosterimage":
952                hideRosterImage();
953                break;
954            case "summarypane":
955                hideSummary();
956                break;
957            case "copyloco":
958                if (checkIfEntrySelected()) {
959                    copyLoco();
960                }
961                break;
962            case "deleteloco":
963                if (checkIfEntrySelected(true)) {
964                    deleteLoco();
965                }
966                break;
967            case "setprogservice":
968                service.setSelected(true);
969                break;
970            case "setprogops":
971                ops.setSelected(true);
972                break;
973            case "setprogedit":
974                edit.setSelected(true);
975                break;
976            case "groupspane":
977                hideGroups();
978                break;
979            case "quit":
980                saveWindowDetails();
981                handleQuit(new WindowEvent(this, frameInstances.size()));
982                break;
983            case "closewindow":
984                closeWindow(null);
985                break;
986            case "newwindow":
987                newWindow();
988                break;
989            case "resettablecolumns":
990                rtable.resetColumnWidths();
991                break;
992            default:
993                log.error("method {} not found", args[0]);
994                break;
995        }
996    }
997
998    JPanel rosterDetails() {
999        JPanel panel = new JPanel();
1000        GridBagLayout gbLayout = new GridBagLayout();
1001        GridBagConstraints cL = new GridBagConstraints();
1002        GridBagConstraints cR = new GridBagConstraints();
1003        Dimension minFieldDim = new Dimension(30, 20);
1004        cL.gridx = 0;
1005        cL.gridy = 0;
1006        cL.ipadx = 3;
1007        cL.anchor = GridBagConstraints.EAST;
1008        cL.insets = new Insets(0, 0, 0, 15);
1009        JLabel row0Label = new JLabel(Bundle.getMessage("FieldID") + ":", JLabel.LEFT);
1010        gbLayout.setConstraints(row0Label, cL);
1011        panel.setLayout(gbLayout);
1012        panel.add(row0Label);
1013        cR.gridx = 1;
1014        cR.gridy = 0;
1015        cR.anchor = GridBagConstraints.WEST;
1016        id.setMinimumSize(minFieldDim);
1017        gbLayout.setConstraints(id, cR);
1018        formatTextAreaAsLabel(id);
1019        panel.add(id);
1020        cL.gridy = 1;
1021        JLabel row1Label = new JLabel(Bundle.getMessage("FieldRoadName") + ":", JLabel.LEFT);
1022        gbLayout.setConstraints(row1Label, cL);
1023        panel.add(row1Label);
1024        cR.gridy = 1;
1025        roadName.setMinimumSize(minFieldDim);
1026        gbLayout.setConstraints(roadName, cR);
1027        formatTextAreaAsLabel(roadName);
1028        panel.add(roadName);
1029        cL.gridy = 2;
1030        JLabel row2Label = new JLabel(Bundle.getMessage("FieldRoadNumber") + ":");
1031        gbLayout.setConstraints(row2Label, cL);
1032        panel.add(row2Label);
1033        cR.gridy = 2;
1034        roadNumber.setMinimumSize(minFieldDim);
1035        gbLayout.setConstraints(roadNumber, cR);
1036        formatTextAreaAsLabel(roadNumber);
1037        panel.add(roadNumber);
1038        cL.gridy = 3;
1039        JLabel row3Label = new JLabel(Bundle.getMessage("FieldManufacturer") + ":");
1040        gbLayout.setConstraints(row3Label, cL);
1041        panel.add(row3Label);
1042        cR.gridy = 3;
1043        mfg.setMinimumSize(minFieldDim);
1044        gbLayout.setConstraints(mfg, cR);
1045        formatTextAreaAsLabel(mfg);
1046        panel.add(mfg);
1047        cL.gridy = 4;
1048        JLabel row4Label = new JLabel(Bundle.getMessage("FieldOwner") + ":");
1049        gbLayout.setConstraints(row4Label, cL);
1050        panel.add(row4Label);
1051        cR.gridy = 4;
1052        owner.setMinimumSize(minFieldDim);
1053        gbLayout.setConstraints(owner, cR);
1054        formatTextAreaAsLabel(owner);
1055        panel.add(owner);
1056        cL.gridy = 5;
1057        JLabel row5Label = new JLabel(Bundle.getMessage("FieldModel") + ":");
1058        gbLayout.setConstraints(row5Label, cL);
1059        panel.add(row5Label);
1060        cR.gridy = 5;
1061        model.setMinimumSize(minFieldDim);
1062        gbLayout.setConstraints(model, cR);
1063        formatTextAreaAsLabel(model);
1064        panel.add(model);
1065        cL.gridy = 6;
1066        JLabel row6Label = new JLabel(Bundle.getMessage("FieldDCCAddress") + ":");
1067        gbLayout.setConstraints(row6Label, cL);
1068        panel.add(row6Label);
1069        cR.gridy = 6;
1070        dccAddress.setMinimumSize(minFieldDim);
1071        gbLayout.setConstraints(dccAddress, cR);
1072        formatTextAreaAsLabel(dccAddress);
1073        panel.add(dccAddress);
1074        cL.gridy = 7;
1075        cR.gridy = 7;
1076        cL.gridy = 8;
1077        cR.gridy = 8;
1078        cL.gridy = 9;
1079        JLabel row9Label = new JLabel(Bundle.getMessage("FieldDecoderFamily") + ":");
1080        gbLayout.setConstraints(row9Label, cL);
1081        panel.add(row9Label);
1082        cR.gridy = 9;
1083        decoderFamily.setMinimumSize(minFieldDim);
1084        gbLayout.setConstraints(decoderFamily, cR);
1085        formatTextAreaAsLabel(decoderFamily);
1086        panel.add(decoderFamily);
1087        cL.gridy = 10;
1088        JLabel row10Label = new JLabel(Bundle.getMessage("FieldDecoderModel") + ":");
1089        gbLayout.setConstraints(row10Label, cL);
1090        panel.add(row10Label);
1091        cR.gridy = 10;
1092        decoderModel.setMinimumSize(minFieldDim);
1093        gbLayout.setConstraints(decoderModel, cR);
1094        formatTextAreaAsLabel(decoderModel);
1095        panel.add(decoderModel);
1096        cL.gridy = 11;
1097        cR.gridy = 11;
1098        cL.gridy = 12;
1099        JLabel row12Label = new JLabel(Bundle.getMessage("FieldFilename") + ":");
1100        gbLayout.setConstraints(row12Label, cL);
1101        panel.add(row12Label);
1102        cR.gridy = 12;
1103        filename.setMinimumSize(minFieldDim);
1104        gbLayout.setConstraints(filename, cR);
1105        formatTextAreaAsLabel(filename);
1106        panel.add(filename);
1107        cL.gridy = 13;
1108        /*
1109         * JLabel row13Label = new
1110         * JLabel(Bundle.getMessage("FieldDateUpdated")+":");
1111         * gbLayout.setConstraints(row13Label,cL); panel.add(row13Label);
1112         */
1113        cR.gridy = 13;
1114        /*
1115         * filename.setMinimumSize(minFieldDim);
1116         * gbLayout.setConstraints(dateUpdated,cR); panel.add(dateUpdated);
1117         */
1118        formatTextAreaAsLabel(dateUpdated);
1119        JPanel retval = new JPanel(new FlowLayout(FlowLayout.LEFT));
1120        retval.add(panel);
1121        return retval;
1122    }
1123
1124    void saveWindowDetails() {
1125        prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideSummary", hideBottomPane);
1126        prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideGroups", hideGroups);
1127        prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideRosterImage", hideRosterImage);
1128        prefsMgr.setProperty(getWindowFrameRef(), SELECTED_ROSTER_GROUP, groups.getSelectedRosterGroup());
1129        String selectedProgMode = "edit";
1130        if (service.isSelected()) {
1131            selectedProgMode = "service";
1132        }
1133        if (ops.isSelected()) {
1134            selectedProgMode = "ops";
1135        }
1136        prefsMgr.setProperty(getWindowFrameRef(), "selectedProgrammer", selectedProgMode);
1137
1138        if (rosterGroupSplitPane.getDividerLocation() > 2) {
1139            prefsMgr.setProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation", rosterGroupSplitPane.getDividerLocation());
1140        } else if (groupSplitPaneLocation > 2) {
1141            prefsMgr.setProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation", groupSplitPaneLocation);
1142        }
1143    }
1144
1145    /**
1146     * Identify locomotive complete, act on it by setting the GUI. This will
1147     * fire "GUI changed" events which will reset the decoder GUI.
1148     *
1149     * @param dccAddress address of locomotive
1150     * @param isLong     true if address is long; false if short
1151     * @param mfgId      manufacturer id as in decoder
1152     * @param modelId    model id as in decoder
1153     */
1154    protected void selectLoco(int dccAddress, boolean isLong, int mfgId, int modelId) {
1155        // raise the button again
1156        // idloco.setSelected(false);
1157        // locate that loco
1158        inStartProgrammer = false;
1159        if (re != null) {
1160            //We remove the propertychangelistener if we had a previoulsy selected entry;
1161            re.removePropertyChangeListener(rosterEntryUpdateListener);
1162        }
1163        List<RosterEntry> l = Roster.getDefault().matchingList(null, null, Integer.toString(dccAddress), null, null, null, null);
1164        log.debug("selectLoco found {} matches", l.size());
1165        if (!l.isEmpty()) {
1166            if (l.size() > 1) {
1167                //More than one possible loco, so check long flag
1168                List<RosterEntry> l2 = new ArrayList<>();
1169                for (RosterEntry _re : l) {
1170                    if (_re.isLongAddress() == isLong) {
1171                        l2.add(_re);
1172                    }
1173                }
1174                if (l2.size() == 1) {
1175                    re = l2.get(0);
1176                } else {
1177                    if (l2.isEmpty()) {
1178                        l2 = l;
1179                    }
1180                    // Still more than one possible loco, so check against the decoder family
1181                    log.trace("Checking against decoder family with mfg {} model {}", mfgId, modelId);
1182                    List<RosterEntry> l3 = new ArrayList<>();
1183                    List<DecoderFile> temp = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, "" + mfgId, "" + modelId, null, null);
1184                    log.trace("found {}", temp.size());
1185                    ArrayList<String> decoderFam = new ArrayList<>();
1186                    for (DecoderFile f : temp) {
1187                        if (!decoderFam.contains(f.getModel())) {
1188                            decoderFam.add(f.getModel());
1189                        }
1190                    }
1191                    log.trace("matched {} times", decoderFam.size());
1192
1193                    for (RosterEntry _re : l2) {
1194                        if (decoderFam.contains(_re.getDecoderModel())) {
1195                            l3.add(_re);
1196                        }
1197                    }
1198                    if (l3.isEmpty()) {
1199                        //Unable to determine the loco against the manufacture therefore will be unable to further identify against decoder.
1200                        re = l2.get(0);
1201                    } else {
1202                        //We have no other options to match against so will return the first one we come across;
1203                        re = l3.get(0);
1204                    }
1205                }
1206            } else {
1207                re = l.get(0);
1208            }
1209            re.addPropertyChangeListener(rosterEntryUpdateListener);
1210            rtable.setSelection(re);
1211            updateDetails();
1212            rtable.moveTableViewToSelected();
1213        } else {
1214            log.warn("Read address {}, but no such loco in roster", dccAddress); //"No roster entry found; changed to promote the number to the front, June 2022,  Bill Chown"
1215            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NotFoundError", dccAddress), Bundle.getMessage("NotFoundErrorTitle", dccAddress), JmriJOptionPane.INFORMATION_MESSAGE);
1216        }
1217    }
1218
1219    /**
1220     * Simple method to change over the programmer buttons.
1221     * <p>
1222     * TODO This should be implemented with the buttons in their own class etc.
1223     * but this will work for now.
1224     *
1225     * @param buttonId   1 or 2; use 1 for basic programmer; 2 for comprehensive
1226     *                   programmer
1227     * @param programmer name of programmer
1228     * @param buttonText button title
1229     */
1230    public void setProgrammerLaunch(int buttonId, String programmer, String buttonText) {
1231        if (buttonId == 1) {
1232            programmer1 = programmer;
1233            prog1Button.setText(buttonText);
1234        } else if (buttonId == 2) {
1235            programmer2 = programmer;
1236            prog2Button.setText(buttonText);
1237        }
1238    }
1239
1240    public void setSelectedRosterGroup(String rosterGroup) {
1241        groups.setSelectedRosterGroup(rosterGroup);
1242    }
1243
1244    protected void showPopup(JmriMouseEvent e) {
1245        int row = rtable.getTable().rowAtPoint(e.getPoint());
1246        if (!rtable.getTable().isRowSelected(row)) {
1247            rtable.getTable().changeSelection(row, 0, false, false);
1248        }
1249        JPopupMenu popupMenu = new JPopupMenu();
1250
1251        JMenuItem menuItem = new JMenuItem(Bundle.getMessage("Program"));
1252        menuItem.addActionListener((ActionEvent e1) -> startProgrammer(null, re, programmer1));
1253        if (re == null) {
1254            menuItem.setEnabled(false);
1255        }
1256        popupMenu.add(menuItem);
1257        ButtonGroup group = new ButtonGroup();
1258        group.add(contextService);
1259        group.add(contextOps);
1260        group.add(contextEdit);
1261        JMenu progMenu = new JMenu(Bundle.getMessage("ProgrammerType"));
1262        contextService.addActionListener((ActionEvent e1) -> {
1263            service.setSelected(true);
1264            updateProgMode();
1265        });
1266        progMenu.add(contextService);
1267        contextOps.addActionListener((ActionEvent e1) -> {
1268            ops.setSelected(true);
1269            updateProgMode();
1270        });
1271        progMenu.add(contextOps);
1272        contextEdit.addActionListener((ActionEvent e1) -> {
1273            edit.setSelected(true);
1274            updateProgMode();
1275        });
1276        if (service.isSelected()) {
1277            contextService.setSelected(true);
1278        } else if (ops.isSelected()) {
1279            contextOps.setSelected(true);
1280        } else {
1281            contextEdit.setSelected(true);
1282        }
1283        progMenu.add(contextEdit);
1284        popupMenu.add(progMenu);
1285
1286        popupMenu.addSeparator();
1287        menuItem = new JMenuItem(Bundle.getMessage("LabelsAndMedia"));
1288        menuItem.addActionListener((ActionEvent e1) -> editMediaButton());
1289        if (re == null) {
1290            menuItem.setEnabled(false);
1291        }
1292        popupMenu.add(menuItem);
1293        menuItem = new JMenuItem(Bundle.getMessage("Throttle"));
1294        menuItem.addActionListener((ActionEvent e1) -> {
1295            ThrottleControllersUIContainer tw = null;
1296            for (RosterEntry re : rtable.getSelectedRosterEntries()) {
1297                ThrottleControllerUI tf;
1298                if (tw == null) {
1299                    tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleController();
1300                    tw = tf.getThrottleControllersContainer();
1301                } else {
1302                    tf = tw.newThrottleController();
1303                }
1304                tf.toFront();
1305                tf.setRosterEntry(re);
1306            }
1307        });
1308        popupMenu.add(menuItem);
1309        popupMenu.addSeparator();
1310
1311        menuItem = new JMenuItem(Bundle.getMessage("PrintSelection"));
1312        menuItem.addActionListener((ActionEvent e1) -> printLoco(false));
1313        if (re == null) {
1314            menuItem.setEnabled(false);
1315        }
1316        popupMenu.add(menuItem);
1317        menuItem = new JMenuItem(Bundle.getMessage("PreviewSelection"));
1318        menuItem.addActionListener((ActionEvent e1) -> printLoco(true));
1319        if (re == null) {
1320            menuItem.setEnabled(false);
1321        }
1322        popupMenu.add(menuItem);
1323        popupMenu.addSeparator();
1324
1325        menuItem = new JMenuItem(Bundle.getMessage("Duplicateddd"));
1326        menuItem.addActionListener((ActionEvent e1) -> copyLoco());
1327        if (re == null) {
1328            menuItem.setEnabled(false);
1329        }
1330        popupMenu.add(menuItem);
1331        boolean deleteFromGroup = this.getSelectedRosterGroup() != null && !this.getSelectedRosterGroup().equals(Roster.NOGROUP);
1332        menuItem = new JMenuItem(deleteFromGroup ? Bundle.getMessage("DeleteFromGroup") : Bundle.getMessage("DeleteFromRoster")); // NOI18N
1333        menuItem.addActionListener((ActionEvent e1) -> deleteLoco());
1334        popupMenu.add(menuItem);
1335        menuItem.setEnabled(this.getSelectedRosterEntries().length > 0);
1336
1337        menuItem = new JMenuItem(new jmri.jmrit.roster.swing.RosterEntryToGroupAction(Bundle.getMessage("AddToGroup"), re));
1338        if (re == null) {
1339            menuItem.setEnabled(false);
1340        }
1341        popupMenu.add(menuItem);
1342
1343        popupMenu.show(e.getComponent(), e.getX(), e.getY());
1344    }
1345
1346    /**
1347     * Start the identify operation after [Identify Loco] button pressed.
1348     * <p>
1349     * This defines what happens when Identify is done.
1350     */
1351    //taken out of CombinedLocoSelPane
1352    protected void startIdentifyLoco() {
1353        final RosterFrame me = this;
1354        Programmer programmer = null;
1355        if (modePanel.isSelected()) {
1356            programmer = modePanel.getProgrammer();
1357        }
1358        if (programmer == null) {
1359            GlobalProgrammerManager gpm = InstanceManager.getNullableDefault(GlobalProgrammerManager.class);
1360            if (gpm != null) {
1361                programmer = gpm.getGlobalProgrammer();
1362                log.warn("Selector did not provide a programmer, attempt to use GlobalProgrammerManager default: {}", programmer);
1363            } else {
1364                log.warn("Selector did not provide a programmer, and no ProgramManager found in InstanceManager");
1365            }
1366        }
1367
1368        // if failed to get programmer, tell user and stop
1369        if (programmer == null) {
1370            log.error("Identify loco called when no service mode programmer is available; button should have been disabled");
1371            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("IdentifyError"));
1372            return;
1373        }
1374
1375        // and now do the work
1376        IdentifyLoco ident = new IdentifyLoco(programmer) {
1377            private final RosterFrame who = me;
1378
1379            @Override
1380            protected void done(int dccAddress) {
1381                // if Done, updated the selected decoder
1382                // on the GUI thread, right now
1383                jmri.util.ThreadingUtil.runOnGUI(() -> who.selectLoco(dccAddress, !shortAddr, cv8val, cv7val));
1384            }
1385
1386            @Override
1387            protected void message(String m) {
1388                // on the GUI thread, right now
1389                jmri.util.ThreadingUtil.runOnGUI(() -> statusField.setText(m));
1390            }
1391
1392            @Override
1393            protected void error() {
1394                // raise the button again
1395                //idloco.setSelected(false);
1396            }
1397        };
1398        ident.start();
1399    }
1400
1401    protected void startProgrammer(DecoderFile decoderFile, RosterEntry re, String filename) {
1402        if (inStartProgrammer) {
1403            log.debug("Call to start programmer has been called twice when the first call hasn't opened");
1404            return;
1405        }
1406        if (!checkIfEntrySelected()) {
1407            return;
1408        }
1409        try {
1410            setCursor(new Cursor(Cursor.WAIT_CURSOR));
1411            inStartProgrammer = true;
1412            String title = re.getId();
1413            JFrame progFrame = null;
1414            if (edit.isSelected()) {
1415                progFrame = new PaneProgFrame(decoderFile, re, title, "programmers" + File.separator + filename + ".xml", null, false) {
1416                    @Override
1417                    protected JPanel getModePane() {
1418                        return null;
1419                    } // hide prog mode buttons pane
1420                };
1421            } else if (service.isSelected()) {
1422                progFrame = new PaneServiceProgFrame(decoderFile, re, title, "programmers" + File.separator + filename + ".xml", modePanel.getProgrammer());
1423            } else if (ops.isSelected()) {
1424                int address = Integer.parseInt(re.getDccAddress());
1425                boolean longAddr = re.isLongAddress();
1426                Programmer pProg = InstanceManager.getDefault(AddressedProgrammerManager.class).getAddressedProgrammer(longAddr, address);
1427                progFrame = new PaneOpsProgFrame(decoderFile, re, title, "programmers" + File.separator + filename + ".xml", pProg);
1428            }
1429            if (progFrame == null) {
1430                return;
1431            }
1432            progFrame.pack();
1433            progFrame.setVisible(true);
1434        } finally {
1435            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
1436        }
1437        inStartProgrammer = false;
1438    }
1439
1440    /**
1441     * Create and display a status bar along the bottom edge of the Roster main
1442     * pane.
1443     * <p>
1444     * TODO This status bar needs sorting out properly
1445     */
1446    protected void statusBar() {
1447        addToStatusBox(serviceModeProgrammerLabel, null);
1448        addToStatusBox(operationsModeProgrammerLabel, null);
1449        JLabel programmerStatusLabel = new JLabel(Bundle.getMessage("ProgrammerStatus"));
1450        statusField.setText(Bundle.getMessage("StateIdle"));
1451        addToStatusBox(programmerStatusLabel, statusField);
1452        Profile profile = ProfileManager.getDefault().getActiveProfile();
1453        if (profile != null) {
1454            addToStatusBox(new JLabel(Bundle.getMessage("ActiveProfile", profile.getName())), null);
1455        }
1456    }
1457
1458    protected void systemsMenu() {
1459        ActiveSystemsMenu.addItems(getMenu());
1460        getMenu().add(new WindowMenu(this));
1461    }
1462
1463    void updateDetails() {
1464        if (re == null) {
1465            String value = (rtable.getTable().getSelectedRowCount() > 1) ? "Multiple Items Selected" : "";
1466            filename.setText(value);
1467            dateUpdated.setText(value);
1468            decoderModel.setText(value);
1469            decoderFamily.setText(value);
1470            id.setText(value);
1471            roadName.setText(value);
1472            dccAddress.setText(value);
1473            roadNumber.setText(value);
1474            mfg.setText(value);
1475            model.setText(value);
1476            owner.setText(value);
1477            locoImage.setImagePath(null);
1478            service.setEnabled(false);
1479            ops.setEnabled(false);
1480            edit.setEnabled(false);
1481            prog1Button.setEnabled(false);
1482            prog2Button.setEnabled(false);
1483            throttleLabels.setEnabled(false);
1484            rosterMedia.setEnabled(false);
1485            throttleLaunch.setEnabled(false );
1486        } else {
1487            filename.setText(re.getFileName());
1488            dateUpdated.setText((re.getDateModified() != null)
1489                    ? DateFormat.getDateTimeInstance().format(re.getDateModified())
1490                    : re.getDateUpdated());
1491            decoderModel.setText(re.getDecoderModel());
1492            decoderFamily.setText(re.getDecoderFamily());
1493            dccAddress.setText(re.getDccAddress());
1494            id.setText(re.getId());
1495            roadName.setText(re.getRoadName());
1496            roadNumber.setText(re.getRoadNumber());
1497            mfg.setText(re.getMfg());
1498            model.setText(re.getModel());
1499            owner.setText(re.getOwner());
1500            locoImage.setImagePath(re.getImagePath());
1501            if (hideRosterImage) {
1502                locoImage.setVisible(false);
1503            } else {
1504                locoImage.setVisible(true);
1505            }
1506            service.setEnabled(isProgrammingTrackEnabled());
1507            ops.setEnabled(isProgrammingOnMainEnabled());
1508            edit.setEnabled(true);
1509            prog1Button.setEnabled(true);
1510            prog2Button.setEnabled(true);
1511            throttleLabels.setEnabled(true);
1512            rosterMedia.setEnabled(true);
1513            throttleLaunch.setEnabled(true);
1514            updateProgMode();
1515        }
1516    }
1517
1518    void updateProgMode() {
1519        String progMode;
1520        if (service.isSelected()) {
1521            progMode = "setprogservice";
1522        } else if (ops.isSelected()) {
1523            progMode = "setprogops";
1524        } else {
1525            progMode = "setprogedit";
1526        }
1527        firePropertyChange(progMode, "setSelected", true);
1528    }
1529
1530    /**
1531     * Handle setting up and updating the GUI for the types of programmer
1532     * available.
1533     *
1534     * @param evt the triggering event; if not null and if a removal of a
1535     *            ProgrammerManager, care will be taken not to trigger the
1536     *            automatic creation of a new ProgrammerManager
1537     */
1538    protected void updateProgrammerStatus(@CheckForNull PropertyChangeEvent evt) {
1539        log.debug("Updating Programmer Status");
1540        ConnectionConfig oldServMode = serModeProCon;
1541        ConnectionConfig oldOpsMode = opsModeProCon;
1542        GlobalProgrammerManager gpm = null;
1543        AddressedProgrammerManager apm = null;
1544
1545        // Find the connection that goes with the global programmer
1546        // test that IM has a default GPM, or that event is not the removal of a GPM
1547        if (InstanceManager.containsDefault(GlobalProgrammerManager.class)
1548                || (evt != null
1549                && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(GlobalProgrammerManager.class))
1550                && evt.getNewValue() == null)) {
1551            gpm = InstanceManager.getNullableDefault(GlobalProgrammerManager.class);
1552            log.trace("found global programming manager {}", gpm);
1553        }
1554        if (gpm != null) {
1555            String serviceModeProgrammerName = gpm.getUserName();
1556            log.debug("GlobalProgrammerManager found of class {} name {} ", gpm.getClass(), serviceModeProgrammerName);
1557            InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> {
1558                for (ConnectionConfig connection : ccm) {
1559                    log.debug("Checking connection name {}", connection.getConnectionName());
1560                    if (connection.getConnectionName() != null && connection.getConnectionName().equals(serviceModeProgrammerName)) {
1561                        log.debug("Connection found for GlobalProgrammermanager");
1562                        serModeProCon = connection;
1563                    }
1564                }
1565            });
1566        }
1567
1568        // Find the connection that goes with the addressed programmer
1569        // test that IM has a default APM, or that event is not the removal of an APM
1570        if (InstanceManager.containsDefault(AddressedProgrammerManager.class)
1571                || (evt != null
1572                && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(AddressedProgrammerManager.class))
1573                && evt.getNewValue() == null)) {
1574            apm = InstanceManager.getNullableDefault(AddressedProgrammerManager.class);
1575            log.trace("found addressed programming manager {}", gpm);
1576        }
1577        if (apm != null) {
1578            String opsModeProgrammerName = apm.getUserName();
1579            log.debug("AddressedProgrammerManager found of class {} name {} ", apm.getClass(), opsModeProgrammerName);
1580            InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> {
1581                for (ConnectionConfig connection : ccm) {
1582                    log.debug("Checking connection name {}", connection.getConnectionName());
1583                    if (connection.getConnectionName() != null && connection.getConnectionName().equals(opsModeProgrammerName)) {
1584                        log.debug("Connection found for AddressedProgrammermanager");
1585                        opsModeProCon = connection;
1586                    }
1587                }
1588            });
1589        }
1590
1591        log.trace("start global check with {}, {}, {}", serModeProCon, gpm, (gpm != null ? gpm.isGlobalProgrammerAvailable() : "<none>"));
1592        if (serModeProCon != null && gpm != null && gpm.isGlobalProgrammerAvailable()) {
1593            if (ConnectionStatus.instance().isConnectionOk(serModeProCon.getConnectionName(), serModeProCon.getInfo())) {
1594                log.debug("GPM Connection online 1");
1595                serviceModeProgrammerLabel.setText(
1596                        Bundle.getMessage("ServiceModeProgOnline", serModeProCon.getConnectionName()));
1597                serviceModeProgrammerLabel.setForeground(new Color(0, 128, 0));
1598            } else {
1599                log.debug("GPM Connection offline");
1600                serviceModeProgrammerLabel.setText(
1601                        Bundle.getMessage("ServiceModeProgOffline", serModeProCon.getConnectionName()));
1602                serviceModeProgrammerLabel.setForeground(Color.red);
1603            }
1604            if (oldServMode == null) {
1605                log.debug("Re-enable user interface");
1606                contextService.setEnabled(isProgrammingTrackEnabled());
1607                contextService.setVisible(true);
1608                service.setEnabled(isProgrammingTrackEnabled());
1609                service.setVisible(true);
1610                firePropertyChange("setprogservice", "setEnabled", true);
1611                getToolBar().getComponents()[1].setEnabled(true);
1612            }
1613        } else if (gpm != null && gpm.isGlobalProgrammerAvailable()) {
1614            if (ConnectionStatus.instance().isSystemOk(gpm.getUserName())) {
1615                log.debug("GPM Connection online 2");
1616                serviceModeProgrammerLabel.setText(
1617                        Bundle.getMessage("ServiceModeProgOnline", gpm.getUserName()));
1618                serviceModeProgrammerLabel.setForeground(new Color(0, 128, 0));
1619            } else {
1620                log.debug("GPM Connection onffline");
1621                serviceModeProgrammerLabel.setText(
1622                        Bundle.getMessage("ServiceModeProgOffline", gpm.getUserName()));
1623                serviceModeProgrammerLabel.setForeground(Color.red);
1624            }
1625            if (oldServMode == null) {
1626                log.debug("Re-enable user interface");
1627                contextService.setEnabled(isProgrammingTrackEnabled());
1628                contextService.setVisible(true);
1629                service.setEnabled(isProgrammingTrackEnabled());
1630                service.setVisible(true);
1631                firePropertyChange("setprogservice", "setEnabled", true);
1632                getToolBar().getComponents()[1].setEnabled(true);
1633            }
1634        } else {
1635            // No service programmer available, disable interface sections not available
1636            log.debug("no service programmer");
1637            serviceModeProgrammerLabel.setText(Bundle.getMessage("NoServiceProgrammerAvailable"));
1638            serviceModeProgrammerLabel.setForeground(Color.red);
1639            if (oldServMode != null) {
1640                contextService.setEnabled(false);
1641                contextService.setVisible(false);
1642                service.setEnabled(false);
1643                service.setVisible(false);
1644                firePropertyChange("setprogservice", "setEnabled", false);
1645            }
1646            // Disable Identify in toolBar
1647            // This relies on it being the 2nd item in the toolbar, as defined in xml//config/parts/jmri/jmrit/roster/swing/RosterFrameToolBar.xml
1648            // Because of I18N, we don't look for a particular Action name here
1649            getToolBar().getComponents()[1].setEnabled(false);
1650            serModeProCon = null;
1651        }
1652
1653        if (opsModeProCon != null && apm != null && apm.isAddressedModePossible()) {
1654            if (ConnectionStatus.instance().isConnectionOk(opsModeProCon.getConnectionName(), opsModeProCon.getInfo())) {
1655                log.debug("Ops Mode Connection online");
1656                operationsModeProgrammerLabel.setText(
1657                        Bundle.getMessage("OpsModeProgOnline", opsModeProCon.getConnectionName()));
1658                operationsModeProgrammerLabel.setForeground(new Color(0, 128, 0));
1659            } else {
1660                log.debug("Ops Mode Connection offline");
1661                operationsModeProgrammerLabel.setText(
1662                        Bundle.getMessage("OpsModeProgOffline", opsModeProCon.getConnectionName()));
1663                operationsModeProgrammerLabel.setForeground(Color.red);
1664            }
1665            if (oldOpsMode == null) {
1666                contextOps.setEnabled(isProgrammingOnMainEnabled());
1667                contextOps.setVisible(true);
1668                ops.setEnabled(isProgrammingOnMainEnabled());
1669                ops.setVisible(true);
1670                firePropertyChange("setprogops", "setEnabled", true);
1671            }
1672        } else if (apm != null && apm.isAddressedModePossible()) {
1673            if (ConnectionStatus.instance().isSystemOk(apm.getUserName())) {
1674                log.debug("Ops Mode Connection online");
1675                operationsModeProgrammerLabel.setText(
1676                        Bundle.getMessage("OpsModeProgOnline", apm.getUserName()));
1677                operationsModeProgrammerLabel.setForeground(new Color(0, 128, 0));
1678            } else {
1679                log.debug("Ops Mode Connection offline");
1680                operationsModeProgrammerLabel.setText(
1681                        Bundle.getMessage("OpsModeProgOffline", apm.getUserName()));
1682                operationsModeProgrammerLabel.setForeground(Color.red);
1683            }
1684            if (oldOpsMode == null) {
1685                contextOps.setEnabled(isProgrammingOnMainEnabled());
1686                contextOps.setVisible(true);
1687                ops.setEnabled(isProgrammingOnMainEnabled());
1688                ops.setVisible(true);
1689                firePropertyChange("setprogops", "setEnabled", true);
1690            }
1691        } else {
1692            // No ops mode programmer available, disable interface sections not available
1693            log.debug("no ops mode programmer");
1694            operationsModeProgrammerLabel.setText(Bundle.getMessage("NoOpsProgrammerAvailable"));
1695            operationsModeProgrammerLabel.setForeground(Color.red);
1696            if (oldOpsMode != null) {
1697                contextOps.setEnabled(false);
1698                contextOps.setVisible(false);
1699                ops.setEnabled(false);
1700                ops.setVisible(false);
1701                firePropertyChange("setprogops", "setEnabled", false);
1702            }
1703            opsModeProCon = null;
1704        }
1705        String strProgMode;
1706        if (service.isEnabled()) {
1707            contextService.setSelected(true);
1708            service.setSelected(true);
1709            strProgMode = "setprogservice";
1710            modePanel.setVisible(true);
1711        } else if (ops.isEnabled()) {
1712            contextOps.setSelected(true);
1713            ops.setSelected(true);
1714            strProgMode = "setprogops";
1715            modePanel.setVisible(false);
1716        } else {
1717            contextEdit.setSelected(true);
1718            edit.setSelected(true);
1719            modePanel.setVisible(false);
1720            strProgMode = "setprogedit";
1721        }
1722        firePropertyChange(strProgMode, "setSelected", true);
1723    }
1724
1725    private boolean isProgrammingTrackEnabled() {
1726        return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null &&
1727                ! InstanceManager.getDefault(ProgrammerConfigManager.class).isDisableProgrammingTrack();
1728    }
1729
1730    private boolean isProgrammingOnMainEnabled() {
1731        return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null &&
1732                ! InstanceManager.getDefault(ProgrammerConfigManager.class).isDisableProgrammingOnMain();
1733    }
1734
1735    @Override
1736    public void windowClosing(WindowEvent e) {
1737        closeWindow(e);
1738        super.windowClosing(e);
1739    }
1740
1741    /**
1742     * Displays a context (right-click) menu for a roster entry.
1743     */
1744    private class RosterPopupListener extends JmriMouseAdapter {
1745
1746        @Override
1747        public void mousePressed(JmriMouseEvent e) {
1748            if (e.isPopupTrigger()) {
1749                showPopup(e);
1750            }
1751        }
1752
1753        @Override
1754        public void mouseReleased(JmriMouseEvent e) {
1755            if (e.isPopupTrigger()) {
1756                showPopup(e);
1757            }
1758        }
1759
1760        @Override
1761        public void mouseClicked(JmriMouseEvent e) {
1762            if (e.isPopupTrigger()) {
1763                showPopup(e);
1764                return;
1765            }
1766            if (e.getClickCount() == 2) {
1767                startProgrammer(null, re, programmer1);
1768            }
1769        }
1770    }
1771
1772    private static class ExportRosterItem extends ExportRosterItemAction {
1773
1774        ExportRosterItem(String pName, Component pWho, RosterEntry re) {
1775            super(pName, pWho);
1776            super.setExistingEntry(re);
1777        }
1778
1779        @Override
1780        protected boolean selectFrom() {
1781            return true;
1782        }
1783    }
1784
1785    private static class CopyRosterItem extends CopyRosterItemAction {
1786
1787        CopyRosterItem(String pName, Component pWho, RosterEntry re) {
1788            super(pName, pWho);
1789            super.setExistingEntry(re);
1790        }
1791
1792        @Override
1793        protected boolean selectFrom() {
1794            return true;
1795        }
1796    }
1797    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RosterFrame.class);
1798
1799}