833 lines
31 KiB
C
833 lines
31 KiB
C
|
#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 = 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);
|
||
|
}
|