/* GIMP - The GNU Image Manipulation Program
 * 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 "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "tools-types.h"

#include "gegl/gimp-gegl-utils.h"

#include "core/gimpchannel.h"
#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable.h"
#include "core/gimperror.h"
#include "core/gimpimage.h"
#include "core/gimplist.h"
#include "core/gimppickable.h"
#include "core/gimpsettings.h"

#include "widgets/gimpbuffersourcebox.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimppickablebutton.h"

#include "propgui/gimppropgui.h"

#include "display/gimpdisplay.h"
#include "display/gimptoolgui.h"

#include "gimpfilteroptions.h"
#include "gimpoperationtool.h"

#include "gimp-intl.h"


typedef struct _AuxInput AuxInput;

struct _AuxInput
{
  GimpOperationTool *tool;
  gchar             *pad;
  GeglNode          *node;
  GtkWidget         *box;
};


/*  local function prototypes  */

static void        gimp_operation_tool_finalize        (GObject           *object);

static gboolean    gimp_operation_tool_initialize      (GimpTool          *tool,
                                                        GimpDisplay       *display,
                                                        GError           **error);
static void        gimp_operation_tool_control         (GimpTool          *tool,
                                                        GimpToolAction     action,
                                                        GimpDisplay       *display);

static gchar     * gimp_operation_tool_get_operation   (GimpFilterTool    *filter_tool,
                                                        gchar            **description);
static void        gimp_operation_tool_dialog          (GimpFilterTool    *filter_tool);
static void        gimp_operation_tool_reset           (GimpFilterTool    *filter_tool);
static void        gimp_operation_tool_set_config      (GimpFilterTool    *filter_tool,
                                                        GimpConfig        *config);
static void        gimp_operation_tool_region_changed  (GimpFilterTool    *filter_tool);
static void        gimp_operation_tool_color_picked    (GimpFilterTool    *filter_tool,
                                                        gpointer           identifier,
                                                        gdouble            x,
                                                        gdouble            y,
                                                        const Babl        *sample_format,
                                                        GeglColor         *color);

static void        gimp_operation_tool_halt            (GimpOperationTool *op_tool);
static void        gimp_operation_tool_commit          (GimpOperationTool *op_tool);

static void        gimp_operation_tool_sync_op         (GimpOperationTool *op_tool,
                                                        gboolean           sync_colors);
static void        gimp_operation_tool_create_gui      (GimpOperationTool *tool);
static void        gimp_operation_tool_add_gui         (GimpOperationTool *tool);

static AuxInput *  gimp_operation_tool_aux_input_new   (GimpOperationTool *tool,
                                                        GeglNode          *operation,
                                                        const gchar       *input_pad,
                                                        const gchar       *label,
                                                        const gchar       *description);
static void        gimp_operation_tool_aux_input_detach(AuxInput          *input);
static void        gimp_operation_tool_aux_input_clear (AuxInput          *input);
static void        gimp_operation_tool_aux_input_free  (AuxInput          *input);

static void        gimp_operation_tool_unlink_chains   (GimpOperationTool *op_tool);
static void        gimp_operation_tool_relink_chains   (GimpOperationTool *op_tool);


G_DEFINE_TYPE (GimpOperationTool, gimp_operation_tool,
               GIMP_TYPE_FILTER_TOOL)

#define parent_class gimp_operation_tool_parent_class


void
gimp_operation_tool_register (GimpToolRegisterCallback  callback,
                              gpointer                  data)
{
  (* callback) (GIMP_TYPE_OPERATION_TOOL,
                GIMP_TYPE_FILTER_OPTIONS,
                gimp_color_options_gui,
                GIMP_CONTEXT_PROP_MASK_FOREGROUND |
                GIMP_CONTEXT_PROP_MASK_BACKGROUND,
                "gimp-operation-tool",
                _("GEGL Operation"),
                _("Operation Tool: Use an arbitrary GEGL operation"),
                NULL, NULL,
                NULL, GIMP_HELP_TOOL_GEGL,
                GIMP_ICON_GEGL,
                data);
}

