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 // May need to take a node out of learn mode so signal that we are closing 568 // Currently only applies to servo modules and the NV edit gui pane 569 getTabs().forEach((pn) -> { 570 if (pn instanceof CbusNodeEditNVarPane) { 571 pn.dispose(); 572 } 573 }); 574 575 super.dispose(); 576 } 577 578 /** 579 * Handles drag actions containing CBUS events to edit / teach to a node 580 */ 581 private class TransferHandler extends javax.swing.TransferHandler { 582 /** 583 * {@inheritDoc} 584 */ 585 @Override 586 public boolean canImport(JComponent c, DataFlavor[] transferFlavors) { 587 588 // prevent draggable on startup when a node has not yet been selected 589 // the draggable must still be able to select a table row 590 if ( (c instanceof JTabbedPane ) && ( _selectedNode < 1 )){ 591 return false; 592 } 593 594 for (DataFlavor flavor : transferFlavors) { 595 if (DataFlavor.stringFlavor.equals(flavor)) { 596 return true; 597 } 598 } 599 return false; 600 } 601 602 /** 603 * {@inheritDoc} 604 */ 605 @Override 606 public boolean importData(JComponent c, Transferable t) { 607 if (canImport(c, t.getTransferDataFlavors())) { 608 609 String eventInJmriFormat; 610 try { 611 eventInJmriFormat = (String) t.getTransferData(DataFlavor.stringFlavor); 612 } catch (UnsupportedFlavorException | IOException e) { 613 log.error("unable to get dragged address", e); 614 return false; 615 } 616 return openNewOrEditEventFrame(eventInJmriFormat); 617 } 618 return false; 619 } 620 } 621 622 /** 623 * Opens a new or edit event frame depending on if existing 624 * @param eventInJmriFormat standard CBUS Sensor / Turnout phrase, eg "+44", "-N123E456", "X0A0B" 625 * @return false if issue opening or editing 626 */ 627 private boolean openNewOrEditEventFrame( String eventInJmriFormat ){ 628 629 // do some validation on the input string 630 // processed in the same way as a sensor, turnout or light so less chance of breaking in future 631 // and can also accept the Hex "X1234;X654876" format 632 String validatedAddr; 633 try { 634 validatedAddr = CbusAddress.validateSysName( eventInJmriFormat ); 635 } catch (IllegalArgumentException e) { 636 return false; 637 } 638 639 CbusNode _node = getNodeModel().getNodeByNodeNum( _selectedNode ); 640 if (_node==null){ 641 log.warn("No Node"); 642 return false; 643 } 644 645 CanMessage m = ( new CbusAddress(validatedAddr) ).makeMessage(0x12); 646 647 CbusNodeEvent newev = new CbusNodeEvent( memo, 648 CbusMessage.getNodeNumber(m), 649 CbusMessage.getEvent(m), 650 _selectedNode, 651 -1, 652 _node.getNodeParamManager().getParameter(5) 653 ); 654 java.util.Arrays.fill(newev.getEvVarArray(),0); 655 656 log.debug("dragged nodeevent {} ",newev); 657 ThreadingUtil.runOnGUI( () -> { 658 getEditEvFrame().initComponents(memo,newev); 659 }); 660 return true; 661 } 662 663 /** 664 * Get the edit event frame 665 * this could be requested from 666 * CbusNodeEventDataModel button click to edit event, 667 * this class when it receives an event via drag n drop, 668 * creating new event from CbusNodeEventVarPane 669 * @return the Frame 670 */ 671 public CbusNodeEditEventFrame getEditEvFrame(){ 672 if (_editEventFrame == null ){ 673 _editEventFrame = new CbusNodeEditEventFrame(this); 674 } 675 return _editEventFrame; 676 } 677 678 /** 679 * Receive notification from the frame that it has disposed 680 */ 681 protected void clearEditEventFrame() { 682 _editEventFrame = null; 683 } 684 685 private boolean _clearEvents; 686 private boolean _teachEvents; 687 private CbusNode _fromNode; 688 private CbusNode _toNode; 689 private JFrame _frame; 690 691 /** 692 * Show a Confirm before Save Dialogue Box then start teach process for Node 693 * <p> 694 * Used in Node Backup restore, Restore from FCU, edit NV's 695 * Edit Event variables currently use a custom dialogue, not this 696 * @param fromNode Node to get data from 697 * @param toNode Node to send changes to 698 * @param teachNVs true to Teach NV's 699 * @param clearEvents true to clear events before teaching new ones 700 * @param teachEvents true to teach events 701 * @param frame the frame to which dialogue boxes can be attached to 702 */ 703 protected void showConfirmThenSave( @Nonnull CbusNode fromNode, @Nonnull CbusNode toNode, 704 boolean teachNVs, boolean clearEvents, boolean teachEvents, @CheckForNull JFrame frame){ 705 706 _clearEvents = clearEvents; 707 _teachEvents = teachEvents; 708 _fromNode = fromNode; 709 _toNode = toNode; 710 711 if ( frame == null ){ 712 frame = topFrame; 713 } 714 _frame = frame; 715 716 StringBuilder buf = new StringBuilder(); 717 buf.append("<html> ") 718 .append( ("Please Confirm Write ") ) 719 .append( ("to <br>") ) 720 .append ( _toNode.toString() ) 721 .append("<hr>"); 722 723 if ( teachNVs ){ 724 725 // Bundle.getMessage("NVConfirmWrite",nodeName) 726 buf.append("Teaching ") 727 .append(_toNode.getNodeNvManager().getNvDifference(_fromNode)) 728 .append(" of ").append(_fromNode.getNodeNvManager().getTotalNVs()).append(" NV's<br>"); 729 } 730 if ( _clearEvents ){ 731 buf.append("Clearing ").append(Math.max( 0,_toNode.getNodeEventManager().getTotalNodeEvents() )).append(" Events<br>"); 732 } 733 if ( _teachEvents ){ 734 buf.append("Teaching ").append(Math.max( 0,_fromNode.getNodeEventManager().getTotalNodeEvents() )).append(" Events<br>"); 735 } 736 buf.append("</html>"); 737 738 int response = JmriJOptionPane.showConfirmDialog(frame, 739 ( buf.toString() ), 740 ( ("Please Confirm Write to Node")), 741 JmriJOptionPane.OK_CANCEL_OPTION, 742 JmriJOptionPane.QUESTION_MESSAGE); 743 if ( response == JmriJOptionPane.OK_OPTION ) { 744 _toNode.addPropertyChangeListener(this); 745 busy_dialog = new jmri.util.swing.BusyDialog(frame, "Write NVs "+_fromNode.toString(), false); 746 busy_dialog.start(); 747 // update main node name from fcu name 748 _toNode.setNameIfNoName( _fromNode.getUserName() ); 749 // request the local nv model pass the nv update request to the CbusNode 750 if ( teachNVs ){ 751 _toNode.getNodeNvManager().sendNvsToNode( _fromNode.getNodeNvManager().getNvArray()); 752 } 753 else { 754 nVTeachComplete(0); 755 } 756 } 757 } 758 759 /** {@inheritDoc} */ 760 @Override 761 public void propertyChange(PropertyChangeEvent ev){ 762 if (ev.getPropertyName().equals("TEACHNVCOMPLETE")) { 763 jmri.util.ThreadingUtil.runOnGUIEventually( ()->{ 764 nVTeachComplete((Integer) ev.getNewValue()); 765 }); 766 } 767 else if (ev.getPropertyName().equals("ADDALLEVCOMPLETE")) { 768 jmri.util.ThreadingUtil.runOnGUIEventually( ()->{ 769 teachEventsComplete((Integer) ev.getNewValue()); 770 }); 771 } 772 } 773 774 /** 775 * Notification from CbusNode NV Teach is complete 776 * Starts check to see if clear events 777 * @param numErrors number of errors writing NVs 778 */ 779 private void nVTeachComplete(int numErrors){ 780 if ( numErrors > 0 ) { 781 JmriJOptionPane.showMessageDialog(_frame, 782 Bundle.getMessage("NVSetFailTitle",numErrors), Bundle.getMessage("WarningTitle"), 783 JmriJOptionPane.ERROR_MESSAGE); 784 } 785 786 if ( _clearEvents ){ 787 788 busy_dialog.setTitle("Clear Events"); 789 790 // node enter learn mode 791 _toNode.send.nodeEnterLearnEvMode( _toNode.getNodeNumber() ); 792 // no response expected but we add a mini delay for other traffic 793 794 ThreadingUtil.runOnLayoutDelayed( () -> { 795 _toNode.send.nNCLR(_toNode.getNodeNumber());// no response expected 796 }, 150 ); 797 ThreadingUtil.runOnLayoutDelayed( () -> { 798 // node exit learn mode 799 _toNode.send.nodeExitLearnEvMode( _toNode.getNodeNumber() ); // no response expected 800 }, jmri.jmrix.can.cbus.node.CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME ); 801 ThreadingUtil.runOnGUIDelayed( () -> { 802 803 clearEventsComplete(); 804 805 }, ( jmri.jmrix.can.cbus.node.CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME + 150 ) ); 806 } 807 else { 808 clearEventsComplete(); 809 } 810 } 811 812 /** 813 * When clear Events completed ( in nvTeachComplete ) 814 * starts process for teaching events to Node 815 */ 816 private void clearEventsComplete() { 817 ArrayList<CbusNodeEvent> arL = _fromNode.getNodeEventManager().getEventArray(); 818 if ( _teachEvents){ 819 if (arL==null){ 820 log.error("No Event Array on Node {}",_fromNode); 821 teachEventsComplete(1); 822 return; 823 } 824 busy_dialog.setTitle("Teach Events"); 825 _toNode.getNodeEventManager().sendNewEvSToNode( arL ); 826 } 827 else { 828 teachEventsComplete(0); 829 } 830 } 831 832 /** 833 * Notification from CbusNode Event Teach is complete 834 * @param numErrors number of errors writing events 835 */ 836 private void teachEventsComplete( int numErrors ) { 837 _toNode.removePropertyChangeListener(this); 838 busy_dialog.finish(); 839 busy_dialog = null; 840 if (numErrors != 0 ) { 841 JmriJOptionPane.showMessageDialog(_frame, 842 Bundle.getMessage("NdEvVarWriteError"), Bundle.getMessage("WarningTitle"), 843 JmriJOptionPane.ERROR_MESSAGE); 844 } 845 _frame = null; 846 _toNode = null; 847 } 848 849 /** 850 * Get the System Connection Node Model 851 * @return System Connection Node Model 852 */ 853 @Nonnull 854 protected CbusNodeTableDataModel getNodeModel(){ 855 if ( memo == null ) { 856 throw new IllegalStateException("No System Connection Set, call initComponents(memo)"); 857 } 858 return memo.get(CbusConfigurationManager.class) 859 .provide(CbusNodeTableDataModel.class); 860 } 861 862 /** 863 * Nested class to create one of these using old-style defaults. 864 * Used as a startup action 865 */ 866 static public class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 867 868 public Default() { 869 super(Bundle.getMessage("MenuItemNodeConfig"), 870 new jmri.util.swing.sdi.JmriJFrameInterface(), 871 NodeConfigToolPane.class.getName(), 872 jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class)); 873 } 874 } 875 876 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NodeConfigToolPane.class); 877 878}