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}