diff options
author | Jonas Smedegaard <dr@jones.dk> | 2025-04-30 22:21:28 +0200 |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2025-04-30 22:23:08 +0200 |
commit | b591061ec982ea087488afe39ce55fff6fa4bfa4 (patch) | |
tree | aa6c1351e5648a6438f440829551dcb8f513bf99 /src/dk.biks.bachelorizer | |
parent | 714b07ea2012a880e0bee2ec6e090324500fa06a (diff) |
merge project portfolio2 into bachelorizer, except class myDB
Diffstat (limited to 'src/dk.biks.bachelorizer')
6 files changed, 519 insertions, 141 deletions
diff --git a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Controller.java b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Controller.java index ab62bfa..63eac9d 100644 --- a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Controller.java +++ b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Controller.java @@ -1,16 +1,25 @@ +// SPDX-FileCopyrightText: 2025 <Alexander Marthin Klemensen stud-marthin@ruc.dk> +// SPDX-FileCopyrightText: 2025 <Ian Valentin Christensen stud-ianc@ruc.dk> // SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk> +// SPDX-FileCopyrightText: 2025 <Zahed Noos zahed@ruc.dk> // SPDX-License-Identifier: GPL-3.0-or-later package dk.biks.bachelorizer; import java.util.List; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; + /// Bachelorizer - Controller -public class Controller { +class Controller { + + /// Storage model + private Database store; - /// Application model - // (declared explicitly only to silence javadoc) - private GUI model; + /// UI model + private UI ui; /// Application view private Window view; @@ -18,75 +27,148 @@ public class Controller { /// Parameters passed on command-line and in JNLP file private List<String> parameters; - /// Default constructor + /// clear the participation database at program launch + void initialize() { + store.initialize(); + } + + /// default constructor /// - /// @param model Application model + /// @param store Storage model + /// @param ui UI model /// @param view Application view - public Controller(final GUI model, final Window view) { - this.model = model; + Controller( + final Database store, final UI ui, final Window view + ) { + this.store = store; + this.ui = ui; this.view = view; } /// parse application parameters /// - /// parse parameters as GNU-style options and arguments, + /// Parse parameters as GNU-style options and arguments, /// i.e. treat dash-prefixed words as options /// until an optional first bare "--", /// taking first non-option argument as name of student - /// and remaining ones as activity selections + /// and remaining ones as activity selections. /// /// @param parameters Application parameters public final void setParameters(final List<String> parameters) { boolean optionsDone = false; boolean studentAssigned = false; +/* TODO for (String item: parameters) { if (!optionsDone && item.matches("--")) { optionsDone = true; } else if (!item.startsWith("-")) { if (!studentAssigned) { - model.addStudent(item); + store.addStudent(item); studentAssigned = true; - showStudent(); +// TODO ui.showStudent( +// model.getStudentName()); } else { - model.addActivity(item); - showActivities(); + store.addParticipation( + store.getActivityIndeks(item)); +// TODO ui.showActivities(); } } } +*/ } - /// Enter activity + /// callback when category has been selected /// - /// @param s String entered - public final void enterActivity(final String s) { - model.addActivity(s); - view.clearActivityEntry(); - showActivities(); + /// @param section section the category is tied to + /// @param category selected category + // TODO: require a UI instead + void onCategorySelected( + final GUI.Section section, + final String category + ) { + view.clearSelections(section); + view.setOptions(section, store.selectProgram(category)); } - /// Display student - public final void showStudent() { - view.setStudentName(model.getStudentName()); + /// callback when activity has been selected + /// + /// @param combo involved activity box + /// @param select selected item + /// @param area whole text area + void onActivitySelected( + final ComboBox<String> combo, + final ComboBox<String> select, + final TextArea area + ) { + + // pass the value chosen in the box + addActivity(select.getValue(), area); + + // update text area based on category choice + // + // Users can choose from the ComboBox, + // and string (activity) and the area will then update. + updateArea(combo, area); } - /// Display list of activity entries - public final void showActivities() { - String toarea = ""; - for (String t: model.getActivities()) { - toarea += t + "\n"; + /// callback when subject module has been selected + /// + /// @param subject1 involved 1st column subject module box + /// @param subject2 involved 2nd column subject module box + void onSubjectModuleSelected( + final ComboBox<String> subject1, + final ComboBox<String> subject2 + ) { + + // remove chosen option from opposite subject module box + for (String sub: store.getAllSubjects()) { + if (sub.equals(subject1.getValue())) { + subject2.getItems().remove( + subject1.getValue()); + } else if ( + !sub.equals(subject1.getValue()) + && !subject2.getItems().contains(sub) + ) { + subject2.getItems().add(sub); + } + } + } + + /// add participation to database + /// @param s activity identifier + /// @param textArea whole text area + void addActivity(final String s, final TextArea textArea) { + store.addParticipation(store.getActivityIndeks(s)); + } + + /// update text area for an activity box + /// + /// Clears the text area + /// and adds all activity names from activities in participation. + /// + /// @param combo involved activity box + /// @param textArea whole text area + void updateArea( + final ComboBox<String> combo, final TextArea textArea + ) { + textArea.clear(); + for (String s: store.getParticipation(combo.getValue()) + ) { + textArea.appendText(s + "\n"); } - view.setArea(toarea); } - /// drop last activity entry - public final void delOne() { - model.delOneActivity(); - showActivities(); + /// update label with current ECTS of program type + /// @param ectslabel text display area for ECTS points + /// @param comboBox involved activity box + void updateEcts( + final Label ectslabel, final ComboBox<String> comboBox + ) { + ectslabel.setText("ECTS: " + + store.getSumEcts(comboBox.getValue())); } - /// drop all activity entries - public final void delAll() { - model.delAllActivities(); - showActivities(); + void fillElective(final ComboBox<String> electiveBox) { + electiveBox.getItems().addAll(store.getAllActivities()); } } diff --git a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Database.java b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Database.java new file mode 100644 index 0000000..f746649 --- /dev/null +++ b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Database.java @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2025 <Alexander Marthin Klemensen stud-marthin@ruc.dk> +// SPDX-FileCopyrightText: 2025 <Ian Valentin Christensen stud-ianc@ruc.dk> +// SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk> +// SPDX-FileCopyrightText: 2025 <Zahed Noos zahed@ruc.dk> +// SPDX-License-Identifier: GPL-3.0-or-later + +package dk.biks.bachelorizer; + +import java.util.ArrayList; +import java.util.List; + +import com.example.portfolio2.MyDB; + +/// Bachelorizer - database model +/// +/// This model handles all interaction with the database. +class Database { + + /// database singleton + private MyDB db = new MyDB(); + + /// default constructor + // (declared explicitly only to silence javadoc) + Database() { } + + /// student object + // TODO: replace this dummy placeholder with database query + private Person student; + + /// clear the participation database at program launch + void initialize() { + clearParticipation(); + } + + /// Add student + /// + /// @param name Name of student + // TODO: replace this dummy placeholder with database query + public final void addStudent(final String name) { + student = new Person(name); + } + + /// Get student name + /// + /// @return name of student + // TODO: replace this dummy placeholder with database query + public final String getStudentName() { + return student.name; + } + + /// resolve activity index from name + /// + /// @param name activity name + /// @return index of activity as integer + int getActivityIndeks(final String name) { + if (name == null) { + return -1; + } + ArrayList<String> result = db.query( + "SELECT indeks FROM activity" + + " WHERE name IS '" + name + "';", + "indeks"); + + return Integer.parseInt(result.getFirst()); + } + + /// insert activity into participation + /// + /// @param activityIndex index of activity + void addParticipation(final int activityIndex) { + db.cmd("INSERT INTO participation VALUES(123, " + + activityIndex + ");"); + } + + /// list currently participating activities + /// + /// @param program programme name + /// @return names of participating activities + ArrayList<String> getParticipation(final String program) { + return db.query( + "SELECT name FROM participation p" + + " INNER JOIN activity a ON p.indeks = a.indeks" + + " WHERE program IS '" + program + "';", + "name"); + } + + /// purge participation database + void clearParticipation() { + db.cmd("DELETE FROM participation"); + } + + /// list activities within a program + /// + /// @param program programme name + /// @return names of contained activities + ArrayList<String> selectProgram(final String program) { + return db.query( + "SELECT name FROM activity" + + " WHERE program IS '" + program + "';", + "name"); + } + + /// sum of ECTS points under the given category + /// + /// @param program programme name + /// @return ECTS points as String + String getSumEcts(final String program) { + if (program == null) { + return "0"; + } + ArrayList<String> result = db.query( + "SELECT SUM(activity.ects)" + + " AS total_ects,student.name" + + " FROM student LEFT OUTER JOIN participation" + + " ON student.studid = participation.studid" + + " INNER JOIN activity" + + " ON participation.indeks = activity.indeks" + + " WHERE program IS '" + program + "'" + + " GROUP BY student.studid ;", + "total_ects"); + if (result.isEmpty()) { + return "0"; + } + + return result.getFirst(); + } + + /// list of available subject modules + /// + /// @return names of all subject modules as list of strings + List<String> getAllSubjects() { + return db.query( + "SELECT DISTINCT program FROM activity" + + " WHERE program NOT IN (" + + " SELECT program from activity" + + " WHERE name LIKE 'BP1 %')", + "program"); + } + + /// list of available activities + /// + /// @return names of all activities as list of strings + ArrayList<String> getAllActivities() { + return db.query("SELECT name FROM activity;", "name"); + } +} diff --git a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/GUI.java b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/GUI.java index f3a399c..66c2565 100644 --- a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/GUI.java +++ b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/GUI.java @@ -3,58 +3,46 @@ package dk.biks.bachelorizer; -import java.util.ArrayList; - -/// Bachelorizer - GUI model -public class GUI { +/// Bachelorizer - graphical user interface model +public class GUI extends UI { /// Default constructor // (declared explicitly only to silence javadoc) public GUI() { } - /// Activity list - private Person student; + /// structural sections of user interface + public enum Section { - /// Activity list - private ArrayList<String> list = new ArrayList<>(); + /// main programme + PROGRAM("Program", 0), - /// Add student - /// - /// @param name Name of student - public final void addStudent(final String name) { - student = new Person(name); - } + /// first subject module + SUBJECT1("Subject 1", 1), - /// Get student name - /// - /// @return name of student - public final String getStudentName() { - return student.name; - } + /// second subject module + SUBJECT2("Subject 2", 2), - /// Add activity to list - /// - /// @param s Activity to add - public final void addActivity(final String s) { - list.add(s); - } + /// elective courses + ELECTIVE("Elective", 3); - /// Get list of activities - /// - /// @return activity list - public final ArrayList<String> getActivities() { - return list; - } + /// text label + final String label; + + /// column position + final int column; - /// Delete last activity from list - public final void delOneActivity() { - if (list.size() > 0) { - list.remove(list.size() - 1); + /// instantiation + /// + /// @param label text label + /// @param column column position + Section(final String label, final int column) { + this.label = label; + this.column = column; } } - /// Delete all activities from list - public final void delAllActivities() { - list.clear(); +/* public static UI.Section asUISection () { + return UI.Section.valueOf(this); } +*/ } diff --git a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/UI.java b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/UI.java new file mode 100644 index 0000000..be092a5 --- /dev/null +++ b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/UI.java @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk> +// SPDX-License-Identifier: GPL-3.0-or-later + +package dk.biks.bachelorizer; + +/// Bachelorizer - reference user interface model +public abstract class UI { + + /// Default constructor + // (declared explicitly only to silence javadoc) + public UI() { } + + /// structural sections of user interface + public enum Section { + + /// main programme + PROGRAM("Program"), + + /// first subject module + SUBJECT1("Subject 1"), + + /// second subject module + SUBJECT2("Subject 2"), + + /// elective courses + ELECTIVE("Elective"); + + /// text label + final String label; + + /// instantiation + /// + /// @param label text label + Section(final String label) { + this.label = label; + } + } +} diff --git a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Window.java b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Window.java index c129aad..6c21db5 100644 --- a/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Window.java +++ b/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Window.java @@ -1,18 +1,23 @@ +// SPDX-FileCopyrightText: 2025 <Alexander Marthin Klemensen stud-marthin@ruc.dk> +// SPDX-FileCopyrightText: 2025 <Ian Valentin Christensen stud-ianc@ruc.dk> // SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk> +// SPDX-FileCopyrightText: 2025 <Zahed Noos zahed@ruc.dk> // SPDX-License-Identifier: GPL-3.0-or-later package dk.biks.bachelorizer; import javafx.application.Application; -import javafx.scene.control.Button; +import javafx.scene.Scene; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.TextArea; -import javafx.scene.control.TextField; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafx.scene.Scene; import javafx.stage.Stage; +import java.io.IOException; +import java.util.List; + /// Bachelorizer - JavaFX Window view // Class is final to forbid subclassing, // because object is passed to controller during instantiation @@ -20,42 +25,39 @@ public final class Window extends Application { /// Default constructor // (declared explicitly only to silence javadoc) - public Window() { - } + public Window() { } /// window width - private static final int WINDOW_WIDTH = 500; + private static final int WINDOW_WIDTH = 1000; /// window height private static final int WINDOW_HEIGHT = 500; + /// box width + private static final int LIST_WIDTH = 250; + /// box height - private static final int BOX_HEIGHT = 10; + private static final int LIST_HEIGHT = 35; /// Label styling private static final String LABEL_STYLE = - "-fx-font-weight: bold; -fx-font-size: 20;"; - - /// Application model - private GUI model = new GUI(); - - /// Application controller - private Controller control = new Controller(model, this); - - /// Name of student - private TextField nameEntry = new TextField(); + "-fx-font-weight: bold;" + + "-fx-font-size: 18;" + + "-fx-padding: 10"; - /// Text entry for adding an activity - private TextField activityEntry = new TextField(); + /// Storage model + private Database store = new Database(); - /// Text area for activity entries - private TextArea area = new TextArea(); + /// UI model + /// + /// Must be subclass GUI to cover columns. + private GUI ui = new GUI(); - /// Button to delete one activity - private Button delOne = new Button("Delete one"); + /// Application controller + private Controller control = new Controller(store, ui, this); - /// Button to delete all activities - private Button delAll = new Button("Delete all"); + /// column state data as List of ActivityColumn objects + private List<ActivityColumn> columns; /// Application instantiation /// @@ -65,62 +67,182 @@ public final class Window extends Application { } @Override - public void start(final Stage stage) { + public void start(final Stage stage) throws IOException { // pass application parameters to controller control.setParameters(getParameters().getRaw()); - // add listeners -// NameEntry.setOnAction(e -> control.enterName( -// activityEntry.getText())); - activityEntry.setOnAction(e -> control.enterActivity( - activityEntry.getText())); - delOne.setOnAction(e -> control.delOne()); - delAll.setOnAction(e -> control.delAll()); - - // add buttons - VBox root = new VBox(BOX_HEIGHT, - ourHBox("Student", nameEntry), - ourHBox("Add activity", activityEntry), - new HBox(BOX_HEIGHT, delOne, delAll), - area); - - // compose stage + // clear old insertions into participation table + control.initialize(); + + // define list of columns based on their names + columns = List.of( + new ActivityColumn(GUI.Section.PROGRAM), + new ActivityColumn(GUI.Section.SUBJECT1), + new ActivityColumn(GUI.Section.SUBJECT2), + new ActivityColumn(GUI.Section.ELECTIVE) + ); + + // define button functionality for each activity column + for (ActivityColumn col : columns) { + col.nameLabel.setStyle(LABEL_STYLE); + col.ectsLabel.setStyle(LABEL_STYLE); + col.categoryCombo.setPrefSize( + LIST_WIDTH, LIST_HEIGHT); + col.activitySelect.setPrefSize( + LIST_WIDTH, LIST_HEIGHT); + col.area.setPrefWidth(LIST_WIDTH); + + // all boxes share same activity logic + col.activitySelect.setOnAction(event -> { + control.onActivitySelected( + col.categoryCombo, + col.activitySelect, + col.area); + control.updateEcts( + col.ectsLabel, + col.categoryCombo); + }); + + // handle each category box + switch (col.section) { + case GUI.Section.PROGRAM -> { + col.categoryCombo.getItems().addAll( + "HumTek", "NatBach"); + col.categoryCombo.setOnAction(event -> { + control.onCategorySelected( + col.section, + col.categoryCombo.getValue()); + }); + } + // TODO: use the list for filling the box + case GUI.Section.SUBJECT1 -> { + col.categoryCombo.getItems().addAll( + "Computer Science", + "Informatik", + "Astrology"); + col.categoryCombo.setOnAction(event -> { + control.onSubjectModuleSelected( + col.categoryCombo, + columns.get( + GUI.Section.SUBJECT2.column) + .categoryCombo); + control.onCategorySelected( + col.section, + col.categoryCombo.getValue()); + }); + } + case GUI.Section.SUBJECT2 -> { + col.categoryCombo.getItems().addAll( + "Computer Science", + "Informatik", + "Astrology"); + // TODO: figure out a better way... + col.categoryCombo.setOnAction(event -> { + control.onSubjectModuleSelected( + col.categoryCombo, + columns.get( + GUI.Section.SUBJECT1.column) + .categoryCombo); + control.onCategorySelected( + col.section, + col.categoryCombo.getValue()); + }); + } + case GUI.Section.ELECTIVE -> { + + // hide useless box + col.categoryCombo.setVisible(false); + + control.fillElective(col.activitySelect); + } + } + } + + // define HBox and scene for columns + HBox root = new HBox( + columns.get(GUI.Section.PROGRAM.column) + .asVBox(), + columns.get(GUI.Section.SUBJECT1.column) + .asVBox(), + columns.get(GUI.Section.SUBJECT2.column) + .asVBox(), + columns.get(GUI.Section.ELECTIVE.column) + .asVBox()); Scene scene = new Scene( root, WINDOW_WIDTH, WINDOW_HEIGHT); - stage.setTitle("JavaFX Demo"); + stage.setTitle("Bachelorizer - RUC Course Selector"); stage.setScene(scene); stage.show(); } - /// action to apply student name + /// column of activities /// - /// @param s Text to apply - public void setStudentName(final String s) { - nameEntry.setText(s); + /// @param section structural section for column + /// @param nameLabel display text + /// @param categoryCombo dropdown list for categories + /// @param activitySelect dropdown list for activities + /// @param area description of chosen activities + /// @param ectsLabel text to display ECTS points + private record ActivityColumn( + GUI.Section section, + Label nameLabel, + ComboBox<String> categoryCombo, + ComboBox<String> activitySelect, + TextArea area, + Label ectsLabel + ) { + + /// column of activities + /// + /// @param section structural section for column + ActivityColumn(final GUI.Section section) { + this( + section, + new Label(section.label), + new ComboBox<>(), + new ComboBox<>(), + new TextArea(), + new Label() + ); + } + + /// activity columne as VBox + /// + /// @return column of activities as VBox + VBox asVBox() { + return new VBox( + nameLabel, + categoryCombo, + activitySelect, + area, + ectsLabel); + } } - /// action to apply text to area + /// populate activities for a category /// - /// @param s Text to apply - public void setArea(final String s) { - area.setText(s); + /// @param section structural section to operate on + /// @param activities activities to apply as string + public void setOptions( + final GUI.Section section, final List<String> activities + ) { + + // clear the activity selection box + columns.get(section.column).activitySelect + .getItems().clear(); + + // fill activity box from data in store + columns.get(section.column).activitySelect + .getItems().addAll(activities); } - /// Button action to clear field - public void clearActivityEntry() { - activityEntry.setText(""); - } - - /// Styled HBox with label and TextField + /// remove selections from a category /// - /// @param s Label string - /// @param f Text field - /// @return HBox containing styled label and text field - public HBox ourHBox(final String s, final TextField f) { - Label label = new Label(s + ":"); - label.setStyle(LABEL_STYLE); - - return new HBox(BOX_HEIGHT, label, f); + /// @param section structural section to operate on + public void clearSelections(final GUI.Section section) { + + // clear text area + columns.get(section.column).area.clear(); } } diff --git a/src/dk.biks.bachelorizer/module-info.java b/src/dk.biks.bachelorizer/module-info.java index 1258a43..1f68a71 100644 --- a/src/dk.biks.bachelorizer/module-info.java +++ b/src/dk.biks.bachelorizer/module-info.java @@ -22,8 +22,10 @@ module dk.biks.bachelorizer { requires javafx.base; requires transitive javafx.controls; requires javafx.graphics; + requires java.sql; // re-export, as types are used in public API + requires transitive com.example.portfolio2; requires transitive com.example.portfolio3; exports dk.biks.bachelorizer; |