🏡 index : quine-global-labs/bazaar.git

/* bz-transact-icon.c
 *
 * Copyright 2026 Eva M
 *
 * 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
 */

#include <bge.h>

#include "bz-application.h"
#include "bz-lazy-wdgt.h"
#include "bz-transact-icon.h"
#include "progress-bar-designs/common.h"

struct _BzTransactIcon
{
  AdwBin parent_instance;

  BzTransactIconInfo *info;

  GSettings                 *settings;
  BzTransactionManager      *ts_manager;
  GListModel                *trackers;
  BzTransactionEntryTracker *tracker;

  char *pride_class;

  BzLazyWdgt *wdgt;
};

G_DEFINE_FINAL_TYPE (BzTransactIcon, bz_transact_icon, ADW_TYPE_BIN);

enum
{
  PROP_0,

  PROP_INFO,

  LAST_PROP
};
static GParamSpec *props[LAST_PROP] = { 0 };

static void
apply_state (BzTransactIcon *self);

static void
update_tracker (BzTransactIcon *self);

static void
check_tracker (BzTransactIcon *self);

static void
ensure_draw_css (BzTransactIcon *self);

static void
update_icon (BzTransactIcon *self);

static void
info_state_notify (BzTransactIcon     *self,
                   GParamSpec         *pspec,
                   BzTransactIconInfo *info);

static void
info_group_notify (BzTransactIcon     *self,
                   GParamSpec         *pspec,
                   BzTransactIconInfo *info);

static void
info_paintable_notify (BzTransactIcon     *self,
                       GParamSpec         *pspec,
                       BzTransactIconInfo *info);

static void
trackers_items_changed (BzTransactIcon *self,
                        guint           position,
                        guint           removed,
                        guint           added,
                        GListModel     *model);

static void
tracker_notify (BzTransactIcon            *self,
                GParamSpec                *pspec,
                BzTransactionEntryTracker *tracker);

static void
pride_flag_changed (BzTransactIcon *self,
                    const char     *key,
                    GSettings      *settings);

static gboolean
next_frame_tick_cb (GtkWidget     *widget,
                    GdkFrameClock *frame_clock,
                    char          *apply_state);

static void
bz_transact_icon_dispose (GObject *object)
{
  BzTransactIcon *self = BZ_TRANSACT_ICON (object);

  if (self->info != NULL)
    {
      g_signal_handlers_disconnect_by_func (
          self->info, info_state_notify, self);
      g_signal_handlers_disconnect_by_func (
          self->info, info_group_notify, self);
      g_signal_handlers_disconnect_by_func (
          self->info, info_paintable_notify, self);
    }
  g_clear_pointer (&self->info, g_object_unref);

  if (self->trackers != NULL)
    g_signal_handlers_disconnect_by_func (
        self->trackers, trackers_items_changed, self);
  if (self->tracker != NULL)
    g_signal_handlers_disconnect_by_func (
        self->tracker, tracker_notify, self);
  if (self->settings != NULL)
    g_signal_handlers_disconnect_by_func (
        self->settings, pride_flag_changed, self);
  g_clear_object (&self->settings);
  g_clear_object (&self->ts_manager);
  g_clear_object (&self->trackers);
  g_clear_object (&self->tracker);

  g_clear_pointer (&self->pride_class, g_free);

  G_OBJECT_CLASS (bz_transact_icon_parent_class)->dispose (object);
}

