practice-timer/TimerTaskTree.c

834 lines
31 KiB
C
Raw Permalink Normal View History

2022-08-28 14:20:12 -07:00
#include "TimerTaskTree.h"
#include "TimerEditWindow.h"
#include "TimerFileWatcher.h"
#include <json-glib/json-glib.h>
#include <math.h>
static gint hash_date(GDateTime *dt) {
int y, m, d;
g_date_time_get_ymd(dt, &y, &m, &d);
GDate *da = g_date_new_dmy(d, m, y);
return g_date_get_julian(da);
}
static gboolean compare_date(GDateTime *dt1, GDateTime *dt2) {
int y1, m1, d1, y2, m2, d2;
g_date_time_get_ymd(dt1, &y1, &m1, &d1);
g_date_time_get_ymd(dt2, &y2, &m2, &d2);
return y1 == y2 && m1 == m2 && d1 == d2;
}
static gint hash_time(GDateTime *t) {
int h = g_date_time_get_hour(t);
int m = g_date_time_get_minute(t);
int s = g_date_time_get_second(t);
return (h * 3600) + (m * 60) + s;
}
enum { DATE_COLUMN, TOTAL_TIME_COLUMN, SORT_COLUMN, DATA_COLUMN, N_COLUMNS };
/* TimerHeader declarations */
#define TIMER_TYPE_HEADER timer_header_get_type()
G_DECLARE_FINAL_TYPE(TimerHeader, timer_header, TIMER, HEADER, GObject);
static TimerHeader *timer_header_new(GDateTime *date, TimerTaskTree *tree);
static void timer_header_add_task(TimerHeader *self, const char *name,
GDateTime *start, gint64 length);
/* TimerTask declarations */
#define TIMER_TYPE_TASK timer_task_get_type()
G_DECLARE_FINAL_TYPE(TimerTask, timer_task, TIMER, TASK, GObject);
static TimerTask *timer_task_new(const char *name, GDateTime *start,
gint64 length, TimerHeader *header);
static gint64 timer_task_get_length(TimerTask *self);
static JsonObject *timer_task_serialize(TimerTask *self);
/* TimerTaskTree variables */
struct _TimerTaskTree {
GtkTreeView parent;
GtkTreeStore *store;
GHashTable *headers;
GtkWidget *popup;
GtkWidget *deleteButton;
GtkWidget *editButton;
char **taskNames;
gsize taskNamesLen;
TimerFileWatcher *fileWatcher;
char *dataPath;
};
G_DEFINE_TYPE(TimerTaskTree, timer_task_tree, GTK_TYPE_TREE_VIEW);
/* TimerTask variables */
struct _TimerTask {
GObject parent;
TimerTaskTree *tree;
GtkTreeStore *store;
char *name;
GDateTime *start;
gint64 length;
GtkTreeIter iter;
};
G_DEFINE_TYPE(TimerTask, timer_task, G_TYPE_OBJECT);
/* TimerHeader class */
struct _TimerHeader {
GObject parent;
TimerTaskTree *tree;
GtkTreeStore *store;
GDateTime *date;
GtkTreeIter iter;
};
G_DEFINE_TYPE(TimerHeader, timer_header, G_TYPE_OBJECT);
static char *timer_header_get_date_string(TimerHeader *self) {
GDateTime *now = g_date_time_new_now_local();
if (compare_date(self->date, now)) {
g_date_time_unref(now);
return g_strdup("Today");
}
GDateTime *yesterday = g_date_time_add_days(now, -1);
g_date_time_unref(now);
if (compare_date(self->date, yesterday)) {
g_date_time_unref(yesterday);
return g_strdup("Yesterday");
}
g_date_time_unref(yesterday);
int y, m, d;
g_date_time_get_ymd(self->date, &y, &m, &d);
return g_strdup_printf("%02d/%02d/%04d", m, d, y);
}
static JsonObject *timer_header_serialize(TimerHeader *self) {
JsonObject *root = json_object_new();
json_object_set_int_member(root, "date", g_date_time_to_unix(self->date));
JsonArray *tasks = json_array_new();
GtkTreeIter child;
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &child,
&self->iter)) {
TimerTask *t;
do {
gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child, DATA_COLUMN,
&t, -1);
g_object_unref(t);
json_array_add_object_element(tasks, timer_task_serialize(t));
} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &child));
}
json_object_set_array_member(root, "tasks", tasks);
return root;
}
static TimerHeader *timer_header_new(GDateTime *date, TimerTaskTree *tree) {
TimerHeader *h = g_object_new(TIMER_TYPE_HEADER, NULL);
h->date = g_date_time_to_local(date);
h->tree = tree;
h->store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));
gtk_tree_store_append(h->store, &h->iter, NULL);
char *ds = timer_header_get_date_string(h);
gtk_tree_store_set(h->store, &h->iter, DATE_COLUMN, ds, TOTAL_TIME_COLUMN,
"00:00:00", SORT_COLUMN, hash_date(h->date), DATA_COLUMN,
h, -1);
g_object_unref(h);
g_free(ds);
gtk_widget_show_all(GTK_WIDGET(tree));
return h;
}
static guint64 timer_header_get_total_time(TimerHeader *self) {
guint64 time = 0;
GtkTreeIter task;
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &task,
&self->iter)) {
TimerTask *t;
do {
gtk_tree_model_get(GTK_TREE_MODEL(self->store), &task, DATA_COLUMN,
&t, -1);
g_object_unref(t);
time += timer_task_get_length(t);
} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &task));
}
return time;
}
static void timer_header_set_time(TimerHeader *self, guint64 time) {
int h = floor(time / 3600.0f);
int m = floor(time / 60.0f) - (h * 60);
int s = time - (m * 60) - (h * 3600);
char *total = g_strdup_printf("%02d:%02d:%02d", h, m, s);
gtk_tree_store_set(self->store, &self->iter, TOTAL_TIME_COLUMN, total, -1);
g_free(total);
}
static void timer_header_update_time(TimerHeader *self) {
timer_header_set_time(self, timer_header_get_total_time(self));
}
static void timer_header_add_task(TimerHeader *self, const char *name,
GDateTime *start, gint64 length) {
timer_task_new(name, start, length, self);
timer_header_update_time(self);
gtk_widget_show_all(GTK_WIDGET(self->tree));
}
static void timer_header_remove(TimerHeader *self) {
gtk_tree_store_remove(self->store, &self->iter);
}
static void timer_header_remove_task(TimerHeader *self, TimerTask *task) {
gtk_tree_store_remove(self->store, &task->iter);
if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(self->store),
&self->iter)) {
timer_header_remove(self);
}
}
static void timer_header_finalize(GObject *self) {
g_hash_table_remove(TIMER_HEADER(self)->tree->headers,
TIMER_HEADER(self)->date);
g_date_time_unref(TIMER_HEADER(self)->date);
G_OBJECT_CLASS(timer_header_parent_class)->finalize(self);
}
static void timer_header_class_init(TimerHeaderClass *class) {
G_OBJECT_CLASS(class)->finalize = timer_header_finalize;
}
static void timer_header_init(TimerHeader *self) {
}
/* TimerTask class */
static TimerHeader *timer_task_get_header(TimerTask *self) {
TimerHeader *h;
GtkTreeIter parent;
gtk_tree_model_iter_parent(GTK_TREE_MODEL(self->store), &parent,
&self->iter);
gtk_tree_model_get(GTK_TREE_MODEL(self->store), &parent, DATA_COLUMN, &h,
-1);
g_object_unref(h);
return h;
}
static void timer_task_update_time(TimerTask *self) {
int h = floor(self->length / 3600.0f);
int m = floor(self->length / 60.0f) - (h * 60);
int s = self->length - (m * 60) - (h * 3600);
char *total = g_strdup_printf("%02d:%02d:%02d", h, m, s);
gtk_tree_store_set(self->store, &self->iter, TOTAL_TIME_COLUMN, total, -1);
TimerHeader *th = timer_task_get_header(self);
timer_header_update_time(th);
g_free(total);
}
static JsonObject *timer_task_serialize(TimerTask *self) {
JsonObject *root = json_object_new();
json_object_set_string_member(root, "name", self->name);
json_object_set_int_member(root, "length", self->length);
json_object_set_int_member(root, "start", g_date_time_to_unix(self->start));
return root;
}
static TimerTask *timer_task_new(const char *name, GDateTime *start,
gint64 length, TimerHeader *header) {
TimerTask *t = g_object_new(TIMER_TYPE_TASK, NULL);
t->name = g_strdup(name);
t->start = g_date_time_to_local(start);
t->length = length;
t->tree = header->tree;
t->store = header->store;
gtk_tree_store_append(t->store, &t->iter, &header->iter);
gtk_tree_store_set(t->store, &t->iter, DATE_COLUMN, t->name, SORT_COLUMN,
hash_time(t->start), DATA_COLUMN, t, -1);
g_object_unref(t);
timer_task_update_time(t);
gtk_widget_show_all(GTK_WIDGET(t->tree));
return t;
}
static void timer_task_edit(TimerTask *self) {
TimerEditWindow *diag = timer_edit_window_new(
self->name, self->start, self->length,
(const char **)self->tree->taskNames, self->tree->taskNamesLen, FALSE, NULL);
int resp = gtk_dialog_run(GTK_DIALOG(diag));
if (resp == GTK_RESPONSE_APPLY) {
GDateTime *newTime = timer_edit_window_get_start(diag);
if (compare_date(self->start, newTime)) {
g_free(self->name);
self->name = timer_edit_window_get_name(diag);
g_date_time_unref(self->start);
self->start = newTime;
gtk_tree_store_set(self->store, &self->iter, DATE_COLUMN,
self->name, SORT_COLUMN, hash_time(newTime), -1);
self->length = timer_edit_window_get_length(diag);
timer_task_update_time(self);
timer_task_tree_save(self->tree);
} else {
TimerHeader *h = timer_task_get_header(self);
timer_header_remove_task(h, self);
char *name = timer_edit_window_get_name(diag);
timer_task_tree_add_task(h->tree, newTime, name,
timer_edit_window_get_length(diag));
g_free(name);
g_date_time_unref(newTime);
}
} else if (resp == GTK_RESPONSE_REJECT) {
TimerHeader *h = timer_task_get_header(self);
timer_header_remove_task(h, self);
}
gtk_widget_destroy(GTK_WIDGET(diag));
}
static gint64 timer_task_get_length(TimerTask *self) {
return self->length;
}
static void timer_task_finalize(GObject *self) {
g_free(TIMER_TASK(self)->name);
g_date_time_unref(TIMER_TASK(self)->start);
G_OBJECT_CLASS(timer_task_parent_class)->finalize(self);
}
static void timer_task_class_init(TimerTaskClass *class) {
G_OBJECT_CLASS(class)->finalize = timer_task_finalize;
}
static void timer_task_init(TimerTask *self) {
}
/* TimerTaskTree class */
GtkWidget *timer_task_tree_new() {
TimerTaskTree *t = g_object_new(TIMER_TYPE_TASK_TREE, NULL);
t->dataPath = NULL;
return GTK_WIDGET(t);
}
static void timer_task_tree_add_task_no_save(TimerTaskTree *self,
GDateTime *date, const char *task,
gint64 time) {
TimerHeader *h;
if (g_hash_table_contains(self->headers, date)) {
h = g_hash_table_lookup(self->headers, date);
} else {
h = timer_header_new(date, self);
g_hash_table_insert(self->headers, g_date_time_to_local(date), h);
}
timer_header_add_task(h, task, date, time);
}
void timer_task_tree_add_task(TimerTaskTree *self, GDateTime *date,
const char *task, gint64 time) {
timer_task_tree_add_task_no_save(self, date, task, time);
timer_task_tree_save(self);
}
void timer_task_tree_set_task_names(TimerTaskTree *self, const char **names,
gsize len) {
gsize i;
for (i = 0; i < self->taskNamesLen; ++i) {
g_free(self->taskNames[i]);
}
g_free(self->taskNames);
self->taskNames = g_malloc_n(len, sizeof(char *));
for (i = 0; i < len; ++i) {
self->taskNames[i] = g_strdup(names[i]);
}
self->taskNamesLen = len;
}
const char **timer_task_tree_get_task_names(TimerTaskTree *self, gsize *len) {
*len = self->taskNamesLen;
return (const char **)self->taskNames;
}
void timer_task_tree_update_header_dates(TimerTaskTree *self) {
GHashTableIter iter;
g_hash_table_iter_init(&iter, self->headers);
TimerHeader *h = NULL;
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &h)) {
char *ds = timer_header_get_date_string(h);
gtk_tree_store_set(self->store, &h->iter, DATE_COLUMN, ds, -1);
free(ds);
}
}
static void timer_task_tree_update_data(TimerTaskTree *self) {
GHashTableIter iter;
g_hash_table_iter_init(&iter, self->headers);
GList *headers = NULL;
TimerHeader *h;
while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&h)) {
headers = g_list_append(headers, h);
}
GList *i;
for (i = headers; i != NULL; i = i->next) {
timer_header_remove(TIMER_HEADER(i->data));
}
g_list_free(headers);
timer_task_tree_add_from_file(self, self->dataPath);
}
static gboolean do_async_update_data_file(TimerTaskTree *tree) {
timer_task_tree_update_data(tree);
timer_task_tree_expand_today(tree);
return FALSE;
}
static void data_file_updated(TimerFileWatcher *fw, TimerTaskTree *tree) {
g_main_context_invoke(NULL, G_SOURCE_FUNC(do_async_update_data_file), tree);
}
void timer_task_tree_set_data_path(TimerTaskTree *self, const char *path) {
g_free(self->dataPath);
self->dataPath = g_strdup(path);
timer_task_tree_update_data(self);
if (self->fileWatcher) {
g_object_unref(self->fileWatcher);
}
self->fileWatcher = NULL;
2022-08-28 14:20:12 -07:00
self->fileWatcher = timer_file_watcher_new(path);
g_signal_connect(self->fileWatcher, "file-changed", G_CALLBACK(data_file_updated), self);
}
void timer_task_tree_expand_today(TimerTaskTree *self) {
GDateTime *today = g_date_time_new_now_local();
TimerHeader *h = g_hash_table_lookup(self->headers, today);
g_date_time_unref(today);
if (h != NULL) {
GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(self->store), &h->iter);
gtk_tree_view_expand_row(GTK_TREE_VIEW(self), path, TRUE);
gtk_tree_path_free(path);
}
}
void timer_task_tree_save(TimerTaskTree *self) {
if (self->fileWatcher) {
timer_file_watcher_pause(self->fileWatcher);
}
GHashTableIter iter;
g_hash_table_iter_init(&iter, self->headers);
TimerHeader *h;
JsonArray *headers = json_array_new();
while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&h)) {
json_array_add_object_element(headers, timer_header_serialize(h));
}
JsonNode *root = json_node_new(JSON_NODE_ARRAY);
json_node_set_array(root, headers);
JsonGenerator *out = json_generator_new();
json_generator_set_root(out, root);
json_node_unref(root);
if (self->dataPath != NULL) {
GError *err = NULL;
json_generator_to_file(out, self->dataPath, &err);
if (err != NULL) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Could not save tasks: %s", err->message);
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
g_error_free(err);
}
}
g_object_unref(out);
if (self->fileWatcher) {
timer_file_watcher_resume(self->fileWatcher, FALSE);
}
}
static gboolean timer_task_tree_add_task_from_object(TimerTaskTree *self,
JsonObject *obj,
gboolean status) {
JsonNode *nameNode = json_object_get_member(obj, "name");
JsonNode *lengthNode = json_object_get_member(obj, "length");
JsonNode *startNode = json_object_get_member(obj, "start");
if (nameNode == NULL || lengthNode == NULL || startNode == NULL ||
json_node_get_value_type(nameNode) != G_TYPE_STRING ||
json_node_get_value_type(lengthNode) != G_TYPE_INT64 ||
json_node_get_value_type(startNode) != G_TYPE_INT64) {
if (status) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Corrupt task found! It and all future corrupt objects will be "
"skipped. No furtur errors will be emmited for the remailder "
"of this load.");
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
}
return FALSE;
}
GDateTime *date =
g_date_time_new_from_unix_local(json_node_get_int(startNode));
timer_task_tree_add_task_no_save(self, date, json_node_get_string(nameNode),
json_node_get_int(lengthNode));
g_date_time_unref(date);
return TRUE;
}
static gboolean timer_task_tree_add_header_from_object(TimerTaskTree *self,
JsonObject *obj,
gboolean status) {
JsonNode *tasksNode = json_object_get_member(obj, "tasks");
if (tasksNode == NULL || JSON_NODE_TYPE(tasksNode) != JSON_NODE_ARRAY) {
if (status) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Corrupt header found! It and all future corrupt objects will "
"be skipped. No furtur errors will be emmited for the "
"remailder of this load.");
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
}
return FALSE;
}
JsonArray *arr = json_node_get_array(tasksNode);
GList *tasks = json_array_get_elements(arr);
GList *i;
for (i = tasks; i != NULL; i = i->next) {
JsonNode *taskNode = (JsonNode *)i->data;
if (JSON_NODE_TYPE(taskNode) != JSON_NODE_OBJECT) {
if (status) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Corrupt header found! It and all future corrupt objects "
"will be skipped. No furtur errors will be emmited for the "
"remailder of this load.");
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
}
status = FALSE;
} else {
if (!timer_task_tree_add_task_from_object(
self, json_node_get_object(taskNode), status)) {
status = FALSE;
}
}
}
g_list_free(tasks);
return status;
}
void timer_task_tree_add_from_file(TimerTaskTree *self, const char *path) {
JsonParser *in = json_parser_new_immutable();
GError *err = NULL;
json_parser_load_from_file(in, path, &err);
if (err != NULL) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Could not loads tasks: %s", err->message);
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
g_error_free(err);
} else {
JsonNode *root = json_parser_get_root(in);
if (JSON_NODE_TYPE(root) != JSON_NODE_ARRAY) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"Task file corrupt! Root was not an array!");
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
} else {
gboolean status = TRUE;
GList *headers =
json_array_get_elements(json_node_get_array((root)));
GList *i;
for (i = headers; i != NULL; i = i->next) {
JsonNode *hNode = (JsonNode *)i->data;
if (JSON_NODE_TYPE(hNode) != JSON_NODE_OBJECT) {
if (status) {
GtkWidget *diag = gtk_message_dialog_new(
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"Corrupt header found! It and all future corrupt "
"objects will be skipped. No furtur errors will be "
"emmited for the remailder of this load.");
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
gtk_dialog_run(GTK_DIALOG(diag));
gtk_widget_destroy(diag);
}
status = FALSE;
} else {
if (!timer_task_tree_add_header_from_object(
self, json_node_get_object(hNode), status)) {
status = FALSE;
}
}
}
g_list_free(headers);
}
}
g_object_unref(in);
}
char *timer_task_tree_get_csv(TimerTaskTree *self) {
GString *string = g_string_new("name,start,length\n");
GHashTableIter hIter;
g_hash_table_iter_init(&hIter, self->headers);
TimerHeader *header;
while (g_hash_table_iter_next(&hIter, NULL, (gpointer *)&header)) {
GtkTreeIter child;
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &child,
&header->iter)) {
TimerTask *task;
do {
gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child,
DATA_COLUMN, &task, -1);
g_object_unref(task);
g_string_append_printf(string, "%s,%ld,%ld\n", task->name,
g_date_time_to_unix(task->start),
task->length);
} while (
gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &child));
}
}
return g_string_free(string, FALSE);
}
TimerDataPoint *timer_task_tree_get_day_data(TimerTaskTree *self, gsize *length) {
*length = 0;
TimerDataPoint *arr = g_malloc(1);
GHashTableIter hIter;
g_hash_table_iter_init(&hIter, self->headers);
TimerHeader *header;
while (g_hash_table_iter_next(&hIter, NULL, (gpointer *)&header)) {
arr = g_realloc(arr, sizeof(TimerDataPoint) * ++(*length));
arr[*length - 1] = (TimerDataPoint){
g_date_time_to_local(header->date), timer_header_get_total_time(header)};
}
return arr;
}
TimerDataPoint *timer_task_tree_get_task_data(TimerTaskTree *self, gsize *length) {
*length = 0;
TimerDataPoint *arr = g_malloc(1);
GHashTableIter hIter;
g_hash_table_iter_init(&hIter, self->headers);
TimerHeader *header;
while (g_hash_table_iter_next(&hIter, NULL, (gpointer *)&header)) {
GtkTreeIter child;
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &child,
&header->iter)) {
TimerTask *task;
do {
gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child,
DATA_COLUMN, &task, -1);
g_object_unref(task);
arr = g_realloc(arr, sizeof(TimerDataPoint) * ++(*length));
arr[*length - 1] = (TimerDataPoint){
g_date_time_to_local(task->start), task->length};
} while (
gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &child));
}
}
return arr;
}
void timer_free_task_data(TimerDataPoint *data, gsize length) {
gsize i;
for (i = 0; i < length; ++i) {
g_date_time_unref(data[i].date);
}
g_free(data);
}
GDateTime *timer_task_tree_get_last_task_end(TimerTaskTree *self) {
GtkTreeIter headerIter;
if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(self->store), &headerIter)) {
return NULL;
}
GtkTreeIter child;
if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(self->store), &child, &headerIter, 0)) {
return NULL;
}
TimerTask *task;
gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child, DATA_COLUMN, &task, -1);
g_object_unref(task);
GDateTime *dt = g_date_time_add_seconds(task->start, task->length);
return dt;
}
static gint tree_sort_compare_func(GtkTreeModel *model, GtkTreeIter *i1,
GtkTreeIter *i2) {
GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
gtk_tree_model_get_value(model, i1, SORT_COLUMN, &v1);
gtk_tree_model_get_value(model, i2, SORT_COLUMN, &v2);
return g_value_get_uint64(&v1) - g_value_get_uint64(&v2);
}
static gboolean mouse_click_callback(TimerTaskTree *tree,
GdkEventButton *event) {
if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
GtkTreePath *path;
if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), event->x,
event->y, &path, NULL, NULL, NULL)) {
gtk_tree_selection_unselect_all(selection);
gtk_tree_selection_select_path(selection, path);
gtk_tree_path_free(path);
}
GtkTreeIter currentIter, parentIter;
if (gtk_tree_selection_get_selected(selection, NULL, &currentIter)) {
if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store),
&parentIter, &currentIter)) {
gtk_widget_set_visible(tree->editButton, TRUE);
} else {
gtk_widget_set_visible(tree->editButton, FALSE);
}
gtk_menu_popup_at_pointer(GTK_MENU(tree->popup), (GdkEvent *)event);
}
return TRUE;
} else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
GtkTreePath *path;
if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), event->x,
event->y, &path, NULL, NULL, NULL)) {
gtk_tree_selection_unselect_all(selection);
gtk_tree_selection_select_path(selection, path);
gtk_tree_path_free(path);
}
GtkTreeIter select;
if (gtk_tree_selection_get_selected(selection, NULL, &select)) {
GObject *obj;
gtk_tree_model_get(GTK_TREE_MODEL(tree->store), &select,
DATA_COLUMN, &obj, -1);
g_object_unref(obj);
if (TIMER_IS_TASK(obj)) {
timer_task_edit(TIMER_TASK(obj));
timer_task_tree_save(tree);
} else if (TIMER_IS_HEADER(obj)) {
GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree->store), &TIMER_HEADER(obj)->iter);
if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tree), path)) {
gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), path);
} else {
gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, TRUE);
}
gtk_tree_path_free(path);
}
}
return TRUE;
}
return FALSE;
}
static void popup_edit_button_callback(GtkMenuItem *btn, TimerTaskTree *tree) {
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
GtkTreeIter select;
if (gtk_tree_selection_get_selected(selection, NULL, &select)) {
TimerTask *task;
gtk_tree_model_get(GTK_TREE_MODEL(tree->store), &select, DATA_COLUMN,
&task, -1);
g_object_unref(task);
timer_task_edit(task);
timer_task_tree_save(tree);
}
}
static void popup_delete_button_callback(GtkMenuItem *btn,
TimerTaskTree *tree) {
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
GtkTreeIter select;
if (gtk_tree_selection_get_selected(selection, NULL, &select)) {
GObject *obj;
gtk_tree_model_get(GTK_TREE_MODEL(tree->store), &select, DATA_COLUMN,
&obj, -1);
g_object_unref(obj);
if (TIMER_IS_TASK(obj)) {
timer_header_remove_task(timer_task_get_header(TIMER_TASK(obj)),
TIMER_TASK(obj));
} else {
GtkWidget *diag = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Are you sure you would like to delete ALL tasks on this day?");
gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
int resp = gtk_dialog_run(GTK_DIALOG(diag));
if (resp == GTK_RESPONSE_YES) {
timer_header_remove(TIMER_HEADER(obj));
}
gtk_widget_destroy(diag);
}
timer_task_tree_save(tree);
}
}
static void timer_task_tree_finalize(GObject *self) {
gsize i;
for (i = 0; i < TIMER_TASK_TREE(self)->taskNamesLen; ++i) {
g_free(TIMER_TASK_TREE(self)->taskNames[i]);
}
g_free(TIMER_TASK_TREE(self)->dataPath);
g_free(TIMER_TASK_TREE(self)->taskNames);
gtk_widget_destroy(TIMER_TASK_TREE(self)->popup);
g_object_unref(TIMER_TASK_TREE(self)->store);
g_hash_table_destroy(TIMER_TASK_TREE(self)->headers);
G_OBJECT_CLASS(timer_task_tree_parent_class)->finalize(self);
}
static void timer_task_tree_class_init(TimerTaskTreeClass *class) {
G_OBJECT_CLASS(class)->finalize = timer_task_tree_finalize;
}
static void timer_task_tree_init(TimerTaskTree *self) {
self->store = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_UINT64, G_TYPE_OBJECT);
gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(self->store));
self->headers =
g_hash_table_new_full((GHashFunc)hash_date, (GCompareFunc)compare_date,
(GDestroyNotify)g_date_time_unref, NULL);
GtkCellRenderer *render = gtk_cell_renderer_text_new();
GValue font = G_VALUE_INIT;
g_value_init(&font, G_TYPE_STRING);
g_value_set_static_string(&font, "16");
g_object_set_property(G_OBJECT(render), "font", &font);
g_value_unset(&font);
gtk_tree_view_append_column(
GTK_TREE_VIEW(self),
gtk_tree_view_column_new_with_attributes(
"Date", render, "text", DATE_COLUMN, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(self),
gtk_tree_view_column_new_with_attributes(
"Time", render,
"text", TOTAL_TIME_COLUMN, NULL));
GtkTreeViewColumn *sort = gtk_tree_view_column_new_with_attributes(
"Sort", render, NULL);
gtk_tree_view_column_set_visible(sort, FALSE);
gtk_tree_view_append_column(GTK_TREE_VIEW(self), sort);
gtk_tree_sortable_set_default_sort_func(
GTK_TREE_SORTABLE(self->store),
(GtkTreeIterCompareFunc)tree_sort_compare_func, NULL, NULL);
gtk_tree_sortable_set_sort_column_id(
GTK_TREE_SORTABLE(self->store),
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_DESCENDING);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);
self->popup = gtk_menu_new();
self->deleteButton = gtk_menu_item_new_with_label("Delete");
self->editButton = gtk_menu_item_new_with_label("Edit");
gtk_menu_shell_append(GTK_MENU_SHELL(self->popup), self->deleteButton);
gtk_menu_shell_append(GTK_MENU_SHELL(self->popup), self->editButton);
gtk_widget_show_all(self->popup);
g_signal_connect(self, "button_press_event",
G_CALLBACK(mouse_click_callback), NULL);
g_signal_connect(self->deleteButton, "activate",
G_CALLBACK(popup_delete_button_callback), self);
g_signal_connect(self->editButton, "activate",
G_CALLBACK(popup_edit_button_callback), self);
}