001package jmri.jmrix.openlcb.swing.stleditor; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.io.*; 006import java.util.*; 007import java.util.List; 008import java.util.concurrent.atomic.AtomicInteger; 009import java.util.regex.Pattern; 010import java.nio.file.*; 011 012import java.beans.PropertyChangeEvent; 013import java.beans.PropertyChangeListener; 014 015import javax.swing.*; 016import javax.swing.event.ChangeEvent; 017import javax.swing.event.ListSelectionEvent; 018import javax.swing.filechooser.FileNameExtensionFilter; 019import javax.swing.table.AbstractTableModel; 020 021import jmri.InstanceManager; 022import jmri.UserPreferencesManager; 023import jmri.jmrix.can.CanSystemConnectionMemo; 024import jmri.util.FileUtil; 025import jmri.util.swing.JComboBoxUtil; 026import jmri.util.swing.JmriJFileChooser; 027import jmri.util.swing.JmriJOptionPane; 028import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 029 030import static org.openlcb.MimicNodeStore.NodeMemo.UPDATE_PROP_SIMPLE_NODE_IDENT; 031 032import org.apache.commons.csv.CSVFormat; 033import org.apache.commons.csv.CSVParser; 034import org.apache.commons.csv.CSVPrinter; 035import org.apache.commons.csv.CSVRecord; 036 037import org.openlcb.*; 038import org.openlcb.cdi.cmd.*; 039import org.openlcb.cdi.impl.ConfigRepresentation; 040 041 042/** 043 * Panel for editing STL logic. 044 * 045 * The primary mode is a connection to a Tower LCC+Q. When a node is selected, the data 046 * is transferred to Java lists and displayed using Java tables. If changes are to be retained, 047 * the Store process is invoked which updates the Tower LCC+Q CDI. 048 * 049 * An alternate mode uses CSV files to import and export the data. This enables offline development. 050 * Since the CDI is loaded automatically when the node is selected, to transfer offline development 051 * is a three step process: Load the CDI, replace the content with the CSV content and then store 052 * to the CDI. 053 * 054 * A third mode is to load a CDI backup file. This can then be used with the CSV process for offline work. 055 * 056 * The reboot process has several steps. 057 * <ul> 058 * <li>The Yes option is selected in the compile needed dialog. This sends the reboot command.</li> 059 * <li>The RebootListener detects that the reboot is done and does getCompileMessage.</li> 060 * <li>getCompileMessage does a reload for the first syntax message.</li> 061 * <li>EntryListener gets the reload done event and calls displayCompileMessage.</li> 062 * </ul> 063 * 064 * @author Dave Sand Copyright (C) 2024 065 * @since 5.7.5 066 */ 067public class StlEditorPane extends jmri.util.swing.JmriPanel 068 implements jmri.jmrix.can.swing.CanPanelInterface { 069 070 /** 071 * The STL Editor is dependent on the Tower LCC+Q software version 072 */ 073 private static int TOWER_LCC_Q_NODE_VERSION = 109; 074 private static String TOWER_LCC_Q_NODE_VERSION_STRING = "v1.09"; 075 076 private CanSystemConnectionMemo _canMemo; 077 private OlcbInterface _iface; 078 private ConfigRepresentation _cdi; 079 private MimicNodeStore _store; 080 081 /* Preferences setup */ 082 final String _storeModeCheck = this.getClass().getName() + ".StoreMode"; 083 private final UserPreferencesManager _pm; 084 private JCheckBox _compactOption = new JCheckBox(Bundle.getMessage("StoreMode")); 085 086 private boolean _dirty = false; 087 private int _logicRow = -1; // The last selected row, -1 for none 088 private int _groupRow = 0; 089 private List<String> _csvMessages = new ArrayList<>(); 090 private AtomicInteger _storeQueueLength = new AtomicInteger(0); 091 private boolean _compileNeeded = false; 092 private boolean _compileInProgress = false; 093 PropertyChangeListener _entryListener = new EntryListener(); 094 private List<String> _messages = new ArrayList<>(); 095 096 private String _csvDirectoryPath = ""; 097 098 private DefaultComboBoxModel<NodeEntry> _nodeModel = new DefaultComboBoxModel<NodeEntry>(); 099 private JComboBox<NodeEntry> _nodeBox; 100 101 private JComboBox<Operator> _operators = new JComboBox<>(Operator.values()); 102 103 private TreeMap<Integer, Token> _tokenMap; 104 105 private List<GroupRow> _groupList = new ArrayList<>(); 106 private List<InputRow> _inputList = new ArrayList<>(); 107 private List<OutputRow> _outputList = new ArrayList<>(); 108 private List<ReceiverRow> _receiverList = new ArrayList<>(); 109 private List<TransmitterRow> _transmitterList = new ArrayList<>(); 110 111 private JTable _groupTable; 112 private JTable _logicTable; 113 private JTable _inputTable; 114 private JTable _outputTable; 115 private JTable _receiverTable; 116 private JTable _transmitterTable; 117 118 private JTabbedPane _detailTabs; 119 120 private JPanel _editButtons; 121 private JButton _addButton; 122 private JButton _insertButton; 123 private JButton _moveUpButton; 124 private JButton _moveDownButton; 125 private JButton _deleteButton; 126 private JButton _percentButton; 127 private JButton _refreshButton; 128 private JButton _storeButton; 129 private JButton _exportButton; 130 private JButton _importButton; 131 132 private JMenuItem _refreshItem; 133 private JMenuItem _storeItem; 134 private JMenuItem _exportItem; 135 private JMenuItem _importItem; 136 private JMenuItem _loadItem; 137 138 // CDI Names 139 private static String INPUT_NAME = "Logic Inputs.Group I%s(%s).Input Description"; 140 private static String INPUT_TRUE = "Logic Inputs.Group I%s(%s).True"; 141 private static String INPUT_FALSE = "Logic Inputs.Group I%s(%s).False"; 142 private static String OUTPUT_NAME = "Logic Outputs.Group Q%s(%s).Output Description"; 143 private static String OUTPUT_TRUE = "Logic Outputs.Group Q%s(%s).True"; 144 private static String OUTPUT_FALSE = "Logic Outputs.Group Q%s(%s).False"; 145 private static String RECEIVER_NAME = "Track Receivers.Rx Circuit(%s).Remote Mast Description"; 146 private static String RECEIVER_EVENT = "Track Receivers.Rx Circuit(%s).Link Address"; 147 private static String TRANSMITTER_NAME = "Track Transmitters.Tx Circuit(%s).Track Circuit Description"; 148 private static String TRANSMITTER_EVENT = "Track Transmitters.Tx Circuit(%s).Link Address"; 149 private static String GROUP_NAME = "Conditionals.Logic(%s).Group Description"; 150 private static String GROUP_MULTI_LINE = "Conditionals.Logic(%s).MultiLine"; 151 private static String SYNTAX_MESSAGE = "Syntax Messages.Syntax Messages.Message 1"; 152 153 // Regex Patterns 154 private static Pattern PARSE_VARIABLE = Pattern.compile("[IQYZM](\\d+)\\.(\\d+)", Pattern.CASE_INSENSITIVE); 155 private static Pattern PARSE_NOVAROPER = Pattern.compile("(A\\(|AN\\(|O\\(|ON\\(|X\\(|XN\\(|\\)|NOT|SET|CLR|SAVE)", Pattern.CASE_INSENSITIVE); 156 private static Pattern PARSE_LABEL = Pattern.compile("([a-zA-Z]\\w{0,3}:)"); 157 private static Pattern PARSE_JUMP = Pattern.compile("(JNBI|JCN|JCB|JNB|JBI|JU|JC)", Pattern.CASE_INSENSITIVE); 158 private static Pattern PARSE_DEST = Pattern.compile("(\\w{1,4})"); 159 private static Pattern PARSE_TIMERWORD = Pattern.compile("([W]#[0123]#\\d{1,3})", Pattern.CASE_INSENSITIVE); 160 private static Pattern PARSE_TIMERVAR = Pattern.compile("([T]\\d{1,2})", Pattern.CASE_INSENSITIVE); 161 private static Pattern PARSE_COMMENT1 = Pattern.compile("//(.*)\\n"); 162 private static Pattern PARSE_COMMENT2 = Pattern.compile("/\\*(.*?)\\*/"); 163 private static Pattern PARSE_HEXPAIR = Pattern.compile("^[0-9a-fA-F]{2}$"); 164 private static Pattern PARSE_VERSION = Pattern.compile("^.*(\\d+)\\.(\\d+)$"); 165 166 167 public StlEditorPane() { 168 _pm = InstanceManager.getDefault(UserPreferencesManager.class); 169 } 170 171 @Override 172 public void initComponents(CanSystemConnectionMemo memo) { 173 _canMemo = memo; 174 _iface = memo.get(OlcbInterface.class); 175 _store = memo.get(MimicNodeStore.class); 176 177 // Add to GUI here 178 setLayout(new BorderLayout()); 179 180 var footer = new JPanel(); 181 footer.setLayout(new BorderLayout()); 182 183 _addButton = new JButton(Bundle.getMessage("ButtonAdd")); 184 _insertButton = new JButton(Bundle.getMessage("ButtonInsert")); 185 _moveUpButton = new JButton(Bundle.getMessage("ButtonMoveUp")); 186 _moveDownButton = new JButton(Bundle.getMessage("ButtonMoveDown")); 187 _deleteButton = new JButton(Bundle.getMessage("ButtonDelete")); 188 _percentButton = new JButton("0%"); 189 _refreshButton = new JButton(Bundle.getMessage("ButtonRefresh")); 190 _storeButton = new JButton(Bundle.getMessage("ButtonStore")); 191 _exportButton = new JButton(Bundle.getMessage("ButtonExport")); 192 _importButton = new JButton(Bundle.getMessage("ButtonImport")); 193 194 _refreshButton.setEnabled(false); 195 _storeButton.setEnabled(false); 196 197 _addButton.addActionListener(this::pushedAddButton); 198 _insertButton.addActionListener(this::pushedInsertButton); 199 _moveUpButton.addActionListener(this::pushedMoveUpButton); 200 _moveDownButton.addActionListener(this::pushedMoveDownButton); 201 _deleteButton.addActionListener(this::pushedDeleteButton); 202 _percentButton.addActionListener(this::pushedPercentButton); 203 _refreshButton.addActionListener(this::pushedRefreshButton); 204 _storeButton.addActionListener(this::pushedStoreButton); 205 _exportButton.addActionListener(this::pushedExportButton); 206 _importButton.addActionListener(this::pushedImportButton); 207 208 _editButtons = new JPanel(); 209 _editButtons.add(_addButton); 210 _editButtons.add(_insertButton); 211 _editButtons.add(_moveUpButton); 212 _editButtons.add(_moveDownButton); 213 _editButtons.add(_deleteButton); 214 _editButtons.add(_percentButton); 215 footer.add(_editButtons, BorderLayout.WEST); 216 217 var dataButtons = new JPanel(); 218 dataButtons.add(_importButton); 219 dataButtons.add(_exportButton); 220 dataButtons.add(new JLabel(" | ")); 221 dataButtons.add(_refreshButton); 222 dataButtons.add(_storeButton); 223 footer.add(dataButtons, BorderLayout.EAST); 224 add(footer, BorderLayout.SOUTH); 225 226 // Define the node selector which goes in the header 227 var nodeSelector = new JPanel(); 228 nodeSelector.setLayout(new FlowLayout()); 229 230 _nodeBox = new JComboBox<NodeEntry>(_nodeModel); 231 232 // Load node selector combo box 233 for (MimicNodeStore.NodeMemo nodeMemo : _store.getNodeMemos() ) { 234 newNodeInList(nodeMemo); 235 } 236 237 _nodeBox.addActionListener(this::nodeSelected); 238 JComboBoxUtil.setupComboBoxMaxRows(_nodeBox); 239 240 // Force combo box width 241 var dim = _nodeBox.getPreferredSize(); 242 var newDim = new Dimension(400, (int)dim.getHeight()); 243 _nodeBox.setPreferredSize(newDim); 244 245 nodeSelector.add(_nodeBox); 246 247 //Setup up store mode checkbox 248 var storeMode = new JPanel(); 249 _compactOption.setToolTipText(Bundle.getMessage("StoreModeTip")); 250 _compactOption.setSelected(_pm.getSimplePreferenceState(_storeModeCheck)); 251 storeMode.add(_compactOption); 252 253 var header = new JPanel(); 254 header.setLayout(new BorderLayout()); 255 header.add(storeMode, BorderLayout.EAST); 256 header.add(nodeSelector, BorderLayout.CENTER); 257 258 add(header, BorderLayout.NORTH); 259 260 // Define the center section of the window which consists of 5 tabs 261 _detailTabs = new JTabbedPane(); 262 263 _detailTabs.add(Bundle.getMessage("ButtonG"), buildLogicPanel()); // NOI18N 264 _detailTabs.add(Bundle.getMessage("ButtonI"), buildInputPanel()); // NOI18N 265 _detailTabs.add(Bundle.getMessage("ButtonQ"), buildOutputPanel()); // NOI18N 266 _detailTabs.add(Bundle.getMessage("ButtonY"), buildReceiverPanel()); // NOI18N 267 _detailTabs.add(Bundle.getMessage("ButtonZ"), buildTransmitterPanel()); // NOI18N 268 269 _detailTabs.addChangeListener(this::tabSelected); 270 271 _detailTabs.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 272 273 add(_detailTabs, BorderLayout.CENTER); 274 275 initalizeLists(); 276 } 277 278 // -------------- tab configurations --------- 279 280 private JScrollPane buildGroupPanel() { 281 // Create scroll pane 282 var model = new GroupModel(); 283 _groupTable = new JTable(model); 284 var scrollPane = new JScrollPane(_groupTable); 285 286 // resize columns 287 for (int i = 0; i < model.getColumnCount(); i++) { 288 int width = model.getPreferredWidth(i); 289 _groupTable.getColumnModel().getColumn(i).setPreferredWidth(width); 290 } 291 292 _groupTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 293 294 var selectionModel = _groupTable.getSelectionModel(); 295 selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 296 selectionModel.addListSelectionListener(this::handleGroupRowSelection); 297 298 return scrollPane; 299 } 300 301 private JSplitPane buildLogicPanel() { 302 // Create scroll pane 303 var model = new LogicModel(); 304 _logicTable = new JTable(model); 305 var logicScrollPane = new JScrollPane(_logicTable); 306 307 // resize columns 308 for (int i = 0; i < _logicTable.getColumnCount(); i++) { 309 int width = model.getPreferredWidth(i); 310 _logicTable.getColumnModel().getColumn(i).setPreferredWidth(width); 311 } 312 313 _logicTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 314 315 // Use the operators combo box for the operator column 316 var col = _logicTable.getColumnModel().getColumn(1); 317 col.setCellEditor(new DefaultCellEditor(_operators)); 318 JComboBoxUtil.setupComboBoxMaxRows(_operators); 319 320 var selectionModel = _logicTable.getSelectionModel(); 321 selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 322 selectionModel.addListSelectionListener(this::handleLogicRowSelection); 323 324 var logicPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, buildGroupPanel(), logicScrollPane); 325 logicPanel.setDividerSize(10); 326 logicPanel.setResizeWeight(.10); 327 logicPanel.setDividerLocation(150); 328 329 return logicPanel; 330 } 331 332 private JScrollPane buildInputPanel() { 333 // Create scroll pane 334 var model = new InputModel(); 335 _inputTable = new JTable(model); 336 var scrollPane = new JScrollPane(_inputTable); 337 338 // resize columns 339 for (int i = 0; i < model.getColumnCount(); i++) { 340 int width = model.getPreferredWidth(i); 341 _inputTable.getColumnModel().getColumn(i).setPreferredWidth(width); 342 } 343 344 _inputTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 345 346 return scrollPane; 347 } 348 349 private JScrollPane buildOutputPanel() { 350 // Create scroll pane 351 var model = new OutputModel(); 352 _outputTable = new JTable(model); 353 var scrollPane = new JScrollPane(_outputTable); 354 355 // resize columns 356 for (int i = 0; i < model.getColumnCount(); i++) { 357 int width = model.getPreferredWidth(i); 358 _outputTable.getColumnModel().getColumn(i).setPreferredWidth(width); 359 } 360 361 _outputTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 362 363 return scrollPane; 364 } 365 366 private JScrollPane buildReceiverPanel() { 367 // Create scroll pane 368 var model = new ReceiverModel(); 369 _receiverTable = new JTable(model); 370 var scrollPane = new JScrollPane(_receiverTable); 371 372 // resize columns 373 for (int i = 0; i < model.getColumnCount(); i++) { 374 int width = model.getPreferredWidth(i); 375 _receiverTable.getColumnModel().getColumn(i).setPreferredWidth(width); 376 } 377 378 _receiverTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 379 380 return scrollPane; 381 } 382 383 private JScrollPane buildTransmitterPanel() { 384 // Create scroll pane 385 var model = new TransmitterModel(); 386 _transmitterTable = new JTable(model); 387 var scrollPane = new JScrollPane(_transmitterTable); 388 389 // resize columns 390 for (int i = 0; i < model.getColumnCount(); i++) { 391 int width = model.getPreferredWidth(i); 392 _transmitterTable.getColumnModel().getColumn(i).setPreferredWidth(width); 393 } 394 395 _transmitterTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 396 397 return scrollPane; 398 } 399 400 private void tabSelected(ChangeEvent e) { 401 if (_detailTabs.getSelectedIndex() == 0) { 402 _editButtons.setVisible(true); 403 } else { 404 _editButtons.setVisible(false); 405 } 406 } 407 408 // -------------- Initialization --------- 409 410 private void initalizeLists() { 411 // Group List 412 for (int i = 0; i < 16; i++) { 413 _groupList.add(new GroupRow("")); 414 } 415 416 // Input List 417 for (int i = 0; i < 128; i++) { 418 _inputList.add(new InputRow("", "", "")); 419 } 420 421 // Output List 422 for (int i = 0; i < 128; i++) { 423 _outputList.add(new OutputRow("", "", "")); 424 } 425 426 // Receiver List 427 for (int i = 0; i < 16; i++) { 428 _receiverList.add(new ReceiverRow("", "")); 429 } 430 431 // Transmitter List 432 for (int i = 0; i < 16; i++) { 433 _transmitterList.add(new TransmitterRow("", "")); 434 } 435 } 436 437 // -------------- Logic table methods --------- 438 439 private void handleGroupRowSelection(ListSelectionEvent e) { 440 if (!e.getValueIsAdjusting()) { 441 _groupRow = _groupTable.getSelectedRow(); 442 _logicTable.revalidate(); 443 _logicTable.repaint(); 444 pushedPercentButton(null); 445 } 446 } 447 448 private void pushedPercentButton(ActionEvent e) { 449 encode(_groupList.get(_groupRow)); 450 _percentButton.setText(_groupList.get(_groupRow).getSize()); 451 } 452 453 private void handleLogicRowSelection(ListSelectionEvent e) { 454 if (!e.getValueIsAdjusting()) { 455 _logicRow = _logicTable.getSelectedRow(); 456 _moveUpButton.setEnabled(_logicRow > 0); 457 _moveDownButton.setEnabled(_logicRow < _logicTable.getRowCount() - 1); 458 } 459 } 460 461 private void pushedAddButton(ActionEvent e) { 462 var logicList = _groupList.get(_groupRow).getLogicList(); 463 logicList.add(new LogicRow("", null, "", "")); 464 _logicRow = logicList.size() - 1; 465 _logicTable.revalidate(); 466 _logicTable.setRowSelectionInterval(_logicRow, _logicRow); 467 setDirty(true); 468 } 469 470 private void pushedInsertButton(ActionEvent e) { 471 var logicList = _groupList.get(_groupRow).getLogicList(); 472 if (_logicRow >= 0 && _logicRow < logicList.size()) { 473 logicList.add(_logicRow, new LogicRow("", null, "", "")); 474 _logicTable.revalidate(); 475 _logicTable.setRowSelectionInterval(_logicRow, _logicRow); 476 } 477 setDirty(true); 478 } 479 480 private void pushedMoveUpButton(ActionEvent e) { 481 var logicList = _groupList.get(_groupRow).getLogicList(); 482 if (_logicRow >= 0 && _logicRow < logicList.size()) { 483 var logicRow = logicList.remove(_logicRow); 484 logicList.add(_logicRow - 1, logicRow); 485 _logicRow--; 486 _logicTable.revalidate(); 487 _logicTable.setRowSelectionInterval(_logicRow, _logicRow); 488 } 489 setDirty(true); 490 } 491 492 private void pushedMoveDownButton(ActionEvent e) { 493 var logicList = _groupList.get(_groupRow).getLogicList(); 494 if (_logicRow >= 0 && _logicRow < logicList.size()) { 495 var logicRow = logicList.remove(_logicRow); 496 logicList.add(_logicRow + 1, logicRow); 497 _logicRow++; 498 _logicTable.revalidate(); 499 _logicTable.setRowSelectionInterval(_logicRow, _logicRow); 500 } 501 setDirty(true); 502 } 503 504 private void pushedDeleteButton(ActionEvent e) { 505 var logicList = _groupList.get(_groupRow).getLogicList(); 506 if (_logicRow >= 0 && _logicRow < logicList.size()) { 507 logicList.remove(_logicRow); 508 _logicTable.revalidate(); 509 } 510 setDirty(true); 511 } 512 513 // -------------- Encode/Decode methods --------- 514 515 private String nameToVariable(String name) { 516 if (name != null && !name.isEmpty()) { 517 if (!name.contains("~")) { 518 // Search input and output tables 519 for (int i = 0; i < 16; i++) { 520 for (int j = 0; j < 8; j++) { 521 int row = (i * 8) + j; 522 if (_inputList.get(row).getName().equals(name)) { 523 return "I" + i + "." + j; 524 } 525 } 526 } 527 528 for (int i = 0; i < 16; i++) { 529 for (int j = 0; j < 8; j++) { 530 int row = (i * 8) + j; 531 if (_outputList.get(row).getName().equals(name)) { 532 return "Q" + i + "." + j; 533 } 534 } 535 } 536 return name; 537 538 } else { 539 // Search receiver and transmitter tables 540 var splitName = name.split("~"); 541 var baseName = splitName[0]; 542 var aspectName = splitName[1]; 543 var aspectNumber = 0; 544 try { 545 aspectNumber = Integer.parseInt(aspectName); 546 if (aspectNumber < 0 || aspectNumber > 7) { 547 warningDialog(Bundle.getMessage("TitleAspect"), Bundle.getMessage("MessageAspect", aspectNumber)); 548 aspectNumber = 0; 549 } 550 } catch (NumberFormatException e) { 551 warningDialog(Bundle.getMessage("TitleAspect"), Bundle.getMessage("MessageAspect", aspectName)); 552 aspectNumber = 0; 553 } 554 for (int i = 0; i < 16; i++) { 555 if (_receiverList.get(i).getName().equals(baseName)) { 556 return "Y" + i + "." + aspectNumber; 557 } 558 } 559 560 for (int i = 0; i < 16; i++) { 561 if (_transmitterList.get(i).getName().equals(baseName)) { 562 return "Z" + i + "." + aspectNumber; 563 } 564 } 565 return name; 566 } 567 } 568 569 return null; 570 } 571 572 private String variableToName(String variable) { 573 String name = variable; 574 575 if (variable.length() > 1) { 576 var varType = variable.substring(0, 1); 577 var match = PARSE_VARIABLE.matcher(variable); 578 if (match.find() && match.groupCount() == 2) { 579 int first = -1; 580 int second = -1; 581 int row = -1; 582 583 try { 584 first = Integer.parseInt(match.group(1)); 585 second = Integer.parseInt(match.group(2)); 586 } catch (NumberFormatException e) { 587 warningDialog(Bundle.getMessage("TitleVariable"), Bundle.getMessage("MessageVariable", variable)); 588 return name; 589 } 590 591 switch (varType) { 592 case "I": 593 row = (first * 8) + second; 594 name = _inputList.get(row).getName(); 595 if (name.isEmpty()) { 596 name = variable; 597 } 598 break; 599 case "Q": 600 row = (first * 8) + second; 601 name = _outputList.get(row).getName(); 602 if (name.isEmpty()) { 603 name = variable; 604 } 605 break; 606 case "Y": 607 row = first; 608 name = _receiverList.get(row).getName() + "~" + second; 609 break; 610 case "Z": 611 row = first; 612 name = _transmitterList.get(row).getName() + "~" + second; 613 break; 614 default: 615 log.error("Variable '{}' has an invalid first letter (IQYZ)", variable); 616 } 617 } 618 } 619 620 return name; 621 } 622 623 private void encode(GroupRow groupRow) { 624 String longLine = ""; 625 String separator = (_compactOption.isSelected()) ? "" : " "; 626 627 var logicList = groupRow.getLogicList(); 628 for (var row : logicList) { 629 var sb = new StringBuilder(); 630 var jumpLabel = false; 631 632 if (!row.getLabel().isEmpty()) { 633 sb.append(row.getLabel() + " "); 634 } 635 636 if (row.getOper() != null) { 637 var oper = row.getOper(); 638 var operName = oper.name(); 639 640 // Fix special enums 641 if (operName.equals("Cp")) { 642 operName = ")"; 643 } else if (operName.equals("EQ")) { 644 operName = "="; 645 } else if (operName.contains("p")) { 646 operName = operName.replace("p", "("); 647 } 648 649 if (operName.startsWith("J")) { 650 jumpLabel =true; 651 } 652 sb.append(operName); 653 } 654 655 if (!row.getName().isEmpty()) { 656 var name = row.getName().trim(); 657 658 if (jumpLabel) { 659 sb.append(" " + name + " "); 660 jumpLabel = false; 661 } else if (isMemory(name)) { 662 sb.append(separator + name); 663 } else if (isTimerWord(name)) { 664 sb.append(separator + name); 665 } else if (isTimerVar(name)) { 666 sb.append(separator + name); 667 } else { 668 var variable = nameToVariable(name); 669 if (variable == null) { 670 JmriJOptionPane.showMessageDialog(null, 671 Bundle.getMessage("MessageBadName", groupRow.getName(), name), 672 Bundle.getMessage("TitleBadName"), 673 JmriJOptionPane.ERROR_MESSAGE); 674 log.error("bad name: {}", name); 675 } else { 676 sb.append(separator + variable); 677 } 678 } 679 } 680 681 if (!row.getComment().isEmpty()) { 682 var comment = row.getComment().trim(); 683 sb.append(separator + "//" + separator + comment); 684 } 685 686 sb.append("\n"); 687 688 longLine = longLine + sb.toString(); 689 } 690 691 log.debug("MultiLine: {}", longLine); 692 693 if (longLine.length() < 256) { 694 groupRow.setMultiLine(longLine); 695 } else { 696 var overflow = longLine.substring(255); 697 JmriJOptionPane.showMessageDialog(null, 698 Bundle.getMessage("MessageOverflow", groupRow.getName(), overflow), 699 Bundle.getMessage("TitleOverflow"), 700 JmriJOptionPane.ERROR_MESSAGE); 701 log.error("The line overflowed, content truncated: {}", overflow); 702 } 703 } 704 705 private boolean isMemory(String name) { 706 var match = PARSE_VARIABLE.matcher(name); 707 return (match.find() && name.startsWith("M")); 708 } 709 710 private boolean isTimerWord(String name) { 711 var match = PARSE_TIMERWORD.matcher(name); 712 return match.find(); 713 } 714 715 private boolean isTimerVar(String name) { 716 var match = PARSE_TIMERVAR.matcher(name); 717 return match.find(); 718 } 719 720 /** 721 * After the token tree map has been created, build the rows for the STL display. 722 * Each row has an optional label, a required operator, a name as needed and an optional comment. 723 * The operator is always required. The other fields are added as needed. 724 * The label is found by looking at the previous token. 725 * The name is usually the next token. If there is no name, it might be a comment. 726 * @param group The CDI group. 727 */ 728 private void decode(GroupRow group) { 729 createTokenMap(group); 730 731 // Get the operator tokens. They are the anchors for the other values. 732 for (Token token : _tokenMap.values()) { 733 if (token.getType().equals("Oper")) { 734 735 var label = ""; 736 var name = ""; 737 var comment = ""; 738 Operator oper = getEnum(token.getName()); 739 740 // Check for a label 741 var prevKey = _tokenMap.lowerKey(token.getStart()); 742 if (prevKey != null) { 743 var prevToken = _tokenMap.get(prevKey); 744 if (prevToken.getType().equals("Label")) { 745 label = prevToken.getName(); 746 } 747 } 748 749 // Get the name and comment 750 var nextKey = _tokenMap.higherKey(token.getStart()); 751 if (nextKey != null) { 752 var nextToken = _tokenMap.get(nextKey); 753 754 if (nextToken.getType().equals("Comment")) { 755 // There is no name between the operator and the comment 756 comment = variableToName(nextToken.getName()); 757 } else { 758 if (!nextToken.getType().equals("Label") && 759 !nextToken.getType().equals("Oper")) { 760 // Set the name value 761 name = variableToName(nextToken.getName()); 762 763 // Look for comment after the name 764 var comKey = _tokenMap.higherKey(nextKey); 765 if (comKey != null) { 766 var comToken = _tokenMap.get(comKey); 767 if (comToken.getType().equals("Comment")) { 768 comment = comToken.getName(); 769 } 770 } 771 } 772 } 773 } 774 775 var logic = new LogicRow(label, oper, name, comment); 776 group.getLogicList().add(logic); 777 } 778 } 779 780 } 781 782 /** 783 * Create a map of the tokens in the MultiLine string. The map key contains the offset for each 784 * token in the string. The tokens are identified using multiple passes of regex tests. 785 * <ol> 786 * <li>Find the labels which consist of 1 to 4 characters and a colon.</li> 787 * <li>Find the table references. These are the IQYZM tables. The related operators are found by parsing backwards.</li> 788 * <li>Find the operators that do not have operands. Note: This might include SETn. These wil be fixed when the timers are processed</li> 789 * <li>Find the jump operators and the jump destinations.</li> 790 * <li>Find the timer word and load operator.</li> 791 * <li>Find timer variable locations and Sx operators. The SE Tn will update the SET token with the same offset. </li> 792 * <li>Find //...nl comments.</li> 793 * <li>Find /*...*/ comments.</li> 794 * </ol> 795 * An additional check looks for overlaps between jump destinations and labels. This can occur when 796 * a using the compact mode, a jump destination has less the 4 characters, and is immediatly followed by a label. 797 * @param group The CDI group. 798 */ 799 private void createTokenMap(GroupRow group) { 800 _messages.clear(); 801 _tokenMap = new TreeMap<>(); 802 var line = group.getMultiLine(); 803 804 // Find label locations 805 var matchLabel = PARSE_LABEL.matcher(line); 806 while (matchLabel.find()) { 807 var label = line.substring(matchLabel.start(), matchLabel.end()); 808 _tokenMap.put(matchLabel.start(), new Token("Label", label, matchLabel.start(), matchLabel.end())); 809 } 810 811 // Find variable locations and operators 812 var matchVar = PARSE_VARIABLE.matcher(line); 813 while (matchVar.find()) { 814 var variable = line.substring(matchVar.start(), matchVar.end()); 815 _tokenMap.put(matchVar.start(), new Token("Var", variable, matchVar.start(), matchVar.end())); 816 var operToken = findOperator(matchVar.start() - 1, line); 817 if (operToken != null) { 818 _tokenMap.put(operToken.getStart(), operToken); 819 } 820 } 821 822 // Find operators without variables 823 var matchOper = PARSE_NOVAROPER.matcher(line); 824 while (matchOper.find()) { 825 var oper = line.substring(matchOper.start(), matchOper.end()); 826 827 if (isOperInComment(line, matchOper.start())) { 828 continue; 829 } 830 831 if (getEnum(oper) != null) { 832 _tokenMap.put(matchOper.start(), new Token("Oper", oper, matchOper.start(), matchOper.end())); 833 } else { 834 _messages.add(Bundle.getMessage("ErrStandAlone", oper)); 835 } 836 } 837 838 // Find jump operators and destinations 839 var matchJump = PARSE_JUMP.matcher(line); 840 while (matchJump.find()) { 841 var jump = line.substring(matchJump.start(), matchJump.end()); 842 if (getEnum(jump) != null && (jump.startsWith("J") || jump.startsWith("j"))) { 843 _tokenMap.put(matchJump.start(), new Token("Oper", jump, matchJump.start(), matchJump.end())); 844 845 // Get the jump destination 846 var matchDest = PARSE_DEST.matcher(line); 847 if (matchDest.find(matchJump.end())) { 848 var dest = matchDest.group(1); 849 _tokenMap.put(matchDest.start(), new Token("Dest", dest, matchDest.start(), matchDest.end())); 850 } else { 851 _messages.add(Bundle.getMessage("ErrJumpDest", jump)); 852 } 853 } else { 854 _messages.add(Bundle.getMessage("ErrJumpOper", jump)); 855 } 856 } 857 858 // Find timer word locations and load operator 859 var matchTimerWord = PARSE_TIMERWORD.matcher(line); 860 while (matchTimerWord.find()) { 861 var timerWord = matchTimerWord.group(1); 862 _tokenMap.put(matchTimerWord.start(), new Token("TimerWord", timerWord, matchTimerWord.start(), matchTimerWord.end())); 863 var operToken = findOperator(matchTimerWord.start() - 1, line); 864 if (operToken != null) { 865 if (operToken.getName().equals("L") || operToken.getName().equals("l")) { 866 _tokenMap.put(operToken.getStart(), operToken); 867 } else { 868 _messages.add(Bundle.getMessage("ErrTimerLoad", operToken.getName())); 869 } 870 } 871 } 872 873 // Find timer variable locations and S operators 874 var matchTimerVar = PARSE_TIMERVAR.matcher(line); 875 while (matchTimerVar.find()) { 876 var timerVar = matchTimerVar.group(1); 877 _tokenMap.put(matchTimerVar.start(), new Token("TimerVar", timerVar, matchTimerVar.start(), matchTimerVar.end())); 878 var operToken = findOperator(matchTimerVar.start() - 1, line); 879 if (operToken != null) { 880 _tokenMap.put(operToken.getStart(), operToken); 881 } 882 } 883 884 // Find comment locations 885 var matchComment1 = PARSE_COMMENT1.matcher(line); 886 while (matchComment1.find()) { 887 var comment = matchComment1.group(1).trim(); 888 _tokenMap.put(matchComment1.start(), new Token("Comment", comment, matchComment1.start(), matchComment1.end())); 889 } 890 891 var matchComment2 = PARSE_COMMENT2.matcher(line); 892 while (matchComment2.find()) { 893 var comment = matchComment2.group(1).trim(); 894 _tokenMap.put(matchComment2.start(), new Token("Comment", comment, matchComment2.start(), matchComment2.end())); 895 } 896 897 // Check for overlapping jump destinations and following labels 898 for (Token token : _tokenMap.values()) { 899 if (token.getType().equals("Dest")) { 900 var nextKey = _tokenMap.higherKey(token.getStart()); 901 if (nextKey != null) { 902 var nextToken = _tokenMap.get(nextKey); 903 if (nextToken.getType().equals("Label")) { 904 if (token.getEnd() > nextToken.getStart()) { 905 _messages.add(Bundle.getMessage("ErrDestLabel", token.getName(), nextToken.getName())); 906 } 907 } 908 } 909 } 910 } 911 912 if (_messages.size() > 0) { 913 // Display messages 914 String msgs = _messages.stream().collect(java.util.stream.Collectors.joining("\n")); 915 JmriJOptionPane.showMessageDialog(null, 916 Bundle.getMessage("MsgParseErr", group.getName(), msgs), 917 Bundle.getMessage("TitleParseErr"), 918 JmriJOptionPane.ERROR_MESSAGE); 919 _messages.forEach((msg) -> { 920 log.error(msg); 921 }); 922 } 923 924 // Create token debugging output 925 if (log.isDebugEnabled()) { 926 log.info("Line = {}", line); 927 for (Token token : _tokenMap.values()) { 928 log.info("Token = {}", token); 929 } 930 } 931 } 932 933 /** 934 * Starting as the operator location minus one, work backwards to find a valid operator. When 935 * one is found, create and return the token object. 936 * @param index The current location in the line. 937 * @param line The line for the current group. 938 * @return a token or null. 939 */ 940 private Token findOperator(int index, String line) { 941 var sb = new StringBuilder(); 942 int limit = 10; 943 944 while (limit > 0 && index >= 0) { 945 var ch = line.charAt(index); 946 if (ch != ' ') { 947 sb.insert(0, ch); 948 if (getEnum(sb.toString()) != null) { 949 String oper = sb.toString(); 950 return new Token("Oper", oper, index, index + oper.length()); 951 } 952 } 953 limit--; 954 index--; 955 } 956 _messages.add(Bundle.getMessage("ErrNoOper", index, line)); 957 return null; 958 } 959 960 /** 961 * Look backwards in the line for the beginning of a comment. This is not a precise check. 962 * @param line The line that contains the Operator. 963 * @param index The offset of the operator. 964 * @return true if the operator appears to be in a comment. 965 */ 966 private boolean isOperInComment(String line, int index) { 967 int limit = 20; // look back 20 characters 968 char previous = 0; 969 970 while (limit > 0 && index >= 0) { 971 var ch = line.charAt(index); 972 973 if (ch == 10) { 974 // Found the end of a previous statement, new line character. 975 return false; 976 } 977 978 if (ch == '*' && previous == '/') { 979 // Found the end of a previous /*...*/ comment 980 return false; 981 } 982 983 if (ch == '/' && (previous == '/' || previous == '*')) { 984 // Found the start of a comment 985 return true; 986 } 987 988 previous = ch; 989 index--; 990 limit--; 991 } 992 return false; 993 } 994 995 private Operator getEnum(String name) { 996 try { 997 var temp = name.toUpperCase(); 998 if (name.equals("=")) { 999 temp = "EQ"; 1000 } else if (name.equals(")")) { 1001 temp = "Cp"; 1002 } else if (name.endsWith("(")) { 1003 temp = name.toUpperCase().replace("(", "p"); 1004 } 1005 1006 Operator oper = Enum.valueOf(Operator.class, temp); 1007 return oper; 1008 } catch (IllegalArgumentException ex) { 1009 return null; 1010 } 1011 } 1012 1013 // -------------- node methods --------- 1014 1015 private void nodeSelected(ActionEvent e) { 1016 NodeEntry node = (NodeEntry) _nodeBox.getSelectedItem(); 1017 node.getNodeMemo().addPropertyChangeListener(new RebootListener()); 1018 log.debug("nodeSelected: {}", node); 1019 1020 if (isValidNodeVersionNumber(node.getNodeMemo())) { 1021 _cdi = _iface.getConfigForNode(node.getNodeID()); 1022 if (_cdi.getRoot() != null) { 1023 loadCdiData(); 1024 } else { 1025 JmriJOptionPane.showMessageDialogNonModal(this, 1026 Bundle.getMessage("MessageCdiLoad", node), 1027 Bundle.getMessage("TitleCdiLoad"), 1028 JmriJOptionPane.INFORMATION_MESSAGE, 1029 null); 1030 _cdi.addPropertyChangeListener(new CdiListener()); 1031 } 1032 } 1033 } 1034 1035 public class CdiListener implements PropertyChangeListener { 1036 public void propertyChange(PropertyChangeEvent e) { 1037 String propertyName = e.getPropertyName(); 1038 log.debug("CdiListener event = {}", propertyName); 1039 1040 if (propertyName.equals("UPDATE_CACHE_COMPLETE")) { 1041 Window[] windows = Window.getWindows(); 1042 for (Window window : windows) { 1043 if (window instanceof JDialog) { 1044 JDialog dialog = (JDialog) window; 1045 if (Bundle.getMessage("TitleCdiLoad").equals(dialog.getTitle())) { 1046 dialog.dispose(); 1047 } 1048 } 1049 } 1050 loadCdiData(); 1051 } 1052 } 1053 } 1054 1055 /** 1056 * Listens for a property change that implies a node has been rebooted. 1057 * This occurs when the user has selected that the editor should do the reboot to compile the updated logic. 1058 * When the updateSimpleNodeIdent event occurs and the compile is in progress it starts the message display process. 1059 */ 1060 public class RebootListener implements PropertyChangeListener { 1061 public void propertyChange(PropertyChangeEvent e) { 1062 String propertyName = e.getPropertyName(); 1063 if (_compileInProgress && propertyName.equals("updateSimpleNodeIdent")) { 1064 log.debug("The reboot appears to be done"); 1065 getCompileMessage(); 1066 } 1067 } 1068 } 1069 1070 private void newNodeInList(MimicNodeStore.NodeMemo nodeMemo) { 1071 // Filter for Tower LCC+Q 1072 NodeID node = nodeMemo.getNodeID(); 1073 String id = node.toString(); 1074 log.debug("node id: {}", id); 1075 if (!id.startsWith("02.01.57.4")) { 1076 return; 1077 } 1078 1079 int i = 0; 1080 if (_nodeModel.getIndexOf(nodeMemo.getNodeID()) >= 0) { 1081 // already exists. Do nothing. 1082 return; 1083 } 1084 NodeEntry e = new NodeEntry(nodeMemo); 1085 1086 while ((i < _nodeModel.getSize()) && (_nodeModel.getElementAt(i).compareTo(e) < 0)) { 1087 ++i; 1088 } 1089 _nodeModel.insertElementAt(e, i); 1090 } 1091 1092 private boolean isValidNodeVersionNumber(MimicNodeStore.NodeMemo nodeMemo) { 1093 SimpleNodeIdent ident = nodeMemo.getSimpleNodeIdent(); 1094 String versionString = ident.getSoftwareVersion(); 1095 1096 int version = 0; 1097 var match = PARSE_VERSION.matcher(versionString); 1098 if (match.find()) { 1099 var major = match.group(1); 1100 var minor = match.group(2); 1101 version = Integer.parseInt(major + minor); 1102 } 1103 1104 if (version < TOWER_LCC_Q_NODE_VERSION) { 1105 JmriJOptionPane.showMessageDialog(null, 1106 Bundle.getMessage("MessageVersion", 1107 nodeMemo.getNodeID(), 1108 versionString, 1109 TOWER_LCC_Q_NODE_VERSION_STRING), 1110 Bundle.getMessage("TitleVersion"), 1111 JmriJOptionPane.WARNING_MESSAGE); 1112 return false; 1113 } 1114 1115 return true; 1116 } 1117 1118 public class EntryListener implements PropertyChangeListener { 1119 public void propertyChange(PropertyChangeEvent e) { 1120 String propertyName = e.getPropertyName(); 1121 log.debug("EntryListener event = {}", propertyName); 1122 1123 if (propertyName.equals("PENDING_WRITE_COMPLETE")) { 1124 int currentLength = _storeQueueLength.decrementAndGet(); 1125 log.debug("Listener: queue length = {}, source = {}", currentLength, e.getSource()); 1126 1127 var entry = (ConfigRepresentation.CdiEntry) e.getSource(); 1128 entry.removePropertyChangeListener(_entryListener); 1129 1130 if (currentLength < 1) { 1131 log.debug("The queue is back to zero which implies the updates are done"); 1132 displayStoreDone(); 1133 } 1134 } 1135 1136 if (_compileInProgress && propertyName.equals("UPDATE_ENTRY_DATA")) { 1137 // The refresh of the first syntax message has completed. 1138 var entry = (ConfigRepresentation.StringEntry) e.getSource(); 1139 entry.removePropertyChangeListener(_entryListener); 1140 displayCompileMessage(entry.getValue()); 1141 } 1142 } 1143 } 1144 1145 private void displayStoreDone() { 1146 _csvMessages.add(Bundle.getMessage("StoreDone")); 1147 var msgType = JmriJOptionPane.ERROR_MESSAGE; 1148 if (_csvMessages.size() == 1) { 1149 msgType = JmriJOptionPane.INFORMATION_MESSAGE; 1150 } 1151 JmriJOptionPane.showMessageDialog(this, 1152 String.join("\n", _csvMessages), 1153 Bundle.getMessage("TitleCdiStore"), 1154 msgType); 1155 1156 if (_compileNeeded) { 1157 log.debug("Display compile needed message"); 1158 1159 String[] options = {Bundle.getMessage("EditorReboot"), Bundle.getMessage("CdiReboot")}; 1160 int response = JmriJOptionPane.showOptionDialog(this, 1161 Bundle.getMessage("MessageCdiReboot"), 1162 Bundle.getMessage("TitleCdiReboot"), 1163 JmriJOptionPane.YES_NO_OPTION, 1164 JmriJOptionPane.QUESTION_MESSAGE, 1165 null, 1166 options, 1167 options[0]); 1168 1169 if (response == JmriJOptionPane.YES_OPTION) { 1170 // Set the compile in process and request the reboot. The completion will be 1171 // handed by the RebootListener. 1172 _compileInProgress = true; 1173 _cdi.getConnection().getDatagramService(). 1174 sendData(_cdi.getRemoteNodeID(), new int[] {0x20, 0xA9}); 1175 } 1176 } 1177 } 1178 1179 /** 1180 * Get the first syntax message entry, add the entry listener and request a reload (refresh). 1181 * The EntryListener will handle the reload event. 1182 */ 1183 private void getCompileMessage() { 1184 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(SYNTAX_MESSAGE); 1185 entry.addPropertyChangeListener(_entryListener); 1186 entry.reload(); 1187 } 1188 1189 /** 1190 * Turn off the compile in progress and display the syntax message. 1191 * @param message The first syntax message. 1192 */ 1193 private void displayCompileMessage(String message) { 1194 _compileInProgress = false; 1195 JmriJOptionPane.showMessageDialog(this, 1196 Bundle.getMessage("MessageCompile", message), 1197 Bundle.getMessage("TitleCompile"), 1198 JmriJOptionPane.INFORMATION_MESSAGE); 1199 } 1200 1201 // Notifies that the contents of a given entry have changed. This will delete and re-add the 1202 // entry to the model, forcing a refresh of the box. 1203 public void updateComboBoxModelEntry(NodeEntry nodeEntry) { 1204 int idx = _nodeModel.getIndexOf(nodeEntry.getNodeID()); 1205 if (idx < 0) { 1206 return; 1207 } 1208 NodeEntry last = _nodeModel.getElementAt(idx); 1209 if (last != nodeEntry) { 1210 // not the same object -- we're talking about an abandoned entry. 1211 nodeEntry.dispose(); 1212 return; 1213 } 1214 NodeEntry sel = (NodeEntry) _nodeModel.getSelectedItem(); 1215 _nodeModel.removeElementAt(idx); 1216 _nodeModel.insertElementAt(nodeEntry, idx); 1217 _nodeModel.setSelectedItem(sel); 1218 } 1219 1220 protected static class NodeEntry implements Comparable<NodeEntry>, PropertyChangeListener { 1221 final MimicNodeStore.NodeMemo nodeMemo; 1222 String description = ""; 1223 1224 NodeEntry(MimicNodeStore.NodeMemo memo) { 1225 this.nodeMemo = memo; 1226 memo.addPropertyChangeListener(this); 1227 updateDescription(); 1228 } 1229 1230 /** 1231 * Constructor for prototype display value 1232 * 1233 * @param description prototype display value 1234 */ 1235 public NodeEntry(String description) { 1236 this.nodeMemo = null; 1237 this.description = description; 1238 } 1239 1240 public NodeID getNodeID() { 1241 return nodeMemo.getNodeID(); 1242 } 1243 1244 MimicNodeStore.NodeMemo getNodeMemo() { 1245 return nodeMemo; 1246 } 1247 1248 private void updateDescription() { 1249 SimpleNodeIdent ident = nodeMemo.getSimpleNodeIdent(); 1250 StringBuilder sb = new StringBuilder(); 1251 sb.append(nodeMemo.getNodeID().toString()); 1252 1253 addToDescription(ident.getUserName(), sb); 1254 addToDescription(ident.getUserDesc(), sb); 1255 if (!ident.getMfgName().isEmpty() || !ident.getModelName().isEmpty()) { 1256 addToDescription(ident.getMfgName() + " " +ident.getModelName(), sb); 1257 } 1258 addToDescription(ident.getSoftwareVersion(), sb); 1259 String newDescription = sb.toString(); 1260 if (!description.equals(newDescription)) { 1261 description = newDescription; 1262 } 1263 } 1264 1265 private void addToDescription(String s, StringBuilder sb) { 1266 if (!s.isEmpty()) { 1267 sb.append(" - "); 1268 sb.append(s); 1269 } 1270 } 1271 1272 private long reorder(long n) { 1273 return (n < 0) ? Long.MAX_VALUE - n : Long.MIN_VALUE + n; 1274 } 1275 1276 @Override 1277 public int compareTo(NodeEntry otherEntry) { 1278 long l1 = reorder(getNodeID().toLong()); 1279 long l2 = reorder(otherEntry.getNodeID().toLong()); 1280 return Long.compare(l1, l2); 1281 } 1282 1283 @Override 1284 public String toString() { 1285 return description; 1286 } 1287 1288 @Override 1289 @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", 1290 justification = "Purposefully attempting lookup using NodeID argument in model " + 1291 "vector.") 1292 public boolean equals(Object o) { 1293 if (o instanceof NodeEntry) { 1294 return getNodeID().equals(((NodeEntry) o).getNodeID()); 1295 } 1296 if (o instanceof NodeID) { 1297 return getNodeID().equals(o); 1298 } 1299 return false; 1300 } 1301 1302 @Override 1303 public int hashCode() { 1304 return getNodeID().hashCode(); 1305 } 1306 1307 @Override 1308 public void propertyChange(PropertyChangeEvent propertyChangeEvent) { 1309 //log.warning("Received model entry update for " + nodeMemo.getNodeID()); 1310 if (propertyChangeEvent.getPropertyName().equals(UPDATE_PROP_SIMPLE_NODE_IDENT)) { 1311 updateDescription(); 1312 } 1313 } 1314 1315 public void dispose() { 1316 //log.warning("dispose of " + nodeMemo.getNodeID().toString()); 1317 nodeMemo.removePropertyChangeListener(this); 1318 } 1319 } 1320 1321 // -------------- load CDI data --------- 1322 1323 private void loadCdiData() { 1324 if (!replaceData()) { 1325 return; 1326 } 1327 1328 // Load data 1329 loadCdiInputs(); 1330 loadCdiOutputs(); 1331 loadCdiReceivers(); 1332 loadCdiTransmitters(); 1333 loadCdiGroups(); 1334 1335 for (GroupRow row : _groupList) { 1336 decode(row); 1337 } 1338 1339 setDirty(false); 1340 1341 _groupTable.setRowSelectionInterval(0, 0); 1342 1343 _groupTable.repaint(); 1344 1345 _exportButton.setEnabled(true); 1346 _refreshButton.setEnabled(true); 1347 _storeButton.setEnabled(true); 1348 _exportItem.setEnabled(true); 1349 _refreshItem.setEnabled(true); 1350 _storeItem.setEnabled(true); 1351 } 1352 1353 private void pushedRefreshButton(ActionEvent e) { 1354 loadCdiData(); 1355 } 1356 1357 private void loadCdiGroups() { 1358 for (int i = 0; i < 16; i++) { 1359 var groupRow = _groupList.get(i); 1360 groupRow.clearLogicList(); 1361 1362 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_NAME, i)); 1363 groupRow.setName(entry.getValue()); 1364 entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_MULTI_LINE, i)); 1365 groupRow.setMultiLine(entry.getValue()); 1366 } 1367 1368 _groupTable.revalidate(); 1369 } 1370 1371 private void loadCdiInputs() { 1372 for (int i = 0; i < 16; i++) { 1373 for (int j = 0; j < 8; j++) { 1374 var inputRow = _inputList.get((i * 8) + j); 1375 1376 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(INPUT_NAME, i, j)); 1377 inputRow.setName(entry.getValue()); 1378 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_TRUE, i, j)); 1379 inputRow.setEventTrue(event.getValue().toShortString()); 1380 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_FALSE, i, j)); 1381 inputRow.setEventFalse(event.getValue().toShortString()); 1382 } 1383 } 1384 _inputTable.revalidate(); 1385 } 1386 1387 private void loadCdiOutputs() { 1388 for (int i = 0; i < 16; i++) { 1389 for (int j = 0; j < 8; j++) { 1390 var outputRow = _outputList.get((i * 8) + j); 1391 1392 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(OUTPUT_NAME, i, j)); 1393 outputRow.setName(entry.getValue()); 1394 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_TRUE, i, j)); 1395 outputRow.setEventTrue(event.getValue().toShortString()); 1396 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_FALSE, i, j)); 1397 outputRow.setEventFalse(event.getValue().toShortString()); 1398 } 1399 } 1400 _outputTable.revalidate(); 1401 } 1402 1403 private void loadCdiReceivers() { 1404 for (int i = 0; i < 16; i++) { 1405 var receiverRow = _receiverList.get(i); 1406 1407 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(RECEIVER_NAME, i)); 1408 receiverRow.setName(entry.getValue()); 1409 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(RECEIVER_EVENT, i)); 1410 receiverRow.setEventId(event.getValue().toShortString()); 1411 } 1412 _receiverTable.revalidate(); 1413 } 1414 1415 private void loadCdiTransmitters() { 1416 for (int i = 0; i < 16; i++) { 1417 var transmitterRow = _transmitterList.get(i); 1418 1419 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_NAME, i)); 1420 transmitterRow.setName(entry.getValue()); 1421 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_EVENT, i)); 1422 transmitterRow.setEventId(event.getValue().toShortString()); 1423 } 1424 _transmitterTable.revalidate(); 1425 } 1426 1427 // -------------- store CDI data --------- 1428 1429 private void pushedStoreButton(ActionEvent e) { 1430 _csvMessages.clear(); 1431 _compileNeeded = false; 1432 _storeQueueLength.set(0); 1433 1434 // Store CDI data 1435 storeInputs(); 1436 storeOutputs(); 1437 storeReceivers(); 1438 storeTransmitters(); 1439 storeGroups(); 1440 1441 setDirty(false); 1442 } 1443 1444 private void storeGroups() { 1445 // store the group data 1446 int currentCount = 0; 1447 1448 for (int i = 0; i < 16; i++) { 1449 var row = _groupList.get(i); 1450 1451 // update the group line 1452 encode(row); 1453 1454 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_NAME, i)); 1455 if (!row.getName().equals(entry.getValue())) { 1456 entry.addPropertyChangeListener(_entryListener); 1457 entry.setValue(row.getName()); 1458 currentCount = _storeQueueLength.incrementAndGet(); 1459 } 1460 1461 entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_MULTI_LINE, i)); 1462 if (!row.getMultiLine().equals(entry.getValue())) { 1463 entry.addPropertyChangeListener(_entryListener); 1464 entry.setValue(row.getMultiLine()); 1465 currentCount = _storeQueueLength.incrementAndGet(); 1466 _compileNeeded = true; 1467 } 1468 1469 log.debug("Group: {}", row.getName()); 1470 log.debug("Logic: {}", row.getMultiLine()); 1471 } 1472 log.debug("storeGroups count = {}", currentCount); 1473 } 1474 1475 private void storeInputs() { 1476 int currentCount = 0; 1477 1478 for (int i = 0; i < 16; i++) { 1479 for (int j = 0; j < 8; j++) { 1480 var row = _inputList.get((i * 8) + j); 1481 1482 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(INPUT_NAME, i, j)); 1483 if (!row.getName().equals(entry.getValue())) { 1484 entry.addPropertyChangeListener(_entryListener); 1485 entry.setValue(row.getName()); 1486 currentCount = _storeQueueLength.incrementAndGet(); 1487 } 1488 1489 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_TRUE, i, j)); 1490 if (!row.getEventTrue().equals(event.getValue().toShortString())) { 1491 event.addPropertyChangeListener(_entryListener); 1492 event.setValue(new EventID(row.getEventTrue())); 1493 currentCount = _storeQueueLength.incrementAndGet(); 1494 } 1495 1496 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_FALSE, i, j)); 1497 if (!row.getEventFalse().equals(event.getValue().toShortString())) { 1498 event.addPropertyChangeListener(_entryListener); 1499 event.setValue(new EventID(row.getEventFalse())); 1500 currentCount = _storeQueueLength.incrementAndGet(); 1501 } 1502 } 1503 } 1504 log.debug("storeInputs count = {}", currentCount); 1505 } 1506 1507 private void storeOutputs() { 1508 int currentCount = 0; 1509 1510 for (int i = 0; i < 16; i++) { 1511 for (int j = 0; j < 8; j++) { 1512 var row = _outputList.get((i * 8) + j); 1513 1514 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(OUTPUT_NAME, i, j)); 1515 if (!row.getName().equals(entry.getValue())) { 1516 entry.addPropertyChangeListener(_entryListener); 1517 entry.setValue(row.getName()); 1518 currentCount = _storeQueueLength.incrementAndGet(); 1519 } 1520 1521 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_TRUE, i, j)); 1522 if (!row.getEventTrue().equals(event.getValue().toShortString())) { 1523 event.addPropertyChangeListener(_entryListener); 1524 event.setValue(new EventID(row.getEventTrue())); 1525 currentCount = _storeQueueLength.incrementAndGet(); 1526 } 1527 1528 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_FALSE, i, j)); 1529 if (!row.getEventFalse().equals(event.getValue().toShortString())) { 1530 event.addPropertyChangeListener(_entryListener); 1531 event.setValue(new EventID(row.getEventFalse())); 1532 currentCount = _storeQueueLength.incrementAndGet(); 1533 } 1534 } 1535 } 1536 log.debug("storeOutputs count = {}", currentCount); 1537 } 1538 1539 private void storeReceivers() { 1540 int currentCount = 0; 1541 1542 for (int i = 0; i < 16; i++) { 1543 var row = _receiverList.get(i); 1544 1545 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(RECEIVER_NAME, i)); 1546 if (!row.getName().equals(entry.getValue())) { 1547 entry.addPropertyChangeListener(_entryListener); 1548 entry.setValue(row.getName()); 1549 currentCount = _storeQueueLength.incrementAndGet(); 1550 } 1551 1552 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(RECEIVER_EVENT, i)); 1553 if (!row.getEventId().equals(event.getValue().toShortString())) { 1554 event.addPropertyChangeListener(_entryListener); 1555 event.setValue(new EventID(row.getEventId())); 1556 currentCount = _storeQueueLength.incrementAndGet(); 1557 } 1558 } 1559 log.debug("storeReceivers count = {}", currentCount); 1560 } 1561 1562 private void storeTransmitters() { 1563 int currentCount = 0; 1564 1565 for (int i = 0; i < 16; i++) { 1566 var row = _transmitterList.get(i); 1567 1568 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_NAME, i)); 1569 if (!row.getName().equals(entry.getValue())) { 1570 entry.addPropertyChangeListener(_entryListener); 1571 entry.setValue(row.getName()); 1572 currentCount = _storeQueueLength.incrementAndGet(); 1573 } 1574 } 1575 log.debug("storeTransmitters count = {}", currentCount); 1576 } 1577 1578 // -------------- Backup Import --------- 1579 1580 private void loadBackupData(ActionEvent m) { 1581 if (!replaceData()) { 1582 return; 1583 } 1584 1585 var fileChooser = new JmriJFileChooser(FileUtil.getUserFilesPath()); 1586 fileChooser.setApproveButtonText(Bundle.getMessage("LoadCdiButton")); 1587 fileChooser.setDialogTitle(Bundle.getMessage("LoadCdiTitle")); 1588 var filter = new FileNameExtensionFilter(Bundle.getMessage("LoadCdiFilter"), "txt"); 1589 fileChooser.addChoosableFileFilter(filter); 1590 fileChooser.setFileFilter(filter); 1591 1592 int response = fileChooser.showOpenDialog(this); 1593 if (response == JFileChooser.CANCEL_OPTION) { 1594 return; 1595 } 1596 1597 List<String> lines = null; 1598 try { 1599 lines = Files.readAllLines(Paths.get(fileChooser.getSelectedFile().getAbsolutePath())); 1600 } catch (IOException e) { 1601 log.error("Failed to load file.", e); 1602 return; 1603 } 1604 1605 for (int i = 0; i < lines.size(); i++) { 1606 if (lines.get(i).startsWith("Logic Inputs.Group")) { 1607 loadBackupInputs(i, lines); 1608 i += 128 * 3; 1609 } 1610 1611 if (lines.get(i).startsWith("Logic Outputs.Group")) { 1612 loadBackupOutputs(i, lines); 1613 i += 128 * 3; 1614 } 1615 if (lines.get(i).startsWith("Track Receivers")) { 1616 loadBackupReceivers(i, lines); 1617 i += 16 * 2; 1618 } 1619 if (lines.get(i).startsWith("Track Transmitters")) { 1620 loadBackupTransmitters(i, lines); 1621 i += 16 * 2; 1622 } 1623 if (lines.get(i).startsWith("Conditionals.Logic")) { 1624 loadBackupGroups(i, lines); 1625 i += 16 * 2; 1626 } 1627 } 1628 1629 for (GroupRow row : _groupList) { 1630 decode(row); 1631 } 1632 1633 setDirty(false); 1634 _groupTable.setRowSelectionInterval(0, 0); 1635 _groupTable.repaint(); 1636 1637 _exportButton.setEnabled(true); 1638 _exportItem.setEnabled(true); 1639 } 1640 1641 private String getLineValue(String line) { 1642 if (line.endsWith("=")) { 1643 return ""; 1644 } 1645 int index = line.indexOf("="); 1646 var newLine = line.substring(index + 1); 1647 newLine = Util.unescapeString(newLine); 1648 return newLine; 1649 } 1650 1651 private void loadBackupInputs(int index, List<String> lines) { 1652 for (int i = 0; i < 128; i++) { 1653 var inputRow = _inputList.get(i); 1654 1655 inputRow.setName(getLineValue(lines.get(index))); 1656 inputRow.setEventTrue(getLineValue(lines.get(index + 1))); 1657 inputRow.setEventFalse(getLineValue(lines.get(index + 2))); 1658 index += 3; 1659 } 1660 1661 _inputTable.revalidate(); 1662 } 1663 1664 private void loadBackupOutputs(int index, List<String> lines) { 1665 for (int i = 0; i < 128; i++) { 1666 var outputRow = _outputList.get(i); 1667 1668 outputRow.setName(getLineValue(lines.get(index))); 1669 outputRow.setEventTrue(getLineValue(lines.get(index + 1))); 1670 outputRow.setEventFalse(getLineValue(lines.get(index + 2))); 1671 index += 3; 1672 } 1673 1674 _outputTable.revalidate(); 1675 } 1676 1677 private void loadBackupReceivers(int index, List<String> lines) { 1678 for (int i = 0; i < 16; i++) { 1679 var receiverRow = _receiverList.get(i); 1680 1681 receiverRow.setName(getLineValue(lines.get(index))); 1682 receiverRow.setEventId(getLineValue(lines.get(index + 1))); 1683 index += 2; 1684 } 1685 1686 _receiverTable.revalidate(); 1687 } 1688 1689 private void loadBackupTransmitters(int index, List<String> lines) { 1690 for (int i = 0; i < 16; i++) { 1691 var transmitterRow = _transmitterList.get(i); 1692 1693 transmitterRow.setName(getLineValue(lines.get(index))); 1694 transmitterRow.setEventId(getLineValue(lines.get(index + 1))); 1695 index += 2; 1696 } 1697 1698 _transmitterTable.revalidate(); 1699 } 1700 1701 private void loadBackupGroups(int index, List<String> lines) { 1702 for (int i = 0; i < 16; i++) { 1703 var groupRow = _groupList.get(i); 1704 groupRow.clearLogicList(); 1705 1706 groupRow.setName(getLineValue(lines.get(index))); 1707 groupRow.setMultiLine(getLineValue(lines.get(index + 1))); 1708 index += 2; 1709 } 1710 1711 _groupTable.revalidate(); 1712 _logicTable.revalidate(); 1713 } 1714 1715 // -------------- CSV Import --------- 1716 1717 private void pushedImportButton(ActionEvent e) { 1718 if (!replaceData()) { 1719 return; 1720 } 1721 1722 if (!setCsvDirectoryPath(true)) { 1723 return; 1724 } 1725 1726 _csvMessages.clear(); 1727 importCsvData(); 1728 setDirty(false); 1729 1730 _exportButton.setEnabled(true); 1731 _exportItem.setEnabled(true); 1732 1733 if (!_csvMessages.isEmpty()) { 1734 JmriJOptionPane.showMessageDialog(this, 1735 String.join("\n", _csvMessages), 1736 Bundle.getMessage("TitleCsvImport"), 1737 JmriJOptionPane.ERROR_MESSAGE); 1738 } 1739 } 1740 1741 private void importCsvData() { 1742 importGroupLogic(); 1743 importInputs(); 1744 importOutputs(); 1745 importReceivers(); 1746 importTransmitters(); 1747 1748 _groupTable.setRowSelectionInterval(0, 0); 1749 1750 _groupTable.repaint(); 1751 } 1752 1753 private void importGroupLogic() { 1754 List<CSVRecord> records = getCsvRecords("group_logic.csv"); 1755 if (records.isEmpty()) { 1756 return; 1757 } 1758 1759 var skipHeader = true; 1760 int groupNumber = -1; 1761 for (CSVRecord record : records) { 1762 if (skipHeader) { 1763 skipHeader = false; 1764 continue; 1765 } 1766 1767 List<String> values = new ArrayList<>(); 1768 record.forEach(values::add); 1769 1770 if (values.size() == 1) { 1771 // Create Group 1772 groupNumber++; 1773 var groupRow = _groupList.get(groupNumber); 1774 groupRow.setName(values.get(0)); 1775 groupRow.setMultiLine(""); 1776 groupRow.clearLogicList(); 1777 } else if (values.size() == 5) { 1778 var oper = getEnum(values.get(2)); 1779 var logicRow = new LogicRow(values.get(1), oper, values.get(3), values.get(4)); 1780 _groupList.get(groupNumber).getLogicList().add(logicRow); 1781 } else { 1782 _csvMessages.add(Bundle.getMessage("ImportGroupError", record.toString())); 1783 } 1784 } 1785 1786 _groupTable.revalidate(); 1787 _logicTable.revalidate(); 1788 } 1789 1790 private void importInputs() { 1791 List<CSVRecord> records = getCsvRecords("inputs.csv"); 1792 if (records.isEmpty()) { 1793 return; 1794 } 1795 1796 for (int i = 0; i < 129; i++) { 1797 if (i == 0) { 1798 continue; 1799 } 1800 1801 var record = records.get(i); 1802 List<String> values = new ArrayList<>(); 1803 record.forEach(values::add); 1804 1805 if (values.size() == 4) { 1806 var inputRow = _inputList.get(i - 1); 1807 inputRow.setName(values.get(1)); 1808 inputRow.setEventTrue(values.get(2)); 1809 inputRow.setEventFalse(values.get(3)); 1810 } else { 1811 _csvMessages.add(Bundle.getMessage("ImportInputError", record.toString())); 1812 } 1813 } 1814 1815 _inputTable.revalidate(); 1816 } 1817 1818 private void importOutputs() { 1819 List<CSVRecord> records = getCsvRecords("outputs.csv"); 1820 if (records.isEmpty()) { 1821 return; 1822 } 1823 1824 for (int i = 0; i < 129; i++) { 1825 if (i == 0) { 1826 continue; 1827 } 1828 1829 var record = records.get(i); 1830 List<String> values = new ArrayList<>(); 1831 record.forEach(values::add); 1832 1833 if (values.size() == 4) { 1834 var outputRow = _outputList.get(i - 1); 1835 outputRow.setName(values.get(1)); 1836 outputRow.setEventTrue(values.get(2)); 1837 outputRow.setEventFalse(values.get(3)); 1838 } else { 1839 _csvMessages.add(Bundle.getMessage("ImportOuputError", record.toString())); 1840 } 1841 } 1842 1843 _outputTable.revalidate(); 1844 } 1845 1846 private void importReceivers() { 1847 List<CSVRecord> records = getCsvRecords("receivers.csv"); 1848 if (records.isEmpty()) { 1849 return; 1850 } 1851 1852 for (int i = 0; i < 17; i++) { 1853 if (i == 0) { 1854 continue; 1855 } 1856 1857 var record = records.get(i); 1858 List<String> values = new ArrayList<>(); 1859 record.forEach(values::add); 1860 1861 if (values.size() == 3) { 1862 var receiverRow = _receiverList.get(i - 1); 1863 receiverRow.setName(values.get(1)); 1864 receiverRow.setEventId(values.get(2)); 1865 } else { 1866 _csvMessages.add(Bundle.getMessage("ImportReceiverError", record.toString())); 1867 } 1868 } 1869 1870 _receiverTable.revalidate(); 1871 } 1872 1873 private void importTransmitters() { 1874 List<CSVRecord> records = getCsvRecords("transmitters.csv"); 1875 if (records.isEmpty()) { 1876 return; 1877 } 1878 1879 for (int i = 0; i < 17; i++) { 1880 if (i == 0) { 1881 continue; 1882 } 1883 1884 var record = records.get(i); 1885 List<String> values = new ArrayList<>(); 1886 record.forEach(values::add); 1887 1888 if (values.size() == 3) { 1889 var transmitterRow = _transmitterList.get(i - 1); 1890 transmitterRow.setName(values.get(1)); 1891 transmitterRow.setEventId(values.get(2)); 1892 } else { 1893 _csvMessages.add(Bundle.getMessage("ImportTransmitterError", record.toString())); 1894 } 1895 } 1896 1897 _transmitterTable.revalidate(); 1898 } 1899 1900 private List<CSVRecord> getCsvRecords(String fileName) { 1901 var recordList = new ArrayList<CSVRecord>(); 1902 FileReader fileReader; 1903 try { 1904 fileReader = new FileReader(_csvDirectoryPath + fileName); 1905 } catch (FileNotFoundException ex) { 1906 _csvMessages.add(Bundle.getMessage("ImportFileNotFound", fileName)); 1907 return recordList; 1908 } 1909 1910 BufferedReader bufferedReader; 1911 CSVParser csvFile; 1912 1913 try { 1914 bufferedReader = new BufferedReader(fileReader); 1915 csvFile = new CSVParser(bufferedReader, CSVFormat.DEFAULT); 1916 recordList.addAll(csvFile.getRecords()); 1917 csvFile.close(); 1918 bufferedReader.close(); 1919 fileReader.close(); 1920 } catch (IOException iox) { 1921 _csvMessages.add(Bundle.getMessage("ImportFileIOError", iox.getMessage(), fileName)); 1922 } 1923 1924 return recordList; 1925 } 1926 1927 // -------------- CSV Export --------- 1928 1929 private void pushedExportButton(ActionEvent e) { 1930 if (!setCsvDirectoryPath(false)) { 1931 return; 1932 } 1933 1934 _csvMessages.clear(); 1935 exportCsvData(); 1936 setDirty(false); 1937 1938 _csvMessages.add(Bundle.getMessage("ExportDone")); 1939 var msgType = JmriJOptionPane.ERROR_MESSAGE; 1940 if (_csvMessages.size() == 1) { 1941 msgType = JmriJOptionPane.INFORMATION_MESSAGE; 1942 } 1943 JmriJOptionPane.showMessageDialog(this, 1944 String.join("\n", _csvMessages), 1945 Bundle.getMessage("TitleCsvExport"), 1946 msgType); 1947 } 1948 1949 private void exportCsvData() { 1950 try { 1951 exportGroupLogic(); 1952 exportInputs(); 1953 exportOutputs(); 1954 exportReceivers(); 1955 exportTransmitters(); 1956 } catch (IOException ex) { 1957 _csvMessages.add(Bundle.getMessage("ExportIOError", ex.getMessage())); 1958 } 1959 1960 } 1961 1962 private void exportGroupLogic() throws IOException { 1963 var fileWriter = new FileWriter(_csvDirectoryPath + "group_logic.csv"); 1964 var bufferedWriter = new BufferedWriter(fileWriter); 1965 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 1966 1967 csvFile.printRecord(Bundle.getMessage("GroupName"), Bundle.getMessage("ColumnLabel"), 1968 Bundle.getMessage("ColumnOper"), Bundle.getMessage("ColumnName"), Bundle.getMessage("ColumnComment")); 1969 1970 for (int i = 0; i < 16; i++) { 1971 var row = _groupList.get(i); 1972 var groupName = row.getName(); 1973 csvFile.printRecord(groupName); 1974 var logicRow = row.getLogicList(); 1975 for (LogicRow logic : logicRow) { 1976 var operName = logic.getOperName(); 1977 csvFile.printRecord("", logic.getLabel(), operName, logic.getName(), logic.getComment()); 1978 } 1979 } 1980 1981 // Flush the write buffer and close the file 1982 csvFile.flush(); 1983 csvFile.close(); 1984 } 1985 1986 private void exportInputs() throws IOException { 1987 var fileWriter = new FileWriter(_csvDirectoryPath + "inputs.csv"); 1988 var bufferedWriter = new BufferedWriter(fileWriter); 1989 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 1990 1991 csvFile.printRecord(Bundle.getMessage("ColumnInput"), Bundle.getMessage("ColumnName"), 1992 Bundle.getMessage("ColumnTrue"), Bundle.getMessage("ColumnFalse")); 1993 1994 for (int i = 0; i < 16; i++) { 1995 for (int j = 0; j < 8; j++) { 1996 var variable = "I" + i + "." + j; 1997 var row = _inputList.get((i * 8) + j); 1998 csvFile.printRecord(variable, row.getName(), row.getEventTrue(), row.getEventFalse()); 1999 } 2000 } 2001 2002 // Flush the write buffer and close the file 2003 csvFile.flush(); 2004 csvFile.close(); 2005 } 2006 2007 private void exportOutputs() throws IOException { 2008 var fileWriter = new FileWriter(_csvDirectoryPath + "outputs.csv"); 2009 var bufferedWriter = new BufferedWriter(fileWriter); 2010 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 2011 2012 csvFile.printRecord(Bundle.getMessage("ColumnOutput"), Bundle.getMessage("ColumnName"), 2013 Bundle.getMessage("ColumnTrue"), Bundle.getMessage("ColumnFalse")); 2014 2015 for (int i = 0; i < 16; i++) { 2016 for (int j = 0; j < 8; j++) { 2017 var variable = "Q" + i + "." + j; 2018 var row = _outputList.get((i * 8) + j); 2019 csvFile.printRecord(variable, row.getName(), row.getEventTrue(), row.getEventFalse()); 2020 } 2021 } 2022 2023 // Flush the write buffer and close the file 2024 csvFile.flush(); 2025 csvFile.close(); 2026 } 2027 2028 private void exportReceivers() throws IOException { 2029 var fileWriter = new FileWriter(_csvDirectoryPath + "receivers.csv"); 2030 var bufferedWriter = new BufferedWriter(fileWriter); 2031 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 2032 2033 csvFile.printRecord(Bundle.getMessage("ColumnCircuit"), Bundle.getMessage("ColumnName"), 2034 Bundle.getMessage("ColumnEventID")); 2035 2036 for (int i = 0; i < 16; i++) { 2037 var variable = "Y" + i; 2038 var row = _receiverList.get(i); 2039 csvFile.printRecord(variable, row.getName(), row.getEventId()); 2040 } 2041 2042 // Flush the write buffer and close the file 2043 csvFile.flush(); 2044 csvFile.close(); 2045 } 2046 2047 private void exportTransmitters() throws IOException { 2048 var fileWriter = new FileWriter(_csvDirectoryPath + "transmitters.csv"); 2049 var bufferedWriter = new BufferedWriter(fileWriter); 2050 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 2051 2052 csvFile.printRecord(Bundle.getMessage("ColumnCircuit"), Bundle.getMessage("ColumnName"), 2053 Bundle.getMessage("ColumnEventID")); 2054 2055 for (int i = 0; i < 16; i++) { 2056 var variable = "Z" + i; 2057 var row = _transmitterList.get(i); 2058 csvFile.printRecord(variable, row.getName(), row.getEventId()); 2059 } 2060 2061 // Flush the write buffer and close the file 2062 csvFile.flush(); 2063 csvFile.close(); 2064 } 2065 2066 /** 2067 * Select the directory that will be used for the CSV file set. 2068 * @param isOpen - True for CSV Import and false for CSV Export. 2069 * @return true if a directory was selected. 2070 */ 2071 private boolean setCsvDirectoryPath(boolean isOpen) { 2072 var directoryChooser = new JmriJFileChooser(FileUtil.getUserFilesPath()); 2073 directoryChooser.setApproveButtonText(Bundle.getMessage("SelectCsvButton")); 2074 directoryChooser.setDialogTitle(Bundle.getMessage("SelectCsvTitle")); 2075 directoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 2076 2077 int response = 0; 2078 if (isOpen) { 2079 response = directoryChooser.showOpenDialog(this); 2080 } else { 2081 response = directoryChooser.showSaveDialog(this); 2082 } 2083 if (response != JFileChooser.APPROVE_OPTION) { 2084 return false; 2085 } 2086 _csvDirectoryPath = directoryChooser.getSelectedFile().getAbsolutePath() + FileUtil.SEPARATOR; 2087 2088 return true; 2089 } 2090 2091 // -------------- Data Utilities --------- 2092 2093 private void setDirty(boolean dirty) { 2094 _dirty = dirty; 2095 } 2096 2097 private boolean isDirty() { 2098 return _dirty; 2099 } 2100 2101 private boolean replaceData() { 2102 if (isDirty()) { 2103 int response = JmriJOptionPane.showConfirmDialog(this, 2104 Bundle.getMessage("MessageRevert"), 2105 Bundle.getMessage("TitleRevert"), 2106 JmriJOptionPane.YES_NO_OPTION); 2107 if (response != JmriJOptionPane.YES_OPTION) { 2108 return false; 2109 } 2110 } 2111 return true; 2112 } 2113 2114 private void warningDialog(String title, String message) { 2115 JmriJOptionPane.showMessageDialog(this, 2116 message, 2117 title, 2118 JmriJOptionPane.WARNING_MESSAGE); 2119 } 2120 2121 // -------------- Data validation --------- 2122 2123 static boolean isLabelValid(String label) { 2124 if (label.isEmpty()) { 2125 return true; 2126 } 2127 2128 var match = PARSE_LABEL.matcher(label); 2129 if (match.find()) { 2130 return true; 2131 } 2132 2133 JmriJOptionPane.showMessageDialog(null, 2134 Bundle.getMessage("MessageLabel", label), 2135 Bundle.getMessage("TitleLabel"), 2136 JmriJOptionPane.ERROR_MESSAGE); 2137 return false; 2138 } 2139 2140 static boolean isEventValid(String event) { 2141 var valid = true; 2142 2143 if (event.isEmpty()) { 2144 return valid; 2145 } 2146 2147 var hexPairs = event.split("\\."); 2148 if (hexPairs.length != 8) { 2149 valid = false; 2150 } else { 2151 for (int i = 0; i < 8; i++) { 2152 var match = PARSE_HEXPAIR.matcher(hexPairs[i]); 2153 if (!match.find()) { 2154 valid = false; 2155 break; 2156 } 2157 } 2158 } 2159 2160 if (!valid) { 2161 JmriJOptionPane.showMessageDialog(null, 2162 Bundle.getMessage("MessageEvent", event), 2163 Bundle.getMessage("TitleEvent"), 2164 JmriJOptionPane.ERROR_MESSAGE); 2165 log.error("bad event: {}", event); 2166 } 2167 2168 return valid; 2169 } 2170 2171 // -------------- table lists --------- 2172 2173 /** 2174 * The Group row contains the name and the raw data for one of the 16 groups. 2175 * It also contains the decoded logic for the group in the logic list. 2176 */ 2177 static class GroupRow { 2178 String _name; 2179 String _multiLine = ""; 2180 List<LogicRow> _logicList = new ArrayList<>(); 2181 2182 2183 GroupRow(String name) { 2184 _name = name; 2185 } 2186 2187 String getName() { 2188 return _name; 2189 } 2190 2191 void setName(String newName) { 2192 _name = newName; 2193 } 2194 2195 List<LogicRow> getLogicList() { 2196 return _logicList; 2197 } 2198 2199 void setLogicList(List<LogicRow> logicList) { 2200 _logicList.clear(); 2201 _logicList.addAll(logicList); 2202 } 2203 2204 void clearLogicList() { 2205 _logicList.clear(); 2206 } 2207 2208 String getMultiLine() { 2209 return _multiLine; 2210 } 2211 2212 void setMultiLine(String newMultiLine) { 2213 _multiLine = newMultiLine.strip(); 2214 } 2215 2216 String getSize() { 2217 int size = (_multiLine.length() * 100) / 255; 2218 return String.valueOf(size) + "%"; 2219 } 2220 } 2221 2222 /** 2223 * The definition of a logic row 2224 */ 2225 static class LogicRow { 2226 String _label; 2227 Operator _oper; 2228 String _name; 2229 String _comment; 2230 2231 LogicRow(String label, Operator oper, String name, String comment) { 2232 _label = label; 2233 _oper = oper; 2234 _name = name; 2235 _comment = comment; 2236 } 2237 2238 String getLabel() { 2239 return _label; 2240 } 2241 2242 void setLabel(String newLabel) { 2243 var label = newLabel.trim(); 2244 if (isLabelValid(label)) { 2245 _label = label; 2246 } 2247 } 2248 2249 Operator getOper() { 2250 return _oper; 2251 } 2252 2253 String getOperName() { 2254 if (_oper == null) { 2255 return ""; 2256 } 2257 2258 String operName = _oper.name(); 2259 2260 // Fix special enums 2261 if (operName.equals("Cp")) { 2262 operName = ")"; 2263 } else if (operName.equals("EQ")) { 2264 operName = "="; 2265 } else if (operName.contains("p")) { 2266 operName = operName.replace("p", "("); 2267 } 2268 2269 return operName; 2270 } 2271 2272 void setOper(Operator newOper) { 2273 _oper = newOper; 2274 } 2275 2276 String getName() { 2277 return _name; 2278 } 2279 2280 void setName(String newName) { 2281 _name = newName.trim(); 2282 } 2283 2284 String getComment() { 2285 return _comment; 2286 } 2287 2288 void setComment(String newComment) { 2289 _comment = newComment; 2290 } 2291 } 2292 2293 /** 2294 * The name and assigned true and false events for an Input. 2295 */ 2296 static class InputRow { 2297 String _name; 2298 String _eventTrue; 2299 String _eventFalse; 2300 2301 InputRow(String name, String eventTrue, String eventFalse) { 2302 _name = name; 2303 _eventTrue = eventTrue; 2304 _eventFalse = eventFalse; 2305 } 2306 2307 String getName() { 2308 return _name; 2309 } 2310 2311 void setName(String newName) { 2312 _name = newName.trim(); 2313 } 2314 2315 String getEventTrue() { 2316 if (_eventTrue.length() == 0) return "00.00.00.00.00.00.00.00"; 2317 return _eventTrue; 2318 } 2319 2320 void setEventTrue(String newEventTrue) { 2321 var event = newEventTrue.trim(); 2322 if (isEventValid(event)) { 2323 _eventTrue = event; 2324 } 2325 } 2326 2327 String getEventFalse() { 2328 if (_eventFalse.length() == 0) return "00.00.00.00.00.00.00.00"; 2329 return _eventFalse; 2330 } 2331 2332 void setEventFalse(String newEventFalse) { 2333 var event = newEventFalse.trim(); 2334 if (isEventValid(event)) { 2335 _eventFalse = event; 2336 } 2337 } 2338 } 2339 2340 /** 2341 * The name and assigned true and false events for an Output. 2342 */ 2343 static class OutputRow { 2344 String _name; 2345 String _eventTrue; 2346 String _eventFalse; 2347 2348 OutputRow(String name, String eventTrue, String eventFalse) { 2349 _name = name; 2350 _eventTrue = eventTrue; 2351 _eventFalse = eventFalse; 2352 } 2353 2354 String getName() { 2355 return _name; 2356 } 2357 2358 void setName(String newName) { 2359 _name = newName.trim(); 2360 } 2361 2362 String getEventTrue() { 2363 if (_eventTrue.length() == 0) return "00.00.00.00.00.00.00.00"; 2364 return _eventTrue; 2365 } 2366 2367 void setEventTrue(String newEventTrue) { 2368 var event = newEventTrue.trim(); 2369 if (isEventValid(event)) { 2370 _eventTrue = event; 2371 } 2372 } 2373 2374 String getEventFalse() { 2375 if (_eventFalse.length() == 0) return "00.00.00.00.00.00.00.00"; 2376 return _eventFalse; 2377 } 2378 2379 void setEventFalse(String newEventFalse) { 2380 var event = newEventFalse.trim(); 2381 if (isEventValid(event)) { 2382 _eventFalse = event; 2383 } 2384 } 2385 } 2386 2387 /** 2388 * The name and assigned event id for a circuit receiver. 2389 */ 2390 static class ReceiverRow { 2391 String _name; 2392 String _eventid; 2393 2394 ReceiverRow(String name, String eventid) { 2395 _name = name; 2396 _eventid = eventid; 2397 } 2398 2399 String getName() { 2400 return _name; 2401 } 2402 2403 void setName(String newName) { 2404 _name = newName.trim(); 2405 } 2406 2407 String getEventId() { 2408 if (_eventid.length() == 0) return "00.00.00.00.00.00.00.00"; 2409 return _eventid; 2410 } 2411 2412 void setEventId(String newEventid) { 2413 var event = newEventid.trim(); 2414 if (isEventValid(event)) { 2415 _eventid = event; 2416 } 2417 } 2418 } 2419 2420 /** 2421 * The name and assigned event id for a circuit transmitter. 2422 */ 2423 static class TransmitterRow { 2424 String _name; 2425 String _eventid; 2426 2427 TransmitterRow(String name, String eventid) { 2428 _name = name; 2429 _eventid = eventid; 2430 } 2431 2432 String getName() { 2433 return _name; 2434 } 2435 2436 void setName(String newName) { 2437 _name = newName.trim(); 2438 } 2439 2440 String getEventId() { 2441 if (_eventid.length() == 0) return "00.00.00.00.00.00.00.00"; 2442 return _eventid; 2443 } 2444 2445 void setEventId(String newEventid) { 2446 var event = newEventid.trim(); 2447 if (isEventValid(event)) { 2448 _eventid = event; 2449 } 2450 } 2451 } 2452 2453 // -------------- table models --------- 2454 2455 /** 2456 * TableModel for Group table entries. 2457 */ 2458 class GroupModel extends AbstractTableModel { 2459 2460 GroupModel() { 2461 } 2462 2463 public static final int ROW_COLUMN = 0; 2464 public static final int NAME_COLUMN = 1; 2465 2466 @Override 2467 public int getRowCount() { 2468 return _groupList.size(); 2469 } 2470 2471 @Override 2472 public int getColumnCount() { 2473 return 2; 2474 } 2475 2476 @Override 2477 public Class<?> getColumnClass(int c) { 2478 return String.class; 2479 } 2480 2481 @Override 2482 public String getColumnName(int col) { 2483 switch (col) { 2484 case ROW_COLUMN: 2485 return ""; 2486 case NAME_COLUMN: 2487 return Bundle.getMessage("ColumnName"); 2488 default: 2489 return "unknown"; // NOI18N 2490 } 2491 } 2492 2493 @Override 2494 public Object getValueAt(int r, int c) { 2495 switch (c) { 2496 case ROW_COLUMN: 2497 return r + 1; 2498 case NAME_COLUMN: 2499 return _groupList.get(r).getName(); 2500 default: 2501 return null; 2502 } 2503 } 2504 2505 @Override 2506 public void setValueAt(Object type, int r, int c) { 2507 switch (c) { 2508 case NAME_COLUMN: 2509 _groupList.get(r).setName((String) type); 2510 setDirty(true); 2511 break; 2512 default: 2513 break; 2514 } 2515 } 2516 2517 @Override 2518 public boolean isCellEditable(int r, int c) { 2519 return (c == NAME_COLUMN); 2520 } 2521 2522 public int getPreferredWidth(int col) { 2523 switch (col) { 2524 case ROW_COLUMN: 2525 return new JTextField(4).getPreferredSize().width; 2526 case NAME_COLUMN: 2527 return new JTextField(20).getPreferredSize().width; 2528 default: 2529 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2530 return new JTextField(8).getPreferredSize().width; 2531 } 2532 } 2533 } 2534 2535 /** 2536 * TableModel for STL table entries. 2537 */ 2538 class LogicModel extends AbstractTableModel { 2539 2540 LogicModel() { 2541 } 2542 2543 public static final int LABEL_COLUMN = 0; 2544 public static final int OPER_COLUMN = 1; 2545 public static final int NAME_COLUMN = 2; 2546 public static final int COMMENT_COLUMN = 3; 2547 2548 @Override 2549 public int getRowCount() { 2550 var logicList = _groupList.get(_groupRow).getLogicList(); 2551 return logicList.size(); 2552 } 2553 2554 @Override 2555 public int getColumnCount() { 2556 return 4; 2557 } 2558 2559 @Override 2560 public Class<?> getColumnClass(int c) { 2561 if (c == OPER_COLUMN) return JComboBox.class; 2562 return String.class; 2563 } 2564 2565 @Override 2566 public String getColumnName(int col) { 2567 switch (col) { 2568 case LABEL_COLUMN: 2569 return Bundle.getMessage("ColumnLabel"); // NOI18N 2570 case OPER_COLUMN: 2571 return Bundle.getMessage("ColumnOper"); // NOI18N 2572 case NAME_COLUMN: 2573 return Bundle.getMessage("ColumnName"); // NOI18N 2574 case COMMENT_COLUMN: 2575 return Bundle.getMessage("ColumnComment"); // NOI18N 2576 default: 2577 return "unknown"; // NOI18N 2578 } 2579 } 2580 2581 @Override 2582 public Object getValueAt(int r, int c) { 2583 var logicList = _groupList.get(_groupRow).getLogicList(); 2584 switch (c) { 2585 case LABEL_COLUMN: 2586 return logicList.get(r).getLabel(); 2587 case OPER_COLUMN: 2588 return logicList.get(r).getOper(); 2589 case NAME_COLUMN: 2590 return logicList.get(r).getName(); 2591 case COMMENT_COLUMN: 2592 return logicList.get(r).getComment(); 2593 default: 2594 return null; 2595 } 2596 } 2597 2598 @Override 2599 public void setValueAt(Object type, int r, int c) { 2600 var logicList = _groupList.get(_groupRow).getLogicList(); 2601 switch (c) { 2602 case LABEL_COLUMN: 2603 logicList.get(r).setLabel((String) type); 2604 setDirty(true); 2605 break; 2606 case OPER_COLUMN: 2607 var z = (Operator) type; 2608 if (z != null) { 2609 if (z.name().startsWith("z")) { 2610 return; 2611 } 2612 if (z.name().equals("x0")) { 2613 logicList.get(r).setOper(null); 2614 return; 2615 } 2616 } 2617 logicList.get(r).setOper((Operator) type); 2618 setDirty(true); 2619 break; 2620 case NAME_COLUMN: 2621 logicList.get(r).setName((String) type); 2622 setDirty(true); 2623 break; 2624 case COMMENT_COLUMN: 2625 logicList.get(r).setComment((String) type); 2626 setDirty(true); 2627 break; 2628 default: 2629 break; 2630 } 2631 } 2632 2633 @Override 2634 public boolean isCellEditable(int r, int c) { 2635 return true; 2636 } 2637 2638 public int getPreferredWidth(int col) { 2639 switch (col) { 2640 case LABEL_COLUMN: 2641 return new JTextField(6).getPreferredSize().width; 2642 case OPER_COLUMN: 2643 return new JTextField(20).getPreferredSize().width; 2644 case NAME_COLUMN: 2645 case COMMENT_COLUMN: 2646 return new JTextField(40).getPreferredSize().width; 2647 default: 2648 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2649 return new JTextField(8).getPreferredSize().width; 2650 } 2651 } 2652 } 2653 2654 /** 2655 * TableModel for Input table entries. 2656 */ 2657 class InputModel extends AbstractTableModel { 2658 2659 InputModel() { 2660 } 2661 2662 public static final int INPUT_COLUMN = 0; 2663 public static final int NAME_COLUMN = 1; 2664 public static final int TRUE_COLUMN = 2; 2665 public static final int FALSE_COLUMN = 3; 2666 2667 @Override 2668 public int getRowCount() { 2669 return _inputList.size(); 2670 } 2671 2672 @Override 2673 public int getColumnCount() { 2674 return 4; 2675 } 2676 2677 @Override 2678 public Class<?> getColumnClass(int c) { 2679 return String.class; 2680 } 2681 2682 @Override 2683 public String getColumnName(int col) { 2684 switch (col) { 2685 case INPUT_COLUMN: 2686 return Bundle.getMessage("ColumnInput"); // NOI18N 2687 case NAME_COLUMN: 2688 return Bundle.getMessage("ColumnName"); // NOI18N 2689 case TRUE_COLUMN: 2690 return Bundle.getMessage("ColumnTrue"); // NOI18N 2691 case FALSE_COLUMN: 2692 return Bundle.getMessage("ColumnFalse"); // NOI18N 2693 default: 2694 return "unknown"; // NOI18N 2695 } 2696 } 2697 2698 @Override 2699 public Object getValueAt(int r, int c) { 2700 switch (c) { 2701 case INPUT_COLUMN: 2702 int grp = r / 8; 2703 int rem = r % 8; 2704 return "I" + grp + "." + rem; 2705 case NAME_COLUMN: 2706 return _inputList.get(r).getName(); 2707 case TRUE_COLUMN: 2708 return _inputList.get(r).getEventTrue(); 2709 case FALSE_COLUMN: 2710 return _inputList.get(r).getEventFalse(); 2711 default: 2712 return null; 2713 } 2714 } 2715 2716 @Override 2717 public void setValueAt(Object type, int r, int c) { 2718 switch (c) { 2719 case NAME_COLUMN: 2720 _inputList.get(r).setName((String) type); 2721 setDirty(true); 2722 break; 2723 case TRUE_COLUMN: 2724 _inputList.get(r).setEventTrue((String) type); 2725 setDirty(true); 2726 break; 2727 case FALSE_COLUMN: 2728 _inputList.get(r).setEventFalse((String) type); 2729 setDirty(true); 2730 break; 2731 default: 2732 break; 2733 } 2734 } 2735 2736 @Override 2737 public boolean isCellEditable(int r, int c) { 2738 return ((c == NAME_COLUMN) || (c == TRUE_COLUMN) || (c == FALSE_COLUMN)); 2739 } 2740 2741 public int getPreferredWidth(int col) { 2742 switch (col) { 2743 case INPUT_COLUMN: 2744 return new JTextField(6).getPreferredSize().width; 2745 case NAME_COLUMN: 2746 return new JTextField(50).getPreferredSize().width; 2747 case TRUE_COLUMN: 2748 case FALSE_COLUMN: 2749 return new JTextField(20).getPreferredSize().width; 2750 default: 2751 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2752 return new JTextField(8).getPreferredSize().width; 2753 } 2754 } 2755 } 2756 2757 /** 2758 * TableModel for Output table entries. 2759 */ 2760 class OutputModel extends AbstractTableModel { 2761 OutputModel() { 2762 } 2763 2764 public static final int OUTPUT_COLUMN = 0; 2765 public static final int NAME_COLUMN = 1; 2766 public static final int TRUE_COLUMN = 2; 2767 public static final int FALSE_COLUMN = 3; 2768 2769 @Override 2770 public int getRowCount() { 2771 return _outputList.size(); 2772 } 2773 2774 @Override 2775 public int getColumnCount() { 2776 return 4; 2777 } 2778 2779 @Override 2780 public Class<?> getColumnClass(int c) { 2781 return String.class; 2782 } 2783 2784 @Override 2785 public String getColumnName(int col) { 2786 switch (col) { 2787 case OUTPUT_COLUMN: 2788 return Bundle.getMessage("ColumnOutput"); // NOI18N 2789 case NAME_COLUMN: 2790 return Bundle.getMessage("ColumnName"); // NOI18N 2791 case TRUE_COLUMN: 2792 return Bundle.getMessage("ColumnTrue"); // NOI18N 2793 case FALSE_COLUMN: 2794 return Bundle.getMessage("ColumnFalse"); // NOI18N 2795 default: 2796 return "unknown"; // NOI18N 2797 } 2798 } 2799 2800 @Override 2801 public Object getValueAt(int r, int c) { 2802 switch (c) { 2803 case OUTPUT_COLUMN: 2804 int grp = r / 8; 2805 int rem = r % 8; 2806 return "Q" + grp + "." + rem; 2807 case NAME_COLUMN: 2808 return _outputList.get(r).getName(); 2809 case TRUE_COLUMN: 2810 return _outputList.get(r).getEventTrue(); 2811 case FALSE_COLUMN: 2812 return _outputList.get(r).getEventFalse(); 2813 default: 2814 return null; 2815 } 2816 } 2817 2818 @Override 2819 public void setValueAt(Object type, int r, int c) { 2820 switch (c) { 2821 case NAME_COLUMN: 2822 _outputList.get(r).setName((String) type); 2823 setDirty(true); 2824 break; 2825 case TRUE_COLUMN: 2826 _outputList.get(r).setEventTrue((String) type); 2827 setDirty(true); 2828 break; 2829 case FALSE_COLUMN: 2830 _outputList.get(r).setEventFalse((String) type); 2831 setDirty(true); 2832 break; 2833 default: 2834 break; 2835 } 2836 } 2837 2838 @Override 2839 public boolean isCellEditable(int r, int c) { 2840 return ((c == NAME_COLUMN) || (c == TRUE_COLUMN) || (c == FALSE_COLUMN)); 2841 } 2842 2843 public int getPreferredWidth(int col) { 2844 switch (col) { 2845 case OUTPUT_COLUMN: 2846 return new JTextField(6).getPreferredSize().width; 2847 case NAME_COLUMN: 2848 return new JTextField(50).getPreferredSize().width; 2849 case TRUE_COLUMN: 2850 case FALSE_COLUMN: 2851 return new JTextField(20).getPreferredSize().width; 2852 default: 2853 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2854 return new JTextField(8).getPreferredSize().width; 2855 } 2856 } 2857 } 2858 2859 /** 2860 * TableModel for circuit receiver table entries. 2861 */ 2862 class ReceiverModel extends AbstractTableModel { 2863 2864 ReceiverModel() { 2865 } 2866 2867 public static final int CIRCUIT_COLUMN = 0; 2868 public static final int NAME_COLUMN = 1; 2869 public static final int EVENTID_COLUMN = 2; 2870 2871 @Override 2872 public int getRowCount() { 2873 return _receiverList.size(); 2874 } 2875 2876 @Override 2877 public int getColumnCount() { 2878 return 3; 2879 } 2880 2881 @Override 2882 public Class<?> getColumnClass(int c) { 2883 return String.class; 2884 } 2885 2886 @Override 2887 public String getColumnName(int col) { 2888 switch (col) { 2889 case CIRCUIT_COLUMN: 2890 return Bundle.getMessage("ColumnCircuit"); // NOI18N 2891 case NAME_COLUMN: 2892 return Bundle.getMessage("ColumnName"); // NOI18N 2893 case EVENTID_COLUMN: 2894 return Bundle.getMessage("ColumnEventID"); // NOI18N 2895 default: 2896 return "unknown"; // NOI18N 2897 } 2898 } 2899 2900 @Override 2901 public Object getValueAt(int r, int c) { 2902 switch (c) { 2903 case CIRCUIT_COLUMN: 2904 return "Y" + r; 2905 case NAME_COLUMN: 2906 return _receiverList.get(r).getName(); 2907 case EVENTID_COLUMN: 2908 return _receiverList.get(r).getEventId(); 2909 default: 2910 return null; 2911 } 2912 } 2913 2914 @Override 2915 public void setValueAt(Object type, int r, int c) { 2916 switch (c) { 2917 case NAME_COLUMN: 2918 _receiverList.get(r).setName((String) type); 2919 setDirty(true); 2920 break; 2921 case EVENTID_COLUMN: 2922 _receiverList.get(r).setEventId((String) type); 2923 setDirty(true); 2924 break; 2925 default: 2926 break; 2927 } 2928 } 2929 2930 @Override 2931 public boolean isCellEditable(int r, int c) { 2932 return ((c == NAME_COLUMN) || (c == EVENTID_COLUMN)); 2933 } 2934 2935 public int getPreferredWidth(int col) { 2936 switch (col) { 2937 case CIRCUIT_COLUMN: 2938 return new JTextField(6).getPreferredSize().width; 2939 case NAME_COLUMN: 2940 return new JTextField(50).getPreferredSize().width; 2941 case EVENTID_COLUMN: 2942 return new JTextField(20).getPreferredSize().width; 2943 default: 2944 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2945 return new JTextField(8).getPreferredSize().width; 2946 } 2947 } 2948 } 2949 2950 /** 2951 * TableModel for circuit transmitter table entries. 2952 */ 2953 class TransmitterModel extends AbstractTableModel { 2954 2955 TransmitterModel() { 2956 } 2957 2958 public static final int CIRCUIT_COLUMN = 0; 2959 public static final int NAME_COLUMN = 1; 2960 public static final int EVENTID_COLUMN = 2; 2961 2962 @Override 2963 public int getRowCount() { 2964 return _transmitterList.size(); 2965 } 2966 2967 @Override 2968 public int getColumnCount() { 2969 return 3; 2970 } 2971 2972 @Override 2973 public Class<?> getColumnClass(int c) { 2974 return String.class; 2975 } 2976 2977 @Override 2978 public String getColumnName(int col) { 2979 switch (col) { 2980 case CIRCUIT_COLUMN: 2981 return Bundle.getMessage("ColumnCircuit"); // NOI18N 2982 case NAME_COLUMN: 2983 return Bundle.getMessage("ColumnName"); // NOI18N 2984 case EVENTID_COLUMN: 2985 return Bundle.getMessage("ColumnEventID"); // NOI18N 2986 default: 2987 return "unknown"; // NOI18N 2988 } 2989 } 2990 2991 @Override 2992 public Object getValueAt(int r, int c) { 2993 switch (c) { 2994 case CIRCUIT_COLUMN: 2995 return "Z" + r; 2996 case NAME_COLUMN: 2997 return _transmitterList.get(r).getName(); 2998 case EVENTID_COLUMN: 2999 return _transmitterList.get(r).getEventId(); 3000 default: 3001 return null; 3002 } 3003 } 3004 3005 @Override 3006 public void setValueAt(Object type, int r, int c) { 3007 switch (c) { 3008 case NAME_COLUMN: 3009 _transmitterList.get(r).setName((String) type); 3010 setDirty(true); 3011 break; 3012 case EVENTID_COLUMN: 3013 _transmitterList.get(r).setEventId((String) type); 3014 setDirty(true); 3015 break; 3016 default: 3017 break; 3018 } 3019 } 3020 3021 @Override 3022 public boolean isCellEditable(int r, int c) { 3023 return ((c == NAME_COLUMN) || (c == EVENTID_COLUMN)); 3024 } 3025 3026 public int getPreferredWidth(int col) { 3027 switch (col) { 3028 case CIRCUIT_COLUMN: 3029 return new JTextField(6).getPreferredSize().width; 3030 case NAME_COLUMN: 3031 return new JTextField(50).getPreferredSize().width; 3032 case EVENTID_COLUMN: 3033 return new JTextField(20).getPreferredSize().width; 3034 default: 3035 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 3036 return new JTextField(8).getPreferredSize().width; 3037 } 3038 } 3039 } 3040 3041 // -------------- Operator Enum --------- 3042 3043 public enum Operator { 3044 x0(Bundle.getMessage("Separator0")), 3045 z1(Bundle.getMessage("Separator1")), 3046 A(Bundle.getMessage("OperatorA")), 3047 AN(Bundle.getMessage("OperatorAN")), 3048 O(Bundle.getMessage("OperatorO")), 3049 ON(Bundle.getMessage("OperatorON")), 3050 X(Bundle.getMessage("OperatorX")), 3051 XN(Bundle.getMessage("OperatorXN")), 3052 3053 z2(Bundle.getMessage("Separator2")), // The STL parens are represented by lower case p 3054 Ap(Bundle.getMessage("OperatorAp")), 3055 ANp(Bundle.getMessage("OperatorANp")), 3056 Op(Bundle.getMessage("OperatorOp")), 3057 ONp(Bundle.getMessage("OperatorONp")), 3058 Xp(Bundle.getMessage("OperatorXp")), 3059 XNp(Bundle.getMessage("OperatorXNp")), 3060 Cp(Bundle.getMessage("OperatorCp")), // Close paren 3061 3062 z3(Bundle.getMessage("Separator3")), 3063 EQ(Bundle.getMessage("OperatorEQ")), // = operator 3064 R(Bundle.getMessage("OperatorR")), 3065 S(Bundle.getMessage("OperatorS")), 3066 3067 z4(Bundle.getMessage("Separator4")), 3068 NOT(Bundle.getMessage("OperatorNOT")), 3069 SET(Bundle.getMessage("OperatorSET")), 3070 CLR(Bundle.getMessage("OperatorCLR")), 3071 SAVE(Bundle.getMessage("OperatorSAVE")), 3072 3073 z5(Bundle.getMessage("Separator5")), 3074 JU(Bundle.getMessage("OperatorJU")), 3075 JC(Bundle.getMessage("OperatorJC")), 3076 JCN(Bundle.getMessage("OperatorJCN")), 3077 JCB(Bundle.getMessage("OperatorJCB")), 3078 JNB(Bundle.getMessage("OperatorJNB")), 3079 JBI(Bundle.getMessage("OperatorJBI")), 3080 JNBI(Bundle.getMessage("OperatorJNBI")), 3081 3082 z6(Bundle.getMessage("Separator6")), 3083 FN(Bundle.getMessage("OperatorFN")), 3084 FP(Bundle.getMessage("OperatorFP")), 3085 3086 z7(Bundle.getMessage("Separator7")), 3087 L(Bundle.getMessage("OperatorL")), 3088 FR(Bundle.getMessage("OperatorFR")), 3089 SP(Bundle.getMessage("OperatorSP")), 3090 SE(Bundle.getMessage("OperatorSE")), 3091 SD(Bundle.getMessage("OperatorSD")), 3092 SS(Bundle.getMessage("OperatorSS")), 3093 SF(Bundle.getMessage("OperatorSF")); 3094 3095 private final String _text; 3096 3097 private Operator(String text) { 3098 this._text = text; 3099 } 3100 3101 @Override 3102 public String toString() { 3103 return _text; 3104 } 3105 3106 } 3107 3108 // -------------- Token Class --------- 3109 3110 static class Token { 3111 String _type = ""; 3112 String _name = ""; 3113 int _offsetStart = 0; 3114 int _offsetEnd = 0; 3115 3116 Token(String type, String name, int offsetStart, int offsetEnd) { 3117 _type = type; 3118 _name = name; 3119 _offsetStart = offsetStart; 3120 _offsetEnd = offsetEnd; 3121 } 3122 3123 public String getType() { 3124 return _type; 3125 } 3126 3127 public String getName() { 3128 return _name; 3129 } 3130 3131 public int getStart() { 3132 return _offsetStart; 3133 } 3134 3135 public int getEnd() { 3136 return _offsetEnd; 3137 } 3138 3139 @Override 3140 public String toString() { 3141 return String.format("Type: %s, Name: %s, Start: %d, End: %d", 3142 _type, _name, _offsetStart, _offsetEnd); 3143 } 3144 } 3145 3146 // -------------- misc items --------- 3147 @Override 3148 public java.util.List<JMenu> getMenus() { 3149 // create a file menu 3150 var retval = new ArrayList<JMenu>(); 3151 var fileMenu = new JMenu(Bundle.getMessage("MenuFile")); 3152 3153 _refreshItem = new JMenuItem(Bundle.getMessage("MenuRefresh")); 3154 _storeItem = new JMenuItem(Bundle.getMessage("MenuStore")); 3155 _importItem = new JMenuItem(Bundle.getMessage("MenuImport")); 3156 _exportItem = new JMenuItem(Bundle.getMessage("MenuExport")); 3157 _loadItem = new JMenuItem(Bundle.getMessage("MenuLoad")); 3158 3159 _refreshItem.addActionListener(this::pushedRefreshButton); 3160 _storeItem.addActionListener(this::pushedStoreButton); 3161 _importItem.addActionListener(this::pushedImportButton); 3162 _exportItem.addActionListener(this::pushedExportButton); 3163 _loadItem.addActionListener(this::loadBackupData); 3164 3165 fileMenu.add(_refreshItem); 3166 fileMenu.add(_storeItem); 3167 fileMenu.addSeparator(); 3168 fileMenu.add(_importItem); 3169 fileMenu.add(_exportItem); 3170 fileMenu.addSeparator(); 3171 fileMenu.add(_loadItem); 3172 3173 _refreshItem.setEnabled(false); 3174 _storeItem.setEnabled(false); 3175 _exportItem.setEnabled(false); 3176 3177 retval.add(fileMenu); 3178 return retval; 3179 } 3180 3181 @Override 3182 public void dispose() { 3183 _pm.setSimplePreferenceState(_storeModeCheck, _compactOption.isSelected()); 3184 // and complete this 3185 super.dispose(); 3186 } 3187 3188 @Override 3189 public String getHelpTarget() { 3190 return "package.jmri.jmrix.openlcb.swing.stleditor.StlEditorPane"; 3191 } 3192 3193 @Override 3194 public String getTitle() { 3195 if (_canMemo != null) { 3196 return (_canMemo.getUserName() + " STL Editor"); 3197 } 3198 return Bundle.getMessage("TitleSTLEditor"); 3199 } 3200 3201 /** 3202 * Nested class to create one of these using old-style defaults 3203 */ 3204 public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 3205 3206 public Default() { 3207 super("STL Editor", 3208 new jmri.util.swing.sdi.JmriJFrameInterface(), 3209 StlEditorPane.class.getName(), 3210 jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 3211 } 3212 } 3213 3214 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(StlEditorPane.class); 3215}