static void
gimp_operation_tool_class_init (GimpOperationToolClass *klass)
{
  GObjectClass        *object_class      = G_OBJECT_CLASS (klass);
  GimpToolClass       *tool_class        = GIMP_TOOL_CLASS (klass);
  GimpFilterToolClass *filter_tool_class = GIMP_FILTER_TOOL_CLASS (klass);

  object_class->finalize            = gimp_operation_tool_finalize;

  tool_class->initialize            = gimp_operation_tool_initialize;
  tool_class->control               = gimp_operation_tool_control;

  filter_tool_class->get_operation  = gimp_operation_tool_get_operation;
  filter_tool_class->dialog         = gimp_operation_tool_dialog;
  filter_tool_class->reset          = gimp_operation_tool_reset;
  filter_tool_class->set_config     = gimp_operation_tool_set_config;
  filter_tool_class->region_changed = gimp_operation_tool_region_changed;
  filter_tool_class->color_picked   = gimp_operation_tool_color_picked;
}

static void
gimp_operation_tool_init (GimpOperationTool *op_tool)
{
}

static void
gimp_operation_tool_finalize (GObject *object)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (object);

  g_clear_pointer (&op_tool->operation,   g_free);
  g_clear_pointer (&op_tool->description, g_free);

  g_list_free_full (op_tool->aux_inputs,
                    (GDestroyNotify) gimp_operation_tool_aux_input_free);
  op_tool->aux_inputs = NULL;

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static gboolean
gimp_operation_tool_initialize (GimpTool     *tool,
                                GimpDisplay  *display,
                                GError      **error)
{
  if (GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
    {
      GimpFilterTool    *filter_tool = GIMP_FILTER_TOOL (tool);
      GimpOperationTool *op_tool     = GIMP_OPERATION_TOOL (tool);

      if (filter_tool->config)
        {
          GtkWidget *options_gui;

          gimp_operation_tool_sync_op (op_tool, TRUE);
          options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
          if (! options_gui)
            {
              gimp_operation_tool_create_gui (op_tool);
              gimp_operation_tool_add_gui (op_tool);
            }
          else
            {
              g_object_unref (options_gui);
            }
        }

      return TRUE;
    }

  return FALSE;
}

static void
gimp_operation_tool_control (GimpTool       *tool,
                             GimpToolAction  action,
                             GimpDisplay    *display)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (tool);

  switch (action)
    {
    case GIMP_TOOL_ACTION_PAUSE:
    case GIMP_TOOL_ACTION_RESUME:
      break;

    case GIMP_TOOL_ACTION_HALT:
      gimp_operation_tool_halt (op_tool);
      break;

    case GIMP_TOOL_ACTION_COMMIT:
      gimp_operation_tool_commit (op_tool);
      break;
    }

  GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
}

static gchar *
gimp_operation_tool_get_operation (GimpFilterTool  *filter_tool,
                                   gchar          **description)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);

  *description = g_strdup (op_tool->description);

  return g_strdup (op_tool->operation);
}

static void
gimp_operation_tool_dialog (GimpFilterTool *filter_tool)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);
  GtkWidget         *main_vbox;
  GtkWidget         *options_sw;
  GtkWidget         *options_gui;
  GtkWidget         *options_box;

  main_vbox = gimp_filter_tool_dialog_get_vbox (filter_tool);

  /*  The options scrolled window  */
  options_sw = gtk_scrolled_window_new (NULL, NULL);
  g_weak_ref_set (&op_tool->options_sw_ref, options_sw);
  gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (options_sw),
                                             FALSE);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (options_sw),
                                       GTK_SHADOW_NONE);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (options_sw),
                                  GTK_POLICY_NEVER, GTK_POLICY_NEVER);
  gtk_box_pack_start (GTK_BOX (main_vbox), options_sw,
                      TRUE, TRUE, 0);
  gtk_widget_show (options_sw);

  /*  The options vbox  */
  options_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  g_weak_ref_set (&op_tool->options_box_ref, options_box);
  gtk_container_add (GTK_CONTAINER (options_sw), options_box);
  gtk_widget_show (options_box);

  options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
  if (options_gui)
    {
      gimp_operation_tool_add_gui (op_tool);
      g_object_unref (options_gui);
    }
}

