diff --git a/pom.xml b/pom.xml
index eff7f82..ed56f26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,7 @@
res
+ true
diff --git a/res/comment.png b/res/comment.png
new file mode 100755
index 0000000..8880c9e
Binary files /dev/null and b/res/comment.png differ
diff --git a/res/disable.png b/res/disable.png
old mode 100644
new mode 100755
index 60149af..0e80c76
Binary files a/res/disable.png and b/res/disable.png differ
diff --git a/res/help.png b/res/help.png
old mode 100644
new mode 100755
index e66abc6..28c16d3
Binary files a/res/help.png and b/res/help.png differ
diff --git a/res/no_comment.png b/res/no_comment.png
new file mode 100755
index 0000000..7ba13c9
Binary files /dev/null and b/res/no_comment.png differ
diff --git a/res/operation.png b/res/operation.png
old mode 100644
new mode 100755
index c1103f7..84c2f02
Binary files a/res/operation.png and b/res/operation.png differ
diff --git a/res/remove.png b/res/remove.png
old mode 100644
new mode 100755
index 406976c..fc42e25
Binary files a/res/remove.png and b/res/remove.png differ
diff --git a/res/stop.png b/res/stop.png
old mode 100644
new mode 100755
index e450c91..29ea6d9
Binary files a/res/stop.png and b/res/stop.png differ
diff --git a/res/stop_active.png b/res/stop_active.png
old mode 100644
new mode 100755
index 9ca2fee..a94eb52
Binary files a/res/stop_active.png and b/res/stop_active.png differ
diff --git a/res/version.properties b/res/version.properties
new file mode 100644
index 0000000..713c915
--- /dev/null
+++ b/res/version.properties
@@ -0,0 +1 @@
+version = ${project.version}
\ No newline at end of file
diff --git a/src/main/java/de/usd/cstchef/operations/Operation.java b/src/main/java/de/usd/cstchef/operations/Operation.java
index fceb094..efd807b 100644
--- a/src/main/java/de/usd/cstchef/operations/Operation.java
+++ b/src/main/java/de/usd/cstchef/operations/Operation.java
@@ -9,6 +9,8 @@
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
import java.io.EOFException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -19,6 +21,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
@@ -28,6 +31,7 @@
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JSpinner;
@@ -68,6 +72,8 @@ public abstract class Operation extends JPanel {
private static ImageIcon disableIcon = new ImageIcon(Operation.class.getResource("/disable.png"));
private static ImageIcon removeIcon = new ImageIcon(Operation.class.getResource("/remove.png"));
private static ImageIcon helpIcon = new ImageIcon(Operation.class.getResource("/help.png"));
+ private static ImageIcon commentIcon = new ImageIcon(Operation.class.getResource("/comment.png"));
+ private static ImageIcon noCommentIcon = new ImageIcon(Operation.class.getResource("/no_comment.png"));
private NotifyChangeListener notifyChangeListener;
@@ -80,6 +86,9 @@ public abstract class Operation extends JPanel {
private Box contentBox;
private Map uiElements;
+ private String comment;
+ private JButton commentBtn;
+
private int operationSkip = 0;
private int laneSkip = 0;
@@ -122,6 +131,19 @@ public Operation() {
removeBtn.setToolTipText("Remove");
JButton helpBtn = createIconButton(Operation.helpIcon);
helpBtn.setToolTipText(opInfos.description());
+ commentBtn = createIconButton(noCommentIcon);
+
+ commentBtn.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ commentBtn.setToolTipText(getComment());
+ String comment = JOptionPane.showInputDialog("Edit comment:", commentBtn.getToolTipText());
+ commentBtn.setToolTipText(comment);
+ setComment(comment);
+ ImageIcon newIcon = comment.isEmpty() ? Operation.noCommentIcon : Operation.commentIcon;
+ commentBtn.setIcon(newIcon);
+ }
+ });
+
disableBtn.addActionListener(new ActionListener() {
@Override
@@ -162,6 +184,8 @@ public void actionPerformed(ActionEvent e) {
header.add(titleLbl);
header.add(Box.createHorizontalStrut(6));
header.add(helpBtn);
+ header.add(Box.createHorizontalStrut(3));
+ header.add(commentBtn);
header.add(Box.createHorizontalGlue());
header.add(disableBtn);
header.add(Box.createHorizontalStrut(3));
@@ -189,6 +213,18 @@ public void actionPerformed(ActionEvent e) {
this.refreshColors();
}
+ public String getComment() {
+ return this.comment;
+ }
+
+ public void setComment(String comment) {
+ if(comment != null) {
+ this.comment = comment;
+ commentBtn.setIcon(Operation.commentIcon);
+ commentBtn.setToolTipText(comment);
+ }
+ }
+
public Map getState() {
Map properties = new HashMap<>();
for (String key : this.uiElements.keySet()) {
diff --git a/src/main/java/de/usd/cstchef/view/RecipePanel.java b/src/main/java/de/usd/cstchef/view/RecipePanel.java
index 8f4cd84..9647439 100644
--- a/src/main/java/de/usd/cstchef/view/RecipePanel.java
+++ b/src/main/java/de/usd/cstchef/view/RecipePanel.java
@@ -12,11 +12,13 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
@@ -350,10 +352,23 @@ public void setFormatMessage(HttpRequestResponse requestResponse, MessageType me
}
public void restoreState(String jsonState) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
- // TODO do we want to remove all existing operations before loading here?
- this.clear(); // Yes!
+ this.clear();
ObjectMapper mapper = new ObjectMapper();
- JsonNode stepNodes = mapper.readTree(jsonState);
+ JsonNode rootNode = mapper.readTree(jsonState);
+ JsonNode stepNodes;
+ JsonNode versionNode;
+
+ // check if "version" ObjectNode is there (since 1.3.2)
+ if(rootNode.get(0) != null && rootNode.get(0).get("version") == null) {
+ // recipes saved by CSTC <= 1.3.1
+ stepNodes = rootNode;
+ }
+ else {
+ // currently 1.3.2
+ versionNode = rootNode.get(0);
+ stepNodes = rootNode.get(1);
+ }
+
if (!stepNodes.isArray()) {
throw new IOException("wrong data format");
}
@@ -364,29 +379,81 @@ public void restoreState(String jsonState) throws IOException, ClassNotFoundExce
throw new IOException("wrong data format");
}
- for (int i = 0; i < operationNodes.size(); i++) {
+ RecipeStepPanel panel = (RecipeStepPanel) this.operationLines.getComponent(step);
+
+ /* two types of ObjectNodes for every RecipeStepPanel:
+ Lane information (always at index 0, if set) and the Operations
+
+ If there's a lane ObjectNode we need to tell the inner loop to begin at index 1.
+ The inner loop iterates over the Operations
+ */
+ int index = 0;
+ if(operationNodes.get(0) != null) {
+ if(operationNodes.get(0).get("lane_title") != null) {
+ index = 1;
+ panel.setTitle(operationNodes.get(0).get("lane_title").asText());
+ }
+ if(operationNodes.get(0).get("lane_comment") != null) {
+ index = 1;
+ panel.setComment(operationNodes.get(0).get("lane_comment").asText());
+ }
+ }
+
+ for (int i = index; i < operationNodes.size(); i++) {
JsonNode operationNode = operationNodes.get(i);
String operation = operationNode.get("operation").asText();
Map parameters = mapper.convertValue(operationNode.get("parameters"), Map.class);
Class cls = (Class) Class.forName(operation);
+
// check if it is an operation
Operation op = cls.newInstance();
op.load(parameters);
op.setDisabled(!operationNode.get("is_enabled").asBoolean());
- RecipeStepPanel panel = (RecipeStepPanel) this.operationLines.getComponent(step);
- panel.addComponent(op, i);
+
+ // check if "comment" attribute is set (since 1.3.2)
+ if(operationNode.get("comment") != null) {
+ if(operationNode.get("comment").asText() != "null") {
+ op.setComment(operationNode.get("comment").asText());
+ }
+ }
+ // depending on if lane name is set we may start the loop at index 1, but want to add the first component at index 0
+ panel.addComponent(op, index == 1 ? i-1 : 0);
}
}
}
private String getStateAsJSON() throws IOException {
ObjectMapper mapper = new ObjectMapper();
+ ArrayNode rootNode = mapper.createArrayNode();
+ ObjectNode versionNode = mapper.createObjectNode();
ArrayNode stepsNode = mapper.createArrayNode();
for (int step = 0; step < this.operationSteps; step++) {
ArrayNode operationsNode = mapper.createArrayNode();
RecipeStepPanel stepPanel = (RecipeStepPanel) this.operationLines.getComponent(step);
+
+ // save lane name in case it differs from the default
+ int laneNumber = step + 1;
+ boolean laneNodeCreated = false;
+ if(!stepPanel.getTitle().equals("Lane " + laneNumber)) {
+ laneNodeCreated = true;
+ ObjectNode laneNode = mapper.createObjectNode();
+ laneNode.put("lane_title", stepPanel.getTitle());
+ // save comment in same node in case it is set
+ if(!stepPanel.getComment().equals("")) {
+ laneNode.put("lane_comment", stepPanel.getComment());
+ }
+ operationsNode.add(laneNode);
+ }
+
+ // save comment in case it's not already
+ if(!laneNodeCreated && !stepPanel.getComment().equals("")) {
+ ObjectNode laneNode = mapper.createObjectNode();
+ laneNode.put("lane_comment", stepPanel.getComment());
+ operationsNode.add(laneNode);
+ }
+
List operations = stepPanel.getOperations();
for (Operation op : operations) {
ObjectNode operationNode = mapper.createObjectNode();
@@ -394,10 +461,25 @@ private String getStateAsJSON() throws IOException {
operationsNode.add(operationNode);
operationNode.putPOJO("parameters", op.getState());
operationNode.putPOJO("is_enabled", !op.isDisabled());
+ // "comment":null if empty
+ operationNode.put("comment", op.getComment());
}
stepsNode.add(operationsNode);
}
- return mapper.writeValueAsString(stepsNode);
+
+ /* maven performs a substitution at compile time in "/res/version.properties"
+ with the version from pom.xml and here it reads from this file
+ */
+ Properties properties = new Properties();
+ properties.load(RecipePanel.class.getResourceAsStream("/version.properties"));
+ String version = properties.getProperty("version");
+
+ versionNode.put("version", version);
+
+ rootNode.add(versionNode);
+ rootNode.add(stepsNode);
+
+ return mapper.writeValueAsString(rootNode);
}
private void save(File file) throws IOException {
@@ -587,6 +669,9 @@ private void saveFilterState() {
private void clear() {
for (int step = 0; step < this.operationSteps; step++) {
RecipeStepPanel stepPanel = (RecipeStepPanel) this.operationLines.getComponent(step);
+ int laneIndex = step + 1;
+ stepPanel.setTitle("Lane " + laneIndex);
+ stepPanel.clearComment();
stepPanel.clearOperations();
}
}
diff --git a/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java b/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
index 89b79da..f479220 100644
--- a/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
+++ b/src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
@@ -3,14 +3,24 @@
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
+import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
@@ -27,6 +37,13 @@ public class RecipeStepPanel extends JPanel {
private JPanel operationsLine;
private GridBagConstraints addContraints;
private ChangeListener changeListener;
+ private JTextField contentTextField;
+
+ private String comment;
+ private JButton commentBtn;
+
+ private static ImageIcon commentIcon = new ImageIcon(Operation.class.getResource("/comment.png"));
+ private static ImageIcon noCommentIcon = new ImageIcon(Operation.class.getResource("/no_comment.png"));
public RecipeStepPanel(String title, ChangeListener changelistener) {
this.changeListener = changelistener;
@@ -41,12 +58,39 @@ public RecipeStepPanel(String title, ChangeListener changelistener) {
CompoundBorder border = new CompoundBorder(lineBorder, margin);
headerBox.setBorder(border);
- JTextField contentTextField = new JTextField();
+ contentTextField = new JTextField();
contentTextField.setBorder(null);
contentTextField.setBackground(new Color(0, 0, 0, 0));
contentTextField.setText(title);
+ contentTextField.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ String newTitle = JOptionPane.showInputDialog("Edit title:", getTitle());
+ contentTextField.setText(newTitle.length() <= 50 ? newTitle : getTitle());
+ setTitle(newTitle.length() <= 50 ? newTitle : getTitle()); // lane name should be leq 50 chars
+ }
+ });
headerBox.add(contentTextField);
+ JPanel panel = new JPanel();
+ panel.setBackground(new Color(0, 0, 0, 0)); // transparent
+
+ commentBtn = createIconButton(noCommentIcon);
+
+ commentBtn.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ commentBtn.setToolTipText(getComment());
+ String comment = JOptionPane.showInputDialog("Edit comment:", commentBtn.getToolTipText());
+ commentBtn.setToolTipText(comment);
+ setComment(comment);
+ ImageIcon newIcon = comment.isEmpty() ? RecipeStepPanel.noCommentIcon : RecipeStepPanel.commentIcon;
+ commentBtn.setIcon(newIcon);
+ }
+ });
+
+ panel.add(commentBtn);
+ headerBox.add(panel);
+
this.add(headerBox, BorderLayout.NORTH);
// body
@@ -124,4 +168,41 @@ public void clearOperations() {
operationsLine.repaint();
this.changeListener.stateChanged(new ChangeEvent(this));
}
+
+ public String getTitle() {
+ return contentTextField.getText();
+ }
+
+ public void setTitle(String title) {
+ contentTextField.setText(title);
+ }
+
+ private JButton createIconButton(ImageIcon icon) {
+ JButton btn = new JButton();
+ btn.setBorder(BorderFactory.createEmptyBorder());
+ btn.setIcon(icon);
+ btn.setContentAreaFilled(false);
+ btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ btn.setAlignmentX(Component.RIGHT_ALIGNMENT);
+
+ return btn;
+ }
+
+ public String getComment() {
+ return this.comment;
+ }
+
+ public void setComment(String comment) {
+ if(comment != null) {
+ this.comment = comment;
+ commentBtn.setIcon(RecipeStepPanel.commentIcon);
+ commentBtn.setToolTipText(comment);
+ }
+ }
+
+ public void clearComment() {
+ this.comment = "";
+ commentBtn.setToolTipText("");
+ commentBtn.setIcon(RecipeStepPanel.noCommentIcon);
+ }
}