#include "TimerTaskTree.h" #include "TimerEditWindow.h" #include "TimerFileWatcher.h" #include #include 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 = 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, ¤tIter)) { if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store), &parentIter, ¤tIter)) { 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); }