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