diff --git a/sample.xml b/sample.xml
index dc327a8f..1e49fc12 100644
--- a/sample.xml
+++ b/sample.xml
@@ -8,32 +8,78 @@
Model 123 Uniblab
EC 415
1.2.3.4
+ Link to OpenLCB.org documentation
-
+ Link to OpenLCB.org documentation
+
Produced Events
The EventIDs for the producers
+ Link to OpenLCB.org documentation
+
+
+
+
+
+
+ 1
+ 250
+ 12
+
+
+
+
+
+
+ Status Field
+ As first string, this content will appear in the tab
+
+
-
+
Consumed Events
The EventIDs for the consumers
+
+
+
Blob to see if works in group element
-
+
Float to see if works in group element
Int of size 4 so that each group is 32 long
+
+ Hideable and Hidden Nested Group
+
+
+
+
+
+
+
+ Hideable and Not Hidden Nested Group
+
+
+
+
+
+
+
+ Non-hideable Nested Group
+
+
+
Sample integer variable
@@ -42,14 +88,28 @@
999
12
+
+ Sample float variable
+ Doesn't do anything
+ 1
+ 999
+ 12
+
+
+ Same float variable
+ Overlaps previous
+ 0
+ 10000
+ 12
+
Sample integer slider
Doesn't do anything either
- 0
- 1000
+ 1
+ 250
12
-
+
@@ -59,7 +119,27 @@
1000
12
-
+
+
+
+
+ Sample integer slider with view
+ And another
+ 1
+ 250
+ 12
+
+
+
+
+
+ Immediate-write integer slider with view
+ You guessed it!
+ 0
+ 1000
+ 12
+
+
@@ -72,10 +152,13 @@
Board must be restarted for this to take effect.
+
+
+
Reset Directly
diff --git a/src/org/openlcb/ProducerConsumerEventReportMessage.java b/src/org/openlcb/ProducerConsumerEventReportMessage.java
index e9ac7278..5c0f7739 100644
--- a/src/org/openlcb/ProducerConsumerEventReportMessage.java
+++ b/src/org/openlcb/ProducerConsumerEventReportMessage.java
@@ -88,9 +88,19 @@ public byte[] getPayloadArray() {
@Override
public String toString() {
- return super.toString()
- +" Producer/Consumer Event Report "+eventID.toString()
- +" payload of "+getPayloadSize();
+ String retval = " Producer/Consumer Event Report "+eventID.toString();
+
+ if ( getPayloadSize() > 0 ) {
+ retval = retval + " payload of "+getPayloadSize()+" : ";
+ int n = getPayloadSize();
+ boolean first = true;
+ for (byte data : payload) {
+ if (!first) retval = retval + ".";
+ retval = retval + Integer.toHexString((int)(data&0xFF)).toUpperCase();
+ first = false;
+ }
+ }
+ return retval;
}
public boolean equals(Object o) {
diff --git a/src/org/openlcb/ProtocolIdentification.java b/src/org/openlcb/ProtocolIdentification.java
index ee19f26a..2cdda57e 100644
--- a/src/org/openlcb/ProtocolIdentification.java
+++ b/src/org/openlcb/ProtocolIdentification.java
@@ -105,7 +105,7 @@ public List getProtocolNames() {
* @param protocol enum representing the protocol bit to test
* @return true if protocol is supported, false otherwise.
*/
- boolean hasProtocol(Protocol protocol) {
+ public boolean hasProtocol(Protocol protocol) {
return protocol.supports(value);
}
}
diff --git a/src/org/openlcb/cdi/CdiRep.java b/src/org/openlcb/cdi/CdiRep.java
index e751e541..6864cb29 100644
--- a/src/org/openlcb/cdi/CdiRep.java
+++ b/src/org/openlcb/cdi/CdiRep.java
@@ -15,6 +15,8 @@ public static interface Identification {
public String getModel();
public String getHardwareVersion();
public String getSoftwareVersion();
+ public String getLinkText();
+ public String getLinkURL();
public Map getMap();
}
@@ -29,6 +31,8 @@ public static interface Segment {
public String getName();
public String getDescription();
+ public String getLinkText();
+ public String getLinkURL();
public Map getMap();
public int getIndexInParent();
}
@@ -44,7 +48,12 @@ public static interface Item {
public static interface Group extends Item {
public java.util.List- getItems();
public int getReplication();
+ public String getLinkText();
+ public String getLinkURL();
public String getRepName(int index, int replications);
+ public boolean isHideable();
+ public boolean isHidden();
+ public boolean isReadOnly();
}
public static interface Map {
@@ -102,8 +111,12 @@ public static interface IntegerRep extends Item {
// Should the slider itself immediately write its value on change?
public boolean isSliderImmediate();
// Optionally specifies the 'distance' between tick marks on the slider.
- // If 0 (default value), don't show tick marks.
+ // If 0 (default value) or 1, don't show tick marks.
public int getSliderTickSpacing();
+ // Optionally specifies if the slider value should be shown in text box
+ public boolean isSliderShowValue();
+ // Did the CDI content hint that this value should be presented as a radio button?
+ public boolean isRadioButtonHint();
}
public static interface FloatRep extends Item {
diff --git a/src/org/openlcb/cdi/impl/ConfigRepresentation.java b/src/org/openlcb/cdi/impl/ConfigRepresentation.java
index 7dd14342..cc22cadd 100644
--- a/src/org/openlcb/cdi/impl/ConfigRepresentation.java
+++ b/src/org/openlcb/cdi/impl/ConfigRepresentation.java
@@ -412,6 +412,10 @@ public void reload() {
MemorySpaceCache cache = getCacheForSpace(space);
cache.reload(origin, size, isNullTerminated());
}
+
+ boolean flaggedReadOnly = false;
+ public boolean isFlaggedReadOnly() { return flaggedReadOnly; }
+ public void setFlaggedReadOnly(boolean state) {flaggedReadOnly = state; }
}
public class Root implements CdiContainer {
@@ -574,6 +578,21 @@ public class GroupEntry extends GroupBase {
}
}
}
+
+ public boolean isHideable() {
+ return group.isHideable();
+ }
+
+ public boolean isHidden() {
+ return group.isHidden();
+ }
+
+ /**
+ * Does this entry carry the readOnly hint?
+ */
+ public boolean isReadOnlyConfigured() {
+ return group.isReadOnly();
+ }
}
/**
diff --git a/src/org/openlcb/cdi/jdom/JdomCdiRep.java b/src/org/openlcb/cdi/jdom/JdomCdiRep.java
index 261c78ae..86f8569f 100644
--- a/src/org/openlcb/cdi/jdom/JdomCdiRep.java
+++ b/src/org/openlcb/cdi/jdom/JdomCdiRep.java
@@ -6,6 +6,7 @@
import java.util.logging.Logger;
import org.jdom2.Attribute;
+import org.jdom2.DataConversionException;
import org.jdom2.Element;
import org.openlcb.cdi.CdiRep;
@@ -49,6 +50,22 @@ public String getSoftwareVersion() {
return c.getText();
}
+ @Override
+ public String getLinkText() {
+ Element c = id.getChild("link");
+ if (c == null) return null;
+ return c.getText();
+ }
+
+ @Override
+ public String getLinkURL() {
+ Element c = id.getChild("link");
+ if (c == null) return null;
+ Attribute a = c.getAttribute("ref");
+ if (a == null) return null;
+ return a.getValue();
+ }
+
@Override
public Map getMap() {
return new Map(id.getChild("map"));
@@ -127,6 +144,8 @@ public java.util.List getItems() {
case "repname":
case "name":
case "description":
+ case "link":
+ case "hints":
break;
default:
list.add(new UnknownRep(element));
@@ -164,6 +183,23 @@ public int getOrigin() {
else return a.getIntValue();
} catch (org.jdom2.DataConversionException e1) { return 0; }
}
+
+ @Override
+ public String getLinkText() {
+ Element c = e.getChild("link");
+ if (c == null) return null;
+ return c.getText();
+ }
+
+ @Override
+ public String getLinkURL() {
+ Element c = e.getChild("link");
+ if (c == null) return null;
+ Attribute a = c.getAttribute("ref");
+ if (a == null) return null;
+ return a.getValue();
+ }
+
}
public static class Map implements CdiRep.Map {
@@ -289,6 +325,65 @@ public int getOffset() {
} catch (org.jdom2.DataConversionException e1) { return 0; }
}
+ @Override
+ public String getLinkText() {
+ Element c = e.getChild("link");
+ if (c == null) return null;
+ return c.getText();
+ }
+
+ @Override
+ public String getLinkURL() {
+ Element c = e.getChild("link");
+ if (c == null) return null;
+ Attribute a = c.getAttribute("ref");
+ if (a == null) return null;
+ return a.getValue();
+ }
+
+ @Override
+ public boolean isHideable() {
+ // defaults to false
+ Element hints = e.getChild("hints");
+ if (hints == null) return false;
+ Element visibility = hints.getChild("visibility");
+ if (visibility == null) return false;
+ Attribute a = visibility.getAttribute("hideable");
+ if (a == null) return false;
+ try {
+ boolean value = a.getBooleanValue();
+ return value;
+ } catch (DataConversionException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isHidden() {
+ // defaults to false
+ Element hints = e.getChild("hints");
+ if (hints == null) return false;
+ Element visibility = hints.getChild("visibility");
+ if (visibility == null) return false;
+ Attribute a = visibility.getAttribute("hidden");
+ if (a == null) return false;
+ try {
+ boolean value = a.getBooleanValue();
+ return value;
+ } catch (DataConversionException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ // defaults to false
+ Element hints = e.getChild("hints");
+ if (hints == null) return false;
+ Element readOnly = hints.getChild("readOnly");
+ return readOnly != null;
+ }
+
/**
* Provides the name for this replication. See the CDI TN for the
* algorithm being used.
@@ -455,8 +550,12 @@ public boolean isSliderImmediate() {
if (slider == null) return false;
Attribute immediate = slider.getAttribute("immediate");
if (immediate == null) return false;
- if (! immediate.getValue().toLowerCase().equals("yes")) return false;
- return true;
+ try {
+ boolean value = immediate.getBooleanValue();
+ return value;
+ } catch (DataConversionException ex) {
+ return false;
+ }
}
@Override
@@ -472,6 +571,31 @@ public int getSliderTickSpacing() {
} catch (org.jdom2.DataConversionException e) { return 0; }
}
+ @Override
+ public boolean isSliderShowValue() {
+ Element hints = e.getChild("hints");
+ if (hints == null) return false;
+ Element slider = hints.getChild("slider");
+ if (slider == null) return false;
+ Attribute showValue = slider.getAttribute("showValue");
+ if (showValue == null) return false;
+ try {
+ boolean value = showValue.getBooleanValue();
+ return value;
+ } catch (DataConversionException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isRadioButtonHint() {
+ Element hints = e.getChild("hints");
+ if (hints == null) return false;
+ Element radiobutton = hints.getChild("radiobutton");
+ if (radiobutton == null) return false;
+ return true;
+ }
+
}
diff --git a/src/org/openlcb/cdi/swing/CdiPanel.java b/src/org/openlcb/cdi/swing/CdiPanel.java
index d8291376..47a38d6f 100644
--- a/src/org/openlcb/cdi/swing/CdiPanel.java
+++ b/src/org/openlcb/cdi/swing/CdiPanel.java
@@ -32,7 +32,10 @@
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
+import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
@@ -49,6 +52,7 @@
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -61,10 +65,12 @@
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.InputVerifier;
import javax.swing.JButton;
@@ -84,10 +90,13 @@
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
+import javax.swing.JRadioButton;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
@@ -110,7 +119,7 @@
*
* Works with a CDI reader.
*
- * @author Bob Jacobsen Copyright 2011
+ * @author Bob Jacobsen Copyright 2011, 2024
* @author Paul Bender Copyright 2016
* @author Balazs Racz Copyright 2016
* @author Pete Cressman Copyright 2020
@@ -1098,17 +1107,31 @@ public void visitGroup(ConfigRepresentation.GroupEntry e) {
}
factory.handleGroupPaneStart(groupPane);
+ if (e.isReadOnlyConfigured()) {
+ // mark the direct children of these, except groups, as readOnly
+ // when the direct child is a grouprep (repetitions > 1), mark those children
+ for (ConfigRepresentation.CdiEntry entry : e.getEntries()) {
+ if (! (entry instanceof ConfigRepresentation.GroupEntry) ) {
+ entry.setFlaggedReadOnly(true);
+ if (entry instanceof ConfigRepresentation.GroupRep ) {
+ for (ConfigRepresentation.CdiEntry subentry : ((ConfigRepresentation.GroupRep)entry).getEntries()) {
+ subentry.setFlaggedReadOnly(true);
+ }
+ }
+ }
+ }
+ }
super.visitGroup(e);
factory.handleGroupPaneEnd(groupPane);
- if (groupPane.getComponentCount() > 0) {
- if (oldPane instanceof SegmentPane) {
- // we make toplevel groups collapsible.
+ if (groupPane.getComponentCount() > 0) { // empty groups are not collabsible
+ if (oldPane instanceof SegmentPane || e.isHideable()) { // we only make toplevel groups collapsible unless hint requests
groupPane.setBorder(null);
CollapsiblePanel cPanel = new CollapsiblePanel(groupPane.getName(), groupPane);
// cPanel.setBorder(BorderFactory.createLineBorder(java.awt.Color.RED)); //debugging
cPanel.setAlignmentY(Component.TOP_ALIGNMENT);
cPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ cPanel.setExpanded(!e.isHidden());
oldPane.add(cPanel);
addNavigationActions(cPanel);
} else {
@@ -1559,7 +1582,7 @@ JPanel createIdentificationPane(CdiRep c) {
JPanel p1 = new JPanel();
p.add(p1);
- p1.setLayout(new util.javaworld.GridLayout2(4,2));
+ p1.setLayout(new util.javaworld.GridLayout2(5,2));
p1.setAlignmentX(Component.LEFT_ALIGNMENT);
p1.add(new JLabel("Manufacturer: "));
@@ -1574,6 +1597,10 @@ JPanel createIdentificationPane(CdiRep c) {
p1.add(new JLabel("Software Version: "));
p1.add(new JLabel(id.getSoftwareVersion()));
+ if (id.getLinkText() != null && id.getLinkURL() != null) {
+ p1.add(new HtmlLabel(id.getLinkText(),id.getLinkURL()));
+ }
+
p1.setMaximumSize(p1.getPreferredSize());
// include map if present
@@ -1629,7 +1656,8 @@ public class SegmentPane extends JPanel {
//p.setBorder(BorderFactory.createTitledBorder(name));
createDescriptionPane(this, item.getDescription());
-
+ createLinkPane(this, item.segment.getLinkText(), item.segment.getLinkURL());
+
// include map if present
JPanel p2 = createPropertyPane(item.getMap());
if (p2 != null) p.add(p2);
@@ -1655,6 +1683,12 @@ public Dimension getMaximumSize() {
parent.add(area);
}
+ void createLinkPane(JPanel parent, String text, String ref) {
+ if (text == null || ref == null) return;
+ parent.add(new HtmlLabel(text, ref));
+ }
+
+
private void addCopyPasteButtons(JPanel linePanel, JTextField textField) {
final JButton b = new JButton("Copy");
final Color defaultColor = b.getBackground();
@@ -1858,6 +1892,7 @@ public class GroupPane extends JPanel {
setName(name);
createDescriptionPane(this, item.getDescription());
+ createLinkPane(this, entry.group.getLinkText(), entry.group.getLinkURL());
// include map if present
JPanel p2 = createPropertyPane(item.getMap());
@@ -1909,31 +1944,51 @@ void release() {
protected void additionalButtons() {}
protected void init() {
+ // a panel that may exist below the main component (textComponent)
+ // which, if present, gets the Refresh and Write buttons
+ JPanel subpanel = null;
+
if (textComponent instanceof JTextArea) {
- JPanel lengthPanel = new JPanel();
+ subpanel = new JPanel();
// include an auto-updating "remaining characters" field
- lengthPanel.setLayout(new FlowLayout());
+ subpanel.setLayout(new FlowLayout());
JLabel lengthLabel = new JLabel("Remaining characters: ");
- lengthPanel.add(lengthLabel);
+ subpanel.add(lengthLabel);
final JTextField countField = new JTextField(6);
countField.setText(""+(entry.size-1));
- lengthPanel.add(countField);
+ subpanel.add(countField);
lengthLabel.setFont(countAreaFont);
countField.setFont(countAreaFont);
JPanel combinedPanel = new JPanel();
combinedPanel.setLayout(new BoxLayout(combinedPanel, BoxLayout.Y_AXIS));
- combinedPanel.add(new JScrollPane(textComponent){
+ JScrollPane spane = new JScrollPane(textComponent){
// Limit how small the layout will make the field
public Dimension getMinimumSize() {
Dimension superSize = super.getMinimumSize();
int width = superSize.width;
- int height = Math.max(superSize.height, 200);
+ int height = Math.max(superSize.height, 50);
return new Dimension(width, height);
}
- });
- combinedPanel.add(lengthPanel);
+ public Dimension getPreferredSize() {
+ Dimension superMin = super.getMinimumSize();
+ Dimension superPref = super.getPreferredSize();
+ int width = Math.max(superMin.width, superPref.width);
+ int height = Math.max(superMin.height, superPref.height);
+ return new Dimension(width, height);
+ }
+ public Dimension getMaximumSize() {
+ Dimension superMax = super.getMaximumSize();
+ Dimension superPref = super.getPreferredSize();
+ int width = Math.max(superMax.width, superPref.width);
+ int height = Math.max(superMax.height, superPref.height);
+ return new Dimension(width, height);
+ }
+ };
+ spane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+ combinedPanel.add(spane);
+ combinedPanel.add(subpanel);
p3.add(combinedPanel);
@@ -1966,6 +2021,8 @@ private void updateLength() {
p3.add(textComponent);
}
textComponent.setMaximumSize(textComponent.getPreferredSize());
+
+ // Add color-setting listeners - this is here to avoid having lots of replicated code
if (textComponent instanceof JTextComponent) {
((JTextComponent) textComponent).getDocument().addDocumentListener(
new DocumentListener() {
@@ -1989,20 +2046,8 @@ private void drawRed() {
}
}
);
- } else if (textComponent instanceof JComboBox) {
- ((JComboBox) textComponent).addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent actionEvent) {
- updateColor();
- }
- });
- } else if (textComponent instanceof JSlider) {
- ((JSlider) textComponent).addChangeListener(new javax.swing.event.ChangeListener(){
- public void stateChanged(javax.swing.event.ChangeEvent e) {
- updateColor();
- }
- });
}
+
entryListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
@@ -2037,16 +2082,27 @@ public void actionPerformed(java.awt.event.ActionEvent e) {
entry.reload();
}
});
- p3.add(b);
-
- writeButton = factory.handleWriteButton(new JButton("Write"));
- writeButton.addActionListener(new java.awt.event.ActionListener() {
- @Override
- public void actionPerformed(java.awt.event.ActionEvent e) {
- writeDisplayTextToNode();
+ if (subpanel != null ) {
+ subpanel.add(b);
+ } else {
+ p3.add(b);
+ }
+
+ // write button is suppressed if flagged as read only
+ if (! entry.isFlaggedReadOnly()) {
+ writeButton = factory.handleWriteButton(new JButton("Write"));
+ writeButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ writeDisplayTextToNode();
+ }
+ });
+ if (subpanel != null ) {
+ subpanel.add(writeButton);
+ } else {
+ p3.add(writeButton);
}
- });
- p3.add(writeButton);
+ }
}
additionalButtons();
@@ -2168,6 +2224,7 @@ public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
if (eventTable != null) {
add(eventNamesLabel);
}
+ if (e.isFlaggedReadOnly()) textComponent.setEnabled(false);
}
/**
@@ -2391,11 +2448,186 @@ private void releaseListener() {
}
}
+ // represent a slider with an optional text view
+ private class SliderWithView extends JPanel {
+ JSlider slider = null;
+ JTextField textField = null;
+
+ SliderWithView(int min, int max, boolean showValue, int size) {
+ setLayout(new FlowLayout());
+
+ // define the slider
+ slider = new JSlider(min, max);
+ slider.setOpaque(true); // so you can color it
+
+ // set a tooltip showing the valid range
+ if (min < 0) {
+ slider.setToolTipText("Signed integer from "
+ +min+" to "+max
+ +" ("+size+" bytes)");
+ } else {
+ slider.setToolTipText("Unsigned integer from "
+ +min+" to "+max
+ +" ("+size+" bytes)");
+ }
+
+ add(slider);
+
+ // optionally define the text field
+ if (showValue) {
+ textField = new JTextField(2+(int)Math.log10(Math.max(1., Math.abs(max)))) {
+ public java.awt.Dimension getMaximumSize() {
+ return getPreferredSize();
+ }
+ };
+ textField.setOpaque(true); // so you can color it
+
+ // set a tooltip showing the valid range
+ if (min < 0) {
+ textField.setToolTipText("Signed integer from "
+ +min+" to "+max
+ +" ("+size+" bytes)");
+ } else {
+ textField.setToolTipText("Unsigned integer from "
+ +min+" to "+max
+ +" ("+size+" bytes)");
+ }
+
+ // add a listener to the slider to fill this
+ slider.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ textField.setText(""+slider.getValue());
+ }
+ });
+
+ // Add listeners to set slider. Value is considered
+ // final when the field is exited or Enter is hit.
+ // We do this instead of listening for a value
+ // change to avoid a possible back-and-forth
+ // setting loop between the text field and slider.
+ textField.addFocusListener(new FocusListener(){
+ public void focusLost(FocusEvent e) {
+ textToSlider();
+ }
+ public void focusGained(FocusEvent e) {
+ }
+ });
+ textField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyReleased(KeyEvent ke) {
+ if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
+ textToSlider();
+ }
+ }
+ });
+
+ add(textField);
+ }
+ }
+
+ // setting background also colors slider
+ @Override
+ public void setBackground(Color color) {
+ // super.setBackground(..) would color whole block
+ if (slider != null) {
+ slider.setBackground(color);
+ }
+ if (textField != null) {
+ textField.setBackground(color);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean state) {
+ super.setEnabled(state);
+ if (slider != null) {
+ slider.setEnabled(state);
+ }
+ if (textField != null) {
+ textField.setEnabled(state);
+ }
+ }
+
+ // copies the textfield value to the slider, handling errors
+ void textToSlider() {
+ try {
+ int current = (int)Double.parseDouble(textField.getText().trim());
+ slider.setValue(current);
+ } catch (NumberFormatException e) {
+ // don't set the value, load from current slider value
+ textField.setText(""+slider.getValue());
+ }
+ }
+ }
+
+ // represents a set of radio buttons
+ private class RadioButtonPane extends JPanel {
+
+ CdiRep.Map map;
+ ButtonGroup group = new ButtonGroup();
+
+ RadioButtonPane(CdiRep.Map map, ActionListener action) {
+ this.map = map;
+
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ // create the pane and fill with radio buttons
+ for (String v : map.getValues()) {
+ JRadioButton button = new JRadioButton(v);
+ button.setActionCommand(v);
+ add(button);
+ // add color listener
+ button.addActionListener(action);
+
+ group.add(button);
+ }
+ }
+
+ long getCurrentValue() {
+ return Long.parseLong(getCurrentValueString());
+ }
+
+ // value is a numeric string
+ void setCurrentValue(String value) {
+ String key = map.getKey(value);
+
+ Enumeration e = group.getElements();
+ while (e.hasMoreElements()) {
+ AbstractButton b = e.nextElement();
+ if (b.getActionCommand().equals(value)) {
+ b.setSelected(true);
+ return;
+ }
+ }
+ // else set unselected
+ logger.log(Level.WARNING, "Value \""+value+"\" does not match a button value, taking 1st button");
+ group.clearSelection();
+ }
+
+ String getCurrentValueString() {
+ String value = getDisplayText();
+ if (map.getKey(value) != null) {
+ return map.getKey(value);
+ } else {
+ logger.severe("Value \""+value+"\" does not match a button name");
+ return map.getKeys().get(0);
+ }
+ }
+
+ String getDisplayText() {
+ if (group.getSelection() == null) {
+ return map.getValues().get(0);
+ }
+ return group.getSelection().getActionCommand();
+ }
+ }
+
private class IntPane extends EntryPane {
JTextField textField = null;
JComboBox box = null;
- JSlider slider = null;
+ SliderWithView sliderView = null;
+ RadioButtonPane radiobuttons = null;
CdiRep.Map map = null;
private final ConfigRepresentation.IntegerEntry entry;
boolean suppressExternal = false; // used to suppress slider output when changed from read
@@ -2410,32 +2642,52 @@ private class IntPane extends EntryPane {
String[] labels;
map = item.getMap();
if ((map != null) && (map.getKeys().size() > 0)) {
- // map present, make selection box
- box = new JComboBox(map.getValues().toArray(new String[]{""})) {
- public java.awt.Dimension getMaximumSize() {
- return getPreferredSize();
- }
- };
- textComponent = box;
+ // map present, make selection box or radio buttons?
+ if (entry.rep.isRadioButtonHint()) {
+ ActionListener action = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ updateColor();
+ }
+ };
+ radiobuttons = new RadioButtonPane(map, action);
+ textComponent = radiobuttons;
+ } else {
+ box = new JComboBox(map.getValues().toArray(new String[]{""})) {
+ public java.awt.Dimension getMaximumSize() {
+ return getPreferredSize();
+ }
+ };
+
+ // add color listener
+ box.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ updateColor();
+ }
+ });
+
+ textComponent = box;
+ }
} else {
// map not present - is it a slider?
if (entry.rep.isSliderHint()) {
// display a slider
- slider = new JSlider((int)entry.rep.getMin(), (int)entry.rep.getMax());
- slider.setOpaque(true); // so you can color it
- if (entry.rep.getSliderTickSpacing() > 1) {
+ sliderView = new SliderWithView((int)entry.rep.getMin(), (int)entry.rep.getMax(), entry.rep.isSliderShowValue(), entry.size);
+
+ if (entry.rep.getSliderTickSpacing() > 0) { // default is zero
// display divisions on the slider
- slider.setMajorTickSpacing(entry.rep.getSliderTickSpacing());
- slider.setLabelTable(slider.createStandardLabels(entry.rep.getSliderTickSpacing()));
- slider.setPaintTicks(true);
- slider.setPaintLabels(true);
+ sliderView.slider.setMajorTickSpacing(entry.rep.getSliderTickSpacing());
+ sliderView.slider.setLabelTable(sliderView.slider.createStandardLabels(entry.rep.getSliderTickSpacing()));
+ sliderView.slider.setPaintTicks(true);
+ sliderView.slider.setPaintLabels(true);
}
// (optionally) listen for changes and immediately write
if (entry.rep.isSliderImmediate()) {
- slider.addChangeListener(new javax.swing.event.ChangeListener(){
+ sliderView.slider.addChangeListener(new javax.swing.event.ChangeListener(){
public void stateChanged(javax.swing.event.ChangeEvent e) {
- if (!slider.getValueIsAdjusting()) {
+ if (!sliderView.slider.getValueIsAdjusting()) {
if (!suppressInternal && !suppressExternal) {
writeDisplayTextToNode();
}
@@ -2446,15 +2698,23 @@ public void stateChanged(javax.swing.event.ChangeEvent e) {
});
}
- textComponent = slider;
+
+ // add the listener that handles changed color
+ sliderView.slider.addChangeListener(new javax.swing.event.ChangeListener(){
+ public void stateChanged(javax.swing.event.ChangeEvent e) {
+ updateColor();
+ }
+ });
+
+ textComponent = sliderView;
// set the tooltip to min and max values
if (entry.rep.getMin() < 0) {
- slider.setToolTipText("Signed integer from "
+ sliderView.setToolTipText("Signed integer from "
+entry.rep.getMin()+" to "+entry.rep.getMax()
+" ("+entry.size+" bytes)");
} else {
- slider.setToolTipText("Unsigned integer from "
+ sliderView.setToolTipText("Unsigned integer from "
+entry.rep.getMin()+" to "+entry.rep.getMax()
+" ("+entry.size+" bytes)");
}
@@ -2480,17 +2740,21 @@ public java.awt.Dimension getMaximumSize() {
}
init();
+ if (e.isFlaggedReadOnly()) textComponent.setEnabled(false);
}
+ // Takes the current GUI content and writes it to the node
@Override
protected void writeDisplayTextToNode() {
long value;
if (textField != null) {
value = Long.parseLong(textField.getText());
- } else if (slider != null) {
+ } else if (sliderView != null) {
// get value from current slider position
suppressInternal = true; // will be set false once change works through
- value = slider.getValue();
+ value = sliderView.slider.getValue();
+ } else if (radiobuttons != null) {
+ value = radiobuttons.getCurrentValue();
} else {
// have to get key from stored map value
String entry = (String) box.getSelectedItem();
@@ -2504,11 +2768,14 @@ protected void writeDisplayTextToNode() {
}
@Override
+ // Sets a specific value into the GUI
protected void updateDisplayText(@NonNull String value) {
if (textField != null) textField.setText(value);
- if (slider != null) {
+ if (sliderView != null) {
suppressInternal = true;
- slider.setValue(Integer.parseInt(value));
+ sliderView.slider.setValue(Integer.parseInt(value));
+ } else if (radiobuttons != null) {
+ radiobuttons.setCurrentValue(value);
}
if (box != null) {
// check to see if item exists
@@ -2537,8 +2804,16 @@ protected void updateDisplayText(@NonNull String value) {
@NonNull
@Override
+ /*
+ * Returns the current content of the GUI as a String
+ * This may be a number, but for a map (or buttons) it's the current label
+ */
protected String getDisplayText() {
- if (slider != null) return ""+slider.getValue();
+ if (sliderView != null) {
+ return ""+sliderView.slider.getValue();
+ } else if (radiobuttons != null) {
+ return radiobuttons.getDisplayText();
+ }
String s = (box == null) ? (String) textField.getText()
: (String) box.getSelectedItem();
return s == null ? "" : s;
@@ -2553,7 +2828,11 @@ protected String getDisplayText() {
*/
@NonNull
protected String getCurrentValue() {
- if (slider != null) return ""+slider.getValue();
+ if (sliderView != null) {
+ return ""+sliderView.slider.getValue();
+ } else if (radiobuttons != null) {
+ return radiobuttons.getCurrentValueString();
+ }
String s;
if (box==null) {
@@ -2616,6 +2895,7 @@ public java.awt.Dimension getMaximumSize() {
+" ("+entry.size+" bytes)");
init();
+ if (e.isFlaggedReadOnly()) textComponent.setEnabled(false);
}
@Override
@@ -2722,7 +3002,10 @@ public Dimension getMaximumSize() {
textField = jtf;
} else {
// Long string. Show multi-line editor
- JTextArea jta = new JTextArea(doc, "", Math.min(40, (int)(entry.size / 40)), 80);// line count is heuristic
+ // For character count handling, see EntryPane#init() below
+ JTextArea jta = new JTextArea(doc, "", Math.min(40, (int)(entry.size / 32)), 80);
+ // Line count estimate is heuristic
+ // Limited to 40 lines to keep GUI under control
jta.setEditable(true);
jta.setLineWrap(true);
jta.setWrapStyleWord(true);
@@ -2731,8 +3014,9 @@ public Dimension getMaximumSize() {
textField = jta;
}
textComponent = textField;
- textComponent.setToolTipText("String of up to "+entry.size+" characters");
+ textComponent.setToolTipText("String of up to "+(entry.size-1)+" characters"); // -1 for terminating zero in field
init();
+ if (e.isFlaggedReadOnly()) textComponent.setEnabled(false);
}
@Override
@@ -2936,4 +3220,35 @@ public JTextArea handleEditorValue(JTextArea value) {
return value;
}
}
+
+ /**
+ * Implements the "link" element by providing a line of
+ * text that serves as an active hyperlink.
+ * Neither argument can be null.
+ */
+ class HtmlLabel extends javax.swing.JTextPane {
+ public HtmlLabel(String text, String ref) {
+ super();
+ setContentType("text/html");
+ String content = ""+text+"";
+ setText(content);
+
+ setAlignmentX(Component.LEFT_ALIGNMENT);
+ setFont(UIManager.getFont("TextArea.font"));
+ setEditable(false);
+ setOpaque(false);
+
+ this.addHyperlinkListener(new javax.swing.event.HyperlinkListener() {
+ public void hyperlinkUpdate(javax.swing.event.HyperlinkEvent e) {
+ try {
+ if (e.getEventType() == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) {
+ if(java.awt.Desktop.isDesktopSupported()) {
+ java.awt.Desktop.getDesktop().browse(e.getURL().toURI());
+ }
+ }
+ } catch (Exception ex) {}
+ }
+ });
+ }
+ }
}
diff --git a/src/org/openlcb/swing/EventIdTextField.java b/src/org/openlcb/swing/EventIdTextField.java
index 52a61345..8c360e84 100644
--- a/src/org/openlcb/swing/EventIdTextField.java
+++ b/src/org/openlcb/swing/EventIdTextField.java
@@ -64,6 +64,7 @@ public static JFormattedTextField getEventIdTextField() {
// 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");
retval.setToolTipText("EventID as eight-byte dotted-hex string, "
diff --git a/src/org/openlcb/swing/NodeSelector.java b/src/org/openlcb/swing/NodeSelector.java
index dd9a7dab..7fbe9f26 100644
--- a/src/org/openlcb/swing/NodeSelector.java
+++ b/src/org/openlcb/swing/NodeSelector.java
@@ -272,5 +272,14 @@ public NodeID getSelectedNodeID() {
return me.getNodeID();
}
+ public void setSelectedNodeID(NodeID nodeID) {
+ for (int i = 0; i < model.getSize(); ++i) {
+ if (model.getElementAt(i).getNodeID().equals(nodeID)) {
+ super.setSelectedItem(model.getElementAt(i));
+ break;
+ }
+ }
+ }
+
private static final Logger log = Logger.getLogger(NodeSelector.class.getName());
}
diff --git a/test/org/openlcb/ProducerConsumerEventReportMessageTest.java b/test/org/openlcb/ProducerConsumerEventReportMessageTest.java
index 2d50e0c1..0f138dcb 100644
--- a/test/org/openlcb/ProducerConsumerEventReportMessageTest.java
+++ b/test/org/openlcb/ProducerConsumerEventReportMessageTest.java
@@ -84,6 +84,18 @@ public void testPayloadArray() {
Assert.assertEquals(12, m2.getPayloadArray()[0]);
}
+ @Test
+ public void testPayloadToString() {
+ ProducerConsumerEventReportMessage m1 = new ProducerConsumerEventReportMessage(
+ nodeID1, eventID1 );
+
+ byte[] payload1 = new byte[]{0x12, 0x34};
+ 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());
+ }
+
@Test
public void testPayloadHashAndEquals() {
ProducerConsumerEventReportMessage mNone = new ProducerConsumerEventReportMessage(