diff --git a/.idea/misc.xml b/.idea/misc.xml
index f16dea7..ef46287 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/org/awdevelopment/smithlab/config/AbstractConfig.java b/src/main/java/org/awdevelopment/smithlab/config/AbstractConfig.java
index 89ae578..9116180 100644
--- a/src/main/java/org/awdevelopment/smithlab/config/AbstractConfig.java
+++ b/src/main/java/org/awdevelopment/smithlab/config/AbstractConfig.java
@@ -79,6 +79,14 @@ private void setConfigEntry(ConfigOption option, T value) throws ConfigExcep
@Override
public abstract String toString();
+ public String readableConfig() {
+ StringBuilder sb = new StringBuilder();
+ for (ConfigEntry> entry : config) {
+ sb.append("Option: ").append(entry.option()).append("\tValue: ").append(entry.value()).append("\n");
+ }
+ return sb.toString();
+ }
+
public abstract Mode mode();
public LoggerHelper LOGGER() { return LOGGER; }
diff --git a/src/main/java/org/awdevelopment/smithlab/config/EmptyInputSheetConfig.java b/src/main/java/org/awdevelopment/smithlab/config/EmptyInputSheetConfig.java
index 05e98ea..e84ce2d 100644
--- a/src/main/java/org/awdevelopment/smithlab/config/EmptyInputSheetConfig.java
+++ b/src/main/java/org/awdevelopment/smithlab/config/EmptyInputSheetConfig.java
@@ -78,6 +78,7 @@ public EmptyInputSheetConfig(Arguments args, LoggerHelper logger) {
@Override
public String toString() { return "Empty Input Sheet Configuration"; }
+
@Override
public Mode mode() { return MODE; };
}
diff --git a/src/main/java/org/awdevelopment/smithlab/data/Condition.java b/src/main/java/org/awdevelopment/smithlab/data/Condition.java
index a8dd0fb..61f70e7 100644
--- a/src/main/java/org/awdevelopment/smithlab/data/Condition.java
+++ b/src/main/java/org/awdevelopment/smithlab/data/Condition.java
@@ -1,18 +1,22 @@
package org.awdevelopment.smithlab.data;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Set;
public class Condition {
- private final String name;
+ private String name;
private final Set strains;
private final Set samples;
public Condition(String name) {
this.name = name;
- this.strains = new HashSet<>();
- this.samples = new HashSet<>();
+ this.strains = new LinkedHashSet<>();
+ this.samples = new LinkedHashSet<>();
+ }
+
+ public void setName(String name) {
+ this.name = name;
}
public void addStrain(Strain strain) {
@@ -37,4 +41,6 @@ public Set getSamples() {
public String toString() {
return name;
}
+
+
}
diff --git a/src/main/java/org/awdevelopment/smithlab/data/Strain.java b/src/main/java/org/awdevelopment/smithlab/data/Strain.java
index 02c3f69..ba1ca50 100644
--- a/src/main/java/org/awdevelopment/smithlab/data/Strain.java
+++ b/src/main/java/org/awdevelopment/smithlab/data/Strain.java
@@ -7,7 +7,7 @@
public class Strain {
private Condition condition;
- private final String name;
+ private String name;
private final Set samples;
public Strain(String name) {
@@ -15,6 +15,7 @@ public Strain(String name) {
samples = new HashSet<>();
}
+ public void setName(String name) { this.name = name; }
public String getName() {
return name;
}
@@ -40,11 +41,18 @@ public boolean equals(Object obj) {
return false;
}
Strain strain = (Strain) obj;
- return name.equals(strain.name) && condition.equals(strain.condition);
+ if (condition == null) {
+ if (strain.condition != null) {
+ return false;
+ }
+ } else if (!condition.equals(strain.condition)) {
+ return false;
+ }
+ return name.equals(strain.name);
}
@Override
public int hashCode() {
- return name.hashCode() + condition.hashCode();
+ return name.hashCode() + (condition != null ? condition.hashCode() : 0);
}
}
diff --git a/src/main/java/org/awdevelopment/smithlab/data/experiment/EmptyExperiment.java b/src/main/java/org/awdevelopment/smithlab/data/experiment/EmptyExperiment.java
index fa8dc79..1270e34 100644
--- a/src/main/java/org/awdevelopment/smithlab/data/experiment/EmptyExperiment.java
+++ b/src/main/java/org/awdevelopment/smithlab/data/experiment/EmptyExperiment.java
@@ -2,7 +2,6 @@
import org.awdevelopment.smithlab.data.Condition;
import org.awdevelopment.smithlab.data.Strain;
-import org.awdevelopment.smithlab.io.exceptions.NoDaysException;
import java.util.Set;
@@ -17,85 +16,20 @@ public class EmptyExperiment extends AbstractExperiment {
private final byte numDays;
private final boolean usingNumDays;
- public EmptyExperiment(Set strains, Set conditions, byte numReplicates, byte[] days, byte numDays) throws NoDaysException {
+ public EmptyExperiment(Set conditions, Set strains, byte numReplicates, byte numConditions,
+ byte numStrains, boolean usingNumConditions, boolean usingNumStrains, byte[] days,
+ byte numDays, boolean usingNumDays) {
super(conditions, strains);
- this.usingNumConditions = false;
- this.usingNumStrains = false;
- this.numConditions = (byte) conditions.size();
- this.numStrains = (byte) strains.size();
this.numReplicates = numReplicates;
- if (numDays <= 0 && days.length == 0) throw new NoDaysException();
- else {
- usingNumDays = numDays > 0;
- if (usingNumDays) {
- this.days = new byte[numDays];
- this.numDays = numDays;
- } else {
- this.days = days;
- this.numDays = (byte) days.length;
- }
- }
- }
-
- public EmptyExperiment(byte numStrains, byte numConditions, byte numReplicates, byte[] days, byte numDays) throws NoDaysException {
- super(null, null);
- this.usingNumConditions = true;
- this.usingNumStrains = true;
this.numConditions = numConditions;
this.numStrains = numStrains;
- this.numReplicates = numReplicates;
- if (numDays <= 0 && days.length == 0) throw new NoDaysException();
- else {
- usingNumDays = numDays > 0;
- if (usingNumDays) {
- this.days = new byte[numDays];
- this.numDays = numDays;
- } else {
- this.days = days;
- this.numDays = (byte) days.length;
- }
- }
- }
-
- public EmptyExperiment(Set strains, byte numConditions, byte numReplicates, byte[] days, byte numDays) throws NoDaysException {
- super(null, strains);
- this.usingNumConditions = false;
- this.usingNumStrains = true;
- this.numConditions = numConditions;
- this.numStrains = (byte) strains.size();
- this.numReplicates = numReplicates;
- if (numDays <= 0 && days.length == 0) throw new NoDaysException();
- else {
- usingNumDays = numDays > 0;
- if (usingNumDays) {
- this.days = new byte[numDays];
- this.numDays = numDays;
- } else {
- this.days = days;
- this.numDays = (byte) days.length;
- }
- }
+ this.usingNumConditions = usingNumConditions;
+ this.usingNumStrains = usingNumStrains;
+ this.days = days;
+ this.numDays = numDays;
+ this.usingNumDays = usingNumDays;
}
- public EmptyExperiment(byte numStrains, Set conditions, byte numReplicates, byte[] days, byte numDays) throws NoDaysException {
- super(conditions, null);
- this.usingNumConditions = true;
- this.usingNumStrains = false;
- this.numConditions = (byte) conditions.size();
- this.numStrains = numStrains;
- this.numReplicates = numReplicates;
- if (numDays <= 0 && days.length == 0) throw new NoDaysException();
- else {
- usingNumDays = numDays > 0;
- if (usingNumDays) {
- this.days = new byte[numDays];
- this.numDays = numDays;
- } else {
- this.days = days;
- this.numDays = (byte) days.length;
- }
- }
- }
public byte getNumReplicates() { return numReplicates; }
public boolean hasNoDays() { return days.length == 0 && !usingNumDays; }
@@ -106,4 +40,37 @@ public EmptyExperiment(byte numStrains, Set conditions, byte numRepli
public boolean usingNumStrains() { return usingNumStrains; }
public byte getNumConditions() { return numConditions; }
public byte getNumStrains() { return numStrains; }
+
+ public String readableExperiment() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Experiment with ");
+ if (usingNumStrains) {
+ sb.append(numStrains).append(" strains, ");
+ } else {
+ StringBuilder strainsList = new StringBuilder();
+ for (Strain strain : strains()) { strainsList.append(strain.getName()).append(", "); }
+ strainsList.setLength(Math.max(strainsList.length() - 2, 0)); // Remove trailing comma and space, if any
+ sb.append(strains().size()).append(" strains (").append(strainsList).append("), ");
+ }
+ if (usingNumConditions) {
+ sb.append(numConditions).append(" conditions, ");
+ } else {
+ StringBuilder conditionsList = new StringBuilder();
+ for (Condition condition : conditions()) { conditionsList.append(condition.getName()).append(", "); }
+ conditionsList.setLength(Math.max(conditionsList.length() - 2, 0)); // Remove trailing comma and space, if any
+ sb.append(conditions().size()).append(" conditions (").append(conditionsList).append("), ");
+ }
+ sb.append(numReplicates).append(" replicates");
+ if (usingNumDays) {
+ sb.append(", ").append(numDays).append(" days.");
+ } else if (days.length > 0) {
+ sb.append(", days: ");
+ for (byte day : days) {
+ sb.append(day).append(" ");
+ }
+ } else {
+ sb.append(".");
+ }
+ return sb.toString();
+ }
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/SceneLoader.java b/src/main/java/org/awdevelopment/smithlab/gui/SceneLoader.java
index 12ed49d..3914cfe 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/SceneLoader.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/SceneLoader.java
@@ -3,6 +3,7 @@
import javafx.stage.Stage;
import org.awdevelopment.smithlab.config.EmptyInputSheetConfig;
import org.awdevelopment.smithlab.gui.controllers.AbstractLabelController;
+import org.awdevelopment.smithlab.gui.controllers.main.MainApplicationController;
import org.awdevelopment.smithlab.logging.LoggerHelper;
public class SceneLoader {
@@ -16,13 +17,17 @@ public static void loadScene(Stage stage, FXMLResourceType fxmlResourceType, Log
stage.show();
}
- public static AbstractLabelController loadScene(Stage stage, FXMLResourceType fxmlResourceType, LoggerHelper logger, EmptyInputSheetConfig config) {
+ public static AbstractLabelController loadScene(Stage stage, FXMLResourceType fxmlResourceType, LoggerHelper logger,
+ EmptyInputSheetConfig config,
+ MainApplicationController mainController) {
FXMLResource fxmlResource = FXMLResourceLoader.load(fxmlResourceType, logger);
fxmlResource.controller().setLogger(logger);
((AbstractLabelController) fxmlResource.controller()).setConfig(config);
fxmlResource.controller().setup();
stage.setTitle(fxmlResourceType.getWindowTitle());
stage.setScene(fxmlResource.scene());
+ AbstractLabelController controller = (AbstractLabelController) fxmlResource.controller();
+ controller.setMainController(mainController);
stage.show();
return (AbstractLabelController) fxmlResource.controller();
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/AbstractLabelController.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/AbstractLabelController.java
index f627bae..71d5e9b 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/AbstractLabelController.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/AbstractLabelController.java
@@ -1,10 +1,12 @@
package org.awdevelopment.smithlab.gui.controllers;
import org.awdevelopment.smithlab.config.EmptyInputSheetConfig;
+import org.awdevelopment.smithlab.gui.controllers.main.MainApplicationController;
public abstract class AbstractLabelController extends AbstractController {
private EmptyInputSheetConfig config;
+ private MainApplicationController mainController;
public AbstractLabelController() { super(); }
@@ -12,7 +14,12 @@ public void setConfig(EmptyInputSheetConfig config) {
this.config = config;
}
+ public void setMainController(MainApplicationController mainController) { this.mainController = mainController; }
+
public EmptyInputSheetConfig config() {
return config;
}
+
+ public MainApplicationController getMainController() { return mainController; }
+
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/ConditionsController.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/ConditionsController.java
index 2b0a810..764d71c 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/ConditionsController.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/ConditionsController.java
@@ -1,28 +1,199 @@
package org.awdevelopment.smithlab.gui.controllers;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
import org.awdevelopment.smithlab.data.Condition;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
+/**
+ * Controller that maintains a dynamic list of TextFields so that:
+ * - There is always exactly one empty field at the bottom.
+ * - When the bottom field becomes non-empty, a new empty field is appended.
+ * - When a user clears a field and leaves it, it disappears if another empty bottom field exists.
+ */
public class ConditionsController extends AbstractLabelController {
+ @FXML private VBox conditionsBox;
+ @FXML private Button saveCloseButton;
+
private boolean usingNumConditions;
- private final Set conditions;
- public ConditionsController() {
- super();
- conditions = new HashSet<>();
+ private final Set conditions = new LinkedHashSet<>();
+ private final Map textFieldToCondition = new HashMap<>();
+
+ // ----------------- lifecycle -----------------
+
+ @FXML
+ public void initialize() {
+ // Make sure we start with one empty field at the bottom
+ ensureOneBlankAtBottom();
+ this.usingNumConditions = true;
}
@Override
- public void setup() {}
+ public void setup() { }
- public boolean usingNumConditions() { return usingNumConditions; }
+ // ----------------- public API -----------------
+
+ public boolean usingNumConditions() {
+ return usingNumConditions;
+ }
public void setUsingNumConditions(boolean usingNumConditions) {
this.usingNumConditions = usingNumConditions;
}
- public Set getConditions() { return conditions; }
+ public Set getConditions() {
+ return Collections.unmodifiableSet(conditions);
+ }
+
+ /**
+ * Pre-populate the UI with known conditions. You may call this before the view is shown.
+ */
+ public void loadConditions(Collection initialConditions) {
+ conditions.clear();
+ conditions.addAll(initialConditions);
+
+ conditionsBox.getChildren().clear();
+ textFieldToCondition.clear();
+
+ for (Condition c : initialConditions) {
+ TextField tf = createField(c.getName());
+ conditionsBox.getChildren().add(tf);
+ textFieldToCondition.put(tf, c);
+ }
+ ensureOneBlankAtBottom();
+ }
+
+ // ----------------- UI callbacks -----------------
+
+ @FXML
+ private void handleSaveAndClose(ActionEvent ignoredEvent) {
+ // Optionally, clean trailing empties (leaving exactly one at bottom):
+ pruneExtraEmptiesKeepOne();
+ Stage stage = (Stage) saveCloseButton.getScene().getWindow();
+ stage.close();
+ }
+
+ // ----------------- core logic -----------------
+
+ private TextField createField(String text) {
+
+ TextField tf = new TextField(text);
+
+ // Show placeholder only if the field is empty
+ if (text == null || text.isEmpty()) {
+ tf.setPromptText("Enter condition name...");
+ }
+
+ // React live to changes
+ tf.textProperty().addListener((obs, oldValue, newValue) -> onTextChanged(tf, newValue));
+
+ // On focus loss, maybe remove if empty
+ tf.focusedProperty().addListener((obs, wasFocused, isFocused) -> {
+ if (!isFocused) {
+ onFocusLost(tf);
+ }
+ });
+
+ return tf;
+ }
+
+ private void onTextChanged(TextField tf, String newValue) {
+ String trimmed = newValue.trim();
+
+ if (trimmed.isEmpty()) {
+ // Remove mapping and condition if it existed
+ Condition cond = textFieldToCondition.remove(tf);
+ if (cond != null) {
+ conditions.remove(cond);
+ super.config().setConditions(conditions);
+ if (conditions.isEmpty()) {
+ this.setUsingNumConditions(true);
+ this.getMainController().enableNumConditions();
+ }
+ }
+ } else {
+ // Non-empty: make sure a Condition exists and stays updated
+ Condition cond = textFieldToCondition.get(tf);
+ if (cond == null) {
+ cond = new Condition(trimmed);
+ textFieldToCondition.put(tf, cond);
+ conditions.add(cond);
+ super.config().setConditions(conditions);
+ super.getMainController().disableNumConditions();
+ this.setUsingNumConditions(false);
+ } else if (!trimmed.equals(cond.getName())) {
+ cond.setName(trimmed);
+ }
+
+ // If user typed into the last (blank) field, append a new blank
+ if (isLast(tf)) {
+ conditionsBox.getChildren().add(createField(""));
+ }
+ }
+ }
+
+ private void onFocusLost(TextField tf) {
+ if (!tf.getText().trim().isEmpty()) {
+ return; // only care about empty fields
+ }
+
+ // If it's not the only field AND there's another empty field at the bottom, remove it
+ if (conditionsBox.getChildren().size() > 1) {
+ TextField last = lastField();
+ if (last != tf && last.getText().trim().isEmpty()) {
+ conditionsBox.getChildren().remove(tf);
+ }
+ }
+
+ // Guarantee there is one (and exactly one) empty field at the bottom
+ ensureOneBlankAtBottom();
+ }
+
+ private void ensureOneBlankAtBottom() {
+ if (conditionsBox.getChildren().isEmpty()) {
+ conditionsBox.getChildren().add(createField(""));
+ return;
+ }
+
+ TextField last = lastField();
+ if (!last.getText().trim().isEmpty()) {
+ conditionsBox.getChildren().add(createField(""));
+ } else {
+ // Also ensure there aren't multiple blank fields at bottom accidentally
+ pruneExtraEmptiesKeepOne();
+ }
+ }
+
+ private void pruneExtraEmptiesKeepOne() {
+ List empties = new ArrayList<>();
+ for (Node n : conditionsBox.getChildren()) {
+ if (n instanceof TextField tf && tf.getText().trim().isEmpty()) {
+ empties.add(tf);
+ }
+ }
+ // keep the *last* one, remove all previous empties
+ for (int i = 0; i < empties.size() - 1; i++) {
+ conditionsBox.getChildren().remove(empties.get(i));
+ }
+ }
+
+ private boolean isLast(TextField tf) {
+ ObservableList kids = conditionsBox.getChildren();
+ return !kids.isEmpty() && kids.getLast() == tf;
+ }
+
+ private TextField lastField() {
+ ObservableList kids = conditionsBox.getChildren();
+ return (TextField) kids.getLast();
+ }
+
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/StrainsController.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/StrainsController.java
index 8445f7b..34a883a 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/StrainsController.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/StrainsController.java
@@ -1,25 +1,183 @@
package org.awdevelopment.smithlab.gui.controllers;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
import org.awdevelopment.smithlab.data.Strain;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
+/**
+ * Controller that maintains a dynamic list of TextFields so that:
+ * - There is always exactly one empty field at the bottom.
+ * - When the bottom field becomes non-empty, a new empty field is appended.
+ * - When a user clears a field and leaves it, it disappears if another empty bottom field exists.
+ */
public class StrainsController extends AbstractLabelController {
+ @FXML private VBox strainsBox;
+ @FXML private Button saveCloseButton;
+
private boolean usingNumStrains;
- private final Set strains;
- public StrainsController() {
- super();
- strains = new HashSet<>();
+ private final Set strains = new LinkedHashSet<>();
+ private final Map textFieldToStrain = new HashMap<>();
+
+ // ----------------- lifecycle -----------------
+
+ @FXML
+ public void initialize() {
+ // Make sure we start with one empty field at the bottom
+ ensureOneBlankAtBottom();
+ this.usingNumStrains = true;
}
@Override
- public void setup() {}
+ public void setup() { }
+
+ // ----------------- public API -----------------
+
+ public boolean usingNumStrains() {
+ return usingNumStrains;
+ }
+
+ public void setUsingNumStrains(boolean usingNumStrains) {
+ this.usingNumStrains = usingNumStrains;
+ }
+
+ public Set getStrains() {
+ return Collections.unmodifiableSet(strains);
+ }
+
+ /**
+ * Pre-populate the UI with known strains. You may call this before the view is shown.
+ */
+ public void loadStrains(Collection initialStrains) {
+ strains.clear();
+ strains.addAll(initialStrains);
+
+ strainsBox.getChildren().clear();
+ textFieldToStrain.clear();
+
+ for (Strain s : initialStrains) {
+ TextField tf = createField(s.getName());
+ strainsBox.getChildren().add(tf);
+ textFieldToStrain.put(tf, s);
+ }
+ ensureOneBlankAtBottom();
+ }
+
+ // ----------------- UI callbacks -----------------
+
+ @FXML
+ private void handleSaveAndClose(ActionEvent ignoredEvent) {
+ pruneExtraEmptiesKeepOne();
+ Stage stage = (Stage) saveCloseButton.getScene().getWindow();
+ stage.close();
+ }
+
+ // ----------------- core logic -----------------
+
+ private TextField createField(String text) {
+ TextField tf = new TextField(text);
- public boolean usingNumStrains() { return usingNumStrains; }
+ if (text == null || text.isEmpty()) {
+ tf.setPromptText("Enter strain name...");
+ }
- public void setUsingNumStrains(boolean usingNumStrains) { this.usingNumStrains = usingNumStrains; }
- public Set getStrains() { return strains; }
+ tf.textProperty().addListener((obs, oldValue, newValue) -> onTextChanged(tf, newValue));
+ tf.focusedProperty().addListener((obs, wasFocused, isFocused) -> {
+ if (!isFocused) {
+ onFocusLost(tf);
+ }
+ });
+
+ return tf;
+ }
+
+ private void onTextChanged(TextField tf, String newValue) {
+ String trimmed = newValue.trim();
+
+ if (trimmed.isEmpty()) {
+ Strain strain = textFieldToStrain.remove(tf);
+ if (strain != null) {
+ strains.remove(strain);
+ super.config().setStrains(strains);
+ if (strains.isEmpty()) {
+ this.setUsingNumStrains(true);
+ this.getMainController().enableNumStrains();
+ }
+ }
+ } else {
+ Strain strain = textFieldToStrain.get(tf);
+ if (strain == null) {
+ strain = new Strain(trimmed);
+ textFieldToStrain.put(tf, strain);
+ strains.add(strain);
+ super.config().setStrains(strains);
+ super.getMainController().disableNumStrains();
+ this.setUsingNumStrains(false);
+ } else if (!trimmed.equals(strain.getName())) {
+ strain.setName(trimmed);
+ }
+
+ if (isLast(tf)) {
+ strainsBox.getChildren().add(createField(""));
+ }
+ }
+ }
+
+ private void onFocusLost(TextField tf) {
+ if (!tf.getText().trim().isEmpty()) return;
+
+ if (strainsBox.getChildren().size() > 1) {
+ TextField last = lastField();
+ if (last != tf && last.getText().trim().isEmpty()) {
+ strainsBox.getChildren().remove(tf);
+ }
+ }
+
+ ensureOneBlankAtBottom();
+ }
+
+ private void ensureOneBlankAtBottom() {
+ if (strainsBox.getChildren().isEmpty()) {
+ strainsBox.getChildren().add(createField(""));
+ return;
+ }
+
+ TextField last = lastField();
+ if (!last.getText().trim().isEmpty()) {
+ strainsBox.getChildren().add(createField(""));
+ } else {
+ pruneExtraEmptiesKeepOne();
+ }
+ }
+
+ private void pruneExtraEmptiesKeepOne() {
+ List empties = new ArrayList<>();
+ for (Node n : strainsBox.getChildren()) {
+ if (n instanceof TextField tf && tf.getText().trim().isEmpty()) {
+ empties.add(tf);
+ }
+ }
+ for (int i = 0; i < empties.size() - 1; i++) {
+ strainsBox.getChildren().remove(empties.get(i));
+ }
+ }
+
+ private boolean isLast(TextField tf) {
+ ObservableList kids = strainsBox.getChildren();
+ return !kids.isEmpty() && kids.getLast() == tf;
+ }
+
+ private TextField lastField() {
+ ObservableList kids = strainsBox.getChildren();
+ return (TextField) kids.getLast();
+ }
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/TimepointsController.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/TimepointsController.java
index 3860aa1..e378f15 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/TimepointsController.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/TimepointsController.java
@@ -5,7 +5,7 @@
public class TimepointsController extends AbstractLabelController {
- private boolean usingNumDays = false;
+ private boolean usingNumDays = true; // Default to using num days
private final Set days;
public TimepointsController() {
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractConfigUpdater.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractConfigUpdater.java
index 7d86867..fe2f744 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractConfigUpdater.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractConfigUpdater.java
@@ -113,18 +113,47 @@ else if (keyEvent != null && (!keyEvent.getText().isEmpty() || keyEvent.getCode(
}
}
+ boolean updateControllerConnectedField(ValidatableField field, ConfigOption option, KeyEvent keyEvent) {
+ LOGGER.atDebug("Updating controller connected field with id: \"" + field.getControlID() + "\"...");
+ updateTextField(field, option, keyEvent);
+ // If the field is not unused and has been validated, return true - this indicates that we are using the number of conditions/strains/days
+ if (field.status() != FieldStatus.UNUSED) {
+ return true;
+ }
+ // If the field is unused, return false - this indicates that we are not using the number of conditions/strains/days
+ LOGGER.atDebug("Field with id: \"" + field.getControlID() + "\" is unused, returning false.");
+ return false;
+ }
+
void updateIfValidated(ValidatableField field, ConfigOption option) {
LOGGER.atDebug("Checking if field was validated successfully...");
if (field.status() == FieldStatus.READY) {
LOGGER.atDebug("Field was validated successfully, updating stored value...");
switch (field.getFieldType()) {
- case BYTE -> config.set(option, Byte.parseByte(getTextField(field).getText()));
+ case BYTE -> {
+ config.set(option, Byte.parseByte(getTextField(field).getText()));
+ if (config.get(option) == ConfigOption.NUM_DAYS) {
+ config.set(ConfigOption.USING_NUM_DAYS, true);
+
+ } else if (config.get(option) == ConfigOption.NUM_CONDITIONS) {
+ config.set(ConfigOption.USING_NUM_CONDITIONS, true);
+ } else if (config.get(option) == ConfigOption.NUM_STRAINS) {
+ config.set(ConfigOption.USING_NUM_STRAINS, true);
+ }
+ }
case FILENAME, STRING -> config.set(option, getTextField(field).getText());
case EXISTING_FILE -> config.set(option, new File(getTextField(field).getText()));
default -> throw new IllegalStateException("Unexpected value: " + field.getFieldType());
}
LOGGER.atDebug("Stored value updated successfully.");
- } else {
+ } else if (field.status() == FieldStatus.UNUSED) {
+ if (option == ConfigOption.NUM_DAYS) {
+ config.set(ConfigOption.USING_NUM_DAYS, false);
+ } else if (option == ConfigOption.NUM_CONDITIONS) {
+ config.set(ConfigOption.USING_NUM_CONDITIONS, false);
+ } else if (option == ConfigOption.NUM_STRAINS) {
+ config.set(ConfigOption.USING_NUM_STRAINS, false);
+ }
LOGGER.atDebug("Field was not validated successfully, not updating stored value.");
}
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractValidator.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractValidator.java
index 7419f48..df3fccb 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractValidator.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/AbstractValidator.java
@@ -44,6 +44,10 @@ public boolean fieldsValid() {
validateTextFieldByID(field.getControlID());
} else if (field.status() == FieldStatus.EDITED_NOT_VALIDATED) {
validateTextFieldByID(field.getControlID());
+ } else if (field.status() == FieldStatus.UNUSED) {
+ LOGGER.atDebug("Field with id: \"" + field.getControlID() + "\" is unused.");
+ LOGGER.atDebug("Field status: \"" + field.status());
+ LOGGER.atDebug("Continuing field validation...");
}
if (field.status() == FieldStatus.INVALID || field.status() == FieldStatus.EMPTY) {
LOGGER.atDebug("Field with id: \"" + field.getControlID() + "\" is invalid or empty.");
@@ -62,11 +66,12 @@ void validateTextFieldNotEmpty(ValidatableField field) {
TextField textField = getTextField(field);
if (textField.getText().isEmpty()) {
field.setStatus(FieldStatus.INVALID);
- guiLogger.errorOccurred(field.getErrorLabel(), "Error: Please enter a value");
LOGGER.atDebug("Field with id: \"" + field.getControlID() + "\" is empty.");
+ LOGGER.atDebug("Error Label: Label object with ID \"" + field.getErrorLabel().getAccessibleHelp() + "\"");
+ guiLogger.errorOccurred(field.getErrorLabel(), "Error: Please enter a value");
} else {
- guiLogger.clearError(field.getErrorLabel());
LOGGER.atDebug("Field with id: \"" + field.getControlID() + "\" is not empty.");
+ guiLogger.clearError(field.getErrorLabel());
field.setStatus(FieldStatus.READY);
}
}
@@ -104,7 +109,6 @@ void validateTextFieldFilename(ValidatableField field) {
}
void validateTextFieldByte(ValidatableField field) {
- if (field.status() == FieldStatus.UNTOUCHED) return;
validateTextFieldNotEmpty(field);
TextField textField = getTextField(field);
try {
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetConfigUpdater.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetConfigUpdater.java
index 43c5455..505031d 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetConfigUpdater.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetConfigUpdater.java
@@ -32,7 +32,7 @@ public void updateFields() {
}
public void updateOutputFilename(KeyEvent keyEvent) {
- updateTextField(fields.getOutputFilenameTextField(), ConfigOption.OUTPUT_FILE, keyEvent);
+ updateTextField(fields.getOutputFilenameTextField(), ConfigOption.EMPTY_INPUT_SHEET_NAME, keyEvent);
}
public void updateNumReplicates(KeyEvent keyEvent) {
@@ -40,15 +40,15 @@ public void updateNumReplicates(KeyEvent keyEvent) {
}
public void updateNumTimepoints(KeyEvent keyEvent) {
- updateTextField(fields.getNumTimepointsTextField(), ConfigOption.NUM_DAYS, keyEvent);
+ config.setUsingNumDays(updateControllerConnectedField(fields.getNumTimepointsTextField(), ConfigOption.NUM_DAYS, keyEvent));
}
public void updateNumConditions(KeyEvent keyEvent) {
- updateTextField(fields.getNumConditionsTextField(), ConfigOption.NUM_CONDITIONS, keyEvent);
+ config.setUsingNumConditions(updateControllerConnectedField(fields.getNumConditionsTextField(), ConfigOption.NUM_CONDITIONS, keyEvent));
}
public void updateNumStrains(KeyEvent keyEvent) {
- updateTextField(fields.getNumStrainsTextField(), ConfigOption.NUM_STRAINS, keyEvent);
+ config.setUsingNumStrains(updateControllerConnectedField(fields.getNumStrainsTextField(), ConfigOption.NUM_STRAINS, keyEvent));
}
public void updateSampleLabelingRadioButtons(ActionEvent actionEvent) {
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetFields.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetFields.java
index 33dd560..c1956f5 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetFields.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetFields.java
@@ -21,7 +21,7 @@ public class EmptyInputSheetFields extends AbstractFields {
private final ValidatableField sampleSortingMethodChoiceBox;
EmptyInputSheetFields(MainApplicationController controller) {
- super(controller, new ValidatableField[] { new ValidatableField(controller.numTimepointsTextField, controller.timepointsAddedLabel, FieldType.BYTE),
+ super(controller, new ValidatableField[] { new ValidatableField(controller.numTimepointsTextField, controller.numTimepointsErrorLabel, FieldType.BYTE),
new ValidatableField(controller.numConditionsTextField, controller.numConditionsErrorLabel, FieldType.BYTE),
new ValidatableField(controller.numStrainsTextField, controller.numStrainErrorLabel, FieldType.BYTE),
new ValidatableField(controller.numReplicatesEmptyInputSheetTextField, controller.numReplicatesErrorLabelEmptyInputSheet, FieldType.BYTE),
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetValidator.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetValidator.java
index ba56d78..69b6a4a 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetValidator.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/EmptyInputSheetValidator.java
@@ -1,6 +1,7 @@
package org.awdevelopment.smithlab.gui.controllers.main;
import org.awdevelopment.smithlab.config.EmptyInputSheetConfig;
+import org.awdevelopment.smithlab.config.SampleLabelingType;
import org.awdevelopment.smithlab.gui.controllers.AbstractLabelController;
import org.awdevelopment.smithlab.gui.controllers.ConditionsController;
import org.awdevelopment.smithlab.gui.controllers.StrainsController;
@@ -22,6 +23,17 @@ public EmptyInputSheetValidator(EmptyInputSheetFields fields, GUILogger guiLogge
this.config = config;
}
+ @Override
+ public boolean fieldsValid() {
+ if (!config.usingNumConditions() || config.sampleLabelingType() == SampleLabelingType.STRAIN) {
+ fields.getNumConditionsTextField().setStatus(FieldStatus.UNUSED);
+ }
+ if (!config.usingNumStrains() || config.sampleLabelingType() == SampleLabelingType.CONDITION) {
+ fields.getNumStrainsTextField().setStatus(FieldStatus.UNUSED);
+ }
+ return super.fieldsValid();
+ }
+
void validateNumTimepoints() { validateControllerConnectedField(fields.getNumTimepointsTextField(), fields.getTimepointsController()); }
void validateOutputFilename() { validateTextFieldFilename(fields.getOutputFilenameTextField()); }
@@ -73,7 +85,9 @@ private void validateControllerConnectedField(ValidatableField field, AbstractLa
switch (controller) {
case null -> { }
case TimepointsController timepointsController -> {
- if (timepointsController.usingNumDays()) validateTextFieldByte(field);
+ if (timepointsController.usingNumDays()) {
+ validateTextFieldByte(field);
+ }
else {
guiLogger.clearError(field.getErrorLabel());
config.setUsingNumDays(timepointsController.usingNumDays());
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/MainApplicationController.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/MainApplicationController.java
index a5e6a6b..8e32b65 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/MainApplicationController.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/MainApplicationController.java
@@ -7,6 +7,8 @@
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.awdevelopment.smithlab.config.*;
+import org.awdevelopment.smithlab.data.Condition;
+import org.awdevelopment.smithlab.data.Strain;
import org.awdevelopment.smithlab.data.experiment.Experiment;
import org.awdevelopment.smithlab.gui.FXMLResourceType;
import org.awdevelopment.smithlab.gui.SceneLoader;
@@ -23,6 +25,8 @@
import org.awdevelopment.smithlab.io.output.OutputGenerator;
import org.awdevelopment.smithlab.logging.GUILogger;
+import java.util.Set;
+
public class MainApplicationController extends AbstractController {
private ConfigManager config;
@@ -108,6 +112,18 @@ public void setup() {
emptyInputSheetFields.getSampleLabelingRadioButtons().setStatus(FieldStatus.READY);
setupErrorLabelsOutputSheet();
setupErrorLabelsEmptyInputSheet();
+ setupSubControllers();
+ }
+
+ private void setupSubControllers() {
+ // Initialize sub-controllers for conditions, strains, and timepoints
+ conditionsController = new ConditionsController();
+ strainsController = new StrainsController();
+ timepointsController = new TimepointsController();
+ // Set up the sub-controllers
+ conditionsController.setup();
+ strainsController.setup();
+ timepointsController.setup();
}
private void choiceBoxSetup() {
@@ -134,6 +150,11 @@ private void radioButtonSetup() {
private void setupErrorLabel(Label label) {
label.setText("");
label.setStyle("");
+ label.setDisable(false);
+ label.setVisible(true);
+ label.setManaged(true);
+ label.setWrapText(true);
+ label.setAccessibleText(label.getId());
}
private void setupErrorLabels(Label... labels) {
@@ -151,7 +172,7 @@ private void setupErrorLabelsEmptyInputSheet() {
}
public void generateOutput() {
- getLogger().atDebug("Mode: " + mode);
+ getLogger().atFatal("Mode: " + mode);
switch (mode) {
case GENERATE_OUTPUT_SHEETS -> {
guiLogger.clearError(statusLabelOutputSheet);
@@ -160,7 +181,10 @@ public void generateOutput() {
}
case GENERATE_EMPTY_INPUT_SHEET -> {
guiLogger.clearError(statusLabelEmptyInputSheet);
- if (!emptyInputSheetValidator.fieldsValid()) return;
+ if (!emptyInputSheetValidator.fieldsValid()) {
+ return;
+ }
+ getLogger().atFatal("Fields are valid, generating empty input sheet...");
generateEmptyInputSheet();
}
case IMAGE_RECOGNITION -> {}
@@ -247,22 +271,36 @@ public void updateMode() {
private void generateEmptyInputSheet() {
OutputGenerator outputGenerator;
+ if (config.getEmptyInputSheetConfig().sampleLabelingType() == SampleLabelingType.STRAIN) {
+ getLogger().atDebug("Using strain labeling type for empty input sheet.");
+ } else if (config.getEmptyInputSheetConfig().sampleLabelingType() == SampleLabelingType.CONDITION) {
+ getLogger().atDebug("Using condition labeling type for empty input sheet.");
+ } else {
+ getLogger().atDebug("Using condition and strain labeling type for empty input sheet.");
+ }
+ config.getEmptyInputSheetConfig().setUsingNumDays(timepointsController.usingNumDays());
+ getLogger().atDebug("CONFIG:\n" + config.getEmptyInputSheetConfig().readableConfig());
try {
outputGenerator = new OutputGenerator(config.getEmptyInputSheetConfig());
+ getLogger().atDebug("CREATE OUTPUT GENERATOR - SUCCESS");
} catch (NoDaysException | NoStrainsOrConditionsException e) {
guiLogger.errorOccurred(statusLabelEmptyInputSheet, e.getMessage());
return;
}
+ getLogger().atDebug("GENERATE EMPTY INPUT SHEET - START");
try {
outputGenerator.generateEmptyInputSheet();
+ getLogger().atDebug("GENERATE EMPTY INPUT SHEET - SUCCESS");
} catch (OutputException e) {
guiLogger.errorOccurred(statusLabelEmptyInputSheet, e.getMessage());
return;
}
+
statusLabelEmptyInputSheet.setText("Successfully generated output!");
statusLabelEmptyInputSheet.setStyle("-fx-text-fill: green");
}
+
public void updateFieldsEmptyInputSheet() {
emptyInputSheetConfigUpdater.updateFields();
emptyInputSheetValidator.preliminaryFieldsValid();
@@ -275,19 +313,28 @@ public void updateFieldsOutputSheets() {
public void openConditionsFXML() {
updateFieldsEmptyInputSheet();
- conditionsController = (ConditionsController) SceneLoader.loadScene(new Stage(), FXMLResourceType.CONDITIONS, getLogger(), config.getEmptyInputSheetConfig());
+ Stage stage = new Stage();
+ conditionsController = (ConditionsController)
+ SceneLoader.loadScene(stage, FXMLResourceType.CONDITIONS, getLogger(), config.getEmptyInputSheetConfig(), this);
+ conditionsController.loadConditions(config.getEmptyInputSheetConfig().conditions());
}
public void openStrainsFXML() {
updateFieldsEmptyInputSheet();
- strainsController = (StrainsController) SceneLoader.loadScene(new Stage(), FXMLResourceType.STRAINS, getLogger(), config.getEmptyInputSheetConfig());
+ Stage stage = new Stage();
+ strainsController = (StrainsController)
+ SceneLoader.loadScene(stage, FXMLResourceType.STRAINS, getLogger(), config.getEmptyInputSheetConfig(), this);
+ strainsController.loadStrains(config.getEmptyInputSheetConfig().strains());
}
public void openTimepointsFXML() {
updateFieldsEmptyInputSheet();
- timepointsController = (TimepointsController) SceneLoader.loadScene(new Stage(), FXMLResourceType.TIMEPOINTS, getLogger(), config.getEmptyInputSheetConfig());
+ Stage stage = new Stage();
+ timepointsController = (TimepointsController)
+ SceneLoader.loadScene(stage, FXMLResourceType.TIMEPOINTS, getLogger(), config.getEmptyInputSheetConfig(), this);
}
+
public void updateSampleLabelingRadioButtons(ActionEvent actionEvent) { emptyInputSheetConfigUpdater.updateSampleLabelingRadioButtons(actionEvent); }
public void updateNumConditionsEmptyInputSheet(KeyEvent keyEvent) { emptyInputSheetConfigUpdater.updateNumConditions(keyEvent); }
@@ -303,4 +350,54 @@ public void openTimepointsFXML() {
public void updateOutputFilenameEmptyInputSheet(KeyEvent keyEvent) { emptyInputSheetConfigUpdater.updateOutputFilename(keyEvent); }
public Control getControlByID(String id) { return (Control) tabPane.getScene().lookup("#" + id); }
+
+ public void disableNumConditions() {
+ this.numConditionsErrorLabel.setDisable(true);
+ this.numConditionsTextField.setDisable(true);
+ this.emptyInputSheetFields.getNumConditionsTextField().setStatus(FieldStatus.UNUSED);
+ }
+
+ public void enableNumConditions() {
+ this.numConditionsErrorLabel.setDisable(false);
+ this.numConditionsTextField.setDisable(false);
+ if (this.numConditionsTextField.getText().isEmpty()) {
+ this.emptyInputSheetFields.getNumConditionsTextField().setStatus(FieldStatus.UNTOUCHED);
+ } else {
+ this.emptyInputSheetFields.getNumConditionsTextField().setStatus(FieldStatus.EDITED_NOT_VALIDATED);
+ }
+ }
+
+ public void disableNumStrains() {
+ this.numStrainErrorLabel.setDisable(true);
+ this.numStrainsTextField.setDisable(true);
+ this.emptyInputSheetFields.getNumStrainsTextField().setStatus(FieldStatus.UNUSED);
+ }
+
+ public void enableNumStrains() {
+ this.numStrainErrorLabel.setDisable(false);
+ this.numStrainsTextField.setDisable(false);
+ if (this.numStrainsTextField.getText().isEmpty()) {
+ this.emptyInputSheetFields.getNumStrainsTextField().setStatus(FieldStatus.UNTOUCHED);
+ }
+ else {
+ this.emptyInputSheetFields.getNumStrainsTextField().setStatus(FieldStatus.EDITED_NOT_VALIDATED);
+ }
+ }
+
+ public void disableNumReplicates() {
+ this.numReplicatesErrorLabelEmptyInputSheet.setDisable(true);
+ this.numReplicatesEmptyInputSheetTextField.setDisable(true);
+ this.emptyInputSheetFields.getNumReplicatesTextField().setStatus(FieldStatus.UNUSED);
+ }
+
+ public void enableNumReplicates() {
+ this.numReplicatesErrorLabelEmptyInputSheet.setDisable(false);
+ this.numReplicatesEmptyInputSheetTextField.setDisable(false);
+ if (this.numReplicatesEmptyInputSheetTextField.getText().isEmpty()) {
+ this.emptyInputSheetFields.getNumReplicatesTextField().setStatus(FieldStatus.UNTOUCHED);
+ } else {
+ this.emptyInputSheetFields.getNumReplicatesTextField().setStatus(FieldStatus.EDITED_NOT_VALIDATED);
+ }
+ }
+
}
diff --git a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/validatable_field/FieldStatus.java b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/validatable_field/FieldStatus.java
index 6e21037..8e3d003 100644
--- a/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/validatable_field/FieldStatus.java
+++ b/src/main/java/org/awdevelopment/smithlab/gui/controllers/main/validatable_field/FieldStatus.java
@@ -5,5 +5,6 @@ public enum FieldStatus {
INVALID,
UNTOUCHED,
EMPTY,
- EDITED_NOT_VALIDATED
+ EDITED_NOT_VALIDATED,
+ UNUSED
}
diff --git a/src/main/java/org/awdevelopment/smithlab/io/output/OutputGenerator.java b/src/main/java/org/awdevelopment/smithlab/io/output/OutputGenerator.java
index 2026ea3..9fa675b 100644
--- a/src/main/java/org/awdevelopment/smithlab/io/output/OutputGenerator.java
+++ b/src/main/java/org/awdevelopment/smithlab/io/output/OutputGenerator.java
@@ -1,8 +1,10 @@
package org.awdevelopment.smithlab.io.output;
-import org.awdevelopment.smithlab.config.ConfigDefaults;
import org.awdevelopment.smithlab.config.EmptyInputSheetConfig;
import org.awdevelopment.smithlab.config.OutputSheetsConfig;
+import org.awdevelopment.smithlab.config.SampleLabelingType;
+import org.awdevelopment.smithlab.data.Condition;
+import org.awdevelopment.smithlab.data.Strain;
import org.awdevelopment.smithlab.data.experiment.EmptyExperiment;
import org.awdevelopment.smithlab.io.exceptions.NoDaysException;
import org.awdevelopment.smithlab.io.exceptions.NoStrainsOrConditionsException;
@@ -12,6 +14,10 @@
import org.awdevelopment.smithlab.logging.LoggerHelper;
import java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
public class OutputGenerator {
@@ -46,10 +52,11 @@ public OutputGenerator(OutputSheetsConfig config) {
public OutputGenerator(EmptyInputSheetConfig config) throws NoDaysException, NoStrainsOrConditionsException {
LOGGER = config.LOGGER();
- outputFileName = ConfigDefaults.EMPTY_INPUT_SHEET_FILENAME;
+ outputFileName = config.emptyInputSheetName();
emptyInputSheetName = config.emptyInputSheetName();
includeBaselineColumn = config.includeBaselineColumn();
emptyExperiment = generateEmptyExperiment(config);
+ LOGGER.atDebug("EMPTY EXPERIMENT:\n" + emptyExperiment.readableExperiment());
GUI = false;
// these are not used in this constructor
this.outputStyle = null;
@@ -79,22 +86,88 @@ public void generateEmptyInputSheet() throws OutputException {
else if (this.emptyExperiment.usingNumDays()) {
LOGGER.atInfo("No days specified. Using number of days and leaving day numbers blank in empty input sheet.");
}
+ LOGGER.atDebug("WRITING EMPTY INPUT SHEET WITH NAME: " + emptyInputSheetName);
emptyInputSheetWriter = new XlsxEmptyInputSheetWriter(emptyInputSheetName, LOGGER, includeBaselineColumn, emptyExperiment);
+ LOGGER.atDebug("SUCCESSFULLY CREATED EMPTY INPUT SHEET WRITER");
emptyInputSheetWriter.writeEmptyInputSheet();
+ LOGGER.atDebug("SUCCESSFULLY WROTE EMPTY INPUT SHEET TO FILE: " + emptyInputSheetName);
}
private EmptyExperiment generateEmptyExperiment(EmptyInputSheetConfig config) throws NoDaysException {
- Byte[] days = config.days().toArray(new Byte[0]);
- byte[] daysArray = new byte[days.length];
- for (int i = 0; i < days.length; i++) { daysArray[i] = days[i];}
- if (config.usingNumConditions() && config.usingNumStrains()) {
- return new EmptyExperiment(config.numStrains(), config.numConditions(), config.numReplicates(), daysArray, config.numDays());
- } else if (config.usingNumConditions()) {
- return new EmptyExperiment(config.strains(), config.numConditions(), config.numReplicates(), daysArray, config.numDays());
- } else if (config.usingNumStrains()) {
- return new EmptyExperiment(config.numStrains(), config.conditions(), config.numReplicates(), daysArray, config.numDays());
+ Set conditionsSet = new LinkedHashSet<>();
+ Set strainsSet = new LinkedHashSet<>();
+ boolean usingNumConditions, usingNumStrains, usingNumDays;
+ byte numConditions, numStrains, numReplicates, numDays;
+ byte[] days;
+ switch (config.sampleLabelingType()) {
+ case SampleLabelingType.STRAIN -> {
+ usingNumConditions = false;
+ conditionsSet = Collections.emptySet();
+ numConditions = -1; // -1 indicates no conditions
+ usingNumStrains = config.usingNumStrains();
+ if (usingNumStrains) {
+ numStrains = config.numStrains();
+ strainsSet = new HashSet<>();
+ for (int i = 0; i < numStrains; i++) { strainsSet.add(new Strain("Strain " + (i + 1))); }
+ } else {
+ strainsSet = config.strains();
+ numStrains = (byte) config.strains().size();
+ }
+ }
+ case SampleLabelingType.CONDITION -> {
+ usingNumConditions = config.usingNumConditions();
+ strainsSet = Collections.emptySet();
+ numStrains = -1; // -1 indicates no strains
+ usingNumStrains = false;
+ if (usingNumConditions) {
+ numConditions = config.numConditions();
+ for (int i = 0; i < numConditions; i++) {
+ conditionsSet.add(new Condition("Condition " + (i + 1)));
+ }
+ } else {
+ conditionsSet = config.conditions();
+ numConditions = (byte) config.conditions().size();
+ }
+ }
+ case SampleLabelingType.CONDITION_AND_STRAIN -> {
+ usingNumConditions = config.usingNumConditions();
+ usingNumStrains = config.usingNumStrains();
+ if (usingNumConditions) {
+ numConditions = config.numConditions();
+ for (int i = 0; i < numConditions; i++) {
+ conditionsSet.add(new Condition("Condition " + (i + 1)));
+ }
+ } else {
+ conditionsSet = config.conditions();
+ numConditions = (byte) config.conditions().size();
+ }
+ if (usingNumStrains) {
+ numStrains = config.numStrains();
+ for (int i = 0; i < numStrains; i++) {
+ strainsSet.add(new Strain("Strain " + (i + 1)));
+ }
+ } else {
+ strainsSet = config.strains();
+ numStrains = (byte) config.strains().size();
+ }
+ }
+ default ->
+ throw new IllegalArgumentException("Invalid sample labeling type: " + config.sampleLabelingType());
+ }
+ if (config.usingNumDays()) {
+ usingNumDays = true;
+ if (config.numDays() <= 0) throw new NoDaysException();
+ numDays = config.numDays();
+ days = new byte[numDays];
+ for (int i = 0; i < numDays; i++) { days[i] = (byte) (i + 1); }
} else {
- return new EmptyExperiment(config.strains(), config.conditions(), config.numReplicates(), daysArray, config.numDays());
+ if (config.days().isEmpty()) throw new NoDaysException();
+ Byte[] daysByteArray = config.days().toArray(new Byte[0]);
+ days = new byte[daysByteArray.length];
+ numDays = (byte) days.length;
}
+ numReplicates = config.numReplicates();
+ return new EmptyExperiment(conditionsSet, strainsSet, numReplicates, numConditions, numStrains,
+ usingNumConditions, usingNumStrains, days, numDays, config.usingNumDays());
}
}
diff --git a/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetHeaderWriter.java b/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetHeaderWriter.java
index a6ef6e6..f7dac8a 100644
--- a/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetHeaderWriter.java
+++ b/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetHeaderWriter.java
@@ -1,6 +1,8 @@
package org.awdevelopment.smithlab.io.output;
import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
@@ -10,6 +12,7 @@
import org.awdevelopment.smithlab.io.exceptions.NoStrainsOrConditionsException;
import org.awdevelopment.smithlab.logging.LoggerHelper;
+import java.util.Iterator;
import java.util.Set;
public class XlsxEmptyInputSheetHeaderWriter {
@@ -21,8 +24,9 @@ public class XlsxEmptyInputSheetHeaderWriter {
private final boolean usingNumDays;
private final boolean includeBaselineColumn;
private final byte numDays;
+ private final byte numReplicates;
- public XlsxEmptyInputSheetHeaderWriter(LoggerHelper logger, Set conditions, Set strains, byte[] days, boolean includeBaselineColumn) {
+ public XlsxEmptyInputSheetHeaderWriter(LoggerHelper logger, Set conditions, Set strains, byte[] days, boolean includeBaselineColumn, byte numReplicates) {
this.LOGGER = logger;
this.conditions = conditions;
this.strains = strains;
@@ -30,9 +34,10 @@ public XlsxEmptyInputSheetHeaderWriter(LoggerHelper logger, Set condi
this.numDays = (byte) days.length;
this.usingNumDays = false;
this.includeBaselineColumn = includeBaselineColumn;
+ this.numReplicates = numReplicates;
}
- public XlsxEmptyInputSheetHeaderWriter(LoggerHelper logger, Set conditions, Set strains, byte numDays, boolean includeBaselineColumn) {
+ public XlsxEmptyInputSheetHeaderWriter(LoggerHelper logger, Set conditions, Set strains, byte numDays, boolean includeBaselineColumn, byte numReplicates) {
this.LOGGER = logger;
this.conditions = conditions;
this.strains = strains;
@@ -40,6 +45,7 @@ public XlsxEmptyInputSheetHeaderWriter(LoggerHelper logger, Set condi
this.numDays = numDays;
this.usingNumDays = true;
this.includeBaselineColumn = includeBaselineColumn;
+ this.numReplicates = numReplicates;
}
public void generateHeaders(XSSFSheet emptyInputSheet) throws NoStrainsOrConditionsException {
@@ -64,6 +70,8 @@ private void generateDayHeaders(XSSFRow headerRow, XSSFRow subHeaderRow, byte la
headerRow.createCell(++lastUsedColumn);
CellRangeAddress mergeRange = new CellRangeAddress(0, 0, lastUsedColumn - 2, lastUsedColumn);
headerRow.getSheet().addMergedRegion(mergeRange);
+ dayHeaderCell.setCellType(CellType.NUMERIC);
+ dayHeaderCell.setCellStyle(dayHeaderCellStyle);
XSSFCell subHeaderCell = subHeaderRow.createCell(lastUsedColumn - 2);
subHeaderCell.setCellValue("Colonies");
subHeaderCell = subHeaderRow.createCell(lastUsedColumn - 1);
@@ -81,16 +89,79 @@ private byte generateStrainConditionLabels(XSSFRow headerRow) throws NoStrainsOr
} else if (conditions.isEmpty()) {
LOGGER.atInfo("No conditions provided - just using strains");
headerRow.createCell(0).setCellValue("Strain");
+ generateStrainLabels(headerRow, 0);
return 0;
} else if (strains.isEmpty()) {
LOGGER.atInfo("No strains provided - just using conditions");
headerRow.createCell(0).setCellValue("Condition");
+ generateConditionLabels(headerRow, false);
return 0;
+
} else {
LOGGER.atInfo("Conditions and strains provided");
headerRow.createCell(0).setCellValue("Condition");
headerRow.createCell(1).setCellValue("Strain");
+ generateConditionLabels(headerRow, true);
+ generateStrainLabels(headerRow, 1);
return 1;
}
}
+
+ private void generateConditionLabels(XSSFRow headerRow, boolean usingStrains) {
+ int curRowNum = 2;
+ int mergedAreaHeight;
+ if (usingStrains) {
+ mergedAreaHeight = numReplicates * strains.size();
+ } else {
+ mergedAreaHeight = numReplicates;
+ }
+ for (Condition condition : conditions) {
+ LOGGER.atInfo("Generating condition: " + condition);
+ for (int i = 0; i < mergedAreaHeight; i++) {
+ headerRow.getSheet().createRow(curRowNum + i).createCell(0);
+ }
+ XSSFCell conditionLabelCell = headerRow.getSheet().getRow(curRowNum).getCell(0);
+ conditionLabelCell.setCellValue(condition.getName());
+ // Create a merged cell region that spans the number of replicates for the condition
+ CellRangeAddress conditionLabelCellAddress = new CellRangeAddress(curRowNum, curRowNum + (mergedAreaHeight - 1), 0, 0);
+ headerRow.getSheet().addMergedRegion(conditionLabelCellAddress);
+ curRowNum += mergedAreaHeight;
+ CellStyle conditionLabelStyle = headerRow.getSheet().getWorkbook().createCellStyle();
+ conditionLabelStyle.setWrapText(true);
+ conditionLabelCell.setCellStyle(conditionLabelStyle);
+
+ }
+ }
+
+ private void generateStrainLabels(XSSFRow headerRow, int columnIndex) {
+ int curRowNum = 2;
+ if (columnIndex == 1) {
+ for (int i = 0; i < conditions.size(); i++) {
+ generateOneSetOfStrainLabels(headerRow, columnIndex, curRowNum, numReplicates);
+ curRowNum += numReplicates * strains.size();
+ }
+ } else {
+ generateOneSetOfStrainLabels(headerRow, columnIndex, curRowNum, numReplicates);
+ }
+
+ }
+
+ private void generateOneSetOfStrainLabels(XSSFRow headerRow, int columnIndex, int curRowNum, int numReplicates) {
+ for (Strain strain : strains) {
+ // Create a merged cell region that spans the number of replicates for the strain
+ CellRangeAddress strainLabelCellAddress = new CellRangeAddress(curRowNum, curRowNum + (numReplicates - 1), columnIndex, columnIndex);
+ headerRow.getSheet().addMergedRegion(strainLabelCellAddress);
+ for (int i = 0; i < numReplicates; i++) {
+ if (headerRow.getSheet().getRow(curRowNum + i) == null) {
+ headerRow.getSheet().createRow(curRowNum + i);
+ }
+ }
+ XSSFCell strainLabelCell = headerRow.getSheet().getRow(curRowNum).createCell(columnIndex);
+ curRowNum += numReplicates;
+ strainLabelCell.setCellValue(strain.getName());
+ CellStyle strainLabelStyle = headerRow.getSheet().getWorkbook().createCellStyle();
+ strainLabelStyle.setWrapText(true);
+ strainLabelCell.setCellStyle(strainLabelStyle);
+ }
+ }
}
diff --git a/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetWriter.java b/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetWriter.java
index f422466..dda6d85 100644
--- a/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetWriter.java
+++ b/src/main/java/org/awdevelopment/smithlab/io/output/XlsxEmptyInputSheetWriter.java
@@ -41,8 +41,8 @@ public void writeEmptyInputSheet() throws FailedToCreateNewWorkbookException, Wr
LOGGER.atInfo("Writing empty input sheet \"" + emptyInputSheetName + "\"...");
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XlsxEmptyInputSheetHeaderWriter headerWriter;
- if (usingNumDays) headerWriter = new XlsxEmptyInputSheetHeaderWriter(LOGGER, conditions, strains, numDays, includeBaselineColumn);
- else headerWriter = new XlsxEmptyInputSheetHeaderWriter(LOGGER, conditions, strains, days, includeBaselineColumn);
+ if (usingNumDays) headerWriter = new XlsxEmptyInputSheetHeaderWriter(LOGGER, conditions, strains, numDays, includeBaselineColumn, numReplicates);
+ else headerWriter = new XlsxEmptyInputSheetHeaderWriter(LOGGER, conditions, strains, days, includeBaselineColumn, numReplicates);
headerWriter.generateHeaders(workbook.createSheet());
try {
workbook.write(new FileOutputStream(emptyInputSheetName));
diff --git a/src/main/resources/fxml/application.fxml b/src/main/resources/fxml/application.fxml
index f4f2cec..43d97f6 100644
--- a/src/main/resources/fxml/application.fxml
+++ b/src/main/resources/fxml/application.fxml
@@ -108,7 +108,7 @@
-
+
diff --git a/src/main/resources/fxml/conditions.fxml b/src/main/resources/fxml/conditions.fxml
index ae8fbf3..f3efeea 100644
--- a/src/main/resources/fxml/conditions.fxml
+++ b/src/main/resources/fxml/conditions.fxml
@@ -1,14 +1,45 @@
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/strains.fxml b/src/main/resources/fxml/strains.fxml
index 1a292a0..474ec86 100644
--- a/src/main/resources/fxml/strains.fxml
+++ b/src/main/resources/fxml/strains.fxml
@@ -1,14 +1,44 @@
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 226473a..837b40c 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -13,8 +13,8 @@
-
-
+
+