From db57e539760b4f9d3fba2f7027abe9dbd017c45d Mon Sep 17 00:00:00 2001
From: Jonas Smedegaard <dr@jones.dk>
Date: Mon, 31 Mar 2025 17:05:42 +0200
Subject: expand to use multi-framework MVC pattern

---
 Makefile                                           |  10 +-
 .../bachelorizer/Control.java"                     |  94 ++++++++++++++++
 .../bachelorizer/Main.java"                        |  75 ++++++++-----
 .../bachelorizer/model/GUI.java"                   |  62 +++++++++++
 .../bachelorizer/model/Person.java"                |  18 ++++
 .../bachelorizer/view/Window.java"                 | 120 +++++++++++++++++++++
 6 files changed, 352 insertions(+), 27 deletions(-)
 create mode 100644 "dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Control.java"
 create mode 100644 "dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/GUI.java"
 create mode 100644 "dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/Person.java"
 create mode 100644 "dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/view/Window.java"

diff --git a/Makefile b/Makefile
index 402cd21..f8f2ecf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,17 @@
 PROJECT = dk/abcdefghijklmnopqrstuvxyzæøå/bachelorizer/Main
-CLASSPATHS = .
+MODULEPATHS = /usr/share/openjfx/lib
+JAVAMODULES = $(addprefix javafx.,base controls graphics)
 DOCUMENTS = delivery1
 
 include _make/*.mk
 
+# silence security restriction warning
+JAVAFLAGS += --enable-native-access=javafx.graphics
+
+# silence warnig specific to JDK 23-24
+# @see <https://stackoverflow.com/a/79526038/18619283>
+JAVAFLAGS += --sun-misc-unsafe-memory-access=allow
+
 all:: render
 
 render:
diff --git "a/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Control.java" "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Control.java"
new file mode 100644
index 0000000..09019e4
--- /dev/null
+++ "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Control.java"
@@ -0,0 +1,94 @@
+// SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer;
+
+import java.util.List;
+
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.model.GUI;
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view.Window;
+
+/// Bachelorizer - Controller
+public class Control{
+
+	/// Application model
+	// (declared explicitly only to silence javadoc)
+	private GUI model;
+
+	/// Application view
+	private Window view;
+
+	/// Parameters passed on command-line and in JNLP file
+	private List<String> parameters;
+
+	/// Default constructor
+	///
+	/// @param model  Application model
+	/// @param view   Application view
+	public Control(GUI model, Window view){
+		this.model = model;
+		this.view = view;
+	}
+
+	/// parse application parameters
+	///
+	/// 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
+	///
+	/// @param parameters  Application parameters
+	public void setParameters(List<String> parameters) {
+		boolean optionsDone = false;
+		boolean studentAssigned = false;
+		for (String item : parameters) {
+			if (!optionsDone && item.matches("--")) {
+				optionsDone = true;
+			} else if (!item.startsWith("-")) {
+				if (!studentAssigned) {
+					model.addStudent(item);
+					studentAssigned = true;
+					showStudent();
+				} else {
+					model.addActivity(item);
+					showActivities();
+				}
+			}
+		}
+	}
+
+	/// Enter activity
+	///
+	/// @param s  String entered
+	public void enterActivity(String s){
+		model.addActivity(s);
+		view.clearActivityEntry();
+		showActivities();
+	}
+
+	/// Display student
+	public void showStudent() {
+		view.setStudentName(model.getStudentName());
+	}
+
+	/// Display list of activity entries
+	public void showActivities() {
+		String toarea = "";
+		for (String t : model.getActivities())
+			toarea += t + "\n";
+		view.setArea(toarea);
+	}
+
+	/// drop last activity entry
+	public void delOne(){
+		model.delOneActivity();
+		showActivities();
+	}
+
+	/// drop all activity entries
+	public void delAll(){
+		model.delAllActivities();
+		showActivities();
+	}
+}
diff --git "a/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Main.java" "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Main.java"
index 633f5f7..d5da2b7 100644
--- "a/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Main.java"
+++ "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/Main.java"
@@ -3,39 +3,48 @@
 
 package dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer;
 
+import java.lang.UnsupportedOperationException;
 import java.util.Arrays;
 
+/* TODO
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view.Oneshot;
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view.Prompt;
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view.Pipe;
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view.Screen;
+*/
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view.Window;
+
 /// Bachelorizer - bachelor programme registrar
 ///
 /// Tool for registering students
 /// for activities in their bachelor programme.
 ///
