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