/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikatoolbar.c
 * Copyright (C) 2023 Jehan
 *
 * 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/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"

#include "widgets-types.h"

#include "core/pika.h"

#include "pikaaction.h"
#include "pikaenumaction.h"
#include "pikaprocedureaction.h"
#include "pikaradioaction.h"
#include "pikatoggleaction.h"
#include "pikatoolbar.h"
#include "pikamenumodel.h"
#include "pikamenushell.h"
#include "pikauimanager.h"

#define PIKA_TOOLBAR_ACTION_KEY "pika-toolbar-action"


/**
 * PikaToolbar:
 *
 * Our own toolbar widget.
 *
 * It can be initialized with a GMenuModel.
 *
 * TODO: in the future, we could also give optional GUI-customizability to use
 * it as a generic toolbar in PIKA's main window (where people could add any
 * actions as buttons).
 */


/*  local function prototypes  */

static void   pika_toolbar_iface_init              (PikaMenuShellInterface  *iface);

static void   pika_toolbar_append                  (PikaMenuShell           *shell,
                                                    PikaMenuModel           *model);
static void   pika_toolbar_add_ui                  (PikaMenuShell           *shell,
                                                    const gchar            **paths,
                                                    const gchar             *action_name,
                                                    gboolean                 top);

static void   pika_toolbar_add_action              (PikaToolbar             *toolbar,
                                                    const gchar             *action_name,
                                                    gboolean                 top);

static void   pika_toolbar_toggle_item_toggled     (GtkToggleToolButton     *item,
                                                    PikaToggleAction        *action);

static void   pika_toolbar_toggle_action_toggled   (PikaToggleAction        *action,
                                                    GtkToggleToolButton     *item);
static void   pika_toolbar_action_notify_sensitive (PikaAction              *action,
                                                    const GParamSpec        *pspec,
                                                    GtkWidget               *item);
static void   pika_toolbar_action_notify_visible   (PikaAction              *action,
                                                    const GParamSpec        *pspec,
                                                    GtkWidget               *item);


G_DEFINE_TYPE_WITH_CODE (PikaToolbar, pika_toolbar, GTK_TYPE_TOOLBAR,
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_MENU_SHELL, pika_toolbar_iface_init))

#define parent_class pika_toolbar_parent_class


/* Class functions */

static void
pika_toolbar_class_init (PikaToolbarClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->get_property = pika_menu_shell_get_property;
  object_class->set_property = pika_menu_shell_set_property;

  pika_menu_shell_install_properties (object_class);
}

static void
pika_toolbar_iface_init (PikaMenuShellInterface *iface)
{
  iface->append = pika_toolbar_append;
  iface->add_ui = pika_toolbar_add_ui;
}

static void
pika_toolbar_init (PikaToolbar *bar)
{
  pika_menu_shell_init (PIKA_MENU_SHELL (bar));
}

static void
pika_toolbar_append (PikaMenuShell *shell,
                     PikaMenuModel *model)
{
  PikaToolbar *toolbar = PIKA_TOOLBAR (shell);
  gint         n_items;

  g_return_if_fail (GTK_IS_CONTAINER (shell));

  n_items = g_menu_model_get_n_items (G_MENU_MODEL (model));
  for (gint i = 0; i < n_items; i++)
    {
      GMenuModel *subsection;
      GMenuModel *submenu;
      gchar      *label       = NULL;
      gchar      *action_name = NULL;

      subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION);
      submenu    = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU);
      g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
      g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name);

      if (subsection != NULL)
        {
          GtkToolItem *item;

          item = gtk_separator_tool_item_new ();
          gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
          gtk_widget_show (GTK_WIDGET (item));

          pika_toolbar_append (shell, PIKA_MENU_MODEL (subsection));

          item = gtk_separator_tool_item_new ();
          gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
          gtk_widget_show (GTK_WIDGET (item));
        }
      else if (submenu == NULL && action_name != NULL)
        {
          pika_toolbar_add_action (toolbar, action_name, FALSE);
        }
      else
        {
          if (submenu != NULL)
            g_warning ("%s: unexpected submenu. Only actions and sections are allowed in toolbar's root.",
                       G_STRFUNC);
          else if (label != NULL)
            g_warning ("%s: unexpected label '%s'. Only actions and sections are allowed in toolbar's root.",
                       G_STRFUNC, label);
          else
            g_warning ("%s: unexpected toolbar item. Only actions and sections are allowed in toolbar's root.",
                       G_STRFUNC);
        }

      g_free (label);
      g_free (action_name);
      g_clear_object (&submenu);
      g_clear_object (&subsection);
    }
}

static void
pika_toolbar_add_ui (PikaMenuShell  *shell,
                     const gchar   **paths,
                     const gchar    *action_name,
                     gboolean        top)
{
  PikaToolbar *toolbar = PIKA_TOOLBAR (shell);

  g_return_if_fail (paths != NULL);

  if (paths[0] != NULL)
    g_warning ("%s: unexpected path '%s'. Menus are not allowed in toolbar's root.",
               G_STRFUNC, paths[0]);
  else
    pika_toolbar_add_action (toolbar, action_name, top);
}


/* Public functions */

GtkWidget *
pika_toolbar_new (PikaMenuModel *model,
                  PikaUIManager *manager)
{
  g_return_val_if_fail (PIKA_IS_UI_MANAGER (manager) &&
                        G_IS_MENU_MODEL (model), NULL);

  return g_object_new (PIKA_TYPE_TOOLBAR,
                       "model",   model,
                       "manager", manager,
                       NULL);
}


