/* 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
 *
 * 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 <string.h>

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

#include "libpikaconfig/pikaconfig.h"

#include "tools-types.h"

#include "core/pika.h"
#include "core/pikacontainer.h"
#include "core/pikaimage.h"
#include "core/pikatoolgroup.h"
#include "core/pikatoolinfo.h"
#include "core/pikatooloptions.h"
#include "core/pikatoolpreset.h"

#include "display/pikadisplay.h"

#include "widgets/pikacairo-mascot.h"

#include "pikagegltool.h"
#include "pikatool.h"
#include "pikatoolcontrol.h"
#include "pikatransformgridtool.h"
#include "tool_manager.h"


typedef struct _PikaToolManager PikaToolManager;

struct _PikaToolManager
{
  Pika          *pika;

  PikaTool      *active_tool;
  GSList        *tool_stack;

  PikaToolGroup *active_tool_group;

  PikaImage     *image;

  GQuark         image_clean_handler_id;
  GQuark         image_dirty_handler_id;
  GQuark         image_saving_handler_id;
};


/*  local function prototypes  */

static PikaToolManager * tool_manager_get                       (Pika            *pika);

static void              tool_manager_select_tool               (PikaToolManager *tool_manager,
                                                                 PikaTool        *tool);

static void              tool_manager_set_active_tool_group     (PikaToolManager *tool_manager,
                                                                 PikaToolGroup   *tool_group);

