*
* Copyright 2025 Adam Masciola
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "BAZAAR::FULL-VIEW-WIDGET"
#include <bzvala.h>
#include <glib/gi18n.h>
#include <json-glib/json-glib.h>
#include "bz-addon-tile.h"
#include "bz-addons-dialog.h"
#include "bz-age-rating-dialog.h"
#include "bz-app-size-dialog.h"
#include "bz-app-tile.h"
#include "bz-apps-page.h"
#include "bz-appstream-description-render.h"
#include "bz-context-tile.h"
#include "bz-developer-badge.h"
#include "bz-dynamic-list-view.h"
#include "bz-entry-inspector.h"
#include "bz-fading-clamp.h"
#include "bz-favorite-button.h"
#include "bz-flatpak-entry.h"
#include "bz-full-view.h"
#include "bz-hardware-support-dialog.h"
#include "bz-install-controls.h"
#include "bz-license-dialog.h"
#include "bz-metainfo-preview.h"
#include "bz-releases-list.h"
#include "bz-safety-dialog.h"
#include "bz-screenshot-page.h"
#include "bz-screenshots-carousel.h"
#include "bz-section-view.h"
#include "bz-share-list.h"
#include "bz-state-info.h"
#include "bz-stats-dialog.h"
#include "bz-window.h"
#include "context-tile-callbacks.h"
#include "error.h"
#include "safety-calculator.h"
#include "spdx.h"
#include "template-callbacks.h"
#include "util.h"
struct _BzFullView
{
AdwBin parent_instance;
BzStateInfo *state;
BzTransactionManager *transactions;
BzEntryGroup *group;
DexFuture *ui_future;
BzResult *ui_entry;
BzResult *runtime;
BzResult *group_model;
gboolean show_sidebar;
GtkScrolledWindow *main_scroll;
AdwViewStack *stack;
GtkWidget *shadow_overlay;
GtkToggleButton *description_toggle;
GtkWidget *wide_install_controls;
GtkWidget *narrow_install_controls;
};
G_DEFINE_FINAL_TYPE (BzFullView, bz_full_view, ADW_TYPE_BIN)
enum
{
PROP_0,
PROP_STATE,
PROP_ENTRY_GROUP,
PROP_UI_ENTRY,
LAST_PROP
};
static GParamSpec *props[LAST_PROP] = { 0 };
enum
{
SIGNAL_UPDATE,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL];
static void
bz_full_view_dispose (GObject *object)
{
BzFullView *self = BZ_FULL_VIEW (object);
dex_clear (&self->ui_future);
g_clear_object (&self->state);
g_clear_object (&self->transactions);
g_clear_object (&self->group);
g_clear_object (&self->ui_entry);
g_clear_object (&self->runtime);
g_clear_object (&self->group_model);
G_OBJECT_CLASS (bz_full_view_parent_class)->dispose (object);
}
static void
bz_full_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
BzFullView *self = BZ_FULL_VIEW (object);
switch (prop_id)
{
case PROP_STATE:
g_value_set_object (value, self->state);
break;
case PROP_ENTRY_GROUP:
g_value_set_object (value, bz_full_view_get_entry_group (self));
break;
case PROP_UI_ENTRY:
g_value_set_object (value, self->ui_entry);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
bz_full_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
BzFullView *self = BZ_FULL_VIEW (object);
switch (prop_id)
{
case PROP_STATE:
g_clear_object (&self->state);
self->state = g_value_dup_object (value);
break;
case PROP_ENTRY_GROUP:
bz_full_view_set_entry_group (self, g_value_get_object (value));
break;
case PROP_UI_ENTRY:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static gboolean
is_scrolled_down (gpointer object,
double value)
{
return value > 100.0;
}
static char *
format_as_link (gpointer object,
const char *value)
{
if (value != NULL)
return g_strdup_printf ("<a href=\"%s\" title=\"%s\">%s</a>",
value, value, value);
else
return g_strdup (_ ("No URL"));
}
static gboolean
has_link (gpointer object,
const char *license)
{
if (license == NULL || *license == '\0')
return FALSE;
return bz_spdx_is_valid (license);
}
static char *
pick_license_warning (gpointer object,
gboolean value)
{
return value
? g_strdup (_ ("This app has a FLOSS license, meaning the source code can be audited for safety."))
: g_strdup (_ ("This app has a proprietary license, meaning the source code is developed privately and cannot be audited by an independent third party."));
}
static char *
format_other_apps_label (gpointer object, const char *developer)
{
if (!developer || *developer == '\0')
return g_strdup (_ ("Other Apps From This Developer"));
return g_strdup_printf (_ ("Other Apps From %s"), developer);
}
static char *
format_more_other_apps_label (gpointer object, const char *developer)
{
return g_strdup (_ ("Show All"));
}
static char *
format_leftover_label (gpointer object, const char *name, guint64 size)
{
g_autofree char *formatted_size = NULL;
formatted_size = g_format_size (size);
return g_strdup_printf (_ ("%s is not installed, but it still has <b>%s</b> of data present."), name, formatted_size);
}
static gpointer
filter_own_app_id (BzEntry *entry, GtkStringList *app_ids)
{
const char *own_id;
g_autoptr (GtkStringList) filtered = NULL;
guint n_items = 0;
if (!BZ_IS_ENTRY (entry) || !GTK_IS_STRING_LIST (app_ids))
return NULL;
own_id = bz_entry_get_id (entry);
if (!own_id)
return NULL;
filtered = gtk_string_list_new (NULL);
n_items = g_list_model_get_n_items (G_LIST_MODEL (app_ids));
for (guint i = 0; i < n_items; i++)
{
const char *id = NULL;
id = gtk_string_list_get_string (app_ids, i);
if (g_strcmp0 (id, own_id) != 0)
gtk_string_list_append (filtered, id);
}
if (g_list_model_get_n_items (G_LIST_MODEL (filtered)) > 0)
return g_steal_pointer (&filtered);
else
return NULL;
}
static GListModel *
get_developer_apps_entries (gpointer object, GtkStringList *app_ids, BzEntry *entry)
{
BzFullView *self = BZ_FULL_VIEW (object);
g_autoptr (GtkStringList) filtered = filter_own_app_id (BZ_ENTRY (entry), app_ids);
BzApplicationMapFactory *factory;
if (!filtered)
return NULL;
factory = bz_state_info_get_application_factory (self->state);
if (!factory)
return NULL;
return bz_application_map_factory_generate (factory, G_LIST_MODEL (filtered));
}
static void
more_apps_button_clicked_cb (BzFullView *self,
GtkButton *button)
{
g_autoptr (GListModel) model = NULL;
guint n_items;
g_autofree char *title = NULL;
g_autofree char *subtitle = NULL;
AdwNavigationPage *apps_page = NULL;
GtkWidget *nav_view = NULL;
g_autoptr (GListModel) app_ids = NULL;
BzEntry *entry = NULL;
const char *developer = NULL;
g_return_if_fail (BZ_IS_FULL_VIEW (self));
g_return_if_fail (GTK_IS_BUTTON (button));
entry = bz_result_get_object (self->ui_entry);
if (entry == NULL)
return;
g_object_get (entry, "developer-apps", &app_ids, NULL);
model = bz_application_map_factory_generate (
bz_state_info_get_application_factory (self->state),
app_ids);
n_items = g_list_model_get_n_items (model);
developer = bz_entry_get_developer (entry);
if (developer != NULL && *developer != '\0')
title = g_strdup_printf (_ ("Apps by %s"), developer);
else
title = g_strdup (_ ("Apps by This Developer"));
subtitle = g_strdup_printf (ngettext ("%d Application", "%d Applications", n_items), n_items);
apps_page = bz_apps_page_new (title, model);
bz_apps_page_set_subtitle (BZ_APPS_PAGE (apps_page), subtitle);
nav_view = gtk_widget_get_ancestor (GTK_WIDGET (self), ADW_TYPE_NAVIGATION_VIEW);
if (nav_view != NULL)
adw_navigation_view_push (ADW_NAVIGATION_VIEW (nav_view), apps_page);
}
static void
open_url_cb (BzFullView *self,
AdwActionRow *row)
{
BzEntry *entry = NULL;
const char *url = NULL;
entry = BZ_ENTRY (bz_result_get_object (self->ui_entry));
url = bz_entry_get_url (entry);
if (url != NULL && *url != '\0')
gtk_widget_activate_action (GTK_WIDGET (row), "window.open-uri", "s", url);
}
static void
license_cb (BzFullView *self,
GtkButton *button)
{
AdwDialog *dialog = NULL;
BzEntry *ui_entry = NULL;
if (self->group == NULL)
return;
ui_entry = bz_result_get_object (self->ui_entry);
if (ui_entry == NULL)
return;
dialog = bz_license_dialog_new (ui_entry);
adw_dialog_present (dialog, GTK_WIDGET (self));
}
static void
age_rating_cb (BzFullView *self,
GtkButton *button)
{
BzAgeRatingDialog *dialog = NULL;
BzEntry *ui_entry = NULL;
if (self->group == NULL)
return;
ui_entry = bz_result_get_object (self->ui_entry);
if (ui_entry == NULL)
return;
dialog = bz_age_rating_dialog_new (ui_entry);
adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (self));
}
static void
dl_stats_cb (BzFullView *self,
GtkButton *button)
{
AdwDialog *dialog = NULL;
AdwBreakpointBin *bin = NULL;
BzEntry *ui_entry = NULL;
if (self->group == NULL)
return;
ui_entry = bz_result_get_object (self->ui_entry);
bin = bz_stats_dialog_new (NULL, NULL, 0);
dialog = adw_dialog_new ();
adw_dialog_set_content_width (dialog, 1250);
adw_dialog_set_content_height (dialog, 750);
adw_dialog_set_child (dialog, GTK_WIDGET (bin));
g_object_bind_property (ui_entry, "download-stats", bin, "model", G_BINDING_SYNC_CREATE);
g_object_bind_property (ui_entry, "download-stats-per-country", bin, "country-model", G_BINDING_SYNC_CREATE);
g_object_bind_property (ui_entry, "total-downloads", bin, "total-downloads", G_BINDING_SYNC_CREATE);
adw_dialog_present (dialog, GTK_WIDGET (self));
bz_stats_dialog_animate_open (BZ_STATS_DIALOG (bin));
}
static void
size_cb (BzFullView *self,
GtkButton *button)
{
AdwDialog *size_dialog = NULL;
if (self->group == NULL)
return;
size_dialog = bz_app_size_dialog_new (self->group);
adw_dialog_present (size_dialog, GTK_WIDGET (self));
}
static void
formfactor_cb (BzFullView *self,
GtkButton *button)
{
AdwDialog *dialog = NULL;
BzEntry *ui_entry = NULL;
if (self->group == NULL)
return;
ui_entry = bz_result_get_object (self->ui_entry);
dialog = ADW_DIALOG (bz_hardware_support_dialog_new (ui_entry));
adw_dialog_present (dialog, GTK_WIDGET (self));
}
static void
safety_cb (BzFullView *self,
GtkButton *button)
{
AdwDialog *dialog = NULL;
BzEntry *ui_entry = NULL;
if (self->group == NULL)
return;
ui_entry = bz_result_get_object (self->ui_entry);
if (ui_entry == NULL)
return;
dialog = bz_safety_dialog_new (ui_entry);
adw_dialog_present (dialog, GTK_WIDGET (self));
}
static void
update_cb (BzFullView *self,
GListModel *entries,
BzInstallControls *controls)
{
g_signal_emit (self, signals[SIGNAL_UPDATE], 0, entries);
}
static DexFuture *
reap_user_data_done (DexFuture *future,
GWeakRef *wr)
{
g_autoptr (BzFullView) self = NULL;
g_autoptr (GError) local_error = NULL;
dex_future_get_value (future, &local_error);
self = g_weak_ref_get (wr);
if (self != NULL && local_error != NULL)
bz_show_error_for_widget (
GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self))),
_ ("Failed to Remove User Data"),
local_error->message);
return dex_future_new_true ();
}
static void
delete_user_data_cb (BzFullView *self,
GtkButton *button)
{
g_autoptr (DexFuture) future = NULL;
g_return_if_fail (BZ_IS_FULL_VIEW (self));
if (self->group == NULL)
return;
future = bz_entry_group_reap_user_data (self->group);
if (future != NULL)
dex_future_disown (dex_future_finally (
dex_ref (future),
(DexFutureCallback) reap_user_data_done,
bz_track_weak (self),
bz_weak_release));
}
static void
support_cb (BzFullView *self,
GtkButton *button)
{
BzEntry *entry = NULL;
const char *url = NULL;
entry = bz_result_get_object (self->ui_entry);
if (entry == NULL)
return;
url = bz_entry_get_donation_url (entry);
if (url != NULL && *url != '\0')
gtk_widget_activate_action (GTK_WIDGET (button), "window.open-uri", "s", url);
}
static GListModel *
get_addon_groups (BzFullView *self,
BzEntryGroup *group)
{
BzApplicationMapFactory *factory = NULL;
GListModel *ids = NULL;
GListModel *groups = NULL;
if (group == NULL)
return NULL;
ids = bz_entry_group_get_addon_group_ids (group);
if (ids == NULL)
return NULL;
factory = bz_state_info_get_application_factory (self->state);
groups = bz_application_map_factory_generate (factory, ids);
if (groups == NULL)
return NULL;
return G_LIST_MODEL (gtk_slice_list_model_new (groups, 0, 3));
}
static gboolean
should_show_addon_overflow (gpointer object,
BzEntryGroup *group)
{
GListModel *ids = NULL;
if (group == NULL)
return FALSE;
ids = bz_entry_group_get_addon_group_ids (group);
if (ids == NULL)
return FALSE;
return g_list_model_get_n_items (ids) > 3;
}
static void
other_source_download_cb (BzFullView *self,
BzEntry *entry)
{
GtkWidget *window = NULL;
GtkAdjustment *adj = NULL;
AdwAnimationTarget *target = NULL;
AdwAnimation *animation = NULL;
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->main_scroll));
target = adw_property_animation_target_new (G_OBJECT (adj), "value");
animation = adw_timed_animation_new (
GTK_WIDGET (self), gtk_adjustment_get_value (adj),
0.0, 300, target);
adw_timed_animation_set_easing (ADW_TIMED_ANIMATION (animation), ADW_EASE_OUT_CUBIC);
adw_animation_play (animation);
bz_install_controls_grab_focus (BZ_INSTALL_CONTROLS (self->narrow_install_controls));
bz_install_controls_grab_focus (BZ_INSTALL_CONTROLS (self->wide_install_controls));
window = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));
if (BZ_IS_WINDOW (window))
bz_window_install_entry (BZ_WINDOW (window), entry);
}
static void
install_addons_cb (BzFullView *self,
GtkButton *button)
{
if (self->group == NULL)
return;
gtk_widget_activate_action (GTK_WIDGET (self), "window.addons-group", "s",
bz_entry_group_get_id (self->group));
}
static void
addon_tile_activated_cb (BzAddonTile *tile)
{
BzFullView *self = NULL;
BzEntryGroup *group = NULL;
AdwDialog *dialog = NULL;
self = BZ_FULL_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (tile), BZ_TYPE_FULL_VIEW));
group = bz_addon_tile_get_group (tile);
if (group == NULL)
return;
dialog = bz_addons_dialog_new_single (group);
adw_dialog_present (dialog, GTK_WIDGET (self));
}
static int
get_description_max_height (gpointer object,
gboolean active)
{
return active ? 10000 : 170;
}
static char *
get_description_toggle_text (gpointer object,
gboolean active)
{
return g_strdup (active ? _ ("Show Less") : _ ("Show More"));
}
static gboolean
metainfo_banner_visible (gpointer object,
const char *remote_name)
{
return g_strcmp0 (remote_name, "local-preview") == 0;
}
static void
preview_other_metainfo_cb (BzFullView *self,
AdwBanner *banner)
{
GtkWidget *window = NULL;
window = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));
bz_window_push_page (BZ_WINDOW (window),
create_entry_group_preview_page (self->group));
}
static void
copy_id_cb (BzFullView *self,
GtkButton *button)
{
const char *id = NULL;
GdkClipboard *clipboard = NULL;
if (self->group == NULL)
return;
id = bz_entry_group_get_id (self->group);
clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
gdk_clipboard_set_text (clipboard, id);
}
static void
debug_id_inspect_cb (BzFullView *self,
GtkButton *button)
{
g_autofree char *unique_id = NULL;
g_autoptr (GtkStringObject) string = NULL;
g_autoptr (BzResult) result = NULL;
if (self->group == NULL)
return;
unique_id = bz_entry_group_dup_ui_entry_id (self->group);
result = bz_application_map_factory_convert_one (
bz_state_info_get_entry_factory (self->state),
gtk_string_object_new (unique_id));
if (result != NULL)
{
BzEntryInspector *inspector = NULL;
inspector = bz_entry_inspector_new ();
bz_entry_inspector_set_result (inspector, result);
gtk_window_present (GTK_WINDOW (inspector));
}
}
static void
bz_full_view_class_init (BzFullViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = bz_full_view_dispose;
object_class->get_property = bz_full_view_get_property;
object_class->set_property = bz_full_view_set_property;
props[PROP_STATE] =
g_param_spec_object (
"state",
NULL, NULL,
BZ_TYPE_STATE_INFO,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
props[PROP_ENTRY_GROUP] =
g_param_spec_object (
"entry-group",
NULL, NULL,
BZ_TYPE_ENTRY_GROUP,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_UI_ENTRY] =
g_param_spec_object (
"ui-entry",
NULL, NULL,
BZ_TYPE_RESULT,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, props);
signals[SIGNAL_UPDATE] =
g_signal_new (
"update",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
G_TYPE_LIST_MODEL);
g_signal_set_va_marshaller (
signals[SIGNAL_UPDATE],
G_TYPE_FROM_CLASS (klass),
g_cclosure_marshal_VOID__OBJECTv);
g_type_ensure (BZ_TYPE_APPSTREAM_DESCRIPTION_RENDER);
g_type_ensure (BZ_TYPE_DEVELOPER_BADGE);
g_type_ensure (BZ_TYPE_DYNAMIC_LIST_VIEW);
g_type_ensure (BZ_TYPE_ENTRY);
g_type_ensure (BZ_TYPE_ENTRY_GROUP);
g_type_ensure (BZ_TYPE_FADING_CLAMP);
g_type_ensure (BZ_TYPE_FAVORITE_BUTTON);
g_type_ensure (BZ_TYPE_FLATPAK_ENTRY);
g_type_ensure (BZ_TYPE_HARDWARE_SUPPORT_DIALOG);
g_type_ensure (BZ_TYPE_INSTALL_CONTROLS);
g_type_ensure (BZ_TYPE_SECTION_VIEW);
g_type_ensure (BZ_TYPE_RELEASES_PREVIEW);
g_type_ensure (BZ_TYPE_SCREENSHOTS_CAROUSEL);
g_type_ensure (BZ_TYPE_SHARE_LIST);
g_type_ensure (BZ_TYPE_CONTEXT_TILE);
g_type_ensure (BZ_TYPE_ADDON_TILE);
g_type_ensure (BZ_TYPE_OTHER_SOURCES_LIST);
gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-full-view.ui");
bz_widget_class_bind_all_util_callbacks (widget_class);
bz_widget_class_bind_all_context_tile_callbacks (widget_class);
gtk_widget_class_bind_template_child (widget_class, BzFullView, stack);
gtk_widget_class_bind_template_child (widget_class, BzFullView, main_scroll);
gtk_widget_class_bind_template_child (widget_class, BzFullView, shadow_overlay);
gtk_widget_class_bind_template_child (widget_class, BzFullView, description_toggle);
gtk_widget_class_bind_template_child (widget_class, BzFullView, wide_install_controls);
gtk_widget_class_bind_template_child (widget_class, BzFullView, narrow_install_controls);
gtk_widget_class_bind_template_callback (widget_class, is_scrolled_down);
gtk_widget_class_bind_template_callback (widget_class, age_rating_cb);
gtk_widget_class_bind_template_callback (widget_class, format_as_link);
gtk_widget_class_bind_template_callback (widget_class, has_link);
gtk_widget_class_bind_template_callback (widget_class, format_leftover_label);
gtk_widget_class_bind_template_callback (widget_class, format_other_apps_label);
gtk_widget_class_bind_template_callback (widget_class, format_more_other_apps_label);
gtk_widget_class_bind_template_callback (widget_class, get_developer_apps_entries);
gtk_widget_class_bind_template_callback (widget_class, more_apps_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, open_url_cb);
gtk_widget_class_bind_template_callback (widget_class, license_cb);
gtk_widget_class_bind_template_callback (widget_class, dl_stats_cb);
gtk_widget_class_bind_template_callback (widget_class, size_cb);
gtk_widget_class_bind_template_callback (widget_class, formfactor_cb);
gtk_widget_class_bind_template_callback (widget_class, safety_cb);
gtk_widget_class_bind_template_callback (widget_class, update_cb);
gtk_widget_class_bind_template_callback (widget_class, delete_user_data_cb);
gtk_widget_class_bind_template_callback (widget_class, support_cb);
gtk_widget_class_bind_template_callback (widget_class, pick_license_warning);
gtk_widget_class_bind_template_callback (widget_class, get_addon_groups);
gtk_widget_class_bind_template_callback (widget_class, should_show_addon_overflow);
gtk_widget_class_bind_template_callback (widget_class, install_addons_cb);
gtk_widget_class_bind_template_callback (widget_class, other_source_download_cb);
gtk_widget_class_bind_template_callback (widget_class, addon_tile_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, get_description_max_height);
gtk_widget_class_bind_template_callback (widget_class, get_description_toggle_text);
gtk_widget_class_bind_template_callback (widget_class, metainfo_banner_visible);
gtk_widget_class_bind_template_callback (widget_class, preview_other_metainfo_cb);
gtk_widget_class_bind_template_callback (widget_class, copy_id_cb);
gtk_widget_class_bind_template_callback (widget_class, debug_id_inspect_cb);
}
static void
bz_full_view_init (BzFullView *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
GtkWidget *
bz_full_view_new (void)
{
return g_object_new (BZ_TYPE_FULL_VIEW, NULL);
}
static DexFuture *
on_ui_entry_resolved (DexFuture *future,
GWeakRef *wr)
{
g_autoptr (BzFullView) self = NULL;
BzEntry *ui_entry = NULL;
g_autoptr (BzResult) runtime_result = NULL;
const GValue *value = NULL;
bz_weak_get_or_return_reject (self, wr);
value = dex_future_get_value (future, NULL);
if (value != NULL && G_VALUE_HOLDS_OBJECT (value))
{
ui_entry = g_value_get_object (value);
if (BZ_IS_FLATPAK_ENTRY (ui_entry))
self->runtime = bz_flatpak_entry_dup_runtime_result (BZ_FLATPAK_ENTRY (ui_entry));
}
adw_view_stack_set_visible_child_name (self->stack, "content");
return dex_future_new_for_boolean (TRUE);
}
static gboolean
idle_focus_install (GWeakRef *wr)
{
g_autoptr (BzFullView) self = NULL;
self = g_weak_ref_get (wr);
if (self == NULL)
return G_SOURCE_REMOVE;
bz_install_controls_grab_focus (BZ_INSTALL_CONTROLS (self->narrow_install_controls));
bz_install_controls_grab_focus (BZ_INSTALL_CONTROLS (self->wide_install_controls));
return G_SOURCE_REMOVE;
}
void
bz_full_view_set_entry_group (BzFullView *self,
BzEntryGroup *group)
{
g_return_if_fail (BZ_IS_FULL_VIEW (self));
g_return_if_fail (group == NULL ||
BZ_IS_ENTRY_GROUP (group));
if (group == self->group)
return;
if (group == NULL)
return;
dex_clear (&self->ui_future);
g_clear_object (&self->group);
g_clear_object (&self->ui_entry);
g_clear_object (&self->runtime);
g_clear_object (&self->group_model);
gtk_toggle_button_set_active (self->description_toggle, FALSE);
self->group = g_object_ref (group);
self->ui_entry = bz_entry_group_dup_ui_entry (group);
if (self->ui_entry != NULL && bz_result_get_resolved (self->ui_entry))
{
BzEntry *entry = NULL;
g_autoptr (GListStore) store = NULL;
g_autoptr (DexFuture) future = NULL;
g_autoptr (DexFuture) object_future = NULL;
GWeakRef *wr = NULL;
entry = bz_result_get_object (self->ui_entry);
store = g_list_store_new (BZ_TYPE_ENTRY);
g_list_store_append (store, entry);
future = dex_future_new_for_object (store);
self->group_model = bz_result_new (future);
object_future = dex_future_new_for_object (entry);
wr = bz_track_weak (self);
dex_unref (on_ui_entry_resolved (object_future, wr));
bz_weak_release (wr);
}
else
{
g_autoptr (DexFuture) future = NULL;
future = bz_entry_group_dup_all_into_store (group);
self->group_model = bz_result_new (future);
if (self->ui_entry != NULL)
{
g_autoptr (DexFuture) ui_future = NULL;
adw_view_stack_set_visible_child_name (self->stack, "loading");
ui_future = bz_result_dup_future (self->ui_entry);
ui_future = dex_future_then (
ui_future,
(DexFutureCallback) on_ui_entry_resolved,
bz_track_weak (self),
bz_weak_release);
self->ui_future = g_steal_pointer (&ui_future);
}
}
gtk_adjustment_set_value (gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->main_scroll)), 0.0);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENTRY_GROUP]);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_UI_ENTRY]);
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) idle_focus_install, bz_track_weak (self), bz_weak_release);
}
BzEntryGroup *
bz_full_view_get_entry_group (BzFullView *self)
{
g_return_val_if_fail (BZ_IS_FULL_VIEW (self), NULL);
return self->group;
}