Compare commits

..

No commits in common. "main" and "v1.0.0-SNAPSHOT" have entirely different histories.

5 changed files with 44 additions and 142 deletions

View File

@ -4,8 +4,7 @@
], ],
"copyright": "Copyright © 2022, Andrew Lalis", "copyright": "Copyright © 2022, Andrew Lalis",
"dependencies": { "dependencies": {
"gtk-d": "~>3.10.0", "gtk-d": "~>3.10.0"
"dsh": "~>1.6.1"
}, },
"stringImportPaths": [ "stringImportPaths": [
"resources" "resources"

View File

@ -1,7 +1,6 @@
{ {
"fileVersion": 1, "fileVersion": 1,
"versions": { "versions": {
"dsh": "1.6.1",
"gtk-d": "3.10.0" "gtk-d": "3.10.0"
} }
} }

View File

@ -107,7 +107,6 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<signal name="activate" handler="onAboutMenuActivated" swapped="no"/>
</object> </object>
</child> </child>
</object> </object>
@ -134,7 +133,7 @@
<object class="GtkListBox" id="taskList"> <object class="GtkListBox" id="taskList">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="activate_on_single_click">False</property> <property name="selection_mode">none</property>
</object> </object>
</child> </child>
</object> </object>
@ -154,7 +153,6 @@
<object class="GtkEntry" id="addTaskEntry"> <object class="GtkEntry" id="addTaskEntry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<signal name="activate" handler="onAddTask" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
@ -171,6 +169,7 @@
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="always_show_image">True</property> <property name="always_show_image">True</property>
<signal name="clicked" handler="onAddTask" swapped="no"/> <signal name="clicked" handler="onAddTask" swapped="no"/>
<accelerator key="Return" signal="clicked"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>

View File

@ -1,10 +1,5 @@
import model; import model;
import std.stdio; import std.stdio;
import std.functional : toDelegate;
import dsh : getHomeDir;
import std.path;
import std.file;
import gtk.MainWindow; import gtk.MainWindow;
import gtk.Main; import gtk.Main;
@ -19,10 +14,6 @@ import gtk.Label;
import gtk.CheckButton; import gtk.CheckButton;
import gtk.ToggleButton; import gtk.ToggleButton;
import gtk.Button; import gtk.Button;
import gtk.Widget;
import gdk.Keymap;
import gdk.Keysyms : GdkKeysyms;
import gio.Resource; import gio.Resource;
import glib.Bytes; import glib.Bytes;
@ -39,6 +30,26 @@ class ToDoItemWidget : Box {
label.setHalign(GtkAlign.START); label.setHalign(GtkAlign.START);
label.setValign(GtkAlign.CENTER); label.setValign(GtkAlign.CENTER);
this.packStart(button, false, false, 0); 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); this.packEnd(label, true, true, 0);
} }
} }
@ -66,110 +77,31 @@ void main(string[] args) {
builder.addCallbackSymbol("onSaveAsMenuActivated", &onSaveAsMenuActivated); builder.addCallbackSymbol("onSaveAsMenuActivated", &onSaveAsMenuActivated);
builder.addCallbackSymbol("onOpenMenuActivated", &onOpenMenuActivated); builder.addCallbackSymbol("onOpenMenuActivated", &onOpenMenuActivated);
builder.addCallbackSymbol("onQuitMenuActivated", &onWindowDestroy); builder.addCallbackSymbol("onQuitMenuActivated", &onWindowDestroy);
builder.addCallbackSymbol("onAboutMenuActivated", &onAboutMenuActivated);
builder.connectSignals(null); builder.connectSignals(null);
taskList = cast(ListBox) builder.getObject("taskList"); taskList = cast(ListBox) builder.getObject("taskList");
Widget listWidget = cast(Widget) taskList;
listWidget.addOnKeyPress(toDelegate(&taskListKeyPressed));
taskEntry = cast(Entry) builder.getObject("addTaskEntry"); taskEntry = cast(Entry) builder.getObject("addTaskEntry");
todoModel = new ToDoModel(); todoModel = new ToDoModel();
string lastOpenPath = buildPath(getHomeDir(), ".config/todo-d/last-open.txt"); import std.functional : toDelegate;
if (exists(lastOpenPath)) { todoModel.addListener(ModelUpdateListener.of(toDelegate(&itemsUpdated)));
import std.string : strip;
string lastOpenFile = readText(lastOpenPath).strip;
todoModel.openFromJson(lastOpenFile);
}
window = cast(ApplicationWindow) builder.getObject("window"); window = cast(ApplicationWindow) builder.getObject("window");
auto listener = new UIModelUpdateListener(window);
todoModel.addListener(listener);
// Trigger UI updates once before rendering the window.
todoModel.notifyListeners();
listener.fileUpdated(todoModel.getOpenFilename());
window.showAll(); window.showAll();
Main.run(); Main.run();
} }
class UIModelUpdateListener : ModelUpdateListener {
private ApplicationWindow window;
this(ApplicationWindow window) {
this.window = window;
}
void itemsUpdated(ToDoItem[] items) { void itemsUpdated(ToDoItem[] items) {
taskList.removeAll(); taskList.removeAll();
foreach (item; items) { foreach (item; items) {
auto widget = new ToDoItemWidget(item, todoModel); auto widget = new ToDoItemWidget(item, todoModel);
auto row = new ListBoxRow(); auto row = new ListBoxRow();
row.setSelectable(true);
row.setActivatable(false);
row.add(widget); row.add(widget);
taskList.add(row); taskList.add(row);
} }
taskList.showAll(); taskList.showAll();
} }
void fileUpdated(string filename) {
if (filename is null) {
window.setTitle("todo-d");
} else {
window.setTitle("todo-d - " ~ filename);
}
window.showAll();
}
}
bool taskListKeyPressed(GdkEventKey* event, Widget w) {
int idx = taskList.getSelectedRow().getIndex();
int selectedPrio = idx + 1;
ToDoItem selectedItem = todoModel.getItemAt(selectedPrio);
if (selectedItem is null) return true;
bool ctrlDown = (event.state & ModifierType.CONTROL_MASK) > 0;
if (ctrlDown && event.keyval == GdkKeysyms.GDK_Up && todoModel.canIncrement(selectedItem)) {
todoModel.incrementPriority(selectedItem);
auto row = taskList.getRowAtIndex(idx - 1);
taskList.selectRow(row);
Widget rowWidget = cast(Widget) row;
rowWidget.grabFocus();
}
if (ctrlDown && event.keyval == GdkKeysyms.GDK_Down && todoModel.canDecrement(selectedItem)) {
todoModel.decrementPriority(selectedItem);
auto row = taskList.getRowAtIndex(idx + 1);
taskList.selectRow(row);
Widget rowWidget = cast(Widget) row;
rowWidget.grabFocus();
}
if (!ctrlDown && event.keyval == GdkKeysyms.GDK_Up && idx > 0) {
auto row = taskList.getRowAtIndex(idx - 1);
taskList.selectRow(row);
Widget rowWidget = cast(Widget) row;
rowWidget.grabFocus();
}
if (!ctrlDown && event.keyval == GdkKeysyms.GDK_Down && idx + 1 < todoModel.itemCount) {
auto row = taskList.getRowAtIndex(idx + 1);
taskList.selectRow(row);
Widget rowWidget = cast(Widget) row;
rowWidget.grabFocus();
}
if (event.keyval == GdkKeysyms.GDK_Delete) {
todoModel.removeItem(selectedPrio);
}
if (event.keyval == GdkKeysyms.GDK_Return) {
selectedItem.checked = !selectedItem.checked;
todoModel.notifyListeners();
auto row = taskList.getRowAtIndex(idx);
taskList.selectRow(row);
Widget rowWidget = cast(Widget) row;
rowWidget.grabFocus();
}
return true;
}
extern (C) void addTask() { extern (C) void addTask() {
todoModel.addItem(taskEntry.getText()); todoModel.addItem(taskEntry.getText());
taskEntry.setText(""); taskEntry.setText("");
@ -179,12 +111,6 @@ extern (C) void onWindowDestroy() {
Main.quit(); Main.quit();
if (todoModel.getOpenFilename() !is null) { if (todoModel.getOpenFilename() !is null) {
todoModel.saveToJson(todoModel.getOpenFilename()); todoModel.saveToJson(todoModel.getOpenFilename());
string configPath = buildPath(getHomeDir(), ".config/todo-d/");
if (!exists(configPath)) {
mkdirRecurse(configPath);
}
string lastOpenFile = buildPath(configPath, "last-open.txt");
std.file.write(lastOpenFile, todoModel.getOpenFilename());
} }
} }
@ -230,15 +156,3 @@ extern (C) void onOpenMenuActivated() {
} }
dialog.close(); dialog.close();
} }
extern (C) void onAboutMenuActivated() {
import gtk.AboutDialog;
AboutDialog dialog = new AboutDialog();
dialog.setProgramName("Todo-D");
dialog.setLicenseType(GtkLicense.MIT_X11);
dialog.setAuthors(["Andrew Lalis"]);
dialog.setComments("A simple To-Do app written in D using GTK.");
dialog.setWebsite("https://github.com/andrewlalis/todo-d");
dialog.run();
dialog.destroy();
}