static void
gimp_operation_tool_reset (GimpFilterTool *filter_tool)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);

  gimp_operation_tool_unlink_chains (op_tool);

  GIMP_FILTER_TOOL_CLASS (parent_class)->reset (filter_tool);

  if (filter_tool->config && GIMP_TOOL (op_tool)->drawables)
    gimp_operation_tool_sync_op (op_tool, TRUE);

  gimp_operation_tool_relink_chains (op_tool);
}

static void
gimp_operation_tool_set_config (GimpFilterTool *filter_tool,
                                GimpConfig     *config)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);

  gimp_operation_tool_unlink_chains (op_tool);

  GIMP_FILTER_TOOL_CLASS (parent_class)->set_config (filter_tool, config);

  if (filter_tool->config && GIMP_TOOL (op_tool)->drawables)
    gimp_operation_tool_sync_op (op_tool, FALSE);

  gimp_operation_tool_relink_chains (op_tool);
}

static void
gimp_operation_tool_region_changed (GimpFilterTool *filter_tool)
{
  GimpOperationTool *op_tool = GIMP_OPERATION_TOOL (filter_tool);

  /* when the region changes, do we want the operation's on-canvas
   * controller to move to a new position, or the operation to
   * change its properties to match the on-canvas controller?
   *
   * decided to leave the on-canvas controller where it is and
   * pretend it has changed, so the operation is updated
   * accordingly...
   */
  if (filter_tool->widget)
    g_signal_emit_by_name (filter_tool->widget, "changed");

  gimp_operation_tool_sync_op (op_tool, FALSE);
}

static void
gimp_operation_tool_color_picked (GimpFilterTool  *filter_tool,
                                  gpointer         identifier,
                                  gdouble          x,
                                  gdouble          y,
                                  const Babl      *sample_format,
                                  GeglColor       *color)
{
  gchar **pspecs = g_strsplit (identifier, ":", 2);

  if (pspecs[1])
    {
      GObjectClass  *object_class = G_OBJECT_GET_CLASS (filter_tool->config);
      GParamSpec    *pspec_x;
      GParamSpec    *pspec_y;
      gint           off_x, off_y;
      GeglRectangle  area;

      gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);

      x -= off_x + area.x;
      y -= off_y + area.y;

      pspec_x = g_object_class_find_property (object_class, pspecs[0]);
      pspec_y = g_object_class_find_property (object_class, pspecs[1]);

      if (pspec_x && pspec_y &&
          G_PARAM_SPEC_TYPE (pspec_x) == G_PARAM_SPEC_TYPE (pspec_y))
        {
          GValue value_x = G_VALUE_INIT;
          GValue value_y = G_VALUE_INIT;

          g_value_init (&value_x, G_PARAM_SPEC_VALUE_TYPE (pspec_x));
          g_value_init (&value_y, G_PARAM_SPEC_VALUE_TYPE (pspec_y));

#define HAS_KEY(p,k,v) gimp_gegl_param_spec_has_key (p, k, v)

          if (HAS_KEY (pspec_x, "unit", "relative-coordinate") &&
              HAS_KEY (pspec_y, "unit", "relative-coordinate"))
            {
              x /= (gdouble) area.width;
              y /= (gdouble) area.height;
            }

          if (G_IS_PARAM_SPEC_INT (pspec_x))
            {
              g_value_set_int (&value_x, x);
              g_value_set_int (&value_y, y);

              g_param_value_validate (pspec_x, &value_x);
              g_param_value_validate (pspec_y, &value_y);

              g_object_set (filter_tool->config,
                            pspecs[0], g_value_get_int (&value_x),
                            pspecs[1], g_value_get_int (&value_y),
                            NULL);
            }
          else if (G_IS_PARAM_SPEC_DOUBLE (pspec_x))
            {
              g_value_set_double (&value_x, x);
              g_value_set_double (&value_y, y);

              g_param_value_validate (pspec_x, &value_x);
              g_param_value_validate (pspec_y, &value_y);

              g_object_set (filter_tool->config,
                            pspecs[0], g_value_get_double (&value_x),
                            pspecs[1], g_value_get_double (&value_y),
                            NULL);
            }
          else
            {
              g_warning ("%s: unhandled param spec of type %s",
                         G_STRFUNC, G_PARAM_SPEC_TYPE_NAME (pspec_x));
            }

          g_value_unset (&value_x);
          g_value_unset (&value_y);
        }
    }
  else
    {
      g_object_set (filter_tool->config,
                    pspecs[0], color,
                    NULL);
    }

  g_strfreev (pspecs);
}