static void              tool_manager_tool_changed              (PikaContext     *user_context,
                                                                 PikaToolInfo    *tool_info,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_preset_changed            (PikaContext     *user_context,
                                                                 PikaToolPreset  *preset,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_image_clean_dirty         (PikaImage       *image,
                                                                 PikaDirtyMask    dirty_mask,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_image_saving              (PikaImage       *image,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_tool_ancestry_changed     (PikaToolInfo    *tool_info,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_group_active_tool_changed (PikaToolGroup   *tool_group,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_image_changed             (PikaContext     *context,
                                                                 PikaImage       *image,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_selected_layers_changed   (PikaImage       *image,
                                                                 PikaToolManager *tool_manager);
static void              tool_manager_active_tool_deactivated   (PikaContext     *context,
                                                                 PikaToolManager *tool_manager);

static void              tool_manager_cast_spell                (PikaToolInfo    *tool_info);


static GQuark tool_manager_quark = 0;


/*  public functions  */

void
tool_manager_init (Pika *pika)
{
  PikaToolManager *tool_manager;
  PikaContext     *user_context;

  g_return_if_fail (PIKA_IS_PIKA (pika));
  g_return_if_fail (tool_manager_quark == 0);

  tool_manager_quark = g_quark_from_static_string ("pika-tool-manager");

  tool_manager = g_slice_new0 (PikaToolManager);

  tool_manager->pika = pika;

  g_object_set_qdata (G_OBJECT (pika), tool_manager_quark, tool_manager);

  tool_manager->image_clean_handler_id =
    pika_container_add_handler (pika->images, "clean",
                                G_CALLBACK (tool_manager_image_clean_dirty),
                                tool_manager);

  tool_manager->image_dirty_handler_id =
    pika_container_add_handler (pika->images, "dirty",
                                G_CALLBACK (tool_manager_image_clean_dirty),
                                tool_manager);

  tool_manager->image_saving_handler_id =
    pika_container_add_handler (pika->images, "saving",
                                G_CALLBACK (tool_manager_image_saving),
                                tool_manager);

  user_context = pika_get_user_context (pika);

  g_signal_connect (user_context, "tool-changed",
                    G_CALLBACK (tool_manager_tool_changed),
                    tool_manager);
  g_signal_connect (user_context, "tool-preset-changed",
                    G_CALLBACK (tool_manager_preset_changed),
                    tool_manager);
  g_signal_connect (user_context, "image-changed",
                    G_CALLBACK (tool_manager_image_changed),
                    tool_manager);

  tool_manager_image_changed (user_context,
                              pika_context_get_image (user_context),
                              tool_manager);
  tool_manager_selected_layers_changed (pika_context_get_image (user_context),
                                        tool_manager);

  tool_manager_tool_changed (user_context,
                             pika_context_get_tool (user_context),
                             tool_manager);
}

void
tool_manager_exit (Pika *pika)
{
  PikaToolManager *tool_manager;
  PikaContext     *user_context;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  g_return_if_fail (tool_manager != NULL);

  user_context = pika_get_user_context (pika);

  g_signal_handlers_disconnect_by_func (user_context,
                                        tool_manager_tool_changed,
                                        tool_manager);
  g_signal_handlers_disconnect_by_func (user_context,
                                        tool_manager_preset_changed,
                                        tool_manager);
  g_signal_handlers_disconnect_by_func (user_context,
                                        tool_manager_image_changed,
                                        tool_manager);

  if (tool_manager->image)
    g_signal_handlers_disconnect_by_func (tool_manager->image,
                                          tool_manager_selected_layers_changed,
                                          tool_manager);

  pika_container_remove_handler (pika->images,
                                 tool_manager->image_clean_handler_id);
  pika_container_remove_handler (pika->images,
                                 tool_manager->image_dirty_handler_id);
  pika_container_remove_handler (pika->images,
                                 tool_manager->image_saving_handler_id);

  if (tool_manager->active_tool)
    {
      g_signal_handlers_disconnect_by_func (
        tool_manager->active_tool->tool_info,
        tool_manager_tool_ancestry_changed,
        tool_manager);

      g_clear_object (&tool_manager->active_tool);
    }

  tool_manager_set_active_tool_group (tool_manager, NULL);

  g_slice_free (PikaToolManager, tool_manager);

  g_object_set_qdata (G_OBJECT (pika), tool_manager_quark, NULL);
}

PikaTool *
tool_manager_get_active (Pika *pika)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);

  tool_manager = tool_manager_get (pika);

  return tool_manager->active_tool;
}

void
tool_manager_push_tool (Pika     *pika,
                        PikaTool *tool)
{
  PikaToolManager *tool_manager;
  PikaDisplay     *focus_display = NULL;

  g_return_if_fail (PIKA_IS_PIKA (pika));
  g_return_if_fail (PIKA_IS_TOOL (tool));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      focus_display = tool_manager->active_tool->focus_display;

      tool_manager->tool_stack = g_slist_prepend (tool_manager->tool_stack,
                                                  tool_manager->active_tool);

      g_object_ref (tool_manager->tool_stack->data);
    }

  tool_manager_select_tool (tool_manager, tool);

  if (focus_display)
    tool_manager_focus_display_active (pika, focus_display);
}

void
tool_manager_pop_tool (Pika *pika)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->tool_stack)
    {
      PikaTool *tool = tool_manager->tool_stack->data;

      tool_manager->tool_stack = g_slist_remove (tool_manager->tool_stack,
                                                 tool);

      tool_manager_select_tool (tool_manager, tool);

      g_object_unref (tool);
    }
}

gboolean
tool_manager_initialize_active (Pika        *pika,
                                PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), FALSE);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      PikaTool *tool = tool_manager->active_tool;

      if (pika_tool_initialize (tool, display))
        {
          PikaImage *image = pika_display_get_image (display);

          g_list_free (tool->drawables);
          tool->drawables = pika_image_get_selected_drawables (image);

          return TRUE;
        }
    }

  return FALSE;
}

void
tool_manager_control_active (Pika           *pika,
                             PikaToolAction  action,
                             PikaDisplay    *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      PikaTool *tool = tool_manager->active_tool;

      if (display && pika_tool_has_display (tool, display))
        {
          pika_tool_control (tool, action, display);
        }
      else if (action == PIKA_TOOL_ACTION_HALT)
        {
          if (pika_tool_control_is_active (tool->control))
            pika_tool_control_halt (tool->control);
        }
    }
}

