Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add operation and lane comments, add lane name #150

Merged
merged 18 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@

<resource>
<directory>res</directory>
<filtering>true</filtering>
</resource>

</resources>
Expand Down
Binary file added res/comment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified res/disable.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified res/help.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/no_comment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified res/operation.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified res/remove.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified res/stop.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified res/stop_active.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions res/version.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
version = ${project.version}
36 changes: 36 additions & 0 deletions src/main/java/de/usd/cstchef/operations/Operation.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -80,6 +86,9 @@ public abstract class Operation extends JPanel {
private Box contentBox;
private Map<String, Component> uiElements;

private String comment;
private JButton commentBtn;

private int operationSkip = 0;
private int laneSkip = 0;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<String, Object> getState() {
Map<String, Object> properties = new HashMap<>();
for (String key : this.uiElements.keySet()) {
Expand Down
99 changes: 92 additions & 7 deletions src/main/java/de/usd/cstchef/view/RecipePanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
}
Expand All @@ -364,40 +379,107 @@ 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<String, Object> parameters = mapper.convertValue(operationNode.get("parameters"), Map.class);
Class<Operation> cls = (Class<Operation>) 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<Operation> operations = stepPanel.getOperations();
for (Operation op : operations) {
ObjectNode operationNode = mapper.createObjectNode();
operationNode.put("operation", op.getClass().getName());
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 {
Expand Down Expand Up @@ -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();
}
}
Expand Down
83 changes: 82 additions & 1 deletion src/main/java/de/usd/cstchef/view/RecipeStepPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}
Loading