/* * SPDX-FileCopyrightText: 2018-2024 Bruce Cowan <bruce@bcowan.me.uk> * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "rugby-list-store.h" #include "rugby-possibility.h" #include <gio/gio.h> #include <gtk/gtk.h> struct _RugbyListStore { GObject parent_instance; int score; GListStore *items; GSettings *settings; }; static void rugby_list_store_list_model_iface_init (GListModelInterface *iface); static void rugby_list_store_section_model_iface_init (GtkSectionModelInterface *iface); G_DEFINE_FINAL_TYPE_WITH_CODE (RugbyListStore, rugby_list_store, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, rugby_list_store_list_model_iface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, rugby_list_store_section_model_iface_init)) enum { PROP_SCORE = 1, N_PROPS }; static GParamSpec *properties[N_PROPS]; // Helper functions static int sort_func ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED void *user_data) { int atries = rugby_possibility_get_tries ((void *) a); int autries = rugby_possibility_get_utries ((void *) a); int btries = rugby_possibility_get_tries ((void *) b); int butries = rugby_possibility_get_utries ((void *) b); int trydiff = (btries + butries) - (atries + autries); // Sort by total tries first, then converted if (trydiff != 0) return trydiff; else return btries - atries; } static void process_data (RugbyListStore *self) { int try_points = g_settings_get_int (self->settings, "try-points"); int utry_points = g_settings_get_int (self->settings, "utry-points"); int kick_points = g_settings_get_int (self->settings, "kick-points"); unsigned old_length = g_list_model_get_n_items (G_LIST_MODEL (self->items)); g_list_store_remove_all (self->items); if (self->score == 0) { g_list_model_items_changed (G_LIST_MODEL (self), 0, old_length, 0); return; } int max_tries = self->score / try_points; int max_utries = self->score / utry_points; for (int tries = 0; tries <= max_tries; tries++) { for (int utries = 0; utries <= max_utries; utries++) { int left = self->score - (tries * try_points) - (utries * utry_points); if (left < 0) break; if (left % kick_points == 0) { int kicks = left / kick_points; RugbyPossibility *possibility = rugby_possibility_new (tries, utries, kicks); g_list_store_append (self->items, possibility); g_object_unref (possibility); } } } g_list_store_sort (self->items, sort_func, NULL); g_list_model_items_changed (G_LIST_MODEL (self), 0, old_length, g_list_model_get_n_items (G_LIST_MODEL (self->items))); } // GListModel implementation static GType rugby_list_store_get_item_type (G_GNUC_UNUSED GListModel *list) { return RUGBY_TYPE_POSSIBILITY; } static unsigned rugby_list_store_get_n_items (GListModel *list) { RugbyListStore *self = RUGBY_LIST_STORE (list); return g_list_model_get_n_items (G_LIST_MODEL (self->items)); } static void * rugby_list_store_get_item (GListModel *list, unsigned position) { RugbyListStore *self = RUGBY_LIST_STORE (list); return g_list_model_get_item (G_LIST_MODEL (self->items), position); } static void rugby_list_store_list_model_iface_init (GListModelInterface *iface) { iface->get_item_type = rugby_list_store_get_item_type; iface->get_n_items = rugby_list_store_get_n_items; iface->get_item = rugby_list_store_get_item; } // SectionModel implementation static void rugby_list_store_get_section (GtkSectionModel *model, unsigned position, unsigned *out_start, unsigned *out_end) { RugbyListStore *self = RUGBY_LIST_STORE (model); unsigned n_items = g_list_model_get_n_items (G_LIST_MODEL (self->items)); RugbyPossibility *possibility = g_list_model_get_item (G_LIST_MODEL (self->items), position); if (!possibility) { *out_start = n_items; *out_end = G_MAXUINT; return; } int target_tries = rugby_possibility_total_tries (possibility); // Find start for (unsigned i = 0; i < n_items; i++) { possibility = g_list_model_get_item (G_LIST_MODEL (self->items), i); int total = rugby_possibility_total_tries (possibility); if (total == target_tries) { *out_start = i; break; } } // Find end for (unsigned i = *out_start + 1; i < n_items; i++) { possibility = g_list_model_get_item (G_LIST_MODEL (self->items), i); int total = rugby_possibility_total_tries (possibility); if (total != target_tries) { *out_end = i; return; } } *out_end = n_items; } static void rugby_list_store_section_model_iface_init (GtkSectionModelInterface *iface) { iface->get_section = rugby_list_store_get_section; } // Class functions static void rugby_list_store_dispose (GObject *object) { RugbyListStore *self = RUGBY_LIST_STORE (object); g_clear_object (&self->settings); g_clear_object (&self->items); G_OBJECT_CLASS (rugby_list_store_parent_class)->dispose (object); } static void rugby_list_store_get_property (GObject *object, unsigned prop_id, GValue *value, GParamSpec *pspec) { RugbyListStore *self = RUGBY_LIST_STORE (object); switch (prop_id) { case PROP_SCORE: g_value_set_int (value, self->score); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void rugby_list_store_set_property (GObject *object, unsigned prop_id, const GValue *value, GParamSpec *pspec) { RugbyListStore *self = RUGBY_LIST_STORE (object); switch (prop_id) { int score; case PROP_SCORE: score = g_value_get_int (value); if (score != self->score) { self->score = score; process_data (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SCORE]); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void rugby_list_store_class_init (RugbyListStoreClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = rugby_list_store_dispose; object_class->get_property = rugby_list_store_get_property; object_class->set_property = rugby_list_store_set_property; properties[PROP_SCORE] = g_param_spec_int ("score", "", "", 0, 200, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); } static void rugby_list_store_init (RugbyListStore *self) { self->settings = g_settings_new ("uk.me.bcowan.Rugby"); g_signal_connect_swapped (self->settings, "changed::try-points", G_CALLBACK (process_data), self); g_signal_connect_swapped (self->settings, "changed::utry-points", G_CALLBACK (process_data), self); g_signal_connect_swapped (self->settings, "changed::kick-points", G_CALLBACK (process_data), self); self->score = 0; self->items = g_list_store_new (RUGBY_TYPE_POSSIBILITY); }