void
tool_manager_button_press_active (Pika                *pika,
                                  const PikaCoords    *coords,
                                  guint32              time,
                                  GdkModifierType      state,
                                  PikaButtonPressType  press_type,
                                  PikaDisplay         *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      pika_tool_button_press (tool_manager->active_tool,
                              coords, time, state, press_type,
                              display);
    }
}

void
tool_manager_button_release_active (Pika             *pika,
                                    const PikaCoords *coords,
                                    guint32           time,
                                    GdkModifierType   state,
                                    PikaDisplay      *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      pika_tool_button_release (tool_manager->active_tool,
                                coords, time, state,
                                display);
    }
}

void
tool_manager_motion_active (Pika             *pika,
                            const PikaCoords *coords,
                            guint32           time,
                            GdkModifierType   state,
                            PikaDisplay      *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      pika_tool_motion (tool_manager->active_tool,
                        coords, time, state,
                        display);
    }
}

gboolean
tool_manager_key_press_active (Pika        *pika,
                               GdkEventKey *kevent,
                               PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_key_press (tool_manager->active_tool,
                                  kevent,
                                  display);
    }

  return FALSE;
}

gboolean
tool_manager_key_release_active (Pika        *pika,
                                 GdkEventKey *kevent,
                                 PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_key_release (tool_manager->active_tool,
                                    kevent,
                                    display);
    }

  return FALSE;
}

void
tool_manager_focus_display_active (Pika        *pika,
                                   PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool &&
      ! pika_tool_control_is_active (tool_manager->active_tool->control))
    {
      pika_tool_set_focus_display (tool_manager->active_tool,
                                   display);
    }
}

void
tool_manager_modifier_state_active (Pika            *pika,
                                    GdkModifierType  state,
                                    PikaDisplay     *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool &&
      ! pika_tool_control_is_active (tool_manager->active_tool->control))
    {
      pika_tool_set_modifier_state (tool_manager->active_tool,
                                    state,
                                    display);
    }
}

void
tool_manager_active_modifier_state_active (Pika            *pika,
                                           GdkModifierType  state,
                                           PikaDisplay     *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      pika_tool_set_active_modifier_state (tool_manager->active_tool,
                                           state,
                                           display);
    }
}

void
tool_manager_oper_update_active (Pika             *pika,
                                 const PikaCoords *coords,
                                 GdkModifierType   state,
                                 gboolean          proximity,
                                 PikaDisplay      *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool &&
      ! pika_tool_control_is_active (tool_manager->active_tool->control))
    {
      pika_tool_oper_update (tool_manager->active_tool,
                             coords, state, proximity,
                             display);
    }
}

void
tool_manager_cursor_update_active (Pika             *pika,
                                   const PikaCoords *coords,
                                   GdkModifierType   state,
                                   PikaDisplay      *display)
{
  PikaToolManager *tool_manager;

  g_return_if_fail (PIKA_IS_PIKA (pika));

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool &&
      ! pika_tool_control_is_active (tool_manager->active_tool->control))
    {
      pika_tool_cursor_update (tool_manager->active_tool,
                               coords, state,
                               display);
    }
}

const gchar *
tool_manager_can_undo_active (Pika        *pika,
                              PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_can_undo (tool_manager->active_tool,
                                 display);
    }

  return NULL;
}

const gchar *
tool_manager_can_redo_active (Pika        *pika,
                              PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_can_redo (tool_manager->active_tool,
                                 display);
    }

  return NULL;
}

gboolean
tool_manager_undo_active (Pika        *pika,
                          PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_undo (tool_manager->active_tool,
                             display);
    }

  return FALSE;
}

gboolean
tool_manager_redo_active (Pika        *pika,
                          PikaDisplay *display)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_redo (tool_manager->active_tool,
                             display);
    }

  return FALSE;
}

PikaUIManager *
tool_manager_get_popup_active (Pika             *pika,
                               const PikaCoords *coords,
                               GdkModifierType   state,
                               PikaDisplay      *display,
                               const gchar     **ui_path)
{
  PikaToolManager *tool_manager;

  g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);

  tool_manager = tool_manager_get (pika);

  if (tool_manager->active_tool)
    {
      return pika_tool_get_popup (tool_manager->active_tool,
                                  coords, state,
                                  display,
                                  ui_path);
    }

  return NULL;
}


