todo-d/source/app.d

245 lines
6.7 KiB
D

import model;
import std.stdio;
import std.functional : toDelegate;
import dsh : getHomeDir;
import std.path;
import std.file;
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;
import gtk.Widget;
import gdk.Keymap;
import gdk.Keysyms : GdkKeysyms;
import gio.Resource;
import glib.Bytes;
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);
this.packEnd(label, true, true, 0);
}
}
Entry taskEntry;
ListBox taskList;
ApplicationWindow window;
ToDoModel todoModel;
void main(string[] args) {
Main.init(args);
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.addCallbackSymbol("onAboutMenuActivated", &onAboutMenuActivated);
builder.connectSignals(null);
taskList = cast(ListBox) builder.getObject("taskList");
Widget listWidget = cast(Widget) taskList;
listWidget.addOnKeyPress(toDelegate(&taskListKeyPressed));
taskEntry = cast(Entry) builder.getObject("addTaskEntry");
todoModel = new ToDoModel();
string lastOpenPath = buildPath(getHomeDir(), ".config/todo-d/last-open.txt");
if (exists(lastOpenPath)) {
import std.string : strip;
string lastOpenFile = readText(lastOpenPath).strip;
todoModel.openFromJson(lastOpenFile);
}
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();
Main.run();
}
class UIModelUpdateListener : ModelUpdateListener {
private ApplicationWindow window;
this(ApplicationWindow window) {
this.window = window;
}
void itemsUpdated(ToDoItem[] items) {
taskList.removeAll();
foreach (item; items) {
auto widget = new ToDoItemWidget(item, todoModel);
auto row = new ListBoxRow();
row.setSelectable(true);
row.setActivatable(false);
row.add(widget);
taskList.add(row);
}
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() {
todoModel.addItem(taskEntry.getText());
taskEntry.setText("");
}
extern (C) void onWindowDestroy() {
Main.quit();
if (todoModel.getOpenFilename() !is null) {
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());
}
}
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();
}
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();
}