001package jmri.jmrix.openlcb.swing.lccpro; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.awt.datatransfer.Transferable; 006 007import java.beans.PropertyChangeEvent; 008import java.beans.PropertyChangeListener; 009import java.util.ArrayList; 010 011import javax.swing.*; 012import javax.swing.event.ListSelectionEvent; 013 014import jmri.InstanceManager; 015import jmri.ShutDownManager; 016import jmri.UserPreferencesManager; 017 018import jmri.swing.ConnectionLabel; 019import jmri.swing.JTablePersistenceManager; 020import jmri.swing.RowSorterUtil; 021 022import jmri.jmrix.ActiveSystemsMenu; 023import jmri.jmrix.ConnectionConfig; 024import jmri.jmrix.ConnectionConfigManager; 025import jmri.jmrix.can.CanSystemConnectionMemo; 026import jmri.jmrix.openlcb.swing.TrafficStatusLabel; 027 028import jmri.util.HelpUtil; 029import jmri.util.WindowMenu; 030import jmri.util.datatransfer.RosterEntrySelection; 031import jmri.util.swing.JmriAbstractAction; 032import jmri.util.swing.JmriJOptionPane; 033import jmri.util.swing.JmriMouseAdapter; 034import jmri.util.swing.JmriMouseEvent; 035import jmri.util.swing.JmriMouseListener; 036import jmri.util.swing.multipane.TwoPaneTBWindow; 037 038import org.openlcb.MimicNodeStore; 039 040/** 041 * A window for LCC Network management. 042 * <p> 043 * 044 * @author Bob Jacobsen Copyright (C) 2010, 2016, 2024 045 * @author Kevin Dickerson Copyright (C) 2011 046 * @author Randall Wood Copyright (C) 2012 047 */ 048public class LccProFrame extends TwoPaneTBWindow { 049 050 static final ArrayList<LccProFrame> frameInstances = new ArrayList<>(); 051 protected boolean allowQuit = true; 052 protected JmriAbstractAction newWindowAction; 053 054 CanSystemConnectionMemo memo; 055 MimicNodeStore nodestore; 056 057 public LccProFrame(String name) { 058 this(name, 059 jmri.InstanceManager.getNullableDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 060 } 061 062 public LccProFrame(String name, CanSystemConnectionMemo memo) { 063 this(name, 064 "xml/config/parts/apps/gui3/lccpro/LccProFrameMenu.xml", 065 "xml/config/parts/apps/gui3/lccpro/LccProFrameToolBar.xml", 066 memo); 067 } 068 069 public LccProFrame(String name, String menubarFile, String toolbarFile) { 070 this(name, menubarFile, toolbarFile, jmri.InstanceManager.getNullableDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 071 } 072 073 public LccProFrame(String name, String menubarFile, String toolbarFile, CanSystemConnectionMemo memo) { 074 super(name, menubarFile, toolbarFile); 075 this.memo = memo; 076 if (memo == null) { 077 // a functional LccFrame can't be created without an LCC ConnectionConfig 078 javax.swing.JOptionPane.showMessageDialog(this, "LccPro requires a configured LCC or OpenLCB connection, will quit now", 079 "LccPro", JOptionPane.ERROR_MESSAGE); 080 // and close the program 081 // This is justified because this should never happen in a properly 082 // built application: The existence of an LCC/OpenLCB connection 083 // should have been checked long before reaching this point. 084 InstanceManager.getDefault(jmri.ShutDownManager.class).shutdown(); 085 return; 086 } 087 this.nodestore = memo.get(MimicNodeStore.class); 088 this.allowInFrameServlet = false; 089 prefsMgr = InstanceManager.getDefault(UserPreferencesManager.class); 090 this.setTitle(name); 091 this.buildWindow(); 092 } 093 094 final NodeInfoPane nodeInfoPane = new NodeInfoPane(); 095 final NodePipPane nodePipPane = new NodePipPane(); 096 JLabel firstHelpLabel; 097 int groupSplitPaneLocation = 0; 098 boolean hideGroups = false; 099 final JTextPane id = new JTextPane(); 100 UserPreferencesManager prefsMgr; 101 final java.util.ResourceBundle rb = java.util.ResourceBundle.getBundle("apps.AppsBundle"); 102 // the three parts of the bottom half 103 final JPanel bottomPanel = new JPanel(); 104 JSplitPane bottomLCPanel; // left and center parts 105 JSplitPane bottomRPanel; // right part 106 // main center window (TODO: rename this; TODO: Does this still need to be split?) 107 JSplitPane rosterGroupSplitPane; 108 109 LccProTable rtable; // node table in center of screen 110 final JLabel statusField = new JLabel(); 111 final static Dimension summaryPaneDim = new Dimension(0, 170); 112 113 protected void additionsToToolBar() { 114 getToolBar().add(Box.createHorizontalGlue()); 115 } 116 117 /** 118 * For use when the DP3 window is called from another JMRI instance, set 119 * this to prevent the DP3 from shutting down JMRI when the window is 120 * closed. 121 * 122 * @param quitAllowed true if closing window should quit application; false 123 * otherwise 124 */ 125 protected void allowQuit(boolean quitAllowed) { 126 if (allowQuit != quitAllowed) { 127 newWindowAction = null; 128 allowQuit = quitAllowed; 129 } 130 131 firePropertyChange("quit", "setEnabled", allowQuit); 132 //if we are not allowing quit, ie opened from JMRI classic 133 //then we must at least allow the window to be closed 134 if (!allowQuit) { 135 firePropertyChange("closewindow", "setEnabled", true); 136 } 137 } 138 139 // Create right side of the bottom panel 140 141 JPanel bottomRight() { 142 JPanel panel = new JPanel(); 143 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 144 panel.setAlignmentX(SwingConstants.LEFT); 145 146 panel.add(new JLabel("Search Node Names:")); 147 var searchField = new JTextField() { 148 @Override 149 public Dimension getMaximumSize() { 150 Dimension size = super.getMaximumSize(); 151 size.height = getPreferredSize().height; 152 return size; 153 } 154 }; 155 searchField.getDocument().putProperty("filterNewlines", Boolean.TRUE); 156 searchField.addKeyListener(new KeyListener() { 157 @Override 158 public void keyTyped(KeyEvent keyEvent) { 159 } 160 161 @Override 162 public void keyReleased(KeyEvent keyEvent) { 163 // on release so the searchField has been updated 164 log.trace("keyTyped {} content {}", keyEvent.getKeyCode(), searchField.getText()); 165 String search = searchField.getText().toLowerCase(); 166 // start search process 167 int count = rtable.getModel().getColumnCount(); 168 for (int row = 0; row < count; row++) { 169 String value = ((String)rtable.getTable().getValueAt(row, LccProTableModel.NAMECOL)).toLowerCase(); 170 if (value.startsWith(search)) { 171 log.trace(" Hit value {} on {}", value, row); 172 rtable.getTable().setRowSelectionInterval(row, row); 173 rtable.getTable().scrollRectToVisible(rtable.getTable().getCellRect(row,LccProTableModel.NAMECOL, true)); 174 return; 175 } 176 } 177 // here we didn't find anything 178 rtable.getTable().clearSelection(); 179 } 180 181 @Override 182 public void keyPressed(KeyEvent keyEvent) { 183 } 184 }); 185 panel.add(searchField); 186 panel.add(Box.createVerticalGlue()); 187 panel.add(new JLabel("bottomRight needs more work")); 188 189 return panel; 190 } 191 192 protected final void buildWindow() { 193 //Additions to the toolbar need to be added first otherwise when trying to hide bits up during the initialisation they remain on screen 194 additionsToToolBar(); 195 frameInstances.add(this); 196 getTop().add(createTop()); 197 getBottom().setMinimumSize(summaryPaneDim); 198 getBottom().add(createBottom()); 199 statusBar(); 200 systemsMenu(); 201 helpMenu(getMenu(), this); 202 203 if (prefsMgr.getSimplePreferenceState(this.getClass().getName() + ".hideSummary")) { 204 //We have to set it to display first, then we can hide it. 205 hideBottomPane(false); 206 hideBottomPane(true); 207 } 208 PropertyChangeListener propertyChangeListener = (PropertyChangeEvent changeEvent) -> { 209 JSplitPane sourceSplitPane = (JSplitPane) changeEvent.getSource(); 210 String propertyName = changeEvent.getPropertyName(); 211 if (propertyName.equals(JSplitPane.LAST_DIVIDER_LOCATION_PROPERTY)) { 212 int current = sourceSplitPane.getDividerLocation() + sourceSplitPane.getDividerSize(); 213 int panesize = (int) (sourceSplitPane.getSize().getHeight()); 214 hideBottomPane = panesize - current <= 1; 215 //p.setSimplePreferenceState(DecoderPro3Window.class.getName()+".hideSummary",hideSummary); 216 } 217 }; 218 219 getSplitPane().addPropertyChangeListener(propertyChangeListener); 220 if (frameInstances.size() > 1) { 221 firePropertyChange("closewindow", "setEnabled", true); 222 allowQuit(frameInstances.get(0).isAllowQuit()); 223 } else { 224 firePropertyChange("closewindow", "setEnabled", false); 225 } 226 } 227 228 //@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 229 void closeWindow(WindowEvent e) { 230 saveWindowDetails(); 231 if (allowQuit && frameInstances.size() == 1 && !InstanceManager.getDefault(ShutDownManager.class).isShuttingDown()) { 232 handleQuit(e); 233 } else { 234 //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 235 frameInstances.remove(this); 236 super.windowClosing(e); 237 if ((frameInstances.size() == 1) && (allowQuit)) { 238 frameInstances.get(0).firePropertyChange("closewindow", "setEnabled", false); 239 } 240 dispose(); 241 } 242 } 243 244 JComponent createBottom() { 245 JPanel leftPanel = nodeInfoPane; 246 JPanel centerPanel = nodePipPane; 247 JPanel rightPanel = bottomRight(); 248 249 bottomLCPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, centerPanel); 250 bottomRPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, bottomLCPanel, rightPanel); 251 252 leftPanel.setBorder(BorderFactory.createLineBorder(Color.black)); 253 centerPanel.setBorder(BorderFactory.createLineBorder(Color.black)); 254 bottomLCPanel.setBorder(null); 255 256 bottomLCPanel.setResizeWeight(0.67); // determined empirically 257 bottomRPanel.setResizeWeight(0.75); 258 259 bottomLCPanel.setOneTouchExpandable(true); 260 bottomRPanel.setOneTouchExpandable(true); 261 262 // load split locations from preferences 263 Object w = prefsMgr.getProperty(getWindowFrameRef(), "bottomLCPanelDividerLocation"); 264 if (w != null) { 265 var splitPaneLocation = (Integer) w; 266 bottomLCPanel.setDividerLocation(splitPaneLocation); 267 } 268 w = prefsMgr.getProperty(getWindowFrameRef(), "bottomRPanelDividerLocation"); 269 if (w != null) { 270 var splitPaneLocation = (Integer) w; 271 bottomRPanel.setDividerLocation(splitPaneLocation); 272 } 273 274 // add listeners that will store location preferences 275 bottomLCPanel.addPropertyChangeListener((PropertyChangeEvent changeEvent) -> { 276 String propertyName = changeEvent.getPropertyName(); 277 if (propertyName.equals("dividerLocation")) { 278 prefsMgr.setProperty(getWindowFrameRef(), "bottomLCPanelDividerLocation", bottomLCPanel.getDividerLocation()); 279 } 280 }); 281 bottomRPanel.addPropertyChangeListener((PropertyChangeEvent changeEvent) -> { 282 String propertyName = changeEvent.getPropertyName(); 283 if (propertyName.equals("dividerLocation")) { 284 prefsMgr.setProperty(getWindowFrameRef(), "bottomRPanelDividerLocation", bottomRPanel.getDividerLocation()); 285 } 286 }); 287 return bottomRPanel; 288 } 289 290 JComponent createTop() { 291 final JPanel rosters = new JPanel(); 292 rosters.setLayout(new BorderLayout()); 293 // set up node table 294 rtable = new LccProTable(memo); 295 rosters.add(rtable, BorderLayout.CENTER); 296 // add selection listener to display selected row 297 rtable.getTable().getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { 298 JTable table = rtable.getTable(); 299 if (!e.getValueIsAdjusting()) { 300 if (table.getSelectedRow() >= 0) { 301 int row = table.convertRowIndexToModel(table.getSelectedRow()); 302 log.debug("Selected: {}", row); 303 MimicNodeStore.NodeMemo nodememo = nodestore.getNodeMemos().toArray(new MimicNodeStore.NodeMemo[0])[row]; 304 log.trace(" node: {}", nodememo.getNodeID().toString()); 305 nodeInfoPane.update(nodememo); 306 nodePipPane.update(nodememo); 307 } 308 } 309 }); 310 311 // Set all the sort and width details of the table first. 312 String nodetableref = getWindowFrameRef() + ":nodes"; 313 rtable.getTable().setName(nodetableref); 314 315 // Allow only one column to be sorted at a time - 316 // Java allows multiple column sorting, but to effectively persist that, we 317 // need to be intelligent about which columns can be meaningfully sorted 318 // with other columns; this bypasses the problem by only allowing the 319 // last column sorted to affect sorting 320 RowSorterUtil.addSingleSortableColumnListener(rtable.getTable().getRowSorter()); 321 322 // Reset and then persist the table's ui state 323 JTablePersistenceManager tpm = InstanceManager.getNullableDefault(JTablePersistenceManager.class); 324 if (tpm != null) { 325 tpm.resetState(rtable.getTable()); 326 tpm.persist(rtable.getTable()); 327 } 328 rtable.getTable().setDragEnabled(true); 329 rtable.getTable().setTransferHandler(new TransferHandler() { 330 331 @Override 332 public int getSourceActions(JComponent c) { 333 return TransferHandler.COPY; 334 } 335 336 @Override 337 public Transferable createTransferable(JComponent c) { 338 JTable table = rtable.getTable(); 339 ArrayList<String> Ids = new ArrayList<>(table.getSelectedRowCount()); 340 for (int i = 0; i < table.getSelectedRowCount(); i++) { 341 // TODO replace this with something about the nodes to be dragged and dropped 342 // Ids.add(rtable.getModel().getValueAt(table.getRowSorter().convertRowIndexToModel(table.getSelectedRows()[i]), RosterTableModel.IDCOL).toString()); 343 } 344 return new RosterEntrySelection(Ids); 345 } 346 347 @Override 348 public void exportDone(JComponent c, Transferable t, int action) { 349 // nothing to do 350 } 351 }); 352 rtable.getTable().addMouseListener(JmriMouseListener.adapt(new NodePopupListener())); 353 354 // assemble roster/groups splitpane 355 // TODO - figure out what to do with the left side of this and expand the following 356 JPanel leftSide = new JPanel(); 357 leftSide.setEnabled(false); 358 leftSide.setVisible(false); 359 360 rosterGroupSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSide, rosters); 361 rosterGroupSplitPane.setOneTouchExpandable(false); // TODO set this true once the leftSide is in use 362 rosterGroupSplitPane.setResizeWeight(0); // emphasize right side (nodes) 363 364 Object w = prefsMgr.getProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation"); 365 if (w != null) { 366 groupSplitPaneLocation = (Integer) w; 367 rosterGroupSplitPane.setDividerLocation(groupSplitPaneLocation); 368 } 369 370 log.trace("createTop returns {}", rosterGroupSplitPane); 371 return rosterGroupSplitPane; 372 } 373 374 /*=============== Getters and Setters for core properties ===============*/ 375 376 /** 377 * @return Will closing the window quit JMRI? 378 */ 379 public boolean isAllowQuit() { 380 return allowQuit; 381 } 382 383 /** 384 * @param allowQuit Set state to either close JMRI or just the roster window 385 */ 386 public void setAllowQuit(boolean allowQuit) { 387 allowQuit(allowQuit); 388 } 389 390 /** 391 * @return the newWindowAction 392 */ 393 protected JmriAbstractAction getNewWindowAction() { 394 if (newWindowAction == null) { 395 newWindowAction = new LccProFrameAction("newWindow", this, allowQuit); 396 } 397 return newWindowAction; 398 } 399 400 /** 401 * @param newWindowAction the newWindowAction to set 402 */ 403 protected void setNewWindowAction(JmriAbstractAction newWindowAction) { 404 this.newWindowAction = newWindowAction; 405 } 406 407 @Override 408 public Object getProperty(String key) { 409 // TODO - does this have any equivalent? 410 if (key.equalsIgnoreCase("hideSummary")) { 411 return hideBottomPane; 412 } 413 // call parent getProperty method to return any properties defined 414 // in the class hierarchy. 415 return super.getProperty(key); 416 } 417 418 void handleQuit(WindowEvent e) { 419 if (e != null && frameInstances.size() == 1) { 420 final String rememberWindowClose = this.getClass().getName() + ".closeDP3prompt"; 421 if (!prefsMgr.getSimplePreferenceState(rememberWindowClose)) { 422 JPanel message = new JPanel(); 423 JLabel question = new JLabel(rb.getString("MessageLongCloseWarning")); 424 final JCheckBox remember = new JCheckBox(rb.getString("MessageRememberSetting")); 425 remember.setFont(remember.getFont().deriveFont(10.0F)); 426 message.setLayout(new BoxLayout(message, BoxLayout.Y_AXIS)); 427 message.add(question); 428 message.add(remember); 429 int result = JmriJOptionPane.showConfirmDialog(null, 430 message, 431 rb.getString("MessageShortCloseWarning"), 432 JmriJOptionPane.YES_NO_OPTION); 433 if (remember.isSelected()) { 434 prefsMgr.setSimplePreferenceState(rememberWindowClose, true); 435 } 436 if (result == JmriJOptionPane.YES_OPTION) { 437 handleQuit(); 438 } 439 } else { 440 handleQuit(); 441 } 442 } else if (frameInstances.size() > 1) { 443 final String rememberWindowClose = this.getClass().getName() + ".closeMultipleDP3prompt"; 444 if (!prefsMgr.getSimplePreferenceState(rememberWindowClose)) { 445 JPanel message = new JPanel(); 446 JLabel question = new JLabel(rb.getString("MessageLongMultipleCloseWarning")); 447 final JCheckBox remember = new JCheckBox(rb.getString("MessageRememberSetting")); 448 remember.setFont(remember.getFont().deriveFont(10.0F)); 449 message.setLayout(new BoxLayout(message, BoxLayout.Y_AXIS)); 450 message.add(question); 451 message.add(remember); 452 int result = JmriJOptionPane.showConfirmDialog(null, 453 message, 454 rb.getString("MessageShortCloseWarning"), 455 JmriJOptionPane.YES_NO_OPTION); 456 if (remember.isSelected()) { 457 prefsMgr.setSimplePreferenceState(rememberWindowClose, true); 458 } 459 if (result == JmriJOptionPane.YES_OPTION) { 460 handleQuit(); 461 } 462 } else { 463 handleQuit(); 464 } 465 //closeWindow(null); 466 } 467 } 468 469 private void handleQuit(){ 470 try { 471 InstanceManager.getDefault(jmri.ShutDownManager.class).shutdown(); 472 } catch (Exception e) { 473 log.error("Continuing after error in handleQuit", e); 474 } 475 } 476 477 protected void helpMenu(JMenuBar menuBar, final JFrame frame) { 478 // create menu and standard items 479 JMenu helpMenu = HelpUtil.makeHelpMenu("package.apps.gui3.lccpro.LccPro", true); 480 // use as main help menu 481 menuBar.add(helpMenu); 482 } 483 484 protected void hideGroups() { 485 boolean boo = !hideGroups; 486 hideGroupsPane(boo); 487 } 488 489 public void hideGroupsPane(boolean hide) { 490 if (hideGroups == hide) { 491 return; 492 } 493 hideGroups = hide; 494 if (hide) { 495 groupSplitPaneLocation = rosterGroupSplitPane.getDividerLocation(); 496 rosterGroupSplitPane.setDividerLocation(1); 497 rosterGroupSplitPane.getLeftComponent().setMinimumSize(new Dimension()); 498 } else { 499 rosterGroupSplitPane.setDividerSize(UIManager.getInt("SplitPane.dividerSize")); 500 rosterGroupSplitPane.setOneTouchExpandable(true); 501 if (groupSplitPaneLocation >= 2) { 502 rosterGroupSplitPane.setDividerLocation(groupSplitPaneLocation); 503 } else { 504 rosterGroupSplitPane.resetToPreferredSizes(); 505 } 506 } 507 } 508 509 protected void hideSummary() { 510 boolean boo = !hideBottomPane; 511 hideBottomPane(boo); 512 } 513 514 protected void newWindow() { 515 this.newWindow(this.getNewWindowAction()); 516 } 517 518 protected void newWindow(JmriAbstractAction action) { 519 action.setWindowInterface(this); 520 action.actionPerformed(null); 521 firePropertyChange("closewindow", "setEnabled", true); 522 } 523 524 /** 525 * Match the first argument in the array against a locally-known method. 526 * 527 * @param args Array of arguments, we take with element 0 528 */ 529 @Override 530 public void remoteCalls(String[] args) { 531 args[0] = args[0].toLowerCase(); 532 switch (args[0]) { 533 case "summarypane": 534 hideSummary(); 535 break; 536 case "groupspane": 537 hideGroups(); 538 break; 539 case "quit": 540 saveWindowDetails(); 541 handleQuit(new WindowEvent(this, frameInstances.size())); 542 break; 543 case "closewindow": 544 closeWindow(null); 545 break; 546 case "newwindow": 547 newWindow(); 548 break; 549 case "resettablecolumns": 550 rtable.resetColumnWidths(); 551 break; 552 default: 553 log.error("method {} not found", args[0]); 554 break; 555 } 556 } 557 558 void saveWindowDetails() { 559 if (prefsMgr != null) { // aborted startup doesn't set prefs manager 560 prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideSummary", hideBottomPane); 561 prefsMgr.setSimplePreferenceState(this.getClass().getName() + ".hideGroups", hideGroups); 562 if (rosterGroupSplitPane.getDividerLocation() > 2) { 563 prefsMgr.setProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation", rosterGroupSplitPane.getDividerLocation()); 564 } else if (groupSplitPaneLocation > 2) { 565 prefsMgr.setProperty(getWindowFrameRef(), "rosterGroupPaneDividerLocation", groupSplitPaneLocation); 566 } 567 } 568 } 569 570 protected void showPopup(JmriMouseEvent e) { 571 int row = rtable.getTable().rowAtPoint(e.getPoint()); 572 if (!rtable.getTable().isRowSelected(row)) { 573 rtable.getTable().changeSelection(row, 0, false, false); 574 } 575 JPopupMenu popupMenu = new JPopupMenu(); 576 577 popupMenu.show(e.getComponent(), e.getX(), e.getY()); 578 } 579 580 /** 581 * Create and display a status bar along the bottom edge of the Roster main 582 * pane. 583 */ 584 protected void statusBar() { 585 for (ConnectionConfig conn : InstanceManager.getDefault(ConnectionConfigManager.class)) { 586 if (!conn.getDisabled()) { 587 addToStatusBox(new ConnectionLabel(conn)); 588 } 589 } 590 addToStatusBox(new TrafficStatusLabel(memo)); 591 } 592 593 protected void systemsMenu() { 594 ActiveSystemsMenu.addItems(getMenu()); 595 getMenu().add(new WindowMenu(this)); 596 } 597 598 void updateDetails() { 599 // TODO - once we decide what details to show, fix this 600 } 601 602 @Override 603 public void windowClosing(WindowEvent e) { 604 closeWindow(e); 605 } 606 607 /** 608 * Displays a context (right-click) menu for a node row. 609 */ 610 private class NodePopupListener extends JmriMouseAdapter { 611 612 @Override 613 public void mousePressed(JmriMouseEvent e) { 614 if (e.isPopupTrigger()) { 615 showPopup(e); 616 } 617 } 618 619 @Override 620 public void mouseReleased(JmriMouseEvent e) { 621 if (e.isPopupTrigger()) { 622 showPopup(e); 623 } 624 } 625 626 @Override 627 public void mouseClicked(JmriMouseEvent e) { 628 if (e.isPopupTrigger()) { 629 showPopup(e); 630 return; 631 } 632 } 633 } 634 635 /** 636 * Displays SNIP information about a specific node 637 */ 638 private static class NodeInfoPane extends JPanel { 639 JLabel name = new JLabel(); 640 JLabel desc = new JLabel(); 641 JLabel nodeID = new JLabel(); 642 JLabel mfg = new JLabel(); 643 JLabel model = new JLabel(); 644 JLabel hardver = new JLabel(); 645 JLabel softver = new JLabel(); 646 647 public NodeInfoPane() { 648 var gbl = new jmri.util.javaworld.GridLayout2(7,2); 649 setLayout(gbl); 650 651 var a = new JLabel("Name: "); 652 a.setHorizontalAlignment(SwingConstants.RIGHT); 653 add(a); 654 add(name); 655 656 a = new JLabel("Description: "); 657 a.setHorizontalAlignment(SwingConstants.RIGHT); 658 add(a); 659 add(desc); 660 661 a = new JLabel("Node ID: "); 662 a.setHorizontalAlignment(SwingConstants.RIGHT); 663 add(a); 664 add(nodeID); 665 666 a = new JLabel("Manufacturer: "); 667 a.setHorizontalAlignment(SwingConstants.RIGHT); 668 add(a); 669 add(mfg); 670 671 a = new JLabel("Model: "); 672 a.setHorizontalAlignment(SwingConstants.RIGHT); 673 add(a); 674 add(model); 675 676 a = new JLabel("Hardware Version: "); 677 a.setHorizontalAlignment(SwingConstants.RIGHT); 678 add(a); 679 add(hardver); 680 681 a = new JLabel("Software Version: "); 682 a.setHorizontalAlignment(SwingConstants.RIGHT); 683 add(a); 684 add(softver); 685 } 686 687 public void update(MimicNodeStore.NodeMemo nodememo) { 688 var snip = nodememo.getSimpleNodeIdent(); 689 690 // update with current contents 691 name.setText(snip.getUserName()); 692 desc.setText(snip.getUserDesc()); 693 nodeID.setText(nodememo.getNodeID().toString()); 694 mfg.setText(snip.getMfgName()); 695 model.setText(snip.getModelName()); 696 hardver.setText(snip.getHardwareVersion()); 697 softver.setText(snip.getSoftwareVersion()); 698 } 699 700 } 701 702 703 /** 704 * Displays PIP information about a specific node 705 */ 706 private static class NodePipPane extends JPanel { 707 708 public NodePipPane () { 709 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 710 add(new JLabel("Supported Protocols:")); 711 } 712 713 public void update(MimicNodeStore.NodeMemo nodememo) { 714 // remove existing content 715 removeAll(); 716 revalidate(); 717 repaint(); 718 // add heading 719 add(new JLabel("Supported Protocols:")); 720 // and display new content 721 var pip = nodememo.getProtocolIdentification(); 722 var names = pip.getProtocolNames(); 723 724 for (String name : names) { 725 // make this name a bit more human-friendly 726 final String regex = "([a-z])([A-Z])"; 727 final String replacement = "$1 $2"; 728 var formattedName = " "+name.replaceAll(regex, replacement); 729 add(new JLabel(formattedName)); 730 } 731 } 732 } 733 734 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LccProFrame.class); 735 736}