001package jmri.jmrix.can.cbus.swing.nodeconfig; 002 003import java.awt.event.ActionEvent; 004import java.awt.BorderLayout; 005import java.awt.Dimension; 006import java.awt.event.ActionListener; 007import java.awt.event.WindowEvent; 008import java.beans.PropertyChangeEvent; 009import java.beans.PropertyChangeListener; 010import java.io.File; 011import java.io.IOException; 012 013import javax.annotation.CheckForNull; 014import javax.annotation.Nonnull; 015import javax.swing.*; 016import javax.swing.event.*; 017import javax.xml.parsers.DocumentBuilder; 018import javax.xml.parsers.DocumentBuilderFactory; 019import javax.xml.parsers.ParserConfigurationException; 020 021import jmri.jmrix.can.CanSystemConnectionMemo; 022import jmri.jmrix.can.cbus.CbusEvent; 023import jmri.jmrix.can.cbus.eventtable.CbusEventTableDataModel; 024import jmri.jmrix.can.cbus.node.*; 025import jmri.util.*; 026import jmri.util.swing.JmriJOptionPane; 027 028import org.w3c.dom.Document; 029import org.w3c.dom.DOMException; 030import org.w3c.dom.Element; 031import org.w3c.dom.Node; 032import org.w3c.dom.NodeList; 033 034import org.xml.sax.SAXException; 035 036/** 037 * 038 * @author Steve Young Copyright (C) 2019 039 */ 040public class CbusNodeRestoreFcuFrame extends JmriJFrame { 041 042 private CbusNodeFromFcuTableDataModel cbusNodeFcuDataModel; 043 044 private JTabbedPane tabbedPane; 045 private CbusNodeTableDataModel nodeModel; 046 private CanSystemConnectionMemo _memo; 047 private final NodeConfigToolPane mainpane; 048 private CbusNodeNVEditTablePane nodevarPane; 049 private CbusNodeEventTablePane nodeEventPane; 050 private JSplitPane split; 051 private CbusNodeInfoPane nodeinfoPane; 052 private JTable nodeTable; 053 private JButton openFCUButton; 054 private JButton nodeToBeTaughtButton; 055 private JLabel fileLocationDisplayLabel; 056 private JLabel eventTableRunningLabel; 057 058 private final JList<String> nodeToTeachTolist; 059 060 private JCheckBox teachNvsCheckBox; 061 private JCheckBox teachEventsCheckBox; 062 private JCheckBox resetEventsBeforeTeach; 063 064 private final PropertyChangeListener memoListener = this::updateEventTableActive; 065 private final TableModelListener nodeModelListener = this::updateNodeToTeachList; 066 067 /** 068 * Create a new instance of CbusNodeRestoreFcuFrame. 069 * @param main the main node table pane 070 */ 071 public CbusNodeRestoreFcuFrame( NodeConfigToolPane main ) { 072 super(); 073 mainpane = main; 074 nodeToTeachTolist = new JList<>(); 075 } 076 077 public void initComponents(@Nonnull CanSystemConnectionMemo memo) { 078 _memo = memo; 079 cbusNodeFcuDataModel = new CbusNodeFromFcuTableDataModel(_memo, 2, CbusNodeFromFcuTableDataModel.FCU_MAX_COLUMN); 080 nodeModel = memo.get(CbusNodeTableDataModel.class); 081 initMainPane(); 082 nodeModel.addTableModelListener(nodeModelListener); 083 _memo.addPropertyChangeListener(memoListener); 084 } 085 086 private void initMainPane() { 087 mainpane.setRestoreFcuActive(true); 088 JPanel infoPane = new JPanel(); 089 infoPane.setLayout(new BorderLayout() ); 090 091 JPanel topPanel = new JPanel(); 092 topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS)); 093 JPanel selectFilePanel = new JPanel(); 094 095 openFCUButton = new JButton(Bundle.getMessage("SelectFcuFile")); 096 selectFilePanel.add(openFCUButton ); 097 fileLocationDisplayLabel = new JLabel(); 098 selectFilePanel.add(fileLocationDisplayLabel); 099 topPanel.add(selectFilePanel); 100 101 JPanel eventTableRunningPanel = new JPanel(); 102 eventTableRunningLabel = new JLabel(); 103 updateEventTableActive(null); 104 eventTableRunningPanel.add(eventTableRunningLabel); 105 topPanel.add(eventTableRunningPanel); 106 107 infoPane.add(topPanel, BorderLayout.PAGE_START); 108 infoPane.add(getMiddlePane(), BorderLayout.CENTER); 109 infoPane.add(getNodeToBeTaughtButtonPane(), BorderLayout.PAGE_END); 110 111 this.add(infoPane); 112 113 ThreadingUtil.runOnGUI( this::pack); 114 this.setResizable(true); 115 116 validate(); 117 repaint(); 118 119 setTitle(getTitle()); 120 ThreadingUtil.runOnGUI( () -> setVisible(true)); 121 122 addWindowListener(new java.awt.event.WindowAdapter() { 123 @Override 124 public void windowClosed(WindowEvent e) { 125 mainpane.setRestoreFcuActive(false); 126 } 127 128 @Override 129 public void windowClosing(WindowEvent e) { 130 mainpane.setRestoreFcuActive(false); 131 } 132 }); 133 134 ActionListener save = ae -> { 135 // pre-validation checks, ie same nv's and same ev vars should be by button enabled 136 CbusNode fromNode = nodeFromSelectedRow(); 137 CbusNode toNode = nodeFromSelectedList(); 138 if ( fromNode == null || toNode == null ) { 139 return; 140 } 141 mainpane.showConfirmThenSave(fromNode, toNode, 142 teachNvsCheckBox.isSelected(),resetEventsBeforeTeach.isSelected(), 143 teachEventsCheckBox.isSelected(), this ); 144 }; 145 nodeToBeTaughtButton.addActionListener(save); 146 147 openFCUButton.addActionListener(this::selectInputFile); 148 149 nodeTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { 150 if ( !e.getValueIsAdjusting() ) { 151 updateTabs(); 152 updateRestoreNodeButton(); 153 } 154 }); 155 156 nodeToTeachTolist.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { 157 if ( !e.getValueIsAdjusting() ) { 158 updateRestoreNodeButton(); 159 } 160 }); 161 updateRestoreNodeButton(); 162 } 163 164 private JSplitPane getMiddlePane(){ 165 166 CbusNodeFcuTablePane fcuTablePane = new CbusNodeFcuTablePane(); 167 fcuTablePane.initComponents(_memo,cbusNodeFcuDataModel); 168 169 nodeTable = fcuTablePane.nodeTable; 170 171 JPanel fcuPane = new JPanel(); 172 fcuPane.setLayout(new BoxLayout(fcuPane, BoxLayout.Y_AXIS)); 173 fcuPane.setPreferredSize(new Dimension(200, 150)); 174 fcuPane.add(fcuTablePane); 175 176 tabbedPane = new JTabbedPane(); 177 178 nodeinfoPane = new CbusNodeInfoPane(null); 179 180 CbusNodeNVTableDataModel nodeNVModel = new CbusNodeNVTableDataModel(_memo, 5, 181 CbusNodeNVTableDataModel.MAX_COLUMN); // controller, row, column 182 nodevarPane = new CbusNodeNVEditTablePane(nodeNVModel); 183 nodevarPane.setNonEditable(); 184 185 CbusNodeEventTableDataModel nodeEvModel = new CbusNodeEventTableDataModel( null, _memo, 10, 186 CbusNodeEventTableDataModel.MAX_COLUMN); 187 nodeEventPane = new CbusNodeEventTablePane(nodeEvModel); 188 189 nodeEventPane.setHideEditButton(); 190 191 nodeEventPane.initComponents(_memo); 192 193 tabbedPane.addTab(Bundle.getMessage("NodeInfo"), nodeinfoPane); 194 tabbedPane.addTab(Bundle.getMessage("NodeVariables"), nodevarPane); 195 tabbedPane.addTab(Bundle.getMessage("NodeEvents"), nodeEventPane); 196 197 tabbedPane.addChangeListener((ChangeEvent e) -> { 198 updateTabs(); 199 }); 200 201 split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, fcuPane, tabbedPane); 202 split.setContinuousLayout(true); 203 return split; 204 } 205 206 private JPanel getNodeToBeTaughtButtonPane() { 207 208 JPanel nodeToBeTaughtButtonPane = new JPanel(); 209 210 nodeToBeTaughtButtonPane.setBorder(BorderFactory.createTitledBorder( 211 BorderFactory.createEtchedBorder(), Bundle.getMessage("ChooseNodeToTeach"))); 212 213 JPanel nodeToBeTaughtPane = new JPanel(); 214 nodeToBeTaughtPane.setLayout(new BoxLayout(nodeToBeTaughtPane, BoxLayout.Y_AXIS)); 215 216 updateNodeToTeachList(null); 217 218 nodeToTeachTolist.setLayoutOrientation(JList.VERTICAL); 219 nodeToTeachTolist.setVisibleRowCount(-1); 220 nodeToTeachTolist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 221 JScrollPane listScroller = new JScrollPane(nodeToTeachTolist); 222 listScroller.setPreferredSize(new Dimension(300, 80)); 223 224 nodeToBeTaughtPane.add(listScroller); 225 nodeToBeTaughtButtonPane.add(nodeToBeTaughtPane); 226 227 JPanel nodeToBeTaughtCheckboxPane = new JPanel(); 228 nodeToBeTaughtCheckboxPane.setLayout(new BoxLayout(nodeToBeTaughtCheckboxPane, BoxLayout.Y_AXIS)); 229 230 nodeToBeTaughtButton = new JButton(Bundle.getMessage("UpdateNodeButton")); 231 232 teachNvsCheckBox = new JCheckBox(Bundle.getMessage("WriteNVs")); 233 teachEventsCheckBox = new JCheckBox(Bundle.getMessage("WriteEvents")); 234 resetEventsBeforeTeach = new JCheckBox(Bundle.getMessage("CBUS_NNCLR")); 235 236 teachNvsCheckBox.setSelected(true); 237 teachEventsCheckBox.setSelected(true); 238 resetEventsBeforeTeach.setSelected(true); 239 240 nodeToBeTaughtCheckboxPane.add(teachNvsCheckBox); 241 nodeToBeTaughtCheckboxPane.add(resetEventsBeforeTeach); 242 nodeToBeTaughtCheckboxPane.add(teachEventsCheckBox); 243 244 nodeToBeTaughtButtonPane.add(nodeToBeTaughtCheckboxPane); 245 nodeToBeTaughtButtonPane.add(nodeToBeTaughtButton); 246 return nodeToBeTaughtButtonPane; 247 } 248 249 private void updateNodeToTeachList(TableModelEvent e) { 250 String before = nodeToTeachTolist.getSelectedValue(); 251 String[] data = nodeModel.getListOfNodeNumberNames().toArray(new String[0]); 252 if ( data.length ==0 ){ 253 data = new String[]{Bundle.getMessage("NodeTableEmpty")}; 254 } 255 nodeToTeachTolist.setListData(data); 256 nodeToTeachTolist.setSelectedValue(before, true); 257 } 258 259 @CheckForNull 260 private CbusNode nodeFromSelectedRow() { 261 int sel = nodeTable.getSelectedRow(); 262 if ( sel > -1 ) { 263 int modelIndex = nodeTable.convertRowIndexToModel(sel); 264 int nodenum = (int) nodeTable.getModel().getValueAt(modelIndex, 265 CbusNodeFromFcuTableDataModel.FCU_NODE_NUMBER_COLUMN); 266 return cbusNodeFcuDataModel.getNodeByNodeNum(nodenum); 267 } else { 268 return null; 269 } 270 } 271 272 /** 273 * Get the selected node to teach to. 274 * @return the node, if one is selected, else null. 275 */ 276 @CheckForNull 277 private CbusNode nodeFromSelectedList() { 278 String obj = nodeToTeachTolist.getSelectedValue(); 279 if ( obj == null ) { 280 return null; 281 } 282 int targetnodenum = StringUtil.getFirstIntFromString(obj); 283 return nodeModel.getNodeByNodeNum(targetnodenum); 284 } 285 286 private void updateTabs() { 287 if ( nodeTable.getSelectedRow() > -1 ) { 288 switch (tabbedPane.getSelectedIndex()) { 289 case 1: // nv pane 290 nodevarPane.setNode( nodeFromSelectedRow() ); 291 break; 292 case 2: // ev pane 293 nodeEventPane.setNode( nodeFromSelectedRow() ); 294 break; 295 default: // info pane 296 nodeinfoPane.setNode( nodeFromSelectedRow() ); 297 break; 298 } 299 } 300 else { 301 nodeinfoPane.setNode( null ); 302 } 303 } 304 305 // only allow nodes with same amount of nv's and ev's 306 private void updateRestoreNodeButton() { 307 308 CbusNode nodeFrom = nodeFromSelectedRow(); 309 if ( nodeFrom == null ) { 310 nodeToBeTaughtButton.setEnabled(false); 311 nodeToBeTaughtButton.setToolTipText("Select a Node from file in top table"); 312 return; 313 } 314 315 CbusNode nodeTo = nodeFromSelectedList(); 316 if ( nodeTo == null ) { 317 nodeToBeTaughtButton.setEnabled(false); 318 nodeToBeTaughtButton.setToolTipText("Select a target Node from list on left"); 319 return; 320 } 321 322 if ( ( nodeFrom.getNodeNvManager().getTotalNVs() == nodeTo.getNodeNvManager().getTotalNVs() ) 323 && ( nodeFrom.getNodeParamManager().getParameter(5) == nodeTo.getNodeParamManager().getParameter(5) ) ) { 324 325 nodeToBeTaughtButton.setEnabled(true); 326 nodeToBeTaughtButton.setToolTipText(null); 327 return; 328 } 329 // default 330 nodeToBeTaughtButton.setEnabled(false); 331 nodeToBeTaughtButton.setToolTipText("Both nodes must have same amount of NV's"); 332 } 333 334 private static JFileChooser chooser; 335 336 private static void initChooser(){ 337 if (chooser == null) { 338 chooser = jmri.jmrit.XmlFile.userFileChooser("XML Files", "xml", "XML"); 339 } 340 } 341 342 private void selectInputFile(ActionEvent e){ 343 344 initChooser(); 345 chooser.rescanCurrentDirectory(); 346 int retVal = chooser.showOpenDialog(this); 347 if (retVal != JFileChooser.APPROVE_OPTION) { 348 return; // give up if no file selected 349 } 350 351 File testForXml = chooser.getSelectedFile(); 352 353 if (!testForXml.getPath().toUpperCase().endsWith("XML")) { 354 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ImportNotXml"), 355 Bundle.getMessage("WarningTitle"), JmriJOptionPane.ERROR_MESSAGE); 356 return; 357 } 358 359 // success, open the file 360 addFile(testForXml); 361 362 } 363 364 protected void addFile(File inputFile) { 365 366 fileLocationDisplayLabel.setText( inputFile.toString() ); 367 368 try { 369 cbusNodeFcuDataModel.resetData(); 370 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); 371 // disable DOCTYPE declaration & setXIncludeAware to reduce Sonar security warnings 372 dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 373 dbFactory.setXIncludeAware(false); 374 DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); 375 Document doc = dBuilder.parse(inputFile); 376 doc.getDocumentElement().normalize(); 377 378 setNodesAndNVs(doc); 379 setEventstoNodes(doc); 380 381 } 382 catch (NumberFormatException | DOMException | IOException | ParserConfigurationException | SAXException e) { 383 log.warn("Error importing xml file. Valid xml?", e); 384 JmriJOptionPane.showMessageDialog(this, (Bundle.getMessage("ImportError") + " Valid XML?"), 385 Bundle.getMessage("WarningTitle"), JmriJOptionPane.ERROR_MESSAGE); 386 } 387 } 388 389 private void setNodesAndNVs(@Nonnull Document doc) { 390 NodeList nodeList = doc.getElementsByTagName("userNodes"); 391 for ( int temp = 0; temp < nodeList.getLength(); temp++) { 392 393 Node nNode = nodeList.item(temp); 394 Element eElement = (Element) nNode; 395 String nodeNum = eElement.getElementsByTagName("nodeNum").item(0).getTextContent(); 396 String nodeName = eElement.getElementsByTagName("nodeName").item(0).getTextContent(); 397 String moduleIdNum = eElement.getElementsByTagName("moduleId").item(0).getTextContent(); 398 String moduleNvString = eElement.getElementsByTagName("NodeVars").item(0).getTextContent(); 399 String nodeVersion = eElement.getElementsByTagName("Version").item(0).getTextContent(); 400 401 int nodenum = Integer.parseInt(nodeNum); 402 int nodetype = Integer.parseInt(moduleIdNum); 403 if ( nodenum>0 ) { 404 CbusNodeFromBackup actualnode = cbusNodeFcuDataModel.provideNodeByNodeNum( nodenum ); 405 actualnode.setNameIfNoName( nodeName ); 406 actualnode.getNodeEventManager().resetNodeEvents(); 407 408 log.debug("node version {}",nodeVersion); 409 410 int[] nvArray = StringUtil.intBytesWithTotalFromNonSpacedHexString(moduleNvString,true); 411 412 // 1st value, ie 7 is total params 413 int [] myarray = new int[] {7,165,-1,nodetype,-1,-1,nvArray[0],-1}; 414 actualnode.getNodeParamManager().setParameters(myarray); 415 if (nvArray.length>1) { 416 // log.info("node {} has {} nvs",actualnode,numNvs); 417 actualnode.getNodeNvManager().setNVs( nvArray ); 418 } 419 } 420 } 421 } 422 423 // loop through the events and add them to their nodes 424 private void setEventstoNodes(@Nonnull Document doc) { 425 426 NodeList eventList = doc.getElementsByTagName("userEvents"); // NOI18N 427 CbusEventTableDataModel eventModel = _memo.get(CbusEventTableDataModel.class); 428 if ( eventModel == null ) { 429 log.info("CBUS Event Table not running, no Event Names imported."); 430 } 431 for ( int temp = 0; temp < eventList.getLength(); temp++) { 432 433 Node nNode = eventList.item(temp); 434 Element eElement = (Element) nNode; 435 436 String hostNodeNumString = eElement.getElementsByTagName("ownerNode").item(0).getTextContent(); 437 String event = eElement.getElementsByTagName("eventValue").item(0).getTextContent(); 438 String eventNode = eElement.getElementsByTagName("eventNode").item(0).getTextContent(); 439 String eventName = eElement.getElementsByTagName("eventName").item(0).getTextContent(); 440 String eventVars = eElement.getElementsByTagName("Values").item(0).getTextContent(); 441 442 int hostNodeNum = Integer.parseInt(hostNodeNumString); 443 int eventNum = Integer.parseInt(event); 444 int eventNodeNum = Integer.parseInt(eventNode); 445 log.debug("event host {} event {} event node {} vars {}",hostNodeNum,eventNum,eventNodeNum,eventVars); 446 447 if ( ( hostNodeNum > 0 ) ) { 448 CbusNodeFromBackup hostNode = cbusNodeFcuDataModel.provideNodeByNodeNum( hostNodeNum ); 449 int[] evVarArray = StringUtil.intBytesWithTotalFromNonSpacedHexString(eventVars,false); 450 451 if ( !eventVars.isEmpty() && hostNode.getNodeParamManager().getParameter(5) < 0 ) { 452 hostNode.getNodeParamManager().setParameter(5,evVarArray.length); 453 } 454 455 CbusNodeEvent ev = new CbusNodeEvent(_memo,eventNodeNum,eventNum,hostNodeNum,-1,hostNode.getNodeParamManager().getParameter(5)); 456 ev.setEvArr(evVarArray); 457 ev.setName(eventName); 458 ev.setTempFcuNodeName(cbusNodeFcuDataModel.getNodeName( eventNodeNum ) ); 459 hostNode.getNodeEventManager().addNewEvent(ev); 460 } 461 462 if ( eventModel != null ) { 463 CbusEvent ev = eventModel.provideEvent(eventNodeNum,eventNum); 464 ev.setNameIfNoName(eventName); 465 } 466 } 467 } 468 469 private void updateEventTableActive(PropertyChangeEvent evt) { 470 CbusEventTableDataModel eventModel = _memo.get(CbusEventTableDataModel.class); 471 eventTableRunningLabel.setText(Bundle.getMessage( eventModel==null ? 472 "EventTableNotRunning" : "EventsImportToTable")); 473 } 474 475 @Override 476 public String getTitle() { 477 return Bundle.getMessage("FcuImportTitle"); 478 } 479 480 @Override 481 public void dispose() { 482 if ( _memo != null) { 483 _memo.removePropertyChangeListener(memoListener); 484 } 485 if ( nodeModel !=null ) { 486 nodeModel.removeTableModelListener(nodeModelListener); 487 } 488 super.dispose(); 489 } 490 491 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeRestoreFcuFrame.class); 492 493}