View File

@ -13,7 +13,14 @@ class ToDoItem {
interface ModelUpdateListener { interface ModelUpdateListener {
void itemsUpdated(ToDoItem[] items); void itemsUpdated(ToDoItem[] items);
void fileUpdated(string filename);
static ModelUpdateListener of(void delegate(ToDoItem[]) dg) {
return new class ModelUpdateListener {
void itemsUpdated(ToDoItem[] items) {
dg(items);
}
};
}
} }
class ToDoModel { class ToDoModel {
@ -29,15 +36,6 @@ class ToDoModel {
return openFilename; return openFilename;
} }
ToDoItem getItemAt(int prio) {
if (prio < 1 || prio > items.length) return null;
return items[prio - 1];
}
ulong itemCount() {
return items.length;
}
void addItem(string text) { void addItem(string text) {
addItem(new ToDoItem(text, 1_000_000, false)); addItem(new ToDoItem(text, 1_000_000, false));
} }
@ -101,10 +99,7 @@ class ToDoModel {
); );
items ~= item; items ~= item;
} }
if (openFilename != filename) {
openFilename = filename; openFilename = filename;
foreach (l; listeners) l.fileUpdated(filename);
}
sort!((a, b) => a.priority < b.priority)(items); sort!((a, b) => a.priority < b.priority)(items);
normalizePrio(); normalizePrio();
notifyListeners(); notifyListeners();
@ -124,16 +119,12 @@ class ToDoModel {
} }
j["items"] = JSONValue(itemObjs); j["items"] = JSONValue(itemObjs);
write(filename, toJSON(j, true)); write(filename, toJSON(j, true));
if (openFilename != filename) {
openFilename = filename; openFilename = filename;
foreach (l; listeners) l.fileUpdated(filename);
}
} }
void clear() { void clear() {
items = []; items = [];
openFilename = null; openFilename = null;
foreach (l; listeners) l.fileUpdated(openFilename);
notifyListeners(); notifyListeners();
} }
@ -144,7 +135,7 @@ class ToDoModel {
} }
} }
public void notifyListeners() { private void notifyListeners() {
foreach (l; listeners) l.itemsUpdated(this.items); foreach (l; listeners) l.itemsUpdated(this.items);
} }
} }