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