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