java/src/jmri/jmrix/openlcb/swing/stleditor/StlEditorPane.java
package jmri.jmrix.openlcb.swing.stleditor;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import java.nio.file.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.AbstractTableModel;
import jmri.jmrix.can.CanSystemConnectionMemo;
import jmri.util.FileUtil;
import jmri.util.swing.JComboBoxUtil;
import jmri.util.swing.JmriJFileChooser;
import jmri.util.swing.JmriJOptionPane;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static org.openlcb.MimicNodeStore.NodeMemo.UPDATE_PROP_SIMPLE_NODE_IDENT;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import org.openlcb.*;
import org.openlcb.cdi.cmd.*;
import org.openlcb.cdi.impl.ConfigRepresentation;
/**
* Panel for editing STL logic.
*
* The primary mode is a connection to a Tower LCC+Q. When a node is selected, the data
* is transferred to Java lists and displayed using Java tables. If changes are to be retained,
* the Store process is invoked which updates the Tower LCC+Q CDI.
*
* An alternate mode uses CSV files to import and export the data. This enables offline development.
* Since the CDI is loaded automatically when the node is selected, to transfer offline development
* is a three step process: Load the CDI, replace the content with the CSV content and then store
* to the CDI.
*
* A third mode is to load a CDI backup file. This can then be used with the CSV process for offline work.
*
* The reboot process has several steps.
* <ul>
* <li>The Yes option is selected in the compile needed dialog. This sends the reboot command.</li>
* <li>The RebootListener detects that the reboot is done and does getCompileMessage.</li>
* <li>getCompileMessage does a reload for the first syntax message.</li>
* <li>EntryListener gets the reload done event and calls displayCompileMessage.</li>
* </ul>
*
* @author Dave Sand Copyright (C) 2024
* @since 5.7.5
*/
public class StlEditorPane extends jmri.util.swing.JmriPanel
implements jmri.jmrix.can.swing.CanPanelInterface {
/**
* The STL Editor is dependent on the Tower LCC+Q software version
*/
private static int TOWER_LCC_Q_NODE_VERSION = 106;
private static String TOWER_LCC_Q_NODE_VERSION_STRING = "v1.06";
private CanSystemConnectionMemo _canMemo;
private OlcbInterface _iface;
private ConfigRepresentation _cdi;
private MimicNodeStore _store;
private boolean _dirty = false;
private int _logicRow = -1; // The last selected row, -1 for none
private int _groupRow = 0;
private List<String> _csvMessages = new ArrayList<>();
private AtomicInteger _storeQueueLength = new AtomicInteger(0);
private boolean _compileNeeded = false;
private boolean _compileInProgress = false;
PropertyChangeListener _entryListener = new EntryListener();
private String _csvDirectoryPath = "";
private DefaultComboBoxModel<NodeEntry> _nodeModel = new DefaultComboBoxModel<NodeEntry>();
private JComboBox<NodeEntry> _nodeBox;
private JComboBox<Operator> _operators = new JComboBox<>(Operator.values());
private List<GroupRow> _groupList = new ArrayList<>();
private List<InputRow> _inputList = new ArrayList<>();
private List<OutputRow> _outputList = new ArrayList<>();
private List<ReceiverRow> _receiverList = new ArrayList<>();
private List<TransmitterRow> _transmitterList = new ArrayList<>();
private JTable _groupTable;
private JTable _logicTable;
private JTable _inputTable;
private JTable _outputTable;
private JTable _receiverTable;
private JTable _transmitterTable;
private JTabbedPane _detailTabs;
private JPanel _editButtons;
private JButton _addButton;
private JButton _insertButton;
private JButton _moveUpButton;
private JButton _moveDownButton;
private JButton _deleteButton;
private JButton _percentButton;
private JButton _refreshButton;
private JButton _storeButton;
private JButton _exportButton;
private JButton _importButton;
private JMenuItem _refreshItem;
private JMenuItem _storeItem;
private JMenuItem _exportItem;
private JMenuItem _importItem;
private JMenuItem _loadItem;
// CDI Names
private static String INPUT_NAME = "Logic Inputs.Group I%s(%s).Input Description";
private static String INPUT_TRUE = "Logic Inputs.Group I%s(%s).True";
private static String INPUT_FALSE = "Logic Inputs.Group I%s(%s).False";
private static String OUTPUT_NAME = "Logic Outputs.Group Q%s(%s).Output Description";
private static String OUTPUT_TRUE = "Logic Outputs.Group Q%s(%s).True";
private static String OUTPUT_FALSE = "Logic Outputs.Group Q%s(%s).False";
private static String RECEIVER_NAME = "Track Receivers.Rx Circuit(%s).Remote Mast Description";
private static String RECEIVER_EVENT = "Track Receivers.Rx Circuit(%s).Link Address";
private static String TRANSMITTER_NAME = "Track Transmitters.Tx Circuit(%s).Track Circuit Description";
private static String TRANSMITTER_EVENT = "Track Transmitters.Tx Circuit(%s).Link Address";
private static String GROUP_NAME = "Conditionals.Logic(%s).Group Description";
private static String GROUP_MULTI_LINE = "Conditionals.Logic(%s).MultiLine";
private static String SYNTAX_MESSAGE = "Syntax Messages.Syntax Messages.Message 1";
// Regex Patterns
private static Pattern PARSE_VARIABLE = Pattern.compile("[IQYZM](\\d+)\\.(\\d+)");
private static Pattern PARSE_LABEL = Pattern.compile("\\D\\w{0,3}:");
private static Pattern PARSE_TIMERWORD = Pattern.compile("W#[0123]#\\d{1,3}");
private static Pattern PARSE_TIMERVAR = Pattern.compile("T\\d{1,2}");
private static Pattern PARSE_HEXPAIR = Pattern.compile("^[0-9a-fA-F]{2}$");
private static Pattern PARSE_VERSION = Pattern.compile("^.*(\\d+)\\.(\\d+)$");
public StlEditorPane() {
}
@Override
public void initComponents(CanSystemConnectionMemo memo) {
_canMemo = memo;
_iface = memo.get(OlcbInterface.class);
_store = memo.get(MimicNodeStore.class);
// Add to GUI here
setLayout(new BorderLayout());
var footer = new JPanel();
footer.setLayout(new BorderLayout());
_addButton = new JButton(Bundle.getMessage("ButtonAdd"));
_insertButton = new JButton(Bundle.getMessage("ButtonInsert"));
_moveUpButton = new JButton(Bundle.getMessage("ButtonMoveUp"));
_moveDownButton = new JButton(Bundle.getMessage("ButtonMoveDown"));
_deleteButton = new JButton(Bundle.getMessage("ButtonDelete"));
_percentButton = new JButton("0%");
_refreshButton = new JButton(Bundle.getMessage("ButtonRefresh"));
_storeButton = new JButton(Bundle.getMessage("ButtonStore"));
_exportButton = new JButton(Bundle.getMessage("ButtonExport"));
_importButton = new JButton(Bundle.getMessage("ButtonImport"));
_refreshButton.setEnabled(false);
_storeButton.setEnabled(false);
_addButton.addActionListener(this::pushedAddButton);
_insertButton.addActionListener(this::pushedInsertButton);
_moveUpButton.addActionListener(this::pushedMoveUpButton);
_moveDownButton.addActionListener(this::pushedMoveDownButton);
_deleteButton.addActionListener(this::pushedDeleteButton);
_percentButton.addActionListener(this::pushedPercentButton);
_refreshButton.addActionListener(this::pushedRefreshButton);
_storeButton.addActionListener(this::pushedStoreButton);
_exportButton.addActionListener(this::pushedExportButton);
_importButton.addActionListener(this::pushedImportButton);
_editButtons = new JPanel();
_editButtons.add(_addButton);
_editButtons.add(_insertButton);
_editButtons.add(_moveUpButton);
_editButtons.add(_moveDownButton);
_editButtons.add(_deleteButton);
_editButtons.add(_percentButton);
footer.add(_editButtons, BorderLayout.WEST);
var dataButtons = new JPanel();
dataButtons.add(_importButton);
dataButtons.add(_exportButton);
dataButtons.add(new JLabel(" | "));
dataButtons.add(_refreshButton);
dataButtons.add(_storeButton);
footer.add(dataButtons, BorderLayout.EAST);
add(footer, BorderLayout.SOUTH);
// Define the node selector which goes in the header
var nodeSelector = new JPanel();
nodeSelector.setLayout(new FlowLayout());
_nodeBox = new JComboBox<NodeEntry>(_nodeModel);
// Load node selector combo box
for (MimicNodeStore.NodeMemo nodeMemo : _store.getNodeMemos() ) {
newNodeInList(nodeMemo);
}
_nodeBox.addActionListener(this::nodeSelected);
JComboBoxUtil.setupComboBoxMaxRows(_nodeBox);
// Force combo box width
var dim = _nodeBox.getPreferredSize();
var newDim = new Dimension(400, (int)dim.getHeight());
_nodeBox.setPreferredSize(newDim);
nodeSelector.add(_nodeBox);
add(nodeSelector, BorderLayout.NORTH);
// Define the center section of the window which consists of 5 tabs
_detailTabs = new JTabbedPane();
_detailTabs.add(Bundle.getMessage("ButtonG"), buildLogicPanel()); // NOI18N
_detailTabs.add(Bundle.getMessage("ButtonI"), buildInputPanel()); // NOI18N
_detailTabs.add(Bundle.getMessage("ButtonQ"), buildOutputPanel()); // NOI18N
_detailTabs.add(Bundle.getMessage("ButtonY"), buildReceiverPanel()); // NOI18N
_detailTabs.add(Bundle.getMessage("ButtonZ"), buildTransmitterPanel()); // NOI18N
_detailTabs.addChangeListener(this::tabSelected);
_detailTabs.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
add(_detailTabs, BorderLayout.CENTER);
initalizeLists();
}
// -------------- tab configurations ---------
private JScrollPane buildGroupPanel() {
// Create scroll pane
var model = new GroupModel();
_groupTable = new JTable(model);
var scrollPane = new JScrollPane(_groupTable);
// resize columns
for (int i = 0; i < model.getColumnCount(); i++) {
int width = model.getPreferredWidth(i);
_groupTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
_groupTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
var selectionModel = _groupTable.getSelectionModel();
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
selectionModel.addListSelectionListener(this::handleGroupRowSelection);
return scrollPane;
}
private JSplitPane buildLogicPanel() {
// Create scroll pane
var model = new LogicModel();
_logicTable = new JTable(model);
var logicScrollPane = new JScrollPane(_logicTable);
// resize columns
for (int i = 0; i < _logicTable.getColumnCount(); i++) {
int width = model.getPreferredWidth(i);
_logicTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
_logicTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
// Use the operators combo box for the operator column
var col = _logicTable.getColumnModel().getColumn(1);
col.setCellEditor(new DefaultCellEditor(_operators));
JComboBoxUtil.setupComboBoxMaxRows(_operators);
var selectionModel = _logicTable.getSelectionModel();
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
selectionModel.addListSelectionListener(this::handleLogicRowSelection);
var logicPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, buildGroupPanel(), logicScrollPane);
logicPanel.setDividerSize(10);
logicPanel.setResizeWeight(.10);
logicPanel.setDividerLocation(150);
return logicPanel;
}
private JScrollPane buildInputPanel() {
// Create scroll pane
var model = new InputModel();
_inputTable = new JTable(model);
var scrollPane = new JScrollPane(_inputTable);
// resize columns
for (int i = 0; i < model.getColumnCount(); i++) {
int width = model.getPreferredWidth(i);
_inputTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
_inputTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
return scrollPane;
}
private JScrollPane buildOutputPanel() {
// Create scroll pane
var model = new OutputModel();
_outputTable = new JTable(model);
var scrollPane = new JScrollPane(_outputTable);
// resize columns
for (int i = 0; i < model.getColumnCount(); i++) {
int width = model.getPreferredWidth(i);
_outputTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
_outputTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
return scrollPane;
}
private JScrollPane buildReceiverPanel() {
// Create scroll pane
var model = new ReceiverModel();
_receiverTable = new JTable(model);
var scrollPane = new JScrollPane(_receiverTable);
// resize columns
for (int i = 0; i < model.getColumnCount(); i++) {
int width = model.getPreferredWidth(i);
_receiverTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
_receiverTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
return scrollPane;
}
private JScrollPane buildTransmitterPanel() {
// Create scroll pane
var model = new TransmitterModel();
_transmitterTable = new JTable(model);
var scrollPane = new JScrollPane(_transmitterTable);
// resize columns
for (int i = 0; i < model.getColumnCount(); i++) {
int width = model.getPreferredWidth(i);
_transmitterTable.getColumnModel().getColumn(i).setPreferredWidth(width);
}
_transmitterTable.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
return scrollPane;
}
private void tabSelected(ChangeEvent e) {
if (_detailTabs.getSelectedIndex() == 0) {
_editButtons.setVisible(true);
} else {
_editButtons.setVisible(false);
}
}
// -------------- Initialization ---------
private void initalizeLists() {
// Group List
for (int i = 0; i < 16; i++) {
_groupList.add(new GroupRow(""));
}
// Input List
for (int i = 0; i < 128; i++) {
_inputList.add(new InputRow("", "", ""));
}
// Output List
for (int i = 0; i < 128; i++) {
_outputList.add(new OutputRow("", "", ""));
}
// Receiver List
for (int i = 0; i < 16; i++) {
_receiverList.add(new ReceiverRow("", ""));
}
// Transmitter List
for (int i = 0; i < 16; i++) {
_transmitterList.add(new TransmitterRow("", ""));
}
}
// -------------- Logic table methods ---------
private void handleGroupRowSelection(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
_groupRow = _groupTable.getSelectedRow();
_logicTable.revalidate();
_logicTable.repaint();
pushedPercentButton(null);
}
}
private void pushedPercentButton(ActionEvent e) {
encode(_groupList.get(_groupRow));
_percentButton.setText(_groupList.get(_groupRow).getSize());
}
private void handleLogicRowSelection(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
_logicRow = _logicTable.getSelectedRow();
_moveUpButton.setEnabled(_logicRow > 0);
_moveDownButton.setEnabled(_logicRow < _logicTable.getRowCount() - 1);
}
}
private void pushedAddButton(ActionEvent e) {
var logicList = _groupList.get(_groupRow).getLogicList();
logicList.add(new LogicRow("", null, "", ""));
_logicRow = logicList.size() - 1;
_logicTable.revalidate();
_logicTable.setRowSelectionInterval(_logicRow, _logicRow);
setDirty(true);
}
private void pushedInsertButton(ActionEvent e) {
var logicList = _groupList.get(_groupRow).getLogicList();
if (_logicRow >= 0 && _logicRow < logicList.size()) {
logicList.add(_logicRow, new LogicRow("", null, "", ""));
_logicTable.revalidate();
_logicTable.setRowSelectionInterval(_logicRow, _logicRow);
}
setDirty(true);
}
private void pushedMoveUpButton(ActionEvent e) {
var logicList = _groupList.get(_groupRow).getLogicList();
if (_logicRow >= 0 && _logicRow < logicList.size()) {
var logicRow = logicList.remove(_logicRow);
logicList.add(_logicRow - 1, logicRow);
_logicRow--;
_logicTable.revalidate();
_logicTable.setRowSelectionInterval(_logicRow, _logicRow);
}
setDirty(true);
}
private void pushedMoveDownButton(ActionEvent e) {
var logicList = _groupList.get(_groupRow).getLogicList();
if (_logicRow >= 0 && _logicRow < logicList.size()) {
var logicRow = logicList.remove(_logicRow);
logicList.add(_logicRow + 1, logicRow);
_logicRow++;
_logicTable.revalidate();
_logicTable.setRowSelectionInterval(_logicRow, _logicRow);
}
setDirty(true);
}
private void pushedDeleteButton(ActionEvent e) {
var logicList = _groupList.get(_groupRow).getLogicList();
if (_logicRow >= 0 && _logicRow < logicList.size()) {
logicList.remove(_logicRow);
_logicTable.revalidate();
}
setDirty(true);
}
// -------------- Encode/Decode methods ---------
private String nameToVariable(String name) {
if (name != null && !name.isEmpty()) {
if (!name.contains("~")) {
// Search input and output tables
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
int row = (i * 8) + j;
if (_inputList.get(row).getName().equals(name)) {
return "I" + i + "." + j;
}
}
}
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
int row = (i * 8) + j;
if (_outputList.get(row).getName().equals(name)) {
return "Q" + i + "." + j;
}
}
}
return name;
} else {
// Search receiver and transmitter tables
var splitName = name.split("~");
var baseName = splitName[0];
var aspectName = splitName[1];
var aspectNumber = 0;
try {
aspectNumber = Integer.parseInt(aspectName);
if (aspectNumber < 0 || aspectNumber > 7) {
warningDialog(Bundle.getMessage("TitleAspect"), Bundle.getMessage("MessageAspect", aspectNumber));
aspectNumber = 0;
}
} catch (NumberFormatException e) {
warningDialog(Bundle.getMessage("TitleAspect"), Bundle.getMessage("MessageAspect", aspectName));
aspectNumber = 0;
}
for (int i = 0; i < 16; i++) {
if (_receiverList.get(i).getName().equals(baseName)) {
return "Y" + i + "." + aspectNumber;
}
}
for (int i = 0; i < 16; i++) {
if (_transmitterList.get(i).getName().equals(baseName)) {
return "Z" + i + "." + aspectNumber;
}
}
return name;
}
}
return null;
}
private String variableToName(String variable) {
String name = "";
if (variable.length() > 1) {
var varType = variable.substring(0, 1);
var match = PARSE_VARIABLE.matcher(variable);
if (match.find() && match.groupCount() == 2) {
int first = -1;
int second = -1;
int row = -1;
try {
first = Integer.parseInt(match.group(1));
second = Integer.parseInt(match.group(2));
} catch (NumberFormatException e) {
warningDialog(Bundle.getMessage("TitleVariable"), Bundle.getMessage("MessageVariable", variable));
return name;
}
switch (varType) {
case "I":
row = (first * 8) + second;
name = _inputList.get(row).getName();
if (name.isEmpty()) {
name = variable;
}
break;
case "Q":
row = (first * 8) + second;
name = _outputList.get(row).getName();
if (name.isEmpty()) {
name = variable;
}
break;
case "Y":
row = first;
name = _receiverList.get(row).getName() + "~" + second;
break;
case "Z":
row = first;
name = _transmitterList.get(row).getName() + "~" + second;
break;
default:
log.error("Variable '{}' has an invalid first letter (IQYZ)", variable);
}
}
}
return name;
}
private void encode(GroupRow groupRow) {
String longLine = "";
var logicList = groupRow.getLogicList();
for (var row : logicList) {
var sb = new StringBuilder();
var jumpLabel = false;
if (!row.getLabel().isEmpty()) {
sb.append(row.getLabel() + " ");
}
if (row.getOper() != null) {
var oper = row.getOper();
var operName = oper.name();
// Fix special enums
if (operName.equals("Cp")) {
operName = ")";
} else if (operName.equals("EQ")) {
operName = "=";
} else if (operName.contains("p")) {
operName = operName.replace("p", "(");
}
if (operName.startsWith("J")) {
jumpLabel =true;
}
sb.append(operName);
}
if (!row.getName().isEmpty()) {
var name = row.getName().trim();
if (jumpLabel) {
sb.append(" " + name);
jumpLabel = false;
} else if (isMemory(name)) {
sb.append(" " + name);
} else if (isTimerWord(name)) {
sb.append(" " + name);
} else if (isTimerVar(name)) {
sb.append(" " + name);
} else {
var variable = nameToVariable(name);
if (variable == null) {
JmriJOptionPane.showMessageDialog(null,
Bundle.getMessage("MessageBadName", groupRow.getName(), name),
Bundle.getMessage("TitleBadName"),
JmriJOptionPane.ERROR_MESSAGE);
log.error("bad name: {}", name);
} else {
sb.append(" " + variable);
}
}
}
if (!row.getComment().isEmpty()) {
var comment = row.getComment().trim();
sb.append(" // " + comment);
}
sb.append("\n");
longLine = longLine + sb.toString();
}
log.debug("MultiLine: {}", longLine);
if (longLine.length() < 256) {
groupRow.setMultiLine(longLine);
} else {
var overflow = longLine.substring(255);
JmriJOptionPane.showMessageDialog(null,
Bundle.getMessage("MessageOverflow", groupRow.getName(), overflow),
Bundle.getMessage("TitleOverflow"),
JmriJOptionPane.ERROR_MESSAGE);
log.error("The line overflowed, content truncated: {}", overflow);
}
}
private boolean isMemory(String name) {
var match = PARSE_VARIABLE.matcher(name);
return (match.find() && name.startsWith("M"));
}
private boolean isTimerWord(String name) {
var match = PARSE_TIMERWORD.matcher(name);
return match.find();
}
private boolean isTimerVar(String name) {
var match = PARSE_TIMERVAR.matcher(name);
return match.find();
}
private void decode(GroupRow groupRow) {
String[] lines = groupRow.getMultiLine().split("\\n");
for (int i = 0; i < lines.length; i++) {
if (lines[i].isEmpty()) {
continue;
}
String[] tokens = lines[i].split(" ");
var label = "";
var name = "";
var comment = "";
Operator oper = null;
boolean needOperator = true;
for (int j = 0; j < tokens.length; j++) {
var token = tokens[j];
// Get label
if (j == 0) {
var match = PARSE_LABEL.matcher(token);
if (match.find()) {
label = token;
continue;
}
}
// Get operator
if (needOperator) {
oper = getEnum(token);
if (oper != null) {
needOperator = false;
continue;
}
}
// Get comment
if (token.equals("//")) {
int commentPosition = lines[i].indexOf("//");
comment = lines[i].substring(commentPosition + 3);
break;
}
// Get name
if (oper != null) {
if (oper.name().startsWith("J")) { // Jump label
name = token;
} else if (isMemory(token)) { // Memory variable
name = token;
} else if (isTimerWord(token)) { // Load timer
name = token;
} else if (isTimerVar(token)) { // Timer variable
name = token;
} else {
var match = PARSE_VARIABLE.matcher(token);
if (match.find()) {
name = variableToName(token);
} else {
name = token;
}
}
}
}
var logic = new LogicRow(label, oper, name, comment);
groupRow.getLogicList().add(logic);
}
}
private Operator getEnum(String name) {
try {
var temp = name;
if (name.equals("=")) {
temp = "EQ";
} else if (name.equals(")")) {
temp = "Cp";
} else if (name.endsWith("(")) {
temp = name.replace("(", "p");
}
Operator oper = Enum.valueOf(Operator.class, temp);
return oper;
} catch (IllegalArgumentException ex) {
return null;
}
}
// -------------- node methods ---------
private void nodeSelected(ActionEvent e) {
NodeEntry node = (NodeEntry) _nodeBox.getSelectedItem();
node.getNodeMemo().addPropertyChangeListener(new RebootListener());
log.debug("nodeSelected: {}", node);
if (isValidNodeVersionNumber(node.getNodeMemo())) {
_cdi = _iface.getConfigForNode(node.getNodeID());
if (_cdi.getRoot() != null) {
loadCdiData();
} else {
JmriJOptionPane.showMessageDialogNonModal(this,
Bundle.getMessage("MessageCdiLoad", node),
Bundle.getMessage("TitleCdiLoad"),
JmriJOptionPane.INFORMATION_MESSAGE,
null);
_cdi.addPropertyChangeListener(new CdiListener());
}
}
}
public class CdiListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
log.debug("CdiListener event = {}", propertyName);
if (propertyName.equals("UPDATE_CACHE_COMPLETE")) {
Window[] windows = Window.getWindows();
for (Window window : windows) {
if (window instanceof JDialog) {
JDialog dialog = (JDialog) window;
if (dialog.getTitle().equals(Bundle.getMessage("TitleCdiLoad"))) {
dialog.dispose();
}
}
}
loadCdiData();
}
}
}
/**
* Listens for a property change that implies a node has been rebooted.
* This occurs when the user has selected that the editor should do the reboot to compile the updated logic.
* When the updateSimpleNodeIdent event occurs and the compile is in progress it starts the message display process.
*/
public class RebootListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (_compileInProgress && propertyName.equals("updateSimpleNodeIdent")) {
log.debug("The reboot appears to be done");
getCompileMessage();
}
}
}
private void newNodeInList(MimicNodeStore.NodeMemo nodeMemo) {
// Filter for Tower LCC+Q
NodeID node = nodeMemo.getNodeID();
String id = node.toString();
log.debug("node id: {}", id);
if (!id.startsWith("02.01.57.4")) {
return;
}
int i = 0;
if (_nodeModel.getIndexOf(nodeMemo.getNodeID()) >= 0) {
// already exists. Do nothing.
return;
}
NodeEntry e = new NodeEntry(nodeMemo);
while ((i < _nodeModel.getSize()) && (_nodeModel.getElementAt(i).compareTo(e) < 0)) {
++i;
}
_nodeModel.insertElementAt(e, i);
}
private boolean isValidNodeVersionNumber(MimicNodeStore.NodeMemo nodeMemo) {
SimpleNodeIdent ident = nodeMemo.getSimpleNodeIdent();
String versionString = ident.getSoftwareVersion();
int version = 0;
var match = PARSE_VERSION.matcher(versionString);
if (match.find()) {
var major = match.group(1);
var minor = match.group(2);
version = Integer.parseInt(major + minor);
}
if (version < TOWER_LCC_Q_NODE_VERSION) {
JmriJOptionPane.showMessageDialog(null,
Bundle.getMessage("MessageVersion",
nodeMemo.getNodeID(),
versionString,
TOWER_LCC_Q_NODE_VERSION_STRING),
Bundle.getMessage("TitleVersion"),
JmriJOptionPane.WARNING_MESSAGE);
return false;
}
return true;
}
public class EntryListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
log.debug("EntryListener event = {}", propertyName);
if (propertyName.equals("PENDING_WRITE_COMPLETE")) {
int currentLength = _storeQueueLength.decrementAndGet();
log.debug("Listener: queue length = {}, source = {}", currentLength, e.getSource());
var entry = (ConfigRepresentation.CdiEntry) e.getSource();
entry.removePropertyChangeListener(_entryListener);
if (currentLength < 1) {
log.debug("The queue is back to zero which implies the updates are done");
displayStoreDone();
}
}
if (_compileInProgress && propertyName.equals("UPDATE_ENTRY_DATA")) {
// The refresh of the first syntax message has completed.
var entry = (ConfigRepresentation.StringEntry) e.getSource();
entry.removePropertyChangeListener(_entryListener);
displayCompileMessage(entry.getValue());
}
}
}
private void displayStoreDone() {
_csvMessages.add(Bundle.getMessage("StoreDone"));
var msgType = JmriJOptionPane.ERROR_MESSAGE;
if (_csvMessages.size() == 1) {
msgType = JmriJOptionPane.INFORMATION_MESSAGE;
}
JmriJOptionPane.showMessageDialog(this,
String.join("\n", _csvMessages),
Bundle.getMessage("TitleCdiStore"),
msgType);
if (_compileNeeded) {
log.debug("Display compile needed message");
String[] options = {Bundle.getMessage("EditorReboot"), Bundle.getMessage("CdiReboot")};
int response = JmriJOptionPane.showOptionDialog(this,
Bundle.getMessage("MessageCdiReboot"),
Bundle.getMessage("TitleCdiReboot"),
JmriJOptionPane.YES_NO_OPTION,
JmriJOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]);
if (response == JmriJOptionPane.YES_OPTION) {
// Set the compile in process and request the reboot. The completion will be
// handed by the RebootListener.
_compileInProgress = true;
_cdi.getConnection().getDatagramService().
sendData(_cdi.getRemoteNodeID(), new int[] {0x20, 0xA9});
}
}
}
/**
* Get the first syntax message entry, add the entry listener and request a reload (refresh).
* The EntryListener will handle the reload event.
*/
private void getCompileMessage() {
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(SYNTAX_MESSAGE);
entry.addPropertyChangeListener(_entryListener);
entry.reload();
}
/**
* Turn off the compile in progress and display the syntax message.
* @param message The first syntax message.
*/
private void displayCompileMessage(String message) {
_compileInProgress = false;
JmriJOptionPane.showMessageDialog(this,
Bundle.getMessage("MessageCompile", message),
Bundle.getMessage("TitleCompile"),
JmriJOptionPane.INFORMATION_MESSAGE);
}
// Notifies that the contents of a given entry have changed. This will delete and re-add the
// entry to the model, forcing a refresh of the box.
public void updateComboBoxModelEntry(NodeEntry nodeEntry) {
int idx = _nodeModel.getIndexOf(nodeEntry.getNodeID());
if (idx < 0) {
return;
}
NodeEntry last = _nodeModel.getElementAt(idx);
if (last != nodeEntry) {
// not the same object -- we're talking about an abandoned entry.
nodeEntry.dispose();
return;
}
NodeEntry sel = (NodeEntry) _nodeModel.getSelectedItem();
_nodeModel.removeElementAt(idx);
_nodeModel.insertElementAt(nodeEntry, idx);
_nodeModel.setSelectedItem(sel);
}
protected static class NodeEntry implements Comparable<NodeEntry>, PropertyChangeListener {
final MimicNodeStore.NodeMemo nodeMemo;
String description = "";
NodeEntry(MimicNodeStore.NodeMemo memo) {
this.nodeMemo = memo;
memo.addPropertyChangeListener(this);
updateDescription();
}
/**
* Constructor for prototype display value
*
* @param description prototype display value
*/
public NodeEntry(String description) {
this.nodeMemo = null;
this.description = description;
}
public NodeID getNodeID() {
return nodeMemo.getNodeID();
}
MimicNodeStore.NodeMemo getNodeMemo() {
return nodeMemo;
}
private void updateDescription() {
SimpleNodeIdent ident = nodeMemo.getSimpleNodeIdent();
StringBuilder sb = new StringBuilder();
sb.append(nodeMemo.getNodeID().toString());
addToDescription(ident.getUserName(), sb);
addToDescription(ident.getUserDesc(), sb);
if (!ident.getMfgName().isEmpty() || !ident.getModelName().isEmpty()) {
addToDescription(ident.getMfgName() + " " +ident.getModelName(), sb);
}
addToDescription(ident.getSoftwareVersion(), sb);
String newDescription = sb.toString();
if (!description.equals(newDescription)) {
description = newDescription;
}
}
private void addToDescription(String s, StringBuilder sb) {
if (!s.isEmpty()) {
sb.append(" - ");
sb.append(s);
}
}
private long reorder(long n) {
return (n < 0) ? Long.MAX_VALUE - n : Long.MIN_VALUE + n;
}
@Override
public int compareTo(NodeEntry otherEntry) {
long l1 = reorder(getNodeID().toLong());
long l2 = reorder(otherEntry.getNodeID().toLong());
return Long.compare(l1, l2);
}
@Override
public String toString() {
return description;
}
@Override
@SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS",
justification = "Purposefully attempting lookup using NodeID argument in model " +
"vector.")
public boolean equals(Object o) {
if (o instanceof NodeEntry) {
return getNodeID().equals(((NodeEntry) o).getNodeID());
}
if (o instanceof NodeID) {
return getNodeID().equals(o);
}
return false;
}
@Override
public int hashCode() {
return getNodeID().hashCode();
}
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
//log.warning("Received model entry update for " + nodeMemo.getNodeID());
if (propertyChangeEvent.getPropertyName().equals(UPDATE_PROP_SIMPLE_NODE_IDENT)) {
updateDescription();
}
}
public void dispose() {
//log.warning("dispose of " + nodeMemo.getNodeID().toString());
nodeMemo.removePropertyChangeListener(this);
}
}
// -------------- load CDI data ---------
private void loadCdiData() {
if (!replaceData()) {
return;
}
// Load data
loadCdiInputs();
loadCdiOutputs();
loadCdiReceivers();
loadCdiTransmitters();
loadCdiGroups();
for (GroupRow row : _groupList) {
decode(row);
}
setDirty(false);
_groupTable.setRowSelectionInterval(0, 0);
_groupTable.repaint();
_exportButton.setEnabled(true);
_refreshButton.setEnabled(true);
_storeButton.setEnabled(true);
_exportItem.setEnabled(true);
_refreshItem.setEnabled(true);
_storeItem.setEnabled(true);
}
private void pushedRefreshButton(ActionEvent e) {
loadCdiData();
}
private void loadCdiGroups() {
for (int i = 0; i < 16; i++) {
var groupRow = _groupList.get(i);
groupRow.clearLogicList();
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_NAME, i));
groupRow.setName(entry.getValue());
entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_MULTI_LINE, i));
groupRow.setMultiLine(entry.getValue());
}
_groupTable.revalidate();
}
private void loadCdiInputs() {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
var inputRow = _inputList.get((i * 8) + j);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(INPUT_NAME, i, j));
inputRow.setName(entry.getValue());
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_TRUE, i, j));
inputRow.setEventTrue(event.getValue().toShortString());
event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_FALSE, i, j));
inputRow.setEventFalse(event.getValue().toShortString());
}
}
_inputTable.revalidate();
}
private void loadCdiOutputs() {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
var outputRow = _outputList.get((i * 8) + j);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(OUTPUT_NAME, i, j));
outputRow.setName(entry.getValue());
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_TRUE, i, j));
outputRow.setEventTrue(event.getValue().toShortString());
event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_FALSE, i, j));
outputRow.setEventFalse(event.getValue().toShortString());
}
}
_outputTable.revalidate();
}
private void loadCdiReceivers() {
for (int i = 0; i < 16; i++) {
var receiverRow = _receiverList.get(i);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(RECEIVER_NAME, i));
receiverRow.setName(entry.getValue());
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(RECEIVER_EVENT, i));
receiverRow.setEventId(event.getValue().toShortString());
}
_receiverTable.revalidate();
}
private void loadCdiTransmitters() {
for (int i = 0; i < 16; i++) {
var transmitterRow = _transmitterList.get(i);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_NAME, i));
transmitterRow.setName(entry.getValue());
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_EVENT, i));
transmitterRow.setEventId(event.getValue().toShortString());
}
_transmitterTable.revalidate();
}
// -------------- store CDI data ---------
private void pushedStoreButton(ActionEvent e) {
_csvMessages.clear();
_compileNeeded = false;
_storeQueueLength.set(0);
// Store CDI data
storeInputs();
storeOutputs();
storeReceivers();
storeTransmitters();
storeGroups();
setDirty(false);
}
private void storeGroups() {
// store the group data
int currentCount = 0;
for (int i = 0; i < 16; i++) {
var row = _groupList.get(i);
// update the group line
encode(row);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_NAME, i));
if (!row.getName().equals(entry.getValue())) {
entry.addPropertyChangeListener(_entryListener);
entry.setValue(row.getName());
currentCount = _storeQueueLength.incrementAndGet();
}
entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(GROUP_MULTI_LINE, i));
if (!row.getMultiLine().equals(entry.getValue())) {
entry.addPropertyChangeListener(_entryListener);
entry.setValue(row.getMultiLine());
currentCount = _storeQueueLength.incrementAndGet();
_compileNeeded = true;
}
log.debug("Group: {}", row.getName());
log.debug("Logic: {}", row.getMultiLine());
}
log.debug("storeGroups count = {}", currentCount);
}
private void storeInputs() {
int currentCount = 0;
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
var row = _inputList.get((i * 8) + j);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(INPUT_NAME, i, j));
if (!row.getName().equals(entry.getValue())) {
entry.addPropertyChangeListener(_entryListener);
entry.setValue(row.getName());
currentCount = _storeQueueLength.incrementAndGet();
}
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_TRUE, i, j));
if (!row.getEventTrue().equals(event.getValue().toShortString())) {
event.addPropertyChangeListener(_entryListener);
event.setValue(new EventID(row.getEventTrue()));
currentCount = _storeQueueLength.incrementAndGet();
}
event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(INPUT_FALSE, i, j));
if (!row.getEventFalse().equals(event.getValue().toShortString())) {
event.addPropertyChangeListener(_entryListener);
event.setValue(new EventID(row.getEventFalse()));
currentCount = _storeQueueLength.incrementAndGet();
}
}
}
log.debug("storeInputs count = {}", currentCount);
}
private void storeOutputs() {
int currentCount = 0;
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
var row = _outputList.get((i * 8) + j);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(OUTPUT_NAME, i, j));
if (!row.getName().equals(entry.getValue())) {
entry.addPropertyChangeListener(_entryListener);
entry.setValue(row.getName());
currentCount = _storeQueueLength.incrementAndGet();
}
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_TRUE, i, j));
if (!row.getEventTrue().equals(event.getValue().toShortString())) {
event.addPropertyChangeListener(_entryListener);
event.setValue(new EventID(row.getEventTrue()));
currentCount = _storeQueueLength.incrementAndGet();
}
event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(OUTPUT_FALSE, i, j));
if (!row.getEventFalse().equals(event.getValue().toShortString())) {
event.addPropertyChangeListener(_entryListener);
event.setValue(new EventID(row.getEventFalse()));
currentCount = _storeQueueLength.incrementAndGet();
}
}
}
log.debug("storeOutputs count = {}", currentCount);
}
private void storeReceivers() {
int currentCount = 0;
for (int i = 0; i < 16; i++) {
var row = _receiverList.get(i);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(RECEIVER_NAME, i));
if (!row.getName().equals(entry.getValue())) {
entry.addPropertyChangeListener(_entryListener);
entry.setValue(row.getName());
currentCount = _storeQueueLength.incrementAndGet();
}
var event = (ConfigRepresentation.EventEntry) _cdi.getVariableForKey(String.format(RECEIVER_EVENT, i));
if (!row.getEventId().equals(event.getValue().toShortString())) {
event.addPropertyChangeListener(_entryListener);
event.setValue(new EventID(row.getEventId()));
currentCount = _storeQueueLength.incrementAndGet();
}
}
log.debug("storeReceivers count = {}", currentCount);
}
private void storeTransmitters() {
int currentCount = 0;
for (int i = 0; i < 16; i++) {
var row = _transmitterList.get(i);
var entry = (ConfigRepresentation.StringEntry) _cdi.getVariableForKey(String.format(TRANSMITTER_NAME, i));
if (!row.getName().equals(entry.getValue())) {
entry.addPropertyChangeListener(_entryListener);
entry.setValue(row.getName());
currentCount = _storeQueueLength.incrementAndGet();
}
}
log.debug("storeTransmitters count = {}", currentCount);
}
// -------------- Backup Import ---------
private void loadBackupData(ActionEvent m) {
if (!replaceData()) {
return;
}
var fileChooser = new JmriJFileChooser(FileUtil.getUserFilesPath());
fileChooser.setApproveButtonText(Bundle.getMessage("LoadCdiButton"));
fileChooser.setDialogTitle(Bundle.getMessage("LoadCdiTitle"));
var filter = new FileNameExtensionFilter(Bundle.getMessage("LoadCdiFilter"), "txt");
fileChooser.addChoosableFileFilter(filter);
fileChooser.setFileFilter(filter);
int response = fileChooser.showOpenDialog(this);
if (response == JFileChooser.CANCEL_OPTION) {
return;
}
List<String> lines = null;
try {
lines = Files.readAllLines(Paths.get(fileChooser.getSelectedFile().getAbsolutePath()));
} catch (IOException e) {
log.error("Failed to load file.", e);
return;
}
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).startsWith("Logic Inputs.Group")) {
loadBackupInputs(i, lines);
i += 128 * 3;
}
if (lines.get(i).startsWith("Logic Outputs.Group")) {
loadBackupOutputs(i, lines);
i += 128 * 3;
}
if (lines.get(i).startsWith("Track Receivers")) {
loadBackupReceivers(i, lines);
i += 16 * 2;
}
if (lines.get(i).startsWith("Track Transmitters")) {
loadBackupTransmitters(i, lines);
i += 16 * 2;
}
if (lines.get(i).startsWith("Conditionals.Logic")) {
loadBackupGroups(i, lines);
i += 16 * 2;
}
}
for (GroupRow row : _groupList) {
decode(row);
}
setDirty(false);
_groupTable.setRowSelectionInterval(0, 0);
_groupTable.repaint();
_exportButton.setEnabled(true);
_exportItem.setEnabled(true);
}
private String getLineValue(String line) {
if (line.endsWith("=")) {
return "";
}
int index = line.indexOf("=");
var newLine = line.substring(index + 1);
newLine = Util.unescapeString(newLine);
return newLine;
}
private void loadBackupInputs(int index, List<String> lines) {
for (int i = 0; i < 128; i++) {
var inputRow = _inputList.get(i);
inputRow.setName(getLineValue(lines.get(index)));
inputRow.setEventTrue(getLineValue(lines.get(index + 1)));
inputRow.setEventFalse(getLineValue(lines.get(index + 2)));
index += 3;
}
_inputTable.revalidate();
}
private void loadBackupOutputs(int index, List<String> lines) {
for (int i = 0; i < 128; i++) {
var outputRow = _outputList.get(i);
outputRow.setName(getLineValue(lines.get(index)));
outputRow.setEventTrue(getLineValue(lines.get(index + 1)));
outputRow.setEventFalse(getLineValue(lines.get(index + 2)));
index += 3;
}
_outputTable.revalidate();
}
private void loadBackupReceivers(int index, List<String> lines) {
for (int i = 0; i < 16; i++) {
var receiverRow = _receiverList.get(i);
receiverRow.setName(getLineValue(lines.get(index)));
receiverRow.setEventId(getLineValue(lines.get(index + 1)));
index += 2;
}
_receiverTable.revalidate();
}
private void loadBackupTransmitters(int index, List<String> lines) {
for (int i = 0; i < 16; i++) {
var transmitterRow = _transmitterList.get(i);
transmitterRow.setName(getLineValue(lines.get(index)));
transmitterRow.setEventId(getLineValue(lines.get(index + 1)));
index += 2;
}
_transmitterTable.revalidate();
}
private void loadBackupGroups(int index, List<String> lines) {
for (int i = 0; i < 16; i++) {
var groupRow = _groupList.get(i);
groupRow.clearLogicList();
groupRow.setName(getLineValue(lines.get(index)));
groupRow.setMultiLine(getLineValue(lines.get(index + 1)));
index += 2;
}
_groupTable.revalidate();
_logicTable.revalidate();
}
// -------------- CSV Import ---------
private void pushedImportButton(ActionEvent e) {
if (!replaceData()) {
return;
}
if (!setCsvDirectoryPath(true)) {
return;
}
_csvMessages.clear();
importCsvData();
setDirty(false);
_exportButton.setEnabled(true);
_exportItem.setEnabled(true);
if (!_csvMessages.isEmpty()) {
JmriJOptionPane.showMessageDialog(this,
String.join("\n", _csvMessages),
Bundle.getMessage("TitleCsvImport"),
JmriJOptionPane.ERROR_MESSAGE);
}
}
private void importCsvData() {
importGroupLogic();
importInputs();
importOutputs();
importReceivers();
importTransmitters();
_groupTable.setRowSelectionInterval(0, 0);
_groupTable.repaint();
}
private void importGroupLogic() {
List<CSVRecord> records = getCsvRecords("group_logic.csv");
if (records.isEmpty()) {
return;
}
var skipHeader = true;
int groupNumber = -1;
for (CSVRecord record : records) {
if (skipHeader) {
skipHeader = false;
continue;
}
List<String> values = new ArrayList<>();
record.forEach(values::add);
if (values.size() == 1) {
// Create Group
groupNumber++;
var groupRow = _groupList.get(groupNumber);
groupRow.setName(values.get(0));
groupRow.setMultiLine("");
groupRow.clearLogicList();
} else if (values.size() == 5) {
var oper = getEnum(values.get(2));
var logicRow = new LogicRow(values.get(1), oper, values.get(3), values.get(4));
_groupList.get(groupNumber).getLogicList().add(logicRow);
} else {
_csvMessages.add(Bundle.getMessage("ImportGroupError", record.toString()));
}
}
_groupTable.revalidate();
_logicTable.revalidate();
}
private void importInputs() {
List<CSVRecord> records = getCsvRecords("inputs.csv");
if (records.isEmpty()) {
return;
}
for (int i = 0; i < 129; i++) {
if (i == 0) {
continue;
}
var record = records.get(i);
List<String> values = new ArrayList<>();
record.forEach(values::add);
if (values.size() == 4) {
var inputRow = _inputList.get(i - 1);
inputRow.setName(values.get(1));
inputRow.setEventTrue(values.get(2));
inputRow.setEventFalse(values.get(3));
} else {
_csvMessages.add(Bundle.getMessage("ImportInputError", record.toString()));
}
}
_inputTable.revalidate();
}
private void importOutputs() {
List<CSVRecord> records = getCsvRecords("outputs.csv");
if (records.isEmpty()) {
return;
}
for (int i = 0; i < 17; i++) {
if (i == 0) {
continue;
}
var record = records.get(i);
List<String> values = new ArrayList<>();
record.forEach(values::add);
if (values.size() == 4) {
var outputRow = _outputList.get(i - 1);
outputRow.setName(values.get(1));
outputRow.setEventTrue(values.get(2));
outputRow.setEventFalse(values.get(3));
} else {
_csvMessages.add(Bundle.getMessage("ImportOuputError", record.toString()));
}
}
_outputTable.revalidate();
}
private void importReceivers() {
List<CSVRecord> records = getCsvRecords("receivers.csv");
if (records.isEmpty()) {
return;
}
for (int i = 0; i < 17; i++) {
if (i == 0) {
continue;
}
var record = records.get(i);
List<String> values = new ArrayList<>();
record.forEach(values::add);
if (values.size() == 3) {
var receiverRow = _receiverList.get(i - 1);
receiverRow.setName(values.get(1));
receiverRow.setEventId(values.get(2));
} else {
_csvMessages.add(Bundle.getMessage("ImportReceiverError", record.toString()));
}
}
_receiverTable.revalidate();
}
private void importTransmitters() {
List<CSVRecord> records = getCsvRecords("transmitters.csv");
if (records.isEmpty()) {
return;
}
for (int i = 0; i < 17; i++) {
if (i == 0) {
continue;
}
var record = records.get(i);
List<String> values = new ArrayList<>();
record.forEach(values::add);
if (values.size() == 3) {
var transmitterRow = _transmitterList.get(i - 1);
transmitterRow.setName(values.get(1));
transmitterRow.setEventId(values.get(2));
} else {
_csvMessages.add(Bundle.getMessage("ImportTransmitterError", record.toString()));
}
}
_transmitterTable.revalidate();
}
private List<CSVRecord> getCsvRecords(String fileName) {
var recordList = new ArrayList<CSVRecord>();
FileReader fileReader;
try {
fileReader = new FileReader(_csvDirectoryPath + fileName);
} catch (FileNotFoundException ex) {
_csvMessages.add(Bundle.getMessage("ImportFileNotFound", fileName));
return recordList;
}
BufferedReader bufferedReader;
CSVParser csvFile;
try {
bufferedReader = new BufferedReader(fileReader);
csvFile = new CSVParser(bufferedReader, CSVFormat.DEFAULT);
recordList.addAll(csvFile.getRecords());
csvFile.close();
bufferedReader.close();
fileReader.close();
} catch (IOException iox) {
_csvMessages.add(Bundle.getMessage("ImportFileIOError", iox.getMessage(), fileName));
}
return recordList;
}
// -------------- CSV Export ---------
private void pushedExportButton(ActionEvent e) {
if (!setCsvDirectoryPath(false)) {
return;
}
_csvMessages.clear();
exportCsvData();
setDirty(false);
_csvMessages.add(Bundle.getMessage("ExportDone"));
var msgType = JmriJOptionPane.ERROR_MESSAGE;
if (_csvMessages.size() == 1) {
msgType = JmriJOptionPane.INFORMATION_MESSAGE;
}
JmriJOptionPane.showMessageDialog(this,
String.join("\n", _csvMessages),
Bundle.getMessage("TitleCsvExport"),
msgType);
}
private void exportCsvData() {
try {
exportGroupLogic();
exportInputs();
exportOutputs();
exportReceivers();
exportTransmitters();
} catch (IOException ex) {
_csvMessages.add(Bundle.getMessage("ExportIOError", ex.getMessage()));
}
}
private void exportGroupLogic() throws IOException {
var fileWriter = new FileWriter(_csvDirectoryPath + "group_logic.csv");
var bufferedWriter = new BufferedWriter(fileWriter);
var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT);
csvFile.printRecord(Bundle.getMessage("GroupName"), Bundle.getMessage("ColumnLabel"),
Bundle.getMessage("ColumnOper"), Bundle.getMessage("ColumnName"), Bundle.getMessage("ColumnComment"));
for (int i = 0; i < 16; i++) {
var row = _groupList.get(i);
var groupName = row.getName();
csvFile.printRecord(groupName);
var logicRow = row.getLogicList();
for (LogicRow logic : logicRow) {
var operName = logic.getOperName();
csvFile.printRecord("", logic.getLabel(), operName, logic.getName(), logic.getComment());
}
}
// Flush the write buffer and close the file
csvFile.flush();
csvFile.close();
}
private void exportInputs() throws IOException {
var fileWriter = new FileWriter(_csvDirectoryPath + "inputs.csv");
var bufferedWriter = new BufferedWriter(fileWriter);
var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT);
csvFile.printRecord(Bundle.getMessage("ColumnInput"), Bundle.getMessage("ColumnName"),
Bundle.getMessage("ColumnTrue"), Bundle.getMessage("ColumnFalse"));
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
var variable = "I" + i + "." + j;
var row = _inputList.get((i * 8) + j);
csvFile.printRecord(variable, row.getName(), row.getEventTrue(), row.getEventFalse());
}
}
// Flush the write buffer and close the file
csvFile.flush();
csvFile.close();
}
private void exportOutputs() throws IOException {
var fileWriter = new FileWriter(_csvDirectoryPath + "outputs.csv");
var bufferedWriter = new BufferedWriter(fileWriter);
var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT);
csvFile.printRecord(Bundle.getMessage("ColumnOutput"), Bundle.getMessage("ColumnName"),
Bundle.getMessage("ColumnTrue"), Bundle.getMessage("ColumnFalse"));
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 8; j++) {
var variable = "Q" + i + "." + j;
var row = _outputList.get((i * 8) + j);
csvFile.printRecord(variable, row.getName(), row.getEventTrue(), row.getEventFalse());
}
}
// Flush the write buffer and close the file
csvFile.flush();
csvFile.close();
}
private void exportReceivers() throws IOException {
var fileWriter = new FileWriter(_csvDirectoryPath + "receivers.csv");
var bufferedWriter = new BufferedWriter(fileWriter);
var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT);
csvFile.printRecord(Bundle.getMessage("ColumnCircuit"), Bundle.getMessage("ColumnName"),
Bundle.getMessage("ColumnEventID"));
for (int i = 0; i < 16; i++) {
var variable = "Y" + i;
var row = _receiverList.get(i);
csvFile.printRecord(variable, row.getName(), row.getEventId());
}
// Flush the write buffer and close the file
csvFile.flush();
csvFile.close();
}
private void exportTransmitters() throws IOException {
var fileWriter = new FileWriter(_csvDirectoryPath + "transmitters.csv");
var bufferedWriter = new BufferedWriter(fileWriter);
var csvFile = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT);
csvFile.printRecord(Bundle.getMessage("ColumnCircuit"), Bundle.getMessage("ColumnName"),
Bundle.getMessage("ColumnEventID"));
for (int i = 0; i < 16; i++) {
var variable = "Z" + i;
var row = _transmitterList.get(i);
csvFile.printRecord(variable, row.getName(), row.getEventId());
}
// Flush the write buffer and close the file
csvFile.flush();
csvFile.close();
}
/**
* Select the directory that will be used for the CSV file set.
* @param isOpen - True for CSV Import and false for CSV Export.
* @return true if a directory was selected.
*/
private boolean setCsvDirectoryPath(boolean isOpen) {
var directoryChooser = new JmriJFileChooser(FileUtil.getUserFilesPath());
directoryChooser.setApproveButtonText(Bundle.getMessage("SelectCsvButton"));
directoryChooser.setDialogTitle(Bundle.getMessage("SelectCsvTitle"));
directoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int response = 0;
if (isOpen) {
response = directoryChooser.showOpenDialog(this);
} else {
response = directoryChooser.showSaveDialog(this);
}
if (response != JFileChooser.APPROVE_OPTION) {
return false;
}
_csvDirectoryPath = directoryChooser.getSelectedFile().getAbsolutePath() + FileUtil.SEPARATOR;
return true;
}
// -------------- Data Utilities ---------
private void setDirty(boolean dirty) {
_dirty = dirty;
}
private boolean isDirty() {
return _dirty;
}
private boolean replaceData() {
if (isDirty()) {
int response = JmriJOptionPane.showConfirmDialog(this,
Bundle.getMessage("MessageRevert"),
Bundle.getMessage("TitleRevert"),
JmriJOptionPane.YES_NO_OPTION);
if (response != JmriJOptionPane.YES_OPTION) {
return false;
}
}
return true;
}
private void warningDialog(String title, String message) {
JmriJOptionPane.showMessageDialog(this,
message,
title,
JmriJOptionPane.WARNING_MESSAGE);
}
// -------------- Data validation ---------
static boolean isLabelValid(String label) {
if (label.isEmpty()) {
return true;
}
var match = PARSE_LABEL.matcher(label);
if (match.find()) {
return true;
}
JmriJOptionPane.showMessageDialog(null,
Bundle.getMessage("MessageLabel", label),
Bundle.getMessage("TitleLabel"),
JmriJOptionPane.ERROR_MESSAGE);
return false;
}
static boolean isEventValid(String event) {
var valid = true;
if (event.isEmpty()) {
return valid;
}
var hexPairs = event.split("\\.");
if (hexPairs.length != 8) {
valid = false;
} else {
for (int i = 0; i < 8; i++) {
var match = PARSE_HEXPAIR.matcher(hexPairs[i]);
if (!match.find()) {
valid = false;
break;
}
}
}
if (!valid) {
JmriJOptionPane.showMessageDialog(null,
Bundle.getMessage("MessageEvent", event),
Bundle.getMessage("TitleEvent"),
JmriJOptionPane.ERROR_MESSAGE);
log.error("bad event: {}", event);
}
return valid;
}
// -------------- table lists ---------
/**
* The Group row contains the name and the raw data for one of the 16 groups.
* It also contains the decoded logic for the group in the logic list.
*/
static class GroupRow {
String _name;
String _multiLine = "";
List<LogicRow> _logicList = new ArrayList<>();
GroupRow(String name) {
_name = name;
}
String getName() {
return _name;
}
void setName(String newName) {
_name = newName;
}
List<LogicRow> getLogicList() {
return _logicList;
}
void setLogicList(List<LogicRow> logicList) {
_logicList.clear();
_logicList.addAll(logicList);
}
void clearLogicList() {
_logicList.clear();
}
String getMultiLine() {
return _multiLine;
}
void setMultiLine(String newMultiLine) {
_multiLine = newMultiLine.strip();
}
String getSize() {
int size = (_multiLine.length() * 100) / 255;
return String.valueOf(size) + "%";
}
}
/**
* The definition of a logic row
*/
static class LogicRow {
String _label;
Operator _oper;
String _name;
String _comment;
LogicRow(String label, Operator oper, String name, String comment) {
_label = label;
_oper = oper;
_name = name;
_comment = comment;
}
String getLabel() {
return _label;
}
void setLabel(String newLabel) {
var label = newLabel.trim();
if (isLabelValid(label)) {
_label = label;
}
}
Operator getOper() {
return _oper;
}
String getOperName() {
if (_oper == null) {
return "";
}
String operName = _oper.name();
// Fix special enums
if (operName.equals("Cp")) {
operName = ")";
} else if (operName.equals("EQ")) {
operName = "=";
} else if (operName.contains("p")) {
operName = operName.replace("p", "(");
}
return operName;
}
void setOper(Operator newOper) {
_oper = newOper;
}
String getName() {
return _name;
}
void setName(String newName) {
_name = newName.trim();
}
String getComment() {
return _comment;
}
void setComment(String newComment) {
_comment = newComment;
}
}
/**
* The name and assigned true and false events for an Input.
*/
static class InputRow {
String _name;
String _eventTrue;
String _eventFalse;
InputRow(String name, String eventTrue, String eventFalse) {
_name = name;
_eventTrue = eventTrue;
_eventFalse = eventFalse;
}
String getName() {
return _name;
}
void setName(String newName) {
_name = newName.trim();
}
String getEventTrue() {
if (_eventTrue.length() == 0) return "00.00.00.00.00.00.00.00";
return _eventTrue;
}
void setEventTrue(String newEventTrue) {
var event = newEventTrue.trim();
if (isEventValid(event)) {
_eventTrue = event;
}
}
String getEventFalse() {
if (_eventFalse.length() == 0) return "00.00.00.00.00.00.00.00";
return _eventFalse;
}
void setEventFalse(String newEventFalse) {
var event = newEventFalse.trim();
if (isEventValid(event)) {
_eventFalse = event;
}
}
}
/**
* The name and assigned true and false events for an Output.
*/
static class OutputRow {
String _name;
String _eventTrue;
String _eventFalse;
OutputRow(String name, String eventTrue, String eventFalse) {
_name = name;
_eventTrue = eventTrue;
_eventFalse = eventFalse;
}
String getName() {
return _name;
}
void setName(String newName) {
_name = newName.trim();
}
String getEventTrue() {
if (_eventTrue.length() == 0) return "00.00.00.00.00.00.00.00";
return _eventTrue;
}
void setEventTrue(String newEventTrue) {
var event = newEventTrue.trim();
if (isEventValid(event)) {
_eventTrue = event;
}
}
String getEventFalse() {
if (_eventFalse.length() == 0) return "00.00.00.00.00.00.00.00";
return _eventFalse;
}
void setEventFalse(String newEventFalse) {
var event = newEventFalse.trim();
if (isEventValid(event)) {
_eventFalse = event;
}
}
}
/**
* The name and assigned event id for a circuit receiver.
*/
static class ReceiverRow {
String _name;
String _eventid;
ReceiverRow(String name, String eventid) {
_name = name;
_eventid = eventid;
}
String getName() {
return _name;
}
void setName(String newName) {
_name = newName.trim();
}
String getEventId() {
if (_eventid.length() == 0) return "00.00.00.00.00.00.00.00";
return _eventid;
}
void setEventId(String newEventid) {
var event = newEventid.trim();
if (isEventValid(event)) {
_eventid = event;
}
}
}
/**
* The name and assigned event id for a circuit transmitter.
*/
static class TransmitterRow {
String _name;
String _eventid;
TransmitterRow(String name, String eventid) {
_name = name;
_eventid = eventid;
}
String getName() {
return _name;
}
void setName(String newName) {
_name = newName.trim();
}
String getEventId() {
if (_eventid.length() == 0) return "00.00.00.00.00.00.00.00";
return _eventid;
}
void setEventId(String newEventid) {
var event = newEventid.trim();
if (isEventValid(event)) {
_eventid = event;
}
}
}
// -------------- table models ---------
/**
* TableModel for Group table entries.
*/
class GroupModel extends AbstractTableModel {
GroupModel() {
}
public static final int ROW_COLUMN = 0;
public static final int NAME_COLUMN = 1;
@Override
public int getRowCount() {
return _groupList.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Class<?> getColumnClass(int c) {
return String.class;
}
@Override
public String getColumnName(int col) {
switch (col) {
case ROW_COLUMN:
return "";
case NAME_COLUMN:
return Bundle.getMessage("ColumnName");
default:
return "unknown"; // NOI18N
}
}
@Override
public Object getValueAt(int r, int c) {
switch (c) {
case ROW_COLUMN:
return r + 1;
case NAME_COLUMN:
return _groupList.get(r).getName();
default:
return null;
}
}
@Override
public void setValueAt(Object type, int r, int c) {
switch (c) {
case NAME_COLUMN:
_groupList.get(r).setName((String) type);
setDirty(true);
break;
default:
break;
}
}
@Override
public boolean isCellEditable(int r, int c) {
return (c == NAME_COLUMN);
}
public int getPreferredWidth(int col) {
switch (col) {
case ROW_COLUMN:
return new JTextField(4).getPreferredSize().width;
case NAME_COLUMN:
return new JTextField(20).getPreferredSize().width;
default:
log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N
return new JTextField(8).getPreferredSize().width;
}
}
}
/**
* TableModel for STL table entries.
*/
class LogicModel extends AbstractTableModel {
LogicModel() {
}
public static final int LABEL_COLUMN = 0;
public static final int OPER_COLUMN = 1;
public static final int NAME_COLUMN = 2;
public static final int COMMENT_COLUMN = 3;
@Override
public int getRowCount() {
var logicList = _groupList.get(_groupRow).getLogicList();
return logicList.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public Class<?> getColumnClass(int c) {
if (c == OPER_COLUMN) return JComboBox.class;
return String.class;
}
@Override
public String getColumnName(int col) {
switch (col) {
case LABEL_COLUMN:
return Bundle.getMessage("ColumnLabel"); // NOI18N
case OPER_COLUMN:
return Bundle.getMessage("ColumnOper"); // NOI18N
case NAME_COLUMN:
return Bundle.getMessage("ColumnName"); // NOI18N
case COMMENT_COLUMN:
return Bundle.getMessage("ColumnComment"); // NOI18N
default:
return "unknown"; // NOI18N
}
}
@Override
public Object getValueAt(int r, int c) {
var logicList = _groupList.get(_groupRow).getLogicList();
switch (c) {
case LABEL_COLUMN:
return logicList.get(r).getLabel();
case OPER_COLUMN:
return logicList.get(r).getOper();
case NAME_COLUMN:
return logicList.get(r).getName();
case COMMENT_COLUMN:
return logicList.get(r).getComment();
default:
return null;
}
}
@Override
public void setValueAt(Object type, int r, int c) {
var logicList = _groupList.get(_groupRow).getLogicList();
switch (c) {
case LABEL_COLUMN:
logicList.get(r).setLabel((String) type);
setDirty(true);
break;
case OPER_COLUMN:
var z = (Operator) type;
if (z != null) {
if (z.name().startsWith("z")) {
return;
}
if (z.name().equals("x0")) {
logicList.get(r).setOper(null);
return;
}
}
logicList.get(r).setOper((Operator) type);
setDirty(true);
break;
case NAME_COLUMN:
logicList.get(r).setName((String) type);
setDirty(true);
break;
case COMMENT_COLUMN:
logicList.get(r).setComment((String) type);
setDirty(true);
break;
default:
break;
}
}
@Override
public boolean isCellEditable(int r, int c) {
return true;
}
public int getPreferredWidth(int col) {
switch (col) {
case LABEL_COLUMN:
return new JTextField(6).getPreferredSize().width;
case OPER_COLUMN:
return new JTextField(20).getPreferredSize().width;
case NAME_COLUMN:
case COMMENT_COLUMN:
return new JTextField(40).getPreferredSize().width;
default:
log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N
return new JTextField(8).getPreferredSize().width;
}
}
}
/**
* TableModel for Input table entries.
*/
class InputModel extends AbstractTableModel {
InputModel() {
}
public static final int INPUT_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public static final int TRUE_COLUMN = 2;
public static final int FALSE_COLUMN = 3;
@Override
public int getRowCount() {
return _inputList.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public Class<?> getColumnClass(int c) {
return String.class;
}
@Override
public String getColumnName(int col) {
switch (col) {
case INPUT_COLUMN:
return Bundle.getMessage("ColumnInput"); // NOI18N
case NAME_COLUMN:
return Bundle.getMessage("ColumnName"); // NOI18N
case TRUE_COLUMN:
return Bundle.getMessage("ColumnTrue"); // NOI18N
case FALSE_COLUMN:
return Bundle.getMessage("ColumnFalse"); // NOI18N
default:
return "unknown"; // NOI18N
}
}
@Override
public Object getValueAt(int r, int c) {
switch (c) {
case INPUT_COLUMN:
int grp = r / 8;
int rem = r % 8;
return "I" + grp + "." + rem;
case NAME_COLUMN:
return _inputList.get(r).getName();
case TRUE_COLUMN:
return _inputList.get(r).getEventTrue();
case FALSE_COLUMN:
return _inputList.get(r).getEventFalse();
default:
return null;
}
}
@Override
public void setValueAt(Object type, int r, int c) {
switch (c) {
case NAME_COLUMN:
_inputList.get(r).setName((String) type);
setDirty(true);
break;
case TRUE_COLUMN:
_inputList.get(r).setEventTrue((String) type);
setDirty(true);
break;
case FALSE_COLUMN:
_inputList.get(r).setEventFalse((String) type);
setDirty(true);
break;
default:
break;
}
}
@Override
public boolean isCellEditable(int r, int c) {
return ((c == NAME_COLUMN) || (c == TRUE_COLUMN) || (c == FALSE_COLUMN));
}
public int getPreferredWidth(int col) {
switch (col) {
case INPUT_COLUMN:
return new JTextField(6).getPreferredSize().width;
case NAME_COLUMN:
return new JTextField(50).getPreferredSize().width;
case TRUE_COLUMN:
case FALSE_COLUMN:
return new JTextField(20).getPreferredSize().width;
default:
log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N
return new JTextField(8).getPreferredSize().width;
}
}
}
/**
* TableModel for Output table entries.
*/
class OutputModel extends AbstractTableModel {
OutputModel() {
}
public static final int OUTPUT_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public static final int TRUE_COLUMN = 2;
public static final int FALSE_COLUMN = 3;
@Override
public int getRowCount() {
return _outputList.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public Class<?> getColumnClass(int c) {
return String.class;
}
@Override
public String getColumnName(int col) {
switch (col) {
case OUTPUT_COLUMN:
return Bundle.getMessage("ColumnOutput"); // NOI18N
case NAME_COLUMN:
return Bundle.getMessage("ColumnName"); // NOI18N
case TRUE_COLUMN:
return Bundle.getMessage("ColumnTrue"); // NOI18N
case FALSE_COLUMN:
return Bundle.getMessage("ColumnFalse"); // NOI18N
default:
return "unknown"; // NOI18N
}
}
@Override
public Object getValueAt(int r, int c) {
switch (c) {
case OUTPUT_COLUMN:
int grp = r / 8;
int rem = r % 8;
return "Q" + grp + "." + rem;
case NAME_COLUMN:
return _outputList.get(r).getName();
case TRUE_COLUMN:
return _outputList.get(r).getEventTrue();
case FALSE_COLUMN:
return _outputList.get(r).getEventFalse();
default:
return null;
}
}
@Override
public void setValueAt(Object type, int r, int c) {
switch (c) {
case NAME_COLUMN:
_outputList.get(r).setName((String) type);
setDirty(true);
break;
case TRUE_COLUMN:
_outputList.get(r).setEventTrue((String) type);
setDirty(true);
break;
case FALSE_COLUMN:
_outputList.get(r).setEventFalse((String) type);
setDirty(true);
break;
default:
break;
}
}
@Override
public boolean isCellEditable(int r, int c) {
return ((c == NAME_COLUMN) || (c == TRUE_COLUMN) || (c == FALSE_COLUMN));
}
public int getPreferredWidth(int col) {
switch (col) {
case OUTPUT_COLUMN:
return new JTextField(6).getPreferredSize().width;
case NAME_COLUMN:
return new JTextField(50).getPreferredSize().width;
case TRUE_COLUMN:
case FALSE_COLUMN:
return new JTextField(20).getPreferredSize().width;
default:
log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N
return new JTextField(8).getPreferredSize().width;
}
}
}
/**
* TableModel for circuit receiver table entries.
*/
class ReceiverModel extends AbstractTableModel {
ReceiverModel() {
}
public static final int CIRCUIT_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public static final int EVENTID_COLUMN = 2;
@Override
public int getRowCount() {
return _receiverList.size();
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public Class<?> getColumnClass(int c) {
return String.class;
}
@Override
public String getColumnName(int col) {
switch (col) {
case CIRCUIT_COLUMN:
return Bundle.getMessage("ColumnCircuit"); // NOI18N
case NAME_COLUMN:
return Bundle.getMessage("ColumnName"); // NOI18N
case EVENTID_COLUMN:
return Bundle.getMessage("ColumnEventID"); // NOI18N
default:
return "unknown"; // NOI18N
}
}
@Override
public Object getValueAt(int r, int c) {
switch (c) {
case CIRCUIT_COLUMN:
return "Y" + r;
case NAME_COLUMN:
return _receiverList.get(r).getName();
case EVENTID_COLUMN:
return _receiverList.get(r).getEventId();
default:
return null;
}
}
@Override
public void setValueAt(Object type, int r, int c) {
switch (c) {
case NAME_COLUMN:
_receiverList.get(r).setName((String) type);
setDirty(true);
break;
case EVENTID_COLUMN:
_receiverList.get(r).setEventId((String) type);
setDirty(true);
break;
default:
break;
}
}
@Override
public boolean isCellEditable(int r, int c) {
return ((c == NAME_COLUMN) || (c == EVENTID_COLUMN));
}
public int getPreferredWidth(int col) {
switch (col) {
case CIRCUIT_COLUMN:
return new JTextField(6).getPreferredSize().width;
case NAME_COLUMN:
return new JTextField(50).getPreferredSize().width;
case EVENTID_COLUMN:
return new JTextField(20).getPreferredSize().width;
default:
log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N
return new JTextField(8).getPreferredSize().width;
}
}
}
/**
* TableModel for circuit transmitter table entries.
*/
class TransmitterModel extends AbstractTableModel {
TransmitterModel() {
}
public static final int CIRCUIT_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public static final int EVENTID_COLUMN = 2;
@Override
public int getRowCount() {
return _transmitterList.size();
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public Class<?> getColumnClass(int c) {
return String.class;
}
@Override
public String getColumnName(int col) {
switch (col) {
case CIRCUIT_COLUMN:
return Bundle.getMessage("ColumnCircuit"); // NOI18N
case NAME_COLUMN:
return Bundle.getMessage("ColumnName"); // NOI18N
case EVENTID_COLUMN:
return Bundle.getMessage("ColumnEventID"); // NOI18N
default:
return "unknown"; // NOI18N
}
}
@Override
public Object getValueAt(int r, int c) {
switch (c) {
case CIRCUIT_COLUMN:
return "Z" + r;
case NAME_COLUMN:
return _transmitterList.get(r).getName();
case EVENTID_COLUMN:
return _transmitterList.get(r).getEventId();
default:
return null;
}
}
@Override
public void setValueAt(Object type, int r, int c) {
switch (c) {
case NAME_COLUMN:
_transmitterList.get(r).setName((String) type);
setDirty(true);
break;
case EVENTID_COLUMN:
_transmitterList.get(r).setEventId((String) type);
setDirty(true);
break;
default:
break;
}
}
@Override
public boolean isCellEditable(int r, int c) {
return ((c == NAME_COLUMN) || (c == EVENTID_COLUMN));
}
public int getPreferredWidth(int col) {
switch (col) {
case CIRCUIT_COLUMN:
return new JTextField(6).getPreferredSize().width;
case NAME_COLUMN:
return new JTextField(50).getPreferredSize().width;
case EVENTID_COLUMN:
return new JTextField(20).getPreferredSize().width;
default:
log.warn("Unexpected column in getPreferredWidth: {}", col); // NOI18N
return new JTextField(8).getPreferredSize().width;
}
}
}
// -------------- Operator Enum ---------
public enum Operator {
x0(Bundle.getMessage("Separator0")),
z1(Bundle.getMessage("Separator1")),
A(Bundle.getMessage("OperatorA")),
AN(Bundle.getMessage("OperatorAN")),
O(Bundle.getMessage("OperatorO")),
ON(Bundle.getMessage("OperatorON")),
X(Bundle.getMessage("OperatorX")),
XN(Bundle.getMessage("OperatorXN")),
z2(Bundle.getMessage("Separator2")), // The STL parens are represented by lower case p
Ap(Bundle.getMessage("OperatorAp")),
ANp(Bundle.getMessage("OperatorANp")),
Op(Bundle.getMessage("OperatorOp")),
ONp(Bundle.getMessage("OperatorONp")),
Xp(Bundle.getMessage("OperatorXp")),
XNp(Bundle.getMessage("OperatorXNp")),
Cp(Bundle.getMessage("OperatorCp")), // Close paren
z3(Bundle.getMessage("Separator3")),
EQ(Bundle.getMessage("OperatorEQ")), // = operator
R(Bundle.getMessage("OperatorR")),
S(Bundle.getMessage("OperatorS")),
z4(Bundle.getMessage("Separator4")),
NOT(Bundle.getMessage("OperatorNOT")),
SET(Bundle.getMessage("OperatorSET")),
CLR(Bundle.getMessage("OperatorCLR")),
SAVE(Bundle.getMessage("OperatorSAVE")),
z5(Bundle.getMessage("Separator5")),
JU(Bundle.getMessage("OperatorJU")),
JC(Bundle.getMessage("OperatorJC")),
JCN(Bundle.getMessage("OperatorJCN")),
JCB(Bundle.getMessage("OperatorJCB")),
JNB(Bundle.getMessage("OperatorJNB")),
JBI(Bundle.getMessage("OperatorJBI")),
JNBI(Bundle.getMessage("OperatorJNBI")),
z6(Bundle.getMessage("Separator6")),
FN(Bundle.getMessage("OperatorFN")),
FP(Bundle.getMessage("OperatorFP")),
z7(Bundle.getMessage("Separator7")),
L(Bundle.getMessage("OperatorL")),
FR(Bundle.getMessage("OperatorFR")),
SP(Bundle.getMessage("OperatorSP")),
SE(Bundle.getMessage("OperatorSE")),
SD(Bundle.getMessage("OperatorSD")),
SS(Bundle.getMessage("OperatorSS")),
SF(Bundle.getMessage("OperatorSF"));
private final String _text;
private Operator(String text) {
this._text = text;
}
@Override
public String toString() {
return _text;
}
}
// -------------- misc items ---------
@Override
public java.util.List<JMenu> getMenus() {
// create a file menu
var retval = new ArrayList<JMenu>();
var fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
_refreshItem = new JMenuItem(Bundle.getMessage("MenuRefresh"));
_storeItem = new JMenuItem(Bundle.getMessage("MenuStore"));
_importItem = new JMenuItem(Bundle.getMessage("MenuImport"));
_exportItem = new JMenuItem(Bundle.getMessage("MenuExport"));
_loadItem = new JMenuItem(Bundle.getMessage("MenuLoad"));
_refreshItem.addActionListener(this::pushedRefreshButton);
_storeItem.addActionListener(this::pushedStoreButton);
_importItem.addActionListener(this::pushedImportButton);
_exportItem.addActionListener(this::pushedExportButton);
_loadItem.addActionListener(this::loadBackupData);
fileMenu.add(_refreshItem);
fileMenu.add(_storeItem);
fileMenu.addSeparator();
fileMenu.add(_importItem);
fileMenu.add(_exportItem);
fileMenu.addSeparator();
fileMenu.add(_loadItem);
_refreshItem.setEnabled(false);
_storeItem.setEnabled(false);
_exportItem.setEnabled(false);
retval.add(fileMenu);
return retval;
}
@Override
public void dispose() {
// and complete this
super.dispose();
}
@Override
public String getHelpTarget() {
return "package.jmri.jmrix.openlcb.swing.stleditor.StlEditorPane";
}
@Override
public String getTitle() {
if (_canMemo != null) {
return (_canMemo.getUserName() + " STL Editor");
}
return Bundle.getMessage("TitleSTLEditor");
}
/**
* Nested class to create one of these using old-style defaults
*/
public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction {
public Default() {
super("STL Editor",
new jmri.util.swing.sdi.JmriJFrameInterface(),
StlEditorPane.class.getName(),
jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class));
}
}
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(StlEditorPane.class);
}