TimerTaskTree.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. #include "TimerTaskTree.h"
  2. #include "TimerEditWindow.h"
  3. #include "TimerFileWatcher.h"
  4. #include <json-glib/json-glib.h>
  5. #include <math.h>
  6. static gint hash_date(GDateTime *dt) {
  7. int y, m, d;
  8. g_date_time_get_ymd(dt, &y, &m, &d);
  9. GDate *da = g_date_new_dmy(d, m, y);
  10. return g_date_get_julian(da);
  11. }
  12. static gboolean compare_date(GDateTime *dt1, GDateTime *dt2) {
  13. int y1, m1, d1, y2, m2, d2;
  14. g_date_time_get_ymd(dt1, &y1, &m1, &d1);
  15. g_date_time_get_ymd(dt2, &y2, &m2, &d2);
  16. return y1 == y2 && m1 == m2 && d1 == d2;
  17. }
  18. static gint hash_time(GDateTime *t) {
  19. int h = g_date_time_get_hour(t);
  20. int m = g_date_time_get_minute(t);
  21. int s = g_date_time_get_second(t);
  22. return (h * 3600) + (m * 60) + s;
  23. }
  24. enum { DATE_COLUMN, TOTAL_TIME_COLUMN, SORT_COLUMN, DATA_COLUMN, N_COLUMNS };
  25. /* TimerHeader declarations */
  26. #define TIMER_TYPE_HEADER timer_header_get_type()
  27. G_DECLARE_FINAL_TYPE(TimerHeader, timer_header, TIMER, HEADER, GObject);
  28. static TimerHeader *timer_header_new(GDateTime *date, TimerTaskTree *tree);
  29. static void timer_header_add_task(TimerHeader *self, const char *name,
  30. GDateTime *start, gint64 length);
  31. /* TimerTask declarations */
  32. #define TIMER_TYPE_TASK timer_task_get_type()
  33. G_DECLARE_FINAL_TYPE(TimerTask, timer_task, TIMER, TASK, GObject);
  34. static TimerTask *timer_task_new(const char *name, GDateTime *start,
  35. gint64 length, TimerHeader *header);
  36. static gint64 timer_task_get_length(TimerTask *self);
  37. static JsonObject *timer_task_serialize(TimerTask *self);
  38. /* TimerTaskTree variables */
  39. struct _TimerTaskTree {
  40. GtkTreeView parent;
  41. GtkTreeStore *store;
  42. GHashTable *headers;
  43. GtkWidget *popup;
  44. GtkWidget *deleteButton;
  45. GtkWidget *editButton;
  46. char **taskNames;
  47. gsize taskNamesLen;
  48. TimerFileWatcher *fileWatcher;
  49. char *dataPath;
  50. };
  51. G_DEFINE_TYPE(TimerTaskTree, timer_task_tree, GTK_TYPE_TREE_VIEW);
  52. /* TimerTask variables */
  53. struct _TimerTask {
  54. GObject parent;
  55. TimerTaskTree *tree;
  56. GtkTreeStore *store;
  57. char *name;
  58. GDateTime *start;
  59. gint64 length;
  60. GtkTreeIter iter;
  61. };
  62. G_DEFINE_TYPE(TimerTask, timer_task, G_TYPE_OBJECT);
  63. /* TimerHeader class */
  64. struct _TimerHeader {
  65. GObject parent;
  66. TimerTaskTree *tree;
  67. GtkTreeStore *store;
  68. GDateTime *date;
  69. GtkTreeIter iter;
  70. };
  71. G_DEFINE_TYPE(TimerHeader, timer_header, G_TYPE_OBJECT);
  72. static char *timer_header_get_date_string(TimerHeader *self) {
  73. GDateTime *now = g_date_time_new_now_local();
  74. if (compare_date(self->date, now)) {
  75. g_date_time_unref(now);
  76. return g_strdup("Today");
  77. }
  78. GDateTime *yesterday = g_date_time_add_days(now, -1);
  79. g_date_time_unref(now);
  80. if (compare_date(self->date, yesterday)) {
  81. g_date_time_unref(yesterday);
  82. return g_strdup("Yesterday");
  83. }
  84. g_date_time_unref(yesterday);
  85. int y, m, d;
  86. g_date_time_get_ymd(self->date, &y, &m, &d);
  87. return g_strdup_printf("%02d/%02d/%04d", m, d, y);
  88. }
  89. static JsonObject *timer_header_serialize(TimerHeader *self) {
  90. JsonObject *root = json_object_new();
  91. json_object_set_int_member(root, "date", g_date_time_to_unix(self->date));
  92. JsonArray *tasks = json_array_new();
  93. GtkTreeIter child;
  94. if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &child,
  95. &self->iter)) {
  96. TimerTask *t;
  97. do {
  98. gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child, DATA_COLUMN,
  99. &t, -1);
  100. g_object_unref(t);
  101. json_array_add_object_element(tasks, timer_task_serialize(t));
  102. } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &child));
  103. }
  104. json_object_set_array_member(root, "tasks", tasks);
  105. return root;
  106. }
  107. static TimerHeader *timer_header_new(GDateTime *date, TimerTaskTree *tree) {
  108. TimerHeader *h = g_object_new(TIMER_TYPE_HEADER, NULL);
  109. h->date = g_date_time_to_local(date);
  110. h->tree = tree;
  111. h->store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)));
  112. gtk_tree_store_append(h->store, &h->iter, NULL);
  113. char *ds = timer_header_get_date_string(h);
  114. gtk_tree_store_set(h->store, &h->iter, DATE_COLUMN, ds, TOTAL_TIME_COLUMN,
  115. "00:00:00", SORT_COLUMN, hash_date(h->date), DATA_COLUMN,
  116. h, -1);
  117. g_object_unref(h);
  118. g_free(ds);
  119. gtk_widget_show_all(GTK_WIDGET(tree));
  120. return h;
  121. }
  122. static guint64 timer_header_get_total_time(TimerHeader *self) {
  123. guint64 time = 0;
  124. GtkTreeIter task;
  125. if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &task,
  126. &self->iter)) {
  127. TimerTask *t;
  128. do {
  129. gtk_tree_model_get(GTK_TREE_MODEL(self->store), &task, DATA_COLUMN,
  130. &t, -1);
  131. g_object_unref(t);
  132. time += timer_task_get_length(t);
  133. } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &task));
  134. }
  135. return time;
  136. }
  137. static void timer_header_set_time(TimerHeader *self, guint64 time) {
  138. int h = floor(time / 3600.0f);
  139. int m = floor(time / 60.0f) - (h * 60);
  140. int s = time - (m * 60) - (h * 3600);
  141. char *total = g_strdup_printf("%02d:%02d:%02d", h, m, s);
  142. gtk_tree_store_set(self->store, &self->iter, TOTAL_TIME_COLUMN, total, -1);
  143. g_free(total);
  144. }
  145. static void timer_header_update_time(TimerHeader *self) {
  146. timer_header_set_time(self, timer_header_get_total_time(self));
  147. }
  148. static void timer_header_add_task(TimerHeader *self, const char *name,
  149. GDateTime *start, gint64 length) {
  150. timer_task_new(name, start, length, self);
  151. timer_header_update_time(self);
  152. gtk_widget_show_all(GTK_WIDGET(self->tree));
  153. }
  154. static void timer_header_remove(TimerHeader *self) {
  155. gtk_tree_store_remove(self->store, &self->iter);
  156. }
  157. static void timer_header_remove_task(TimerHeader *self, TimerTask *task) {
  158. gtk_tree_store_remove(self->store, &task->iter);
  159. if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(self->store),
  160. &self->iter)) {
  161. timer_header_remove(self);
  162. }
  163. }
  164. static void timer_header_finalize(GObject *self) {
  165. g_hash_table_remove(TIMER_HEADER(self)->tree->headers,
  166. TIMER_HEADER(self)->date);
  167. g_date_time_unref(TIMER_HEADER(self)->date);
  168. G_OBJECT_CLASS(timer_header_parent_class)->finalize(self);
  169. }
  170. static void timer_header_class_init(TimerHeaderClass *class) {
  171. G_OBJECT_CLASS(class)->finalize = timer_header_finalize;
  172. }
  173. static void timer_header_init(TimerHeader *self) {
  174. }
  175. /* TimerTask class */
  176. static TimerHeader *timer_task_get_header(TimerTask *self) {
  177. TimerHeader *h;
  178. GtkTreeIter parent;
  179. gtk_tree_model_iter_parent(GTK_TREE_MODEL(self->store), &parent,
  180. &self->iter);
  181. gtk_tree_model_get(GTK_TREE_MODEL(self->store), &parent, DATA_COLUMN, &h,
  182. -1);
  183. g_object_unref(h);
  184. return h;
  185. }
  186. static void timer_task_update_time(TimerTask *self) {
  187. int h = floor(self->length / 3600.0f);
  188. int m = floor(self->length / 60.0f) - (h * 60);
  189. int s = self->length - (m * 60) - (h * 3600);
  190. char *total = g_strdup_printf("%02d:%02d:%02d", h, m, s);
  191. gtk_tree_store_set(self->store, &self->iter, TOTAL_TIME_COLUMN, total, -1);
  192. TimerHeader *th = timer_task_get_header(self);
  193. timer_header_update_time(th);
  194. g_free(total);
  195. }
  196. static JsonObject *timer_task_serialize(TimerTask *self) {
  197. JsonObject *root = json_object_new();
  198. json_object_set_string_member(root, "name", self->name);
  199. json_object_set_int_member(root, "length", self->length);
  200. json_object_set_int_member(root, "start", g_date_time_to_unix(self->start));
  201. return root;
  202. }
  203. static TimerTask *timer_task_new(const char *name, GDateTime *start,
  204. gint64 length, TimerHeader *header) {
  205. TimerTask *t = g_object_new(TIMER_TYPE_TASK, NULL);
  206. t->name = g_strdup(name);
  207. t->start = g_date_time_to_local(start);
  208. t->length = length;
  209. t->tree = header->tree;
  210. t->store = header->store;
  211. gtk_tree_store_append(t->store, &t->iter, &header->iter);
  212. gtk_tree_store_set(t->store, &t->iter, DATE_COLUMN, t->name, SORT_COLUMN,
  213. hash_time(t->start), DATA_COLUMN, t, -1);
  214. g_object_unref(t);
  215. timer_task_update_time(t);
  216. gtk_widget_show_all(GTK_WIDGET(t->tree));
  217. return t;
  218. }
  219. static void timer_task_edit(TimerTask *self) {
  220. TimerEditWindow *diag = timer_edit_window_new(
  221. self->name, self->start, self->length,
  222. (const char **)self->tree->taskNames, self->tree->taskNamesLen, FALSE, NULL);
  223. int resp = gtk_dialog_run(GTK_DIALOG(diag));
  224. if (resp == GTK_RESPONSE_APPLY) {
  225. GDateTime *newTime = timer_edit_window_get_start(diag);
  226. if (compare_date(self->start, newTime)) {
  227. g_free(self->name);
  228. self->name = timer_edit_window_get_name(diag);
  229. g_date_time_unref(self->start);
  230. self->start = newTime;
  231. gtk_tree_store_set(self->store, &self->iter, DATE_COLUMN,
  232. self->name, SORT_COLUMN, hash_time(newTime), -1);
  233. self->length = timer_edit_window_get_length(diag);
  234. timer_task_update_time(self);
  235. timer_task_tree_save(self->tree);
  236. } else {
  237. TimerHeader *h = timer_task_get_header(self);
  238. timer_header_remove_task(h, self);
  239. char *name = timer_edit_window_get_name(diag);
  240. timer_task_tree_add_task(h->tree, newTime, name,
  241. timer_edit_window_get_length(diag));
  242. g_free(name);
  243. g_date_time_unref(newTime);
  244. }
  245. } else if (resp == GTK_RESPONSE_REJECT) {
  246. TimerHeader *h = timer_task_get_header(self);
  247. timer_header_remove_task(h, self);
  248. }
  249. gtk_widget_destroy(GTK_WIDGET(diag));
  250. }
  251. static gint64 timer_task_get_length(TimerTask *self) {
  252. return self->length;
  253. }
  254. static void timer_task_finalize(GObject *self) {
  255. g_free(TIMER_TASK(self)->name);
  256. g_date_time_unref(TIMER_TASK(self)->start);
  257. G_OBJECT_CLASS(timer_task_parent_class)->finalize(self);
  258. }
  259. static void timer_task_class_init(TimerTaskClass *class) {
  260. G_OBJECT_CLASS(class)->finalize = timer_task_finalize;
  261. }
  262. static void timer_task_init(TimerTask *self) {
  263. }
  264. /* TimerTaskTree class */
  265. GtkWidget *timer_task_tree_new() {
  266. TimerTaskTree *t = g_object_new(TIMER_TYPE_TASK_TREE, NULL);
  267. t->dataPath = NULL;
  268. return GTK_WIDGET(t);
  269. }
  270. static void timer_task_tree_add_task_no_save(TimerTaskTree *self,
  271. GDateTime *date, const char *task,
  272. gint64 time) {
  273. TimerHeader *h;
  274. if (g_hash_table_contains(self->headers, date)) {
  275. h = g_hash_table_lookup(self->headers, date);
  276. } else {
  277. h = timer_header_new(date, self);
  278. g_hash_table_insert(self->headers, g_date_time_to_local(date), h);
  279. }
  280. timer_header_add_task(h, task, date, time);
  281. }
  282. void timer_task_tree_add_task(TimerTaskTree *self, GDateTime *date,
  283. const char *task, gint64 time) {
  284. timer_task_tree_add_task_no_save(self, date, task, time);
  285. timer_task_tree_save(self);
  286. }
  287. void timer_task_tree_set_task_names(TimerTaskTree *self, const char **names,
  288. gsize len) {
  289. gsize i;
  290. for (i = 0; i < self->taskNamesLen; ++i) {
  291. g_free(self->taskNames[i]);
  292. }
  293. g_free(self->taskNames);
  294. self->taskNames = g_malloc_n(len, sizeof(char *));
  295. for (i = 0; i < len; ++i) {
  296. self->taskNames[i] = g_strdup(names[i]);
  297. }
  298. self->taskNamesLen = len;
  299. }
  300. const char **timer_task_tree_get_task_names(TimerTaskTree *self, gsize *len) {
  301. *len = self->taskNamesLen;
  302. return (const char **)self->taskNames;
  303. }
  304. void timer_task_tree_update_header_dates(TimerTaskTree *self) {
  305. GHashTableIter iter;
  306. g_hash_table_iter_init(&iter, self->headers);
  307. TimerHeader *h = NULL;
  308. while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &h)) {
  309. char *ds = timer_header_get_date_string(h);
  310. gtk_tree_store_set(self->store, &h->iter, DATE_COLUMN, ds, -1);
  311. free(ds);
  312. }
  313. }
  314. static void timer_task_tree_update_data(TimerTaskTree *self) {
  315. GHashTableIter iter;
  316. g_hash_table_iter_init(&iter, self->headers);
  317. GList *headers = NULL;
  318. TimerHeader *h;
  319. while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&h)) {
  320. headers = g_list_append(headers, h);
  321. }
  322. GList *i;
  323. for (i = headers; i != NULL; i = i->next) {
  324. timer_header_remove(TIMER_HEADER(i->data));
  325. }
  326. g_list_free(headers);
  327. timer_task_tree_add_from_file(self, self->dataPath);
  328. }
  329. static gboolean do_async_update_data_file(TimerTaskTree *tree) {
  330. timer_task_tree_update_data(tree);
  331. timer_task_tree_expand_today(tree);
  332. return FALSE;
  333. }
  334. static void data_file_updated(TimerFileWatcher *fw, TimerTaskTree *tree) {
  335. g_main_context_invoke(NULL, G_SOURCE_FUNC(do_async_update_data_file), tree);
  336. }
  337. void timer_task_tree_set_data_path(TimerTaskTree *self, const char *path) {
  338. g_free(self->dataPath);
  339. self->dataPath = g_strdup(path);
  340. timer_task_tree_update_data(self);
  341. if (self->fileWatcher) {
  342. g_object_unref(self->fileWatcher);
  343. }
  344. self->fileWatcher = NULL;
  345. self->fileWatcher = timer_file_watcher_new(path);
  346. g_signal_connect(self->fileWatcher, "file-changed", G_CALLBACK(data_file_updated), self);
  347. }
  348. void timer_task_tree_expand_today(TimerTaskTree *self) {
  349. GDateTime *today = g_date_time_new_now_local();
  350. TimerHeader *h = g_hash_table_lookup(self->headers, today);
  351. g_date_time_unref(today);
  352. if (h != NULL) {
  353. GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(self->store), &h->iter);
  354. gtk_tree_view_expand_row(GTK_TREE_VIEW(self), path, TRUE);
  355. gtk_tree_path_free(path);
  356. }
  357. }
  358. void timer_task_tree_save(TimerTaskTree *self) {
  359. if (self->fileWatcher) {
  360. timer_file_watcher_pause(self->fileWatcher);
  361. }
  362. GHashTableIter iter;
  363. g_hash_table_iter_init(&iter, self->headers);
  364. TimerHeader *h;
  365. JsonArray *headers = json_array_new();
  366. while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&h)) {
  367. json_array_add_object_element(headers, timer_header_serialize(h));
  368. }
  369. JsonNode *root = json_node_new(JSON_NODE_ARRAY);
  370. json_node_set_array(root, headers);
  371. JsonGenerator *out = json_generator_new();
  372. json_generator_set_root(out, root);
  373. json_node_unref(root);
  374. if (self->dataPath != NULL) {
  375. GError *err = NULL;
  376. json_generator_to_file(out, self->dataPath, &err);
  377. if (err != NULL) {
  378. GtkWidget *diag = gtk_message_dialog_new(
  379. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
  380. "Could not save tasks: %s", err->message);
  381. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  382. gtk_dialog_run(GTK_DIALOG(diag));
  383. gtk_widget_destroy(diag);
  384. g_error_free(err);
  385. }
  386. }
  387. g_object_unref(out);
  388. if (self->fileWatcher) {
  389. timer_file_watcher_resume(self->fileWatcher, FALSE);
  390. }
  391. }
  392. static gboolean timer_task_tree_add_task_from_object(TimerTaskTree *self,
  393. JsonObject *obj,
  394. gboolean status) {
  395. JsonNode *nameNode = json_object_get_member(obj, "name");
  396. JsonNode *lengthNode = json_object_get_member(obj, "length");
  397. JsonNode *startNode = json_object_get_member(obj, "start");
  398. if (nameNode == NULL || lengthNode == NULL || startNode == NULL ||
  399. json_node_get_value_type(nameNode) != G_TYPE_STRING ||
  400. json_node_get_value_type(lengthNode) != G_TYPE_INT64 ||
  401. json_node_get_value_type(startNode) != G_TYPE_INT64) {
  402. if (status) {
  403. GtkWidget *diag = gtk_message_dialog_new(
  404. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
  405. "Corrupt task found! It and all future corrupt objects will be "
  406. "skipped. No furtur errors will be emmited for the remailder "
  407. "of this load.");
  408. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  409. gtk_dialog_run(GTK_DIALOG(diag));
  410. gtk_widget_destroy(diag);
  411. }
  412. return FALSE;
  413. }
  414. GDateTime *date =
  415. g_date_time_new_from_unix_local(json_node_get_int(startNode));
  416. timer_task_tree_add_task_no_save(self, date, json_node_get_string(nameNode),
  417. json_node_get_int(lengthNode));
  418. g_date_time_unref(date);
  419. return TRUE;
  420. }
  421. static gboolean timer_task_tree_add_header_from_object(TimerTaskTree *self,
  422. JsonObject *obj,
  423. gboolean status) {
  424. JsonNode *tasksNode = json_object_get_member(obj, "tasks");
  425. if (tasksNode == NULL || JSON_NODE_TYPE(tasksNode) != JSON_NODE_ARRAY) {
  426. if (status) {
  427. GtkWidget *diag = gtk_message_dialog_new(
  428. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
  429. "Corrupt header found! It and all future corrupt objects will "
  430. "be skipped. No furtur errors will be emmited for the "
  431. "remailder of this load.");
  432. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  433. gtk_dialog_run(GTK_DIALOG(diag));
  434. gtk_widget_destroy(diag);
  435. }
  436. return FALSE;
  437. }
  438. JsonArray *arr = json_node_get_array(tasksNode);
  439. GList *tasks = json_array_get_elements(arr);
  440. GList *i;
  441. for (i = tasks; i != NULL; i = i->next) {
  442. JsonNode *taskNode = (JsonNode *)i->data;
  443. if (JSON_NODE_TYPE(taskNode) != JSON_NODE_OBJECT) {
  444. if (status) {
  445. GtkWidget *diag = gtk_message_dialog_new(
  446. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
  447. "Corrupt header found! It and all future corrupt objects "
  448. "will be skipped. No furtur errors will be emmited for the "
  449. "remailder of this load.");
  450. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  451. gtk_dialog_run(GTK_DIALOG(diag));
  452. gtk_widget_destroy(diag);
  453. }
  454. status = FALSE;
  455. } else {
  456. if (!timer_task_tree_add_task_from_object(
  457. self, json_node_get_object(taskNode), status)) {
  458. status = FALSE;
  459. }
  460. }
  461. }
  462. g_list_free(tasks);
  463. return status;
  464. }
  465. void timer_task_tree_add_from_file(TimerTaskTree *self, const char *path) {
  466. JsonParser *in = json_parser_new_immutable();
  467. GError *err = NULL;
  468. json_parser_load_from_file(in, path, &err);
  469. if (err != NULL) {
  470. GtkWidget *diag = gtk_message_dialog_new(
  471. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
  472. "Could not loads tasks: %s", err->message);
  473. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  474. gtk_dialog_run(GTK_DIALOG(diag));
  475. gtk_widget_destroy(diag);
  476. g_error_free(err);
  477. } else {
  478. JsonNode *root = json_parser_get_root(in);
  479. if (JSON_NODE_TYPE(root) != JSON_NODE_ARRAY) {
  480. GtkWidget *diag = gtk_message_dialog_new(
  481. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
  482. "Task file corrupt! Root was not an array!");
  483. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  484. gtk_dialog_run(GTK_DIALOG(diag));
  485. gtk_widget_destroy(diag);
  486. } else {
  487. gboolean status = TRUE;
  488. GList *headers =
  489. json_array_get_elements(json_node_get_array((root)));
  490. GList *i;
  491. for (i = headers; i != NULL; i = i->next) {
  492. JsonNode *hNode = (JsonNode *)i->data;
  493. if (JSON_NODE_TYPE(hNode) != JSON_NODE_OBJECT) {
  494. if (status) {
  495. GtkWidget *diag = gtk_message_dialog_new(
  496. NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
  497. GTK_BUTTONS_OK,
  498. "Corrupt header found! It and all future corrupt "
  499. "objects will be skipped. No furtur errors will be "
  500. "emmited for the remailder of this load.");
  501. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  502. gtk_dialog_run(GTK_DIALOG(diag));
  503. gtk_widget_destroy(diag);
  504. }
  505. status = FALSE;
  506. } else {
  507. if (!timer_task_tree_add_header_from_object(
  508. self, json_node_get_object(hNode), status)) {
  509. status = FALSE;
  510. }
  511. }
  512. }
  513. g_list_free(headers);
  514. }
  515. }
  516. g_object_unref(in);
  517. }
  518. char *timer_task_tree_get_csv(TimerTaskTree *self) {
  519. GString *string = g_string_new("name,start,length\n");
  520. GHashTableIter hIter;
  521. g_hash_table_iter_init(&hIter, self->headers);
  522. TimerHeader *header;
  523. while (g_hash_table_iter_next(&hIter, NULL, (gpointer *)&header)) {
  524. GtkTreeIter child;
  525. if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &child,
  526. &header->iter)) {
  527. TimerTask *task;
  528. do {
  529. gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child,
  530. DATA_COLUMN, &task, -1);
  531. g_object_unref(task);
  532. g_string_append_printf(string, "%s,%ld,%ld\n", task->name,
  533. g_date_time_to_unix(task->start),
  534. task->length);
  535. } while (
  536. gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &child));
  537. }
  538. }
  539. return g_string_free(string, FALSE);
  540. }
  541. TimerDataPoint *timer_task_tree_get_day_data(TimerTaskTree *self, gsize *length) {
  542. *length = 0;
  543. TimerDataPoint *arr = g_malloc(1);
  544. GHashTableIter hIter;
  545. g_hash_table_iter_init(&hIter, self->headers);
  546. TimerHeader *header;
  547. while (g_hash_table_iter_next(&hIter, NULL, (gpointer *)&header)) {
  548. arr = g_realloc(arr, sizeof(TimerDataPoint) * ++(*length));
  549. arr[*length - 1] = (TimerDataPoint){
  550. g_date_time_to_local(header->date), timer_header_get_total_time(header)};
  551. }
  552. return arr;
  553. }
  554. TimerDataPoint *timer_task_tree_get_task_data(TimerTaskTree *self, gsize *length) {
  555. *length = 0;
  556. TimerDataPoint *arr = g_malloc(1);
  557. GHashTableIter hIter;
  558. g_hash_table_iter_init(&hIter, self->headers);
  559. TimerHeader *header;
  560. while (g_hash_table_iter_next(&hIter, NULL, (gpointer *)&header)) {
  561. GtkTreeIter child;
  562. if (gtk_tree_model_iter_children(GTK_TREE_MODEL(self->store), &child,
  563. &header->iter)) {
  564. TimerTask *task;
  565. do {
  566. gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child,
  567. DATA_COLUMN, &task, -1);
  568. g_object_unref(task);
  569. arr = g_realloc(arr, sizeof(TimerDataPoint) * ++(*length));
  570. arr[*length - 1] = (TimerDataPoint){
  571. g_date_time_to_local(task->start), task->length};
  572. } while (
  573. gtk_tree_model_iter_next(GTK_TREE_MODEL(self->store), &child));
  574. }
  575. }
  576. return arr;
  577. }
  578. void timer_free_task_data(TimerDataPoint *data, gsize length) {
  579. gsize i;
  580. for (i = 0; i < length; ++i) {
  581. g_date_time_unref(data[i].date);
  582. }
  583. g_free(data);
  584. }
  585. GDateTime *timer_task_tree_get_last_task_end(TimerTaskTree *self) {
  586. GtkTreeIter headerIter;
  587. if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(self->store), &headerIter)) {
  588. return NULL;
  589. }
  590. GtkTreeIter child;
  591. if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(self->store), &child, &headerIter, 0)) {
  592. return NULL;
  593. }
  594. TimerTask *task;
  595. gtk_tree_model_get(GTK_TREE_MODEL(self->store), &child, DATA_COLUMN, &task, -1);
  596. g_object_unref(task);
  597. GDateTime *dt = g_date_time_add_seconds(task->start, task->length);
  598. return dt;
  599. }
  600. static gint tree_sort_compare_func(GtkTreeModel *model, GtkTreeIter *i1,
  601. GtkTreeIter *i2) {
  602. GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
  603. gtk_tree_model_get_value(model, i1, SORT_COLUMN, &v1);
  604. gtk_tree_model_get_value(model, i2, SORT_COLUMN, &v2);
  605. return g_value_get_uint64(&v1) - g_value_get_uint64(&v2);
  606. }
  607. static gboolean mouse_click_callback(TimerTaskTree *tree,
  608. GdkEventButton *event) {
  609. if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
  610. GtkTreeSelection *selection =
  611. gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  612. GtkTreePath *path;
  613. if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), event->x,
  614. event->y, &path, NULL, NULL, NULL)) {
  615. gtk_tree_selection_unselect_all(selection);
  616. gtk_tree_selection_select_path(selection, path);
  617. gtk_tree_path_free(path);
  618. }
  619. GtkTreeIter currentIter, parentIter;
  620. if (gtk_tree_selection_get_selected(selection, NULL, &currentIter)) {
  621. if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store),
  622. &parentIter, &currentIter)) {
  623. gtk_widget_set_visible(tree->editButton, TRUE);
  624. } else {
  625. gtk_widget_set_visible(tree->editButton, FALSE);
  626. }
  627. gtk_menu_popup_at_pointer(GTK_MENU(tree->popup), (GdkEvent *)event);
  628. }
  629. return TRUE;
  630. } else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
  631. GtkTreeSelection *selection =
  632. gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  633. GtkTreePath *path;
  634. if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), event->x,
  635. event->y, &path, NULL, NULL, NULL)) {
  636. gtk_tree_selection_unselect_all(selection);
  637. gtk_tree_selection_select_path(selection, path);
  638. gtk_tree_path_free(path);
  639. }
  640. GtkTreeIter select;
  641. if (gtk_tree_selection_get_selected(selection, NULL, &select)) {
  642. GObject *obj;
  643. gtk_tree_model_get(GTK_TREE_MODEL(tree->store), &select,
  644. DATA_COLUMN, &obj, -1);
  645. g_object_unref(obj);
  646. if (TIMER_IS_TASK(obj)) {
  647. timer_task_edit(TIMER_TASK(obj));
  648. timer_task_tree_save(tree);
  649. } else if (TIMER_IS_HEADER(obj)) {
  650. GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree->store), &TIMER_HEADER(obj)->iter);
  651. if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tree), path)) {
  652. gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), path);
  653. } else {
  654. gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, TRUE);
  655. }
  656. gtk_tree_path_free(path);
  657. }
  658. }
  659. return TRUE;
  660. }
  661. return FALSE;
  662. }
  663. static void popup_edit_button_callback(GtkMenuItem *btn, TimerTaskTree *tree) {
  664. GtkTreeSelection *selection =
  665. gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  666. GtkTreeIter select;
  667. if (gtk_tree_selection_get_selected(selection, NULL, &select)) {
  668. TimerTask *task;
  669. gtk_tree_model_get(GTK_TREE_MODEL(tree->store), &select, DATA_COLUMN,
  670. &task, -1);
  671. g_object_unref(task);
  672. timer_task_edit(task);
  673. timer_task_tree_save(tree);
  674. }
  675. }
  676. static void popup_delete_button_callback(GtkMenuItem *btn,
  677. TimerTaskTree *tree) {
  678. GtkTreeSelection *selection =
  679. gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
  680. GtkTreeIter select;
  681. if (gtk_tree_selection_get_selected(selection, NULL, &select)) {
  682. GObject *obj;
  683. gtk_tree_model_get(GTK_TREE_MODEL(tree->store), &select, DATA_COLUMN,
  684. &obj, -1);
  685. g_object_unref(obj);
  686. if (TIMER_IS_TASK(obj)) {
  687. timer_header_remove_task(timer_task_get_header(TIMER_TASK(obj)),
  688. TIMER_TASK(obj));
  689. } else {
  690. 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?");
  691. gtk_window_set_position(GTK_WINDOW(diag), GTK_WIN_POS_MOUSE);
  692. int resp = gtk_dialog_run(GTK_DIALOG(diag));
  693. if (resp == GTK_RESPONSE_YES) {
  694. timer_header_remove(TIMER_HEADER(obj));
  695. }
  696. gtk_widget_destroy(diag);
  697. }
  698. timer_task_tree_save(tree);
  699. }
  700. }
  701. static void timer_task_tree_finalize(GObject *self) {
  702. gsize i;
  703. for (i = 0; i < TIMER_TASK_TREE(self)->taskNamesLen; ++i) {
  704. g_free(TIMER_TASK_TREE(self)->taskNames[i]);
  705. }
  706. g_free(TIMER_TASK_TREE(self)->dataPath);
  707. g_free(TIMER_TASK_TREE(self)->taskNames);
  708. gtk_widget_destroy(TIMER_TASK_TREE(self)->popup);
  709. g_object_unref(TIMER_TASK_TREE(self)->store);
  710. g_hash_table_destroy(TIMER_TASK_TREE(self)->headers);
  711. G_OBJECT_CLASS(timer_task_tree_parent_class)->finalize(self);
  712. }
  713. static void timer_task_tree_class_init(TimerTaskTreeClass *class) {
  714. G_OBJECT_CLASS(class)->finalize = timer_task_tree_finalize;
  715. }
  716. static void timer_task_tree_init(TimerTaskTree *self) {
  717. self->store = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
  718. G_TYPE_UINT64, G_TYPE_OBJECT);
  719. gtk_tree_view_set_model(GTK_TREE_VIEW(self), GTK_TREE_MODEL(self->store));
  720. self->headers =
  721. g_hash_table_new_full((GHashFunc)hash_date, (GCompareFunc)compare_date,
  722. (GDestroyNotify)g_date_time_unref, NULL);
  723. GtkCellRenderer *render = gtk_cell_renderer_text_new();
  724. GValue font = G_VALUE_INIT;
  725. g_value_init(&font, G_TYPE_STRING);
  726. g_value_set_static_string(&font, "16");
  727. g_object_set_property(G_OBJECT(render), "font", &font);
  728. g_value_unset(&font);
  729. gtk_tree_view_append_column(
  730. GTK_TREE_VIEW(self),
  731. gtk_tree_view_column_new_with_attributes(
  732. "Date", render, "text", DATE_COLUMN, NULL));
  733. gtk_tree_view_append_column(GTK_TREE_VIEW(self),
  734. gtk_tree_view_column_new_with_attributes(
  735. "Time", render,
  736. "text", TOTAL_TIME_COLUMN, NULL));
  737. GtkTreeViewColumn *sort = gtk_tree_view_column_new_with_attributes(
  738. "Sort", render, NULL);
  739. gtk_tree_view_column_set_visible(sort, FALSE);
  740. gtk_tree_view_append_column(GTK_TREE_VIEW(self), sort);
  741. gtk_tree_sortable_set_default_sort_func(
  742. GTK_TREE_SORTABLE(self->store),
  743. (GtkTreeIterCompareFunc)tree_sort_compare_func, NULL, NULL);
  744. gtk_tree_sortable_set_sort_column_id(
  745. GTK_TREE_SORTABLE(self->store),
  746. GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_DESCENDING);
  747. gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);
  748. self->popup = gtk_menu_new();
  749. self->deleteButton = gtk_menu_item_new_with_label("Delete");
  750. self->editButton = gtk_menu_item_new_with_label("Edit");
  751. gtk_menu_shell_append(GTK_MENU_SHELL(self->popup), self->deleteButton);
  752. gtk_menu_shell_append(GTK_MENU_SHELL(self->popup), self->editButton);
  753. gtk_widget_show_all(self->popup);
  754. g_signal_connect(self, "button_press_event",
  755. G_CALLBACK(mouse_click_callback), NULL);
  756. g_signal_connect(self->deleteButton, "activate",
  757. G_CALLBACK(popup_delete_button_callback), self);
  758. g_signal_connect(self->editButton, "activate",
  759. G_CALLBACK(popup_edit_button_callback), self);
  760. }