2022-08-28 14:20:12 -07:00
#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 );
}
2022-11-08 14:53:51 -08:00
self -> fileWatcher = NULL ;
2022-08-28 14:20:12 -07:00
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 , & currentIter )) {
if ( gtk_tree_model_iter_parent ( GTK_TREE_MODEL ( tree -> store ),
& parentIter , & currentIter )) {
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 );
}