diff --git a/gresource.xml b/gresource.xml
new file mode 100644
index 0000000..89cf656
--- /dev/null
+++ b/gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+ todo-ui.glade
+
+
\ No newline at end of file
diff --git a/source/app.d b/source/app.d
index c73ff1b..9c9f19b 100644
--- a/source/app.d
+++ b/source/app.d
@@ -1,16 +1,99 @@
-import std.stdio;
-
import model;
-import view : setupUI;
+import std.stdio;
import gtk.MainWindow;
import gtk.Main;
+import gtk.Builder;
+import gtk.Application;
+import gtk.ApplicationWindow;
+import gtk.Entry;
+import gtk.Box;
+import gtk.ListBox;
+import gtk.ListBoxRow;
+import gtk.Label;
+import gtk.CheckButton;
+import gtk.ToggleButton;
+import gtk.Button;
+
+class ToDoItemWidget : Box {
+ this(ToDoItem item, ToDoModel todoModel) {
+ super(GtkOrientation.HORIZONTAL, 5);
+ CheckButton button = new CheckButton();
+ button.setActive(item.checked);
+ button.addOnToggled(delegate(ToggleButton b) {
+ item.checked = b.getActive();
+ });
+ Label label = new Label(item.text);
+ label.setLineWrap(true);
+ label.setHalign(GtkAlign.START);
+ label.setValign(GtkAlign.CENTER);
+ this.packStart(button, false, false, 0);
+
+ Button removeButton = new Button(StockID.REMOVE);
+ removeButton.addOnClicked(delegate(Button b) {
+ todoModel.removeItem(item.priority);
+ });
+ this.packEnd(removeButton, false, false, 0);
+
+ if (todoModel.canIncrement(item)) {
+ Button upButton = new Button(StockID.GO_UP, delegate(Button b) {
+ todoModel.incrementPriority(item);
+ });
+ this.packEnd(upButton, false, false, 0);
+ }
+ if (todoModel.canDecrement(item)) {
+ Button downButton = new Button(StockID.GO_DOWN, delegate(Button b) {
+ todoModel.decrementPriority(item);
+ });
+ this.packEnd(downButton, false, false, 0);
+ }
+
+ this.packEnd(label, true, true, 0);
+ }
+}
+
+Entry taskEntry;
+ListBox taskList;
+
+ToDoModel todoModel;
void main(string[] args) {
Main.init(args);
- MainWindow window = new MainWindow("To-Do");
- window.setDefaultSize(300, 500);
- setupUI(window);
+ Builder builder = new Builder("todo-ui.glade");
+
+ builder.addCallbackSymbol("onAddTask", &addTask);
+ builder.addCallbackSymbol("onWindowDestroy", &onWindowDestroy);
+ builder.addCallbackSymbol("onQuitMenuActivated", &onWindowDestroy);
+ builder.connectSignals(null);
+
+ taskList = cast(ListBox) builder.getObject("taskList");
+ taskEntry = cast(Entry) builder.getObject("addTaskEntry");
+
+ todoModel = new ToDoModel();
+ import std.functional : toDelegate;
+ todoModel.addListener(ModelUpdateListener.of(toDelegate(&itemsUpdated)));
+
+ ApplicationWindow window = cast(ApplicationWindow) builder.getObject("window");
window.showAll();
Main.run();
}
+
+void itemsUpdated(ToDoItem[] items) {
+ taskList.removeAll();
+ foreach (item; items) {
+ auto widget = new ToDoItemWidget(item, todoModel);
+ auto row = new ListBoxRow();
+ row.add(widget);
+ taskList.add(row);
+ }
+ taskList.showAll();
+}
+
+extern (C) void addTask() {
+ todoModel.addItem(taskEntry.getText());
+ taskEntry.setText("");
+}
+
+extern (C) void onWindowDestroy() {
+ Main.quit();
+}
diff --git a/source/model.d b/source/model.d
index ea01d83..f82ff98 100644
--- a/source/model.d
+++ b/source/model.d
@@ -1,6 +1,92 @@
module model;
-struct ToDoItem {
- string text;
- int priority;
+class ToDoItem {
+ string text;
+ int priority;
+ bool checked;
+ this(string text, int priority, bool checked) {
+ this.text = text;
+ this.priority = priority;
+ this.checked = checked;
+ }
+}
+
+interface ModelUpdateListener {
+ void itemsUpdated(ToDoItem[] items);
+
+ static ModelUpdateListener of(void delegate(ToDoItem[]) dg) {
+ return new class ModelUpdateListener {
+ void itemsUpdated(ToDoItem[] items) {
+ dg(items);
+ }
+ };
+ }
+}
+
+class ToDoModel {
+ private ToDoItem[] items;
+ private ModelUpdateListener[] listeners;
+
+ void addListener(ModelUpdateListener listener) {
+ listeners ~= listener;
+ }
+
+ void addItem(string text) {
+ addItem(new ToDoItem(text, 1_000_000, false));
+ }
+
+ void addItem(ToDoItem item) {
+ items ~= item;
+ normalizePrio();
+ notifyListeners();
+ }
+
+ void removeItem(int prio) {
+ import std.algorithm;
+ size_t idx = prio - 1; // Assume prio is normalized to start at 1.
+ items = items.remove(idx);
+ normalizePrio();
+ notifyListeners();
+ }
+
+ bool canIncrement(ToDoItem item) {
+ return item.priority > 1;
+ }
+
+ void incrementPriority(ToDoItem item) {
+ size_t idx = item.priority - 1;
+ if (idx == 0) return;
+ ToDoItem higher = items[idx - 1];
+ item.priority -= 1;
+ higher.priority += 1;
+ items[idx - 1] = item;
+ items[idx] = higher;
+ notifyListeners();
+ }
+
+ bool canDecrement(ToDoItem item) {
+ return item.priority < items.length;
+ }
+
+ void decrementPriority(ToDoItem item) {
+ size_t idx = item.priority - 1;
+ if (idx + 1 >= items.length) return;
+ ToDoItem lower = items[idx + 1];
+ item.priority += 1;
+ lower.priority -= 1;
+ items[idx + 1] = item;
+ items[idx] = lower;
+ notifyListeners();
+ }
+
+ private void normalizePrio() {
+ int prio = 1;
+ foreach (item; items) {
+ item.priority = prio++;
+ }
+ }
+
+ private void notifyListeners() {
+ foreach (l; listeners) l.itemsUpdated(this.items);
+ }
}
\ No newline at end of file
diff --git a/source/view.d b/source/view.d
deleted file mode 100644
index b545e0a..0000000
--- a/source/view.d
+++ /dev/null
@@ -1,105 +0,0 @@
-module view;
-
-import model;
-
-import gtk.Box;
-import gtk.ListBox;
-import gtk.ListBoxRow;
-import gtk.Label;
-import gtk.CheckButton;
-import gtk.Window;
-import gtk.ScrolledWindow;
-import gtk.Entry;
-import gtk.Button;
-
-// Yes, I use global items for my application.
-
-ListBox todoList;
-Entry todoEntry;
-
-ToDoItem[] items = [];
-
-void setupUI(Window w) {
- todoList = new ListBox();
- todoEntry = new Entry();
-
- auto vbox = new Box(GtkOrientation.VERTICAL, 5);
-
- todoList.setSelectionMode(GtkSelectionMode.NONE);
-
- vbox.packStart(new ScrolledWindow(todoList), true, true, 5);
-
- auto addBox = new Box(GtkOrientation.HORIZONTAL, 5);
- addBox.packStart(todoEntry, true, true, 0);
- Button addButton = new Button("Add");
- addButton.addOnClicked(delegate(Button b) {addItem();});
- addBox.packEnd(addButton, false, false, 0);
- vbox.packEnd(addBox, false, false, 0);
-
- w.add(vbox);
-}
-
-void addItem() {
- import std.algorithm;
- import std.string;
- string text = todoEntry.getText().strip;
- if (text.length == 0) return;
- int maxPrio = -1_000_000;
- foreach (item; items) {
- maxPrio = max(maxPrio, item.priority);
- }
- ToDoItem newItem = ToDoItem(text, maxPrio + 1);
- items ~= newItem;
- todoEntry.setText("");
- normalizePriorities();
- refreshList();
-}
-
-void normalizePriorities() {
- int prio = 1;
- foreach (item; items) {
- item.priority = prio++;
- }
-}
-
-void removeItem(int prio) {
- import std.algorithm;
- import std.array;
- ToDoItem[] newItems = items.filter!(item => item.priority != prio).array;
- items = newItems;
- normalizePriorities();
- refreshList();
-}
-
-void refreshList() {
- todoList.removeAll();
- foreach (item; items) {
- auto widget = new ToDoItemWidget(item);
- auto row = new ListBoxRow();
- row.add(widget);
- todoList.add(row);
- }
- todoList.showAll();
-}
-
-class ToDoItemWidget : Box {
- private ToDoItem item;
-
- this(ToDoItem item) {
- super(GtkOrientation.HORIZONTAL, 5);
- this.item = item;
- CheckButton button = new CheckButton();
- Label label = new Label(item.text);
- label.setLineWrap(true);
- label.setHalign(GtkAlign.START);
- label.setValign(GtkAlign.CENTER);
- this.packStart(button, false, false, 0);
-
- Button removeButton = new Button("Remove");
- removeButton.addOnClicked(delegate(Button b) {
- removeItem(item.priority);
- });
- this.packEnd(removeButton, false, false, 0);
- this.packEnd(label, true, true, 0);
- }
-}
\ No newline at end of file
diff --git a/todo-ui.glade b/todo-ui.glade
new file mode 100644
index 0000000..1fafd2e
--- /dev/null
+++ b/todo-ui.glade
@@ -0,0 +1,234 @@
+
+
+
+
+
+