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