static void
gimp_operation_tool_halt (GimpOperationTool *op_tool)
{
  /*  don't reset op_tool->operation and op_tool->description so the
   *  tool can be properly restarted by clicking on an image
   */

  g_list_free_full (op_tool->aux_inputs,
                    (GDestroyNotify) gimp_operation_tool_aux_input_free);
  op_tool->aux_inputs = NULL;
}

static void
gimp_operation_tool_commit (GimpOperationTool *op_tool)
{
  /*  remove the aux input boxes from the dialog, so they don't get
   *  destroyed when the parent class runs its commit()
   */

  g_list_foreach (op_tool->aux_inputs,
                  (GFunc) gimp_operation_tool_aux_input_detach, NULL);
}

static void
gimp_operation_tool_sync_op (GimpOperationTool *op_tool,
                             gboolean           sync_colors)
{
  GimpFilterTool   *filter_tool = GIMP_FILTER_TOOL (op_tool);
  GimpToolOptions  *options     = GIMP_TOOL_GET_OPTIONS (op_tool);
  GParamSpec      **pspecs;
  guint             n_pspecs;
  gint              off_x, off_y;
  GeglRectangle     area;
  gint              i;

  gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);

  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (filter_tool->config),
                                           &n_pspecs);

  for (i = 0; i < n_pspecs; i++)
    {
      GParamSpec *pspec = pspecs[i];

#define HAS_KEY(p,k,v) gimp_gegl_param_spec_has_key (p, k, v)

      if (HAS_KEY (pspec, "role", "output-extent"))
        {
          if (HAS_KEY (pspec, "unit", "pixel-coordinate") &&
              HAS_KEY (pspec, "axis", "x"))
            {
              g_object_set (filter_tool->config, pspec->name, 0, NULL);
            }
          else if (HAS_KEY (pspec, "unit", "pixel-coordinate") &&
                   HAS_KEY (pspec, "axis", "y"))
            {
              g_object_set (filter_tool->config, pspec->name, 0, NULL);
            }
          else if (HAS_KEY (pspec, "unit", "pixel-distance") &&
                   HAS_KEY (pspec, "axis", "x"))
            {
              g_object_set (filter_tool->config, pspec->name, area.width, NULL);
            }
          else if (HAS_KEY (pspec, "unit", "pixel-distance") &&
                   HAS_KEY (pspec, "axis", "y"))
            {
              g_object_set (filter_tool->config, pspec->name, area.height, NULL);
            }
        }
      else if (sync_colors)
        {
          GeglColor *color = NULL;

          if (HAS_KEY (pspec, "role", "color-primary"))
            color = gimp_context_get_foreground (GIMP_CONTEXT (options));
          else if (sync_colors && HAS_KEY (pspec, "role", "color-secondary"))
            color = gimp_context_get_background (GIMP_CONTEXT (options));

          if (color != NULL)
            g_object_set (filter_tool->config, pspec->name, color, NULL);
        }
    }

  g_free (pspecs);
}

