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