practice-timer/TimerGraphWindow.c

350 lines
12 KiB
C

#include "TimerGraphWindow.h"
#include "TimerTaskTree.h"
#include "TimerGraph.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <math.h>
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);
}