static void
gimp_operation_tool_create_gui (GimpOperationTool *op_tool)
{
  GimpFilterTool  *filter_tool = GIMP_FILTER_TOOL (op_tool);
  GtkWidget       *options_gui;
  gint             off_x, off_y;
  GeglRectangle    area;
  gchar          **input_pads;

  gimp_filter_tool_get_drawable_area (filter_tool, &off_x, &off_y, &area);

  options_gui =
    gimp_prop_gui_new (G_OBJECT (filter_tool->config),
                       G_TYPE_FROM_INSTANCE (filter_tool->config), 0,
                       &area,
                       GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (op_tool)),
                       (GimpCreatePickerFunc) gimp_filter_tool_add_color_picker,
                       (GimpCreateControllerFunc) gimp_filter_tool_add_controller,
                       filter_tool);
  g_weak_ref_set (&op_tool->options_gui_ref, options_gui);

  input_pads = gegl_node_list_input_pads (filter_tool->operation);

  if (input_pads)
    {
      gint i;

      for (i = 0; input_pads[i]; i++)
        {
          AuxInput *input;
          GRegex   *regex;
          gchar    *label;

          if (! strcmp (input_pads[i], "input"))
            continue;

          regex = g_regex_new ("^aux(\\d*)$", 0, 0, NULL);

          g_return_if_fail (regex != NULL);

          label = g_strdup (gegl_node_get_pad_label (filter_tool->operation, input_pads[i]));

          if (label == NULL)
            label = g_regex_replace (regex,
                                     input_pads[i], -1, 0,
                                     /* Translators: don't translate "Aux" */
                                     _("Aux\\1 Input"),
                                     0, NULL);

          input = gimp_operation_tool_aux_input_new (op_tool,
                                                     filter_tool->operation,
                                                     input_pads[i], label,
                                                     gegl_node_get_pad_description (filter_tool->operation,
                                                                                    input_pads[i]));

          op_tool->aux_inputs = g_list_prepend (op_tool->aux_inputs, input);

          g_free (label);

          g_regex_unref (regex);
        }

      g_strfreev (input_pads);
    }
}

static void
gimp_operation_tool_add_gui (GimpOperationTool *op_tool)
{
  GtkSizeGroup   *size_group  = NULL;
  GtkWidget      *options_gui;
  GtkWidget      *options_box;
  GtkWidget      *options_sw;
  GtkWidget      *shell;
  GdkRectangle    workarea;
  GtkRequisition  minimum;
  GList          *list;
  gboolean        scrolling;

  options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
  options_box = g_weak_ref_get (&op_tool->options_box_ref);
  options_sw  = g_weak_ref_get (&op_tool->options_sw_ref);
  g_return_if_fail (options_gui && options_box && options_sw);

  for (list = op_tool->aux_inputs; list; list = g_list_next (list))
    {
      AuxInput  *input = list->data;
      GtkWidget *toggle;

      if (! size_group)
        size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);

      toggle =
        gimp_buffer_source_box_get_toggle (GIMP_BUFFER_SOURCE_BOX (input->box));

      gtk_size_group_add_widget (size_group, toggle);

      gtk_box_pack_start (GTK_BOX (options_box), input->box,
                          FALSE, FALSE, 0);
      gtk_widget_show (input->box);
    }

  if (size_group)
    g_object_unref (size_group);

  gtk_box_pack_start (GTK_BOX (options_box), options_gui, TRUE, TRUE, 0);
  gtk_widget_show (options_gui);

  shell = GTK_WIDGET (gimp_display_get_shell (GIMP_TOOL (op_tool)->display));
  gdk_monitor_get_workarea (gimp_widget_get_monitor (shell), &workarea);
  gtk_widget_get_preferred_size (options_box, &minimum, NULL);

  scrolling = minimum.height > workarea.height / 2;

  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (options_sw),
                                  GTK_POLICY_NEVER,
                                  scrolling ?
                                  GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER);

  if (scrolling)
    gtk_widget_set_size_request (options_sw, -1, workarea.height / 2);
  else
    gtk_widget_set_size_request (options_sw, -1, -1);

  g_object_unref (options_gui);
  g_object_unref (options_box);
  g_object_unref (options_sw);
}