/*  private functions  */

static PikaToolManager *
tool_manager_get (Pika *pika)
{
  return g_object_get_qdata (G_OBJECT (pika), tool_manager_quark);
}

static void
tool_manager_select_tool (PikaToolManager *tool_manager,
                          PikaTool        *tool)
{
  Pika *pika = tool_manager->pika;

  /*  reset the previously selected tool, but only if it is not only
   *  temporarily pushed to the tool stack
   */
  if (tool_manager->active_tool)
    {
      if (! tool_manager->tool_stack ||
          tool_manager->active_tool != tool_manager->tool_stack->data)
        {
          PikaTool    *active_tool = tool_manager->active_tool;
          PikaDisplay *display;

          /*  NULL image returns any display (if there is any)  */
          display = pika_tool_has_image (active_tool, NULL);

          tool_manager_control_active (pika, PIKA_TOOL_ACTION_HALT, display);
          tool_manager_focus_display_active (pika, NULL);
        }
    }

  g_set_object (&tool_manager->active_tool, tool);
}

static void
tool_manager_set_active_tool_group (PikaToolManager *tool_manager,
                                    PikaToolGroup   *tool_group)
{
  if (tool_group != tool_manager->active_tool_group)
    {
      if (tool_manager->active_tool_group)
        {
          g_signal_handlers_disconnect_by_func (
            tool_manager->active_tool_group,
            tool_manager_group_active_tool_changed,
            tool_manager);
        }

      g_set_weak_pointer (&tool_manager->active_tool_group, tool_group);

      if (tool_manager->active_tool_group)
        {
          g_signal_connect (
            tool_manager->active_tool_group, "active-tool-changed",
            G_CALLBACK (tool_manager_group_active_tool_changed),
            tool_manager);
        }
    }
}

static void
tool_manager_tool_changed (PikaContext     *user_context,
                           PikaToolInfo    *tool_info,
                           PikaToolManager *tool_manager)
{
  PikaTool *new_tool = NULL;

  if (! tool_info)
    return;

  if (! g_type_is_a (tool_info->tool_type, PIKA_TYPE_TOOL))
    {
      g_warning ("%s: tool_info->tool_type is no PikaTool subclass",
                 G_STRFUNC);
      return;
    }

  /* FIXME: pika_busy HACK */
  if (user_context->pika->busy)
    {
      /*  there may be contexts waiting for the user_context's "tool-changed"
       *  signal, so stop emitting it.
       */
      g_signal_stop_emission_by_name (user_context, "tool-changed");

      if (G_TYPE_FROM_INSTANCE (tool_manager->active_tool) !=
          tool_info->tool_type)
        {
          g_signal_handlers_block_by_func (user_context,
                                           tool_manager_tool_changed,
                                           tool_manager);

          /*  explicitly set the current tool  */
          pika_context_set_tool (user_context,
                                 tool_manager->active_tool->tool_info);

          g_signal_handlers_unblock_by_func (user_context,
                                             tool_manager_tool_changed,
                                             tool_manager);
        }

      return;
    }

  g_return_if_fail (tool_manager->tool_stack == NULL);

  if (tool_manager->active_tool)
    {
      tool_manager_active_tool_deactivated (user_context, tool_manager);
    }

  g_signal_connect (tool_info, "ancestry-changed",
                    G_CALLBACK (tool_manager_tool_ancestry_changed),
                    tool_manager);

  tool_manager_tool_ancestry_changed (tool_info, tool_manager);

  new_tool = g_object_new (tool_info->tool_type,
                           "tool-info", tool_info,
                           NULL);

  tool_manager_select_tool (tool_manager, new_tool);

  /* Auto-activate any transform or GEGL operation tools */
  if (PIKA_IS_TRANSFORM_GRID_TOOL (new_tool) ||
      PIKA_IS_GEGL_TOOL (new_tool))
    {
      PikaDisplay *new_display;

      new_display = pika_context_get_display (user_context);
      if (new_display && pika_display_get_image (new_display))
        tool_manager_initialize_active (user_context->pika, new_display);
    }

  g_object_unref (new_tool);

  /* ??? */
  tool_manager_cast_spell (tool_info);
}