/* Private functions */

static void
pika_toolbar_add_action (PikaToolbar *toolbar,
                         const gchar *action_name,
                         gboolean     top)
{
  PikaUIManager *manager;
  PikaAction    *action;
  const gchar   *action_label;
  GtkToolItem   *item;
  gboolean       visible;

  g_return_if_fail (PIKA_IS_TOOLBAR (toolbar));

  manager = pika_menu_shell_get_manager (PIKA_MENU_SHELL (toolbar));
  action  = pika_ui_manager_find_action (manager, NULL, action_name);

  g_return_if_fail (PIKA_IS_ACTION (action));

  action_label = pika_action_get_short_label (PIKA_ACTION (action));
  g_return_if_fail (action_label != NULL);

  if (PIKA_IS_TOGGLE_ACTION (action))
    {
      if (PIKA_IS_RADIO_ACTION (action))
        {
          GtkToolItem *sibling = NULL;
          GSList      *group = NULL;

          if (top)
            sibling = gtk_toolbar_get_nth_item (GTK_TOOLBAR (toolbar), 0);
          else if (gtk_toolbar_get_n_items (GTK_TOOLBAR (toolbar)) > 0)
            sibling = gtk_toolbar_get_nth_item (GTK_TOOLBAR (toolbar),
                                                gtk_toolbar_get_n_items (GTK_TOOLBAR (toolbar)) - 1);

          if (sibling != NULL && GTK_IS_RADIO_TOOL_BUTTON (sibling))
            {
              PikaAction *sibling_action;

              sibling_action = g_object_get_data (G_OBJECT (sibling), PIKA_TOOLBAR_ACTION_KEY);

              if (sibling_action != NULL                &&
                  PIKA_IS_RADIO_ACTION (sibling_action) &&
                  pika_radio_action_get_group (PIKA_RADIO_ACTION (sibling_action)) == pika_radio_action_get_group (PIKA_RADIO_ACTION (action)))
                group = gtk_radio_tool_button_get_group (GTK_RADIO_TOOL_BUTTON (sibling));
            }

          item = gtk_radio_tool_button_new (group);
        }
      else
        {
          item = gtk_toggle_tool_button_new ();
        }

      gtk_tool_button_set_label (GTK_TOOL_BUTTON (item), action_label);
      gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (item),
                                     pika_action_get_icon_name (PIKA_ACTION (action)));
      gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (item),
                                         pika_toggle_action_get_active (PIKA_TOGGLE_ACTION (action)));

      g_signal_connect (item, "toggled",
                        G_CALLBACK (pika_toolbar_toggle_item_toggled),
                        action);
      g_signal_connect_object (action, "toggled",
                               G_CALLBACK (pika_toolbar_toggle_action_toggled),
                               item, 0);
    }
  else if (PIKA_IS_PROCEDURE_ACTION (action) ||
           PIKA_IS_ENUM_ACTION (action))
    {
      item = gtk_tool_button_new (NULL, action_label);

      g_signal_connect_swapped (item, "clicked",
                                G_CALLBACK (pika_action_activate),
                                action);
    }
  else
    {
      item = gtk_tool_button_new (NULL, action_label);

      gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name);
    }

  g_object_set_data (G_OBJECT (item), PIKA_TOOLBAR_ACTION_KEY, action);

  pika_action_set_proxy (PIKA_ACTION (action), GTK_WIDGET (item));

  gtk_widget_set_sensitive (GTK_WIDGET (item),
                            pika_action_is_sensitive (PIKA_ACTION (action), NULL));
  g_signal_connect_object (action, "notify::sensitive",
                           G_CALLBACK (pika_toolbar_action_notify_sensitive),
                           item, 0);

  if (top)
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
  else
    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);

  visible = pika_action_is_visible (PIKA_ACTION (action));
  gtk_widget_set_visible (GTK_WIDGET (item), visible);
  g_signal_connect_object (action, "notify::visible",
                           G_CALLBACK (pika_toolbar_action_notify_visible),
                           item, 0);
}

static void
pika_toolbar_toggle_item_toggled (GtkToggleToolButton *item,
                                  PikaToggleAction    *action)
{
  gboolean active = gtk_toggle_tool_button_get_active (item);

  g_signal_handlers_block_by_func (action,
                                   G_CALLBACK (pika_toolbar_toggle_action_toggled),
                                   item);
  pika_toggle_action_set_active (action, active);
  g_signal_handlers_unblock_by_func (action,
                                     G_CALLBACK (pika_toolbar_toggle_action_toggled),
                                     item);
}

static void
pika_toolbar_toggle_action_toggled (PikaToggleAction    *action,
                                    GtkToggleToolButton *item)
{
  gboolean active = pika_toggle_action_get_active (action);

  g_signal_handlers_block_by_func (item,
                                   G_CALLBACK (pika_toolbar_toggle_item_toggled),
                                   action);
  gtk_toggle_tool_button_set_active (item, active);
  g_signal_handlers_unblock_by_func (item,
                                   G_CALLBACK (pika_toolbar_toggle_item_toggled),
                                   action);
}

static void
pika_toolbar_action_notify_sensitive (PikaAction       *action,
                                      const GParamSpec *pspec,
                                      GtkWidget        *item)
{
  gtk_widget_set_sensitive (item, pika_action_is_sensitive (action, NULL));
}

static void
pika_toolbar_action_notify_visible (PikaAction       *action,
                                    const GParamSpec *pspec,
                                    GtkWidget        *item)
{
  gtk_widget_set_visible (item, pika_action_is_visible (action));
}
