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