static void
bz_transact_icon_get_property (GObject    *object,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  BzTransactIcon *self = BZ_TRANSACT_ICON (object);

  switch (prop_id)
    {
    case PROP_INFO:
      g_value_set_object (value, bz_transact_icon_get_info (self));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
bz_transact_icon_set_property (GObject      *object,
                               guint         prop_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  BzTransactIcon *self = BZ_TRANSACT_ICON (object);

  switch (prop_id)
    {
    case PROP_INFO:
      bz_transact_icon_set_info (self, g_value_get_object (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
bz_transact_icon_class_init (BzTransactIconClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = bz_transact_icon_set_property;
  object_class->get_property = bz_transact_icon_get_property;
  object_class->dispose      = bz_transact_icon_dispose;

  props[PROP_INFO] =
      g_param_spec_object (
          "info",
          NULL, NULL,
          BZ_TYPE_TRANSACT_ICON_INFO,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (object_class, LAST_PROP, props);
}

static void
bz_transact_icon_init (BzTransactIcon *self)
{
  GtkWidget *placeholder_icon = NULL;

  self->wdgt = bz_lazy_wdgt_new ();
  bz_lazy_wdgt_set_wdgt_resource (self->wdgt, "/io/github/kolunmi/Bazaar/bz-transact-icon.wdgt");
  g_object_bind_property (self, "info", self->wdgt, "wdgt-reference", G_BINDING_DEFAULT);
  adw_bin_set_child (ADW_BIN (self), GTK_WIDGET (self->wdgt));

  placeholder_icon = gtk_image_new ();
  gtk_widget_add_css_class (placeholder_icon, "icon-dropshadow");
  g_object_bind_property (self, "height-request", placeholder_icon, "pixel-size", G_BINDING_SYNC_CREATE);

  adw_bin_set_child (ADW_BIN (self->wdgt), placeholder_icon);
  update_icon (self);
}

BzTransactIcon *
bz_transact_icon_new (void)
{
  return g_object_new (BZ_TYPE_TRANSACT_ICON, NULL);
}

BzTransactIconInfo *
bz_transact_icon_get_info (BzTransactIcon *self)
{
  g_return_val_if_fail (BZ_IS_TRANSACT_ICON (self), NULL);
  return self->info;
}

void
bz_transact_icon_set_info (BzTransactIcon     *self,
                           BzTransactIconInfo *info)
{
  g_return_if_fail (BZ_IS_TRANSACT_ICON (self));
  g_return_if_fail (info == NULL || BZ_IS_TRANSACT_ICON_INFO (info));

  if (info == self->info)
    return;

  if (self->info != NULL)
    {
      g_signal_handlers_disconnect_by_func (
          self->info, info_state_notify, self);
      g_signal_handlers_disconnect_by_func (
          self->info, info_group_notify, self);
      g_signal_handlers_disconnect_by_func (
          self->info, info_paintable_notify, self);
    }

  g_clear_pointer (&self->info, g_object_unref);
  if (info != NULL)
    {
      self->info = g_object_ref (info);
      g_signal_connect_swapped (self->info, "notify::state", G_CALLBACK (info_state_notify), self);
      g_signal_connect_swapped (self->info, "notify::group", G_CALLBACK (info_group_notify), self);
      g_signal_connect_swapped (self->info, "notify::paintable", G_CALLBACK (info_paintable_notify), self);
    }

  apply_state (self);
  update_icon (self);

  bz_transact_icon_info_set_state (
      info,
      bz_state_info_get_default ());

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INFO]);
}

static void
apply_state (BzTransactIcon *self)
{
  if (self->trackers != NULL)
    g_signal_handlers_disconnect_by_func (
        self->trackers, trackers_items_changed, self);
  if (self->tracker != NULL)
    g_signal_handlers_disconnect_by_func (
        self->tracker, tracker_notify, self);
  if (self->settings != NULL)
    g_signal_handlers_disconnect_by_func (
        self->settings, pride_flag_changed, self);
  g_clear_object (&self->settings);
  g_clear_object (&self->ts_manager);
  g_clear_object (&self->trackers);
  g_clear_object (&self->tracker);

  if (self->info != NULL)
    {
      BzStateInfo *state = NULL;

      state = bz_transact_icon_info_get_state (self->info);
      if (state != NULL)
        {
          g_object_get (
              state,
              "transaction-manager", &self->ts_manager,
              "settings", &self->settings,
              NULL);
          if (self->ts_manager != NULL)
            {
              g_object_get (
                  self->ts_manager,
                  "all-trackers", &self->trackers,
                  NULL);
              if (self->trackers != NULL)
                g_signal_connect_swapped (
                    self->trackers, "items-changed",
                    G_CALLBACK (trackers_items_changed), self);
            }
          if (self->settings != NULL)
            {
              g_signal_connect_swapped (
                  self->settings, "changed::global-progress-bar-theme",
                  G_CALLBACK (pride_flag_changed), self);
            }
        }
    }

  update_tracker (self);
  ensure_draw_css (self);
}

static void
update_tracker (BzTransactIcon *self)
{
  g_autoptr (BzTransactionEntryTracker) found = NULL;

  if (self->trackers != NULL)
    {
      BzEntryGroup *group = NULL;

      group = bz_transact_icon_info_get_group (self->info);
      if (group != NULL)
        {
          const char *id         = NULL;
          guint       n_trackers = 0;

          id         = bz_entry_group_get_id (group);
          n_trackers = g_list_model_get_n_items (self->trackers);

          for (guint i = 0; i < n_trackers; i++)
            {
              g_autoptr (BzTransactionEntryTracker) tracker = NULL;
              BzEntry *entry                                = NULL;

              tracker = g_list_model_get_item (self->trackers, i);
              entry   = bz_transaction_entry_tracker_get_entry (tracker);
              if (entry == NULL)
                continue;

              if (g_strcmp0 (bz_entry_get_id (entry), id) == 0)
                {
                  found = g_steal_pointer (&tracker);
                  break;
                }
            }
        }
    }

  bz_transact_icon_info_set_tracker (self->info, found);
  if (found != self->tracker)
    {
      if (self->tracker != NULL)
        g_signal_handlers_disconnect_by_func (
            self->tracker, tracker_notify, self);
      g_clear_object (&self->tracker);

      if (found != NULL)
        {
          self->tracker = g_object_ref (found);
          g_signal_connect_swapped (self->tracker, "notify::active", G_CALLBACK (tracker_notify), self);
          g_signal_connect_swapped (self->tracker, "notify::pending", G_CALLBACK (tracker_notify), self);
          g_signal_connect_swapped (self->tracker, "notify::status", G_CALLBACK (tracker_notify), self);
        }

      check_tracker (self);
    }
}

static void
check_tracker (BzTransactIcon *self)
{
  const char *state = NULL;

  if (self->tracker != NULL)
    {
      gboolean                 active  = FALSE;
      gboolean                 pending = FALSE;
      BzTransactionEntryStatus status  = 0;

      active  = bz_transaction_entry_tracker_get_active (self->tracker);
      pending = bz_transaction_entry_tracker_get_pending (self->tracker);
      status  = bz_transaction_entry_tracker_get_status (self->tracker);

      if ((active || pending) && status == BZ_TRANSACTION_ENTRY_STATUS_CANCELLED)
        state = "inactive";
      else if (pending || status == BZ_TRANSACTION_ENTRY_STATUS_QUEUED)
        state = "pending";
      else if (!active || status == BZ_TRANSACTION_ENTRY_STATUS_DONE)
        state = "inactive";
      else
        state = "fraction";
    }
  else
    state = "inactive";

  if (!bz_lazy_wdgt_get_activated (self->wdgt))
    {
      /* Ensure smooth animation from idle; hacky but if we schedule the state
         change on the next animation tick, then it will appear to smoothly
         animate from the placeholder */
      bz_lazy_wdgt_set_wdgt_state (self->wdgt, "inactive");
      gtk_widget_add_tick_callback (
          GTK_WIDGET (self),
          (GtkTickCallback) next_frame_tick_cb,
          g_strdup (state), g_free);
    }
  else
    bz_lazy_wdgt_set_wdgt_state (self->wdgt, state);

  update_icon (self);
  ensure_draw_css (self);
}

static void
ensure_draw_css (BzTransactIcon *self)
{
  GtkWidget *renderer          = NULL;
  g_autoptr (GtkWidget) widget = NULL;
  g_autofree char *id          = NULL;
  g_autofree char *class       = NULL;

  renderer = adw_bin_get_child (ADW_BIN (self->wdgt));
  if (!BGE_IS_WDGT_RENDERER (renderer))
    return;

  widget = bge_wdgt_renderer_lookup_object (BGE_WDGT_RENDERER (renderer), "flag");

  if (self->settings == NULL)
    {
      if (self->pride_class != NULL)
        gtk_widget_remove_css_class (widget, self->pride_class);
      g_clear_pointer (&self->pride_class, g_free);
      return;
    }

  id    = g_settings_get_string (self->settings, "global-progress-bar-theme");
  class = bz_dup_css_class_for_pride_id (id);

  if (self->pride_class != NULL &&
      g_strcmp0 (self->pride_class, class) == 0)
    return;

  if (self->pride_class != NULL)
    gtk_widget_remove_css_class (widget, self->pride_class);
  g_clear_pointer (&self->pride_class, g_free);
  gtk_widget_add_css_class (widget, class);
  self->pride_class = g_steal_pointer (&class);
}

static void
update_icon (BzTransactIcon *self)
{
  GtkWidget *renderer        = NULL;
  g_autoptr (GtkWidget) icon = NULL;
  GdkPaintable *paintable    = NULL;

  renderer = adw_bin_get_child (ADW_BIN (self->wdgt));
  if (BGE_IS_WDGT_RENDERER (renderer))
    icon = bge_wdgt_renderer_lookup_object (BGE_WDGT_RENDERER (renderer), "icon");
  else
    icon = g_object_ref (renderer);

  if (self->info != NULL)
    paintable = bz_transact_icon_info_get_paintable (self->info);

  if (paintable != NULL)
    gtk_image_set_from_paintable (GTK_IMAGE (icon), paintable);
  else
    gtk_image_set_from_icon_name (GTK_IMAGE (icon), "application-x-executable");
}

static void
info_state_notify (BzTransactIcon     *self,
                   GParamSpec         *pspec,
                   BzTransactIconInfo *info)
{
  apply_state (self);
}

static void
info_group_notify (BzTransactIcon     *self,
                   GParamSpec         *pspec,
                   BzTransactIconInfo *info)
{
  update_tracker (self);
}

static void
info_paintable_notify (BzTransactIcon     *self,
                       GParamSpec         *pspec,
                       BzTransactIconInfo *info)
{
  update_icon (self);
}

static void
trackers_items_changed (BzTransactIcon *self,
                        guint           position,
                        guint           removed,
                        guint           added,
                        GListModel     *model)
{
  update_tracker (self);
}

static void
tracker_notify (BzTransactIcon            *self,
                GParamSpec                *pspec,
                BzTransactionEntryTracker *tracker)
{
  check_tracker (self);
}

static void
pride_flag_changed (BzTransactIcon *self,
                    const char     *key,
                    GSettings      *settings)
{
  ensure_draw_css (self);
}

static gboolean
next_frame_tick_cb (GtkWidget     *widget,
                    GdkFrameClock *frame_clock,
                    char          *apply_state)
{
  BzTransactIcon *self = BZ_TRANSACT_ICON (widget);

  bz_lazy_wdgt_set_wdgt_state (self->wdgt, apply_state);
  return G_SOURCE_REMOVE;
}

/* End of bz-transact-icon.c */