/*  aux input utility functions  */

static void
gimp_operation_tool_aux_input_notify (GimpBufferSourceBox *box,
                                      const GParamSpec    *pspec,
                                      AuxInput            *input)
{
  GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (input->tool);

  /* emit "notify" so GimpFilterTool will update its preview
   *
   * FIXME: this is a bad hack that should go away once GimpImageMap
   * and GimpFilterTool are refactored to be more filter-ish.
   */
  if (filter_tool->config)
    g_signal_emit_by_name (filter_tool->config,
                           "notify", NULL);
}

static AuxInput *
gimp_operation_tool_aux_input_new (GimpOperationTool *op_tool,
                                   GeglNode          *operation,
                                   const gchar       *input_pad,
                                   const gchar       *label,
                                   const gchar       *description)
{
  AuxInput    *input = g_slice_new (AuxInput);
  GimpContext *context;

  input->tool = op_tool;
  input->pad  = g_strdup (input_pad);
  input->node = gegl_node_new_child (NULL,
                                     "operation", "gegl:buffer-source",
                                     NULL);

  gegl_node_connect (input->node, "output",
                     operation,   input_pad);

  context = GIMP_CONTEXT (GIMP_TOOL_GET_OPTIONS (op_tool));

  input->box = gimp_buffer_source_box_new (context, input->node, label);
  if (description)
    gtk_widget_set_tooltip_text (input->box, description);

  /* make AuxInput owner of the box */
  g_object_ref_sink (input->box);

  g_signal_connect (input->box, "notify::pickable",
                    G_CALLBACK (gimp_operation_tool_aux_input_notify),
                    input);
  g_signal_connect (input->box, "notify::enabled",
                    G_CALLBACK (gimp_operation_tool_aux_input_notify),
                    input);

  return input;
}

static void
gimp_operation_tool_aux_input_detach (AuxInput *input)
{
  GtkWidget *parent = gtk_widget_get_parent (input->box);

  if (parent)
    gtk_container_remove (GTK_CONTAINER (parent), input->box);
}

static void
gimp_operation_tool_aux_input_clear (AuxInput *input)
{
  gimp_operation_tool_aux_input_detach (input);

  g_object_set (input->box,
                "pickable", NULL,
                NULL);
}

static void
gimp_operation_tool_aux_input_free (AuxInput *input)
{
  gimp_operation_tool_aux_input_clear (input);

  g_free (input->pad);
  g_object_unref (input->node);
  g_object_unref (input->box);

  g_slice_free (AuxInput, input);
}

static void
gimp_operation_tool_unlink_chains (GimpOperationTool *op_tool)
{
  GObject *options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
  GList   *chains;

  g_return_if_fail (options_gui != NULL);

  chains = g_object_get_data (options_gui, "chains");

  while (chains)
    {
      GimpChainButton *chain = chains->data;
      gboolean         active;

      active = gimp_chain_button_get_active (chain);

      g_object_set_data (G_OBJECT (chain), "was-active",
                         GINT_TO_POINTER (active));

      if (active)
        {
          gimp_chain_button_set_active (chain, FALSE);
        }

      chains = chains->next;
    }

  g_object_unref (options_gui);
}

