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