diff --git a/.gitignore b/.gitignore index f81423a..5983686 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,4 @@ target/ out/ # JDT-specific (Eclipse Java Development Tools) -# .classpath - -# Include directories for initial commit -src/main/resources/* -!src/main/resources/.gitkeep - -src/test/java/* -!src/test/java/.gitkeep +# .classpath \ No newline at end of file diff --git a/oppgavetekster/oving0/README.md b/oppgavetekster/oving0/README.md new file mode 100644 index 0000000..e96af31 --- /dev/null +++ b/oppgavetekster/oving0/README.md @@ -0,0 +1,66 @@ +# Øving 0: Oppsett av Java + +Denne øvingen er valgfri og ikke en del av det tellende øvingsopplegget. Øvingen er ment som en introduksjon til Java og hvordan du kan komme i gang med å programmere i Java. Du skal installere Java Development Kit (JDK) og kjøre enkle Java-programmer. Du skal også lære grunnleggende forskjell på Java og Python, og oversette Python-kode til Java. + +## Øvingsmål + +- Intallere Java Development kit +- Installere og sette opp VS Code og Git +- Lære grunnleggende forskjell på Java og Python, og oversette Python-kode til Java +- Kjøre og kompilere Java-kode +- Kjøre enhetstester for å sjekke at koden fungerer som den skal + +### Del 1: Installere JDK og VS Code + +For å kunne programmere i Java må du installere Java Development Kit (JDK). Vi kommer til å bruke VS Code som koderedigeringsverktøy i TDT4100. + +- Gå inn på [denne siden](https://www.ntnu.no/wiki/x/Fgb6DQ) og følg instruksjonene for å installere JDK og VS Code. Husk å følge instruksjonene for ditt operativsystem. **På grunn av trøbbel med tilgang til å redigere wiki er denne litt utdatert. Den anbefaler Java 21, men vi kommer til å bruke Java 23. Dere finner den [her](https://adoptium.net/temurin/releases/?version=23&os=any).** + +### Del 2: Sette opp øvingsprosjektet + +For å kunne gjøre øvingene i TDT4100 må du sette opp et prosjekt i VS Code. Dette gjør du ved å følge instruksjonene på [denne siden](https://www.ntnu.no/wiki/x/Ggb6DQ). Husk å følge instruksjonene for ditt operativsystem. **På grunn av samme feil som nevnt i forrige del vil også denne siden være litt utdatert. Dere vil ikke klone fra "", men fra [https://git.ntnu.no/tdt4100/tdt4100-ovinger-25](https://git.ntnu.no/tdt4100/tdt4100-ovinger-25).** + +### Del 3: Hello world + +For å teste at du har installert JDK og VS Code riktig, og satt opp prosjektet riktig, skal du nå kjøre et enkelt program som skriver ut "Hello world!" til konsollen. + +- Åpne øvingsprosjektet i VS Code. Sørg for at mappen `tdt4100-ovinger-25` er den ytteste mappen i VS Code: + ![oppgavetekster/oving0/ovingsprosjekt.png](./img/prosjektmappe.png) + +- Åpne filen [src/main/java/oving0/HelloWorld.java](../../src/main/java/oving0/HelloWorld.java). + Denne filen inneholder et enkelt program som skriver ut "Hello world!" til konsollen. Klikk på "Run" knappen i VS Code for å kjøre programmet. Du skal nå se "Hello world!" i konsollen: + ![oppgavetekster/oving0/helloworld.png](./img/helloWorld.png) + +### Del 4: JavaFX og SceneBuilder + +#### JavaFX + +For å teste at JavaFX er installert riktig, skal du nå kjøre et enkelt program som bruker JavaFX. Åpne filen [src/main/java/oving0/todolist/fxui/TodoApp.java](../../src/main/java/oving0/todolist/fxui/TodoApp.java). Klikk på "Run" knappen i VS Code for å kjøre programmet. Du skal nå se en todolist applikasjon. Hvis du får en feilmelding i VS Code, prøv å trykke ctrl/cmd+shift+p og skriv "Java: Clean the Java language server workspace" og trykk enter. Hvis du fortsatt får feilmelding, prøv å lukke VS Code og åpne det på nytt. + +#### SceneBuilder + +Følg instruksjonene på [denne siden](https://www.ntnu.no/wiki/x/LAMxDg) for å installere SceneBuilder. Scene Builder er et verktøy som kan brukes til å designe brukergrensesnitt for JavaFX-applikasjoner. SceneBuilder er ikke nødvendig for å gjøre øvingene, men det vil være nyttig i prosjektet. + +For å teste at SceneBuilder er installert riktig, skal du nå åpne todolist-applikasjonen i Scene Builder. Åpne SceneBuilder programmet, og klikk "Open Project". Naviger frem til filen [src/main/resources/oving0/todolist/fxui/Todo.fxml](../../src/main/resources/oving0/todolist/fxui/Todo.fxml) og velg den. Du skal nå se todolist-applikasjonen i Scene Builder: + +![oppgavetekster/oving0/scenebuilder.png](./img/scenebuilder.png) + +### Del 5: Kjøre enhetstester + +Enhetstester er en måte å sjekke at koden fungerer som den skal. I øvingene i TDT4100 følger det med enhetstester for hver oppgave. For å sjekke at prosjetet ditt er satt opp riktig, skal du kjøre enhetstester for Hello world-programmet. + +- Åpne filen [/src/test/java/oving0/HelloWorldTest.java](../../src/test/java/oving0/HelloWorldTest.java). Denne filen inneholder enhetstester for Hello world-programmet. For å kjøre alle testene klikker man på den øverste grønne dobbel-pilen i VS Code. Man kan også kjøre en og en test ved å klikke på den grønne enkelt-pilen ved siden av hver test: + ![oppgavetekster/oving0/test.png](./img/kjor_test.png) + +- Etter å ha kjørt testene skal du se at alle testene er grønne, og at det står 2/2 tests passed i vinduet som åpner seg til venstre: + ![oppgavetekster/oving0/test_resultat.png](./img/Passed_tests.png) + +### Del 6: Java vs Python + +I denne delen skal du lære grunnleggende forskjell på Java og Python, og oversette Python-kode til Java. Fortsett i [denne filen](./python_vs_java.md). + +### Del 7: Videre lesing + +Har du kommet til denne delen er du nok klar for å starte på de ordentlige øvingene. Vi anbefaler også at du blir litt kjent med Wiki-sidene, spesielt [Objektorientert programmering](https://www.ntnu.no/wiki/x/wRzuAw), [Java programmering](https://www.ntnu.no/wiki/x/zx3uAw) og [Prosedyreorientert programmering](https://www.ntnu.no/wiki/x/qx3uAw). Det er ikke forventet at dere skal lese alt som står på alle undersidene her, men det er greit å ha en oversikt over hva som er der, så vet dere hvor dere kan finne informasjon om dere trenger det. + +Lykke til med øvingene! diff --git a/oppgavetekster/oving0/img/Passed_tests.png b/oppgavetekster/oving0/img/Passed_tests.png new file mode 100644 index 0000000..fd5826d Binary files /dev/null and b/oppgavetekster/oving0/img/Passed_tests.png differ diff --git a/oppgavetekster/oving0/img/helloWorld.png b/oppgavetekster/oving0/img/helloWorld.png new file mode 100644 index 0000000..e768e4d Binary files /dev/null and b/oppgavetekster/oving0/img/helloWorld.png differ diff --git a/oppgavetekster/oving0/img/kjor_test.png b/oppgavetekster/oving0/img/kjor_test.png new file mode 100644 index 0000000..e607955 Binary files /dev/null and b/oppgavetekster/oving0/img/kjor_test.png differ diff --git a/oppgavetekster/oving0/img/opp1.png b/oppgavetekster/oving0/img/opp1.png new file mode 100644 index 0000000..dcf7bb5 Binary files /dev/null and b/oppgavetekster/oving0/img/opp1.png differ diff --git a/oppgavetekster/oving0/img/prosjektmappe.png b/oppgavetekster/oving0/img/prosjektmappe.png new file mode 100644 index 0000000..1e752b2 Binary files /dev/null and b/oppgavetekster/oving0/img/prosjektmappe.png differ diff --git a/oppgavetekster/oving0/img/scenebuilder.png b/oppgavetekster/oving0/img/scenebuilder.png new file mode 100644 index 0000000..5736938 Binary files /dev/null and b/oppgavetekster/oving0/img/scenebuilder.png differ diff --git a/oppgavetekster/oving0/python_vs_java.md b/oppgavetekster/oving0/python_vs_java.md new file mode 100644 index 0000000..d5abb70 --- /dev/null +++ b/oppgavetekster/oving0/python_vs_java.md @@ -0,0 +1,259 @@ +# Python VS Java + +Noe av det første du kommer til å merke når du skal programmere i Java er at det kreves en del flere linjer kode enn i Python. La oss starte med noe av det enkleste, å skrive ut en tekst til konsollen. + +## Skrive ut tekst til konsollen + +I Python er det veldig greit å skrive ut tekst til konsollen. Du trenger bare å skrive `print("tekst")` og så vil teksten skrives ut til konsollen. I Java trengs det litt bokstaver: + +**Python**: + +```python +print("Hello World!") +``` + +**Java**: + +```java +System.out.println("Hello World!"); +``` + +En snarvei i VS Code for å slippe å skrive hele `System.out.println()` er å skrive `sout` og trykke `tab`-tasten. Da vil hele koden bli skrevet ut for deg. + +## Variabler + +I Python er det ganske rett frem å lage variabler. Du trenger bare å skrive `variabelnavn = verdi`. I Java må du først angi hvilken type variabelen skal være. De vanligste typene er `int`, `double`, `boolean` og `String`. `double` er et desimaltall (samme som `float` i python), `boolean` er en `true` eller `false`-verdi og `String` er en tekststreng. + +La oss se på et eksempel: + +**Python**: + +```python +x = 5 +y = 10.6 +s = "hei" +ja = True +``` + +**Java**: + +```java +int x = 5; +double y = 10.6; +String s = "hei"; +boolean ja = true; +``` + +Noen ting som er viktig å merke seg: + +- I Java må man ha semikolon på slutten av hver linje der man deklarerer variabler (og mange andre steder, men det kommer vi tilbake til senere). + +- Boolske verdier skrives med liten forbokstav i Java (`true`, `false`), mens de skrives med stor forbokstav i Python. + +## Oppgave 1 + +Nå skal du lage et program som ganger sammen to tall og skriver ut resultatet til konsollen. Bruk variabler til å lagre tallene. Bruk `System.out.println()` til å skrive ut resultatet. + +- Lag en ny fil ved å høyreklikke på `src/main/java/oving0`-mappen og velg `New file...`. Gi filen et navn som slutter på `.java`, for eksempel `Oppgave1.java`. Når du trykker Enter vil du få opp den nye filen og noe forhåndsutfylt kode: + ![Ny fil](./img/opp1.png) + Velg `class`. + +- For å kjøre kode i Java må man lage en `main`-metode. Denne ser slik ut: + +```java +public static void main(String[] args) { + // Kode her +} +``` + +- Enten kopier koden over, eller skriv `main` og trykk `tab`-tasten for å auto-fylle koden. + +- Lag to variabler som inneholder tallene du skal gange sammen. Du kan kalle dem `x` og `y` eller noe annet du vil. Velg selv om du vil bruke `int` eller `double`. + +- Lag en variabel som inneholder resultatet av gangeoperasjonen. Du kan kalle den `z` eller noe annet du vil. + +- Skriv ut resultatet til konsollen ved å bruke `System.out.println();`. + +## `if`-setninger + +Nå skal vi se på forskjellen mellom `if`-setninger i Python og Java: + +**Python**: + +```python +if betingelse1 or not betingelse2: + # Kode her +elif betingelse1 and betingelse2: + # Kode her +else: + # Kode her +``` + +**Java**: + +```java +if (betingelse1 || !betingelse2) { + // Kode her +} +else if (betingelse1 && betingelse2) { + // Kode her +} +else { + // Kode her +} +``` + +Viktige forskjeller å merke seg: + +- Python bruker kolon (`:`) etter if-setningen, mens Java bruker krøllparenteser (`{}`). + +- Python bruker `elif` for å si at det er en `else if`-setning, mens Java bruker `else if`. + +- Python bruker innrykk for å si at noe kode skal være inni if-setningen, mens Java bruker krøllparenteser. + +- Python bruker `and` og `or`, mens Java bruker `&&` og `||` for å si at to betingelser skal være sant samtidig eller at en av betingelsene skal være sant. + +- Python bruker `not` for å si at en betingelse skal være falsk, mens Java bruker `!`. + +- Java krever at betingelsene er omgitt av parenteser. + +## Oppgave 2 + +Velg om du vil lage en ny fil eller bruke den du lagde i forrige oppgave. Skriv koden i en `main`-metode. +Oversett koden under fra Python til Java: + +```python +x = 3 +y = 5 + +if x > 5 and y < 10: + print("x er større enn 5 og y er mindre enn 10") +elif x > 5 or y < 10: + print("x er større enn 5 eller y er mindre enn 10") +else: + print("x er mindre enn 5 og y er større enn 10") +``` + +## Løkker + +Nå skal vi se på forskjellen mellom løkker i Python og Java: + +**Python**: + +```python +for i in range(10): + # Kode her + +while betingelse: + # Kode her +``` + +**Java**: + +```java +for (int i = 0; i < 10; i++) { + // Kode her +} + +while (betingelse) { + // Kode her +} +``` + +`while`-løkken er ganske lik i Python og Java. `for`-løkken kan virke litt mer skremmende, men vi skal se på den litt nærmere. + +### For-løkker + +En `for`-løkke i Java består av tre deler, og ligner egentlig ganske mye på en `while`-løkke. De tre delene er: + +- En variabel som skal telle oppover eller nedover. Denne kan være av typen `int`, `double` eller `float`. Denne variabelen må være unik for løkken, det vil si at den ikke kan hete det samme som en variabel som allerede er brukt i koden. + +- En betingelse som må være sant for at løkken skal fortsette. + +- En operasjon som skal utføres hver gang løkken kjører. Denne operasjonen kan være å øke eller redusere variabelen som telle oppover eller nedover. + +I eksempelet over er variabelen `i`, betingelsen `i < 10` og operasjonen `i++`. Variabelen, betingelsen og operasjonen er sepparert med semikolon (`;`). `i++` betyr at variabelen `i` skal økes med 1 hver gang løkken kjører. Man kan også øke med for eksempel `3`, og skrive `i += 3`. Startverdien til variabelen kan endres til f.eks. `5` ved å skrive `for (int i = 5; ...`. + +## Oppgave 3 + +Velg om du vil lage en ny fil eller bruke den du lagde i forrige oppgave. Skriv koden i en `main`-metode. + +Oversett koden under fra Python til Java: + +```python +for i in range(3,10): + if i % 2 == 0: + print(i) + +j = 0 +while j < 10: + print(j) + j += 1 +``` + +## Funksjoner + +Nå skal vi se på forskjellen mellom funksjoner i Python og Java: + +**Python**: + +```python +def funksjonsnavn(parameter1, parameter2): + # Kode her + return resultat +``` + +**Java**: + +```java +public int funksjonsnavn(int parameter1, int parameter2) { + // Kode her + return resultat; +} +``` + +De fleste funskjoner man lager i Java hører til en gitt **klasse** (ikke tenk på hva dette er akkurat nå, dette lærer dere snart). Funksjoner som hører til klasser kalles **metoder**. + +En metode i Java har alltid en **return-type**. Return-type er typen variabelen som skal returneres fra metoden. Return-type kan være `void`, `int`, `double`, `float`, `String` eller en annen type. Hvis return-type er `void` betyr det at metoden ikke skal returnere noe. Alle parametre som skal sendes til metoden må ha en **type**. Typen til parametrene kan være `int`, `double`, `float`, `String` eller en annen type. For nå kan du ignorere at det står `public` foran metoden, dette lærer du om snart. + +Alle funskjoner (metoder) i Java må ligge inni en klasse. + +Eksempel på en klasse med en metode: + +```java +package minpakke; + +public class KlasseNavn { + + // Kode her + + public int metode1(int parameter1, int parameter2) { + // Kode her + int resultat = parameter1 + parameter2; + return resultat; + } +} +``` + +## Oppgave 4 + +Enten lag en ny klasse, eller bruk den du brukte i forrige oppgave. Lag metoder som tilsvarer funksjonene i Python-koden under: + +```python +def division(x, y): + return x / y + +def fakultet(x): + fak = 1 + for i in range(1, x+1): + fak *= i + return fak + +def erPrimtall(x): + if x < 2: + return False + for i in range(2, x): + if x % i == 0: + return False + return True +``` diff --git a/src/main/java/oving0/HelloWorld.java b/src/main/java/oving0/HelloWorld.java new file mode 100644 index 0000000..e28be25 --- /dev/null +++ b/src/main/java/oving0/HelloWorld.java @@ -0,0 +1,16 @@ +package oving0; + +public class HelloWorld { + + public String getHelloWorld() { + return "Hello World!"; + } + + public int getHelloWorldLength() { + return this.getHelloWorld().length(); + } + + public static void main(String[] args) { + System.out.println(new HelloWorld().getHelloWorld()); + } +} diff --git a/src/main/java/oving0/todolist/fxui/ITodoFileReading.java b/src/main/java/oving0/todolist/fxui/ITodoFileReading.java new file mode 100644 index 0000000..4b10633 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/ITodoFileReading.java @@ -0,0 +1,43 @@ +package oving0.todolist.fxui; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import oving0.todolist.model.TodoList; + +public interface ITodoFileReading { + + /** + * Read a TodoList from a given InputStream. + * + * @param ios The input stream to read from. + * @return The TodoList from the InputStream. + */ + TodoList readTodoList(InputStream is); + + /** + * Read a TodoList with a given name, from a default (implementation-specific) location. + * + * @param name The name of the TodoList + * @return The TodoList with the given name from the default location + * @throws IOException if the TodoList can't be found. + */ + TodoList readTodoList(String name) throws IOException; + + /** + * Write a TodoList to a given OutputStream + * + * @param todoList The list to write + * @param os The stream to write to + */ + void writeTodoList(TodoList todoList, OutputStream os); + + /** + * Write a TodoList to a file named after the list in a default (implementation specific) + * location. + * + * @param todoList The list to write + * @throws IOException If a file at the proper location can't be written to + */ + void writeTodoList(TodoList todoList) throws IOException; +} diff --git a/src/main/java/oving0/todolist/fxui/TodoApp.java b/src/main/java/oving0/todolist/fxui/TodoApp.java new file mode 100644 index 0000000..6190f58 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/TodoApp.java @@ -0,0 +1,21 @@ +package oving0.todolist.fxui; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class TodoApp extends Application { + + @Override + public void start(Stage stage) throws Exception { + Parent parent = FXMLLoader.load(getClass().getResource("Todo.fxml")); + stage.setScene(new Scene(parent)); + stage.show(); + } + + public static void main(String[] args) { + launch(TodoApp.class, args); + } +} diff --git a/src/main/java/oving0/todolist/fxui/TodoController.java b/src/main/java/oving0/todolist/fxui/TodoController.java new file mode 100644 index 0000000..b7ccbb2 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/TodoController.java @@ -0,0 +1,238 @@ +package oving0.todolist.fxui; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.control.TextField; +import javafx.scene.text.Text; +import javafx.stage.FileChooser; +import javafx.stage.Window; +import oving0.todolist.model.TodoEntry; +import oving0.todolist.model.TodoList; + +public class TodoController { + + private final TodoSettings todoSettings = new TodoSettings(); + + public TodoSettings getTodoSettings() { + return todoSettings; + } + + private TodoList todoList; + + public void setTodoList(final TodoList todoList) { + this.todoList = todoList; + updateTodoListView(); + } + + private final ITodoFileReading fileSupport = new TodoFileSupport(); + + @FXML + private String sampleTodoListResource; + + @FXML + private Text todoListNameView; + + @FXML + private ListView todoListEntriesView; + + @FXML + private TextField todoEntryTextField; + + @FXML + private TextField fileLocationNameField; + + @FXML + private Button todoEntryButton; + + @FXML + private Text statusText; + private String defaultStatusText; + + @SuppressWarnings("unused") + @FXML + public void initialize() { + final var todoList = new TodoList(); + todoList.setName("todo list"); + setTodoList(todoList); + todoListEntriesView.getSelectionModel().selectedItemProperty() + .addListener((prop, oldValue, newValue) -> { + handleTodoListEntriesViewSelectedItemChanged(); + }); + handleTodoListEntriesViewSelectedItemChanged(); + defaultStatusText = statusText.getText(); + } + + private void updateTodoListView() { + todoListNameView.setText(todoList.getName()); + todoListEntriesView.getItems().setAll(getOrderedTodoListItems()); + } + + protected List getOrderedTodoListItems() { + final List listItems = new ArrayList<>(); + for (final var entry : todoList) { + listItems.add(entry.getText()); + } + switch (todoSettings.getTodoListOrder()) { + case ADD_ORDER_REVERSED: { + Collections.reverse(listItems); + break; + } + case LEXICOGRAPHIC_ORDER: { + Collections.sort(listItems); + break; + } + default: + } + return listItems; + } + + private void handleTodoListEntriesViewSelectedItemChanged() { + todoEntryTextField.setText(todoListEntriesView.getSelectionModel().getSelectedItem()); + } + + @FXML + private void handleTodoEntryTextFieldChange(final StringProperty prop, final String oldValue, + final String newValue) { + if (todoList.hasEntry(newValue)) { + todoEntryButton.setText("Remove"); + } else { + todoEntryButton.setText("Add"); + } + todoEntryButton.setDisable(newValue == null || newValue.isBlank()); + } + + private FileChooser fileChooser; + + private String getFileLocationName(final boolean isSave) { + if (fileChooser == null) { + fileChooser = new FileChooser(); + } + final Window window = fileLocationNameField.getScene().getWindow(); + File file = null; + if (isSave) { + file = fileChooser.showSaveDialog(window); + } else { + file = fileChooser.showOpenDialog(window); + } + if (file != null) { + String name = file.getName(); + final int pos = name.lastIndexOf('.'); + if (pos >= 0) { + name = name.substring(0, pos); + } + if (!name.isBlank()) { + return file.getParent() + File.separator + name; + } + } + return null; + } + + @FXML + private void handleBrowseButtonAction() { + final String name = getFileLocationName(false); + if (name != null) { + fileLocationNameField.setText(name); + } + } + + private String ensureFileLocation(final boolean isSave) { + String name = fileLocationNameField.getText(); + if (name.isBlank()) { + name = getFileLocationName(false); + if (name != null) { + fileLocationNameField.setText(name); + } + } + return name; + } + + @FXML + private void handleLoadButtonAction() { + final String name = ensureFileLocation(false); + if (!name.isBlank()) { + try { + setTodoList(fileSupport.readTodoList(name)); + statusText.setText(defaultStatusText); + } catch (final IOException e) { + statusText.setText(e.getMessage()); + } + } + } + + @FXML + private void handleSaveButtonAction() { + final String name = ensureFileLocation(true); + if (!name.isBlank()) { + try { + todoList.setName(name); + fileSupport.writeTodoList(todoList); + todoListNameView.setText(todoList.getName()); + statusText.setText(defaultStatusText); + } catch (final IOException e) { + statusText.setText(e.getMessage()); + } + } + } + + @FXML + private void handleTodoEntryButtonAction() { + final var text = todoEntryTextField.getText(); + final var entry = todoList.findEntry(text); + if (entry != null) { + todoList.removeEntry(entry); + } else { + todoList.addEntry(new TodoEntry(text)); + } + todoEntryTextField.clear(); + updateTodoListView(); + } + + private TodoSettingsController settingsController; + + private Scene scene; + private Parent oldSceneRoot, settingsSceneRoot; + + @FXML + private void handleSettingsAction() { + // check if we need to load settings ui from fxml + if (settingsController == null) { + final FXMLLoader fxmlLoader = + new FXMLLoader(getClass().getResource("TodoSettings.fxml")); + try { + // load settings ui + settingsSceneRoot = fxmlLoader.load(); + // remember old ui + scene = todoListEntriesView.getScene(); + oldSceneRoot = scene.getRoot(); + // get the settings controller, so we can set its todoSettings property + settingsController = fxmlLoader.getController(); + settingsController.setTodoController(this); + } catch (final IOException e) { + } + } + if (settingsController != null) { + todoListEntriesView.getScene().setRoot(settingsSceneRoot); + settingsController.setTodoSettings(todoSettings); + } + } + + public void applyTodoSettings(final TodoSettings settings) { + if (settings != null) { + settings.copyInto(this.todoSettings); + } + scene.setRoot(oldSceneRoot); + if (settings != null) { + updateTodoListView(); + } + } +} diff --git a/src/main/java/oving0/todolist/fxui/TodoFileSupport.java b/src/main/java/oving0/todolist/fxui/TodoFileSupport.java new file mode 100644 index 0000000..2e1f520 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/TodoFileSupport.java @@ -0,0 +1,80 @@ +package oving0.todolist.fxui; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Scanner; +import oving0.todolist.model.TodoEntry; +import oving0.todolist.model.TodoList; + +public class TodoFileSupport implements ITodoFileReading { + + public static final String TODO_EXTENSION = "todo"; + + private Path getTodoUserFolderPath() { + return Path.of(System.getProperty("user.home"), "tdt4100", "todo"); + } + + private boolean ensureTodoUserFolder() { + try { + Files.createDirectories(getTodoUserFolderPath()); + return true; + } catch (IOException ioe) { + return false; + } + } + + private Path getTodoListPath(String name) { + return getTodoUserFolderPath().resolve(name + "." + TODO_EXTENSION); + } + + public TodoList readTodoList(InputStream input) { + TodoList todoList = null; + try (var scanner = new Scanner(input)) { + todoList = new TodoList(); + while (scanner.hasNextLine()) { + var line = scanner.nextLine().stripTrailing(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + if (todoList.getName() == null) { + todoList.setName(line); + } else { + todoList.addEntry(new TodoEntry(line)); + } + } + } + return todoList; + } + + public TodoList readTodoList(String name) throws IOException { + var todoListPath = getTodoListPath(name); + try (var input = new FileInputStream(todoListPath.toFile())) { + return readTodoList(input); + } + } + + public void writeTodoList(TodoList todoList, OutputStream output) { + try (var writer = new PrintWriter(output)) { + writer.println("# name"); + writer.println(todoList.getName()); + writer.println("# entries"); + for (var entry : todoList) { + writer.println(entry.getText()); + } + } + } + + public void writeTodoList(TodoList todoList) throws IOException { + var todoListPath = getTodoListPath(todoList.getName()); + ensureTodoUserFolder(); + try (var output = new FileOutputStream(todoListPath.toFile())) { + writeTodoList(todoList, output); + } + } +} diff --git a/src/main/java/oving0/todolist/fxui/TodoLauncher.java b/src/main/java/oving0/todolist/fxui/TodoLauncher.java new file mode 100644 index 0000000..e81d1a5 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/TodoLauncher.java @@ -0,0 +1,11 @@ +package oving0.todolist.fxui; + +/** + * Class needed in a shaded über jar to run a JavaFX app + */ +public class TodoLauncher { + + public static void main(String[] args) { + TodoApp.main(args); + } +} diff --git a/src/main/java/oving0/todolist/fxui/TodoSettings.java b/src/main/java/oving0/todolist/fxui/TodoSettings.java new file mode 100644 index 0000000..453c4d6 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/TodoSettings.java @@ -0,0 +1,38 @@ +package oving0.todolist.fxui; + +public class TodoSettings { + + public enum TodoListOrder { + ADD_ORDER, ADD_ORDER_REVERSED, LEXICOGRAPHIC_ORDER; + } + + private TodoListOrder todoListOrder = TodoListOrder.ADD_ORDER; + + public TodoSettings() {} + + /** + * Initialises this TodoSettings from the provided argument + * + * @param target the target TodoSettings + */ + public TodoSettings(final TodoSettings todoSettings) { + todoSettings.copyInto(this); + } + + /** + * Copies all properties in this TodoSettings into target + * + * @param target the target TodoSettings + */ + public void copyInto(final TodoSettings target) { + target.setTodoListOrder(this.getTodoListOrder()); + } + + public TodoListOrder getTodoListOrder() { + return todoListOrder; + } + + public void setTodoListOrder(final TodoListOrder todoListOrder) { + this.todoListOrder = todoListOrder; + } +} diff --git a/src/main/java/oving0/todolist/fxui/TodoSettingsController.java b/src/main/java/oving0/todolist/fxui/TodoSettingsController.java new file mode 100644 index 0000000..e3022e2 --- /dev/null +++ b/src/main/java/oving0/todolist/fxui/TodoSettingsController.java @@ -0,0 +1,50 @@ +package oving0.todolist.fxui; + +import javafx.fxml.FXML; +import javafx.scene.control.ChoiceBox; + +public class TodoSettingsController { + + private TodoController todoController; + + public void setTodoController(final TodoController todoController) { + this.todoController = todoController; + } + + private TodoSettings todoSettings = new TodoSettings(); + + public void setTodoSettings(final TodoSettings todoSettings) { + this.todoSettings = todoSettings; + updateView(); + } + + @FXML + private ChoiceBox listOrderSelector; + + @FXML + private void initialize() { + // same order as TodoSettings.ListOrder + listOrderSelector.getItems().setAll("Add order", "Add order reversed", "Alphabetic"); + updateView(); + } + + private void updateView() { + listOrderSelector.getSelectionModel().select(todoSettings.getTodoListOrder().ordinal()); + } + + @FXML + public void handleApplySettings() { + todoController.applyTodoSettings(todoSettings); + } + + @FXML + public void handleCancelSettings() { + todoController.applyTodoSettings(null); + } + + @FXML + public void handleListOrderSelection() { + this.todoSettings.setTodoListOrder(TodoSettings.TodoListOrder.values()[listOrderSelector + .getSelectionModel().getSelectedIndex()]); + } +} diff --git a/src/main/java/oving0/todolist/model/TodoEntry.java b/src/main/java/oving0/todolist/model/TodoEntry.java new file mode 100644 index 0000000..65a5f2d --- /dev/null +++ b/src/main/java/oving0/todolist/model/TodoEntry.java @@ -0,0 +1,31 @@ +package oving0.todolist.model; + +/** + * Class representing a single entry in a To-Do-list. + * + * The class contains a text-value representing the label of the entry, and has a getText-method to + * retrieve said label. + * + */ +public class TodoEntry { + + private final String text; + + /** + * Create a new entry with a given label. + * + * @param text The label to assign to this entry + */ + public TodoEntry(String text) { + this.text = text; + } + + @Override + public String toString() { + return String.format("[TodoEntry \"%s\"]", getText()); + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/oving0/todolist/model/TodoList.java b/src/main/java/oving0/todolist/model/TodoList.java new file mode 100644 index 0000000..45b07c9 --- /dev/null +++ b/src/main/java/oving0/todolist/model/TodoList.java @@ -0,0 +1,81 @@ +package oving0.todolist.model; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A TodoList, which is an iterable of {@link TodoEntry}s. + * + * The class has a name, along with methods for adding and removing entries, searching through them, + * and checking if one exists in the list. + */ +public class TodoList implements Iterable { + + private String name; + + private List entries = new ArrayList<>(); + + @Override + public String toString() { + return String.format("[TodoList \"%s\" with %s entries]", getName(), entries.size()); + } + + @Override + public Iterator iterator() { + return entries.iterator(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Find an entry given its text label. + * + * @param text The label to look for. + * @return An entry with the given text label if it exists, otherwise null. + */ + public TodoEntry findEntry(String text) { + for (var entry : entries) { + if (entry.getText().equals(text)) { + return entry; + } + } + return null; + } + + /** + * Check if this TodoList has an entry with a given text label. + * + * @param text The text label of the entry + * @return true if an entry with the given text label is in this TodoList, otherwise false + */ + public boolean hasEntry(String text) { + return findEntry(text) != null; + } + + /** + * Add an entry to this TodoList. + * + * @param entry The entry to add. + */ + public void addEntry(TodoEntry entry) { + if (!entries.contains(entry)) { + entries.add(entry); + } + } + + /** + * Remove and entry from this TodoList. + * + * @param entry The entry to remove. + */ + public void removeEntry(TodoEntry entry) { + entries.remove(entry); + } +} diff --git a/src/main/resources/.gitkeep b/src/main/resources/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/oving0/todolist/fxui/Todo.fxml b/src/main/resources/oving0/todolist/fxui/Todo.fxml new file mode 100644 index 0000000..0783fd5 --- /dev/null +++ b/src/main/resources/oving0/todolist/fxui/Todo.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + +