diff --git a/.gitignore b/.gitignore index 66c032c..8aa6cff 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ todo-d-test-* *.o *.obj *.lst +*.glade~ +*.gresource \ No newline at end of file diff --git a/dub.json b/dub.json index 7edeced..614ab67 100644 --- a/dub.json +++ b/dub.json @@ -6,6 +6,12 @@ "dependencies": { "gtk-d": "~>3.10.0" }, + "stringImportPaths": [ + "resources" + ], + "preBuildCommands": [ + "glib-compile-resources --sourcedir=resources --target=resources/resources.gresource resources/gresource.xml" + ], "description": "A simple to-do list, implemented as a desktop application in D.", "license": "MIT", "name": "todo-d" diff --git a/gresource.xml b/resources/gresource.xml similarity index 100% rename from gresource.xml rename to resources/gresource.xml diff --git a/todo-ui.glade b/resources/todo-ui.glade similarity index 74% rename from todo-ui.glade rename to resources/todo-ui.glade index 1fafd2e..2b8cad7 100644 --- a/todo-ui.glade +++ b/resources/todo-ui.glade @@ -35,6 +35,8 @@ False True True + + @@ -44,24 +46,28 @@ False True True + - + gtk-save True False True True + + - + gtk-save-as True False True True + @@ -84,56 +90,6 @@ - - - True - False - _Edit - True - - - True - False - - - gtk-cut - True - False - True - True - - - - - gtk-copy - True - False - True - True - - - - - gtk-paste - True - False - True - True - - - - - gtk-delete - True - False - True - True - - - - - - True @@ -145,7 +101,7 @@ True False - + gtk-about True False @@ -213,6 +169,7 @@ True True + False diff --git a/source/app.d b/source/app.d index 9c9f19b..231bd2c 100644 --- a/source/app.d +++ b/source/app.d @@ -14,6 +14,8 @@ import gtk.Label; import gtk.CheckButton; import gtk.ToggleButton; import gtk.Button; +import gio.Resource; +import glib.Bytes; class ToDoItemWidget : Box { this(ToDoItem item, ToDoModel todoModel) { @@ -54,15 +56,26 @@ class ToDoItemWidget : Box { Entry taskEntry; ListBox taskList; +ApplicationWindow window; ToDoModel todoModel; void main(string[] args) { Main.init(args); - Builder builder = new Builder("todo-ui.glade"); + + auto bytes = new Bytes(cast(ubyte[]) import("resources.gresource")); + Resource.register(new Resource(bytes)); + + Builder builder = new Builder(); + builder.addFromResource("/ui/todo-ui.glade"); builder.addCallbackSymbol("onAddTask", &addTask); builder.addCallbackSymbol("onWindowDestroy", &onWindowDestroy); + + builder.addCallbackSymbol("onNewMenuActivated", &onNewMenuActivated); + builder.addCallbackSymbol("onSaveMenuActivated", &onSaveMenuActivated); + builder.addCallbackSymbol("onSaveAsMenuActivated", &onSaveAsMenuActivated); + builder.addCallbackSymbol("onOpenMenuActivated", &onOpenMenuActivated); builder.addCallbackSymbol("onQuitMenuActivated", &onWindowDestroy); builder.connectSignals(null); @@ -73,7 +86,7 @@ void main(string[] args) { import std.functional : toDelegate; todoModel.addListener(ModelUpdateListener.of(toDelegate(&itemsUpdated))); - ApplicationWindow window = cast(ApplicationWindow) builder.getObject("window"); + window = cast(ApplicationWindow) builder.getObject("window"); window.showAll(); Main.run(); } @@ -96,4 +109,50 @@ extern (C) void addTask() { extern (C) void onWindowDestroy() { Main.quit(); + if (todoModel.getOpenFilename() !is null) { + todoModel.saveToJson(todoModel.getOpenFilename()); + } +} + +extern (C) void onNewMenuActivated() { + todoModel.clear(); +} + +extern (C) void onSaveMenuActivated() { + if (todoModel.getOpenFilename() is null) { + onSaveAsMenuActivated(); + } else { + todoModel.saveToJson(todoModel.getOpenFilename()); + } +} + +extern (C) void onSaveAsMenuActivated() { + import gtk.FileChooserDialog; + auto dialog = new FileChooserDialog( + "Save As", + window, + FileChooserAction.SAVE + ); + if (todoModel.getOpenFilename() !is null) { + dialog.setFilename(todoModel.getOpenFilename()); + } + int result = dialog.run(); + if (result == -5) { + todoModel.saveToJson(dialog.getFilename()); + } + dialog.close(); +} + +extern (C) void onOpenMenuActivated() { + import gtk.FileChooserDialog; + auto dialog = new FileChooserDialog( + "Open", + window, + FileChooserAction.OPEN + ); + int result = dialog.run(); + if (result == -5) { + todoModel.openFromJson(dialog.getFilename()); + } + dialog.close(); } diff --git a/source/model.d b/source/model.d index f82ff98..b34147c 100644 --- a/source/model.d +++ b/source/model.d @@ -26,11 +26,16 @@ interface ModelUpdateListener { class ToDoModel { private ToDoItem[] items; private ModelUpdateListener[] listeners; + private string openFilename = null; void addListener(ModelUpdateListener listener) { listeners ~= listener; } + string getOpenFilename() { + return openFilename; + } + void addItem(string text) { addItem(new ToDoItem(text, 1_000_000, false)); } @@ -79,6 +84,50 @@ class ToDoModel { notifyListeners(); } + void openFromJson(string filename) { + import std.json; + import std.file; + import std.algorithm; + JSONValue j = parseJSON(readText(filename)); + JSONValue[] itemsArray = j["items"].array(); + items = []; + foreach (JSONValue itemObj; itemsArray) { + ToDoItem item = new ToDoItem( + itemObj["text"].str, + itemObj["priority"].get!int, + itemObj["checked"].boolean + ); + items ~= item; + } + openFilename = filename; + sort!((a, b) => a.priority < b.priority)(items); + normalizePrio(); + notifyListeners(); + } + + void saveToJson(string filename) { + import std.json; + import std.file; + JSONValue j = JSONValue(); + JSONValue[] itemObjs; + foreach (item; items) { + JSONValue itemObj = JSONValue(); + itemObj["text"] = JSONValue(item.text); + itemObj["priority"] = JSONValue(item.priority); + itemObj["checked"] = JSONValue(item.checked); + itemObjs ~= itemObj; + } + j["items"] = JSONValue(itemObjs); + write(filename, toJSON(j, true)); + openFilename = filename; + } + + void clear() { + items = []; + openFilename = null; + notifyListeners(); + } + private void normalizePrio() { int prio = 1; foreach (item; items) {