/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright (C) 2013-2017 Richard Hughes * Copyright (C) 2013 Matthias Clasen * Copyright (C) 2014-2018 Kalev Lember * * SPDX-License-Identifier: GPL-2.0+ */ #include "config.h" #include #include #include "gs-shell.h" #include "gs-installed-page.h" #include "gs-common.h" #include "gs-app-row.h" #include "gs-utils.h" struct _GsInstalledPage { GsPage parent_instance; GsPluginLoader *plugin_loader; GCancellable *cancellable; GtkSizeGroup *sizegroup_name; GtkSizeGroup *sizegroup_button_label; GtkSizeGroup *sizegroup_button_image; gboolean cache_valid; gboolean waiting; GsShell *shell; GSettings *settings; guint pending_apps_counter; gboolean is_narrow; GtkWidget *group_install_in_progress; GtkWidget *group_install_apps; GtkWidget *group_install_system_apps; GtkWidget *group_install_addons; GtkWidget *group_install_web_apps; GtkWidget *list_box_install_in_progress; GtkWidget *list_box_install_apps; GtkWidget *list_box_install_system_apps; GtkWidget *list_box_install_addons; GtkWidget *list_box_install_web_apps; GtkWidget *scrolledwindow_install; GtkWidget *spinner_install; GtkWidget *stack_install; }; G_DEFINE_TYPE (GsInstalledPage, gs_installed_page, GS_TYPE_PAGE) typedef enum { PROP_IS_NARROW = 1, /* Overrides: */ PROP_VADJUSTMENT, PROP_TITLE, } GsInstalledPageProperty; static GParamSpec *obj_props[PROP_IS_NARROW + 1] = { NULL, }; static void gs_installed_page_pending_apps_refined_cb (GObject *source, GAsyncResult *res, gpointer user_data); static GsPluginRefineFlags gs_installed_page_get_refine_flags (GsInstalledPage *self); static void gs_installed_page_notify_state_changed_cb (GsApp *app, GParamSpec *pspec, GsInstalledPage *self); typedef enum { GS_UPDATE_LIST_SECTION_INSTALLING_AND_REMOVING, GS_UPDATE_LIST_SECTION_REMOVABLE_APPS, GS_UPDATE_LIST_SECTION_SYSTEM_APPS, GS_UPDATE_LIST_SECTION_ADDONS, GS_UPDATE_LIST_SECTION_WEB_APPS, GS_UPDATE_LIST_SECTION_LAST } GsInstalledPageSection; /* This must mostly mirror gs_installed_page_get_app_sort_key() otherwise apps * will end up sorted into a section they don’t belong in. */ static GsInstalledPageSection gs_installed_page_get_app_section (GsApp *app) { GsAppState state = gs_app_get_state (app); AsComponentKind kind = gs_app_get_kind (app); if (state == GS_APP_STATE_INSTALLING || state == GS_APP_STATE_QUEUED_FOR_INSTALL || state == GS_APP_STATE_REMOVING) return GS_UPDATE_LIST_SECTION_INSTALLING_AND_REMOVING; if (kind == AS_COMPONENT_KIND_DESKTOP_APP) { if (gs_app_has_quirk (app, GS_APP_QUIRK_COMPULSORY)) return GS_UPDATE_LIST_SECTION_SYSTEM_APPS; return GS_UPDATE_LIST_SECTION_REMOVABLE_APPS; } if (kind == AS_COMPONENT_KIND_WEB_APP) return GS_UPDATE_LIST_SECTION_WEB_APPS; return GS_UPDATE_LIST_SECTION_ADDONS; } static void update_groups (GsInstalledPage *self) { gtk_widget_set_visible (self->group_install_in_progress, gtk_widget_get_first_child (self->list_box_install_in_progress) != NULL); gtk_widget_set_visible (self->group_install_apps, gtk_widget_get_first_child (self->list_box_install_apps) != NULL); gtk_widget_set_visible (self->group_install_system_apps, gtk_widget_get_first_child (self->list_box_install_system_apps) != NULL); gtk_widget_set_visible (self->group_install_addons, gtk_widget_get_first_child (self->list_box_install_addons) != NULL); gtk_widget_set_visible (self->group_install_web_apps, gtk_widget_get_first_child (self->list_box_install_web_apps) != NULL); } static GsInstalledPageSection gs_installed_page_get_row_section (GsInstalledPage *self, GsAppRow *app_row) { GtkWidget *parent; g_return_val_if_fail (GS_IS_INSTALLED_PAGE (self), GS_UPDATE_LIST_SECTION_LAST); g_return_val_if_fail (GS_IS_APP_ROW (app_row), GS_UPDATE_LIST_SECTION_LAST); parent = gtk_widget_get_parent (GTK_WIDGET (app_row)); if (parent == self->list_box_install_in_progress) return GS_UPDATE_LIST_SECTION_INSTALLING_AND_REMOVING; if (parent == self->list_box_install_apps) return GS_UPDATE_LIST_SECTION_REMOVABLE_APPS; if (parent == self->list_box_install_system_apps) return GS_UPDATE_LIST_SECTION_SYSTEM_APPS; if (parent == self->list_box_install_addons) return GS_UPDATE_LIST_SECTION_ADDONS; if (parent == self->list_box_install_web_apps) return GS_UPDATE_LIST_SECTION_WEB_APPS; g_warn_if_reached (); return GS_UPDATE_LIST_SECTION_LAST; } static void gs_installed_page_invalidate (GsInstalledPage *self) { self->cache_valid = FALSE; } static void gs_installed_page_app_row_activated_cb (GtkListBox *list_box, GtkListBoxRow *row, GsInstalledPage *self) { GsApp *app; app = gs_app_row_get_app (GS_APP_ROW (row)); gs_shell_show_app (self->shell, app); } static void row_unrevealed (GObject *row, GParamSpec *pspec, gpointer data) { GsInstalledPage *self = GS_INSTALLED_PAGE (gtk_widget_get_ancestor (GTK_WIDGET (row), GS_TYPE_INSTALLED_PAGE)); GtkWidget *list; list = gtk_widget_get_parent (GTK_WIDGET (row)); if (list == NULL) return; gtk_list_box_remove (GTK_LIST_BOX (list), GTK_WIDGET (row)); update_groups (self); } static void gs_installed_page_unreveal_row (GsAppRow *app_row) { GsApp *app = gs_app_row_get_app (app_row); if (app != NULL) { g_signal_handlers_disconnect_matched (app, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK (gs_installed_page_notify_state_changed_cb), NULL); } g_signal_connect (app_row, "unrevealed", G_CALLBACK (row_unrevealed), NULL); gs_app_row_unreveal (app_row); } static GsAppRow * /* (transfer none) */ gs_installed_page_find_app_row (GsInstalledPage *self, GsApp *app) { GtkWidget *lists[] = { self->list_box_install_in_progress, self->list_box_install_apps, self->list_box_install_system_apps, self->list_box_install_addons, self->list_box_install_web_apps, NULL }; for (gsize i = 0; lists[i]; i++) { for (GtkWidget *child = gtk_widget_get_first_child (lists[i]); child != NULL; child = gtk_widget_get_next_sibling (child)) { GsAppRow *app_row = GS_APP_ROW (child); if (gs_app_row_get_app (app_row) == app) { return app_row; } } } return NULL; } static void gs_installed_page_app_removed (GsPage *page, GsApp *app) { GsInstalledPage *self = GS_INSTALLED_PAGE (page); GsAppRow *app_row = gs_installed_page_find_app_row (self, app); if (app_row != NULL) gs_installed_page_unreveal_row (app_row); } static void gs_installed_page_app_remove_cb (GsAppRow *app_row, GsInstalledPage *self) { GsApp *app; app = gs_app_row_get_app (app_row); gs_page_remove_app (GS_PAGE (self), app, self->cancellable); } static void gs_installed_page_maybe_move_app_row (GsInstalledPage *self, GsAppRow *app_row) { GsInstalledPageSection current_section, expected_section; current_section = gs_installed_page_get_row_section (self, app_row); g_return_if_fail (current_section != GS_UPDATE_LIST_SECTION_LAST); expected_section = gs_installed_page_get_app_section (gs_app_row_get_app (app_row)); if (expected_section != current_section) { GtkWidget *widget = GTK_WIDGET (app_row); g_object_ref (app_row); gtk_list_box_remove (GTK_LIST_BOX (gtk_widget_get_parent (widget)), widget); switch (expected_section) { case GS_UPDATE_LIST_SECTION_INSTALLING_AND_REMOVING: widget = self->list_box_install_in_progress; break; case GS_UPDATE_LIST_SECTION_REMOVABLE_APPS: widget = self->list_box_install_apps; break; case GS_UPDATE_LIST_SECTION_SYSTEM_APPS: widget = self->list_box_install_system_apps; break; case GS_UPDATE_LIST_SECTION_ADDONS: widget = self->list_box_install_addons; break; case GS_UPDATE_LIST_SECTION_WEB_APPS: widget = self->list_box_install_web_apps; break; default: g_warn_if_reached (); widget = NULL; break; } if (widget != NULL) gtk_list_box_append (GTK_LIST_BOX (widget), GTK_WIDGET (app_row)); g_object_unref (app_row); update_groups (self); } } static void gs_installed_page_notify_state_changed_cb (GsApp *app, GParamSpec *pspec, GsInstalledPage *self) { GsAppState state = gs_app_get_state (app); GsAppRow *app_row = gs_installed_page_find_app_row (self, app); g_assert (app_row != NULL); gtk_list_box_row_changed (GTK_LIST_BOX_ROW (app_row)); /* Filter which applications can be shown in the installed page */ if (state != GS_APP_STATE_INSTALLING && state != GS_APP_STATE_INSTALLED && state != GS_APP_STATE_REMOVING && state != GS_APP_STATE_UPDATABLE && state != GS_APP_STATE_UPDATABLE_LIVE) gs_installed_page_unreveal_row (app_row); else gs_installed_page_maybe_move_app_row (self, app_row); } static gboolean should_show_installed_size (GsInstalledPage *self) { return g_settings_get_boolean (self->settings, "installed-page-show-size"); } static gboolean gs_installed_page_is_actual_app (GsApp *app) { if (gs_app_get_description (app) != NULL) return TRUE; /* special snowflake */ if (g_strcmp0 (gs_app_get_id (app), "google-chrome.desktop") == 0) return TRUE; /* web apps sometimes don't have descriptions */ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_WEB_APP) return TRUE; g_debug ("%s is not an actual app", gs_app_get_unique_id (app)); return FALSE; } static void gs_installed_page_add_app (GsInstalledPage *self, GsAppList *list, GsApp *app) { GtkWidget *app_row; /* only show if is an actual application */ if (!gs_installed_page_is_actual_app (app)) return; app_row = g_object_new (GS_TYPE_APP_ROW, "app", app, "show-buttons", TRUE, "show-source", gs_utils_list_has_component_fuzzy (list, app), "show-installed-size", !gs_app_has_quirk (app, GS_APP_QUIRK_COMPULSORY) && should_show_installed_size (self), NULL); g_signal_connect (app_row, "button-clicked", G_CALLBACK (gs_installed_page_app_remove_cb), self); g_signal_connect_object (app, "notify::state", G_CALLBACK (gs_installed_page_notify_state_changed_cb), self, 0); switch (gs_installed_page_get_app_section (app)) { case GS_UPDATE_LIST_SECTION_INSTALLING_AND_REMOVING: gtk_list_box_append (GTK_LIST_BOX (self->list_box_install_in_progress), app_row); break; case GS_UPDATE_LIST_SECTION_REMOVABLE_APPS: gtk_list_box_append (GTK_LIST_BOX (self->list_box_install_apps), app_row); break; case GS_UPDATE_LIST_SECTION_SYSTEM_APPS: gtk_list_box_append (GTK_LIST_BOX (self->list_box_install_system_apps), app_row); break; case GS_UPDATE_LIST_SECTION_ADDONS: gtk_list_box_append (GTK_LIST_BOX (self->list_box_install_addons), app_row); break; case GS_UPDATE_LIST_SECTION_WEB_APPS: gtk_list_box_append (GTK_LIST_BOX (self->list_box_install_web_apps), app_row); break; default: g_assert_not_reached (); } update_groups (self); gs_app_row_set_size_groups (GS_APP_ROW (app_row), self->sizegroup_name, self->sizegroup_button_label, self->sizegroup_button_image); gs_app_row_set_show_description (GS_APP_ROW (app_row), FALSE); gs_app_row_set_show_source (GS_APP_ROW (app_row), FALSE); g_object_bind_property (self, "is-narrow", app_row, "is-narrow", G_BINDING_SYNC_CREATE); } static void gs_installed_page_get_installed_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { guint i; GsApp *app; GsInstalledPage *self = GS_INSTALLED_PAGE (user_data); GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); g_autoptr(GError) error = NULL; g_autoptr(GsAppList) list = NULL; g_autoptr(GsAppList) pending = gs_plugin_loader_get_pending (plugin_loader); g_autoptr(GsPluginJob) plugin_job = NULL; gtk_spinner_stop (GTK_SPINNER (self->spinner_install)); gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "view"); self->waiting = FALSE; self->cache_valid = TRUE; list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); if (list == NULL) { if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ("failed to get installed apps: %s", error->message); goto out; } for (i = 0; i < gs_app_list_length (list); i++) { app = gs_app_list_index (list, i); gs_installed_page_add_app (self, list, app); } out: if (gs_app_list_length (pending) > 0) { plugin_job = gs_plugin_job_refine_new (pending, gs_installed_page_get_refine_flags (self)); gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, self->cancellable, gs_installed_page_pending_apps_refined_cb, self); } } static void gs_installed_page_remove_all_cb (GtkWidget *container, GtkWidget *child) { if (GS_IS_APP_ROW (child)) { GsApp *app = gs_app_row_get_app (GS_APP_ROW (child)); if (app != NULL) { g_signal_handlers_disconnect_matched (app, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, G_CALLBACK (gs_installed_page_notify_state_changed_cb), NULL); } } else { g_warn_if_reached (); } gtk_list_box_remove (GTK_LIST_BOX (container), child); } static gboolean filter_app_kinds_cb (GsApp *app, gpointer user_data) { /* Remove invalid apps. */ if (!gs_plugin_loader_app_is_valid (app, GS_PLUGIN_REFINE_FLAGS_NONE)) return FALSE; switch (gs_app_get_kind (app)) { case AS_COMPONENT_KIND_OPERATING_SYSTEM: case AS_COMPONENT_KIND_CODEC: case AS_COMPONENT_KIND_FONT: g_debug ("app invalid as %s: %s", as_component_kind_to_string (gs_app_get_kind (app)), gs_app_get_unique_id (app)); return FALSE; default: return TRUE; } } static GsPluginRefineFlags gs_installed_page_get_refine_flags (GsInstalledPage *self) { GsPluginRefineFlags flags; flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON | GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY | GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION | GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION | GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS | GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME | GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE | GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION | GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE | GS_PLUGIN_REFINE_FLAGS_REQUIRE_CATEGORIES | GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING; if (should_show_installed_size (self)) flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE; return flags; } static void gs_installed_page_load (GsInstalledPage *self) { g_autoptr(GsAppQuery) query = NULL; g_autoptr(GsPluginJob) plugin_job = NULL; if (self->waiting) return; self->waiting = TRUE; /* remove old entries */ gs_widget_remove_all (self->list_box_install_in_progress, gs_installed_page_remove_all_cb); gs_widget_remove_all (self->list_box_install_apps, gs_installed_page_remove_all_cb); gs_widget_remove_all (self->list_box_install_system_apps, gs_installed_page_remove_all_cb); gs_widget_remove_all (self->list_box_install_addons, gs_installed_page_remove_all_cb); gs_widget_remove_all (self->list_box_install_web_apps, gs_installed_page_remove_all_cb); update_groups (self); /* get installed apps */ query = gs_app_query_new ("is-installed", GS_APP_QUERY_TRISTATE_TRUE, "refine-flags", gs_installed_page_get_refine_flags (self), "filter-func", filter_app_kinds_cb, NULL); plugin_job = gs_plugin_job_list_apps_new (query, GS_PLUGIN_LIST_APPS_FLAGS_INTERACTIVE); gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, self->cancellable, gs_installed_page_get_installed_cb, self); gtk_spinner_start (GTK_SPINNER (self->spinner_install)); gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "spinner"); } static void gs_installed_page_reload (GsPage *page) { GsInstalledPage *self = GS_INSTALLED_PAGE (page); gs_installed_page_invalidate (self); gs_installed_page_load (self); } static void gs_installed_page_switch_to (GsPage *page) { GsInstalledPage *self = GS_INSTALLED_PAGE (page); if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_INSTALLED) { g_warning ("Called switch_to(installed) when in mode %s", gs_shell_get_mode_string (self->shell)); return; } if (gs_shell_get_mode (self->shell) == GS_SHELL_MODE_INSTALLED) { gs_grab_focus_when_mapped (self->scrolledwindow_install); } /* no need to refresh */ if (self->cache_valid) return; gs_installed_page_load (self); } /** * gs_installed_page_get_app_sort_key: * * Get a sort key to achive this: * * 1. state:installing applications * 2. state: applications queued for installing * 3. state:removing applications * 4. kind:normal applications * 5. kind:system applications * * Within each of these groups, they are sorted by the install date and then * by name. **/ static gchar * gs_installed_page_get_app_sort_key (GsApp *app) { GString *key; g_autofree gchar *sort_name = NULL; key = g_string_sized_new (64); /* sort installed, removing, other */ switch (gs_app_get_state (app)) { case GS_APP_STATE_INSTALLING: g_string_append (key, "1:"); break; case GS_APP_STATE_QUEUED_FOR_INSTALL: g_string_append (key, "2:"); break; case GS_APP_STATE_REMOVING: g_string_append (key, "3:"); break; default: g_string_append (key, "4:"); break; } /* sort apps by kind */ switch (gs_app_get_kind (app)) { case AS_COMPONENT_KIND_DESKTOP_APP: g_string_append (key, "2:"); break; case AS_COMPONENT_KIND_WEB_APP: g_string_append (key, "3:"); break; case AS_COMPONENT_KIND_RUNTIME: g_string_append (key, "4:"); break; case AS_COMPONENT_KIND_ADDON: g_string_append (key, "5:"); break; case AS_COMPONENT_KIND_CODEC: g_string_append (key, "6:"); break; case AS_COMPONENT_KIND_FONT: g_string_append (key, "6:"); break; case AS_COMPONENT_KIND_INPUT_METHOD: g_string_append (key, "7:"); break; default: if (gs_app_get_special_kind (app) == GS_APP_SPECIAL_KIND_OS_UPDATE) g_string_append (key, "1:"); else g_string_append (key, "8:"); break; } /* sort normal, compulsory */ if (!gs_app_has_quirk (app, GS_APP_QUIRK_COMPULSORY)) g_string_append (key, "1:"); else g_string_append (key, "2:"); /* finally, sort by short name */ if (gs_app_get_name (app) != NULL) { sort_name = gs_utils_sort_key (gs_app_get_name (app)); g_string_append (key, sort_name); } return g_string_free (key, FALSE); } static gint gs_installed_page_sort_func (GtkListBoxRow *a, GtkListBoxRow *b, gpointer user_data) { GsApp *a1, *a2; g_autofree gchar *key1 = NULL; g_autofree gchar *key2 = NULL; a1 = gs_app_row_get_app (GS_APP_ROW (a)); a2 = gs_app_row_get_app (GS_APP_ROW (b)); key1 = gs_installed_page_get_app_sort_key (a1); key2 = gs_installed_page_get_app_sort_key (a2); /* compare the keys according to the algorithm above */ return g_strcmp0 (key1, key2); } static gboolean gs_installed_page_has_app (GsInstalledPage *self, GsApp *app) { GtkWidget *lists[] = { self->list_box_install_in_progress, self->list_box_install_apps, self->list_box_install_system_apps, self->list_box_install_addons, self->list_box_install_web_apps, NULL }; for (gsize i = 0; lists[i]; i++) { for (GtkWidget *child = gtk_widget_get_first_child (lists[i]); child != NULL; child = gtk_widget_get_next_sibling (child)) { GsAppRow *app_row = GS_APP_ROW (child); if (gs_app_row_get_app (app_row) == app) return TRUE; } } return FALSE; } static void gs_installed_page_add_pending_apps (GsInstalledPage *self, GsAppList *list, gboolean should_install) { guint pending_apps_count = 0; for (guint i = 0; i < gs_app_list_length (list); ++i) { GsApp *app = gs_app_list_index (list, i); if (gs_app_is_installed (app)) { continue; } /* never show OS upgrades, we handle the scheduling and * cancellation in GsUpgradeBanner */ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_OPERATING_SYSTEM) continue; if (gs_app_get_state (app) == GS_APP_STATE_AVAILABLE) gs_app_set_state (app, GS_APP_STATE_QUEUED_FOR_INSTALL); if (should_install && gs_app_get_state (app) == GS_APP_STATE_QUEUED_FOR_INSTALL && gs_plugin_loader_get_network_available (self->plugin_loader) && !gs_plugin_loader_get_network_metered (self->plugin_loader)) gs_page_install_app (GS_PAGE (self), app, GS_SHELL_INTERACTION_FULL, gs_app_get_cancellable (app)); ++pending_apps_count; if (!gs_installed_page_has_app (self, app)) gs_installed_page_add_app (self, list, app); } /* update the number of on-going operations */ if (pending_apps_count != self->pending_apps_counter) { self->pending_apps_counter = pending_apps_count; g_object_notify (G_OBJECT (self), "counter"); } } static void gs_installed_page_pending_apps_refined_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source); GsInstalledPage *self = GS_INSTALLED_PAGE (user_data); g_autoptr(GError) error = NULL; g_autoptr(GsAppList) list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); if (list == NULL) { if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) g_warning ("failed to refine pending apps: %s", error->message); return; } /* we add the pending apps and install them because this is called after we * populate the page, and there may be pending apps coming from the saved list * (i.e. after loading the saved pending apps from the disk) */ gs_installed_page_add_pending_apps (self, list, TRUE); } static void gs_installed_page_pending_apps_changed_cb (GsPluginLoader *plugin_loader, GsInstalledPage *self) { g_autoptr(GsAppList) pending = gs_plugin_loader_get_pending (plugin_loader); /* we don't call install every time the pending apps list changes because * it may be queued in the plugin loader */ gs_installed_page_add_pending_apps (self, pending, FALSE); } static gboolean gs_installed_page_setup (GsPage *page, GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *cancellable, GError **error) { GsInstalledPage *self = GS_INSTALLED_PAGE (page); g_return_val_if_fail (GS_IS_INSTALLED_PAGE (self), TRUE); self->shell = shell; self->plugin_loader = g_object_ref (plugin_loader); g_signal_connect (self->plugin_loader, "pending-apps-changed", G_CALLBACK (gs_installed_page_pending_apps_changed_cb), self); self->cancellable = g_object_ref (cancellable); /* setup installed */ gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_install_in_progress), gs_installed_page_sort_func, self, NULL); gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_install_apps), gs_installed_page_sort_func, self, NULL); gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_install_system_apps), gs_installed_page_sort_func, self, NULL); gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_install_addons), gs_installed_page_sort_func, self, NULL); gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_install_web_apps), gs_installed_page_sort_func, self, NULL); return TRUE; } static void gs_installed_page_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GsInstalledPage *self = GS_INSTALLED_PAGE (object); switch ((GsInstalledPageProperty) prop_id) { case PROP_IS_NARROW: g_value_set_boolean (value, gs_installed_page_get_is_narrow (self)); break; case PROP_VADJUSTMENT: g_value_set_object (value, gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolledwindow_install))); break; case PROP_TITLE: /* Translators: This is in the context of a list of apps which are installed on the system. */ g_value_set_string (value, C_("List of installed apps", "Installed")); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gs_installed_page_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GsInstalledPage *self = GS_INSTALLED_PAGE (object); switch ((GsInstalledPageProperty) prop_id) { case PROP_IS_NARROW: gs_installed_page_set_is_narrow (self, g_value_get_boolean (value)); break; case PROP_VADJUSTMENT: case PROP_TITLE: /* Read only. */ g_assert_not_reached (); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gs_installed_page_dispose (GObject *object) { GsInstalledPage *self = GS_INSTALLED_PAGE (object); g_clear_object (&self->sizegroup_name); g_clear_object (&self->sizegroup_button_label); g_clear_object (&self->sizegroup_button_image); g_clear_object (&self->plugin_loader); g_clear_object (&self->cancellable); g_clear_object (&self->settings); G_OBJECT_CLASS (gs_installed_page_parent_class)->dispose (object); } static void gs_installed_page_class_init (GsInstalledPageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GsPageClass *page_class = GS_PAGE_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = gs_installed_page_get_property; object_class->set_property = gs_installed_page_set_property; object_class->dispose = gs_installed_page_dispose; page_class->app_removed = gs_installed_page_app_removed; page_class->switch_to = gs_installed_page_switch_to; page_class->reload = gs_installed_page_reload; page_class->setup = gs_installed_page_setup; /** * GsInstalledPage:is-narrow: * * Whether the page is in narrow mode. * * In narrow mode, the page will take up less horizontal space, doing so * by e.g. using icons rather than labels in buttons. This is needed to * keep the UI useable on small form-factors like smartphones. * * Since: 41 */ obj_props[PROP_IS_NARROW] = g_param_spec_boolean ("is-narrow", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment"); g_object_class_override_property (object_class, PROP_TITLE, "title"); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-installed-page.ui"); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, group_install_in_progress); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, group_install_apps); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, group_install_system_apps); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, group_install_addons); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, group_install_web_apps); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, list_box_install_in_progress); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, list_box_install_apps); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, list_box_install_system_apps); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, list_box_install_addons); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, list_box_install_web_apps); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, scrolledwindow_install); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, spinner_install); gtk_widget_class_bind_template_child (widget_class, GsInstalledPage, stack_install); gtk_widget_class_bind_template_callback (widget_class, gs_installed_page_app_row_activated_cb); } static void gs_installed_page_init (GsInstalledPage *self) { gtk_widget_init_template (GTK_WIDGET (self)); self->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->sizegroup_button_label = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->sizegroup_button_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); self->settings = g_settings_new ("org.gnome.software"); } /** * gs_installed_page_get_is_narrow: * @self: a #GsInstalledPage * * Get the value of #GsInstalledPage:is-narrow. * * Returns: %TRUE if the page is in narrow mode, %FALSE otherwise * * Since: 41 */ gboolean gs_installed_page_get_is_narrow (GsInstalledPage *self) { g_return_val_if_fail (GS_IS_INSTALLED_PAGE (self), FALSE); return self->is_narrow; } /** * gs_installed_page_set_is_narrow: * @self: a #GsInstalledPage * @is_narrow: %TRUE to set the page in narrow mode, %FALSE otherwise * * Set the value of #GsInstalledPage:is-narrow. * * Since: 41 */ void gs_installed_page_set_is_narrow (GsInstalledPage *self, gboolean is_narrow) { g_return_if_fail (GS_IS_INSTALLED_PAGE (self)); is_narrow = !!is_narrow; if (self->is_narrow == is_narrow) return; self->is_narrow = is_narrow; g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_IS_NARROW]); } GsInstalledPage * gs_installed_page_new (void) { GsInstalledPage *self; self = g_object_new (GS_TYPE_INSTALLED_PAGE, NULL); return GS_INSTALLED_PAGE (self); }