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