From a179aab77f64bb758353eb69a04ebd234fae9e1c Mon Sep 17 00:00:00 2001 From: Frederick Hornsey Date: Tue, 3 Apr 2018 17:30:55 -0500 Subject: [PATCH] Save Entires and Sort by Last Ran by default Also added "Sort By" Menu Added with "Last Ran", "Most Ran", and "Least Ran" as options. --- Entry.c | 75 ++++++++++++++++++++++++++++++++++++++++++--------- Entry.h | 11 +++++++- launcher.c | 2 ++ launcher.h | 12 +++++++++ main_window.c | 72 +++++++++++++++++++++++++++++++++++++++---------- util.c | 14 ++++++++++ util.h | 29 ++++++++++++++++++++ 7 files changed, 187 insertions(+), 28 deletions(-) diff --git a/Entry.c b/Entry.c index 6f21b5e..8bb78b4 100644 --- a/Entry.c +++ b/Entry.c @@ -20,6 +20,7 @@ void Entry_delete(Entry * entry) { if (entry->event_box) g_object_unref(entry->event_box); if (entry->exec) g_free(entry->exec); if (entry->cd) g_free(entry->cd); + if (entry->last_ran) free(entry->last_ran); free(entry); } @@ -63,6 +64,12 @@ void Entry_run(Entry * entry) { if (debug) { printf("Would run \"%s\": %s\n", entry->name, exec); } else { + // Update Entry + entry->count++; + if (entry->last_ran) g_free(entry->last_ran); + entry->last_ran = get_time_string(); + Entries_save(entries_file); + // execl wants a file to run AND what to set as argv[0] execl("/bin/sh", "/bin/sh", "-c", exec, NULL); // If we get here, execl had a problem running sh... @@ -143,20 +150,27 @@ bool Entries_load(Entries * entries, const gchar * path) { Entry_set_name(entry, g_key_file_get_string(ini, groups[i], "name", NULL) ); + + // count entry->count = g_key_file_get_integer(ini, groups[i], "count", NULL); // image_path entry->image_path = g_key_file_get_string(ini, groups[i], "image", NULL); + // last_ran + if (g_key_file_has_key(ini, groups[i], "last_ran", NULL)) { + entry->last_ran = g_key_file_get_string(ini, groups[i], "last_ran", NULL); + } + // Run Information if (g_key_file_has_key(ini, groups[i], "exec", NULL)) { - entry->exec = g_key_file_get_value(ini, groups[i], "exec", NULL); + entry->exec = g_key_file_get_string(ini, groups[i], "exec", NULL); } if (g_key_file_has_key(ini, groups[i], "cd", NULL)) { - entry->cd = g_key_file_get_value(ini, groups[i], "cd", NULL); + entry->cd = g_key_file_get_string(ini, groups[i], "cd", NULL); } if (g_key_file_has_key(ini, groups[i], "steam_id", NULL)) { - entry->steam_id = g_key_file_get_value( + entry->steam_id = g_key_file_get_string( ini, groups[i], "steam_id", NULL ); } @@ -188,13 +202,34 @@ Entries * Entries_filter(Entries * entries, const char * filter) { return filtered; } +/* + * Return true if "a" goes first, else returns false. + */ bool Entry_compare(Entry * a, Entry * b) { - // a is true, b is false - if (a->count > b->count) { - return true; - } else if (a->count < b->count) { - return false; - } + bool more_ran = a->count > b->count; + bool less_ran = a->count < b->count; + switch (sort_by) { + case LAST_RAN: + if (a->last_ran && !b->last_ran) + return true; + else if (!a->last_ran && b->last_ran) + return false; + else if (a->last_ran && b->last_ran) + compare_time_strings(a->last_ran, b->last_ran); + break; + case MOST_RAN: + if (more_ran) + return true; + else if (less_ran) + return false; + break; + case LEAST_RAN: + if (more_ran) + return false; + else if (less_ran) + return true; + break; + }; return g_utf8_collate(a->uc_name, b->uc_name) < 0; } @@ -296,19 +331,33 @@ void Entries_insert_steam() { // Download printf("%s -> %s\n", steam_header_url, header_path); - download(steam_header_url, update_bar, steam_header_url, header_path); + download(NULL, update_bar, steam_header_url, header_path); free(steam_header_url); g_free(header_path); } } } -bool Entries_save(Entries * entries, const char * path) { +bool Entries_save(const char * path) { bool had_error = false; GKeyFile * ini = g_key_file_new(); - for (Node * node = entries->head; node; node = node->next) { - /* g_ket_file_set_string(ini, */ + g_key_file_set_integer(ini, "meta", "next_id", next_id); + + for (Node * node = all_entries->head; node; node = node->next) { + Entry * e = node->entry; + g_key_file_set_string(ini, e->id, "name", e->name); + g_key_file_set_string(ini, e->id, "image", e->image_path); + g_key_file_set_integer(ini, e->id, "count", e->count); + g_key_file_set_boolean(ini, e->id, "favorite", e->favorite); + if (e->last_ran) + g_key_file_set_string(ini, e->id, "last_ran", e->last_ran); + if (e->exec) + g_key_file_set_string(ini, e->id, "exec", e->exec); + if (e->cd) + g_key_file_set_string(ini, e->id, "cd", e->cd); + if (e->steam_id) + g_key_file_set_string(ini, e->id, "steam_id", e->steam_id); } // Save entries to file diff --git a/Entry.h b/Entry.h index b26ef2f..fcc30cc 100644 --- a/Entry.h +++ b/Entry.h @@ -2,6 +2,7 @@ #define ENTRY_HEADER #include +#include #include @@ -18,8 +19,11 @@ struct Entry_struct { char * name; char * uc_name; char * image_path; - unsigned count; + + // Sort + unsigned count; // Number of times this Entry has been run bool favorite; + char * last_ran; // As YYYYMMDDhhmmss // Run through exec char * exec; @@ -119,4 +123,9 @@ void Entries_sort(Entries * entries); */ void Entries_insert_steam(); +/* + * Save Entries to "path" + */ +bool Entries_save(const char * path); + #endif diff --git a/launcher.c b/launcher.c index b6ed9f8..34d5495 100644 --- a/launcher.c +++ b/launcher.c @@ -164,6 +164,8 @@ int main(int argc, char * argv[]) { visable_entries = NULL; steam_path = NULL; include_steam_entries = false; + entries_changed = false; + sort_by = LAST_RAN; steam_header_url_head = STEAM_HEADER_URL_HEAD; steam_header_url_tail = STEAM_HEADER_URL_TAIL; diff --git a/launcher.h b/launcher.h index aebb234..9c2f201 100644 --- a/launcher.h +++ b/launcher.h @@ -10,6 +10,18 @@ #define BANNER_HIGHT 215 #define GRID_WIDTH 3 +// Sorting State +// How to sort displayed entries +enum _Sort_By { + LAST_RAN, + MOST_RAN, + LEAST_RAN +}; +typedef enum _Sort_By Sort_By; +Sort_By sort_by; + +bool entries_changed; + bool debug; bool dev_mode; diff --git a/main_window.c b/main_window.c index d96f171..2a04c5f 100644 --- a/main_window.c +++ b/main_window.c @@ -1,3 +1,6 @@ +#include +#include + #include "main_window.h" GtkWidget * window; @@ -7,6 +10,8 @@ GtkWidget * scroll; GtkWidget * grid; GtkWidget * menu; +char * filter_string; + void entry_click(GtkWidget * widget, GdkEventButton * event, gpointer data) { Entry * entry = (Entry *) data; if (debug) printf( @@ -38,22 +43,26 @@ void add_entries_to_grid(Entries * entries) { } } -void filter_changed(GtkEntryBuffer * b) { - const char * filter = gtk_entry_buffer_get_text(b); - if (debug) printf("Filter: %s\n", filter); +void update_displayed_entries() { Entries_clear_container(GTK_CONTAINER(grid), visable_entries); if (visable_entries != all_entries) { Entries_delete(visable_entries); } - if (filter[0]) { - visable_entries = Entries_filter(all_entries, filter); + if (filter_string && filter_string[0]) { + visable_entries = Entries_filter(all_entries, filter_string); } else { - if (debug) printf(" \n"); visable_entries = all_entries; } add_entries_to_grid(visable_entries); } +void filter_changed(GtkEntryBuffer * b) { + if (filter_string) free(filter_string); + filter_string = strdup(gtk_entry_buffer_get_text(b)); + if (debug) printf("Filter: %s\n", filter_string); + update_displayed_entries(); +} + void init_entries_gui(Entries * entries) { for (Node * node = entries->head; node; node = node->next) { Entry * entry = node->entry; @@ -90,21 +99,30 @@ void init_entries_gui(Entries * entries) { gtk_container_add(GTK_CONTAINER(event_box), entry->image); } else { // Create Error Entry GUI - g_warning("Could load image %s: \"%s\"\n", - full_image_path, error->message + char * error_message = g_strdup_printf( + "Could load image %s: \"%s\"\n", + full_image_path, + error->message ); g_error_free(error); - GtkWidget * error_message = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add(GTK_CONTAINER(error_message), + if (debug) fprintf(stderr, error_message); + + GtkWidget * error_message_box = gtk_box_new( + GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(error_message_box), + gtk_label_new(entry->name) + ); + gtk_container_add(GTK_CONTAINER(error_message_box), gtk_image_new_from_icon_name( "image-missing", GTK_ICON_SIZE_DIALOG ) ); - gtk_container_add(GTK_CONTAINER(error_message), - gtk_label_new(entry->name) + gtk_container_add(GTK_CONTAINER(error_message_box), + gtk_label_new(error_message) ); - gtk_container_add(GTK_CONTAINER(event_box), error_message); + gtk_container_add(GTK_CONTAINER(event_box), error_message_box); + g_free(error_message); } } @@ -122,6 +140,12 @@ static bool esc_close(GtkWindow * widget, GdkEventKey *event, gpointer data) { return false; } +void set_sort_by(void * value) { + sort_by = (Sort_By) value; + Entries_sort(all_entries); + update_displayed_entries(); +} + void init_menu() { menu = gtk_menu_new(); @@ -137,6 +161,24 @@ void init_menu() { GtkWidget * add_item = gtk_menu_item_new_with_label("Add Game(s)"); gtk_menu_attach(GTK_MENU(menu), add_item, 0, 1, a++, b++); + // Sort Entries + GtkWidget * sort_by_item = gtk_menu_item_new_with_label("Sort By"); + GtkWidget * sort_by_menu = gtk_menu_new(); + char * names[3] = {"Last Ran", "Most Ran", "Least Ran"}; + GSList * group = NULL; + for (unsigned i = 0; i < 3; i++) { + GtkWidget * item = gtk_radio_menu_item_new_with_label(group, names[i]); + if (!i) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), true); + group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); + gtk_menu_attach(GTK_MENU(sort_by_menu), item, 0, 1, i, i+1); + g_signal_connect_swapped( + G_OBJECT(item), "activate", + G_CALLBACK(set_sort_by), (void *) (long unsigned) i + ); + } + gtk_menu_item_set_submenu(GTK_MENU_ITEM(sort_by_item), sort_by_menu); + gtk_menu_attach(GTK_MENU(menu), sort_by_item, 0, 1, a++, b++); + GtkWidget * settings_item = gtk_menu_item_new_with_label("Settings"); gtk_menu_attach(GTK_MENU(menu), settings_item, 0, 1, a++, b++); @@ -149,6 +191,8 @@ void init_menu() { } void init_main_window(GtkApplication * app, gpointer user_data) { + filter = NULL; + // Window window = gtk_application_window_new(app); gtk_window_set_resizable(GTK_WINDOW(window), false); @@ -191,7 +235,7 @@ void init_main_window(GtkApplication * app, gpointer user_data) { // Init Entry Elements and add them init_entries_gui(all_entries); visable_entries = all_entries; - add_entries_to_grid(visable_entries); + update_displayed_entries(); // Entry Context Menu init_menu(); diff --git a/util.c b/util.c index f309b2e..22f4320 100644 --- a/util.c +++ b/util.c @@ -1,7 +1,10 @@ #include #include +#include +#include #include +#include #include "util.h" @@ -67,3 +70,14 @@ bool starts_with(const char * string, const char * prefix) { } } +#define TIME_STRING_LEN 15 +// 4 + 2+2+2+2+2+ 1 = 15 +// YYYYMMDDhhmmss /0 +char * get_time_string() { + char * string = g_malloc(TIME_STRING_LEN); + time_t epoch = time(NULL); + struct tm utc = *gmtime(&epoch); + strftime(string, TIME_STRING_LEN, "%Y%m%d%H%M%S", &utc); + return string; +} + diff --git a/util.h b/util.h index 901febc..5b8b7ba 100644 --- a/util.h +++ b/util.h @@ -1,8 +1,37 @@ #include #include +#include +/* + * Try to download a file from "url" to "destination" with "data" + * being passed to "callback". + */ bool download(void * data, int (*callback)(void*, double, double, double, double), const char * url, const char * destination ); + +/* + * Returns true if "string" starts with or is the same as "prefix". + */ bool starts_with(const char * string, const char * prefix); + +/* ====================================================================== + * Time Functions + * + * Time is stored as a string, in a format similar to ISO 8601: + * YYYYMMDDhhmmss + * Ex: July 20, 1969 at 20:18:00 is "19690720201800" + */ + +/* + * Return the current time in YYYYMMDDhhmmss format on the glib heap. + */ +char * get_time_string(); + +/* + * Compare 2 time strings, returns true if "a" is more recent than "b" + */ +//bool compare_time_strings(const char * a, const char * b) +#define compare_time_strings(a, b) (strcmp((a), (b)) < 0) +