-/// Core class usable in several ways
-/// * as self-contained executable via method main()
-/// * embedded in a larger system by instantiating Bachelorizer()
+/// Runner class spawning an interactive or non-interactive application
+/// based on passed arguments
+///
+/// Multi-framework MVC structure inspired by project Криптоанализатор
+/// written by Александр Хмелев <akhmelev@gmail.com>.
 ///
 /// * v0.0.1-draft
 ///   * initial release, as part of delivery "Portfolio 1"
 ///
 /// @version 0.0.1-draft
 /// @see <https://moodle.ruc.dk/mod/assign/view.php?id=523186>
+/// @see <https://github.com/demologin/CryptoAnalyzerLed>
 public class Main {
 
-	/// Student name
-	public String name;
-
-	/// Student activity list
-	public String[] activities;
-
-	/// Main constructor
+	/// Default constructor
 	///
 	/// @param args  command-line arguments or default demo data
 	public Main(final String[] args) {
-		if (args.length > 0 && args[0] != null) {
-			this.name = args[0];
-			if (args.length > 1 && args[1] != null) {
-				this.activities = Arrays.copyOfRange(
-					args, 1, args.length);
+
+		switch (uiFromArgs(args)) {
+			case "gui" -> { Window.main(args); }
+// TODO			case "tui" -> { Screen.main(args); }
+// TODO			case "cli" -> { Line.main(args); }
+			default -> {
+				throw new UnsupportedOperationException(
+					"Not yet implemented.");
 			}
 		}
 	}
@@ -45,23 +54,37 @@ public class Main {
 	/// @param args  command-line arguments
 	public static void main(String[] args) {
 
-		if (args.length == 0)
+		// inject initial sample data unless passed as arguments
+		if ((args.length == 0)
+			|| (!Arrays.stream(args).anyMatch(
+				s -> s != null && !s.startsWith("-")))
+		) {
 			args = new String[] {
 				"Jonas Smedegaard",
 				"CS-SMC2",
 				"CS-SMC3",
 			};
+		}
 
-		Main session = new Main(args);
+		new Main(args);
+	}
+
+	/// minimal argument parser to detect explicit UI choice
+	///
+	/// @param args  command-line arguments
+	/// @return      choice of UI as String
+	public static String uiFromArgs(String[] args) {
+		// TODO: make "cli" the default when implemented
+		String defaultUI = "gui";
+
+		for (String arg : args) {
+			if (arg.matches("--(gui|tui|cli)")) {
+				return (arg.length() == 2)
+					? defaultUI
+					: arg.substring(2);
+			}
+		}
 
-		// minimal viable product
-		System.out.printf("Hi %s%n",
-			(session.name != null)
-				? session.name
-				: "stranger");
-		System.out.printf("You chose these activities: %s%n",
-			(session.activities != null)
-				? Arrays.toString(session.activities)
-				: "[nothing]");
+		return defaultUI;
 	}
 }
diff --git "a/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/GUI.java" "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/GUI.java"
new file mode 100644
index 0000000..8abfec9
--- /dev/null
+++ "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/GUI.java"
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.model;
+
+import java.util.ArrayList;
+
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.model.Person;
+
+/// Bachelorizer - GUI model
+public class GUI{
+
+	/// Default constructor
+	// (declared explicitly only to silence javadoc)
+	public GUI(){
+	}
+
+	/// Activity list
+	private Person student;
+
+	/// Activity list
+	private ArrayList<String> list = new ArrayList<>();
+
+	/// Add student
+	///
+	/// @param name  Name of student
+	public void addStudent(String name){
+		student = new Person(name);
+	}
+
+	/// Get student name
+	///
+	/// @return  name of student
+	public String getStudentName(){
+		return student.name;
+	}
+
+	/// Add activity to list
+	///
+	/// @param s  Activity to add
+	public void addActivity(String s){
+		list.add(s);
+	}
+
+	/// Get list of activities
+	///
+	/// @return  activity list
+	public ArrayList<String> getActivities(){
+		return list;
+	}
+
+	/// Delete last activity from list
+	public void delOneActivity(){
+		if(list.size()>0)
+			list.remove(list.size()-1);
+	}
+
+	/// Delete all activities from list
+	public void delAllActivities(){
+		list.clear();
+	}
+}
diff --git "a/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/Person.java" "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/Person.java"
new file mode 100644
index 0000000..294af3f
--- /dev/null
+++ "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/model/Person.java"
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.model;
+
+/// Bachelorizer - Person model
+public class Person {
+
+	/// Person name
+	public String name;
+
+	/// Constructor
+	///
+	/// @param name  Name of person
+	public Person (String name) {
+		this.name = name;
+	}
+}
diff --git "a/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/view/Window.java" "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/view/Window.java"
new file mode 100644
index 0000000..bf2af3d
--- /dev/null
+++ "b/dk/abcdefghijklmnopqrstuvxyz\303\246\303\270\303\245/bachelorizer/view/Window.java"
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.view;
+
+import java.util.List;
+import javafx.application.Application;
+import javafx.scene.control.Button;
+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 dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.Control;
+import dk.abcdefghijklmnopqrstuvxyzæøå.bachelorizer.model.GUI;
+
+/// Bachelorizer - JavaFX Window view
+// Class is final to forbid subclassing,
+// because object is passed to controller during instatiation
+public final class Window extends Application { // the View
+
+	/// Default constructor
+	// (declared explicitly only to silence javadoc)
+	public Window() {
+	}
+
+	/// Label styling
+	public static String LABEL_STYLE =
+		"-fx-font-weight: bold; -fx-font-size: 20;";
+
+	/// Application model
+	private GUI model = new GUI();
+
+	/// Application controller
+	private Control control = new Control(model, this);
+
+	/// Name of student
+	private TextField nameEntry = new TextField();
+
+	/// Text entry for adding an activity
+	private TextField activityEntry = new TextField();
+
+	/// Text area for activity entries
+	private TextArea area = new TextArea();
+
+	/// Button to delete one activity
+	private Button delOne = new Button("Delete one");
+
+	/// Button to delete all activities
+	private Button delAll = new Button("Delete all");
+
+	/// Application instantiation
+	///
+	/// @param args  application parameters
+	public static void main(String[] args) {
+		launch(args);
+	}
+
+	@Override
+	public void start(Stage stage) {
+
+		// 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(10,
+			ourHBox("Student", nameEntry),
+			ourHBox("Add activity", activityEntry),
+			new HBox(10, delOne, delAll),
+			area);
+
+		// compose stage
+		Scene scene = new Scene(root, 500, 500);
+		stage.setTitle("JavaFX Demo");
+		stage.setScene(scene);
+		stage.show();
+	}
+
+	/// action to apply student name
+	///
+	/// @param s  Text to apply
+	public void setStudentName(String s) {
+		nameEntry.setText(s);
+	}
+
+	/// action to apply text to area
+	///
+	/// @param s  Text to apply
+	public void setArea(String s) {
+		area.setText(s);
+	}
+
+	/// Button action to clear field
+	public void clearActivityEntry() {
+		activityEntry.setText("");
+	}
+
+	/// Styled HBox with label and TextField
+	///
+	/// @param s  Label string
+	/// @param f  Text field
+	/// @return   HBox containing styled label and text field
+	public HBox ourHBox(String s, TextField f) {
+		Label label = new Label(s+":");
+		label.setStyle(LABEL_STYLE);
+
+		return new HBox(10, label, f);
+	}
+}
-- 
cgit v1.2.3