static void
gimp_operation_tool_relink_chains (GimpOperationTool *op_tool)
{
  GimpFilterTool *filter_tool = GIMP_FILTER_TOOL (op_tool);
  GObject        *options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
  GList          *chains;

  g_return_if_fail (options_gui != NULL);

  chains = g_object_get_data (options_gui, "chains");

  while (chains)
    {
      GimpChainButton *chain = chains->data;

      if (g_object_get_data (G_OBJECT (chain), "was-active"))
        {
          const gchar *name_x    = g_object_get_data (chains->data, "x-property");
          const gchar *name_y    = g_object_get_data (chains->data, "y-property");
          const gchar *names[2]  = { name_x, name_y };
          GValue       values[2] = { G_VALUE_INIT, G_VALUE_INIT };
          GValue       double_x  = G_VALUE_INIT;
          GValue       double_y  = G_VALUE_INIT;

          g_object_getv (filter_tool->config, 2, names, values);

          g_value_init (&double_x, G_TYPE_DOUBLE);
          g_value_init (&double_y, G_TYPE_DOUBLE);

          if (g_value_transform (&values[0], &double_x) &&
              g_value_transform (&values[1], &double_y) &&
              g_value_get_double (&double_x) ==
              g_value_get_double (&double_y))
            {
              gimp_chain_button_set_active (chain, TRUE);
            }

          g_value_unset (&double_x);
          g_value_unset (&double_y);
          g_value_unset (&values[0]);
          g_value_unset (&values[1]);

          g_object_set_data (G_OBJECT (chain), "was-active", NULL);
        }

      chains = chains->next;
    }

  g_object_unref (options_gui);
}


/*  public functions  */

void
gimp_operation_tool_set_operation (GimpOperationTool  *op_tool,
                                   GimpDrawableFilter *filter,
                                   const gchar        *operation,
                                   const gchar        *title,
                                   const gchar        *description,
                                   const gchar        *undo_desc,
                                   const gchar        *icon_name,
                                   const gchar        *help_id)
{
  GimpTool       *tool;
  GimpFilterTool *filter_tool;
  GtkWidget      *options_gui;

  g_return_if_fail (GIMP_IS_OPERATION_TOOL (op_tool));

  tool        = GIMP_TOOL (op_tool);
  filter_tool = GIMP_FILTER_TOOL (op_tool);

  g_free (op_tool->operation);
  g_free (op_tool->description);

  op_tool->operation   = g_strdup (operation);
  op_tool->description = g_strdup (description);

  gimp_tool_set_label     (tool, title);
  gimp_tool_set_undo_desc (tool, undo_desc);
  gimp_tool_set_icon_name (tool, icon_name);
  gimp_tool_set_help_id   (tool, help_id);

  g_list_free_full (op_tool->aux_inputs,
                    (GDestroyNotify) gimp_operation_tool_aux_input_free);
  op_tool->aux_inputs = NULL;

  gimp_filter_tool_set_widget (filter_tool, NULL);

  options_gui = g_weak_ref_get (&op_tool->options_gui_ref);
  if (options_gui)
    {
      gimp_filter_tool_disable_color_picking (filter_tool);
      g_object_unref (options_gui);
      gtk_widget_destroy (options_gui);
    }

  if (! operation)
    return;

  gimp_filter_tool_get_operation (filter_tool, filter);

  /* Update filter name for GeglOperation tool presets */
  if (filter_tool->filter && description)
    g_object_set (filter_tool->filter,
                  "name", description,
                  NULL);

  if (tool->drawables)
    gimp_operation_tool_sync_op (op_tool, TRUE);

  if (filter_tool->config && tool->display)
    {
      GtkWidget *options_box;

      gimp_operation_tool_create_gui (op_tool);

      options_box = g_weak_ref_get (&op_tool->options_box_ref);
      if (options_box)
        {
          gimp_operation_tool_add_gui (op_tool);
          g_object_unref (options_box);
        }
    }
}
