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("addgroup", "setEnabled", enable); 599 } 600 601 protected void exportLoco() { 602 ExportRosterItem act = new ExportRosterItem(Bundle.getMessage("Export"), this, re); 603 act.actionPerformed(null); 604 } 605 606 void formatTextAreaAsLabel(JTextPane pane) { 607 pane.setOpaque(false); 608 pane.setEditable(false); 609 pane.setBorder(null); 610 } 611 612 /*=============== Getters and Setters for core properties ===============*/ 613 614 /** 615 * @return Will closing the window quit JMRI? 616 */ 617 public boolean isAllowQuit() { 618 return allowQuit; 619 } 620 621 /** 622 * @param allowQuit Set state to either close JMRI or just the roster window 623 */ 624 public void setAllowQuit(boolean allowQuit) { 625 allowQuit(allowQuit); 626 } 627 628 /** 629 * @return the baseTitle 630 */ 631 protected String getBaseTitle() { 632 return baseTitle; 633 } 634 635 /** 636 * @param baseTitle the baseTitle to set 637 */ 638 protected final void setBaseTitle(String baseTitle) { 639 String title = null; 640 if (this.baseTitle == null) { 641 title = this.getTitle(); 642 } 643 this.baseTitle = baseTitle; 644 if (title != null) { 645 this.setTitle(title); 646 } 647 } 648 649 /** 650 * @return the newWindowAction 651 */ 652 protected JmriAbstractAction getNewWindowAction() { 653 if (newWindowAction == null) { 654 newWindowAction = new RosterFrameAction("newWindow", this, allowQuit); 655 } 656 return newWindowAction; 657 } 658 659 /** 660 * @param newWindowAction the newWindowAction to set 661 */ 662 protected void setNewWindowAction(JmriAbstractAction newWindowAction) { 663 this.newWindowAction = newWindowAction; 664 this.groups.setNewWindowMenuAction(newWindowAction); 665 } 666 667 @Override 668 public void setTitle(String title) { 669 if (title == null || title.isEmpty()) { 670 title = Roster.ALLENTRIES; 671 } 672 if (this.baseTitle != null) { 673 if (!title.equals(this.baseTitle) && !title.startsWith(this.baseTitle)) { 674 super.setTitle(this.baseTitle + ": " + title); 675 } 676 } else { 677 super.setTitle(title); 678 } 679 } 680 681 @Override 682 public Object getProperty(String key) { 683 if (key.equalsIgnoreCase(SELECTED_ROSTER_GROUP)) { 684 return getSelectedRosterGroup(); 685 } else if (key.equalsIgnoreCase("hideSummary")) { 686 return hideBottomPane; 687 } 688 // call parent getProperty method to return any properties defined 689 // in the class hierarchy. 690 return super.getProperty(key); 691 } 692 693 public Object getRemoteObject(String value) { 694 return getProperty(value); 695 } 696 697 @Override 698 public RosterEntry[] getSelectedRosterEntries() { 699 RosterEntry[] entries = rtable.getSelectedRosterEntries(); 700 return Arrays.copyOf(entries, entries.length); 701 } 702 703 public RosterEntry[] getAllRosterEntries() { 704 RosterEntry[] entries = rtable.getSortedRosterEntries(); 705 return Arrays.copyOf(entries, entries.length); 706 } 707 708 @Override 709 public String getSelectedRosterGroup() { 710 return groups.getSelectedRosterGroup(); 711 } 712 713 protected ProgrammerConfigManager getProgrammerConfigManager() { 714 return InstanceManager.getDefault(ProgrammerConfigManager.class); 715 } 716 717 void handleQuit(WindowEvent e) { 718 if (e != null && frameInstances.size() == 1) { 719 final String rememberWindowClose = this.getClass().getName() + ".closeDP3prompt"; 720 if (!prefsMgr.getSimplePreferenceState(rememberWindowClose)) { 721 JPanel message = new JPanel(); 722 JLabel question = new JLabel(rb.getString("MessageLongCloseWarning")); 723 final JCheckBox remember = new JCheckBox(rb.getString("MessageRememberSetting")); 724 remember.setFont(remember.getFont().deriveFont(10.0F)); 725 message.setLayout(new BoxLayout(message, BoxLayout.Y_AXIS)); 726 message.add(question); 727 message.add(remember); 728 int result = JmriJOptionPane.showConfirmDialog(null, 729 message, 730 rb.getString("MessageShortCloseWarning"), 731 JmriJOptionPane.YES_NO_OPTION); 732 if (remember.isSelected()) { 733 prefsMgr.setSimplePreferenceState(rememberWindowClose, true); 734 } 735 if (result == JmriJOptionPane.YES_OPTION) { 736 handleQuit(); 737 } 738 } else { 739 handleQuit(); 740 } 741 } else if (frameInstances.size() > 1) { 742 final String rememberWindowClose = this.getClass().getName() + ".closeMultipleDP3prompt"; 743 if (!prefsMgr.getSimplePreferenceState(rememberWindowClose)) { 744 JPanel message = new JPanel(); 745 JLabel question = new JLabel(rb.getString("MessageLongMultipleCloseWarning")); 746 final JCheckBox remember = new JCheckBox(rb.getString("MessageRememberSetting")); 747 remember.setFont(remember.getFont().deriveFont(10.0F)); 748 message.setLayout(new BoxLayout(message, BoxLayout.Y_AXIS)); 749 message.add(question); 750 message.add(remember); 751 int result = JmriJOptionPane.showConfirmDialog(null, 752 message, 753 rb.getString("MessageShortCloseWarning"), 754 JmriJOptionPane.YES_NO_OPTION); 755 if (remember.isSelected()) { 756 prefsMgr.setSimplePreferenceState(rememberWindowClose, true); 757 } 758 if (result == JmriJOptionPane.YES_OPTION) { 759 handleQuit(); 760 } 761 } else { 762 handleQuit(); 763 } 764 //closeWindow(null); 765 } 766 } 767 768 private void handleQuit(){ 769 try { 770 InstanceManager.getDefault(jmri.ShutDownManager.class).shutdown(); 771 } catch (Exception e) { 772 log.error("Continuing after error in handleQuit", e); 773 } 774 } 775 776 protected void helpMenu(JMenuBar menuBar, final JFrame frame) { 777 // create menu and standard items 778 JMenu helpMenu = HelpUtil.makeHelpMenu("package.apps.gui3.dp3.DecoderPro3", true); 779 // use as main help menu 780 menuBar.add(helpMenu); 781 } 782 783 protected void hideGroups() { 784 boolean boo = !hideGroups; 785 hideGroupsPane(boo); 786 } 787 788 public void hideGroupsPane(boolean hide) { 789 if (hideGroups == hide) { 790 return; 791 } 792 hideGroups = hide; 793 if (hide) { 794 groupSplitPaneLocation = rosterGroupSplitPane.getDividerLocation(); 795 rosterGroupSplitPane.setDividerLocation(1); 796 rosterGroupSplitPane.getLeftComponent().setMinimumSize(new Dimension()); 797 if (Roster.getDefault().getRosterGroupList().isEmpty()) { 798 rosterGroupSplitPane.setOneTouchExpandable(false); 799 rosterGroupSplitPane.setDividerSize(0); 800 } 801 } else { 802 rosterGroupSplitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize")); 803 rosterGroupSplitPane.setOneTouchExpandable(true); 804 if (groupSplitPaneLocation >= 2) { 805 rosterGroupSplitPane.setDividerLocation(groupSplitPaneLocation); 806 } else { 807 rosterGroupSplitPane.resetToPreferredSizes(); 808 } 809 } 810 } 811 812 protected void hideRosterImage() { 813 hideRosterImage = !hideRosterImage; 814 //p.setSimplePreferenceState(DecoderPro3Window.class.getName()+".hideRosterImage",hideRosterImage); 815 if (hideRosterImage) { 816 locoImage.setVisible(false); 817 } else { 818 locoImage.setVisible(true); 819 } 820 } 821 822 protected void hideSummary() { 823 boolean boo = !hideBottomPane; 824 hideBottomPane(boo); 825 } 826 827 /** 828 * An entry has been selected in the Roster Table, activate the bottom part 829 * of the window. 830 * 831 * @param id ID of the selected roster entry 832 */ 833 final void locoSelected(String id) { 834 if (id != null) { 835 log.debug("locoSelected ID {}", id); 836 if (re != null) { 837 // we remove the propertychangelistener if we had a previously selected entry; 838 re.removePropertyChangeListener(rosterEntryUpdateListener); 839 } 840 // convert to roster entry 841 re = Roster.getDefault().entryFromTitle(id); 842 re.addPropertyChangeListener(rosterEntryUpdateListener); 843 } else { 844 log.debug("Multiple selection"); 845 re = null; 846 } 847 updateDetails(); 848 } 849 850 protected void newWindow() { 851 this.newWindow(this.getNewWindowAction()); 852 } 853 854 protected void newWindow(JmriAbstractAction action) { 855 action.setWindowInterface(this); 856 action.actionPerformed(null); 857 firePropertyChange("closewindow", "setEnabled", true); 858 } 859 860 /** 861 * Prepare a roster entry to be printed, and display a selection list. 862 * 863 * @see jmri.jmrit.roster.PrintRosterEntry#printPanes(boolean) 864 * @param preview true if output should go to a Preview pane on screen, false 865 * to output to a printer (dialog) 866 */ 867 protected void printLoco(boolean preview) { 868 log.debug("Selected entry: {}", re.getDisplayName()); 869 String programmer = "Basic"; 870 if (this.getProgrammerConfigManager().getDefaultFile() != null) { 871 programmer = this.getProgrammerConfigManager().getDefaultFile(); 872 } else { 873 log.error("programmer is NULL"); 874 } 875 PrintRosterEntry pre = new PrintRosterEntry(re, this, "programmers" + File.separator + programmer + ".xml"); 876 // uses programmer set in prefs when printing a selected entry from (this) top Roster frame 877 // compare with: jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame#printPanes(boolean) 878 // as user expects to see more tabs on printout using Comprehensive or just 1 tab for Basic programmer 879 pre.printPanes(preview); 880 } 881 882 /** 883 * Print the displayed table, as displayed. 884 * 885 */ 886 protected void printCurrentTable() { 887 try { 888 var cal = java.util.Calendar.getInstance(); 889 var sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm"); 890 String time = sdf.format(cal.getTime()); 891 892 var selectedRosterGroup = getSelectedRosterGroup(); 893 String g = (selectedRosterGroup != null) ? selectedRosterGroup : "All Entries"; 894 String group = String.format("%-20s",g); // pad to right to fixed length 895 896 rtable.getTable().print(javax.swing.JTable.PrintMode.FIT_WIDTH, 897 null, // no header 898 new java.text.MessageFormat(group+" - {0} - "+time) // spaces for heuristic formatting, don't change 899 ); 900 } catch (java.awt.print.PrinterException ep) { 901 log.error("While printing",ep); 902 } 903 } 904 905 /** 906 * Match the first argument in the array against a locally-known method. 907 * 908 * @param args Array of arguments, we take with element 0 909 */ 910 @Override 911 public void remoteCalls(String[] args) { 912 args[0] = args[0].toLowerCase(); 913 switch (args[0]) { 914 case "identifyloco": 915 startIdentifyLoco(); 916 break; 917 case "printcurrenttable": 918 printCurrentTable(); 919 break; 920 case "printloco": 921 if (checkIfEntrySelected()) { 922 printLoco(false); 923 } 924 break; 925 case "printpreviewloco": 926 if (checkIfEntrySelected()) { 927 printLoco(true); 928 } 929 break; 930 case "exportloco": 931 if (checkIfEntrySelected()) { 932 exportLoco(); 933 } 934 break; 935 case "basicprogrammer": 936 if (checkIfEntrySelected()) { 937 startProgrammer(null, re, programmer2); 938 } 939 break; 940 case "comprehensiveprogrammer": 941 if (checkIfEntrySelected()) { 942 startProgrammer(null, re, programmer1); 943 } 944 break; 945 case "editthrottlelabels": 946 if (checkIfEntrySelected()) { 947 startProgrammer(null, re, "dp3" + File.separator + "ThrottleLabels"); 948 } 949 break; 950 case "editrostermedia": 951 if (checkIfEntrySelected()) { 952 startProgrammer(null, re, "dp3" + File.separator + "MediaPane"); 953 } 954 break; 955 case "hiderosterimage": 956 hideRosterImage(); 957 break; 958 case "summarypane": 959 hideSummary(); 960 break; 961 case "copyloco": 962 if (checkIfEntrySelected()) { 963 copyLoco(); 964 } 965 break; 966 case "deleteloco": 967 if (checkIfEntrySelected(true)) { 968 deleteLoco(); 969 } 970 break; 971 case "setprogservice": 972 service.setSelected(true); 973 break; 974 case "setprogops": 975 ops.setSelected(true); 976 break; 977 case "setprogedit": 978 edit.setSelected(true); 979 break; 980 case "groupspane": 981 hideGroups(); 982 break; 983 case "quit": 984 saveWindowDetails(); 985 handleQuit(new WindowEvent(this, frameInstances.size())); 986 break; 987 case "closewindow": 988 closeWindow(null); 989 break; 990 case "newwindow": 991 newWindow(); 992 break; 993 case "resettablecolumns": 994 rtable.resetColumnWidths(); 995 break; 996 default: 997 log.error("method {} not found", args[0]); 998 break; 999 } 1000 } 1001 1002 JPanel rosterDetails() { 1003 JPanel panel = new JPanel(); 1004 GridBagLayout gbLayout = new GridBagLayout(); 1005 GridBagConstraints cL = new GridBagConstraints(); 1006 GridBagConstraints cR = new GridBagConstraints(); 1007 Dimension minFieldDim = new Dimension(30, 20); 1008 cL.gridx = 0; 1009 cL.gridy = 0; 1010 cL.ipadx = 3; 1011 cL.anchor = GridBagConstraints.EAST; 1012 cL.insets = new Insets(0, 0, 0, 15); 1013 JLabel row0Label = new JLabel(Bundle.getMessage("FieldID") + ":", JLabel.LEFT); 1014 gbLayout.setConstraints(row0Label, cL); 1015 panel.setLayout(gbLayout); 1016 panel.add(row0Label); 1017 cR.gridx = 1; 1018 cR.gridy = 0; 1019 cR.anchor = GridBagConstraints.WEST; 1020 id.setMinimumSize(minFieldDim); 1021 gbLayout.setConstraints(id, cR); 1022 formatTextAreaAsLabel(id); 1023 panel.add(id); 1024 cL.gridy = 1; 1025 JLabel row1Label = new JLabel(Bundle.getMessage("FieldRoadName") + ":", JLabel.LEFT); 1026 gbLayout.setConstraints(row1Label, cL); 1027 panel.add(row1Label); 1028 cR.gridy = 1; 1029 roadName.setMinimumSize(minFieldDim); 1030 gbLayout.setConstraints(roadName, cR); 1031 formatTextAreaAsLabel(roadName); 1032 panel.add(roadName); 1033 cL.gridy = 2; 1034 JLabel row2Label = new JLabel(Bundle.getMessage("FieldRoadNumber") + ":"); 1035 gbLayout.setConstraints(row2Label, cL); 1036 panel.add(row2Label); 1037 cR.gridy = 2; 1038 roadNumber.setMinimumSize(minFieldDim); 1039 gbLayout.setConstraints(roadNumber, cR); 1040 formatTextAreaAsLabel(roadNumber); 1041 panel.add(roadNumber); 1042 cL.gridy = 3; 1043 JLabel row3Label = new JLabel(Bundle.getMessage("FieldManufacturer") + ":"); 1044 gbLayout.setConstraints(row3Label, cL); 1045 panel.add(row3Label); 1046 cR.gridy = 3; 1047 mfg.setMinimumSize(minFieldDim); 1048 gbLayout.setConstraints(mfg, cR); 1049 formatTextAreaAsLabel(mfg); 1050 panel.add(mfg); 1051 cL.gridy = 4; 1052 JLabel row4Label = new JLabel(Bundle.getMessage("FieldOwner") + ":"); 1053 gbLayout.setConstraints(row4Label, cL); 1054 panel.add(row4Label); 1055 cR.gridy = 4; 1056 owner.setMinimumSize(minFieldDim); 1057 gbLayout.setConstraints(owner, cR); 1058 formatTextAreaAsLabel(owner); 1059 panel.add(owner); 1060 cL.gridy = 5; 1061 JLabel row5Label = new JLabel(Bundle.getMessage("FieldModel") + ":"); 1062 gbLayout.setConstraints(row5Label, cL); 1063 panel.add(row5Label); 1064 cR.gridy = 5; 1065 model.setMinimumSize(minFieldDim); 1066 gbLayout.setConstraints(model, cR); 1067 formatTextAreaAsLabel(model); 1068 panel.add(model); 1069 cL.gridy = 6; 1070 JLabel row6Label = new JLabel(Bundle.getMessage("FieldDCCAddress") + ":"); 1071 gbLayout.setConstraints(row6Label, cL); 1072 panel.add(row6Label); 1073 cR.gridy = 6; 1074 dccAddress.setMinimumSize(minFieldDim); 1075 gbLayout.setConstraints(dccAddress, cR); 1076 formatTextAreaAsLabel(dccAddress); 1077 panel.add(dccAddress); 1078 cL.gridy = 7; 1079 cR.gridy = 7; 1080 cL.gridy = 8; 1081 cR.gridy = 8; 1082 cL.gridy = 9; 1083 JLabel row9Label = new JLabel(Bundle.getMessage("FieldDecoderFamily") + ":"); 1084 gbLayout.setConstraints(row9Label, cL); 1085 panel.add(row9Label); 1086 cR.gridy = 9; 1087 decoderFamily.setMinimumSize(minFieldDim); 1088 gbLayout.setConstraints(decoderFamily, cR); 1089 formatTextAreaAsLabel(decoderFamily); 1090 panel.add(decoderFamily); 1091 cL.gridy = 10; 1092 JLabel row10Label = new JLabel(Bundle.getMessage("FieldDecoderModel") + ":"); 1093 gbLayout.setConstraints(row10Label, cL); 1094 panel.add(row10Label); 1095 cR.gridy = 10; 1096 decoderModel.setMinimumSize(minFieldDim); 1097 gbLayout.setConstraints(decoderModel, cR); 1098 formatTextAreaAsLabel(decoderModel); 1099 panel.add(decoderModel); 1100 cL.gridy = 11; 1101 cR.gridy = 11; 1102 cL.gridy = 12; 1103 JLabel row12Label = new JLabel(Bundle.getMessage("FieldFilename") + ":"); 1104 gbLayout.setConstraints(row12Label, cL); 1105 panel.add(row12Label); 1106 cR.gridy = 12; 1107 filename.setMinimumSize(minFieldDim); 1108 gbLayout.setConstraints(filename, cR); 1109 formatTextAreaAsLabel(filename); 1110 panel.add(filename); 1111 cL.gridy = 13; 1112 /* 1113 * JLabel row13Label = new 1114 * JLabel(Bundle.getMessage("FieldDateUpdated")+":"); 1115 * gbLayout.setConstraints(row13Label,cL); panel.add(row13Label); 1116 */ 1117 cR.gridy = 13; 1118 /* 1119 * filename.setMinimumSize(minFieldDim); 1120 * gbLayout.setConstraints(dateUpdated,cR); panel.add(dateUpdated); 1121 */ 1122 formatTextAreaAsLabel(dateUpdated); 1123 JPanel retval = new JPanel(new FlowLayout(FlowLayout.LEFT)); 1124 retval.add(panel); 1125 return retval; 1126 } 1127 1128 void saveWindowDetails() { 1129 prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideSummary", hideBottomPane); 1130 prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideGroups", hideGroups); 1131 prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideRosterImage", hideRosterImage); 1132 prefsMgr.setProperty(getWindowFrameRef(), SELECTED_ROSTER_GROUP, groups.getSelectedRosterGroup()); 1133 String selectedProgMode = "edit"; 1134 if (service.isSelected()) { 1135 selectedProgMode = "service"; 1136 } 1137 if (ops.isSelected()) { 1138 selectedProgMode = "ops"; 1139 } 1140 prefsMgr.setProperty(getWindowFrameRef(), "selectedProgrammer", selectedProgMode); 1141 1142 if (rosterGroupSplitPane.getDividerLocation() > 2) { 1143 prefsMgr.setProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation", rosterGroupSplitPane.getDividerLocation()); 1144 } else if (groupSplitPaneLocation > 2) { 1145 prefsMgr.setProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation", groupSplitPaneLocation); 1146 } 1147 } 1148 1149 /** 1150 * Identify locomotive complete, act on it by setting the GUI. This will 1151 * fire "GUI changed" events which will reset the decoder GUI. 1152 * 1153 * @param dccAddress address of locomotive 1154 * @param isLong true if address is long; false if short 1155 * @param mfgId manufacturer id as in decoder 1156 * @param modelId model id as in decoder 1157 */ 1158 protected void selectLoco(int dccAddress, boolean isLong, int mfgId, int modelId) { 1159 // raise the button again 1160 // idloco.setSelected(false); 1161 // locate that loco 1162 inStartProgrammer = false; 1163 if (re != null) { 1164 //We remove the propertychangelistener if we had a previoulsy selected entry; 1165 re.removePropertyChangeListener(rosterEntryUpdateListener); 1166 } 1167 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, Integer.toString(dccAddress), null, null, null, null); 1168 log.debug("selectLoco found {} matches", l.size()); 1169 if (!l.isEmpty()) { 1170 if (l.size() > 1) { 1171 //More than one possible loco, so check long flag 1172 List<RosterEntry> l2 = new ArrayList<>(); 1173 for (RosterEntry _re : l) { 1174 if (_re.isLongAddress() == isLong) { 1175 l2.add(_re); 1176 } 1177 } 1178 if (l2.size() == 1) { 1179 re = l2.get(0); 1180 } else { 1181 if (l2.isEmpty()) { 1182 l2 = l; 1183 } 1184 // Still more than one possible loco, so check against the decoder family 1185 log.trace("Checking against decoder family with mfg {} model {}", mfgId, modelId); 1186 List<RosterEntry> l3 = new ArrayList<>(); 1187 List<DecoderFile> temp = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, "" + mfgId, "" + modelId, null, null); 1188 log.trace("found {}", temp.size()); 1189 ArrayList<String> decoderFam = new ArrayList<>(); 1190 for (DecoderFile f : temp) { 1191 if (!decoderFam.contains(f.getModel())) { 1192 decoderFam.add(f.getModel()); 1193 } 1194 } 1195 log.trace("matched {} times", decoderFam.size()); 1196 1197 for (RosterEntry _re : l2) { 1198 if (decoderFam.contains(_re.getDecoderModel())) { 1199 l3.add(_re); 1200 } 1201 } 1202 if (l3.isEmpty()) { 1203 //Unable to determine the loco against the manufacture therefore will be unable to further identify against decoder. 1204 re = l2.get(0); 1205 } else { 1206 //We have no other options to match against so will return the first one we come across; 1207 re = l3.get(0); 1208 } 1209 } 1210 } else { 1211 re = l.get(0); 1212 } 1213 re.addPropertyChangeListener(rosterEntryUpdateListener); 1214 rtable.setSelection(re); 1215 updateDetails(); 1216 rtable.moveTableViewToSelected(); 1217 } else { 1218 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" 1219 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NotFoundError", dccAddress), Bundle.getMessage("NotFoundErrorTitle", dccAddress), JmriJOptionPane.INFORMATION_MESSAGE); 1220 } 1221 } 1222 1223 /** 1224 * Simple method to change over the programmer buttons. 1225 * <p> 1226 * TODO This should be implemented with the buttons in their own class etc. 1227 * but this will work for now. 1228 * 1229 * @param buttonId 1 or 2; use 1 for basic programmer; 2 for comprehensive 1230 * programmer 1231 * @param programmer name of programmer 1232 * @param buttonText button title 1233 */ 1234 public void setProgrammerLaunch(int buttonId, String programmer, String buttonText) { 1235 if (buttonId == 1) { 1236 programmer1 = programmer; 1237 prog1Button.setText(buttonText); 1238 } else if (buttonId == 2) { 1239 programmer2 = programmer; 1240 prog2Button.setText(buttonText); 1241 } 1242 } 1243 1244 public void setSelectedRosterGroup(String rosterGroup) { 1245 groups.setSelectedRosterGroup(rosterGroup); 1246 } 1247 1248 protected void showPopup(JmriMouseEvent e) { 1249 int row = rtable.getTable().rowAtPoint(e.getPoint()); 1250 if (!rtable.getTable().isRowSelected(row)) { 1251 rtable.getTable().changeSelection(row, 0, false, false); 1252 } 1253 JPopupMenu popupMenu = new JPopupMenu(); 1254 1255 JMenuItem menuItem = new JMenuItem(Bundle.getMessage("Program")); 1256 menuItem.addActionListener((ActionEvent e1) -> startProgrammer(null, re, programmer1)); 1257 if (re == null) { 1258 menuItem.setEnabled(false); 1259 } 1260 popupMenu.add(menuItem); 1261 ButtonGroup group = new ButtonGroup(); 1262 group.add(contextService); 1263 group.add(contextOps); 1264 group.add(contextEdit); 1265 JMenu progMenu = new JMenu(Bundle.getMessage("ProgrammerType")); 1266 contextService.addActionListener((ActionEvent e1) -> { 1267 service.setSelected(true); 1268 updateProgMode(); 1269 }); 1270 progMenu.add(contextService); 1271 contextOps.addActionListener((ActionEvent e1) -> { 1272 ops.setSelected(true); 1273 updateProgMode(); 1274 }); 1275 progMenu.add(contextOps); 1276 contextEdit.addActionListener((ActionEvent e1) -> { 1277 edit.setSelected(true); 1278 updateProgMode(); 1279 }); 1280 if (service.isSelected()) { 1281 contextService.setSelected(true); 1282 } else if (ops.isSelected()) { 1283 contextOps.setSelected(true); 1284 } else { 1285 contextEdit.setSelected(true); 1286 } 1287 progMenu.add(contextEdit); 1288 popupMenu.add(progMenu); 1289 1290 popupMenu.addSeparator(); 1291 menuItem = new JMenuItem(Bundle.getMessage("LabelsAndMedia")); 1292 menuItem.addActionListener((ActionEvent e1) -> editMediaButton()); 1293 if (re == null) { 1294 menuItem.setEnabled(false); 1295 } 1296 popupMenu.add(menuItem); 1297 menuItem = new JMenuItem(Bundle.getMessage("Throttle")); 1298 menuItem.addActionListener((ActionEvent e1) -> { 1299 ThrottleControllersUIContainer tw = null; 1300 for (RosterEntry re : rtable.getSelectedRosterEntries()) { 1301 ThrottleControllerUI tf; 1302 if (tw == null) { 1303 tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame(); 1304 tw = tf.getThrottleControllersContainer(); 1305 } else { 1306 tf = tw.newThrottleController(); 1307 } 1308 tf.toFront(); 1309 tf.setRosterEntry(re); 1310 } 1311 }); 1312 popupMenu.add(menuItem); 1313 menuItem = new JMenuItem(Bundle.getMessage("SimpleThrottle")); 1314 menuItem.addActionListener((ActionEvent e1) -> { 1315 ThrottleControllerUI tf =InstanceManager.getDefault(ThrottleFrameManager.class).createSimpleThrottleFrame(re); 1316 tf.toFront(); 1317 }); 1318 if (re == null) { 1319 menuItem.setEnabled(false); 1320 } 1321 popupMenu.add(menuItem); 1322 popupMenu.addSeparator(); 1323 1324 menuItem = new JMenuItem(Bundle.getMessage("PrintSelection")); 1325 menuItem.addActionListener((ActionEvent e1) -> printLoco(false)); 1326 if (re == null) { 1327 menuItem.setEnabled(false); 1328 } 1329 popupMenu.add(menuItem); 1330 menuItem = new JMenuItem(Bundle.getMessage("PreviewSelection")); 1331 menuItem.addActionListener((ActionEvent e1) -> printLoco(true)); 1332 if (re == null) { 1333 menuItem.setEnabled(false); 1334 } 1335 popupMenu.add(menuItem); 1336 popupMenu.addSeparator(); 1337 1338 menuItem = new JMenuItem(Bundle.getMessage("Duplicateddd")); 1339 menuItem.addActionListener((ActionEvent e1) -> copyLoco()); 1340 if (re == null) { 1341 menuItem.setEnabled(false); 1342 } 1343 popupMenu.add(menuItem); 1344 boolean deleteFromGroup = this.getSelectedRosterGroup() != null && !this.getSelectedRosterGroup().equals(Roster.NOGROUP); 1345 menuItem = new JMenuItem(deleteFromGroup ? Bundle.getMessage("DeleteFromGroup") : Bundle.getMessage("DeleteFromRoster")); // NOI18N 1346 menuItem.addActionListener((ActionEvent e1) -> deleteLoco()); 1347 popupMenu.add(menuItem); 1348 menuItem.setEnabled(this.getSelectedRosterEntries().length > 0); 1349 1350 menuItem = new JMenuItem(new jmri.jmrit.roster.swing.RosterEntryToGroupAction(Bundle.getMessage("AddToGroup"), re)); 1351 if (re == null) { 1352 menuItem.setEnabled(false); 1353 } 1354 popupMenu.add(menuItem); 1355 1356 popupMenu.show(e.getComponent(), e.getX(), e.getY()); 1357 } 1358 1359 /** 1360 * Start the identify operation after [Identify Loco] button pressed. 1361 * <p> 1362 * This defines what happens when Identify is done. 1363 */ 1364 //taken out of CombinedLocoSelPane 1365 protected void startIdentifyLoco() { 1366 final RosterFrame me = this; 1367 Programmer programmer = null; 1368 if (modePanel.isSelected()) { 1369 programmer = modePanel.getProgrammer(); 1370 } 1371 if (programmer == null) { 1372 GlobalProgrammerManager gpm = InstanceManager.getNullableDefault(GlobalProgrammerManager.class); 1373 if (gpm != null) { 1374 programmer = gpm.getGlobalProgrammer(); 1375 log.warn("Selector did not provide a programmer, attempt to use GlobalProgrammerManager default: {}", programmer); 1376 } else { 1377 log.warn("Selector did not provide a programmer, and no ProgramManager found in InstanceManager"); 1378 } 1379 } 1380 1381 // if failed to get programmer, tell user and stop 1382 if (programmer == null) { 1383 log.error("Identify loco called when no service mode programmer is available; button should have been disabled"); 1384 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("IdentifyError")); 1385 return; 1386 } 1387 1388 // and now do the work 1389 IdentifyLoco ident = new IdentifyLoco(programmer) { 1390 private final RosterFrame who = me; 1391 1392 @Override 1393 protected void done(int dccAddress) { 1394 // if Done, updated the selected decoder 1395 // on the GUI thread, right now 1396 jmri.util.ThreadingUtil.runOnGUI(() -> who.selectLoco(dccAddress, !shortAddr, cv8val, cv7val)); 1397 } 1398 1399 @Override 1400 protected void message(String m) { 1401 // on the GUI thread, right now 1402 jmri.util.ThreadingUtil.runOnGUI(() -> statusField.setText(m)); 1403 } 1404 1405 @Override 1406 protected void error() { 1407 // raise the button again 1408 //idloco.setSelected(false); 1409 } 1410 }; 1411 ident.start(); 1412 } 1413 1414 protected void startProgrammer(DecoderFile decoderFile, RosterEntry re, String filename) { 1415 if (inStartProgrammer) { 1416 log.debug("Call to start programmer has been called twice when the first call hasn't opened"); 1417 return; 1418 } 1419 if (!checkIfEntrySelected()) { 1420 return; 1421 } 1422 try { 1423 setCursor(new Cursor(Cursor.WAIT_CURSOR)); 1424 inStartProgrammer = true; 1425 String title = re.getId(); 1426 JFrame progFrame = null; 1427 if (edit.isSelected()) { 1428 progFrame = new PaneProgFrame(decoderFile, re, title, "programmers" + File.separator + filename + ".xml", null, false) { 1429 @Override 1430 protected JPanel getModePane() { 1431 return null; 1432 } // hide prog mode buttons pane 1433 }; 1434 } else if (service.isSelected()) { 1435 progFrame = new PaneServiceProgFrame(decoderFile, re, title, "programmers" + File.separator + filename + ".xml", modePanel.getProgrammer()); 1436 } else if (ops.isSelected()) { 1437 int address = Integer.parseInt(re.getDccAddress()); 1438 boolean longAddr = re.isLongAddress(); 1439 Programmer pProg = InstanceManager.getDefault(AddressedProgrammerManager.class).getAddressedProgrammer(longAddr, address); 1440 progFrame = new PaneOpsProgFrame(decoderFile, re, title, "programmers" + File.separator + filename + ".xml", pProg); 1441 } 1442 if (progFrame == null) { 1443 return; 1444 } 1445 progFrame.pack(); 1446 progFrame.setVisible(true); 1447 } finally { 1448 setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); 1449 } 1450 inStartProgrammer = false; 1451 } 1452 1453 /** 1454 * Create and display a status bar along the bottom edge of the Roster main 1455 * pane. 1456 * <p> 1457 * TODO This status bar needs sorting out properly 1458 */ 1459 protected void statusBar() { 1460 addToStatusBox(serviceModeProgrammerLabel, null); 1461 addToStatusBox(operationsModeProgrammerLabel, null); 1462 JLabel programmerStatusLabel = new JLabel(Bundle.getMessage("ProgrammerStatus")); 1463 statusField.setText(Bundle.getMessage("StateIdle")); 1464 addToStatusBox(programmerStatusLabel, statusField); 1465 Profile profile = ProfileManager.getDefault().getActiveProfile(); 1466 if (profile != null) { 1467 addToStatusBox(new JLabel(Bundle.getMessage("ActiveProfile", profile.getName())), null); 1468 } 1469 } 1470 1471 protected void systemsMenu() { 1472 ActiveSystemsMenu.addItems(getMenu()); 1473 getMenu().add(new WindowMenu(this)); 1474 } 1475 1476 void updateDetails() { 1477 if (re == null) { 1478 String value = (rtable.getTable().getSelectedRowCount() > 1) ? "Multiple Items Selected" : ""; 1479 filename.setText(value); 1480 dateUpdated.setText(value); 1481 decoderModel.setText(value); 1482 decoderFamily.setText(value); 1483 id.setText(value); 1484 roadName.setText(value); 1485 dccAddress.setText(value); 1486 roadNumber.setText(value); 1487 mfg.setText(value); 1488 model.setText(value); 1489 owner.setText(value); 1490 locoImage.setImagePath(null); 1491 service.setEnabled(false); 1492 ops.setEnabled(false); 1493 edit.setEnabled(false); 1494 prog1Button.setEnabled(false); 1495 prog2Button.setEnabled(false); 1496 throttleLabels.setEnabled(false); 1497 rosterMedia.setEnabled(false); 1498 throttleLaunch.setEnabled(false ); 1499 } else { 1500 filename.setText(re.getFileName()); 1501 dateUpdated.setText((re.getDateModified() != null) 1502 ? DateFormat.getDateTimeInstance().format(re.getDateModified()) 1503 : re.getDateUpdated()); 1504 decoderModel.setText(re.getDecoderModel()); 1505 decoderFamily.setText(re.getDecoderFamily()); 1506 dccAddress.setText(re.getDccAddress()); 1507 id.setText(re.getId()); 1508 roadName.setText(re.getRoadName()); 1509 roadNumber.setText(re.getRoadNumber()); 1510 mfg.setText(re.getMfg()); 1511 model.setText(re.getModel()); 1512 owner.setText(re.getOwner()); 1513 locoImage.setImagePath(re.getImagePath()); 1514 if (hideRosterImage) { 1515 locoImage.setVisible(false); 1516 } else { 1517 locoImage.setVisible(true); 1518 } 1519 service.setEnabled(isProgrammingTrackEnabled()); 1520 ops.setEnabled(isProgrammingOnMainEnabled()); 1521 edit.setEnabled(true); 1522 prog1Button.setEnabled(true); 1523 prog2Button.setEnabled(true); 1524 throttleLabels.setEnabled(true); 1525 rosterMedia.setEnabled(true); 1526 throttleLaunch.setEnabled(true); 1527 updateProgMode(); 1528 } 1529 } 1530 1531 void updateProgMode() { 1532 String progMode; 1533 if (service.isSelected()) { 1534 progMode = "setprogservice"; 1535 } else if (ops.isSelected()) { 1536 progMode = "setprogops"; 1537 } else { 1538 progMode = "setprogedit"; 1539 } 1540 firePropertyChange(progMode, "setSelected", true); 1541 } 1542 1543 /** 1544 * Handle setting up and updating the GUI for the types of programmer 1545 * available. 1546 * 1547 * @param evt the triggering event; if not null and if a removal of a 1548 * ProgrammerManager, care will be taken not to trigger the 1549 * automatic creation of a new ProgrammerManager 1550 */ 1551 protected void updateProgrammerStatus(@CheckForNull PropertyChangeEvent evt) { 1552 log.debug("Updating Programmer Status"); 1553 ConnectionConfig oldServMode = serModeProCon; 1554 ConnectionConfig oldOpsMode = opsModeProCon; 1555 GlobalProgrammerManager gpm = null; 1556 AddressedProgrammerManager apm = null; 1557 1558 // Find the connection that goes with the global programmer 1559 // test that IM has a default GPM, or that event is not the removal of a GPM 1560 if (InstanceManager.containsDefault(GlobalProgrammerManager.class) 1561 || (evt != null 1562 && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(GlobalProgrammerManager.class)) 1563 && evt.getNewValue() == null)) { 1564 gpm = InstanceManager.getNullableDefault(GlobalProgrammerManager.class); 1565 log.trace("found global programming manager {}", gpm); 1566 } 1567 if (gpm != null) { 1568 String serviceModeProgrammerName = gpm.getUserName(); 1569 log.debug("GlobalProgrammerManager found of class {} name {} ", gpm.getClass(), serviceModeProgrammerName); 1570 InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> { 1571 for (ConnectionConfig connection : ccm) { 1572 log.debug("Checking connection name {}", connection.getConnectionName()); 1573 if (connection.getConnectionName() != null && connection.getConnectionName().equals(serviceModeProgrammerName)) { 1574 log.debug("Connection found for GlobalProgrammermanager"); 1575 serModeProCon = connection; 1576 } 1577 } 1578 }); 1579 } 1580 1581 // Find the connection that goes with the addressed programmer 1582 // test that IM has a default APM, or that event is not the removal of an APM 1583 if (InstanceManager.containsDefault(AddressedProgrammerManager.class) 1584 || (evt != null 1585 && evt.getPropertyName().equals(InstanceManager.getDefaultsPropertyName(AddressedProgrammerManager.class)) 1586 && evt.getNewValue() == null)) { 1587 apm = InstanceManager.getNullableDefault(AddressedProgrammerManager.class); 1588 log.trace("found addressed programming manager {}", gpm); 1589 } 1590 if (apm != null) { 1591 String opsModeProgrammerName = apm.getUserName(); 1592 log.debug("AddressedProgrammerManager found of class {} name {} ", apm.getClass(), opsModeProgrammerName); 1593 InstanceManager.getOptionalDefault(ConnectionConfigManager.class).ifPresent((ccm) -> { 1594 for (ConnectionConfig connection : ccm) { 1595 log.debug("Checking connection name {}", connection.getConnectionName()); 1596 if (connection.getConnectionName() != null && connection.getConnectionName().equals(opsModeProgrammerName)) { 1597 log.debug("Connection found for AddressedProgrammermanager"); 1598 opsModeProCon = connection; 1599 } 1600 } 1601 }); 1602 } 1603 1604 log.trace("start global check with {}, {}, {}", serModeProCon, gpm, (gpm != null ? gpm.isGlobalProgrammerAvailable() : "<none>")); 1605 if (serModeProCon != null && gpm != null && gpm.isGlobalProgrammerAvailable()) { 1606 if (ConnectionStatus.instance().isConnectionOk(serModeProCon.getAdapter().getSystemConnectionMemo())) { 1607 log.debug("GPM Connection online 1"); 1608 serviceModeProgrammerLabel.setText( 1609 Bundle.getMessage("ServiceModeProgOnline", serModeProCon.getConnectionName())); 1610 serviceModeProgrammerLabel.setForeground(new Color(0, 128, 0)); 1611 } else { 1612 log.debug("GPM Connection offline"); 1613 serviceModeProgrammerLabel.setText( 1614 Bundle.getMessage("ServiceModeProgOffline", serModeProCon.getConnectionName())); 1615 serviceModeProgrammerLabel.setForeground(Color.red); 1616 } 1617 if (oldServMode == null) { 1618 log.debug("Re-enable user interface"); 1619 contextService.setEnabled(isProgrammingTrackEnabled()); 1620 contextService.setVisible(true); 1621 service.setEnabled(isProgrammingTrackEnabled()); 1622 service.setVisible(true); 1623 firePropertyChange("setprogservice", "setEnabled", true); 1624 getToolBar().getComponents()[1].setEnabled(true); 1625 } 1626 } else if (gpm != null && gpm.isGlobalProgrammerAvailable()) { 1627 if (ConnectionStatus.instance().isSystemOk(gpm.getUserName())) { 1628 log.debug("GPM Connection online 2"); 1629 serviceModeProgrammerLabel.setText( 1630 Bundle.getMessage("ServiceModeProgOnline", gpm.getUserName())); 1631 serviceModeProgrammerLabel.setForeground(new Color(0, 128, 0)); 1632 } else { 1633 log.debug("GPM Connection onffline"); 1634 serviceModeProgrammerLabel.setText( 1635 Bundle.getMessage("ServiceModeProgOffline", gpm.getUserName())); 1636 serviceModeProgrammerLabel.setForeground(Color.red); 1637 } 1638 if (oldServMode == null) { 1639 log.debug("Re-enable user interface"); 1640 contextService.setEnabled(isProgrammingTrackEnabled()); 1641 contextService.setVisible(true); 1642 service.setEnabled(isProgrammingTrackEnabled()); 1643 service.setVisible(true); 1644 firePropertyChange("setprogservice", "setEnabled", true); 1645 getToolBar().getComponents()[1].setEnabled(true); 1646 } 1647 } else { 1648 // No service programmer available, disable interface sections not available 1649 log.debug("no service programmer"); 1650 serviceModeProgrammerLabel.setText(Bundle.getMessage("NoServiceProgrammerAvailable")); 1651 serviceModeProgrammerLabel.setForeground(Color.red); 1652 if (oldServMode != null) { 1653 contextService.setEnabled(false); 1654 contextService.setVisible(false); 1655 service.setEnabled(false); 1656 service.setVisible(false); 1657 firePropertyChange("setprogservice", "setEnabled", false); 1658 } 1659 // Disable Identify in toolBar 1660 // This relies on it being the 2nd item in the toolbar, as defined in xml//config/parts/jmri/jmrit/roster/swing/RosterFrameToolBar.xml 1661 // Because of I18N, we don't look for a particular Action name here 1662 getToolBar().getComponents()[1].setEnabled(false); 1663 serModeProCon = null; 1664 } 1665 1666 if (opsModeProCon != null && apm != null && apm.isAddressedModePossible()) { 1667 if (ConnectionStatus.instance().isConnectionOk(opsModeProCon.getAdapter().getSystemConnectionMemo())) { 1668 log.debug("Ops Mode Connection online"); 1669 operationsModeProgrammerLabel.setText( 1670 Bundle.getMessage("OpsModeProgOnline", opsModeProCon.getConnectionName())); 1671 operationsModeProgrammerLabel.setForeground(new Color(0, 128, 0)); 1672 } else { 1673 log.debug("Ops Mode Connection offline"); 1674 operationsModeProgrammerLabel.setText( 1675 Bundle.getMessage("OpsModeProgOffline", opsModeProCon.getConnectionName())); 1676 operationsModeProgrammerLabel.setForeground(Color.red); 1677 } 1678 if (oldOpsMode == null) { 1679 contextOps.setEnabled(isProgrammingOnMainEnabled()); 1680 contextOps.setVisible(true); 1681 ops.setEnabled(isProgrammingOnMainEnabled()); 1682 ops.setVisible(true); 1683 firePropertyChange("setprogops", "setEnabled", true); 1684 } 1685 } else if (apm != null && apm.isAddressedModePossible()) { 1686 if (ConnectionStatus.instance().isSystemOk(apm.getUserName())) { 1687 log.debug("Ops Mode Connection online"); 1688 operationsModeProgrammerLabel.setText( 1689 Bundle.getMessage("OpsModeProgOnline", apm.getUserName())); 1690 operationsModeProgrammerLabel.setForeground(new Color(0, 128, 0)); 1691 } else { 1692 log.debug("Ops Mode Connection offline"); 1693 operationsModeProgrammerLabel.setText( 1694 Bundle.getMessage("OpsModeProgOffline", apm.getUserName())); 1695 operationsModeProgrammerLabel.setForeground(Color.red); 1696 } 1697 if (oldOpsMode == null) { 1698 contextOps.setEnabled(isProgrammingOnMainEnabled()); 1699 contextOps.setVisible(true); 1700 ops.setEnabled(isProgrammingOnMainEnabled()); 1701 ops.setVisible(true); 1702 firePropertyChange("setprogops", "setEnabled", true); 1703 } 1704 } else { 1705 // No ops mode programmer available, disable interface sections not available 1706 log.debug("no ops mode programmer"); 1707 operationsModeProgrammerLabel.setText(Bundle.getMessage("NoOpsProgrammerAvailable")); 1708 operationsModeProgrammerLabel.setForeground(Color.red); 1709 if (oldOpsMode != null) { 1710 contextOps.setEnabled(false); 1711 contextOps.setVisible(false); 1712 ops.setEnabled(false); 1713 ops.setVisible(false); 1714 firePropertyChange("setprogops", "setEnabled", false); 1715 } 1716 opsModeProCon = null; 1717 } 1718 String strProgMode; 1719 if (service.isEnabled()) { 1720 contextService.setSelected(true); 1721 service.setSelected(true); 1722 strProgMode = "setprogservice"; 1723 modePanel.setVisible(true); 1724 } else if (ops.isEnabled()) { 1725 contextOps.setSelected(true); 1726 ops.setSelected(true); 1727 strProgMode = "setprogops"; 1728 modePanel.setVisible(false); 1729 } else { 1730 contextEdit.setSelected(true); 1731 edit.setSelected(true); 1732 modePanel.setVisible(false); 1733 strProgMode = "setprogedit"; 1734 } 1735 firePropertyChange(strProgMode, "setSelected", true); 1736 } 1737 1738 private boolean isProgrammingTrackEnabled() { 1739 return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null && 1740 ! InstanceManager.getDefault(ProgrammerConfigManager.class).isDisableProgrammingTrack(); 1741 } 1742 1743 private boolean isProgrammingOnMainEnabled() { 1744 return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null && 1745 ! InstanceManager.getDefault(ProgrammerConfigManager.class).isDisableProgrammingOnMain(); 1746 } 1747 1748 @Override 1749 public void windowClosing(WindowEvent e) { 1750 closeWindow(e); 1751 super.windowClosing(e); 1752 } 1753 1754 /** 1755 * Displays a context (right-click) menu for a roster entry. 1756 */ 1757 private class RosterPopupListener extends JmriMouseAdapter { 1758 1759 @Override 1760 public void mousePressed(JmriMouseEvent e) { 1761 if (e.isPopupTrigger()) { 1762 showPopup(e); 1763 } 1764 } 1765 1766 @Override 1767 public void mouseReleased(JmriMouseEvent e) { 1768 if (e.isPopupTrigger()) { 1769 showPopup(e); 1770 } 1771 } 1772 1773 @Override 1774 public void mouseClicked(JmriMouseEvent e) { 1775 if (e.isPopupTrigger()) { 1776 showPopup(e); 1777 return; 1778 } 1779 if (e.getClickCount() == 2) { 1780 startProgrammer(null, re, programmer1); 1781 } 1782 } 1783 } 1784 1785 private static class ExportRosterItem extends ExportRosterItemAction { 1786 1787 ExportRosterItem(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 1798 private static class CopyRosterItem extends CopyRosterItemAction { 1799 1800 CopyRosterItem(String pName, Component pWho, RosterEntry re) { 1801 super(pName, pWho); 1802 super.setExistingEntry(re); 1803 } 1804 1805 @Override 1806 protected boolean selectFrom() { 1807 return true; 1808 } 1809 } 1810 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RosterFrame.class); 1811 1812}