diff --git a/manifest b/manifest index 062dee08..e12bee53 100644 --- a/manifest +++ b/manifest @@ -1,6 +1,6 @@ Manifest-Version: 1.0 Main-Class: scenarios.ScenarioRunner -Class-Path: lib/junit.jar lib/jdom-2.0.5.jar lib/jdom.jar lib/jlfgr-1_0.jar +Class-Path: lib/junit.jar lib/jdom2-2.0.6.jar lib/jlfgr-1_0.jar lib/xercesImpl.jar Name: org.openlcb Specification-Title: OpenLCB diff --git a/src/org/openlcb/EventID.java b/src/org/openlcb/EventID.java index 9bc12381..472f2830 100644 --- a/src/org/openlcb/EventID.java +++ b/src/org/openlcb/EventID.java @@ -48,6 +48,15 @@ public EventID(@NonNull String value) { System.arraycopy(data, 0, this.contents, 0, BYTECOUNT); } + // note long's 64th bit is a sign + @CheckReturnValue + public EventID(long value) { + this.contents = new byte[BYTECOUNT]; + for (int index = 0; index < 8; index++) { + contents[index] = (byte)((value>>(8*(7-index))) & 0xFF); + } + } + byte[] contents; @CheckReturnValue @@ -117,4 +126,185 @@ public long toLong() { } return ret; } + + /** + * Take the eventID from a range, and return + * the lower flag bytes as a hex dotted string. + * @return the lower flag bytes + */ + public long rangeSuffix() { + // find the mask value + long eid = this.toLong(); + long sampleBit = eid & 0x01; + long mask = 0L; + while ( (eid &0x01L) == sampleBit) { + mask = (mask <<1) | 0x01; + eid = eid >> 1; + } + return mask; + } + + + /** + * Decode well-known and specifically defined event IDs + * @return "" if nothing interesting about the event + */ + public String parse() { + String eid = this.toShortString().substring(0, 2); + switch (eid) { + case "00": + return reserved(); + case "01": + return wellKnown(); + case "09": + if (this.toShortString().startsWith("09.00.99.FF")) { + return trainSearch(); + } + // deliberately falling through + //$FALL-THROUGH$ + default: + return ""; + } + } + + protected String reserved() { + return "Reserved "+this.toShortString(); + } + + protected String wellKnown() { + String eid = this.toShortString(); + switch (eid) { + case "01.00.00.00.00.00.FF.FF": + return "Emergency off"; + case "01.00.00.00.00.00.FF.FE": + return "Clear Emergency Off"; + case "01.00.00.00.00.00.FF.FD": + return "Emergency stop of all operations"; + case "01.00.00.00.00.00.FF.FC": + return "Clear emergency stop of all operations"; + case "01.00.00.00.00.00.FF.F8": + return "Node recorded a new log entry"; + case "01.00.00.00.00.00.FF.F1": + return "Power supply brownout detected below minimum required by node"; + case "01.00.00.00.00.00.FF.F0": + return "Power supply brownout detected below minimum required by standard"; + case "01.00.00.00.00.00.FE.00": + return "Ident button combination pressed"; + case "01.00.00.00.00.00.FD.01": + return "Link error code 1 – the specific meaning is link wire protocol specific"; + case "01.00.00.00.00.00.FD.02": + return "Link error code 2"; + case "01.00.00.00.00.00.FD.03": + return "Link error code 3"; + case "01.00.00.00.00.00.FD.04": + return "Link error code 4"; + + case "01.01.00.00.00.00.02.01": + return "Duplicate Node ID Detected"; + case "01.01.00.00.00.00.03.03": + return "This node is a Train"; + case "01.01.00.00.00.00.03.04": + return "This node is a Train Control Proxy"; + case "01.01.00.00.00.00.06.01": + return "Firmware Corrupted"; + case "01.01.00.00.00.00.06.02": + return "Firmware Upgrade Request by Hardware Switch"; + + default: + // check for fastclock and DCC ranges + if (eid.startsWith("01.01.00.00.01")) { + return fastClock(); + } else if (eid.startsWith("01.01.02")) { + return dccRange(); + } else { + return "Well-Known "+eid; + } + } + } + + protected String fastClock() { + String clockNum = this.toShortString().substring(16, 17); + byte[] contents = this.getContents(); + int lowByte = contents[7]&0xFF; + int highByte = contents[6]&0xFF; + int highByteMasked = 0x7F&highByte; + int bothBytes = highByte*256+lowByte; + String function = ""; + + String set = ((0x80 & highByte) == 0x80) ? "Set " : ""; + + if ((highByte & 0xF0) == 0xC0) { // set rate + int rate = (highByte&0xF)*256+lowByte; + function = "Set rate "+(rate/4.); + } else if (bothBytes == 0xF000) { // + function = "Query"; + } else if (bothBytes == 0xF001) { // + function = "Stop"; + } else if (bothBytes == 0xF002) { // + function = "Start"; + } else if (bothBytes == 0xF003) { // + function = "Date Rollover"; + } else if (highByteMasked < 24) { // time + String lowString = "00"+Integer.toString(lowByte); + lowString = lowString.substring(lowString.length()-2); + function = set+"time "+highByteMasked+":"+lowString; + } else if (highByteMasked <= 0x2C) { // date + String lowString = "00"+Integer.toString(lowByte); + lowString = lowString.substring(lowString.length()-2); + function = set+"date "+(highByteMasked-0x20)+"/"+lowString; + } else if (highByteMasked < 0x40) { // year + int year = (highByteMasked*256+lowByte)-0x3000; + function = set+"year "+year; + } else { + function = "reserved"; + } + + return "Fast Clock "+clockNum+" "+function; + } + + protected String dccRange() { + String eid = this.toShortString(); + if (eid.startsWith("01.01.02.00.00.FF")) { + return "DCC Acc Decoder "+parseAccDecoderNumber(this.toLong())+" active"; + + } else if (eid.startsWith("01.01.02.00.00.FE")) { + return "DCC Acc Decoder "+parseAccDecoderNumber(this.toLong())+" inactive"; + + } else if (eid.startsWith("01.01.02.00.00.FD")) { + return "DCC Turnout Feedback "+(this.toLong()&0x7FFL)+" active"; + + } else if (eid.startsWith("01.01.02.00.00.FC")) { + return "DCC Turnout Feedback "+(this.toLong()&0x7FFL)+" inactive"; + + } else if (eid.startsWith("01.01.02.00.00.FB")) { + return "DCC Sensor "+(this.toLong()&0xFFFL)+" active"; + + } else if (eid.startsWith("01.01.02.00.00.FA")) { + return "DCC Sensor "+(this.toLong()&0xFFFL)+" inactive"; + + } else if (eid.startsWith("01.01.02.00.01")) { + return "DCC Extended Accessory " + +((this.toLong()>>8)&0x7FFL) + +" "+(this.toLong()&0xFF); + } else { + return "DCC Well-Known "+this; + } + } + + protected String parseAccDecoderNumber(long event) { + int input = (int)(event&0xFFF); + + int result = (((input-8)>>1)+1)&0x7FF; + + if ((event & 0x01) !=0 ) + return ""+result+" N/C/On "; + else { + return ""+result+" R/T/Off"; + } + } + + protected String trainSearch() { + return "Train Search"; + } + } diff --git a/src/org/openlcb/EventNameStore.java b/src/org/openlcb/EventNameStore.java new file mode 100644 index 00000000..c35b1912 --- /dev/null +++ b/src/org/openlcb/EventNameStore.java @@ -0,0 +1,23 @@ +package org.openlcb; + +/** + * Provide a mapping from EventID from/to User readable event names + * + * Intended to be implemented and provided externally to this library. + * The event names here obey some external syntax, and are sourced + * externally. + * + * This is deliberately separate from the + * {@link org.openlcb.implementations.EventTable} + * which is an internal structure specific to this library. + * + * + * @author Bob Jacobsen Copyright (C) 2024 + */ +public interface EventNameStore { + + public EventID getEventID(String eventName); + + public String getEventName(EventID eventID); + +} diff --git a/src/org/openlcb/ProducerConsumerEventReportMessage.java b/src/org/openlcb/ProducerConsumerEventReportMessage.java index 5c0f7739..a403959b 100644 --- a/src/org/openlcb/ProducerConsumerEventReportMessage.java +++ b/src/org/openlcb/ProducerConsumerEventReportMessage.java @@ -55,6 +55,7 @@ public void applyTo(MessageDecoder decoder, Connection sender) { /** * Get the size of the payload, which doesn't include * the eight bytes of the event ID itself + * @return payload size in bytes */ public int getPayloadSize() { if (payload == null) return 0; @@ -88,7 +89,7 @@ public byte[] getPayloadArray() { @Override public String toString() { - String retval = " Producer/Consumer Event Report "+eventID.toString(); + String retval = super.toString() + " Producer/Consumer Event Report "+eventID.toString(); if ( getPayloadSize() > 0 ) { retval = retval + " payload of "+getPayloadSize()+" : "; diff --git a/src/org/openlcb/Utilities.java b/src/org/openlcb/Utilities.java index a0937c2e..d2ab7da0 100644 --- a/src/org/openlcb/Utilities.java +++ b/src/org/openlcb/Utilities.java @@ -222,6 +222,8 @@ static public void HostToNetworkUint48(byte[] arr, int offset, long value) { /** * Find the longest starting substring of a List of Strings. * This is useful for finding e.g. the common prefix of a replication dump + * @param list of Strings to compare + * @return longest leading substring */ @CheckReturnValue @NonNull diff --git a/src/org/openlcb/can/AliasMap.java b/src/org/openlcb/can/AliasMap.java index f20d8498..933aba93 100644 --- a/src/org/openlcb/can/AliasMap.java +++ b/src/org/openlcb/can/AliasMap.java @@ -65,6 +65,8 @@ public void processFrame(OpenLcbCanFrame f) { /** * Store a local alias which should be kept when * the caches are cleared by an AME global + * @param alias alias of the local node to preserve + * @param nid NodeID of the local alias to preserve */ public void insertLocalAlias(int alias, NodeID nid) { localAliases.put(alias, nid); diff --git a/src/org/openlcb/can/NIDaAlgorithm.java b/src/org/openlcb/can/NIDaAlgorithm.java index f845e50a..1070d0bd 100644 --- a/src/org/openlcb/can/NIDaAlgorithm.java +++ b/src/org/openlcb/can/NIDaAlgorithm.java @@ -87,6 +87,7 @@ public int getNIDa() { } /** + * @param f frame to check * @return True if frame matches current NodeID */ boolean compareDataAndNodeID(OpenLcbCanFrame f) { diff --git a/src/org/openlcb/cdi/CdiRep.java b/src/org/openlcb/cdi/CdiRep.java index 6864cb29..fcc9deff 100644 --- a/src/org/openlcb/cdi/CdiRep.java +++ b/src/org/openlcb/cdi/CdiRep.java @@ -92,6 +92,8 @@ public static interface Map { /** * Add an item to the map. Useful if e.g. a non-mapped * value is found in a location. + * @param key to be added + * @param entry to be added */ public void addItemToMap(String key, String entry); } diff --git a/src/org/openlcb/cdi/cmd/BackupConfig.java b/src/org/openlcb/cdi/cmd/BackupConfig.java index c5690195..029c8f0d 100644 --- a/src/org/openlcb/cdi/cmd/BackupConfig.java +++ b/src/org/openlcb/cdi/cmd/BackupConfig.java @@ -43,6 +43,7 @@ public static void writeConfigToFile(String fileName, ConfigRepresentation repr) /** * @param writer Receives output. Flushed at end, but not closed. * @param repr Representation containing contents to be written. + * @throws IOException if trouble writing out */ public static void writeConfigToWriter(BufferedWriter writer, ConfigRepresentation repr) throws IOException { @@ -61,8 +62,7 @@ public void visitInt(ConfigRepresentation.IntegerEntry e) { @Override public void visitEvent(ConfigRepresentation.EventEntry e) { - writeEntry(finalWriter, e.key, Utilities.toHexDotsString(e.getValue - ().getContents())); + writeEntry(finalWriter, e.key, e.getValue()); } } ); diff --git a/src/org/openlcb/cdi/impl/ConfigRepresentation.java b/src/org/openlcb/cdi/impl/ConfigRepresentation.java index cc22cadd..172df1bb 100644 --- a/src/org/openlcb/cdi/impl/ConfigRepresentation.java +++ b/src/org/openlcb/cdi/impl/ConfigRepresentation.java @@ -60,7 +60,8 @@ public class ConfigRepresentation extends DefaultPropertyListenerSupport { // Last time the progressbar was updated from the load. private long lastProgress; - + public org.openlcb.cdi.swing.CdiPanel.GuiItemFactory factory = null; + /** * Connects to a node, populates the cache by fetching and parsing the CDI. * @param connection OpenLCB network. @@ -589,6 +590,7 @@ public boolean isHidden() { /** * Does this entry carry the readOnly hint? + * @return true if it carries the readOnly hint */ public boolean isReadOnlyConfigured() { return group.isReadOnly(); @@ -734,19 +736,21 @@ public CdiRep.Item getCdiItem() { @Override protected void updateVisibleValue() { - EventID v = getValue(); + String v = getValue(); if (v != null) { - lastVisibleValue = Utilities.toHexDotsString(v.getContents()); + lastVisibleValue = v; } else { lastVisibleValue = ""; } } - public EventID getValue() { + public String getValue() { MemorySpaceCache cache = getCacheForSpace(space); byte[] b = cache.read(origin, size); if (b == null) return null; - return new EventID(b); + EventID eid = new EventID(b); + if (factory == null) return eid.toShortString(); + return factory.getStringFromEventID(eid); } public void setValue(EventID event) { diff --git a/src/org/openlcb/cdi/swing/CdiPanel.java b/src/org/openlcb/cdi/swing/CdiPanel.java index 47a38d6f..87e5256b 100644 --- a/src/org/openlcb/cdi/swing/CdiPanel.java +++ b/src/org/openlcb/cdi/swing/CdiPanel.java @@ -77,7 +77,6 @@ import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFileChooser; -import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; @@ -88,6 +87,7 @@ import javax.swing.JSlider; import javax.swing.JTabbedPane; import javax.swing.JTextArea; +import javax.swing.text.JTextComponent; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.JRadioButton; @@ -201,6 +201,7 @@ public void release() { task.run(); } cleanupTasks.clear(); + tabColorTimerStopped = true; tabColorTimer.cancel(); } @@ -225,7 +226,8 @@ public void initComponents(ConfigRepresentation rep, GuiItemFactory factory) { setAlignmentX(Component.LEFT_ALIGNMENT); this.rep = rep; this.factory = factory; - + rep.factory = factory; + contentPanel = new JPanel(); contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); contentPanel.setAlignmentX(Component.LEFT_ALIGNMENT); @@ -267,9 +269,9 @@ public void initComponents(ConfigRepresentation rep, GuiItemFactory factory) { bottomPanel.add(bb); if (rep.getConnection() != null && rep.getRemoteNodeID() != null) { - bb = new JButton("Reboot"); + bb = new JButton("Restart"); bb.setToolTipText("Requests the configured node to restart."); - bb.addActionListener(actionEvent -> runReboot()); + bb.addActionListener(actionEvent -> runRestart()); addButtonToMoreFunctions(bb); bb = new JButton("Update Complete"); @@ -336,7 +338,7 @@ public java.awt.Dimension getMaximumSize() { lineHelper.setAlignmentX(Component.LEFT_ALIGNMENT); lineHelper.setLayout(new BoxLayout(lineHelper, BoxLayout.X_AXIS)); lineHelper.setBorder(BorderFactory.createTitledBorder("Event Id for Active / Thrown")); - JFormattedTextField activeTextField = factory.handleEventIdTextField(EventIdTextField + JTextComponent activeTextField = factory.handleEventIdTextField(EventIdTextField .getEventIdTextField()); activeTextField.setMaximumSize(activeTextField.getPreferredSize()); lineHelper.add(activeTextField); @@ -348,7 +350,7 @@ public java.awt.Dimension getMaximumSize() { lineHelper.setAlignmentX(Component.LEFT_ALIGNMENT); lineHelper.setLayout(new BoxLayout(lineHelper, BoxLayout.X_AXIS)); lineHelper.setBorder(BorderFactory.createTitledBorder("Event Id for Inactive / Closed")); - JFormattedTextField inactiveTextField = factory.handleEventIdTextField(EventIdTextField + JTextComponent inactiveTextField = factory.handleEventIdTextField(EventIdTextField .getEventIdTextField()); inactiveTextField.setMaximumSize(inactiveTextField.getPreferredSize()); lineHelper.add(inactiveTextField); @@ -569,7 +571,7 @@ public void onError(String error) { _unsavedRestore = true; } - private void runReboot() { + private void runRestart() { rep.getConnection().getDatagramService().sendData(rep.getRemoteNodeID(), new int[] {0x20, 0xA9}); } @@ -628,6 +630,7 @@ private void runUpdateComplete() { SearchPane searchPane = new SearchPane(); private Timer tabColorTimer; + private boolean tabColorTimerStopped = false; long lastColorRefreshNeeded = 0; // guarded by tabColorTimer long lastColorRefreshDone = Long.MAX_VALUE; // guarded by tabColorTimer @@ -637,7 +640,7 @@ private void notifyTabColorRefresh() { currentTick = ++lastColorRefreshNeeded; } final long actualRequest = currentTick; - tabColorTimer.schedule(new TimerTask() { + if (!tabColorTimerStopped) tabColorTimer.schedule(new TimerTask() { @Override public void run() { EventQueue.invokeLater(() -> performTabColorRefresh(actualRequest)); @@ -1219,6 +1222,7 @@ public void propertyChange(PropertyChangeEvent event) { * Generate the tab label for a group item. * Including any needed navigation, tooltip, popup menu, etc. * @param parentTabbedPane The tabbed pane which it to be navigated + * @param index number of tab * @param name The name to display * @param rep the configuration data representation * @return Tab label component @@ -1272,6 +1276,8 @@ public void actionPerformed(ActionEvent e) { /** * Perform a "copy" operation on a selected group tab + * @param index number of tab to copy + * @param rep GroupRep to copy */ protected void performGroupReplCopy(int index, ConfigRepresentation.GroupRep rep) { String result = groupReplToString(rep); @@ -1285,6 +1291,8 @@ protected void performGroupReplCopy(int index, ConfigRepresentation.GroupRep rep /** * Copy an entire group replication to a String + * @param rep GroupRep to copy to String + * @return the GroupRep as a String */ protected String groupReplToString(ConfigRepresentation.GroupRep rep) { StringBuilder result = new StringBuilder(); @@ -1308,8 +1316,7 @@ public void visitFloat(ConfigRepresentation.FloatEntry e) { @Override public void visitEvent(ConfigRepresentation.EventEntry e) { - writeEntry(e.key, org.openlcb.Utilities.toHexDotsString(e.getValue - ().getContents())); + writeEntry(e.key, e.getValue()); } protected void writeEntry(String key, String entry) { @@ -1328,6 +1335,8 @@ protected void writeEntry(String key, String entry) { /** * Perform a "paste" operation into a selected group tab + * @param index number of selected tab + * @param rep GroupRep to insert */ protected void performGroupReplPaste(int index, ConfigRepresentation.GroupRep rep) { // retrieve from clipboard @@ -1689,7 +1698,7 @@ void createLinkPane(JPanel parent, String text, String ref) { } - private void addCopyPasteButtons(JPanel linePanel, JTextField textField) { + private void addCopyPasteButtons(JPanel linePanel, JTextComponent textField) { final JButton b = new JButton("Copy"); final Color defaultColor = b.getBackground(); b.addActionListener(new ActionListener() { @@ -1742,7 +1751,7 @@ public void actionPerformed(ActionEvent actionEvent) { private class SearchPane extends JPanel { JPanel parent = null; JTextField textField; - JTextField outputField; + JTextComponent outputField; JPopupMenu suggestMenu = null; SearchPane() { setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); @@ -1864,7 +1873,7 @@ private void cancelSearch() { } } - void attachParent(JPanel parentPane, JTextField output) { + void attachParent(JPanel parentPane, JTextComponent output) { if (parent != null) { cancelSearch(); } @@ -2148,6 +2157,7 @@ void updateWriteButton() { /** * For types that can check input for validity, e.g. in-range integer + * @return true if data meets criteria */ boolean isDataInvalid() { // by default, this does nothing @@ -2183,7 +2193,7 @@ protected String getCurrentValue() { private class EventIdPane extends EntryPane { private final ConfigRepresentation.EventEntry entry; - JFormattedTextField textField; + JTextComponent textField; JLabel eventNamesLabel = null; EventTable.EventTableEntryHolder eventTableEntryHolder = null; String lastEventText; @@ -2310,7 +2320,7 @@ private String getEventName() { @Override protected void additionalButtons() { - final JTextField tf = textField; + final JTextComponent tf = textField; JButton bb = factory.handleProduceButton(new JButton("Trigger")); bb.setToolTipText("Click to fire this event."); @@ -2318,7 +2328,7 @@ protected void additionalButtons() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { NodeID node = rep.getConnection().getNodeId(); - EventID ev = new EventID(org.openlcb.Utilities.bytesFromHexString((String)textField.getText())); + EventID ev = factory.getEventIDFromString(textField.getText()); rep.getConnection().getOutputConnection().put(new ProducerConsumerEventReportMessage(node, ev), rep.getConnection().getOutputConnection()); } }); @@ -2388,16 +2398,16 @@ private void showEventidMoreFunctionsMenu() { @Override protected void writeDisplayTextToNode() { - byte[] contents = org.openlcb.Utilities.bytesFromHexString((String) textField - .getText()); - entry.setValue(new EventID(contents)); + entry.setValue(factory.getEventIDFromString(textField.getText())); _changeMade = true; notifyTabColorRefresh(); } @Override protected void updateDisplayText(@NonNull String value) { - textField.setText(value); + EventID eid = factory.getEventIDFromString(value); + String retval = factory.getStringFromEventID(eid); + textField.setText(retval); } @NonNull @@ -2418,19 +2428,22 @@ void updateColor() { } lastEventText = s; EventID id; + try { - id = new EventID(s); - } catch(RuntimeException e) { +// id = new EventID(s); + id = factory.getEventIDFromString(s); + } catch (RuntimeException e) { // Event is not in the right format. Ignore. return; } + if (eventTableEntryHolder != null) { if (eventTableEntryHolder.getEntry().getEvent().equals(id)) { return; } releaseListener(); } - if (id.equals(nullEvent)) { + if (id == null || id.equals(nullEvent)) { // Ignore event if it is the null event. eventNamesLabel.setVisible(false); return; @@ -3199,7 +3212,7 @@ public void handleGroupPaneEnd(JPanel pane) { * @param field The proposed EventID entry field * @return The EventID entry field to use */ - public JFormattedTextField handleEventIdTextField(JFormattedTextField field) { + public JTextComponent handleEventIdTextField(EventIdTextField field) { return field; } @@ -3219,6 +3232,24 @@ public JTextField handleStringValue(JTextField value) { public JTextArea handleEditorValue(JTextArea value) { return value; } + + /** Convert a String into an EventID, doing any additional local + * dealiasing required. + * @param content Content to convert, e.g. from a text component + * @return eventID that represents the content + */ + public EventID getEventIDFromString(String content) { + return new EventID(content); + } + + /** Convert an EventID into a String, doing any additional local + * aliasing required. + * @param event EventID to convert, e.g. from reading a node + * @return local representation fo that EventID, often just the dotted hex + */ + public String getStringFromEventID(EventID event) { + return event.toShortString(); + } } /** diff --git a/src/org/openlcb/swing/EventIdTextField.java b/src/org/openlcb/swing/EventIdTextField.java index 8c360e84..2be7c790 100644 --- a/src/org/openlcb/swing/EventIdTextField.java +++ b/src/org/openlcb/swing/EventIdTextField.java @@ -49,37 +49,38 @@ * static method you call to get the field) rather than a subclass. * * @author Bob Jacobsen Copyright (C) 2012 - * @version $Revision$ */ public class EventIdTextField extends JFormattedTextField { - /** Comment for serialVersionUID. */ - private static final long serialVersionUID = 44833863963351863L; - private final static Logger logger = Logger.getLogger(EventIdTextField.class.getName()); - public static JFormattedTextField getEventIdTextField() { - JFormattedTextField retval = new JFormattedTextField( - createFormatter("HH.HH.HH.HH.HH.HH.HH.HH")); + public EventIdTextField() { + this("HH.HH.HH.HH.HH.HH.HH.HH"); + } + + public EventIdTextField(String mask) { + super(createFormatter(mask)); - // Let's size the event ID fields for the longest event ID in pixels. - retval.setValue("DD.DD.DD.DD.DD.DD.DD.DD"); - retval.setPreferredSize(retval.getPreferredSize()); - retval.setMinimumSize(retval.getPreferredSize()); - retval.setValue("00.00.00.00.00.00.00.00"); + setValue("DD.DD.DD.DD.DD.DD.DD.DD"); + setPreferredSize(getPreferredSize()); + setMinimumSize(getPreferredSize()); + setValue("00.00.00.00.00.00.00.00"); - retval.setToolTipText("EventID as eight-byte dotted-hex string, " + setToolTipText("EventID as eight-byte dotted-hex string, " + "e.g. 01.02.0A.AB.34.56.78.00 " + "You can drag&drop or ctrl-click for more options"); - retval.setDragEnabled(true); - retval.setTransferHandler(new CustomTransferHandler()); + setDragEnabled(true); + setTransferHandler(new CustomTransferHandler()); - configurePopUp(retval); - - return retval; + configurePopUp(this); + } + + @Deprecated + public static EventIdTextField getEventIdTextField() { + return new EventIdTextField(); } - private static void configurePopUp(JFormattedTextField textfield) { + public static void configurePopUp(JTextComponent textfield) { // JMRI's apps.Apps.java line 330 adds cut/copy/paste to all JTextComponents // (Also serves as example of cut/copy/paste code) @@ -90,7 +91,7 @@ private static void configurePopUp(JFormattedTextField textfield) { textfield.setInheritsPopupMenu(false); } - private static void checkAndShowPopup(JFormattedTextField textfield, MouseEvent event) { + private static void checkAndShowPopup(JTextComponent textfield, MouseEvent event) { if ( event.isPopupTrigger() ) { // user requested the popup menu JPopupMenu popup = createPopupMenu(textfield); @@ -98,7 +99,7 @@ private static void checkAndShowPopup(JFormattedTextField textfield, MouseEvent } } - private static JPopupMenu createPopupMenu(JFormattedTextField textfield) { + public static JPopupMenu createPopupMenu(JTextComponent textfield) { JPopupMenu popup = new JPopupMenu(); // add the usual copy and paste operators @@ -161,7 +162,7 @@ public void actionPerformed(ActionEvent e) { return popup; } - public static JMenu makeWellKnownEventMenu(JFormattedTextField textfield) { + public static JMenu makeWellKnownEventMenu(JTextComponent textfield) { JMenu wkeMenu = new JMenu("Insert well-known event"); wkeMenu.add(new EventIdInserter( "Emergency off (de-energize)", "01.00.00.00.05.00.FF.FF", textfield)); @@ -181,7 +182,7 @@ public static JMenu makeWellKnownEventMenu(JFormattedTextField textfield) { return wkeMenu; } - public static JMenuItem makeClockEventMenuItem(JFormattedTextField textfield) { + public static JMenuItem makeClockEventMenuItem(JTextComponent textfield) { JMenuItem menuItem = new JMenuItem("Insert Clock event..."); menuItem.addActionListener(new ActionListener() { @Override @@ -225,7 +226,7 @@ public void actionPerformed(ActionEvent e) { return menuItem; } - public static JMenuItem makeDccAccessoryEventMenuItem(JFormattedTextField textfield) { + public static JMenuItem makeDccAccessoryEventMenuItem(JTextComponent textfield) { JMenuItem menuItem = new JMenuItem("Insert DCC accessory decoder events ..."); menuItem.addActionListener(new ActionListener() { @Override @@ -278,7 +279,7 @@ public void actionPerformed(ActionEvent e) { } private static class EventIdInserter extends JMenuItem { - public EventIdInserter(String name, String value, JFormattedTextField target) { + public EventIdInserter(String name, String value, JTextComponent target) { super(name); this.value = value; this.target = target; @@ -291,7 +292,7 @@ public void actionPerformed(ActionEvent e) { }); } final String value; - final JFormattedTextField target; + final JTextComponent target; } diff --git a/src/org/openlcb/swing/MemorySpaceSelector.java b/src/org/openlcb/swing/MemorySpaceSelector.java index 00db7e09..be7e9a63 100644 --- a/src/org/openlcb/swing/MemorySpaceSelector.java +++ b/src/org/openlcb/swing/MemorySpaceSelector.java @@ -36,7 +36,8 @@ public MemorySpaceSelector() { /** * Get the selected value, constrained to 0-255 inclusive - * + * + * @return the selected memory space number */ public int getMemorySpace() { int value = 0; diff --git a/src/util/CollapsiblePanel.java b/src/util/CollapsiblePanel.java index 8bd3b6be..d8309d37 100644 --- a/src/util/CollapsiblePanel.java +++ b/src/util/CollapsiblePanel.java @@ -162,6 +162,8 @@ public void toggleSelection() { contentPanel_.setVisible(selected); validate(); + javax.swing.JFrame top = (javax.swing.JFrame)getTopLevelAncestor(); + if (top != null) top.pack(); headerPanel_.repaint(); } diff --git a/test/org/openlcb/EventIDTest.java b/test/org/openlcb/EventIDTest.java index 5deaae4e..015e5f20 100644 --- a/test/org/openlcb/EventIDTest.java +++ b/test/org/openlcb/EventIDTest.java @@ -152,4 +152,118 @@ public void testToLong() { Assert.assertEquals(-2L, new EventID(new byte[]{(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff, (byte)0xff,(byte)0xff,(byte)0xff,(byte)0xfe}).toLong()); } + + @Test + public void testFromLong() { + EventID eid; + eid = new EventID(0x12345678); + Assert.assertEquals(0x12345678L, eid.toLong()); + } + + @Test + public void testBoring() { + EventID eid = new EventID("02.02.59.00.00.00.00.00"); + Assert.assertEquals("", eid.parse()); + } + + @Test + public void testDefault() { + EventID eid = new EventID("00.00.00.00.00.00.02.00"); + Assert.assertEquals("Reserved 00.00.00.00.00.00.02.00", eid.parse()); + } + + @Test + public void testWellKnown() { + EventID eid = new EventID("01.00.00.00.00.00.FF.FE"); + Assert.assertEquals("Clear Emergency Off", eid.parse()); + } + + @Test + public void testFastClock() { + EventID eid = new EventID("01.01.00.00.01.01.09.02"); + Assert.assertEquals("Fast Clock 1 time 9:02", eid.parse()); + eid = new EventID("01.01.00.00.01.01.09.32"); + Assert.assertEquals("Fast Clock 1 time 9:50", eid.parse()); + eid = new EventID("01.01.00.00.01.01.89.32"); + Assert.assertEquals("Fast Clock 1 Set time 9:50", eid.parse()); + eid = new EventID("01.01.00.00.01.01.F0.02"); + Assert.assertEquals("Fast Clock 1 Start", eid.parse()); + } + + @Test + public void testRangeSuffix() { + EventID eid = new EventID("00.00.00.00.00.00.FF.FF"); + Assert.assertEquals(0xFFFF, eid.rangeSuffix()); + eid = new EventID("00.00.00.00.00.FF.00.00"); + Assert.assertEquals(0xFFFF, eid.rangeSuffix()); + + eid = new EventID("00.00.00.00.00.FF.80.00"); + Assert.assertEquals(0x7FFF, eid.rangeSuffix()); + + eid = new EventID("00.00.00.00.00.00.00.03"); + Assert.assertEquals(0x03, eid.rangeSuffix()); + } + + @Test + public void testTurnout() { + EventID eid = new EventID("01.01.02.00.00.FF.00.09"); + Assert.assertEquals("DCC Acc Decoder 1 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.08"); + Assert.assertEquals("DCC Acc Decoder 1 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.0B"); + Assert.assertEquals("DCC Acc Decoder 2 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.0A"); + Assert.assertEquals("DCC Acc Decoder 2 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.0F"); + Assert.assertEquals("DCC Acc Decoder 4 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.0E"); + Assert.assertEquals("DCC Acc Decoder 4 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.17"); + Assert.assertEquals("DCC Acc Decoder 8 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.16"); + Assert.assertEquals("DCC Acc Decoder 8 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.27"); + Assert.assertEquals("DCC Acc Decoder 16 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.26"); + Assert.assertEquals("DCC Acc Decoder 16 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.47"); + Assert.assertEquals("DCC Acc Decoder 32 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.46"); + Assert.assertEquals("DCC Acc Decoder 32 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.87"); + Assert.assertEquals("DCC Acc Decoder 64 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.86"); + Assert.assertEquals("DCC Acc Decoder 64 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.01.07"); + Assert.assertEquals("DCC Acc Decoder 128 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.01.06"); + Assert.assertEquals("DCC Acc Decoder 128 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.02.07"); + Assert.assertEquals("DCC Acc Decoder 256 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.02.06"); + Assert.assertEquals("DCC Acc Decoder 256 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.04.07"); + Assert.assertEquals("DCC Acc Decoder 512 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.04.06"); + Assert.assertEquals("DCC Acc Decoder 512 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.08.07"); + Assert.assertEquals("DCC Acc Decoder 1024 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.08.06"); + Assert.assertEquals("DCC Acc Decoder 1024 R/T/Off active", eid.parse()); + + eid = new EventID("01.01.02.00.00.FF.00.05"); + Assert.assertEquals("DCC Acc Decoder 2047 N/C/On active", eid.parse()); + eid = new EventID("01.01.02.00.00.FF.00.04"); + Assert.assertEquals("DCC Acc Decoder 2047 R/T/Off active", eid.parse()); + } } diff --git a/test/org/openlcb/ProducerConsumerEventReportMessageTest.java b/test/org/openlcb/ProducerConsumerEventReportMessageTest.java index 0f138dcb..1e2809ef 100644 --- a/test/org/openlcb/ProducerConsumerEventReportMessageTest.java +++ b/test/org/openlcb/ProducerConsumerEventReportMessageTest.java @@ -93,7 +93,7 @@ public void testPayloadToString() { ProducerConsumerEventReportMessage m2 = new ProducerConsumerEventReportMessage( nodeID1, eventID1, payload1 ); - Assert.assertEquals(" Producer/Consumer Event Report EventID:01.00.00.00.00.00.01.00 payload of 2 : 12.34", m2.toString()); + Assert.assertEquals("01.02.03.04.05.06 Producer/Consumer Event Report EventID:01.00.00.00.00.00.01.00 payload of 2 : 12.34", m2.toString()); } @Test diff --git a/test/org/openlcb/cdi/swing/CdiPanelDemo.java b/test/org/openlcb/cdi/swing/CdiPanelDemo.java index c65fbf3d..4310e09e 100644 --- a/test/org/openlcb/cdi/swing/CdiPanelDemo.java +++ b/test/org/openlcb/cdi/swing/CdiPanelDemo.java @@ -80,6 +80,14 @@ static public void main(String[] args) { fname = args[0]; System.out.println("Using input file " + fname); } + + // configure a L&F +// try { +// // Set cross-platform Java L&F (also called "Metal") +// javax.swing.UIManager.setLookAndFeel( +// javax.swing.UIManager.getCrossPlatformLookAndFeelClassName()); +// } catch (Exception e) {}; + d.displayFile(fname); } } diff --git a/test/scenarios/ScenarioRunner.java b/test/scenarios/ScenarioRunner.java index 4491e111..d9763e7a 100644 --- a/test/scenarios/ScenarioRunner.java +++ b/test/scenarios/ScenarioRunner.java @@ -41,6 +41,7 @@ public void actionPerformed(ActionEvent e) { } }); p.add(b); + b = new JButton("Blue/Gold Configuration Demo"); b.addActionListener(new ActionListener(){ @Override @@ -54,6 +55,19 @@ public void actionPerformed(ActionEvent e) { }); p.add(b); + b = new JButton("Display CDI File"); + b.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + try { + org.openlcb.cdi.swing.CdiPanelDemo.main(new String[] {}); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + p.add(b); + f.pack(); f.setVisible(true); }