#include "TimerGraphWindow.h" #include "TimerTaskTree.h" #include "TimerGraph.h" #include #include #include enum { DATA_COLUMN = 0, SEVEN_DAY_COLUMN, THIRTY_DAY_COLUMN, SIX_MONTH_COLUMN, YEAR_COLUMN, ALL_TIME_COLUMN, N_COLUMNS }; typedef struct { gint64 max; gint64 min; gint64 avg; } TimerStatSet; struct _TimerGraphWindow { GtkApplicationWindow parent; GtkWidget *closeButton; GtkWidget *chartBox; GtkWidget *dayChart; GtkWidget *monthChart; GtkWidget *table; GtkTreeStore *tableStore; TimerDataPoint *dayData; gsize dayDataLen; TimerDataPoint *taskData; gsize taskDataLen; }; G_DEFINE_TYPE(TimerGraphWindow, timer_graph_window, GTK_TYPE_DIALOG); static gboolean came_after_or_same_day(GDateTime *start, GDateTime *date) { int y1, m1, d1, y2, m2, d2; g_date_time_get_ymd(start, &y1, &m1, &d1); g_date_time_get_ymd(date, &y2, &m2, &d2); return (y1 < y2) || (y1 == y2 && m1 < m2) || (y1 == y2 && m1 == m2 && d1 <= d2); } static gint day_offset(GDateTime *start, GDateTime *end) { gint64 su = g_date_time_to_unix(start); gint64 eu = g_date_time_to_unix(end); return round((float) (eu - su) / (60 * 60 * 24)); } static TimerStatSet timer_graph_window_get_stats_for_days(TimerGraphWindow *self, gsize days) { GDateTime *ref = NULL; if (days != 0) { GDateTime *now = g_date_time_new_now_local(); ref = g_date_time_add_days(now, -days); g_date_time_unref(now); } else { days = self->dayDataLen; } gint64 total = 0, max = 0, min = G_MAXINT64; gsize i; for (i = 0; i < self->dayDataLen; ++i) { if (ref == NULL || came_after_or_same_day(ref, self->dayData[i].date)) { total += self->dayData[i].lenght; if (self->dayData[i].lenght > max) { max = self->dayData[i].lenght; } if (self->dayData[i].lenght < min) { min = self->dayData[i].lenght; } } } if (ref != NULL) { g_date_time_unref(ref); } if (min == G_MAXINT64) { min = 0; } int avg = 0; if (days != 0) { avg = floor((float)total / (float)days); } return (TimerStatSet){max, min, avg}; } static char *format_hours_and_min(gint64 time) { int hour = floor(time / 3600.0f); int min = floor(time / 60.0f) - (hour * 60); return g_strdup_printf("%02d:%02d", hour, min); } static void timer_graph_window_add_table_data(TimerGraphWindow *self) { TimerStatSet d7 = timer_graph_window_get_stats_for_days(self, 7); TimerStatSet d30 = timer_graph_window_get_stats_for_days(self, 30); TimerStatSet d183 = timer_graph_window_get_stats_for_days(self, 183); TimerStatSet d365 = timer_graph_window_get_stats_for_days(self, 365); TimerStatSet da = timer_graph_window_get_stats_for_days(self, 0); GtkTreeIter highIter; gtk_tree_store_append(self->tableStore, &highIter, NULL); char *d7ms = format_hours_and_min(d7.max); char *d30ms = format_hours_and_min(d30.max); char *d183ms = format_hours_and_min(d183.max); char *d365ms = format_hours_and_min(d365.max); char *dams = format_hours_and_min(da.max); gtk_tree_store_set(self->tableStore, &highIter, DATA_COLUMN, "High", SEVEN_DAY_COLUMN, d7ms, THIRTY_DAY_COLUMN, d30ms, SIX_MONTH_COLUMN, d183ms, YEAR_COLUMN, d365ms, ALL_TIME_COLUMN, dams, -1); g_free(d7ms); g_free(d30ms); g_free(d183ms); g_free(d365ms); g_free(dams); /* Dad requested I remove this GtkTreeIter lowIter; gtk_tree_store_append(self->tableStore, &lowIter, NULL); char *d7ls = format_hours_and_min(d7.min); char *d30ls = format_hours_and_min(d30.min); char *d183ls = format_hours_and_min(d183.min); char *d365ls = format_hours_and_min(d365.min); char *dals = format_hours_and_min(da.min); gtk_tree_store_set(self->tableStore, &lowIter, DATA_COLUMN, "Low", SEVEN_DAY_COLUMN, d7ls, THIRTY_DAY_COLUMN, d30ls, SIX_MONTH_COLUMN, d183ls, YEAR_COLUMN, d365ls, ALL_TIME_COLUMN, dals, -1); g_free(d7ls); g_free(d30ls); g_free(d183ls); g_free(d365ls); g_free(dals); */ GtkTreeIter avgIter; gtk_tree_store_append(self->tableStore, &avgIter, NULL); char *d7as = format_hours_and_min(d7.avg); char *d30as = format_hours_and_min(d30.avg); char *d183as = format_hours_and_min(d183.avg); char *d365as = format_hours_and_min(d365.avg); char *daas = format_hours_and_min(da.avg); gtk_tree_store_set(self->tableStore, &avgIter, DATA_COLUMN, "Average", SEVEN_DAY_COLUMN, d7as, THIRTY_DAY_COLUMN, d30as, SIX_MONTH_COLUMN, d183as, YEAR_COLUMN, d365as, ALL_TIME_COLUMN, daas, -1); g_free(d7as); g_free(d30as); g_free(d183as); g_free(d365as); g_free(daas); } static TimerGraphPoint *timer_graph_window_get_day_graph_data(TimerGraphWindow *self) { TimerGraphPoint *pts = g_malloc_n(14, sizeof(TimerGraphPoint)); gsize i; for (i = 0; i < 14; ++i) { pts[i].x = i + 1; pts[i].y = 0; } GDateTime *now = g_date_time_new_now_local(); for (i = 0; i < self->dayDataLen; ++i) { int off = day_offset(self->dayData[i].date, now); if (off <= 14) { pts[off - 1].y += (int) (self->dayData[i].lenght / 60); } } g_date_time_unref(now); return pts; } static void timer_graph_window_make_day_graph(TimerGraphWindow *self) { TimerGraphPoint *pts = timer_graph_window_get_day_graph_data(self); self->dayChart = timer_graph_new(pts, 14, "Day", "Time (Total Minutes)", "Last 14 Days"); gtk_widget_set_vexpand(self->dayChart, TRUE); gtk_widget_set_hexpand(self->dayChart, TRUE); g_free(pts); gtk_box_pack_start(GTK_BOX(self->chartBox), self->dayChart, TRUE, TRUE, 0); } static gboolean same_month(GDateTime *d1, GDateTime *d2) { int y1, m1, y2, m2; g_date_time_get_ymd(d1, &y1, &m1, NULL); g_date_time_get_ymd(d2, &y2, &m2, NULL); return y1 == y2 && m1 == m2; } static int get_days_for_month(int month, int year) { switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; case 4: case 6: case 9: case 11: return 30; case 2: if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) { return 29; } else { return 28; } default: return 0; } } static TimerGraphPoint *timer_graph_window_get_month_graph_data(TimerGraphWindow *self) { TimerGraphPoint *pts = g_malloc_n(12, sizeof(TimerGraphPoint)); GDateTime *now = g_date_time_new_now_local(); GDateTime *ref = g_date_time_add_months(now, -1); g_date_time_unref(now); gsize i; for (i = 0; i < 12; ++i) { pts[i].x = i + 1; int total = 0; gsize j; for (j = 0; j < self->dayDataLen; ++j) { if (same_month(self->dayData[j].date, ref)) { total += (int) (self->dayData[j].lenght / 60); } } int y, m; g_date_time_get_ymd(ref, &y, &m, NULL); pts[i].y = total / get_days_for_month(m, y); GDateTime *new = g_date_time_add_months(ref, -1); g_date_time_unref(ref); ref = new; } g_date_time_unref(ref); return pts; } static void timer_graph_window_make_month_graph(TimerGraphWindow *self) { TimerGraphPoint *pts = timer_graph_window_get_month_graph_data(self); self->monthChart = timer_graph_new(pts, 12, "Month", "Time (Average Minutes)", "Last Year"); gtk_widget_set_vexpand(self->monthChart, TRUE); gtk_widget_set_hexpand(self->monthChart, TRUE); g_free(pts); gtk_box_pack_start(GTK_BOX(self->chartBox), self->monthChart, TRUE, TRUE, 0); } TimerGraphWindow *timer_graph_window_new(TimerApplication *app, TimerDataPoint *dayData, gsize dayDataLen, TimerDataPoint *taskData, gsize taskDataLen) { TimerGraphWindow *w = g_object_new(TIMER_TYPE_GRAPH_WINDOW, "application", app, "title", "Statistics", NULL); w->dayData = dayData; w->taskData = taskData; w->dayDataLen = dayDataLen; w->taskDataLen = taskDataLen; timer_graph_window_add_table_data(w); timer_graph_window_make_day_graph(w); timer_graph_window_make_month_graph(w); GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(w)); GdkDisplay *display = gdk_screen_get_display(screen); GdkMonitor *monitor = gdk_display_get_primary_monitor(display); if (monitor) { GdkRectangle monSize; gdk_monitor_get_geometry(monitor, &monSize); gtk_window_set_default_size(GTK_WINDOW(w), monSize.width * 0.75f, monSize.height * 0.75f); } gtk_widget_show_all(GTK_WIDGET(w)); return w; } static void close_button_callback(GtkButton *btn, TimerGraphWindow *win) { gtk_window_close(GTK_WINDOW(win)); } static void timer_graph_window_build_ui(TimerGraphWindow *self) { self->closeButton = gtk_dialog_add_button(GTK_DIALOG(self), "Close", 0); GtkWidget *topBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); self->table = gtk_tree_view_new(); gtk_widget_set_halign(self->table, GTK_ALIGN_CENTER); gtk_box_pack_start(GTK_BOX(topBox), self->table, FALSE, FALSE, 0); self->chartBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add(GTK_CONTAINER(topBox), self->chartBox); gtk_container_add( GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(self))), topBox); } static void timer_graph_window_add_tree_columns(TimerGraphWindow *self) { gtk_tree_view_append_column( GTK_TREE_VIEW(self->table), gtk_tree_view_column_new_with_attributes( "", gtk_cell_renderer_text_new(), "text", DATA_COLUMN, NULL)); gtk_tree_view_append_column(GTK_TREE_VIEW(self->table), gtk_tree_view_column_new_with_attributes( "Last 7 Days", gtk_cell_renderer_text_new(), "text", SEVEN_DAY_COLUMN, NULL)); gtk_tree_view_append_column(GTK_TREE_VIEW(self->table), gtk_tree_view_column_new_with_attributes( "Last 30 Days", gtk_cell_renderer_text_new(), "text", THIRTY_DAY_COLUMN, NULL)); gtk_tree_view_append_column(GTK_TREE_VIEW(self->table), gtk_tree_view_column_new_with_attributes( "Last 6 Months", gtk_cell_renderer_text_new(), "text", SIX_MONTH_COLUMN, NULL)); gtk_tree_view_append_column(GTK_TREE_VIEW(self->table), gtk_tree_view_column_new_with_attributes( "Last Year", gtk_cell_renderer_text_new(), "text", YEAR_COLUMN, NULL)); gtk_tree_view_append_column(GTK_TREE_VIEW(self->table), gtk_tree_view_column_new_with_attributes( "All Time", gtk_cell_renderer_text_new(), "text", ALL_TIME_COLUMN, NULL)); } static void timer_graph_window_finalize(GObject *self) { timer_free_task_data(TIMER_GRAPH_WINDOW(self)->dayData, TIMER_GRAPH_WINDOW(self)->dayDataLen); timer_free_task_data(TIMER_GRAPH_WINDOW(self)->taskData, TIMER_GRAPH_WINDOW(self)->taskDataLen); G_OBJECT_CLASS(timer_graph_window_parent_class)->finalize(self); } static void timer_graph_window_class_init(TimerGraphWindowClass *class) { G_OBJECT_CLASS(class)->finalize = timer_graph_window_finalize; } static void timer_graph_window_init(TimerGraphWindow *self) { gtk_window_set_keep_above(GTK_WINDOW(self), TRUE); gtk_window_set_position(GTK_WINDOW(self), GTK_WIN_POS_MOUSE); timer_graph_window_build_ui(self); g_signal_connect(self->closeButton, "clicked", G_CALLBACK(close_button_callback), self); gtk_tree_selection_set_mode( gtk_tree_view_get_selection(GTK_TREE_VIEW(self->table)), GTK_SELECTION_NONE); self->tableStore = gtk_tree_store_new( N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model(GTK_TREE_VIEW(self->table), GTK_TREE_MODEL(self->tableStore)); timer_graph_window_add_tree_columns(self); }