TimerGraphWindow.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. #include "TimerGraphWindow.h"
  2. #include "TimerTaskTree.h"
  3. #include "TimerGraph.h"
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. #include <math.h>
  7. enum {
  8. DATA_COLUMN = 0,
  9. SEVEN_DAY_COLUMN,
  10. THIRTY_DAY_COLUMN,
  11. SIX_MONTH_COLUMN,
  12. YEAR_COLUMN,
  13. ALL_TIME_COLUMN,
  14. N_COLUMNS
  15. };
  16. typedef struct {
  17. gint64 max;
  18. gint64 min;
  19. gint64 avg;
  20. } TimerStatSet;
  21. struct _TimerGraphWindow {
  22. GtkApplicationWindow parent;
  23. GtkWidget *closeButton;
  24. GtkWidget *chartBox;
  25. GtkWidget *dayChart;
  26. GtkWidget *monthChart;
  27. GtkWidget *table;
  28. GtkTreeStore *tableStore;
  29. TimerDataPoint *dayData;
  30. gsize dayDataLen;
  31. TimerDataPoint *taskData;
  32. gsize taskDataLen;
  33. };
  34. G_DEFINE_TYPE(TimerGraphWindow, timer_graph_window, GTK_TYPE_DIALOG);
  35. static gboolean came_after_or_same_day(GDateTime *start, GDateTime *date) {
  36. int y1, m1, d1, y2, m2, d2;
  37. g_date_time_get_ymd(start, &y1, &m1, &d1);
  38. g_date_time_get_ymd(date, &y2, &m2, &d2);
  39. return (y1 < y2) || (y1 == y2 && m1 < m2) ||
  40. (y1 == y2 && m1 == m2 && d1 <= d2);
  41. }
  42. static gint day_offset(GDateTime *start, GDateTime *end) {
  43. gint64 su = g_date_time_to_unix(start);
  44. gint64 eu = g_date_time_to_unix(end);
  45. return round((float) (eu - su) / (60 * 60 * 24));
  46. }
  47. static TimerStatSet timer_graph_window_get_stats_for_days(TimerGraphWindow *self, gsize days) {
  48. GDateTime *ref = NULL;
  49. if (days != 0) {
  50. GDateTime *now = g_date_time_new_now_local();
  51. ref = g_date_time_add_days(now, -days);
  52. g_date_time_unref(now);
  53. } else {
  54. days = self->dayDataLen;
  55. }
  56. gint64 total = 0, max = 0, min = G_MAXINT64;
  57. gsize i;
  58. for (i = 0; i < self->dayDataLen; ++i) {
  59. if (ref == NULL || came_after_or_same_day(ref, self->dayData[i].date)) {
  60. total += self->dayData[i].lenght;
  61. if (self->dayData[i].lenght > max) {
  62. max = self->dayData[i].lenght;
  63. }
  64. if (self->dayData[i].lenght < min) {
  65. min = self->dayData[i].lenght;
  66. }
  67. }
  68. }
  69. if (ref != NULL) {
  70. g_date_time_unref(ref);
  71. }
  72. if (min == G_MAXINT64) {
  73. min = 0;
  74. }
  75. int avg = 0;
  76. if (days != 0) {
  77. avg = floor((float)total / (float)days);
  78. }
  79. return (TimerStatSet){max, min, avg};
  80. }
  81. static char *format_hours_and_min(gint64 time) {
  82. int hour = floor(time / 3600.0f);
  83. int min = floor(time / 60.0f) - (hour * 60);
  84. return g_strdup_printf("%02d:%02d", hour, min);
  85. }
  86. static void timer_graph_window_add_table_data(TimerGraphWindow *self) {
  87. TimerStatSet d7 = timer_graph_window_get_stats_for_days(self, 7);
  88. TimerStatSet d30 = timer_graph_window_get_stats_for_days(self, 30);
  89. TimerStatSet d183 = timer_graph_window_get_stats_for_days(self, 183);
  90. TimerStatSet d365 = timer_graph_window_get_stats_for_days(self, 365);
  91. TimerStatSet da = timer_graph_window_get_stats_for_days(self, 0);
  92. GtkTreeIter highIter;
  93. gtk_tree_store_append(self->tableStore, &highIter, NULL);
  94. char *d7ms = format_hours_and_min(d7.max);
  95. char *d30ms = format_hours_and_min(d30.max);
  96. char *d183ms = format_hours_and_min(d183.max);
  97. char *d365ms = format_hours_and_min(d365.max);
  98. char *dams = format_hours_and_min(da.max);
  99. gtk_tree_store_set(self->tableStore, &highIter, DATA_COLUMN, "High",
  100. SEVEN_DAY_COLUMN, d7ms, THIRTY_DAY_COLUMN, d30ms,
  101. SIX_MONTH_COLUMN, d183ms, YEAR_COLUMN, d365ms,
  102. ALL_TIME_COLUMN, dams, -1);
  103. g_free(d7ms);
  104. g_free(d30ms);
  105. g_free(d183ms);
  106. g_free(d365ms);
  107. g_free(dams);
  108. /* Dad requested I remove this
  109. GtkTreeIter lowIter;
  110. gtk_tree_store_append(self->tableStore, &lowIter, NULL);
  111. char *d7ls = format_hours_and_min(d7.min);
  112. char *d30ls = format_hours_and_min(d30.min);
  113. char *d183ls = format_hours_and_min(d183.min);
  114. char *d365ls = format_hours_and_min(d365.min);
  115. char *dals = format_hours_and_min(da.min);
  116. gtk_tree_store_set(self->tableStore, &lowIter, DATA_COLUMN, "Low",
  117. SEVEN_DAY_COLUMN, d7ls, THIRTY_DAY_COLUMN, d30ls,
  118. SIX_MONTH_COLUMN, d183ls, YEAR_COLUMN, d365ls,
  119. ALL_TIME_COLUMN, dals, -1);
  120. g_free(d7ls);
  121. g_free(d30ls);
  122. g_free(d183ls);
  123. g_free(d365ls);
  124. g_free(dals); */
  125. GtkTreeIter avgIter;
  126. gtk_tree_store_append(self->tableStore, &avgIter, NULL);
  127. char *d7as = format_hours_and_min(d7.avg);
  128. char *d30as = format_hours_and_min(d30.avg);
  129. char *d183as = format_hours_and_min(d183.avg);
  130. char *d365as = format_hours_and_min(d365.avg);
  131. char *daas = format_hours_and_min(da.avg);
  132. gtk_tree_store_set(self->tableStore, &avgIter, DATA_COLUMN, "Average",
  133. SEVEN_DAY_COLUMN, d7as, THIRTY_DAY_COLUMN, d30as,
  134. SIX_MONTH_COLUMN, d183as, YEAR_COLUMN, d365as,
  135. ALL_TIME_COLUMN, daas, -1);
  136. g_free(d7as);
  137. g_free(d30as);
  138. g_free(d183as);
  139. g_free(d365as);
  140. g_free(daas);
  141. }
  142. static TimerGraphPoint *timer_graph_window_get_day_graph_data(TimerGraphWindow *self) {
  143. TimerGraphPoint *pts = g_malloc_n(14, sizeof(TimerGraphPoint));
  144. gsize i;
  145. for (i = 0; i < 14; ++i) {
  146. pts[i].x = i + 1;
  147. pts[i].y = 0;
  148. }
  149. GDateTime *now = g_date_time_new_now_local();
  150. for (i = 0; i < self->dayDataLen; ++i) {
  151. int off = day_offset(self->dayData[i].date, now);
  152. if (off <= 14) {
  153. pts[off - 1].y += (int) (self->dayData[i].lenght / 60);
  154. }
  155. }
  156. g_date_time_unref(now);
  157. return pts;
  158. }
  159. static void timer_graph_window_make_day_graph(TimerGraphWindow *self) {
  160. TimerGraphPoint *pts = timer_graph_window_get_day_graph_data(self);
  161. self->dayChart = timer_graph_new(pts, 14, "Day", "Time (Total Minutes)", "Last 14 Days");
  162. gtk_widget_set_vexpand(self->dayChart, TRUE);
  163. gtk_widget_set_hexpand(self->dayChart, TRUE);
  164. g_free(pts);
  165. gtk_box_pack_start(GTK_BOX(self->chartBox), self->dayChart, TRUE, TRUE, 0);
  166. }
  167. static gboolean same_month(GDateTime *d1, GDateTime *d2) {
  168. int y1, m1, y2, m2;
  169. g_date_time_get_ymd(d1, &y1, &m1, NULL);
  170. g_date_time_get_ymd(d2, &y2, &m2, NULL);
  171. return y1 == y2 && m1 == m2;
  172. }
  173. static int get_days_for_month(int month, int year) {
  174. switch (month) {
  175. case 1:
  176. case 3:
  177. case 5:
  178. case 7:
  179. case 8:
  180. case 10:
  181. case 12:
  182. return 31;
  183. case 4:
  184. case 6:
  185. case 9:
  186. case 11:
  187. return 30;
  188. case 2:
  189. if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
  190. return 29;
  191. } else {
  192. return 28;
  193. }
  194. default:
  195. return 0;
  196. }
  197. }
  198. static TimerGraphPoint *timer_graph_window_get_month_graph_data(TimerGraphWindow *self) {
  199. TimerGraphPoint *pts = g_malloc_n(12, sizeof(TimerGraphPoint));
  200. GDateTime *now = g_date_time_new_now_local();
  201. GDateTime *ref = g_date_time_add_months(now, -1);
  202. g_date_time_unref(now);
  203. gsize i;
  204. for (i = 0; i < 12; ++i) {
  205. pts[i].x = i + 1;
  206. int total = 0;
  207. gsize j;
  208. for (j = 0; j < self->dayDataLen; ++j) {
  209. if (same_month(self->dayData[j].date, ref)) {
  210. total += (int) (self->dayData[j].lenght / 60);
  211. }
  212. }
  213. int y, m;
  214. g_date_time_get_ymd(ref, &y, &m, NULL);
  215. pts[i].y = total / get_days_for_month(m, y);
  216. GDateTime *new = g_date_time_add_months(ref, -1);
  217. g_date_time_unref(ref);
  218. ref = new;
  219. }
  220. g_date_time_unref(ref);
  221. return pts;
  222. }
  223. static void timer_graph_window_make_month_graph(TimerGraphWindow *self) {
  224. TimerGraphPoint *pts = timer_graph_window_get_month_graph_data(self);
  225. self->monthChart = timer_graph_new(pts, 12, "Month", "Time (Average Minutes)", "Last Year");
  226. gtk_widget_set_vexpand(self->monthChart, TRUE);
  227. gtk_widget_set_hexpand(self->monthChart, TRUE);
  228. g_free(pts);
  229. gtk_box_pack_start(GTK_BOX(self->chartBox), self->monthChart, TRUE, TRUE, 0);
  230. }
  231. TimerGraphWindow *timer_graph_window_new(TimerApplication *app,
  232. TimerDataPoint *dayData, gsize dayDataLen, TimerDataPoint *taskData, gsize taskDataLen) {
  233. TimerGraphWindow *w =
  234. g_object_new(TIMER_TYPE_GRAPH_WINDOW, "application", app, "title", "Statistics", NULL);
  235. w->dayData = dayData;
  236. w->taskData = taskData;
  237. w->dayDataLen = dayDataLen;
  238. w->taskDataLen = taskDataLen;
  239. timer_graph_window_add_table_data(w);
  240. timer_graph_window_make_day_graph(w);
  241. timer_graph_window_make_month_graph(w);
  242. GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(w));
  243. GdkDisplay *display = gdk_screen_get_display(screen);
  244. GdkMonitor *monitor = gdk_display_get_primary_monitor(display);
  245. if (monitor) {
  246. GdkRectangle monSize;
  247. gdk_monitor_get_geometry(monitor, &monSize);
  248. gtk_window_set_default_size(GTK_WINDOW(w), monSize.width * 0.75f, monSize.height * 0.75f);
  249. }
  250. gtk_widget_show_all(GTK_WIDGET(w));
  251. return w;
  252. }
  253. static void close_button_callback(GtkButton *btn, TimerGraphWindow *win) {
  254. gtk_window_close(GTK_WINDOW(win));
  255. }
  256. static void timer_graph_window_build_ui(TimerGraphWindow *self) {
  257. self->closeButton = gtk_dialog_add_button(GTK_DIALOG(self), "Close", 0);
  258. GtkWidget *topBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  259. self->table = gtk_tree_view_new();
  260. gtk_widget_set_halign(self->table, GTK_ALIGN_CENTER);
  261. gtk_box_pack_start(GTK_BOX(topBox), self->table, FALSE, FALSE, 0);
  262. self->chartBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  263. gtk_container_add(GTK_CONTAINER(topBox), self->chartBox);
  264. gtk_container_add(
  265. GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(self))), topBox);
  266. }
  267. static void timer_graph_window_add_tree_columns(TimerGraphWindow *self) {
  268. gtk_tree_view_append_column(
  269. GTK_TREE_VIEW(self->table),
  270. gtk_tree_view_column_new_with_attributes(
  271. "", gtk_cell_renderer_text_new(), "text", DATA_COLUMN, NULL));
  272. gtk_tree_view_append_column(GTK_TREE_VIEW(self->table),
  273. gtk_tree_view_column_new_with_attributes(
  274. "Last 7 Days", gtk_cell_renderer_text_new(),
  275. "text", SEVEN_DAY_COLUMN, NULL));
  276. gtk_tree_view_append_column(GTK_TREE_VIEW(self->table),
  277. gtk_tree_view_column_new_with_attributes(
  278. "Last 30 Days",
  279. gtk_cell_renderer_text_new(), "text",
  280. THIRTY_DAY_COLUMN, NULL));
  281. gtk_tree_view_append_column(GTK_TREE_VIEW(self->table),
  282. gtk_tree_view_column_new_with_attributes(
  283. "Last 6 Months",
  284. gtk_cell_renderer_text_new(), "text",
  285. SIX_MONTH_COLUMN, NULL));
  286. gtk_tree_view_append_column(GTK_TREE_VIEW(self->table),
  287. gtk_tree_view_column_new_with_attributes(
  288. "Last Year", gtk_cell_renderer_text_new(),
  289. "text", YEAR_COLUMN, NULL));
  290. gtk_tree_view_append_column(GTK_TREE_VIEW(self->table),
  291. gtk_tree_view_column_new_with_attributes(
  292. "All Time", gtk_cell_renderer_text_new(),
  293. "text", ALL_TIME_COLUMN, NULL));
  294. }
  295. static void timer_graph_window_finalize(GObject *self) {
  296. timer_free_task_data(TIMER_GRAPH_WINDOW(self)->dayData, TIMER_GRAPH_WINDOW(self)->dayDataLen);
  297. timer_free_task_data(TIMER_GRAPH_WINDOW(self)->taskData, TIMER_GRAPH_WINDOW(self)->taskDataLen);
  298. G_OBJECT_CLASS(timer_graph_window_parent_class)->finalize(self);
  299. }
  300. static void timer_graph_window_class_init(TimerGraphWindowClass *class) {
  301. G_OBJECT_CLASS(class)->finalize = timer_graph_window_finalize;
  302. }
  303. static void timer_graph_window_init(TimerGraphWindow *self) {
  304. gtk_window_set_keep_above(GTK_WINDOW(self), TRUE);
  305. gtk_window_set_position(GTK_WINDOW(self), GTK_WIN_POS_MOUSE);
  306. timer_graph_window_build_ui(self);
  307. g_signal_connect(self->closeButton, "clicked",
  308. G_CALLBACK(close_button_callback), self);
  309. gtk_tree_selection_set_mode(
  310. gtk_tree_view_get_selection(GTK_TREE_VIEW(self->table)),
  311. GTK_SELECTION_NONE);
  312. self->tableStore = gtk_tree_store_new(
  313. N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
  314. G_TYPE_STRING, G_TYPE_STRING);
  315. gtk_tree_view_set_model(GTK_TREE_VIEW(self->table),
  316. GTK_TREE_MODEL(self->tableStore));
  317. timer_graph_window_add_tree_columns(self);
  318. }