001package jmri.jmrix.openlcb.swing.eventtable; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.beans.*; 006import java.io.*; 007import java.util.*; 008 009import javax.swing.*; 010import javax.swing.table.*; 011 012import jmri.*; 013import jmri.jmrix.can.CanSystemConnectionMemo; 014import jmri.jmrix.openlcb.*; 015import jmri.util.ThreadingUtil; 016 017import jmri.swing.JmriJTablePersistenceManager; 018import jmri.util.swing.MultiLineCellRenderer; 019import jmri.util.table.ButtonEditor; 020import jmri.util.table.ButtonRenderer; 021 022import org.openlcb.*; 023import org.openlcb.implementations.*; 024import org.openlcb.swing.*; 025 026 027/** 028 * Pane for displaying a table of relationships of nodes, producers and consumers. 029 * 030 * @author Bob Jacobsen Copyright (C) 2023, 2026 031 * @since 5.3.4 032 */ 033public class EventTablePane extends jmri.util.swing.JmriPanel 034 implements jmri.jmrix.can.swing.CanPanelInterface { 035 036 protected CanSystemConnectionMemo memo; 037 Connection connection; 038 NodeID nid; 039 OlcbEventNameStore nameStore; 040 OlcbNodeGroupStore groupStore; 041 042 MimicNodeStore mimcStore; 043 EventTableDataModel model; 044 JTable table; 045 Monitor monitor; 046 047 JComboBox<String> matchGroupName; // required group name to display; index <= 0 is all 048 JCheckBox showRequiresLabel; // requires a user-provided name to display 049 JCheckBox showRequiresMatch; // requires at least one consumer and one producer exist to display 050 JCheckBox popcorn; // popcorn mode displays events in real time 051 052 JFormattedTextField findID; 053 JTextField findTextID; 054 055 private transient TableRowSorter<EventTableDataModel> sorter; 056 057 public String getTitle(String menuTitle) { 058 return Bundle.getMessage("TitleEventTable"); 059 } 060 061 @Override 062 public void initComponents(CanSystemConnectionMemo memo) { 063 this.memo = memo; 064 this.connection = memo.get(Connection.class); 065 this.nid = memo.get(NodeID.class); 066 this.nameStore = memo.get(OlcbEventNameStore.class); 067 this.groupStore = InstanceManager.getDefault(OlcbNodeGroupStore.class); 068 this.mimcStore = memo.get(MimicNodeStore.class); 069 EventTable stdEventTable = memo.get(OlcbInterface.class).getEventTable(); 070 071 UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class); 072 073 model = new EventTableDataModel(mimcStore, stdEventTable, nameStore); 074 sorter = new TableRowSorter<>(model); 075 076 077 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 078 079 // Add to GUI here 080 081 table = new JTable(model); 082 083 model.table = table; 084 model.sorter = sorter; 085 table.setAutoCreateRowSorter(true); 086 table.setRowSorter(sorter); 087 table.setDefaultRenderer(String.class, new MultiLineCellRenderer()); 088 table.setShowGrid(true); 089 table.setGridColor(Color.BLACK); 090 table.getTableHeader().setBackground(Color.LIGHT_GRAY); 091 table.setName("jmri.jmrix.openlcb.swing.eventtable.EventTablePane.table"); // for persistence 092 table.setColumnSelectionAllowed(true); 093 table.setRowSelectionAllowed(true); 094 ButtonRenderer buttonRenderer = new ButtonRenderer(); 095 table.setDefaultRenderer(JButton.class, buttonRenderer); 096 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 097 table.setDefaultEditor(JButton.class, buttonEditor); 098 099 100 // render in fixed size font 101 var defaultFont = table.getFont(); 102 var fixedFont = new Font(Font.MONOSPACED, Font.PLAIN, defaultFont.getSize()); 103 table.setFont(fixedFont); 104 105 var scrollPane = new JScrollPane(table); 106 107 // restore the column layout and start monitoring it 108 InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent((tpm) -> { 109 tpm.resetState(table); 110 tpm.persist(table); 111 }); 112 113 add(scrollPane); 114 115 var buttonPanel = new JToolBar(); 116 buttonPanel.setLayout(new jmri.util.swing.WrapLayout()); 117 118 add(buttonPanel); 119 120 var updateButton = new JButton(Bundle.getMessage("ButtonUpdate")); 121 updateButton.addActionListener(this::sendRequestEvents); 122 updateButton.setToolTipText(Bundle.getMessage("ButtonUpdateTt")); 123 buttonPanel.add(updateButton); 124 125 matchGroupName = new JComboBox<>(); 126 updateMatchGroupName(); // before adding listener 127 matchGroupName.addActionListener((ActionEvent e) -> { 128 filter(); 129 }); 130 groupStore.addPropertyChangeListener((PropertyChangeEvent evt) -> { 131 updateMatchGroupName(); 132 }); 133 buttonPanel.add(matchGroupName); 134 135 showRequiresLabel = new JCheckBox(Bundle.getMessage("BoxShowRequiresLabel")); 136 showRequiresLabel.addActionListener((ActionEvent e) -> { 137 filter(); 138 }); 139 showRequiresLabel.setToolTipText(Bundle.getMessage("BoxShowRequiresLabelTt")); 140 showRequiresLabel.setOpaque(false); // make transparent 141 buttonPanel.add(showRequiresLabel); 142 143 showRequiresMatch = new JCheckBox(Bundle.getMessage("BoxShowRequiresMatch")); 144 showRequiresMatch.addActionListener((ActionEvent e) -> { 145 filter(); 146 }); 147 showRequiresMatch.setToolTipText(Bundle.getMessage("BoxShowRequiresMatchTt")); 148 showRequiresMatch.setOpaque(false); // make transparent 149 buttonPanel.add(showRequiresMatch); 150 151 popcorn = new JCheckBox(Bundle.getMessage("BoxPopcorn")); 152 popcorn.addActionListener((ActionEvent e) -> { 153 popcornButtonChanged(); 154 }); 155 popcorn.setToolTipText(Bundle.getMessage("BoxPopcornTt")); 156 popcorn.setOpaque(false); // make transparent 157 buttonPanel.add(popcorn); 158 159 JPanel findpanel = new JPanel(); // keep button and text together 160 findpanel.setOpaque(false); // make transparent 161 findpanel.setToolTipText(Bundle.getMessage("FindPanelFindEventTt")); 162 buttonPanel.add(findpanel); 163 164 JLabel find = new JLabel(Bundle.getMessage("FindPanelFindEvent")); 165 findpanel.add(find); 166 167 findID = new EventIdTextField(); 168 findID.setToolTipText(Bundle.getMessage("FindPanelFindEventFieldTt")); 169 findID.addActionListener(this::findRequested); 170 findID.addKeyListener(new KeyListener() { 171 @Override 172 public void keyTyped(KeyEvent keyEvent) { 173 } 174 175 @Override 176 public void keyReleased(KeyEvent keyEvent) { 177 // on release so the searchField has been updated 178 log.trace("keyTyped {} content {}", keyEvent.getKeyCode(), findTextID.getText()); 179 findRequested(null); 180 } 181 182 @Override 183 public void keyPressed(KeyEvent keyEvent) { 184 } 185 }); 186 findpanel.add(findID); 187 JButton addButton = new JButton(Bundle.getMessage("FindPanelButtonAdd")); 188 addButton.addActionListener(this::addRequested); 189 addButton.setToolTipText(Bundle.getMessage("FindPanelButtonAddTt")); 190 findpanel.add(addButton); 191 192 findpanel = new JPanel(); // keep button and text together 193 findpanel.setOpaque(false); // make transparent 194 findpanel.setToolTipText(Bundle.getMessage("FindPanelFindNameTt")); 195 buttonPanel.add(findpanel); 196 197 JLabel findText = new JLabel(Bundle.getMessage("FindPanelFindName")); 198 findpanel.add(findText); 199 200 findTextID = new JTextField(16); 201 findTextID.addActionListener(this::findTextRequested); 202 findTextID.setToolTipText(Bundle.getMessage("FindPanelFindNameTt")); 203 findTextID.addKeyListener(new KeyListener() { 204 @Override 205 public void keyTyped(KeyEvent keyEvent) { 206 } 207 208 @Override 209 public void keyReleased(KeyEvent keyEvent) { 210 // on release so the searchField has been updated 211 log.trace("keyTyped {} content {}", keyEvent.getKeyCode(), findTextID.getText()); 212 findTextRequested(null); 213 } 214 215 @Override 216 public void keyPressed(KeyEvent keyEvent) { 217 } 218 }); 219 findpanel.add(findTextID); 220 221 JButton sensorButton = new JButton(Bundle.getMessage("FindPanelButtonSensor")); 222 sensorButton.addActionListener(this::sensorRequested); 223 sensorButton.setToolTipText(Bundle.getMessage("FindPanelButtonSensorTt")); 224 buttonPanel.add(sensorButton); 225 226 JButton turnoutButton = new JButton(Bundle.getMessage("FindPanelButtonTurnout")); 227 turnoutButton.addActionListener(this::turnoutRequested); 228 turnoutButton.setToolTipText(Bundle.getMessage("FindPanelButtonTurnoutTt")); 229 buttonPanel.add(turnoutButton); 230 231 buttonPanel.setMaximumSize(buttonPanel.getPreferredSize()); 232 233 // hook up to receive traffic 234 monitor = new Monitor(model); 235 memo.get(OlcbInterface.class).registerMessageListener(monitor); 236 237 // set options 238 showRequiresLabel.setSelected(pm.getSimplePreferenceState(requireLabelChecked)); 239 showRequiresMatch.setSelected(pm.getSimplePreferenceState(requireBothPCChecked)); 240 filter(); 241 } 242 243 public EventTablePane() { 244 // interface and connections built in initComponents(..) 245 } 246 247 // load updateMatchGroup combobox with current contents 248 protected void updateMatchGroupName() { 249 matchGroupName.removeAllItems(); 250 matchGroupName.addItem(Bundle.getMessage("FrameAllGroups")); 251 252 var list = groupStore.getGroupNames(); 253 for (String group : list) { 254 matchGroupName.addItem(group); 255 } 256 257 matchGroupName.setVisible(matchGroupName.getItemCount() > 1); 258 } 259 260 String requireLabelChecked = this.getClass().getName() + ".requireLabelChecked"; // NOI18N 261 String requireBothPCChecked = this.getClass().getName() + ".requireBothPCChecked"; // NOI18N 262 263 @Override 264 public void dispose() { 265 // Save the column layout 266 InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent((tpm) -> { 267 tpm.stopPersisting(table); 268 }); 269 // save checkbox properties 270 UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class); 271 pm.setSimplePreferenceState(requireLabelChecked, showRequiresLabel.isSelected()); 272 pm.setSimplePreferenceState(requireBothPCChecked, showRequiresMatch.isSelected()); 273 274 // remove traffic connection 275 memo.get(OlcbInterface.class).unRegisterMessageListener(monitor); 276 monitor = null; 277 // drop model connections 278 model.dispose(); 279 model = null; 280 // and complete this 281 super.dispose(); 282 } 283 284 @Override 285 public java.util.List<JMenu> getMenus() { 286 // create a file menu 287 var retval = new ArrayList<JMenu>(); 288 var fileMenu = new JMenu(Bundle.getMessage("PaneMenuFile")); 289 fileMenu.setMnemonic(KeyEvent.VK_F); 290 291 var csvWriteItem = new JMenuItem(Bundle.getMessage("PaneSaveToCsv"), KeyEvent.VK_S); 292 KeyStroke ctrlSKeyStroke = KeyStroke.getKeyStroke("control S"); 293 if (jmri.util.SystemType.isMacOSX()) { 294 ctrlSKeyStroke = KeyStroke.getKeyStroke("meta S"); 295 } 296 csvWriteItem.setAccelerator(ctrlSKeyStroke); 297 csvWriteItem.addActionListener(this::writeToCsvFile); 298 fileMenu.add(csvWriteItem); 299 300 var csvReadItem = new JMenuItem(Bundle.getMessage("PaneReadFromCsv"), KeyEvent.VK_O); 301 KeyStroke ctrlOKeyStroke = KeyStroke.getKeyStroke("control O"); 302 if (jmri.util.SystemType.isMacOSX()) { 303 ctrlOKeyStroke = KeyStroke.getKeyStroke("meta O"); 304 } 305 csvReadItem.setAccelerator(ctrlOKeyStroke); 306 csvReadItem.addActionListener(this::readFromCsvFile); 307 fileMenu.add(csvReadItem); 308 309 retval.add(fileMenu); 310 return retval; 311 } 312 313 @Override 314 public String getHelpTarget() { 315 return "package.jmri.jmrix.openlcb.swing.eventtable.EventTablePane"; 316 } 317 318 @Override 319 public String getTitle() { 320 if (memo != null) { 321 return (memo.getUserName() + " " + Bundle.getMessage("TitleEventTable")); 322 } 323 return getTitle(Bundle.getMessage("TitleEventTable")); 324 } 325 326 public void sendRequestEvents(java.awt.event.ActionEvent e) { 327 model.clearAllPresentNodes(); 328 329 model.loadNameStoreEventIDs(); 330 model.handleTableUpdate(-1, -1); 331 332 final int IDENTIFY_EVENTS_DELAY = 250; // msec between operations - 250 events at speed 333 int nextDelay = 0; 334 335 // assumes that a VerifyNodes has been done and all nodes are in the MimicNodeStore 336 for (var memo : mimcStore.getNodeMemos()) { 337 338 jmri.util.ThreadingUtil.runOnLayoutDelayed(() -> { 339 var destNodeID = memo.getNodeID(); 340 log.trace("send IdentifyEventsAddressedMessage {} {}", nid, destNodeID); 341 Message m = new IdentifyEventsAddressedMessage(nid, destNodeID); 342 connection.put(m, null); 343 }, nextDelay); 344 345 nextDelay += IDENTIFY_EVENTS_DELAY; 346 } 347 // Our reference to the node names in the MimicNodeStore will 348 // trigger a SNIP request if we don't have them yet. In case that happens 349 // we want to trigger a table refresh to make sure they get displayed. 350 final int REFRESH_INTERVAL = 1000; 351 jmri.util.ThreadingUtil.runOnGUIDelayed(() -> { 352 model.handleTableUpdate(-1,-1); 353 }, nextDelay+REFRESH_INTERVAL); 354 jmri.util.ThreadingUtil.runOnGUIDelayed(() -> { 355 model.handleTableUpdate(-1,-1); 356 }, nextDelay+REFRESH_INTERVAL*2); 357 jmri.util.ThreadingUtil.runOnGUIDelayed(() -> { 358 model.handleTableUpdate(-1,-1); 359 }, nextDelay+REFRESH_INTERVAL*4); 360 361 } 362 363 void popcornButtonChanged() { 364 model.popcornModeActive = popcorn.isSelected(); 365 log.debug("Popcorn mode {}", model.popcornModeActive); 366 } 367 368 369 public void findRequested(java.awt.event.ActionEvent e) { 370 var text = findID.getText(); 371 // take off all the trailing .00 372 text = text.strip().replaceAll("(.00)*$", ""); 373 log.debug("Request find event [{}]", text); 374 // just search event ID 375 table.clearSelection(); 376 if (findTextSearch(text, EventTableDataModel.COL_EVENTID)) return; 377 } 378 379 public void findTextRequested(java.awt.event.ActionEvent e) { 380 String text = findTextID.getText(); 381 log.debug("Request find text {}", text); 382 // first search event name, then from config, then producer name, then consumer name 383 table.clearSelection(); 384 if (findTextSearch(text, EventTableDataModel.COL_EVENTNAME)) return; 385 if (findTextSearch(text, EventTableDataModel.COL_CONTEXT_INFO)) return; 386 if (findTextSearch(text, EventTableDataModel.COL_PRODUCER_NAME)) return; 387 if (findTextSearch(text, EventTableDataModel.COL_CONSUMER_NAME)) return; 388 return; 389 390 //model.highlightEvent(new EventID(findID.getText())); 391 } 392 393 protected boolean findTextSearch(String text, int column) { 394 text = text.toUpperCase(); 395 try { 396 for (int row = 0; row < model.getRowCount(); row++) { 397 var cell = table.getValueAt(row, column); 398 if (cell == null) continue; 399 var value = cell.toString().toUpperCase(); 400 if (value.startsWith(text)) { 401 table.changeSelection(row, column, false, false); 402 return true; 403 } 404 } 405 } catch (RuntimeException e) { 406 // we get ArrayIndexOutOfBoundsException occasionally for no known reason 407 log.debug("unexpected AIOOBE"); 408 } 409 return false; 410 } 411 412 public void addRequested(java.awt.event.ActionEvent e) { 413 var text = findID.getText(); 414 EventID eventID = new EventID(text); 415 // first, add the event 416 var memo = new EventTableDataModel.TripleMemo( 417 eventID, 418 "", 419 null, 420 "", 421 null, 422 "" 423 ); 424 // check to see if already in there: 425 boolean found = false; 426 for (var check : EventTableDataModel.memos) { 427 if (memo.eventID.equals(check.eventID)) { 428 found = true; 429 break; 430 } 431 } 432 if (! found) { 433 EventTableDataModel.memos.add(memo); 434 } 435 model.fireTableDataChanged(); 436 // now select that one 437 findRequested(e); 438 439 } 440 441 public void sensorRequested(java.awt.event.ActionEvent e) { 442 // loop over sensors to find the OpenLCB ones 443 var beans = InstanceManager.getDefault(SensorManager.class).getNamedBeanSet(); 444 for (NamedBean bean : beans ) { 445 if (bean instanceof OlcbSensor) { 446 oneSensorToTag(true, bean); // active 447 oneSensorToTag(false, bean); // inactive 448 } 449 } 450 } 451 452 private void oneSensorToTag(boolean isActive, NamedBean bean) { 453 var sensor = (OlcbSensor) bean; 454 var sensorID = sensor.getEventID(isActive); 455 if (! isEventNamePresent(sensorID)) { 456 // add the association 457 nameStore.addMatch(sensorID, sensor.getEventName(isActive)); 458 } 459 } 460 461 public void turnoutRequested(java.awt.event.ActionEvent e) { 462 // loop over turnouts to find the OpenLCB ones 463 var beans = InstanceManager.getDefault(TurnoutManager.class).getNamedBeanSet(); 464 for (NamedBean bean : beans ) { 465 if (bean instanceof OlcbTurnout) { 466 oneTurnoutToTag(true, bean); // thrown 467 oneTurnoutToTag(false, bean); // closed 468 } 469 } 470 } 471 472 private void oneTurnoutToTag(boolean isThrown, NamedBean bean) { 473 var turnout = (OlcbTurnout) bean; 474 var turnoutID = turnout.getEventID(isThrown); 475 if (! isEventNamePresent(turnoutID)) { 476 // add the association 477 nameStore.addMatch(turnoutID, turnout.getEventName(isThrown)); 478 } 479 } 480 481 482 // CSV file chooser 483 // static to remember choice from one use to another. 484 static JFileChooser fileChooser = null; 485 486 /** 487 * Write out contents in CSV form 488 * @param e Needed for signature of method, but ignored here 489 */ 490 public void writeToCsvFile(ActionEvent e) { 491 var file = getCsvWriteFile(); 492 493 if (file == null) { 494 log.info("Event Table CVS write aborted"); 495 return; 496 } 497 log.debug("start to export to CSV file {}", file); 498 499 model.csvWriteOperation(file); 500 } 501 502 /** 503 * User selection of file for doing CSV write. 504 * @return selected output file or null for aborted 505 */ 506 File getCsvWriteFile() { 507 if (fileChooser == null) { 508 fileChooser = new jmri.util.swing.JmriJFileChooser(); 509 } 510 fileChooser.setDialogTitle(Bundle.getMessage("PaneSaveCsvFile")); 511 fileChooser.rescanCurrentDirectory(); 512 fileChooser.setSelectedFile(new File("eventtable.csv")); 513 514 int retVal = fileChooser.showSaveDialog(this); 515 516 if (retVal == JFileChooser.APPROVE_OPTION) { 517 return fileChooser.getSelectedFile(); 518 } else { 519 return null; 520 } 521 } 522 523 /** 524 * Read event names from a CSV file 525 * @param e Needed for signature of method, but ignored here 526 */ 527 public void readFromCsvFile(ActionEvent e) { 528 529 File file = getCsvReadFile(); 530 531 model.csvReadOperation(file); 532 } 533 534 File getCsvReadFile() { 535 if (fileChooser == null) { 536 fileChooser = new jmri.util.swing.JmriJFileChooser(); 537 } 538 fileChooser.setDialogTitle(Bundle.getMessage("PaneOpenCsvFile")); 539 fileChooser.rescanCurrentDirectory(); 540 541 int retVal = fileChooser.showOpenDialog(this); 542 543 if (retVal == JFileChooser.APPROVE_OPTION) { 544 File file = fileChooser.getSelectedFile(); 545 if (log.isDebugEnabled()) { 546 log.debug("start to read from CSV file {}", file); 547 } 548 return file; 549 } else { 550 return null; 551 } 552 } 553 554 /** 555 * Check whether a Event Name tag is defined or not. 556 * Check for other uses before changing this. 557 * @param eventID EventID in native form 558 * @return true is the event name tag is present 559 */ 560 public boolean isEventNamePresent(EventID eventID) { 561 return nameStore.hasEventName(eventID); 562 } 563 564 /** 565 * Set up filtering of displayed rows 566 */ 567 private void filter() { 568 RowFilter<EventTableDataModel, Integer> rf = new RowFilter<EventTableDataModel, Integer>() { 569 /** 570 * @return true if row is to be displayed 571 */ 572 @Override 573 public boolean include(RowFilter.Entry<? extends EventTableDataModel, ? extends Integer> entry) { 574 575 int row = entry.getIdentifier(); 576 577 var name = model.getValueAt(row, EventTableDataModel.COL_EVENTNAME); 578 if ( showRequiresLabel.isSelected() && (name == null || name.toString().isEmpty()) ) return false; 579 580 if ( showRequiresMatch.isSelected()) { 581 var memo = model.getTripleMemo(row); 582 583 if (memo.producer == null && !model.producerPresent(memo.eventID)) { 584 // no matching producer 585 return false; 586 } 587 588 if (memo.consumer == null && !model.consumerPresent(memo.eventID)) { 589 // no matching consumer 590 return false; 591 } 592 } 593 594 // check for group match 595 if ( matchGroupName.getSelectedIndex() > 0) { // -1 is empty combobox 596 String group = matchGroupName.getSelectedItem().toString(); 597 var memo = model.getTripleMemo(row); 598 if ( (! groupStore.isNodeInGroup(memo.producer, group)) 599 && (! groupStore.isNodeInGroup(memo.consumer, group)) ) { 600 return false; 601 } 602 } 603 604 // passed all filters 605 return true; 606 } 607 }; 608 sorter.setRowFilter(rf); 609 } 610 611 /** 612 * Internal class to watch OpenLCB traffic 613 */ 614 615 static class Monitor extends MessageDecoder { 616 617 Monitor(EventTableDataModel model) { 618 this.model = model; 619 } 620 621 EventTableDataModel model; 622 623 /** 624 * Handle "Producer/Consumer Event Report" message 625 * @param msg message to handle 626 * @param sender connection where it came from 627 */ 628 @Override 629 public void handleProducerConsumerEventReport(ProducerConsumerEventReportMessage msg, Connection sender){ 630 ThreadingUtil.runOnGUIEventually(()->{ 631 var nodeID = msg.getSourceNodeID(); 632 var eventID = msg.getEventID(); 633 model.recordProducer(eventID, nodeID, "", true); 634 model.highlightProducer(eventID, nodeID); 635 }); 636 } 637 638 /** 639 * Handle "Consumer Identified" message 640 * @param msg message to handle 641 * @param sender connection where it came from 642 */ 643 @Override 644 public void handleConsumerIdentified(ConsumerIdentifiedMessage msg, Connection sender){ 645 ThreadingUtil.runOnGUIEventually(()->{ 646 var nodeID = msg.getSourceNodeID(); 647 var eventID = msg.getEventID(); 648 model.recordConsumer(eventID, nodeID, ""); 649 }); 650 } 651 652 /** 653 * Handle "Producer Identified" message 654 * @param msg message to handle 655 * @param sender connection where it came from 656 */ 657 @Override 658 public void handleProducerIdentified(ProducerIdentifiedMessage msg, Connection sender){ 659 ThreadingUtil.runOnGUIEventually(()->{ 660 var nodeID = msg.getSourceNodeID(); 661 var eventID = msg.getEventID(); 662 model.recordProducer(eventID, nodeID, "", false); 663 }); 664 } 665 666 @Override 667 public void handleConsumerRangeIdentified(ConsumerRangeIdentifiedMessage msg, Connection sender){ 668 ThreadingUtil.runOnGUIEventually(()->{ 669 final var nodeID = msg.getSourceNodeID(); 670 final var eventID = msg.getEventID(); 671 672 final long rangeSuffix = eventID.rangeSuffix(); 673 // have to set low part of event ID to 0's as it might be 1's 674 EventID zeroedEID = new EventID(eventID.toLong() & (~rangeSuffix)); 675 676 model.recordConsumer(zeroedEID, nodeID, (new EventID(eventID.toLong() | rangeSuffix)).toShortString()); 677 }); 678 } 679 680 @Override 681 public void handleProducerRangeIdentified(ProducerRangeIdentifiedMessage msg, Connection sender){ 682 ThreadingUtil.runOnGUIEventually(()->{ 683 final var nodeID = msg.getSourceNodeID(); 684 final var eventID = msg.getEventID(); 685 686 final long rangeSuffix = eventID.rangeSuffix(); 687 // have to set low part of event ID to 0's as it might be 1's 688 EventID zeroedEID = new EventID(eventID.toLong() & (~rangeSuffix)); 689 690 model.recordProducer(zeroedEID, nodeID, (new EventID(eventID.toLong() | rangeSuffix)).toShortString(), false); 691 }); 692 } 693 694 /* 695 * We no longer handle "Simple Node Ident Info Reply" messages because of 696 * excessive redisplays. Instead, we expect the MimicNodeStore to handle 697 * these and provide the information when requested. 698 */ 699 } 700 701 /** 702 * Nested class to create one of these using old-style defaults 703 */ 704 public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 705 706 public Default() { 707 super("LCC Event Table", 708 new jmri.util.swing.sdi.JmriJFrameInterface(), 709 EventTablePane.class.getName(), 710 jmri.InstanceManager.getNullableDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 711 } 712 713 public Default(String name, jmri.util.swing.WindowInterface iface) { 714 super(name, 715 iface, 716 EventTablePane.class.getName(), 717 jmri.InstanceManager.getNullableDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 718 } 719 720 public Default(String name, Icon icon, jmri.util.swing.WindowInterface iface) { 721 super(name, 722 icon, iface, 723 EventTablePane.class.getName(), 724 jmri.InstanceManager.getNullableDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 725 } 726 } 727 728 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EventTablePane.class); 729}