Add oving0

This commit is contained in:
Andreas Omholt Olsen
2026-01-05 13:47:03 +01:00
parent 33086c14b8
commit 977b9310ef
24 changed files with 1061 additions and 8 deletions

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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<String> 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<String> getOrderedTodoListItems() {
final List<String> 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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<String> 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()]);
}
}

View File

@@ -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;
}
}

View File

@@ -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<TodoEntry> {
private String name;
private List<TodoEntry> entries = new ArrayList<>();
@Override
public String toString() {
return String.format("[TodoList \"%s\" with %s entries]", getName(), entries.size());
}
@Override
public Iterator<TodoEntry> 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);
}
}