001package jmri.jmrix.can.cbus.swing.nodeconfig; 002 003import java.awt.BorderLayout; 004import java.awt.datatransfer.DataFlavor; 005import java.awt.datatransfer.Transferable; 006import java.awt.Dimension; 007import java.awt.datatransfer.UnsupportedFlavorException; 008import java.awt.event.ActionListener; 009import java.beans.PropertyChangeListener; 010import java.beans.PropertyChangeEvent; 011import java.io.IOException; 012import java.util.*; 013 014import javax.annotation.CheckForNull; 015import javax.annotation.Nonnull; 016import javax.swing.*; 017import javax.swing.event.*; 018 019import jmri.GlobalProgrammerManager; 020import jmri.jmrix.can.CanMessage; 021import jmri.jmrix.can.CanSystemConnectionMemo; 022import jmri.jmrix.can.cbus.*; 023import jmri.jmrix.can.cbus.CbusDccProgrammer.CbusDccProgrammerConfigurator; 024import jmri.jmrix.can.cbus.node.CbusNode; 025import jmri.jmrix.can.cbus.node.CbusNodeEvent; 026import jmri.jmrix.can.cbus.node.CbusNodeTableDataModel; 027import jmri.jmrix.can.cbus.swing.modules.CbusConfigPaneProvider; 028import jmri.util.ThreadingUtil; 029import jmri.util.swing.JmriJOptionPane; 030 031/** 032 * Master Pane for CBUS node configuration incl. CBUS node table 033 * 034 * @author Bob Jacobsen Copyright (C) 2008 035 * @author Steve Young (C) 2019 036 * @see CbusNodeTableDataModel 037 * 038 * @since 2.99.2 039 */ 040public class NodeConfigToolPane extends jmri.jmrix.can.swing.CanPanel implements PropertyChangeListener{ 041 042 public JTable nodeTable; 043 private CbusPreferences preferences; 044 045 protected CbusNodeTablePane nodeTablePane; 046 private CbusNodeRestoreFcuFrame fcuFrame; 047 private CbusNodeEditEventFrame _editEventFrame; 048 049 private JScrollPane eventScroll; 050 private JSplitPane split; 051 protected JTabbedPane tabbedPane; 052 053 private ArrayList<CbusNodeConfigTab> tabbedPanes; 054 055 private int _selectedNode; 056 private jmri.util.swing.BusyDialog busy_dialog; 057 058 public int NODE_SEARCH_TIMEOUT = 5000; 059 060 private final JMenuItem teachNodeFromFcuFile; 061 private final JMenuItem searchForNodesMenuItem; 062 private final JCheckBoxMenuItem nodeNumRequestMenuItem; 063 private final JRadioButtonMenuItem backgroundDisabled; 064 private final JRadioButtonMenuItem backgroundSlow; 065 private final JRadioButtonMenuItem backgroundFast; 066 private final JCheckBoxMenuItem addCommandStationMenuItem; 067 private final JCheckBoxMenuItem addNodesMenuItem; 068 private final JCheckBoxMenuItem startupCommandStationMenuItem; 069 private final JCheckBoxMenuItem startupNodesMenuItem; 070 private final JCheckBoxMenuItem startupNodesXmlMenuItem; 071 private final JRadioButtonMenuItem zeroBackups; 072 private final JRadioButtonMenuItem fiveBackups; 073 private final JRadioButtonMenuItem tenBackups; 074 private final JRadioButtonMenuItem twentyBackups; 075 private CbusDccProgrammerManager progMan; 076 077 /** 078 * {@inheritDoc} 079 */ 080 @Override 081 public void initComponents(CanSystemConnectionMemo memo) { 082 super.initComponents(memo); 083 084 CbusConfigPaneProvider.loadInstances(); 085 086 _selectedNode = -1; 087 088 preferences = memo.get(jmri.jmrix.can.cbus.CbusPreferences.class); 089 try { 090 progMan = memo.get(CbusConfigurationManager.class).get(GlobalProgrammerManager.class); 091 } catch (NullPointerException e) { 092 log.info("No Global Programmer available for NV programming"); 093 } 094 init(); 095 096 } 097 098 /** 099 * Create a new NodeConfigToolPane 100 */ 101 public NodeConfigToolPane() { 102 super(); 103 nodeNumRequestMenuItem = new JCheckBoxMenuItem(("Listen for Node Number Requests")); 104 teachNodeFromFcuFile = new JMenuItem(("Restore Node / Import Data from FCU XML")); // FCU 105 searchForNodesMenuItem = new JMenuItem("Search for Nodes and Command Stations"); 106 addCommandStationMenuItem = new JCheckBoxMenuItem(("Add Command Stations when found")); 107 addNodesMenuItem = new JCheckBoxMenuItem(("Add Nodes when found")); 108 backgroundDisabled = new JRadioButtonMenuItem(Bundle.getMessage("HighlightDisabled")); 109 backgroundSlow = new JRadioButtonMenuItem(("Slow")); 110 backgroundFast = new JRadioButtonMenuItem(("Fast")); 111 112 startupCommandStationMenuItem = new JCheckBoxMenuItem(("Search Command Stations on Startup")); 113 startupNodesMenuItem = new JCheckBoxMenuItem(("Search Nodes on Startup")); 114 115 startupNodesXmlMenuItem = new JCheckBoxMenuItem(("Add previously seen Nodes on Startup")); 116 zeroBackups = new JRadioButtonMenuItem(("0")); 117 fiveBackups = new JRadioButtonMenuItem(("5")); 118 tenBackups = new JRadioButtonMenuItem(("10")); 119 twentyBackups = new JRadioButtonMenuItem(("20")); 120 } 121 122 protected final ArrayList<CbusNodeConfigTab> getTabs() { 123 if (tabbedPanes==null) { 124 tabbedPanes = new ArrayList<>(6); 125 tabbedPanes.add( new CbusNodeInfoPane(this)); 126 tabbedPanes.add( new CbusNodeUserCommentsPane(this)); 127 tabbedPanes.add( new CbusNodeEditNVarPane(this)); 128 tabbedPanes.add( new CbusNodeEventVarPane(this)); 129 tabbedPanes.add( new CbusNodeSetupPane(this)); 130 tabbedPanes.add( new CbusNodeBackupsPane(this)); 131 } 132 return new ArrayList<>(this.tabbedPanes); 133 } 134 135 /** 136 * Initialise the NodeConfigToolPane 137 */ 138 public void init() { 139 140 setMenuOptions(); // called when memo available 141 142 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 143 144 // main pane 145 JPanel _pane1 = new JPanel(); 146 _pane1.setLayout(new BorderLayout()); 147 148 // basis for future menu-bar if one required 149 150 // buttoncontainer.setLayout(new BorderLayout()); 151 // updatenodesButton = new JButton(("Search for Nodes")); 152 // buttoncontainer.add(updatenodesButton); 153 // JPanel toppanelcontainer = new JPanel(); 154 // toppanelcontainer.setLayout(new BoxLayout(toppanelcontainer, BoxLayout.X_AXIS)); 155 // toppanelcontainer.add(buttoncontainer); 156 // pane1.add(toppanelcontainer, BorderLayout.PAGE_START); 157 158 // scroller for main table 159 nodeTablePane = new CbusNodeTablePane(); 160 nodeTablePane.initComponents(memo); 161 162 nodeTable = nodeTablePane.nodeTable; 163 164 eventScroll = new JScrollPane( nodeTablePane ); 165 166 JPanel mainNodePane = new JPanel(); 167 168 mainNodePane.setLayout(new BorderLayout()); 169 mainNodePane.add(eventScroll); 170 171 tabbedPane = new JTabbedPane(); 172 173 tabbedPane.setEnabled(false); 174 175 getTabs().forEach((pn) -> { 176 tabbedPane.addTab(pn.getTitle(),pn); 177 }); 178 179 Dimension minimumSize = new Dimension(40, 40); 180 mainNodePane.setMinimumSize(minimumSize); 181 tabbedPane.setMinimumSize(minimumSize); 182 183 this.setPreferredSize(new Dimension(700, 450)); 184 185 split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, mainNodePane, tabbedPane); 186 split.setDividerLocation(preferences.getNodeTableSplit()); // px from top of node table pane 187 split.setContinuousLayout(true); 188 _pane1.add(split, BorderLayout.CENTER); 189 split.addPropertyChangeListener((PropertyChangeEvent changeEvent) -> { 190 JSplitPane sourceSplitPane = (JSplitPane) changeEvent.getSource(); 191 String propertyName = changeEvent.getPropertyName(); 192 if (propertyName.equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) { 193 preferences.setNodeTableSplit(sourceSplitPane.getDividerLocation()); 194 } 195 }); 196 197 add(_pane1); 198 ThreadingUtil.runOnGUI( () -> _pane1.setVisible(true) ); 199 200 tabbedPane.addChangeListener((ChangeEvent e) -> { 201 userViewChanged(); 202 }); 203 204 // also add listener to tab action 205 nodeTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { 206 if ( !e.getValueIsAdjusting() ) { 207 userViewChanged(); 208 } 209 }); 210 211 userViewChanged(); 212 213 tabbedPane.setTransferHandler(new TransferHandler()); 214 nodeTable.setTransferHandler(new TransferHandler()); 215 216 revalidate(); 217 218 } 219 220 private final JFrame topFrame = (JFrame) javax.swing.SwingUtilities.getWindowAncestor(this); 221 222 /** 223 * Create a document-modal Dialog with node search results. 224 * @param csfound number of Command Stations 225 * @param ndfound number of nodes 226 */ 227 public void notifyNodeSearchComplete(int csfound, int ndfound){ 228 busy_dialog.finish(); 229 busy_dialog=null; 230 231 JmriJOptionPane.showMessageDialog(this, "<html><h3>Node Responses : " + ndfound + 232 "</h3><p>Of which Command Stations: " + csfound + "</p></html>", "Node Search Complete", JmriJOptionPane.INFORMATION_MESSAGE); 233 searchForNodesMenuItem.setEnabled(true); 234 } 235 236 /** 237 * Notify this pane that the selected node or viewed tab has changed 238 */ 239 protected void userViewChanged(){ 240 241 int sel = nodeTable.getSelectedRow(); 242 int rowBefore = nodeTable.getSelectedRow()-1; 243 int rowAfter = nodeTable.getSelectedRow()+1; 244 if ( sel > -1 ) { 245 tabbedPane.setEnabled(true); 246 247 248 _selectedNode = (int) nodeTable.getModel().getValueAt(nodeTable.convertRowIndexToModel(sel), CbusNodeTableDataModel.NODE_NUMBER_COLUMN); 249 250 int tabindex = tabbedPane.getSelectedIndex(); 251 252 int nodeBefore = -1; 253 int nodeAfter = -1; 254 255 if ( rowBefore > -1 ) { 256 nodeBefore = (int) nodeTable.getModel().getValueAt(rowBefore, CbusNodeTableDataModel.NODE_NUMBER_COLUMN); 257 } 258 if ( rowAfter < nodeTable.getRowCount() ) { 259 nodeAfter = (int) nodeTable.getModel().getValueAt(rowAfter, CbusNodeTableDataModel.NODE_NUMBER_COLUMN); 260 } 261 262 log.debug("node {} selected tab index {} , node before {} node after {}", _selectedNode , tabindex, nodeBefore,nodeAfter ); 263 264 boolean veto = false; 265 for (CbusNodeConfigTab tab : getTabs()) { 266 if ( tab.getActiveDialog() || tab.getVetoBeingChanged()) { 267 veto = true; 268 break; // or return obj 269 } 270 } 271 272 if (veto){ 273 return; 274 } 275 276 tabbedPane.setSelectedIndex(tabindex); 277 nodeTable.setRowSelectionInterval(sel,sel); 278 279 // this also starts urgent fetch loop if not currently looping 280 getNodeModel().setUrgentFetch(_selectedNode,nodeBefore,nodeAfter); 281 282 getTabs().get(tabindex).setNode( getNodeModel().getNodeByNodeNum(_selectedNode) ); 283 284 try { 285 ((CbusDccProgrammerConfigurator)(progMan.getGlobalProgrammer().getConfigurator())) 286 .setNodeOfInterest(getNodeModel().getNodeByNodeNum(_selectedNode)); 287 } catch(NullPointerException e) { 288 log.info("No programmer available for NV programming"); 289 } 290 } 291 else { 292 tabbedPane.setEnabled(false); 293 294 } 295 } 296 297 /** 298 * Set Menu Options eg. which checkboxes etc. should be checked 299 */ 300 private void setMenuOptions(){ 301 302 nodeNumRequestMenuItem.setSelected( 303 preferences.getAllocateNNListener() ); 304 backgroundDisabled.setSelected(false); 305 backgroundSlow.setSelected(false); 306 backgroundFast.setSelected(false); 307 308 switch ((int) preferences.getNodeBackgroundFetchDelay()) { 309 case 0: 310 backgroundDisabled.setSelected(true); 311 break; 312 case 50: 313 backgroundFast.setSelected(true); 314 break; 315 case 100: 316 backgroundSlow.setSelected(true); 317 break; 318 default: 319 break; 320 } 321 322 addCommandStationMenuItem.setSelected( preferences.getAddCommandStations() ); 323 addNodesMenuItem.setSelected( preferences.getAddNodes() ); 324 325 startupCommandStationMenuItem.setSelected( preferences.getStartupSearchForCs() ); 326 startupNodesMenuItem.setSelected( preferences.getStartupSearchForNodes() ); 327 startupNodesXmlMenuItem.setSelected( preferences.getSearchForNodesBackupXmlOnStartup() ); 328 329 zeroBackups.setSelected(false); 330 fiveBackups.setSelected(false); 331 tenBackups.setSelected(false); 332 twentyBackups.setSelected(false); 333 334 switch (preferences.getMinimumNumBackupsToKeep()) { 335 case 0: 336 zeroBackups.setSelected(true); 337 break; 338 case 5: 339 fiveBackups.setSelected(true); 340 break; 341 case 10: 342 tenBackups.setSelected(true); 343 break; 344 case 20: 345 twentyBackups.setSelected(true); 346 break; 347 default: 348 break; 349 } 350 351 } 352 353 /** 354 * Creates a Menu List. 355 * {@inheritDoc} 356 */ 357 @Override 358 public List<JMenu> getMenus() { 359 List<JMenu> menuList = new ArrayList<>(); 360 361 JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile")); 362 363 fileMenu.add(teachNodeFromFcuFile); 364 365 JMenu optionsMenu = new JMenu("Options"); 366 367 368 369 JMenuItem sendSysResetMenuItem = new JMenuItem("Send System Reset"); 370 371 searchForNodesMenuItem.setToolTipText(("Timeout set to " + NODE_SEARCH_TIMEOUT + "ms.")); 372 373 374 nodeNumRequestMenuItem.setToolTipText("Also adds a check for any node already awaiting a number when performing node searches."); 375 376 377 378 JMenu backgroundFetchMenu = new JMenu("Node Info Fetch Speed"); 379 ButtonGroup backgroundFetchGroup = new ButtonGroup(); 380 381 382 383 JMenu numBackupsMenu = new JMenu("Min. Auto Backups to retain"); 384 ButtonGroup minNumBackupsGroup = new ButtonGroup(); 385 386 387 388 minNumBackupsGroup.add(zeroBackups); 389 minNumBackupsGroup.add(fiveBackups); 390 minNumBackupsGroup.add(tenBackups); 391 minNumBackupsGroup.add(twentyBackups); 392 393 numBackupsMenu.add(zeroBackups); 394 numBackupsMenu.add(fiveBackups); 395 numBackupsMenu.add(tenBackups); 396 numBackupsMenu.add(twentyBackups); 397 398 backgroundFetchGroup.add(backgroundDisabled); 399 backgroundFetchGroup.add(backgroundSlow); 400 backgroundFetchGroup.add(backgroundFast); 401 402 backgroundFetchMenu.add(backgroundDisabled); 403 backgroundFetchMenu.add(backgroundSlow); 404 backgroundFetchMenu.add(backgroundFast); 405 406 optionsMenu.add( searchForNodesMenuItem ); 407 optionsMenu.add( new JSeparator() ); 408 optionsMenu.add( sendSysResetMenuItem ); 409 optionsMenu.add( new JSeparator() ); 410 optionsMenu.add( backgroundFetchMenu ); 411 optionsMenu.add( new JSeparator() ); 412 optionsMenu.add( nodeNumRequestMenuItem ); 413 optionsMenu.add( new JSeparator() ); 414 optionsMenu.add( addCommandStationMenuItem ); 415 optionsMenu.add( addNodesMenuItem ); 416 optionsMenu.add( new JSeparator() ); 417 optionsMenu.add( startupCommandStationMenuItem ); 418 optionsMenu.add( startupNodesMenuItem ); 419 optionsMenu.add( startupNodesXmlMenuItem ); 420 optionsMenu.add( new JSeparator() ); 421 optionsMenu.add( numBackupsMenu ); 422 423 menuList.add(fileMenu); 424 menuList.add(optionsMenu); 425 426 ActionListener teachNodeFcu = ae -> { 427 fcuFrame = new CbusNodeRestoreFcuFrame(this); 428 fcuFrame.initComponents(memo); 429 }; 430 431 teachNodeFromFcuFile.addActionListener(teachNodeFcu); 432 433 // saved preferences go through the cbus table model so they can be actioned immediately 434 // they'll be also saved by the table, not here. 435 436 ActionListener updatenodes = ae -> { 437 searchForNodesMenuItem.setEnabled(false); 438 busy_dialog = new jmri.util.swing.BusyDialog(topFrame, "Node Search", false); 439 busy_dialog.start(); 440 getNodeModel().startASearchForNodes( this , NODE_SEARCH_TIMEOUT ); 441 }; 442 searchForNodesMenuItem.addActionListener(updatenodes); 443 444 ActionListener systemReset = ae -> { 445 new CbusSend(memo).aRST(); 446 // flash something to user so they know that something has happened 447 busy_dialog = new jmri.util.swing.BusyDialog(topFrame, "System Reset", false); 448 busy_dialog.start(); 449 ThreadingUtil.runOnGUIDelayed( () -> { 450 busy_dialog.finish(); 451 busy_dialog=null; 452 },300 ); 453 }; 454 sendSysResetMenuItem.addActionListener(systemReset); 455 456 ActionListener nodeRequestActive = ae -> { 457 getNodeModel().setBackgroundAllocateListener( nodeNumRequestMenuItem.isSelected() ); 458 preferences.setAllocateNNListener( nodeNumRequestMenuItem.isSelected() ); 459 }; 460 nodeNumRequestMenuItem.addActionListener(nodeRequestActive); 461 462 // values need to match setMenuOptions() 463 ActionListener fetchListener = ae -> { 464 if ( backgroundDisabled.isSelected() ) { 465 preferences.setNodeBackgroundFetchDelay(0L); 466 getNodeModel().startBackgroundFetch(); 467 } 468 else if ( backgroundSlow.isSelected() ) { 469 preferences.setNodeBackgroundFetchDelay(100L); 470 getNodeModel().startBackgroundFetch(); 471 } 472 else if ( backgroundFast.isSelected() ) { 473 preferences.setNodeBackgroundFetchDelay(50L); 474 getNodeModel().startBackgroundFetch(); 475 } 476 }; 477 backgroundDisabled.addActionListener(fetchListener); 478 backgroundSlow.addActionListener(fetchListener); 479 backgroundFast.addActionListener(fetchListener); 480 481 ActionListener addCsListener = ae -> { 482 preferences.setAddCommandStations( addCommandStationMenuItem.isSelected() ); 483 }; 484 addCommandStationMenuItem.addActionListener(addCsListener); 485 486 ActionListener addNodeListener = ae -> { 487 preferences.setAddNodes( addNodesMenuItem.isSelected() ); 488 }; 489 addNodesMenuItem.addActionListener(addNodeListener); 490 491 ActionListener addstartupCommandStationMenuItem = ae -> { 492 preferences.setStartupSearchForCs( startupCommandStationMenuItem.isSelected() ); 493 }; 494 startupCommandStationMenuItem.addActionListener(addstartupCommandStationMenuItem); 495 496 ActionListener addstartupNodesMenuItem = ae -> { 497 preferences.setStartupSearchForNodes( startupNodesMenuItem.isSelected() ); 498 }; 499 startupNodesMenuItem.addActionListener(addstartupNodesMenuItem); 500 501 ActionListener addstartupNodesXmlMenuItem = ae -> { 502 preferences.setSearchForNodesBackupXmlOnStartup( startupNodesXmlMenuItem.isSelected() ); 503 }; 504 startupNodesXmlMenuItem.addActionListener(addstartupNodesXmlMenuItem); 505 506 // values need to match setMenuOptions() 507 ActionListener minBackupsListener = ae -> { 508 if ( zeroBackups.isSelected() ) { 509 preferences.setMinimumNumBackupsToKeep(0); 510 } 511 else if ( fiveBackups.isSelected() ) { 512 preferences.setMinimumNumBackupsToKeep(5); 513 } 514 else if ( tenBackups.isSelected() ) { 515 preferences.setMinimumNumBackupsToKeep(10); 516 } 517 else if ( twentyBackups.isSelected() ) { 518 preferences.setMinimumNumBackupsToKeep(10); 519 } 520 }; 521 zeroBackups.addActionListener(minBackupsListener); 522 fiveBackups.addActionListener(minBackupsListener); 523 tenBackups.addActionListener(minBackupsListener); 524 twentyBackups.addActionListener(minBackupsListener); 525 526 527 return menuList; 528 } 529 530 /** 531 * Set Restore from FCU Menu Item active as only 1 instance per NodeConfigToolPane allowed 532 * @param isActive set true if Frame opened, else false to notify closed 533 */ 534 protected void setRestoreFcuActive( boolean isActive ){ 535 teachNodeFromFcuFile.setEnabled(!isActive); 536 } 537 538 /** 539 * {@inheritDoc} 540 */ 541 @Override 542 public String getTitle() { 543 return prependConnToString(Bundle.getMessage("MenuItemNodeConfig")); 544 } 545 546 /** 547 * {@inheritDoc} 548 */ 549 @Override 550 public String getHelpTarget() { 551 return "package.jmri.jmrix.can.cbus.swing.nodeconfig.NodeConfigToolPane"; 552 } 553 554 /** 555 * {@inheritDoc} 556 */ 557 @Override 558 public void dispose() { 559 560 if (_toNode!=null){ 561 _toNode.removePropertyChangeListener(this); 562 } 563 564 // nodeTable = null; 565 // eventScroll = null; 566 567 // Dispose all tabs, each tab registers itself as a listener on its node, 568 // so any not disposed remain in memory reacting to node changes after closing. 569 // The NV edit pane may also need to take a node out of learn mode. 570 getTabs().forEach(CbusNodeConfigTab::dispose); 571 572 super.dispose(); 573 } 574 575 /** 576 * Handles drag actions containing CBUS events to edit / teach to a node 577 */ 578 private class TransferHandler extends javax.swing.TransferHandler { 579 /** 580 * {@inheritDoc} 581 */ 582 @Override 583 public boolean canImport(JComponent c, DataFlavor[] transferFlavors) { 584 585 // prevent draggable on startup when a node has not yet been selected 586 // the draggable must still be able to select a table row 587 if ( (c instanceof JTabbedPane ) && ( _selectedNode < 1 )){ 588 return false; 589 } 590 591 for (DataFlavor flavor : transferFlavors) { 592 if (DataFlavor.stringFlavor.equals(flavor)) { 593 return true; 594 } 595 } 596 return false; 597 } 598 599 /** 600 * {@inheritDoc} 601 */ 602 @Override 603 public boolean importData(JComponent c, Transferable t) { 604 if (canImport(c, t.getTransferDataFlavors())) { 605 606 String eventInJmriFormat; 607 try { 608 eventInJmriFormat = (String) t.getTransferData(DataFlavor.stringFlavor); 609 } catch (UnsupportedFlavorException | IOException e) { 610 log.error("unable to get dragged address", e); 611 return false; 612 } 613 return openNewOrEditEventFrame(eventInJmriFormat); 614 } 615 return false; 616 } 617 } 618 619 /** 620 * Opens a new or edit event frame depending on if existing 621 * @param eventInJmriFormat standard CBUS Sensor / Turnout phrase, eg "+44", "-N123E456", "X0A0B" 622 * @return false if issue opening or editing 623 */ 624 private boolean openNewOrEditEventFrame( String eventInJmriFormat ){ 625 626 // do some validation on the input string 627 // processed in the same way as a sensor, turnout or light so less chance of breaking in future 628 // and can also accept the Hex "X1234;X654876" format 629 String validatedAddr; 630 try { 631 validatedAddr = CbusAddress.validateSysName( eventInJmriFormat ); 632 } catch (IllegalArgumentException e) { 633 return false; 634 } 635 636 CbusNode _node = getNodeModel().getNodeByNodeNum( _selectedNode ); 637 if (_node==null){ 638 log.warn("No Node"); 639 return false; 640 } 641 642 CanMessage m = ( new CbusAddress(validatedAddr) ).makeMessage(0x12); 643 644 CbusNodeEvent newev = new CbusNodeEvent( memo, 645 CbusMessage.getNodeNumber(m), 646 CbusMessage.getEvent(m), 647 _selectedNode, 648 -1, 649 _node.getNodeParamManager().getParameter(5) 650 ); 651 java.util.Arrays.fill(newev.getEvVarArray(),0); 652 653 log.debug("dragged nodeevent {} ",newev); 654 ThreadingUtil.runOnGUI( () -> { 655 getEditEvFrame().initComponents(memo,newev); 656 }); 657 return true; 658 } 659 660 /** 661 * Get the edit event frame 662 * this could be requested from 663 * CbusNodeEventDataModel button click to edit event, 664 * this class when it receives an event via drag n drop, 665 * creating new event from CbusNodeEventVarPane 666 * @return the Frame 667 */ 668 public CbusNodeEditEventFrame getEditEvFrame(){ 669 if (_editEventFrame == null ){ 670 _editEventFrame = new CbusNodeEditEventFrame(this); 671 } 672 return _editEventFrame; 673 } 674 675 /** 676 * Receive notification from the frame that it has disposed 677 */ 678 protected void clearEditEventFrame() { 679 _editEventFrame = null; 680 } 681 682 private boolean _clearEvents; 683 private boolean _teachEvents; 684 private CbusNode _fromNode; 685 private CbusNode _toNode; 686 private JFrame _frame; 687 688 /** 689 * Show a Confirm before Save Dialogue Box then start teach process for Node 690 * <p> 691 * Used in Node Backup restore, Restore from FCU, edit NV's 692 * Edit Event variables currently use a custom dialogue, not this 693 * @param fromNode Node to get data from 694 * @param toNode Node to send changes to 695 * @param teachNVs true to Teach NV's 696 * @param clearEvents true to clear events before teaching new ones 697 * @param teachEvents true to teach events 698 * @param frame the frame to which dialogue boxes can be attached to 699 */ 700 protected void showConfirmThenSave( @Nonnull CbusNode fromNode, @Nonnull CbusNode toNode, 701 boolean teachNVs, boolean clearEvents, boolean teachEvents, @CheckForNull JFrame frame){ 702 703 _clearEvents = clearEvents; 704 _teachEvents = teachEvents; 705 _fromNode = fromNode; 706 _toNode = toNode; 707 708 if ( frame == null ){ 709 frame = topFrame; 710 } 711 _frame = frame; 712 713 StringBuilder buf = new StringBuilder(); 714 buf.append("<html> ") 715 .append( ("Please Confirm Write ") ) 716 .append( ("to <br>") ) 717 .append ( _toNode.toString() ) 718 .append("<hr>"); 719 720 if ( teachNVs ){ 721 722 // Bundle.getMessage("NVConfirmWrite",nodeName) 723 buf.append("Teaching ") 724 .append(_toNode.getNodeNvManager().getNvDifference(_fromNode)) 725 .append(" of ").append(_fromNode.getNodeNvManager().getTotalNVs()).append(" NV's<br>"); 726 } 727 if ( _clearEvents ){ 728 buf.append("Clearing ").append(Math.max( 0,_toNode.getNodeEventManager().getTotalNodeEvents() )).append(" Events<br>"); 729 } 730 if ( _teachEvents ){ 731 buf.append("Teaching ").append(Math.max( 0,_fromNode.getNodeEventManager().getTotalNodeEvents() )).append(" Events<br>"); 732 } 733 buf.append("</html>"); 734 735 int response = JmriJOptionPane.showConfirmDialog(frame, 736 ( buf.toString() ), 737 ( ("Please Confirm Write to Node")), 738 JmriJOptionPane.OK_CANCEL_OPTION, 739 JmriJOptionPane.QUESTION_MESSAGE); 740 if ( response == JmriJOptionPane.OK_OPTION ) { 741 _toNode.addPropertyChangeListener(this); 742 busy_dialog = new jmri.util.swing.BusyDialog(frame, "Write NVs "+_fromNode.toString(), false); 743 busy_dialog.start(); 744 // update main node name from fcu name 745 _toNode.setNameIfNoName( _fromNode.getUserName() ); 746 // request the local nv model pass the nv update request to the CbusNode 747 if ( teachNVs ){ 748 _toNode.getNodeNvManager().sendNvsToNode( _fromNode.getNodeNvManager().getNvArray()); 749 } 750 else { 751 nVTeachComplete(0); 752 } 753 } 754 } 755 756 /** {@inheritDoc} */ 757 @Override 758 public void propertyChange(PropertyChangeEvent ev){ 759 if (ev.getPropertyName().equals("TEACHNVCOMPLETE")) { 760 jmri.util.ThreadingUtil.runOnGUIEventually( ()->{ 761 nVTeachComplete((Integer) ev.getNewValue()); 762 }); 763 } 764 else if (ev.getPropertyName().equals("ADDALLEVCOMPLETE")) { 765 jmri.util.ThreadingUtil.runOnGUIEventually( ()->{ 766 teachEventsComplete((Integer) ev.getNewValue()); 767 }); 768 } 769 } 770 771 /** 772 * Notification from CbusNode NV Teach is complete 773 * Starts check to see if clear events 774 * @param numErrors number of errors writing NVs 775 */ 776 private void nVTeachComplete(int numErrors){ 777 if ( numErrors > 0 ) { 778 JmriJOptionPane.showMessageDialog(_frame, 779 Bundle.getMessage("NVSetFailTitle",numErrors), Bundle.getMessage("WarningTitle"), 780 JmriJOptionPane.ERROR_MESSAGE); 781 } 782 783 if ( _clearEvents ){ 784 785 busy_dialog.setTitle("Clear Events"); 786 787 // node enter learn mode 788 _toNode.send.nodeEnterLearnEvMode( _toNode.getNodeNumber() ); 789 // no response expected but we add a mini delay for other traffic 790 791 ThreadingUtil.runOnLayoutDelayed( () -> { 792 _toNode.send.nNCLR(_toNode.getNodeNumber());// no response expected 793 }, 150 ); 794 ThreadingUtil.runOnLayoutDelayed( () -> { 795 // node exit learn mode 796 _toNode.send.nodeExitLearnEvMode( _toNode.getNodeNumber() ); // no response expected 797 }, jmri.jmrix.can.cbus.node.CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME ); 798 ThreadingUtil.runOnGUIDelayed( () -> { 799 800 clearEventsComplete(); 801 802 }, ( jmri.jmrix.can.cbus.node.CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME + 150 ) ); 803 } 804 else { 805 clearEventsComplete(); 806 } 807 } 808 809 /** 810 * When clear Events completed ( in nvTeachComplete ) 811 * starts process for teaching events to Node 812 */ 813 private void clearEventsComplete() { 814 ArrayList<CbusNodeEvent> arL = _fromNode.getNodeEventManager().getEventArray(); 815 if ( _teachEvents){ 816 if (arL==null){ 817 log.error("No Event Array on Node {}",_fromNode); 818 teachEventsComplete(1); 819 return; 820 } 821 busy_dialog.setTitle("Teach Events"); 822 _toNode.getNodeEventManager().sendNewEvSToNode( arL ); 823 } 824 else { 825 teachEventsComplete(0); 826 } 827 } 828 829 /** 830 * Notification from CbusNode Event Teach is complete 831 * @param numErrors number of errors writing events 832 */ 833 private void teachEventsComplete( int numErrors ) { 834 _toNode.removePropertyChangeListener(this); 835 busy_dialog.finish(); 836 busy_dialog = null; 837 if (numErrors != 0 ) { 838 JmriJOptionPane.showMessageDialog(_frame, 839 Bundle.getMessage("NdEvVarWriteError"), Bundle.getMessage("WarningTitle"), 840 JmriJOptionPane.ERROR_MESSAGE); 841 } 842 _frame = null; 843 _toNode = null; 844 } 845 846 /** 847 * Get the System Connection Node Model 848 * @return System Connection Node Model 849 */ 850 @Nonnull 851 protected CbusNodeTableDataModel getNodeModel(){ 852 if ( memo == null ) { 853 throw new IllegalStateException("No System Connection Set, call initComponents(memo)"); 854 } 855 return memo.get(CbusConfigurationManager.class) 856 .provide(CbusNodeTableDataModel.class); 857 } 858 859 /** 860 * Nested class to create one of these using old-style defaults. 861 * Used as a startup action 862 */ 863 public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 864 865 public Default() { 866 super(Bundle.getMessage("MenuItemNodeConfig"), 867 new jmri.util.swing.sdi.JmriJFrameInterface(), 868 NodeConfigToolPane.class.getName(), 869 jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class)); 870 } 871 } 872 873 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NodeConfigToolPane.class); 874 875}