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 = 106; 074 private static String TOWER_LCC_Q_NODE_VERSION_STRING = "v1.06"; 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(separator + 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 if (getEnum(oper) != null) { 827 _tokenMap.put(matchOper.start(), new Token("Oper", oper, matchOper.start(), matchOper.end())); 828 } else { 829 _messages.add(Bundle.getMessage("ErrStandAlone", oper)); 830 } 831 } 832 833 // Find jump operators and destinations 834 var matchJump = PARSE_JUMP.matcher(line); 835 while (matchJump.find()) { 836 var jump = line.substring(matchJump.start(), matchJump.end()); 837 if (getEnum(jump) != null && (jump.startsWith("J") || jump.startsWith("j"))) { 838 _tokenMap.put(matchJump.start(), new Token("Oper", jump, matchJump.start(), matchJump.end())); 839 840 // Get the jump destination 841 var matchDest = PARSE_DEST.matcher(line); 842 if (matchDest.find(matchJump.end())) { 843 var dest = matchDest.group(1); 844 _tokenMap.put(matchDest.start(), new Token("Dest", dest, matchDest.start(), matchDest.end())); 845 } else { 846 _messages.add(Bundle.getMessage("ErrJumpDest", jump)); 847 } 848 } else { 849 _messages.add(Bundle.getMessage("ErrJumpOper", jump)); 850 } 851 } 852 853 // Find timer word locations and load operator 854 var matchTimerWord = PARSE_TIMERWORD.matcher(line); 855 while (matchTimerWord.find()) { 856 var timerWord = matchTimerWord.group(1); 857 _tokenMap.put(matchTimerWord.start(), new Token("TimerWord", timerWord, matchTimerWord.start(), matchTimerWord.end())); 858 var operToken = findOperator(matchTimerWord.start() - 1, line); 859 if (operToken != null) { 860 if (operToken.getName().equals("L") || operToken.getName().equals("l")) { 861 _tokenMap.put(operToken.getStart(), operToken); 862 } else { 863 _messages.add(Bundle.getMessage("ErrTimerLoad", operToken.getName())); 864 } 865 } 866 } 867 868 // Find timer variable locations and S operators 869 var matchTimerVar = PARSE_TIMERVAR.matcher(line); 870 while (matchTimerVar.find()) { 871 var timerVar = matchTimerVar.group(1); 872 _tokenMap.put(matchTimerVar.start(), new Token("TimerVar", timerVar, matchTimerVar.start(), matchTimerVar.end())); 873 var operToken = findOperator(matchTimerVar.start() - 1, line); 874 if (operToken != null) { 875 _tokenMap.put(operToken.getStart(), operToken); 876 } 877 } 878 879 // Find comment locations 880 var matchComment1 = PARSE_COMMENT1.matcher(line); 881 while (matchComment1.find()) { 882 var comment = matchComment1.group(1).trim(); 883 _tokenMap.put(matchComment1.start(), new Token("Comment", comment, matchComment1.start(), matchComment1.end())); 884 } 885 886 var matchComment2 = PARSE_COMMENT2.matcher(line); 887 while (matchComment2.find()) { 888 var comment = matchComment2.group(1).trim(); 889 _tokenMap.put(matchComment2.start(), new Token("Comment", comment, matchComment2.start(), matchComment2.end())); 890 } 891 892 // Check for overlapping jump destinations and following labels 893 for (Token token : _tokenMap.values()) { 894 if (token.getType().equals("Dest")) { 895 var nextKey = _tokenMap.higherKey(token.getStart()); 896 if (nextKey != null) { 897 var nextToken = _tokenMap.get(nextKey); 898 if (nextToken.getType().equals("Label")) { 899 if (token.getEnd() > nextToken.getStart()) { 900 _messages.add(Bundle.getMessage("ErrDestLabel", token.getName(), nextToken.getName())); 901 } 902 } 903 } 904 } 905 } 906 907 if (_messages.size() > 0) { 908 // Display messages 909 String msgs = _messages.stream().collect(java.util.stream.Collectors.joining("\n")); 910 JmriJOptionPane.showMessageDialog(null, 911 Bundle.getMessage("MsgParseErr", group.getName(), msgs), 912 Bundle.getMessage("TitleParseErr"), 913 JmriJOptionPane.ERROR_MESSAGE); 914 _messages.forEach((msg) -> { 915 log.error(msg); 916 }); 917 } 918 919 // Create token debugging output 920 if (log.isDebugEnabled()) { 921 log.info("Line = {}", line); 922 for (Token token : _tokenMap.values()) { 923 log.info("Token = {}", token); 924 } 925 } 926 } 927 928 /** 929 * Starting as the operator location minus one, work backwards to find a valid operator. When 930 * one is found, create and return the token object. 931 * @param index The current location in the line. 932 * @param line The line for the current group. 933 * @return a token or null. 934 */ 935 private Token findOperator(int index, String line) { 936 var sb = new StringBuilder(); 937 int limit = 10; 938 939 while (limit > 0 && index >= 0) { 940 var ch = line.charAt(index); 941 if (ch != ' ') { 942 sb.insert(0, ch); 943 if (getEnum(sb.toString()) != null) { 944 String oper = sb.toString(); 945 return new Token("Oper", oper, index, index + oper.length()); 946 } 947 } 948 limit--; 949 index--; 950 } 951 _messages.add(Bundle.getMessage("ErrNoOper", index, line)); 952 return null; 953 } 954 955 private Operator getEnum(String name) { 956 try { 957 var temp = name.toUpperCase(); 958 if (name.equals("=")) { 959 temp = "EQ"; 960 } else if (name.equals(")")) { 961 temp = "Cp"; 962 } else if (name.endsWith("(")) { 963 temp = name.toUpperCase().replace("(", "p"); 964 } 965 966 Operator oper = Enum.valueOf(Operator.class, temp); 967 return oper; 968 } catch (IllegalArgumentException ex) { 969 return null; 970 } 971 } 972 973 // -------------- node methods --------- 974 975 private void nodeSelected(ActionEvent e) { 976 NodeEntry node = (NodeEntry) _nodeBox.getSelectedItem(); 977 node.getNodeMemo().addPropertyChangeListener(new RebootListener()); 978 log.debug("nodeSelected: {}", node); 979 980 if (isValidNodeVersionNumber(node.getNodeMemo())) { 981 _cdi = _iface.getConfigForNode(node.getNodeID()); 982 if (_cdi.getRoot() != null) { 983 loadCdiData(); 984 } else { 985 JmriJOptionPane.showMessageDialogNonModal(this, 986 Bundle.getMessage("MessageCdiLoad", node), 987 Bundle.getMessage("TitleCdiLoad"), 988 JmriJOptionPane.INFORMATION_MESSAGE, 989 null); 990 _cdi.addPropertyChangeListener(new CdiListener()); 991 } 992 } 993 } 994 995 public class CdiListener implements PropertyChangeListener { 996 public void propertyChange(PropertyChangeEvent e) { 997 String propertyName = e.getPropertyName(); 998 log.debug("CdiListener event = {}", propertyName); 999 1000 if (propertyName.equals("UPDATE_CACHE_COMPLETE")) { 1001 Window[] windows = Window.getWindows(); 1002 for (Window window : windows) { 1003 if (window instanceof JDialog) { 1004 JDialog dialog = (JDialog) window; 1005 if (dialog.getTitle().equals(Bundle.getMessage("TitleCdiLoad"))) { 1006 dialog.dispose(); 1007 } 1008 } 1009 } 1010 loadCdiData(); 1011 } 1012 } 1013 } 1014 1015 /** 1016 * Listens for a property change that implies a node has been rebooted. 1017 * This occurs when the user has selected that the editor should do the reboot to compile the updated logic. 1018 * When the updateSimpleNodeIdent event occurs and the compile is in progress it starts the message display process. 1019 */ 1020 public class RebootListener implements PropertyChangeListener { 1021 public void propertyChange(PropertyChangeEvent e) { 1022 String propertyName = e.getPropertyName(); 1023 if (_compileInProgress && propertyName.equals("updateSimpleNodeIdent")) { 1024 log.debug("The reboot appears to be done"); 1025 getCompileMessage(); 1026 } 1027 } 1028 } 1029 1030 private void newNodeInList(MimicNodeStore.NodeMemo nodeMemo) { 1031 // Filter for Tower LCC+Q 1032 NodeID node = nodeMemo.getNodeID(); 1033 String id = node.toString(); 1034 log.debug("node id: {}", id); 1035 if (!id.startsWith("02.01.57.4")) { 1036 return; 1037 } 1038 1039 int i = 0; 1040 if (_nodeModel.getIndexOf(nodeMemo.getNodeID()) >= 0) { 1041 // already exists. Do nothing. 1042 return; 1043 } 1044 NodeEntry e = new NodeEntry(nodeMemo); 1045 1046 while ((i < _nodeModel.getSize()) && (_nodeModel.getElementAt(i).compareTo(e) < 0)) { 1047 ++i; 1048 } 1049 _nodeModel.insertElementAt(e, i); 1050 } 1051 1052 private boolean isValidNodeVersionNumber(MimicNodeStore.NodeMemo nodeMemo) { 1053 SimpleNodeIdent ident = nodeMemo.getSimpleNodeIdent(); 1054 String versionString = ident.getSoftwareVersion(); 1055 1056 int version = 0; 1057 var match = PARSE_VERSION.matcher(versionString); 1058 if (match.find()) { 1059 var major = match.group(1); 1060 var minor = match.group(2); 1061 version = Integer.parseInt(major + minor); 1062 } 1063 1064 if (version < TOWER_LCC_Q_NODE_VERSION) { 1065 JmriJOptionPane.showMessageDialog(null, 1066 Bundle.getMessage("MessageVersion", 1067 nodeMemo.getNodeID(), 1068 versionString, 1069 TOWER_LCC_Q_NODE_VERSION_STRING), 1070 Bundle.getMessage("TitleVersion"), 1071 JmriJOptionPane.WARNING_MESSAGE); 1072 return false; 1073 } 1074 1075 return true; 1076 } 1077 1078 public class EntryListener implements PropertyChangeListener { 1079 public void propertyChange(PropertyChangeEvent e) { 1080 String propertyName = e.getPropertyName(); 1081 log.debug("EntryListener event = {}", propertyName); 1082 1083 if (propertyName.equals("PENDING_WRITE_COMPLETE")) { 1084 int currentLength = _storeQueueLength.decrementAndGet(); 1085 log.debug("Listener: queue length = {}, source = {}", currentLength, e.getSource()); 1086 1087 var entry = (ConfigRepresentation.CdiEntry) e.getSource(); 1088 entry.removePropertyChangeListener(_entryListener); 1089 1090 if (currentLength < 1) { 1091 log.debug("The queue is back to zero which implies the updates are done"); 1092 displayStoreDone(); 1093 } 1094 } 1095 1096 if (_compileInProgress && propertyName.equals("UPDATE_ENTRY_DATA")) { 1097 // The refresh of the first syntax message has completed. 1098 var entry = (ConfigRepresentation.StringEntry) e.getSource(); 1099 entry.removePropertyChangeListener(_entryListener); 1100 displayCompileMessage(entry.getValue()); 1101 } 1102 } 1103 } 1104 1105 private void displayStoreDone() { 1106 _csvMessages.add(Bundle.getMessage("StoreDone")); 1107 var msgType = JmriJOptionPane.ERROR_MESSAGE; 1108 if (_csvMessages.size() == 1) { 1109 msgType = JmriJOptionPane.INFORMATION_MESSAGE; 1110 } 1111 JmriJOptionPane.showMessageDialog(this, 1112 String.join("\n", _csvMessages), 1113 Bundle.getMessage("TitleCdiStore"), 1114 msgType); 1115 1116 if (_compileNeeded) { 1117 log.debug("Display compile needed message"); 1118 1119 String[] options = {Bundle.getMessage("EditorReboot"), Bundle.getMessage("CdiReboot")}; 1120 int response = JmriJOptionPane.showOptionDialog(this, 1121 Bundle.getMessage("MessageCdiReboot"), 1122 Bundle.getMessage("TitleCdiReboot"), 1123 JmriJOptionPane.YES_NO_OPTION, 1124 JmriJOptionPane.QUESTION_MESSAGE, 1125 null, 1126 options, 1127 options[0]); 1128 1129 if (response == JmriJOptionPane.YES_OPTION) { 1130 // Set the compile in process and request the reboot. The completion will be 1131 // handed by the RebootListener. 1132 _compileInProgress = true; 1133 _cdi.getConnection().getDatagramService(). 1134 sendData(_cdi.getRemoteNodeID(), new int[] {0x20, 0xA9}); 1135 } 1136 } 1137 } 1138 1139 /** 1140 * Get the first syntax message entry, add the entry listener and request a reload (refresh). 1141 * The EntryListener will handle the reload event. 1142 */ 1143 private void getCompileMessage() { 1144 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(SYNTAX_MESSAGE); 1145 entry.addPropertyChangeListener(_entryListener); 1146 entry.reload(); 1147 } 1148 1149 /** 1150 * Turn off the compile in progress and display the syntax message. 1151 * @param message The first syntax message. 1152 */ 1153 private void displayCompileMessage(String message) { 1154 _compileInProgress = false; 1155 JmriJOptionPane.showMessageDialog(this, 1156 Bundle.getMessage("MessageCompile", message), 1157 Bundle.getMessage("TitleCompile"), 1158 JmriJOptionPane.INFORMATION_MESSAGE); 1159 } 1160 1161 // Notifies that the contents of a given entry have changed. This will delete and re-add the 1162 // entry to the model, forcing a refresh of the box. 1163 public void updateComboBoxModelEntry(NodeEntry nodeEntry) { 1164 int idx = _nodeModel.getIndexOf(nodeEntry.getNodeID()); 1165 if (idx < 0) { 1166 return; 1167 } 1168 NodeEntry last = _nodeModel.getElementAt(idx); 1169 if (last != nodeEntry) { 1170 // not the same object -- we're talking about an abandoned entry. 1171 nodeEntry.dispose(); 1172 return; 1173 } 1174 NodeEntry sel = (NodeEntry) _nodeModel.getSelectedItem(); 1175 _nodeModel.removeElementAt(idx); 1176 _nodeModel.insertElementAt(nodeEntry, idx); 1177 _nodeModel.setSelectedItem(sel); 1178 } 1179 1180 protected static class NodeEntry implements Comparable<NodeEntry>, PropertyChangeListener { 1181 final MimicNodeStore.NodeMemo nodeMemo; 1182 String description = ""; 1183 1184 NodeEntry(MimicNodeStore.NodeMemo memo) { 1185 this.nodeMemo = memo; 1186 memo.addPropertyChangeListener(this); 1187 updateDescription(); 1188 } 1189 1190 /** 1191 * Constructor for prototype display value 1192 * 1193 * @param description prototype display value 1194 */ 1195 public NodeEntry(String description) { 1196 this.nodeMemo = null; 1197 this.description = description; 1198 } 1199 1200 public NodeID getNodeID() { 1201 return nodeMemo.getNodeID(); 1202 } 1203 1204 MimicNodeStore.NodeMemo getNodeMemo() { 1205 return nodeMemo; 1206 } 1207 1208 private void updateDescription() { 1209 SimpleNodeIdent ident = nodeMemo.getSimpleNodeIdent(); 1210 StringBuilder sb = new StringBuilder(); 1211 sb.append(nodeMemo.getNodeID().toString()); 1212 1213 addToDescription(ident.getUserName(), sb); 1214 addToDescription(ident.getUserDesc(), sb); 1215 if (!ident.getMfgName().isEmpty() || !ident.getModelName().isEmpty()) { 1216 addToDescription(ident.getMfgName() + " " +ident.getModelName(), sb); 1217 } 1218 addToDescription(ident.getSoftwareVersion(), sb); 1219 String newDescription = sb.toString(); 1220 if (!description.equals(newDescription)) { 1221 description = newDescription; 1222 } 1223 } 1224 1225 private void addToDescription(String s, StringBuilder sb) { 1226 if (!s.isEmpty()) { 1227 sb.append(" - "); 1228 sb.append(s); 1229 } 1230 } 1231 1232 private long reorder(long n) { 1233 return (n < 0) ? Long.MAX_VALUE - n : Long.MIN_VALUE + n; 1234 } 1235 1236 @Override 1237 public int compareTo(NodeEntry otherEntry) { 1238 long l1 = reorder(getNodeID().toLong()); 1239 long l2 = reorder(otherEntry.getNodeID().toLong()); 1240 return Long.compare(l1, l2); 1241 } 1242 1243 @Override 1244 public String toString() { 1245 return description; 1246 } 1247 1248 @Override 1249 @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", 1250 justification = "Purposefully attempting lookup using NodeID argument in model " + 1251 "vector.") 1252 public boolean equals(Object o) { 1253 if (o instanceof NodeEntry) { 1254 return getNodeID().equals(((NodeEntry) o).getNodeID()); 1255 } 1256 if (o instanceof NodeID) { 1257 return getNodeID().equals(o); 1258 } 1259 return false; 1260 } 1261 1262 @Override 1263 public int hashCode() { 1264 return getNodeID().hashCode(); 1265 } 1266 1267 @Override 1268 public void propertyChange(PropertyChangeEvent propertyChangeEvent) { 1269 //log.warning("Received model entry update for " + nodeMemo.getNodeID()); 1270 if (propertyChangeEvent.getPropertyName().equals(UPDATE_PROP_SIMPLE_NODE_IDENT)) { 1271 updateDescription(); 1272 } 1273 } 1274 1275 public void dispose() { 1276 //log.warning("dispose of " + nodeMemo.getNodeID().toString()); 1277 nodeMemo.removePropertyChangeListener(this); 1278 } 1279 } 1280 1281 // -------------- load CDI data --------- 1282 1283 private void loadCdiData() { 1284 if (!replaceData()) { 1285 return; 1286 } 1287 1288 // Load data 1289 loadCdiInputs(); 1290 loadCdiOutputs(); 1291 loadCdiReceivers(); 1292 loadCdiTransmitters(); 1293 loadCdiGroups(); 1294 1295 for (GroupRow row : _groupList) { 1296 decode(row); 1297 } 1298 1299 setDirty(false); 1300 1301 _groupTable.setRowSelectionInterval(0, 0); 1302 1303 _groupTable.repaint(); 1304 1305 _exportButton.setEnabled(true); 1306 _refreshButton.setEnabled(true); 1307 _storeButton.setEnabled(true); 1308 _exportItem.setEnabled(true); 1309 _refreshItem.setEnabled(true); 1310 _storeItem.setEnabled(true); 1311 } 1312 1313 private void pushedRefreshButton(ActionEvent e) { 1314 loadCdiData(); 1315 } 1316 1317 private void loadCdiGroups() { 1318 for (int i = 0; i < 16; i++) { 1319 var groupRow = _groupList.get(i); 1320 groupRow.clearLogicList(); 1321 1322 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_NAME, i)); 1323 groupRow.setName(entry.getValue()); 1324 entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_MULTI_LINE, i)); 1325 groupRow.setMultiLine(entry.getValue()); 1326 } 1327 1328 _groupTable.revalidate(); 1329 } 1330 1331 private void loadCdiInputs() { 1332 for (int i = 0; i < 16; i++) { 1333 for (int j = 0; j < 8; j++) { 1334 var inputRow = _inputList.get((i * 8) + j); 1335 1336 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(INPUT_NAME, i, j)); 1337 inputRow.setName(entry.getValue()); 1338 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_TRUE, i, j)); 1339 inputRow.setEventTrue(event.getValue().toShortString()); 1340 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_FALSE, i, j)); 1341 inputRow.setEventFalse(event.getValue().toShortString()); 1342 } 1343 } 1344 _inputTable.revalidate(); 1345 } 1346 1347 private void loadCdiOutputs() { 1348 for (int i = 0; i < 16; i++) { 1349 for (int j = 0; j < 8; j++) { 1350 var outputRow = _outputList.get((i * 8) + j); 1351 1352 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(OUTPUT_NAME, i, j)); 1353 outputRow.setName(entry.getValue()); 1354 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_TRUE, i, j)); 1355 outputRow.setEventTrue(event.getValue().toShortString()); 1356 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_FALSE, i, j)); 1357 outputRow.setEventFalse(event.getValue().toShortString()); 1358 } 1359 } 1360 _outputTable.revalidate(); 1361 } 1362 1363 private void loadCdiReceivers() { 1364 for (int i = 0; i < 16; i++) { 1365 var receiverRow = _receiverList.get(i); 1366 1367 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(RECEIVER_NAME, i)); 1368 receiverRow.setName(entry.getValue()); 1369 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(RECEIVER_EVENT, i)); 1370 receiverRow.setEventId(event.getValue().toShortString()); 1371 } 1372 _receiverTable.revalidate(); 1373 } 1374 1375 private void loadCdiTransmitters() { 1376 for (int i = 0; i < 16; i++) { 1377 var transmitterRow = _transmitterList.get(i); 1378 1379 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_NAME, i)); 1380 transmitterRow.setName(entry.getValue()); 1381 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_EVENT, i)); 1382 transmitterRow.setEventId(event.getValue().toShortString()); 1383 } 1384 _transmitterTable.revalidate(); 1385 } 1386 1387 // -------------- store CDI data --------- 1388 1389 private void pushedStoreButton(ActionEvent e) { 1390 _csvMessages.clear(); 1391 _compileNeeded = false; 1392 _storeQueueLength.set(0); 1393 1394 // Store CDI data 1395 storeInputs(); 1396 storeOutputs(); 1397 storeReceivers(); 1398 storeTransmitters(); 1399 storeGroups(); 1400 1401 setDirty(false); 1402 } 1403 1404 private void storeGroups() { 1405 // store the group data 1406 int currentCount = 0; 1407 1408 for (int i = 0; i < 16; i++) { 1409 var row = _groupList.get(i); 1410 1411 // update the group line 1412 encode(row); 1413 1414 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_NAME, i)); 1415 if (!row.getName().equals(entry.getValue())) { 1416 entry.addPropertyChangeListener(_entryListener); 1417 entry.setValue(row.getName()); 1418 currentCount = _storeQueueLength.incrementAndGet(); 1419 } 1420 1421 entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_MULTI_LINE, i)); 1422 if (!row.getMultiLine().equals(entry.getValue())) { 1423 entry.addPropertyChangeListener(_entryListener); 1424 entry.setValue(row.getMultiLine()); 1425 currentCount = _storeQueueLength.incrementAndGet(); 1426 _compileNeeded = true; 1427 } 1428 1429 log.debug("Group: {}", row.getName()); 1430 log.debug("Logic: {}", row.getMultiLine()); 1431 } 1432 log.debug("storeGroups count = {}", currentCount); 1433 } 1434 1435 private void storeInputs() { 1436 int currentCount = 0; 1437 1438 for (int i = 0; i < 16; i++) { 1439 for (int j = 0; j < 8; j++) { 1440 var row = _inputList.get((i * 8) + j); 1441 1442 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(INPUT_NAME, i, j)); 1443 if (!row.getName().equals(entry.getValue())) { 1444 entry.addPropertyChangeListener(_entryListener); 1445 entry.setValue(row.getName()); 1446 currentCount = _storeQueueLength.incrementAndGet(); 1447 } 1448 1449 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_TRUE, i, j)); 1450 if (!row.getEventTrue().equals(event.getValue().toShortString())) { 1451 event.addPropertyChangeListener(_entryListener); 1452 event.setValue(new EventID(row.getEventTrue())); 1453 currentCount = _storeQueueLength.incrementAndGet(); 1454 } 1455 1456 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_FALSE, i, j)); 1457 if (!row.getEventFalse().equals(event.getValue().toShortString())) { 1458 event.addPropertyChangeListener(_entryListener); 1459 event.setValue(new EventID(row.getEventFalse())); 1460 currentCount = _storeQueueLength.incrementAndGet(); 1461 } 1462 } 1463 } 1464 log.debug("storeInputs count = {}", currentCount); 1465 } 1466 1467 private void storeOutputs() { 1468 int currentCount = 0; 1469 1470 for (int i = 0; i < 16; i++) { 1471 for (int j = 0; j < 8; j++) { 1472 var row = _outputList.get((i * 8) + j); 1473 1474 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(OUTPUT_NAME, i, j)); 1475 if (!row.getName().equals(entry.getValue())) { 1476 entry.addPropertyChangeListener(_entryListener); 1477 entry.setValue(row.getName()); 1478 currentCount = _storeQueueLength.incrementAndGet(); 1479 } 1480 1481 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_TRUE, i, j)); 1482 if (!row.getEventTrue().equals(event.getValue().toShortString())) { 1483 event.addPropertyChangeListener(_entryListener); 1484 event.setValue(new EventID(row.getEventTrue())); 1485 currentCount = _storeQueueLength.incrementAndGet(); 1486 } 1487 1488 event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_FALSE, i, j)); 1489 if (!row.getEventFalse().equals(event.getValue().toShortString())) { 1490 event.addPropertyChangeListener(_entryListener); 1491 event.setValue(new EventID(row.getEventFalse())); 1492 currentCount = _storeQueueLength.incrementAndGet(); 1493 } 1494 } 1495 } 1496 log.debug("storeOutputs count = {}", currentCount); 1497 } 1498 1499 private void storeReceivers() { 1500 int currentCount = 0; 1501 1502 for (int i = 0; i < 16; i++) { 1503 var row = _receiverList.get(i); 1504 1505 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(RECEIVER_NAME, i)); 1506 if (!row.getName().equals(entry.getValue())) { 1507 entry.addPropertyChangeListener(_entryListener); 1508 entry.setValue(row.getName()); 1509 currentCount = _storeQueueLength.incrementAndGet(); 1510 } 1511 1512 var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(RECEIVER_EVENT, i)); 1513 if (!row.getEventId().equals(event.getValue().toShortString())) { 1514 event.addPropertyChangeListener(_entryListener); 1515 event.setValue(new EventID(row.getEventId())); 1516 currentCount = _storeQueueLength.incrementAndGet(); 1517 } 1518 } 1519 log.debug("storeReceivers count = {}", currentCount); 1520 } 1521 1522 private void storeTransmitters() { 1523 int currentCount = 0; 1524 1525 for (int i = 0; i < 16; i++) { 1526 var row = _transmitterList.get(i); 1527 1528 var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_NAME, i)); 1529 if (!row.getName().equals(entry.getValue())) { 1530 entry.addPropertyChangeListener(_entryListener); 1531 entry.setValue(row.getName()); 1532 currentCount = _storeQueueLength.incrementAndGet(); 1533 } 1534 } 1535 log.debug("storeTransmitters count = {}", currentCount); 1536 } 1537 1538 // -------------- Backup Import --------- 1539 1540 private void loadBackupData(ActionEvent m) { 1541 if (!replaceData()) { 1542 return; 1543 } 1544 1545 var fileChooser = new JmriJFileChooser(FileUtil.getUserFilesPath()); 1546 fileChooser.setApproveButtonText(Bundle.getMessage("LoadCdiButton")); 1547 fileChooser.setDialogTitle(Bundle.getMessage("LoadCdiTitle")); 1548 var filter = new FileNameExtensionFilter(Bundle.getMessage("LoadCdiFilter"), "txt"); 1549 fileChooser.addChoosableFileFilter(filter); 1550 fileChooser.setFileFilter(filter); 1551 1552 int response = fileChooser.showOpenDialog(this); 1553 if (response == JFileChooser.CANCEL_OPTION) { 1554 return; 1555 } 1556 1557 List<String> lines = null; 1558 try { 1559 lines = Files.readAllLines(Paths.get(fileChooser.getSelectedFile().getAbsolutePath())); 1560 } catch (IOException e) { 1561 log.error("Failed to load file.", e); 1562 return; 1563 } 1564 1565 for (int i = 0; i < lines.size(); i++) { 1566 if (lines.get(i).startsWith("Logic Inputs.Group")) { 1567 loadBackupInputs(i, lines); 1568 i += 128 * 3; 1569 } 1570 1571 if (lines.get(i).startsWith("Logic Outputs.Group")) { 1572 loadBackupOutputs(i, lines); 1573 i += 128 * 3; 1574 } 1575 if (lines.get(i).startsWith("Track Receivers")) { 1576 loadBackupReceivers(i, lines); 1577 i += 16 * 2; 1578 } 1579 if (lines.get(i).startsWith("Track Transmitters")) { 1580 loadBackupTransmitters(i, lines); 1581 i += 16 * 2; 1582 } 1583 if (lines.get(i).startsWith("Conditionals.Logic")) { 1584 loadBackupGroups(i, lines); 1585 i += 16 * 2; 1586 } 1587 } 1588 1589 for (GroupRow row : _groupList) { 1590 decode(row); 1591 } 1592 1593 setDirty(false); 1594 _groupTable.setRowSelectionInterval(0, 0); 1595 _groupTable.repaint(); 1596 1597 _exportButton.setEnabled(true); 1598 _exportItem.setEnabled(true); 1599 } 1600 1601 private String getLineValue(String line) { 1602 if (line.endsWith("=")) { 1603 return ""; 1604 } 1605 int index = line.indexOf("="); 1606 var newLine = line.substring(index + 1); 1607 newLine = Util.unescapeString(newLine); 1608 return newLine; 1609 } 1610 1611 private void loadBackupInputs(int index, List<String> lines) { 1612 for (int i = 0; i < 128; i++) { 1613 var inputRow = _inputList.get(i); 1614 1615 inputRow.setName(getLineValue(lines.get(index))); 1616 inputRow.setEventTrue(getLineValue(lines.get(index + 1))); 1617 inputRow.setEventFalse(getLineValue(lines.get(index + 2))); 1618 index += 3; 1619 } 1620 1621 _inputTable.revalidate(); 1622 } 1623 1624 private void loadBackupOutputs(int index, List<String> lines) { 1625 for (int i = 0; i < 128; i++) { 1626 var outputRow = _outputList.get(i); 1627 1628 outputRow.setName(getLineValue(lines.get(index))); 1629 outputRow.setEventTrue(getLineValue(lines.get(index + 1))); 1630 outputRow.setEventFalse(getLineValue(lines.get(index + 2))); 1631 index += 3; 1632 } 1633 1634 _outputTable.revalidate(); 1635 } 1636 1637 private void loadBackupReceivers(int index, List<String> lines) { 1638 for (int i = 0; i < 16; i++) { 1639 var receiverRow = _receiverList.get(i); 1640 1641 receiverRow.setName(getLineValue(lines.get(index))); 1642 receiverRow.setEventId(getLineValue(lines.get(index + 1))); 1643 index += 2; 1644 } 1645 1646 _receiverTable.revalidate(); 1647 } 1648 1649 private void loadBackupTransmitters(int index, List<String> lines) { 1650 for (int i = 0; i < 16; i++) { 1651 var transmitterRow = _transmitterList.get(i); 1652 1653 transmitterRow.setName(getLineValue(lines.get(index))); 1654 transmitterRow.setEventId(getLineValue(lines.get(index + 1))); 1655 index += 2; 1656 } 1657 1658 _transmitterTable.revalidate(); 1659 } 1660 1661 private void loadBackupGroups(int index, List<String> lines) { 1662 for (int i = 0; i < 16; i++) { 1663 var groupRow = _groupList.get(i); 1664 groupRow.clearLogicList(); 1665 1666 groupRow.setName(getLineValue(lines.get(index))); 1667 groupRow.setMultiLine(getLineValue(lines.get(index + 1))); 1668 index += 2; 1669 } 1670 1671 _groupTable.revalidate(); 1672 _logicTable.revalidate(); 1673 } 1674 1675 // -------------- CSV Import --------- 1676 1677 private void pushedImportButton(ActionEvent e) { 1678 if (!replaceData()) { 1679 return; 1680 } 1681 1682 if (!setCsvDirectoryPath(true)) { 1683 return; 1684 } 1685 1686 _csvMessages.clear(); 1687 importCsvData(); 1688 setDirty(false); 1689 1690 _exportButton.setEnabled(true); 1691 _exportItem.setEnabled(true); 1692 1693 if (!_csvMessages.isEmpty()) { 1694 JmriJOptionPane.showMessageDialog(this, 1695 String.join("\n", _csvMessages), 1696 Bundle.getMessage("TitleCsvImport"), 1697 JmriJOptionPane.ERROR_MESSAGE); 1698 } 1699 } 1700 1701 private void importCsvData() { 1702 importGroupLogic(); 1703 importInputs(); 1704 importOutputs(); 1705 importReceivers(); 1706 importTransmitters(); 1707 1708 _groupTable.setRowSelectionInterval(0, 0); 1709 1710 _groupTable.repaint(); 1711 } 1712 1713 private void importGroupLogic() { 1714 List<CSVRecord> records = getCsvRecords("group_logic.csv"); 1715 if (records.isEmpty()) { 1716 return; 1717 } 1718 1719 var skipHeader = true; 1720 int groupNumber = -1; 1721 for (CSVRecord record : records) { 1722 if (skipHeader) { 1723 skipHeader = false; 1724 continue; 1725 } 1726 1727 List<String> values = new ArrayList<>(); 1728 record.forEach(values::add); 1729 1730 if (values.size() == 1) { 1731 // Create Group 1732 groupNumber++; 1733 var groupRow = _groupList.get(groupNumber); 1734 groupRow.setName(values.get(0)); 1735 groupRow.setMultiLine(""); 1736 groupRow.clearLogicList(); 1737 } else if (values.size() == 5) { 1738 var oper = getEnum(values.get(2)); 1739 var logicRow = new LogicRow(values.get(1), oper, values.get(3), values.get(4)); 1740 _groupList.get(groupNumber).getLogicList().add(logicRow); 1741 } else { 1742 _csvMessages.add(Bundle.getMessage("ImportGroupError", record.toString())); 1743 } 1744 } 1745 1746 _groupTable.revalidate(); 1747 _logicTable.revalidate(); 1748 } 1749 1750 private void importInputs() { 1751 List<CSVRecord> records = getCsvRecords("inputs.csv"); 1752 if (records.isEmpty()) { 1753 return; 1754 } 1755 1756 for (int i = 0; i < 129; i++) { 1757 if (i == 0) { 1758 continue; 1759 } 1760 1761 var record = records.get(i); 1762 List<String> values = new ArrayList<>(); 1763 record.forEach(values::add); 1764 1765 if (values.size() == 4) { 1766 var inputRow = _inputList.get(i - 1); 1767 inputRow.setName(values.get(1)); 1768 inputRow.setEventTrue(values.get(2)); 1769 inputRow.setEventFalse(values.get(3)); 1770 } else { 1771 _csvMessages.add(Bundle.getMessage("ImportInputError", record.toString())); 1772 } 1773 } 1774 1775 _inputTable.revalidate(); 1776 } 1777 1778 private void importOutputs() { 1779 List<CSVRecord> records = getCsvRecords("outputs.csv"); 1780 if (records.isEmpty()) { 1781 return; 1782 } 1783 1784 for (int i = 0; i < 129; i++) { 1785 if (i == 0) { 1786 continue; 1787 } 1788 1789 var record = records.get(i); 1790 List<String> values = new ArrayList<>(); 1791 record.forEach(values::add); 1792 1793 if (values.size() == 4) { 1794 var outputRow = _outputList.get(i - 1); 1795 outputRow.setName(values.get(1)); 1796 outputRow.setEventTrue(values.get(2)); 1797 outputRow.setEventFalse(values.get(3)); 1798 } else { 1799 _csvMessages.add(Bundle.getMessage("ImportOuputError", record.toString())); 1800 } 1801 } 1802 1803 _outputTable.revalidate(); 1804 } 1805 1806 private void importReceivers() { 1807 List<CSVRecord> records = getCsvRecords("receivers.csv"); 1808 if (records.isEmpty()) { 1809 return; 1810 } 1811 1812 for (int i = 0; i < 17; i++) { 1813 if (i == 0) { 1814 continue; 1815 } 1816 1817 var record = records.get(i); 1818 List<String> values = new ArrayList<>(); 1819 record.forEach(values::add); 1820 1821 if (values.size() == 3) { 1822 var receiverRow = _receiverList.get(i - 1); 1823 receiverRow.setName(values.get(1)); 1824 receiverRow.setEventId(values.get(2)); 1825 } else { 1826 _csvMessages.add(Bundle.getMessage("ImportReceiverError", record.toString())); 1827 } 1828 } 1829 1830 _receiverTable.revalidate(); 1831 } 1832 1833 private void importTransmitters() { 1834 List<CSVRecord> records = getCsvRecords("transmitters.csv"); 1835 if (records.isEmpty()) { 1836 return; 1837 } 1838 1839 for (int i = 0; i < 17; i++) { 1840 if (i == 0) { 1841 continue; 1842 } 1843 1844 var record = records.get(i); 1845 List<String> values = new ArrayList<>(); 1846 record.forEach(values::add); 1847 1848 if (values.size() == 3) { 1849 var transmitterRow = _transmitterList.get(i - 1); 1850 transmitterRow.setName(values.get(1)); 1851 transmitterRow.setEventId(values.get(2)); 1852 } else { 1853 _csvMessages.add(Bundle.getMessage("ImportTransmitterError", record.toString())); 1854 } 1855 } 1856 1857 _transmitterTable.revalidate(); 1858 } 1859 1860 private List<CSVRecord> getCsvRecords(String fileName) { 1861 var recordList = new ArrayList<CSVRecord>(); 1862 FileReader fileReader; 1863 try { 1864 fileReader = new FileReader(_csvDirectoryPath + fileName); 1865 } catch (FileNotFoundException ex) { 1866 _csvMessages.add(Bundle.getMessage("ImportFileNotFound", fileName)); 1867 return recordList; 1868 } 1869 1870 BufferedReader bufferedReader; 1871 CSVParser csvFile; 1872 1873 try { 1874 bufferedReader = new BufferedReader(fileReader); 1875 csvFile = new CSVParser(bufferedReader, CSVFormat.DEFAULT); 1876 recordList.addAll(csvFile.getRecords()); 1877 csvFile.close(); 1878 bufferedReader.close(); 1879 fileReader.close(); 1880 } catch (IOException iox) { 1881 _csvMessages.add(Bundle.getMessage("ImportFileIOError", iox.getMessage(), fileName)); 1882 } 1883 1884 return recordList; 1885 } 1886 1887 // -------------- CSV Export --------- 1888 1889 private void pushedExportButton(ActionEvent e) { 1890 if (!setCsvDirectoryPath(false)) { 1891 return; 1892 } 1893 1894 _csvMessages.clear(); 1895 exportCsvData(); 1896 setDirty(false); 1897 1898 _csvMessages.add(Bundle.getMessage("ExportDone")); 1899 var msgType = JmriJOptionPane.ERROR_MESSAGE; 1900 if (_csvMessages.size() == 1) { 1901 msgType = JmriJOptionPane.INFORMATION_MESSAGE; 1902 } 1903 JmriJOptionPane.showMessageDialog(this, 1904 String.join("\n", _csvMessages), 1905 Bundle.getMessage("TitleCsvExport"), 1906 msgType); 1907 } 1908 1909 private void exportCsvData() { 1910 try { 1911 exportGroupLogic(); 1912 exportInputs(); 1913 exportOutputs(); 1914 exportReceivers(); 1915 exportTransmitters(); 1916 } catch (IOException ex) { 1917 _csvMessages.add(Bundle.getMessage("ExportIOError", ex.getMessage())); 1918 } 1919 1920 } 1921 1922 private void exportGroupLogic() throws IOException { 1923 var fileWriter = new FileWriter(_csvDirectoryPath + "group_logic.csv"); 1924 var bufferedWriter = new BufferedWriter(fileWriter); 1925 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 1926 1927 csvFile.printRecord(Bundle.getMessage("GroupName"), Bundle.getMessage("ColumnLabel"), 1928 Bundle.getMessage("ColumnOper"), Bundle.getMessage("ColumnName"), Bundle.getMessage("ColumnComment")); 1929 1930 for (int i = 0; i < 16; i++) { 1931 var row = _groupList.get(i); 1932 var groupName = row.getName(); 1933 csvFile.printRecord(groupName); 1934 var logicRow = row.getLogicList(); 1935 for (LogicRow logic : logicRow) { 1936 var operName = logic.getOperName(); 1937 csvFile.printRecord("", logic.getLabel(), operName, logic.getName(), logic.getComment()); 1938 } 1939 } 1940 1941 // Flush the write buffer and close the file 1942 csvFile.flush(); 1943 csvFile.close(); 1944 } 1945 1946 private void exportInputs() throws IOException { 1947 var fileWriter = new FileWriter(_csvDirectoryPath + "inputs.csv"); 1948 var bufferedWriter = new BufferedWriter(fileWriter); 1949 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 1950 1951 csvFile.printRecord(Bundle.getMessage("ColumnInput"), Bundle.getMessage("ColumnName"), 1952 Bundle.getMessage("ColumnTrue"), Bundle.getMessage("ColumnFalse")); 1953 1954 for (int i = 0; i < 16; i++) { 1955 for (int j = 0; j < 8; j++) { 1956 var variable = "I" + i + "." + j; 1957 var row = _inputList.get((i * 8) + j); 1958 csvFile.printRecord(variable, row.getName(), row.getEventTrue(), row.getEventFalse()); 1959 } 1960 } 1961 1962 // Flush the write buffer and close the file 1963 csvFile.flush(); 1964 csvFile.close(); 1965 } 1966 1967 private void exportOutputs() throws IOException { 1968 var fileWriter = new FileWriter(_csvDirectoryPath + "outputs.csv"); 1969 var bufferedWriter = new BufferedWriter(fileWriter); 1970 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 1971 1972 csvFile.printRecord(Bundle.getMessage("ColumnOutput"), Bundle.getMessage("ColumnName"), 1973 Bundle.getMessage("ColumnTrue"), Bundle.getMessage("ColumnFalse")); 1974 1975 for (int i = 0; i < 16; i++) { 1976 for (int j = 0; j < 8; j++) { 1977 var variable = "Q" + i + "." + j; 1978 var row = _outputList.get((i * 8) + j); 1979 csvFile.printRecord(variable, row.getName(), row.getEventTrue(), row.getEventFalse()); 1980 } 1981 } 1982 1983 // Flush the write buffer and close the file 1984 csvFile.flush(); 1985 csvFile.close(); 1986 } 1987 1988 private void exportReceivers() throws IOException { 1989 var fileWriter = new FileWriter(_csvDirectoryPath + "receivers.csv"); 1990 var bufferedWriter = new BufferedWriter(fileWriter); 1991 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 1992 1993 csvFile.printRecord(Bundle.getMessage("ColumnCircuit"), Bundle.getMessage("ColumnName"), 1994 Bundle.getMessage("ColumnEventID")); 1995 1996 for (int i = 0; i < 16; i++) { 1997 var variable = "Y" + i; 1998 var row = _receiverList.get(i); 1999 csvFile.printRecord(variable, row.getName(), row.getEventId()); 2000 } 2001 2002 // Flush the write buffer and close the file 2003 csvFile.flush(); 2004 csvFile.close(); 2005 } 2006 2007 private void exportTransmitters() throws IOException { 2008 var fileWriter = new FileWriter(_csvDirectoryPath + "transmitters.csv"); 2009 var bufferedWriter = new BufferedWriter(fileWriter); 2010 var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT); 2011 2012 csvFile.printRecord(Bundle.getMessage("ColumnCircuit"), Bundle.getMessage("ColumnName"), 2013 Bundle.getMessage("ColumnEventID")); 2014 2015 for (int i = 0; i < 16; i++) { 2016 var variable = "Z" + i; 2017 var row = _transmitterList.get(i); 2018 csvFile.printRecord(variable, row.getName(), row.getEventId()); 2019 } 2020 2021 // Flush the write buffer and close the file 2022 csvFile.flush(); 2023 csvFile.close(); 2024 } 2025 2026 /** 2027 * Select the directory that will be used for the CSV file set. 2028 * @param isOpen - True for CSV Import and false for CSV Export. 2029 * @return true if a directory was selected. 2030 */ 2031 private boolean setCsvDirectoryPath(boolean isOpen) { 2032 var directoryChooser = new JmriJFileChooser(FileUtil.getUserFilesPath()); 2033 directoryChooser.setApproveButtonText(Bundle.getMessage("SelectCsvButton")); 2034 directoryChooser.setDialogTitle(Bundle.getMessage("SelectCsvTitle")); 2035 directoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 2036 2037 int response = 0; 2038 if (isOpen) { 2039 response = directoryChooser.showOpenDialog(this); 2040 } else { 2041 response = directoryChooser.showSaveDialog(this); 2042 } 2043 if (response != JFileChooser.APPROVE_OPTION) { 2044 return false; 2045 } 2046 _csvDirectoryPath = directoryChooser.getSelectedFile().getAbsolutePath() + FileUtil.SEPARATOR; 2047 2048 return true; 2049 } 2050 2051 // -------------- Data Utilities --------- 2052 2053 private void setDirty(boolean dirty) { 2054 _dirty = dirty; 2055 } 2056 2057 private boolean isDirty() { 2058 return _dirty; 2059 } 2060 2061 private boolean replaceData() { 2062 if (isDirty()) { 2063 int response = JmriJOptionPane.showConfirmDialog(this, 2064 Bundle.getMessage("MessageRevert"), 2065 Bundle.getMessage("TitleRevert"), 2066 JmriJOptionPane.YES_NO_OPTION); 2067 if (response != JmriJOptionPane.YES_OPTION) { 2068 return false; 2069 } 2070 } 2071 return true; 2072 } 2073 2074 private void warningDialog(String title, String message) { 2075 JmriJOptionPane.showMessageDialog(this, 2076 message, 2077 title, 2078 JmriJOptionPane.WARNING_MESSAGE); 2079 } 2080 2081 // -------------- Data validation --------- 2082 2083 static boolean isLabelValid(String label) { 2084 if (label.isEmpty()) { 2085 return true; 2086 } 2087 2088 var match = PARSE_LABEL.matcher(label); 2089 if (match.find()) { 2090 return true; 2091 } 2092 2093 JmriJOptionPane.showMessageDialog(null, 2094 Bundle.getMessage("MessageLabel", label), 2095 Bundle.getMessage("TitleLabel"), 2096 JmriJOptionPane.ERROR_MESSAGE); 2097 return false; 2098 } 2099 2100 static boolean isEventValid(String event) { 2101 var valid = true; 2102 2103 if (event.isEmpty()) { 2104 return valid; 2105 } 2106 2107 var hexPairs = event.split("\\."); 2108 if (hexPairs.length != 8) { 2109 valid = false; 2110 } else { 2111 for (int i = 0; i < 8; i++) { 2112 var match = PARSE_HEXPAIR.matcher(hexPairs[i]); 2113 if (!match.find()) { 2114 valid = false; 2115 break; 2116 } 2117 } 2118 } 2119 2120 if (!valid) { 2121 JmriJOptionPane.showMessageDialog(null, 2122 Bundle.getMessage("MessageEvent", event), 2123 Bundle.getMessage("TitleEvent"), 2124 JmriJOptionPane.ERROR_MESSAGE); 2125 log.error("bad event: {}", event); 2126 } 2127 2128 return valid; 2129 } 2130 2131 // -------------- table lists --------- 2132 2133 /** 2134 * The Group row contains the name and the raw data for one of the 16 groups. 2135 * It also contains the decoded logic for the group in the logic list. 2136 */ 2137 static class GroupRow { 2138 String _name; 2139 String _multiLine = ""; 2140 List<LogicRow> _logicList = new ArrayList<>(); 2141 2142 2143 GroupRow(String name) { 2144 _name = name; 2145 } 2146 2147 String getName() { 2148 return _name; 2149 } 2150 2151 void setName(String newName) { 2152 _name = newName; 2153 } 2154 2155 List<LogicRow> getLogicList() { 2156 return _logicList; 2157 } 2158 2159 void setLogicList(List<LogicRow> logicList) { 2160 _logicList.clear(); 2161 _logicList.addAll(logicList); 2162 } 2163 2164 void clearLogicList() { 2165 _logicList.clear(); 2166 } 2167 2168 String getMultiLine() { 2169 return _multiLine; 2170 } 2171 2172 void setMultiLine(String newMultiLine) { 2173 _multiLine = newMultiLine.strip(); 2174 } 2175 2176 String getSize() { 2177 int size = (_multiLine.length() * 100) / 255; 2178 return String.valueOf(size) + "%"; 2179 } 2180 } 2181 2182 /** 2183 * The definition of a logic row 2184 */ 2185 static class LogicRow { 2186 String _label; 2187 Operator _oper; 2188 String _name; 2189 String _comment; 2190 2191 LogicRow(String label, Operator oper, String name, String comment) { 2192 _label = label; 2193 _oper = oper; 2194 _name = name; 2195 _comment = comment; 2196 } 2197 2198 String getLabel() { 2199 return _label; 2200 } 2201 2202 void setLabel(String newLabel) { 2203 var label = newLabel.trim(); 2204 if (isLabelValid(label)) { 2205 _label = label; 2206 } 2207 } 2208 2209 Operator getOper() { 2210 return _oper; 2211 } 2212 2213 String getOperName() { 2214 if (_oper == null) { 2215 return ""; 2216 } 2217 2218 String operName = _oper.name(); 2219 2220 // Fix special enums 2221 if (operName.equals("Cp")) { 2222 operName = ")"; 2223 } else if (operName.equals("EQ")) { 2224 operName = "="; 2225 } else if (operName.contains("p")) { 2226 operName = operName.replace("p", "("); 2227 } 2228 2229 return operName; 2230 } 2231 2232 void setOper(Operator newOper) { 2233 _oper = newOper; 2234 } 2235 2236 String getName() { 2237 return _name; 2238 } 2239 2240 void setName(String newName) { 2241 _name = newName.trim(); 2242 } 2243 2244 String getComment() { 2245 return _comment; 2246 } 2247 2248 void setComment(String newComment) { 2249 _comment = newComment; 2250 } 2251 } 2252 2253 /** 2254 * The name and assigned true and false events for an Input. 2255 */ 2256 static class InputRow { 2257 String _name; 2258 String _eventTrue; 2259 String _eventFalse; 2260 2261 InputRow(String name, String eventTrue, String eventFalse) { 2262 _name = name; 2263 _eventTrue = eventTrue; 2264 _eventFalse = eventFalse; 2265 } 2266 2267 String getName() { 2268 return _name; 2269 } 2270 2271 void setName(String newName) { 2272 _name = newName.trim(); 2273 } 2274 2275 String getEventTrue() { 2276 if (_eventTrue.length() == 0) return "00.00.00.00.00.00.00.00"; 2277 return _eventTrue; 2278 } 2279 2280 void setEventTrue(String newEventTrue) { 2281 var event = newEventTrue.trim(); 2282 if (isEventValid(event)) { 2283 _eventTrue = event; 2284 } 2285 } 2286 2287 String getEventFalse() { 2288 if (_eventFalse.length() == 0) return "00.00.00.00.00.00.00.00"; 2289 return _eventFalse; 2290 } 2291 2292 void setEventFalse(String newEventFalse) { 2293 var event = newEventFalse.trim(); 2294 if (isEventValid(event)) { 2295 _eventFalse = event; 2296 } 2297 } 2298 } 2299 2300 /** 2301 * The name and assigned true and false events for an Output. 2302 */ 2303 static class OutputRow { 2304 String _name; 2305 String _eventTrue; 2306 String _eventFalse; 2307 2308 OutputRow(String name, String eventTrue, String eventFalse) { 2309 _name = name; 2310 _eventTrue = eventTrue; 2311 _eventFalse = eventFalse; 2312 } 2313 2314 String getName() { 2315 return _name; 2316 } 2317 2318 void setName(String newName) { 2319 _name = newName.trim(); 2320 } 2321 2322 String getEventTrue() { 2323 if (_eventTrue.length() == 0) return "00.00.00.00.00.00.00.00"; 2324 return _eventTrue; 2325 } 2326 2327 void setEventTrue(String newEventTrue) { 2328 var event = newEventTrue.trim(); 2329 if (isEventValid(event)) { 2330 _eventTrue = event; 2331 } 2332 } 2333 2334 String getEventFalse() { 2335 if (_eventFalse.length() == 0) return "00.00.00.00.00.00.00.00"; 2336 return _eventFalse; 2337 } 2338 2339 void setEventFalse(String newEventFalse) { 2340 var event = newEventFalse.trim(); 2341 if (isEventValid(event)) { 2342 _eventFalse = event; 2343 } 2344 } 2345 } 2346 2347 /** 2348 * The name and assigned event id for a circuit receiver. 2349 */ 2350 static class ReceiverRow { 2351 String _name; 2352 String _eventid; 2353 2354 ReceiverRow(String name, String eventid) { 2355 _name = name; 2356 _eventid = eventid; 2357 } 2358 2359 String getName() { 2360 return _name; 2361 } 2362 2363 void setName(String newName) { 2364 _name = newName.trim(); 2365 } 2366 2367 String getEventId() { 2368 if (_eventid.length() == 0) return "00.00.00.00.00.00.00.00"; 2369 return _eventid; 2370 } 2371 2372 void setEventId(String newEventid) { 2373 var event = newEventid.trim(); 2374 if (isEventValid(event)) { 2375 _eventid = event; 2376 } 2377 } 2378 } 2379 2380 /** 2381 * The name and assigned event id for a circuit transmitter. 2382 */ 2383 static class TransmitterRow { 2384 String _name; 2385 String _eventid; 2386 2387 TransmitterRow(String name, String eventid) { 2388 _name = name; 2389 _eventid = eventid; 2390 } 2391 2392 String getName() { 2393 return _name; 2394 } 2395 2396 void setName(String newName) { 2397 _name = newName.trim(); 2398 } 2399 2400 String getEventId() { 2401 if (_eventid.length() == 0) return "00.00.00.00.00.00.00.00"; 2402 return _eventid; 2403 } 2404 2405 void setEventId(String newEventid) { 2406 var event = newEventid.trim(); 2407 if (isEventValid(event)) { 2408 _eventid = event; 2409 } 2410 } 2411 } 2412 2413 // -------------- table models --------- 2414 2415 /** 2416 * TableModel for Group table entries. 2417 */ 2418 class GroupModel extends AbstractTableModel { 2419 2420 GroupModel() { 2421 } 2422 2423 public static final int ROW_COLUMN = 0; 2424 public static final int NAME_COLUMN = 1; 2425 2426 @Override 2427 public int getRowCount() { 2428 return _groupList.size(); 2429 } 2430 2431 @Override 2432 public int getColumnCount() { 2433 return 2; 2434 } 2435 2436 @Override 2437 public Class<?> getColumnClass(int c) { 2438 return String.class; 2439 } 2440 2441 @Override 2442 public String getColumnName(int col) { 2443 switch (col) { 2444 case ROW_COLUMN: 2445 return ""; 2446 case NAME_COLUMN: 2447 return Bundle.getMessage("ColumnName"); 2448 default: 2449 return "unknown"; // NOI18N 2450 } 2451 } 2452 2453 @Override 2454 public Object getValueAt(int r, int c) { 2455 switch (c) { 2456 case ROW_COLUMN: 2457 return r + 1; 2458 case NAME_COLUMN: 2459 return _groupList.get(r).getName(); 2460 default: 2461 return null; 2462 } 2463 } 2464 2465 @Override 2466 public void setValueAt(Object type, int r, int c) { 2467 switch (c) { 2468 case NAME_COLUMN: 2469 _groupList.get(r).setName((String) type); 2470 setDirty(true); 2471 break; 2472 default: 2473 break; 2474 } 2475 } 2476 2477 @Override 2478 public boolean isCellEditable(int r, int c) { 2479 return (c == NAME_COLUMN); 2480 } 2481 2482 public int getPreferredWidth(int col) { 2483 switch (col) { 2484 case ROW_COLUMN: 2485 return new JTextField(4).getPreferredSize().width; 2486 case NAME_COLUMN: 2487 return new JTextField(20).getPreferredSize().width; 2488 default: 2489 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2490 return new JTextField(8).getPreferredSize().width; 2491 } 2492 } 2493 } 2494 2495 /** 2496 * TableModel for STL table entries. 2497 */ 2498 class LogicModel extends AbstractTableModel { 2499 2500 LogicModel() { 2501 } 2502 2503 public static final int LABEL_COLUMN = 0; 2504 public static final int OPER_COLUMN = 1; 2505 public static final int NAME_COLUMN = 2; 2506 public static final int COMMENT_COLUMN = 3; 2507 2508 @Override 2509 public int getRowCount() { 2510 var logicList = _groupList.get(_groupRow).getLogicList(); 2511 return logicList.size(); 2512 } 2513 2514 @Override 2515 public int getColumnCount() { 2516 return 4; 2517 } 2518 2519 @Override 2520 public Class<?> getColumnClass(int c) { 2521 if (c == OPER_COLUMN) return JComboBox.class; 2522 return String.class; 2523 } 2524 2525 @Override 2526 public String getColumnName(int col) { 2527 switch (col) { 2528 case LABEL_COLUMN: 2529 return Bundle.getMessage("ColumnLabel"); // NOI18N 2530 case OPER_COLUMN: 2531 return Bundle.getMessage("ColumnOper"); // NOI18N 2532 case NAME_COLUMN: 2533 return Bundle.getMessage("ColumnName"); // NOI18N 2534 case COMMENT_COLUMN: 2535 return Bundle.getMessage("ColumnComment"); // NOI18N 2536 default: 2537 return "unknown"; // NOI18N 2538 } 2539 } 2540 2541 @Override 2542 public Object getValueAt(int r, int c) { 2543 var logicList = _groupList.get(_groupRow).getLogicList(); 2544 switch (c) { 2545 case LABEL_COLUMN: 2546 return logicList.get(r).getLabel(); 2547 case OPER_COLUMN: 2548 return logicList.get(r).getOper(); 2549 case NAME_COLUMN: 2550 return logicList.get(r).getName(); 2551 case COMMENT_COLUMN: 2552 return logicList.get(r).getComment(); 2553 default: 2554 return null; 2555 } 2556 } 2557 2558 @Override 2559 public void setValueAt(Object type, int r, int c) { 2560 var logicList = _groupList.get(_groupRow).getLogicList(); 2561 switch (c) { 2562 case LABEL_COLUMN: 2563 logicList.get(r).setLabel((String) type); 2564 setDirty(true); 2565 break; 2566 case OPER_COLUMN: 2567 var z = (Operator) type; 2568 if (z != null) { 2569 if (z.name().startsWith("z")) { 2570 return; 2571 } 2572 if (z.name().equals("x0")) { 2573 logicList.get(r).setOper(null); 2574 return; 2575 } 2576 } 2577 logicList.get(r).setOper((Operator) type); 2578 setDirty(true); 2579 break; 2580 case NAME_COLUMN: 2581 logicList.get(r).setName((String) type); 2582 setDirty(true); 2583 break; 2584 case COMMENT_COLUMN: 2585 logicList.get(r).setComment((String) type); 2586 setDirty(true); 2587 break; 2588 default: 2589 break; 2590 } 2591 } 2592 2593 @Override 2594 public boolean isCellEditable(int r, int c) { 2595 return true; 2596 } 2597 2598 public int getPreferredWidth(int col) { 2599 switch (col) { 2600 case LABEL_COLUMN: 2601 return new JTextField(6).getPreferredSize().width; 2602 case OPER_COLUMN: 2603 return new JTextField(20).getPreferredSize().width; 2604 case NAME_COLUMN: 2605 case COMMENT_COLUMN: 2606 return new JTextField(40).getPreferredSize().width; 2607 default: 2608 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2609 return new JTextField(8).getPreferredSize().width; 2610 } 2611 } 2612 } 2613 2614 /** 2615 * TableModel for Input table entries. 2616 */ 2617 class InputModel extends AbstractTableModel { 2618 2619 InputModel() { 2620 } 2621 2622 public static final int INPUT_COLUMN = 0; 2623 public static final int NAME_COLUMN = 1; 2624 public static final int TRUE_COLUMN = 2; 2625 public static final int FALSE_COLUMN = 3; 2626 2627 @Override 2628 public int getRowCount() { 2629 return _inputList.size(); 2630 } 2631 2632 @Override 2633 public int getColumnCount() { 2634 return 4; 2635 } 2636 2637 @Override 2638 public Class<?> getColumnClass(int c) { 2639 return String.class; 2640 } 2641 2642 @Override 2643 public String getColumnName(int col) { 2644 switch (col) { 2645 case INPUT_COLUMN: 2646 return Bundle.getMessage("ColumnInput"); // NOI18N 2647 case NAME_COLUMN: 2648 return Bundle.getMessage("ColumnName"); // NOI18N 2649 case TRUE_COLUMN: 2650 return Bundle.getMessage("ColumnTrue"); // NOI18N 2651 case FALSE_COLUMN: 2652 return Bundle.getMessage("ColumnFalse"); // NOI18N 2653 default: 2654 return "unknown"; // NOI18N 2655 } 2656 } 2657 2658 @Override 2659 public Object getValueAt(int r, int c) { 2660 switch (c) { 2661 case INPUT_COLUMN: 2662 int grp = r / 8; 2663 int rem = r % 8; 2664 return "I" + grp + "." + rem; 2665 case NAME_COLUMN: 2666 return _inputList.get(r).getName(); 2667 case TRUE_COLUMN: 2668 return _inputList.get(r).getEventTrue(); 2669 case FALSE_COLUMN: 2670 return _inputList.get(r).getEventFalse(); 2671 default: 2672 return null; 2673 } 2674 } 2675 2676 @Override 2677 public void setValueAt(Object type, int r, int c) { 2678 switch (c) { 2679 case NAME_COLUMN: 2680 _inputList.get(r).setName((String) type); 2681 setDirty(true); 2682 break; 2683 case TRUE_COLUMN: 2684 _inputList.get(r).setEventTrue((String) type); 2685 setDirty(true); 2686 break; 2687 case FALSE_COLUMN: 2688 _inputList.get(r).setEventFalse((String) type); 2689 setDirty(true); 2690 break; 2691 default: 2692 break; 2693 } 2694 } 2695 2696 @Override 2697 public boolean isCellEditable(int r, int c) { 2698 return ((c == NAME_COLUMN) || (c == TRUE_COLUMN) || (c == FALSE_COLUMN)); 2699 } 2700 2701 public int getPreferredWidth(int col) { 2702 switch (col) { 2703 case INPUT_COLUMN: 2704 return new JTextField(6).getPreferredSize().width; 2705 case NAME_COLUMN: 2706 return new JTextField(50).getPreferredSize().width; 2707 case TRUE_COLUMN: 2708 case FALSE_COLUMN: 2709 return new JTextField(20).getPreferredSize().width; 2710 default: 2711 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2712 return new JTextField(8).getPreferredSize().width; 2713 } 2714 } 2715 } 2716 2717 /** 2718 * TableModel for Output table entries. 2719 */ 2720 class OutputModel extends AbstractTableModel { 2721 OutputModel() { 2722 } 2723 2724 public static final int OUTPUT_COLUMN = 0; 2725 public static final int NAME_COLUMN = 1; 2726 public static final int TRUE_COLUMN = 2; 2727 public static final int FALSE_COLUMN = 3; 2728 2729 @Override 2730 public int getRowCount() { 2731 return _outputList.size(); 2732 } 2733 2734 @Override 2735 public int getColumnCount() { 2736 return 4; 2737 } 2738 2739 @Override 2740 public Class<?> getColumnClass(int c) { 2741 return String.class; 2742 } 2743 2744 @Override 2745 public String getColumnName(int col) { 2746 switch (col) { 2747 case OUTPUT_COLUMN: 2748 return Bundle.getMessage("ColumnOutput"); // NOI18N 2749 case NAME_COLUMN: 2750 return Bundle.getMessage("ColumnName"); // NOI18N 2751 case TRUE_COLUMN: 2752 return Bundle.getMessage("ColumnTrue"); // NOI18N 2753 case FALSE_COLUMN: 2754 return Bundle.getMessage("ColumnFalse"); // NOI18N 2755 default: 2756 return "unknown"; // NOI18N 2757 } 2758 } 2759 2760 @Override 2761 public Object getValueAt(int r, int c) { 2762 switch (c) { 2763 case OUTPUT_COLUMN: 2764 int grp = r / 8; 2765 int rem = r % 8; 2766 return "Q" + grp + "." + rem; 2767 case NAME_COLUMN: 2768 return _outputList.get(r).getName(); 2769 case TRUE_COLUMN: 2770 return _outputList.get(r).getEventTrue(); 2771 case FALSE_COLUMN: 2772 return _outputList.get(r).getEventFalse(); 2773 default: 2774 return null; 2775 } 2776 } 2777 2778 @Override 2779 public void setValueAt(Object type, int r, int c) { 2780 switch (c) { 2781 case NAME_COLUMN: 2782 _outputList.get(r).setName((String) type); 2783 setDirty(true); 2784 break; 2785 case TRUE_COLUMN: 2786 _outputList.get(r).setEventTrue((String) type); 2787 setDirty(true); 2788 break; 2789 case FALSE_COLUMN: 2790 _outputList.get(r).setEventFalse((String) type); 2791 setDirty(true); 2792 break; 2793 default: 2794 break; 2795 } 2796 } 2797 2798 @Override 2799 public boolean isCellEditable(int r, int c) { 2800 return ((c == NAME_COLUMN) || (c == TRUE_COLUMN) || (c == FALSE_COLUMN)); 2801 } 2802 2803 public int getPreferredWidth(int col) { 2804 switch (col) { 2805 case OUTPUT_COLUMN: 2806 return new JTextField(6).getPreferredSize().width; 2807 case NAME_COLUMN: 2808 return new JTextField(50).getPreferredSize().width; 2809 case TRUE_COLUMN: 2810 case FALSE_COLUMN: 2811 return new JTextField(20).getPreferredSize().width; 2812 default: 2813 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2814 return new JTextField(8).getPreferredSize().width; 2815 } 2816 } 2817 } 2818 2819 /** 2820 * TableModel for circuit receiver table entries. 2821 */ 2822 class ReceiverModel extends AbstractTableModel { 2823 2824 ReceiverModel() { 2825 } 2826 2827 public static final int CIRCUIT_COLUMN = 0; 2828 public static final int NAME_COLUMN = 1; 2829 public static final int EVENTID_COLUMN = 2; 2830 2831 @Override 2832 public int getRowCount() { 2833 return _receiverList.size(); 2834 } 2835 2836 @Override 2837 public int getColumnCount() { 2838 return 3; 2839 } 2840 2841 @Override 2842 public Class<?> getColumnClass(int c) { 2843 return String.class; 2844 } 2845 2846 @Override 2847 public String getColumnName(int col) { 2848 switch (col) { 2849 case CIRCUIT_COLUMN: 2850 return Bundle.getMessage("ColumnCircuit"); // NOI18N 2851 case NAME_COLUMN: 2852 return Bundle.getMessage("ColumnName"); // NOI18N 2853 case EVENTID_COLUMN: 2854 return Bundle.getMessage("ColumnEventID"); // NOI18N 2855 default: 2856 return "unknown"; // NOI18N 2857 } 2858 } 2859 2860 @Override 2861 public Object getValueAt(int r, int c) { 2862 switch (c) { 2863 case CIRCUIT_COLUMN: 2864 return "Y" + r; 2865 case NAME_COLUMN: 2866 return _receiverList.get(r).getName(); 2867 case EVENTID_COLUMN: 2868 return _receiverList.get(r).getEventId(); 2869 default: 2870 return null; 2871 } 2872 } 2873 2874 @Override 2875 public void setValueAt(Object type, int r, int c) { 2876 switch (c) { 2877 case NAME_COLUMN: 2878 _receiverList.get(r).setName((String) type); 2879 setDirty(true); 2880 break; 2881 case EVENTID_COLUMN: 2882 _receiverList.get(r).setEventId((String) type); 2883 setDirty(true); 2884 break; 2885 default: 2886 break; 2887 } 2888 } 2889 2890 @Override 2891 public boolean isCellEditable(int r, int c) { 2892 return ((c == NAME_COLUMN) || (c == EVENTID_COLUMN)); 2893 } 2894 2895 public int getPreferredWidth(int col) { 2896 switch (col) { 2897 case CIRCUIT_COLUMN: 2898 return new JTextField(6).getPreferredSize().width; 2899 case NAME_COLUMN: 2900 return new JTextField(50).getPreferredSize().width; 2901 case EVENTID_COLUMN: 2902 return new JTextField(20).getPreferredSize().width; 2903 default: 2904 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2905 return new JTextField(8).getPreferredSize().width; 2906 } 2907 } 2908 } 2909 2910 /** 2911 * TableModel for circuit transmitter table entries. 2912 */ 2913 class TransmitterModel extends AbstractTableModel { 2914 2915 TransmitterModel() { 2916 } 2917 2918 public static final int CIRCUIT_COLUMN = 0; 2919 public static final int NAME_COLUMN = 1; 2920 public static final int EVENTID_COLUMN = 2; 2921 2922 @Override 2923 public int getRowCount() { 2924 return _transmitterList.size(); 2925 } 2926 2927 @Override 2928 public int getColumnCount() { 2929 return 3; 2930 } 2931 2932 @Override 2933 public Class<?> getColumnClass(int c) { 2934 return String.class; 2935 } 2936 2937 @Override 2938 public String getColumnName(int col) { 2939 switch (col) { 2940 case CIRCUIT_COLUMN: 2941 return Bundle.getMessage("ColumnCircuit"); // NOI18N 2942 case NAME_COLUMN: 2943 return Bundle.getMessage("ColumnName"); // NOI18N 2944 case EVENTID_COLUMN: 2945 return Bundle.getMessage("ColumnEventID"); // NOI18N 2946 default: 2947 return "unknown"; // NOI18N 2948 } 2949 } 2950 2951 @Override 2952 public Object getValueAt(int r, int c) { 2953 switch (c) { 2954 case CIRCUIT_COLUMN: 2955 return "Z" + r; 2956 case NAME_COLUMN: 2957 return _transmitterList.get(r).getName(); 2958 case EVENTID_COLUMN: 2959 return _transmitterList.get(r).getEventId(); 2960 default: 2961 return null; 2962 } 2963 } 2964 2965 @Override 2966 public void setValueAt(Object type, int r, int c) { 2967 switch (c) { 2968 case NAME_COLUMN: 2969 _transmitterList.get(r).setName((String) type); 2970 setDirty(true); 2971 break; 2972 case EVENTID_COLUMN: 2973 _transmitterList.get(r).setEventId((String) type); 2974 setDirty(true); 2975 break; 2976 default: 2977 break; 2978 } 2979 } 2980 2981 @Override 2982 public boolean isCellEditable(int r, int c) { 2983 return ((c == NAME_COLUMN) || (c == EVENTID_COLUMN)); 2984 } 2985 2986 public int getPreferredWidth(int col) { 2987 switch (col) { 2988 case CIRCUIT_COLUMN: 2989 return new JTextField(6).getPreferredSize().width; 2990 case NAME_COLUMN: 2991 return new JTextField(50).getPreferredSize().width; 2992 case EVENTID_COLUMN: 2993 return new JTextField(20).getPreferredSize().width; 2994 default: 2995 log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N 2996 return new JTextField(8).getPreferredSize().width; 2997 } 2998 } 2999 } 3000 3001 // -------------- Operator Enum --------- 3002 3003 public enum Operator { 3004 x0(Bundle.getMessage("Separator0")), 3005 z1(Bundle.getMessage("Separator1")), 3006 A(Bundle.getMessage("OperatorA")), 3007 AN(Bundle.getMessage("OperatorAN")), 3008 O(Bundle.getMessage("OperatorO")), 3009 ON(Bundle.getMessage("OperatorON")), 3010 X(Bundle.getMessage("OperatorX")), 3011 XN(Bundle.getMessage("OperatorXN")), 3012 3013 z2(Bundle.getMessage("Separator2")), // The STL parens are represented by lower case p 3014 Ap(Bundle.getMessage("OperatorAp")), 3015 ANp(Bundle.getMessage("OperatorANp")), 3016 Op(Bundle.getMessage("OperatorOp")), 3017 ONp(Bundle.getMessage("OperatorONp")), 3018 Xp(Bundle.getMessage("OperatorXp")), 3019 XNp(Bundle.getMessage("OperatorXNp")), 3020 Cp(Bundle.getMessage("OperatorCp")), // Close paren 3021 3022 z3(Bundle.getMessage("Separator3")), 3023 EQ(Bundle.getMessage("OperatorEQ")), // = operator 3024 R(Bundle.getMessage("OperatorR")), 3025 S(Bundle.getMessage("OperatorS")), 3026 3027 z4(Bundle.getMessage("Separator4")), 3028 NOT(Bundle.getMessage("OperatorNOT")), 3029 SET(Bundle.getMessage("OperatorSET")), 3030 CLR(Bundle.getMessage("OperatorCLR")), 3031 SAVE(Bundle.getMessage("OperatorSAVE")), 3032 3033 z5(Bundle.getMessage("Separator5")), 3034 JU(Bundle.getMessage("OperatorJU")), 3035 JC(Bundle.getMessage("OperatorJC")), 3036 JCN(Bundle.getMessage("OperatorJCN")), 3037 JCB(Bundle.getMessage("OperatorJCB")), 3038 JNB(Bundle.getMessage("OperatorJNB")), 3039 JBI(Bundle.getMessage("OperatorJBI")), 3040 JNBI(Bundle.getMessage("OperatorJNBI")), 3041 3042 z6(Bundle.getMessage("Separator6")), 3043 FN(Bundle.getMessage("OperatorFN")), 3044 FP(Bundle.getMessage("OperatorFP")), 3045 3046 z7(Bundle.getMessage("Separator7")), 3047 L(Bundle.getMessage("OperatorL")), 3048 FR(Bundle.getMessage("OperatorFR")), 3049 SP(Bundle.getMessage("OperatorSP")), 3050 SE(Bundle.getMessage("OperatorSE")), 3051 SD(Bundle.getMessage("OperatorSD")), 3052 SS(Bundle.getMessage("OperatorSS")), 3053 SF(Bundle.getMessage("OperatorSF")); 3054 3055 private final String _text; 3056 3057 private Operator(String text) { 3058 this._text = text; 3059 } 3060 3061 @Override 3062 public String toString() { 3063 return _text; 3064 } 3065 3066 } 3067 3068 // -------------- Token Class --------- 3069 3070 static class Token { 3071 String _type = ""; 3072 String _name = ""; 3073 int _offsetStart = 0; 3074 int _offsetEnd = 0; 3075 3076 Token(String type, String name, int offsetStart, int offsetEnd) { 3077 _type = type; 3078 _name = name; 3079 _offsetStart = offsetStart; 3080 _offsetEnd = offsetEnd; 3081 } 3082 3083 public String getType() { 3084 return _type; 3085 } 3086 3087 public String getName() { 3088 return _name; 3089 } 3090 3091 public int getStart() { 3092 return _offsetStart; 3093 } 3094 3095 public int getEnd() { 3096 return _offsetEnd; 3097 } 3098 3099 @Override 3100 public String toString() { 3101 return String.format("Type: %s, Name: %s, Start: %d, End: %d", 3102 _type, _name, _offsetStart, _offsetEnd); 3103 } 3104 } 3105 3106 // -------------- misc items --------- 3107 @Override 3108 public java.util.List<JMenu> getMenus() { 3109 // create a file menu 3110 var retval = new ArrayList<JMenu>(); 3111 var fileMenu = new JMenu(Bundle.getMessage("MenuFile")); 3112 3113 _refreshItem = new JMenuItem(Bundle.getMessage("MenuRefresh")); 3114 _storeItem = new JMenuItem(Bundle.getMessage("MenuStore")); 3115 _importItem = new JMenuItem(Bundle.getMessage("MenuImport")); 3116 _exportItem = new JMenuItem(Bundle.getMessage("MenuExport")); 3117 _loadItem = new JMenuItem(Bundle.getMessage("MenuLoad")); 3118 3119 _refreshItem.addActionListener(this::pushedRefreshButton); 3120 _storeItem.addActionListener(this::pushedStoreButton); 3121 _importItem.addActionListener(this::pushedImportButton); 3122 _exportItem.addActionListener(this::pushedExportButton); 3123 _loadItem.addActionListener(this::loadBackupData); 3124 3125 fileMenu.add(_refreshItem); 3126 fileMenu.add(_storeItem); 3127 fileMenu.addSeparator(); 3128 fileMenu.add(_importItem); 3129 fileMenu.add(_exportItem); 3130 fileMenu.addSeparator(); 3131 fileMenu.add(_loadItem); 3132 3133 _refreshItem.setEnabled(false); 3134 _storeItem.setEnabled(false); 3135 _exportItem.setEnabled(false); 3136 3137 retval.add(fileMenu); 3138 return retval; 3139 } 3140 3141 @Override 3142 public void dispose() { 3143 _pm.setSimplePreferenceState(_storeModeCheck, _compactOption.isSelected()); 3144 // and complete this 3145 super.dispose(); 3146 } 3147 3148 @Override 3149 public String getHelpTarget() { 3150 return "package.jmri.jmrix.openlcb.swing.stleditor.StlEditorPane"; 3151 } 3152 3153 @Override 3154 public String getTitle() { 3155 if (_canMemo != null) { 3156 return (_canMemo.getUserName() + " STL Editor"); 3157 } 3158 return Bundle.getMessage("TitleSTLEditor"); 3159 } 3160 3161 /** 3162 * Nested class to create one of these using old-style defaults 3163 */ 3164 public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 3165 3166 public Default() { 3167 super("STL Editor", 3168 new jmri.util.swing.sdi.JmriJFrameInterface(), 3169 StlEditorPane.class.getName(), 3170 jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 3171 } 3172 } 3173 3174 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(StlEditorPane.class); 3175}