static void
tool_manager_copy_tool_options (GObject *src,
                                GObject *dest)
{
  GList *diff;

  diff = pika_config_diff (src, dest, G_PARAM_READWRITE);

  if (diff)
    {
      GList *list;

      g_object_freeze_notify (dest);

      for (list = diff; list; list = list->next)
        {
          GParamSpec *prop_spec = list->data;

          if (g_type_is_a (prop_spec->owner_type, PIKA_TYPE_TOOL_OPTIONS) &&
              ! (prop_spec->flags & G_PARAM_CONSTRUCT_ONLY))
            {
              GValue value = G_VALUE_INIT;

              g_value_init (&value, prop_spec->value_type);

              g_object_get_property (src,  prop_spec->name, &value);
              g_object_set_property (dest, prop_spec->name, &value);

              g_value_unset (&value);
            }
        }

      g_object_thaw_notify (dest);

      g_list_free (diff);
    }
}

static void
tool_manager_preset_changed (PikaContext     *user_context,
                             PikaToolPreset  *preset,
                             PikaToolManager *tool_manager)
{
  PikaToolInfo *preset_tool;

  if (! preset || user_context->pika->busy)
    return;

  preset_tool = pika_context_get_tool (PIKA_CONTEXT (preset->tool_options));

  /* first, select the preset's tool, even if it's already the active
   * tool
   */
  pika_context_set_tool (user_context, preset_tool);

  /* then, copy the context properties the preset remembers, possibly
   * changing some tool options due to the "link brush stuff to brush
   * defaults" settings in pikatooloptions.c
   */
  pika_context_copy_properties (PIKA_CONTEXT (preset->tool_options),
                                user_context,
                                pika_tool_preset_get_prop_mask (preset));

  /* finally, copy all tool options properties, overwriting any
   * changes resulting from setting the context properties above, we
   * really want exactly what is in the preset and nothing else
   */
  tool_manager_copy_tool_options (G_OBJECT (preset->tool_options),
                                  G_OBJECT (preset_tool->tool_options));
}

static void
tool_manager_image_clean_dirty (PikaImage       *image,
                                PikaDirtyMask    dirty_mask,
                                PikaToolManager *tool_manager)
{
  PikaTool *tool = tool_manager->active_tool;

  if (tool &&
      ! pika_tool_control_get_preserve (tool->control) &&
      (pika_tool_control_get_dirty_mask (tool->control) & dirty_mask))
    {
      PikaDisplay *display = pika_tool_has_image (tool, image);

      if (display)
        {
          tool_manager_control_active (
            image->pika,
            pika_tool_control_get_dirty_action (tool->control),
            display);
        }
    }
}

static void
tool_manager_image_saving (PikaImage       *image,
                           PikaToolManager *tool_manager)
{
  PikaTool *tool = tool_manager->active_tool;

  if (tool &&
      ! pika_tool_control_get_preserve (tool->control))
    {
      PikaDisplay *display = pika_tool_has_image (tool, image);

      if (display)
        tool_manager_control_active (image->pika, PIKA_TOOL_ACTION_COMMIT,
                                     display);
    }
}

static void
tool_manager_tool_ancestry_changed (PikaToolInfo    *tool_info,
                                    PikaToolManager *tool_manager)
{
  PikaViewable *parent;

  parent = pika_viewable_get_parent (PIKA_VIEWABLE (tool_info));

  if (parent)
    {
      pika_tool_group_set_active_tool_info (PIKA_TOOL_GROUP (parent),
                                            tool_info);
    }

  tool_manager_set_active_tool_group (tool_manager, PIKA_TOOL_GROUP (parent));
}

