From 7ddfe9739b0a1870c8c8b6a7bee95ffdc2d959b3 Mon Sep 17 00:00:00 2001 From: Eilert Tunheim <emtunhei@stud.ntnu.no> Date: Fri, 1 Apr 2022 16:01:58 +0200 Subject: [PATCH] Added check for lower or uppercase input given by the user and changed input from textfields to dropdown menues --- .../java/com/application/DB/Constants.java | 5 +- src/main/java/com/application/DB/DB.java | 47 ++-- .../com/application/GUI/InputPopUpWindow.java | 203 ++++++++++++------ .../GUI/LineChartFunctionality.java | 27 +-- .../com.application/CSS/styleSheet.css | 2 +- .../application/GUI/InputPopUpWindow.class | Bin 5139 -> 5798 bytes 6 files changed, 175 insertions(+), 109 deletions(-) diff --git a/src/main/java/com/application/DB/Constants.java b/src/main/java/com/application/DB/Constants.java index 69d7d92..b2f5926 100644 --- a/src/main/java/com/application/DB/Constants.java +++ b/src/main/java/com/application/DB/Constants.java @@ -20,14 +20,13 @@ public final class Constants { // User inputs public static String TREE_SPECIES = ""; - public static String WIDTH_DIMENTION = ""; - public static String HEIGHT_DIMENTION = ""; + public static String DIMENSIONS = ""; public static String SAWSET = ""; public static String MOISTURE_GOAL = ""; // Number of wanted drying periods - public static int NUMBER_OF_PERIODS = 2; + public static int NUMBER_OF_PERIODS = 1; // Database ID/name public static final String PROJECT_ID = "sf-drying-optimization"; diff --git a/src/main/java/com/application/DB/DB.java b/src/main/java/com/application/DB/DB.java index 7e976b1..670782d 100644 --- a/src/main/java/com/application/DB/DB.java +++ b/src/main/java/com/application/DB/DB.java @@ -127,32 +127,30 @@ public class DB { public static Map<Integer, Map<String, Number>> setInputParameters() throws Exception { - String extraInputParameter = ""; - String treeSpecies = "AND " +NAME_PARAMATERS+ " LIKE "+'"'+"%"+ TREE_SPECIES +"%"+'"'+" "; - String width = "AND " +NAME_PARAMATERS+ " LIKE "+'"'+"%"+ WIDTH_DIMENTION +"%"+'"'+" "; - String height = "AND " +NAME_PARAMATERS+ " LIKE "+'"'+"%"+ HEIGHT_DIMENTION +"%"+'"'+" "; - String widthXHeight = "AND " +NAME_PARAMATERS+ " LIKE "+'"'+"%"+ WIDTH_DIMENTION+"x"+HEIGHT_DIMENTION +"%"+'"'+" "; - String sawset = "AND " +NAME_PARAMATERS+ " LIKE "+'"'+"%"+ SAWSET +"%"+'"'+" "; - String moistureGoal = "AND " +NAME_PARAMATERS+ " LIKE "+'"'+"%"+ MOISTURE_GOAL+'%' +"%"+'"'+" "; + // If location is Valasen, then the database stores furu as fura, swedish. + if(LOCATION_ID == 124 && TREE_SPECIES.equalsIgnoreCase("Furu")) { + TREE_SPECIES = "Fura"; + } System.out.printf("Tree species: \t%s\n",TREE_SPECIES); - System.out.printf("Width: \t\t\t%s\n",WIDTH_DIMENTION); - System.out.printf("Height: \t\t%s\n",HEIGHT_DIMENTION); + System.out.printf("Width: \t\t\t%s\n",DIMENSIONS); System.out.printf("Sawset: \t\t%s\n",SAWSET); System.out.printf("Moisture: \t\t%s\n",MOISTURE_GOAL); + String extraInputParameter = ""; + String treeSpecies = "AND LOWER(" +NAME_PARAMATERS+ ") LIKE LOWER("+'"'+"%"+ TREE_SPECIES +"%"+'"'+") "; + String dimensions = "AND LOWER(" +NAME_PARAMATERS+ ") LIKE LOWER("+'"'+"%"+ DIMENSIONS +"%"+'"'+") "; + String sawset = "AND LOWER(" +NAME_PARAMATERS+ ") LIKE LOWER("+'"'+"%"+ SAWSET +"%"+'"'+") "; + String moistureGoal = "AND LOWER(" +NAME_PARAMATERS+ ") LIKE LOWER("+'"'+"%"+ MOISTURE_GOAL +"%"+'"'+") "; + + + // Input parameters if(!TREE_SPECIES.isEmpty()){ extraInputParameter += treeSpecies; } - if(!WIDTH_DIMENTION.isEmpty() && !HEIGHT_DIMENTION.isEmpty()){ - extraInputParameter += widthXHeight; - } - else if(!WIDTH_DIMENTION.isEmpty()){ - extraInputParameter += width; - } - else if(!HEIGHT_DIMENTION.isEmpty()){ - extraInputParameter += height; + if(!DIMENSIONS.isEmpty()){ + extraInputParameter += dimensions; } if(!SAWSET.isEmpty()){ extraInputParameter += sawset; @@ -163,7 +161,7 @@ public class DB { - Map<String, String> results = new HashMap<>(); + Map<String, String> results; while(true){ System.out.printf("\nExtra parameters:\n %s\n\n",extraInputParameter); // Retrieves the dates @@ -171,21 +169,15 @@ public class DB { // Checks if any dates where found, if not parameters are removed until dates are found if(results.size()<NUMBER_OF_PERIODS){ - if(extraInputParameter.contains(width)) { - extraInputParameter = extraInputParameter.replace(width,""); - System.out.println("Width is removed"); - } else if(extraInputParameter.contains(sawset)) { + if(extraInputParameter.contains(sawset)) { extraInputParameter = extraInputParameter.replace(sawset,""); System.out.println("Sawset is removed"); } else if(extraInputParameter.contains(treeSpecies)) { extraInputParameter = extraInputParameter.replace(treeSpecies,""); System.out.println("Tree species is removed"); - } else if(extraInputParameter.contains(height)) { - extraInputParameter = extraInputParameter.replace(height,""); + } else if(extraInputParameter.contains(dimensions)) { + extraInputParameter = extraInputParameter.replace(dimensions,""); System.out.println("Height is removed"); - } else if(extraInputParameter.contains(widthXHeight)) { - extraInputParameter = extraInputParameter.replace(widthXHeight,""); - System.out.println("widthXHeight is removed"); } else if(extraInputParameter.contains(moistureGoal)) { extraInputParameter = extraInputParameter.replace(moistureGoal,""); System.out.println("Moisture goal is removed"); @@ -329,6 +321,7 @@ public class DB { "AND "+START_DRYING_NAME+" BETWEEN \"1990-01-01 00:00:00\" AND \"" + TODAYS_DATE + "\" " + "AND "+STOP_DRYING_NAME+" BETWEEN \"1990-01-01 00:00:00\" AND \"" + TODAYS_DATE + "\" " + extraInputParameter + + "AND LOWER(" +NAME_PARAMATERS+ ") NOT LIKE LOWER("+'"'+"%"+ "test" +"%"+'"'+") " + "Group by "+ START_DRYING_NAME + " " + "Order by "+ START_DRYING_NAME + " ASC " + "LIMIT " + LIMIT; diff --git a/src/main/java/com/application/GUI/InputPopUpWindow.java b/src/main/java/com/application/GUI/InputPopUpWindow.java index d69a71d..0595c67 100644 --- a/src/main/java/com/application/GUI/InputPopUpWindow.java +++ b/src/main/java/com/application/GUI/InputPopUpWindow.java @@ -1,16 +1,19 @@ package com.application.GUI; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.stage.*; -import org.checkerframework.checker.units.qual.K; import java.util.Map; +import java.util.concurrent.*; import static com.application.DB.Constants.*; import static com.application.DB.DB.setInputParameters; +import static com.application.GUI.LineChartFunctionality.loadMultipleSeries; import static com.application.GUI.LineChartFunctionality.loadSingleSeries; @@ -22,8 +25,13 @@ import static com.application.GUI.LineChartFunctionality.loadSingleSeries; */ public class InputPopUpWindow { - public static void display() - { + private static final Integer SLEEP_MILLS = 3000; + private static final Integer RUN_SLEEP_MILLS = 1000; + private static int afterSeconds = SLEEP_MILLS / RUN_SLEEP_MILLS; + + + + public static void display() { Stage window = new Stage(); window.initModality(Modality.APPLICATION_MODAL); @@ -37,38 +45,60 @@ public class InputPopUpWindow { // Tree species Label treeSpeciesInputLabel = new Label("Tree Species"); treeSpeciesInputLabel.setId("inputLabel"); - TextField treeSpeciesInputText = new TextField(); - treeSpeciesInputText.setId("inputLabelText"); - treeSpeciesInputText.setPromptText("Bjørk"); - treeSpeciesInputText.getText(); - - // Width - Label widthInputLabel = new Label("Width"); - widthInputLabel.setId("inputLabel"); - TextField widthInputText = new TextField(); - widthInputText.setId("inputLabelText"); - widthInputText.setPromptText("47"); - - // Height - Label heightInputLabel = new Label("Height"); - heightInputLabel.setId("inputLabel"); - TextField heightInputText = new TextField(); - heightInputText.setId("inputLabelText"); - heightInputText.setPromptText("200"); + ObservableList<String> treeSpecies = FXCollections.observableArrayList( + "Furu", + "Gran" + ); + ComboBox<String> treeSpeciesList = new ComboBox<String>(treeSpecies); + treeSpeciesList.setPromptText("Select Tree Species"); + treeSpeciesList.setId("inputDropDownBox"); + treeSpeciesList.setEditable(true); + + // Dimensions + Label dimensionsInputLabel = new Label("Dimensions"); + dimensionsInputLabel.setId("inputLabel"); + ObservableList<String> dimensions = FXCollections.observableArrayList( + "47x150", + "47x200", + "50x150", + "50x200" + ); + ComboBox<String> dimensionsList = new ComboBox<String>(dimensions); + dimensionsList.setPromptText("Select Dimensions"); + dimensionsList.setId("inputDropDownBox"); + dimensionsList.setEditable(true); + + // Sawset Label sawsetInputLabel = new Label("Sawset"); sawsetInputLabel.setId("inputLabel"); - TextField sawsetInputText = new TextField(); - sawsetInputText.setId("inputLabelText"); - sawsetInputText.setPromptText("4x"); + ObservableList<String> sawset = FXCollections.observableArrayList( + "1ex", + "2ex", + "3ex", + "4ex" + ); + ComboBox<String> sawsetList = new ComboBox<String>(sawset); + sawsetList.setPromptText("Select Dimensions"); + sawsetList.setId("inputDropDownBox"); + sawsetList.setEditable(true); // Moisture Label moistureGoalInputLabel = new Label("Moisture Goal"); moistureGoalInputLabel.setId("inputLabel"); - TextField moistureGoalInputText = new TextField(); - moistureGoalInputText.setId("inputLabelText"); - moistureGoalInputText.setPromptText("12%"); + ObservableList<String> moistureGoal = FXCollections.observableArrayList( + "10%", + "12%", + "14%", + "16%", + "18%", + "20%" + ); + ComboBox<String> moistureList = new ComboBox<String>(moistureGoal); + moistureList.setPromptText("Select Dimensions"); + moistureList.setId("inputDropDownBox"); + moistureList.setEditable(true);; // Bottom - start button @@ -76,16 +106,89 @@ public class InputPopUpWindow { startButton.setId("inputButtonStart"); startButton.setOnAction(e -> { - TREE_SPECIES = treeSpeciesInputText.getText(); - WIDTH_DIMENTION = widthInputText.getText(); - HEIGHT_DIMENTION = heightInputText.getText(); - SAWSET = sawsetInputText.getText(); - MOISTURE_GOAL = moistureGoalInputText.getText(); + // Retrieves the user inputs + TREE_SPECIES = treeSpeciesList.getValue(); + DIMENSIONS = dimensionsList.getValue(); + SAWSET = sawsetList.getValue(); + MOISTURE_GOAL = moistureList.getValue(); + + // If the input is null, sets the corresponding value to be empty + if(treeSpeciesList.getValue() == null){TREE_SPECIES = "";} + if(dimensionsList.getValue() == null){DIMENSIONS = "";} + if(sawsetList.getValue() == null){SAWSET = "";} + if(moistureList.getValue() == null){MOISTURE_GOAL = "";} + + + + window.close(); + + + try { + loadSingleSeries(setInputParameters()); + //loadMultipleSeries(setInputParameters()); + //loadSingleSeries(); + //loadMultipleSeries(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + ); + + /* + try{ + Thread thread = new Thread(new Runnable() { + + @Override + public void run() { + try { + // Henter her data fra databasen + loadSingleSeries(setInputParameters()); + //loadSingleSeries(); + //loadMultipleSeries(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + thread.start(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + ); + + */ + +/* + // Fungerende ny thread!!@@@@@ + try{ + Thread thread = new Thread(new Runnable() { + + @Override + public void run() { + try { + // Henter her data fra databasen + loadSingleSeries(setInputParameters()); + //loadSingleSeries(); + //loadMultipleSeries(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + thread.start(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } - window.close(); + ); + */ - class KwhThread implements Runnable { +/* + class KwhThread implements Runnable { Map<Integer, Map<String, Number>> dataVariable; private volatile boolean finished; @@ -104,8 +207,6 @@ public class InputPopUpWindow { } } - - public Map<Integer, Map<String, Number>> getDataVariable() throws InterruptedException { synchronized (this){ @@ -129,37 +230,17 @@ public class InputPopUpWindow { } + */ +/* - /* - // Fungerende ny thread!!@@@@@ - try{ - Thread thread = new Thread(new Runnable() { - - @Override - public void run() { - try { - loadSingleSeries(setInputParameters()); - //loadSingleSeries(); - //loadMultipleSeries(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - }); - thread.start(); - //thread.join(); - } catch (Exception ex) { - ex.printStackTrace(); - } + */ - */ - }); VBox layout = new VBox(10); - layout.getChildren().addAll(inputLabel, treeSpeciesInputLabel, treeSpeciesInputText, widthInputLabel, widthInputText, heightInputLabel, heightInputText, - sawsetInputLabel, sawsetInputText, moistureGoalInputLabel, moistureGoalInputText, startButton); + layout.getChildren().addAll(inputLabel, treeSpeciesInputLabel, treeSpeciesList, dimensionsInputLabel, dimensionsList, + sawsetInputLabel, sawsetList, moistureGoalInputLabel, moistureList, startButton); layout.setAlignment(Pos.CENTER); Scene scene = new Scene(layout, 600, 500); diff --git a/src/main/java/com/application/GUI/LineChartFunctionality.java b/src/main/java/com/application/GUI/LineChartFunctionality.java index f856137..2aa9836 100644 --- a/src/main/java/com/application/GUI/LineChartFunctionality.java +++ b/src/main/java/com/application/GUI/LineChartFunctionality.java @@ -15,10 +15,10 @@ public class LineChartFunctionality { private static NumberAxis yAxis; - public LineChartFunctionality(){ + public LineChartFunctionality() { xAxis = new CategoryAxis(); yAxis = new NumberAxis(); - lineChart = new LineChart<>(xAxis,yAxis); + lineChart = new LineChart<>(xAxis, yAxis); xAxis.setLabel("Date"); xAxis.setAnimated(false); yAxis.setLabel("Kwh"); @@ -34,11 +34,11 @@ public class LineChartFunctionality { return lineChart; } - public static void updateLineChart(XYChart.Series<String, Number> series){ + public static void updateLineChart(XYChart.Series<String, Number> series) { lineChart.getData().add(series); } - public static void clearLineChart(){ + public static void clearLineChart() { lineChart.getData().clear(); } @@ -75,17 +75,17 @@ public class LineChartFunctionality { return getLineChart(); } - public static void loadMultipleSeries() throws Exception { + public static LineChart<String, Number> loadMultipleSeries(Map<Integer, Map<String, Number>> userInput) throws Exception { - Map<Integer, Map<String, Number>> kWh = DB.setInputParameters(); + //Map<Integer, Map<String, Number>> kWh = DB.setInputParameters(); //System.out.println(kWh.size()); - for (Map.Entry<Integer, Map<String, Number>> entryKwh : kWh.entrySet()) { + for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) { Map data = entryKwh.getValue(); //System.out.println(data.size()); XYChart.Series<String, Number> newSeries = new XYChart.Series<String, Number>(); - for (Object entryData : data.entrySet()){ + for (Object entryData : data.entrySet()) { //System.out.println("data: \t"+entryData); String entryString = entryData.toString(); String[] arr = entryString.split("="); @@ -96,18 +96,11 @@ public class LineChartFunctionality { // Connect the data to a series - newSeries.getData().add(new XYChart.Data<String, Number>(date,kwhValue)); + newSeries.getData().add(new XYChart.Data<String, Number>(date, kwhValue)); } updateLineChart(newSeries); } - - -/* - for (Map.Entry<Integer, Map> entryKwh : kWh.entrySet()) { - System.out.printf("Index: \t%s\t\t\tkWh: \t%s\n",entryKwh.getKey(),entryKwh.getValue()); - } - */ + return getLineChart(); } - } diff --git a/src/main/resources/com.application/CSS/styleSheet.css b/src/main/resources/com.application/CSS/styleSheet.css index 9669a07..dcd1a78 100644 --- a/src/main/resources/com.application/CSS/styleSheet.css +++ b/src/main/resources/com.application/CSS/styleSheet.css @@ -93,7 +93,7 @@ -fx-font-family: Arial; } -#inputLabelText { +#inputDropDownBox { -fx-pref-height: 17; -fx-translate-x: 5; -fx-max-height: infinity; diff --git a/target/classes/com/application/GUI/InputPopUpWindow.class b/target/classes/com/application/GUI/InputPopUpWindow.class index 5c8bdbb587857b0435a8caf341dc7a0ab82de3d6..9ea2513d2c3ca6d86f03a8c1eccdbd16db02f761 100644 GIT binary patch delta 3483 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 VGX_b9-Oy5Z4+As9UWR=P`vE`wGl2jA delta 2836 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 T$uJjM_{?KqW|+^gfMFp3GAfn4 -- GitLab