static void
tool_manager_group_active_tool_changed (PikaToolGroup   *tool_group,
                                        PikaToolManager *tool_manager)
{
  pika_context_set_tool (tool_manager->pika->user_context,
                         pika_tool_group_get_active_tool_info (tool_group));
}

static void
tool_manager_image_changed (PikaContext     *context,
                            PikaImage       *image,
                            PikaToolManager *tool_manager)
{
  if (tool_manager->image)
    {
      g_signal_handlers_disconnect_by_func (tool_manager->image,
                                            tool_manager_selected_layers_changed,
                                            tool_manager);
    }

  tool_manager->image = image;

  /* Turn off auto-activated tools when switching images */
  if (image                                                    &&
      (PIKA_IS_TRANSFORM_GRID_TOOL (tool_manager->active_tool) ||
       PIKA_IS_GEGL_TOOL (tool_manager->active_tool)))
    {
      pika_context_set_tool (context,
                             tool_manager->active_tool->tool_info);

      tool_manager_active_tool_deactivated (context, tool_manager);
    }

  if (image)
    g_signal_connect (tool_manager->image, "selected-layers-changed",
                      G_CALLBACK (tool_manager_selected_layers_changed),
                      tool_manager);
}

static void
tool_manager_selected_layers_changed (PikaImage       *image,
                                      PikaToolManager *tool_manager)
{
  /* Re-activate transform tools when changing selected layers */
  if (image                                                    &&
      (PIKA_IS_TRANSFORM_GRID_TOOL (tool_manager->active_tool) ||
       PIKA_IS_GEGL_TOOL (tool_manager->active_tool)))
    {
      pika_context_set_tool (tool_manager->pika->user_context,
                             tool_manager->active_tool->tool_info);

      tool_manager_tool_changed (tool_manager->pika->user_context,
                                 pika_context_get_tool (
                                   tool_manager->pika->user_context),
                                 tool_manager);
    }
}

static void
tool_manager_active_tool_deactivated (PikaContext     *context,
                                      PikaToolManager *tool_manager)
{
  PikaDisplay *display;

  display = pika_tool_has_image (tool_manager->active_tool, NULL);

  /*  NULL image returns any display (if there is any)  */
  if (display)
    tool_manager_control_active (context->pika, PIKA_TOOL_ACTION_COMMIT,
                                 display);

  /*  commit the old tool's operation before creating the new tool
   *  because creating a tool might mess with the old tool's
   *  options (old and new tool might be the same)
   */
  g_signal_handlers_disconnect_by_func (tool_manager->active_tool->tool_info,
                                        tool_manager_tool_ancestry_changed,
                                        tool_manager);
}

static void
tool_manager_cast_spell (PikaToolInfo *tool_info)
{
  typedef struct
  {
    const gchar *sequence;
    GCallback    func;
  } Spell;

  static const Spell spells[] =
  {
    { .sequence = "pika-warp-tool\0"
                  "pika-iscissors-tool\0"
                  "pika-gradient-tool\0"
                  "pika-vector-tool\0"
                  "pika-ellipse-select-tool\0"
                  "pika-rect-select-tool\0",
      .func     = pika_cairo_mascot_toggle_pointer_eyes
    }
  };

  static const gchar *spell_progress[G_N_ELEMENTS (spells)];
  const gchar        *tool_name;
  gint                i;

  tool_name = pika_object_get_name (PIKA_OBJECT (tool_info));

  for (i = 0; i < G_N_ELEMENTS (spells); i++)
    {
      if (! spell_progress[i])
        spell_progress[i] = spells[i].sequence;

      while (spell_progress[i])
        {
          if (! strcmp (tool_name, spell_progress[i]))
            {
              spell_progress[i] += strlen (spell_progress[i]) + 1;

              if (! *spell_progress[i])
                {
                  spell_progress[i] = NULL;

                  spells[i].func ();
                }

              break;
            }
          else
            {
              if (spell_progress[i] == spells[i].sequence)
                spell_progress[i] = NULL;
              else
                spell_progress[i] = spells[i].sequence;
            }
        }
    }
}
