/* 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
 *
 * pikafiltertool-widgets.c
 *
 * 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 "libpikamath/pikamath.h"

#include "tools-types.h"

#include "core/pikacontainer.h"
#include "core/pikaitem.h"

#include "display/pikadisplay.h"
#include "display/pikatoolfocus.h"
#include "display/pikatoolgyroscope.h"
#include "display/pikatoolline.h"
#include "display/pikatooltransformgrid.h"
#include "display/pikatoolwidgetgroup.h"

#include "pikafilteroptions.h"
#include "pikafiltertool.h"
#include "pikafiltertool-widgets.h"


typedef struct _Controller Controller;

struct _Controller
{
  PikaFilterTool     *filter_tool;
  PikaControllerType  controller_type;
  PikaToolWidget     *widget;
  GCallback           creator_callback;
  gpointer            creator_data;
};


/*  local function prototypes  */

static Controller * pika_filter_tool_controller_new          (void);
static void         pika_filter_tool_controller_free         (Controller                 *controller);

static void         pika_filter_tool_set_line                (Controller                 *controller,
                                                              GeglRectangle              *area,
                                                              gdouble                     x1,
                                                              gdouble                     y1,
                                                              gdouble                     x2,
                                                              gdouble                     y2);
static void         pika_filter_tool_line_changed            (PikaToolWidget             *widget,
                                                              Controller                 *controller);

static void         pika_filter_tool_set_slider_line         (Controller                 *controller,
                                                              GeglRectangle              *area,
                                                              gdouble                     x1,
                                                              gdouble                     y1,
                                                              gdouble                     x2,
                                                              gdouble                     y2,
                                                              const PikaControllerSlider *sliders,
                                                              gint                        n_sliders);
static void         pika_filter_tool_slider_line_changed     (PikaToolWidget             *widget,
                                                              Controller                 *controller);

static void         pika_filter_tool_set_transform_grid      (Controller                 *controller,
                                                              GeglRectangle              *area,
                                                              const PikaMatrix3          *transform);
static void         pika_filter_tool_transform_grid_changed  (PikaToolWidget             *widget,
                                                              Controller                 *controller);

static void         pika_filter_tool_set_transform_grids     (Controller                 *controller,
                                                              GeglRectangle              *area,
                                                              const PikaMatrix3          *transforms,
                                                              gint                        n_transforms);
static void         pika_filter_tool_transform_grids_changed (PikaToolWidget             *widget,
                                                              Controller                 *controller);

static void         pika_filter_tool_set_gyroscope           (Controller                 *controller,
                                                              GeglRectangle              *area,
                                                              gdouble                     yaw,
                                                              gdouble                     pitch,
                                                              gdouble                     roll,
                                                              gdouble                     zoom,
                                                              gboolean                    invert);
static void         pika_filter_tool_gyroscope_changed       (PikaToolWidget             *widget,
                                                              Controller                 *controller);

static void         pika_filter_tool_set_focus               (Controller                 *controller,
                                                              GeglRectangle              *area,
                                                              PikaLimitType               type,
                                                              gdouble                     x,
                                                              gdouble                     y,
                                                              gdouble                     radius,
                                                              gdouble                     aspect_ratio,
                                                              gdouble                     angle,
                                                              gdouble                     inner_limit,
                                                              gdouble                     midpoint);
static void         pika_filter_tool_focus_changed           (PikaToolWidget             *widget,
                                                              Controller                 *controller);


/*  public functions  */

PikaToolWidget *
pika_filter_tool_create_widget (PikaFilterTool     *filter_tool,
                                PikaControllerType  controller_type,
                                const gchar        *status_title,
                                GCallback           callback,
                                gpointer            callback_data,
                                GCallback          *set_func,
                                gpointer           *set_func_data)
{
  PikaTool         *tool;
  PikaDisplayShell *shell;
  Controller       *controller;

  g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), NULL);
  g_return_val_if_fail (filter_tool->config != NULL, NULL);

  tool = PIKA_TOOL (filter_tool);

  g_return_val_if_fail (tool->display != NULL, NULL);

  shell = pika_display_get_shell (tool->display);

  controller = pika_filter_tool_controller_new ();

  controller->filter_tool      = filter_tool;
  controller->controller_type  = controller_type;
  controller->creator_callback = callback;
  controller->creator_data     = callback_data;

  switch (controller_type)
    {
    case PIKA_CONTROLLER_TYPE_LINE:
      controller->widget = pika_tool_line_new (shell, 100, 100, 500, 500);

      g_object_set (controller->widget,
                    "status-title", status_title,
                    NULL);

      g_signal_connect (controller->widget, "changed",
                        G_CALLBACK (pika_filter_tool_line_changed),
                        controller);

      *set_func      = (GCallback) pika_filter_tool_set_line;
      *set_func_data = controller;
      break;

    case PIKA_CONTROLLER_TYPE_SLIDER_LINE:
      controller->widget = pika_tool_line_new (shell, 100, 100, 500, 500);

      g_object_set (controller->widget,
                    "status-title", status_title,
                    NULL);

      g_signal_connect (controller->widget, "changed",
                        G_CALLBACK (pika_filter_tool_slider_line_changed),
                        controller);

      *set_func      = (GCallback) pika_filter_tool_set_slider_line;
      *set_func_data = controller;
      break;

    case PIKA_CONTROLLER_TYPE_TRANSFORM_GRID:
      {
        PikaMatrix3   transform;
        gint          off_x, off_y;
        GeglRectangle area;
        gdouble       x1, y1;
        gdouble       x2, y2;

        pika_matrix3_identity (&transform);

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

        x1 = off_x + area.x;
        y1 = off_y + area.y;
        x2 = x1 + area.width;
        y2 = y1 + area.height;

        controller->widget = pika_tool_transform_grid_new (shell, &transform,
                                                           x1, y1, x2, y2);

        g_object_set (controller->widget,
                      "pivot-x",                 (x1 + x2) / 2.0,
                      "pivot-y",                 (y1 + y2) / 2.0,
                      "inside-function",         PIKA_TRANSFORM_FUNCTION_MOVE,
                      "outside-function",        PIKA_TRANSFORM_FUNCTION_ROTATE,
                      "use-corner-handles",      TRUE,
                      "use-perspective-handles", TRUE,
                      "use-side-handles",        TRUE,
                      "use-shear-handles",       TRUE,
                      "use-pivot-handle",        TRUE,
                      NULL);

        g_signal_connect (controller->widget, "changed",
                          G_CALLBACK (pika_filter_tool_transform_grid_changed),
                          controller);

        *set_func      = (GCallback) pika_filter_tool_set_transform_grid;
        *set_func_data = controller;
      }
      break;

    case PIKA_CONTROLLER_TYPE_TRANSFORM_GRIDS:
      {
        controller->widget = pika_tool_widget_group_new (shell);

        pika_tool_widget_group_set_auto_raise (
          PIKA_TOOL_WIDGET_GROUP (controller->widget), TRUE);

        g_signal_connect (controller->widget, "changed",
                          G_CALLBACK (pika_filter_tool_transform_grids_changed),
                          controller);

        *set_func      = (GCallback) pika_filter_tool_set_transform_grids;
        *set_func_data = controller;
      }
      break;

    case PIKA_CONTROLLER_TYPE_GYROSCOPE:
      {
        GeglRectangle area;
        gint          off_x, off_y;

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

        controller->widget = pika_tool_gyroscope_new (shell);

        g_object_set (controller->widget,
                      "speed",   1.0 / MAX (area.width, area.height),
                      "pivot-x", off_x + area.x + area.width  / 2.0,
                      "pivot-y", off_y + area.y + area.height / 2.0,
                      NULL);

        g_signal_connect (controller->widget, "changed",
                          G_CALLBACK (pika_filter_tool_gyroscope_changed),
                          controller);

        *set_func      = (GCallback) pika_filter_tool_set_gyroscope;
        *set_func_data = controller;
      }
      break;

    case PIKA_CONTROLLER_TYPE_FOCUS:
      controller->widget = pika_tool_focus_new (shell);

      g_signal_connect (controller->widget, "changed",
                        G_CALLBACK (pika_filter_tool_focus_changed),
                        controller);

      *set_func      = (GCallback) pika_filter_tool_set_focus;
      *set_func_data = controller;
      break;
    }

  g_object_add_weak_pointer (G_OBJECT (controller->widget),
                            (gpointer) &controller->widget);

  g_object_set_data_full (filter_tool->config,
                          "pika-filter-tool-controller", controller,
                          (GDestroyNotify) pika_filter_tool_controller_free);

  return controller->widget;
}

static void
pika_filter_tool_reset_transform_grid (PikaToolWidget *widget,
                                       PikaFilterTool *filter_tool)
{
  PikaMatrix3   *transform;
  gint           off_x, off_y;
  GeglRectangle  area;
  gdouble        x1, y1;
  gdouble        x2, y2;
  gdouble        pivot_x, pivot_y;

  g_object_get (widget,
                "transform", &transform,
                NULL);

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

  x1 = off_x + area.x;
  y1 = off_y + area.y;
  x2 = x1 + area.width;
  y2 = y1 + area.height;

  pika_matrix3_transform_point (transform,
                                (x1 + x2) / 2.0, (y1 + y2) / 2.0,
                                &pivot_x, &pivot_y);

  g_object_set (widget,
                "pivot-x", pivot_x,
                "pivot-y", pivot_y,
                NULL);

  g_free (transform);
}

void
pika_filter_tool_reset_widget (PikaFilterTool *filter_tool,
                               PikaToolWidget *widget)
{
  Controller *controller;

  g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool));
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (filter_tool->config != NULL);

  controller = g_object_get_data (filter_tool->config,
                                  "pika-filter-tool-controller");

  g_return_if_fail (controller != NULL);

  switch (controller->controller_type)
    {
    case PIKA_CONTROLLER_TYPE_TRANSFORM_GRID:
      {
        g_signal_handlers_block_by_func (controller->widget,
                                         pika_filter_tool_transform_grid_changed,
                                         controller);

        pika_filter_tool_reset_transform_grid (controller->widget, filter_tool);

        g_signal_handlers_unblock_by_func (controller->widget,
                                           pika_filter_tool_transform_grid_changed,
                                           controller);
      }
      break;

    case PIKA_CONTROLLER_TYPE_TRANSFORM_GRIDS:
      {
        g_signal_handlers_block_by_func (controller->widget,
                                         pika_filter_tool_transform_grids_changed,
                                         controller);

        pika_container_foreach (
          pika_tool_widget_group_get_children (
            PIKA_TOOL_WIDGET_GROUP (controller->widget)),
          (GFunc) pika_filter_tool_reset_transform_grid,
          filter_tool);

        g_signal_handlers_unblock_by_func (controller->widget,
                                           pika_filter_tool_transform_grids_changed,
                                           controller);
      }
      break;

    default:
      break;
    }
}


/*  private functions  */

static Controller *
pika_filter_tool_controller_new (void)
{
  return g_slice_new0 (Controller);
}

static void
pika_filter_tool_controller_free (Controller *controller)
{
  if (controller->widget)
    {
      g_signal_handlers_disconnect_by_data (controller->widget, controller);

      g_clear_weak_pointer (&controller->widget);
    }

  g_slice_free (Controller, controller);
}

static void
pika_filter_tool_set_line (Controller    *controller,
                           GeglRectangle *area,
                           gdouble        x1,
                           gdouble        y1,
                           gdouble        x2,
                           gdouble        y2)
{
  PikaTool     *tool;
  PikaDrawable *drawable;

  if (! controller->widget)
    return;

  tool     = PIKA_TOOL (controller->filter_tool);
  drawable = tool->drawables->data;

  if (drawable)
    {
      gint off_x, off_y;

      pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

      x1 += off_x + area->x;
      y1 += off_y + area->y;
      x2 += off_x + area->x;
      y2 += off_y + area->y;
    }

  g_signal_handlers_block_by_func (controller->widget,
                                   pika_filter_tool_line_changed,
                                   controller);

  g_object_set (controller->widget,
                "x1", x1,
                "y1", y1,
                "x2", x2,
                "y2", y2,
                NULL);

  g_signal_handlers_unblock_by_func (controller->widget,
                                     pika_filter_tool_line_changed,
                                     controller);
}

static void
pika_filter_tool_line_changed (PikaToolWidget *widget,
                               Controller     *controller)
{
  PikaFilterTool             *filter_tool = controller->filter_tool;
  PikaControllerLineCallback  line_callback;
  gdouble                     x1, y1, x2, y2;
  gint                        off_x, off_y;
  GeglRectangle               area;

  line_callback = (PikaControllerLineCallback) controller->creator_callback;

  g_object_get (widget,
                "x1", &x1,
                "y1", &y1,
                "x2", &x2,
                "y2", &y2,
                NULL);

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

  x1 -= off_x + area.x;
  y1 -= off_y + area.y;
  x2 -= off_x + area.x;
  y2 -= off_y + area.y;

  line_callback (controller->creator_data,
                 &area, x1, y1, x2, y2);
}

static void
pika_filter_tool_set_slider_line (Controller                 *controller,
                                  GeglRectangle              *area,
                                  gdouble                     x1,
                                  gdouble                     y1,
                                  gdouble                     x2,
                                  gdouble                     y2,
                                  const PikaControllerSlider *sliders,
                                  gint                        n_sliders)
{
  PikaTool     *tool;
  PikaDrawable *drawable;

  if (! controller->widget)
    return;

  tool     = PIKA_TOOL (controller->filter_tool);
  drawable = tool->drawables->data;

  if (drawable)
    {
      gint off_x, off_y;

      pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

      x1 += off_x + area->x;
      y1 += off_y + area->y;
      x2 += off_x + area->x;
      y2 += off_y + area->y;
    }

  g_signal_handlers_block_by_func (controller->widget,
                                   pika_filter_tool_slider_line_changed,
                                   controller);

  g_object_set (controller->widget,
                "x1", x1,
                "y1", y1,
                "x2", x2,
                "y2", y2,
                NULL);

  pika_tool_line_set_sliders (PIKA_TOOL_LINE (controller->widget),
                              sliders, n_sliders);

  g_signal_handlers_unblock_by_func (controller->widget,
                                     pika_filter_tool_slider_line_changed,
                                     controller);
}

static void
pika_filter_tool_slider_line_changed (PikaToolWidget *widget,
                                      Controller     *controller)
{
  PikaFilterTool                   *filter_tool = controller->filter_tool;
  PikaControllerSliderLineCallback  slider_line_callback;
  gdouble                           x1, y1, x2, y2;
  const PikaControllerSlider       *sliders;
  gint                              n_sliders;
  gint                              off_x, off_y;
  GeglRectangle                     area;

  slider_line_callback =
    (PikaControllerSliderLineCallback) controller->creator_callback;

  g_object_get (widget,
                "x1", &x1,
                "y1", &y1,
                "x2", &x2,
                "y2", &y2,
                NULL);

  sliders = pika_tool_line_get_sliders (PIKA_TOOL_LINE (controller->widget),
                                        &n_sliders);

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

  x1 -= off_x + area.x;
  y1 -= off_y + area.y;
  x2 -= off_x + area.x;
  y2 -= off_y + area.y;

  slider_line_callback (controller->creator_data,
                        &area, x1, y1, x2, y2, sliders, n_sliders);
}

static void
pika_filter_tool_set_transform_grid (Controller        *controller,
                                     GeglRectangle     *area,
                                     const PikaMatrix3 *transform)
{
  PikaTool     *tool;
  PikaDrawable *drawable;
  gdouble       x1 = area->x;
  gdouble       y1 = area->y;
  gdouble       x2 = area->x + area->width;
  gdouble       y2 = area->y + area->height;
  PikaMatrix3   matrix;

  if (! controller->widget)
    return;

  tool     = PIKA_TOOL (controller->filter_tool);
  drawable = tool->drawables->data;

  if (drawable)
    {
      gint off_x, off_y;

      pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

      x1 += off_x;
      y1 += off_y;
      x2 += off_x;
      y2 += off_y;
    }

  pika_matrix3_identity (&matrix);
  pika_matrix3_translate (&matrix, -x1, -y1);
  pika_matrix3_mult (transform, &matrix);
  pika_matrix3_translate (&matrix, +x1, +y1);

  g_signal_handlers_block_by_func (controller->widget,
                                   pika_filter_tool_transform_grid_changed,
                                   controller);

  g_object_set (controller->widget,
                "transform", &matrix,
                "x1",        x1,
                "y1",        y1,
                "x2",        x2,
                "y2",        y2,
                NULL);

  g_signal_handlers_unblock_by_func (controller->widget,
                                     pika_filter_tool_transform_grid_changed,
                                     controller);
}

static void
pika_filter_tool_transform_grid_changed (PikaToolWidget *widget,
                                         Controller     *controller)
{
  PikaFilterTool                      *filter_tool = controller->filter_tool;
  PikaControllerTransformGridCallback  transform_grid_callback;
  gint                                 off_x, off_y;
  GeglRectangle                        area;
  PikaMatrix3                         *transform;
  PikaMatrix3                          matrix;

  transform_grid_callback =
    (PikaControllerTransformGridCallback) controller->creator_callback;

  g_object_get (widget,
                "transform", &transform,
                NULL);

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

  pika_matrix3_identity (&matrix);
  pika_matrix3_translate (&matrix, +(off_x + area.x), +(off_y + area.y));
  pika_matrix3_mult (transform, &matrix);
  pika_matrix3_translate (&matrix, -(off_x + area.x), -(off_y + area.y));

  transform_grid_callback (controller->creator_data,
                           &area, &matrix);

  g_free (transform);
}

static void
pika_filter_tool_set_transform_grids (Controller        *controller,
                                      GeglRectangle     *area,
                                      const PikaMatrix3 *transforms,
                                      gint               n_transforms)
{
  PikaTool         *tool;
  PikaDisplayShell *shell;
  PikaDrawable     *drawable;
  PikaContainer    *grids;
  PikaToolWidget   *grid = NULL;
  gdouble           x1   = area->x;
  gdouble           y1   = area->y;
  gdouble           x2   = area->x + area->width;
  gdouble           y2   = area->y + area->height;
  PikaMatrix3       matrix;
  gint              i;

  if (! controller->widget)
    return;

  tool     = PIKA_TOOL (controller->filter_tool);
  shell    = pika_display_get_shell (tool->display);
  drawable = tool->drawables->data;

  g_signal_handlers_block_by_func (controller->widget,
                                   pika_filter_tool_transform_grids_changed,
                                   controller);

  if (drawable)
    {
      gint off_x, off_y;

      pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

      x1 += off_x;
      y1 += off_y;
      x2 += off_x;
      y2 += off_y;
    }

  grids = pika_tool_widget_group_get_children (
    PIKA_TOOL_WIDGET_GROUP (controller->widget));

  if (n_transforms > pika_container_get_n_children (grids))
    {
      pika_matrix3_identity (&matrix);

      for (i = pika_container_get_n_children (grids); i < n_transforms; i++)
        {
          gdouble pivot_x;
          gdouble pivot_y;

          grid = pika_tool_transform_grid_new (shell, &matrix, x1, y1, x2, y2);

          if (i > 0 && ! memcmp (&transforms[i], &transforms[i - 1],
                                 sizeof (PikaMatrix3)))
            {
              g_object_get (pika_container_get_last_child (grids),
                            "pivot-x", &pivot_x,
                            "pivot-y", &pivot_y,
                            NULL);
            }
          else
            {
              pivot_x = (x1 + x2) / 2.0;
              pivot_y = (y1 + y2) / 2.0;

              pika_matrix3_transform_point (&transforms[i],
                                            pivot_x, pivot_y,
                                            &pivot_x, &pivot_y);
            }

          g_object_set (grid,
                        "pivot-x",                 pivot_x,
                        "pivot-y",                 pivot_y,
                        "inside-function",         PIKA_TRANSFORM_FUNCTION_MOVE,
                        "outside-function",        PIKA_TRANSFORM_FUNCTION_ROTATE,
                        "use-corner-handles",      TRUE,
                        "use-perspective-handles", TRUE,
                        "use-side-handles",        TRUE,
                        "use-shear-handles",       TRUE,
                        "use-pivot-handle",        TRUE,
                        NULL);

          pika_container_add (grids, PIKA_OBJECT (grid));

          g_object_unref (grid);
        }

      pika_tool_widget_set_focus (grid, TRUE);
    }
  else
    {
      while (pika_container_get_n_children (grids) > n_transforms)
        pika_container_remove (grids, pika_container_get_last_child (grids));
    }

  for (i = 0; i < n_transforms; i++)
    {
      pika_matrix3_identity (&matrix);
      pika_matrix3_translate (&matrix, -x1, -y1);
      pika_matrix3_mult (&transforms[i], &matrix);
      pika_matrix3_translate (&matrix, +x1, +y1);

      g_object_set (pika_container_get_child_by_index (grids, i),
                    "transform", &matrix,
                    "x1",        x1,
                    "y1",        y1,
                    "x2",        x2,
                    "y2",        y2,
                    NULL);
    }

  g_signal_handlers_unblock_by_func (controller->widget,
                                     pika_filter_tool_transform_grids_changed,
                                     controller);
}

static void
pika_filter_tool_transform_grids_changed (PikaToolWidget *widget,
                                          Controller     *controller)
{
  PikaFilterTool                       *filter_tool = controller->filter_tool;
  PikaControllerTransformGridsCallback  transform_grids_callback;
  PikaContainer                        *grids;
  gint                                  off_x, off_y;
  GeglRectangle                         area;
  PikaMatrix3                          *transforms;
  gint                                  n_transforms;
  gint                                  i;

  transform_grids_callback =
    (PikaControllerTransformGridsCallback) controller->creator_callback;

  grids = pika_tool_widget_group_get_children (
    PIKA_TOOL_WIDGET_GROUP (controller->widget));

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

  n_transforms = pika_container_get_n_children (grids);
  transforms   = g_new (PikaMatrix3, n_transforms);

  for (i = 0; i < n_transforms; i++)
    {
      PikaMatrix3 *transform;

      g_object_get (pika_container_get_child_by_index (grids, i),
                    "transform", &transform,
                    NULL);

      pika_matrix3_identity (&transforms[i]);
      pika_matrix3_translate (&transforms[i],
                              +(off_x + area.x), +(off_y + area.y));
      pika_matrix3_mult (transform, &transforms[i]);
      pika_matrix3_translate (&transforms[i],
                              -(off_x + area.x), -(off_y + area.y));

      g_free (transform);
    }

  transform_grids_callback (controller->creator_data,
                            &area, transforms, n_transforms);

  g_free (transforms);
}

static void
pika_filter_tool_set_gyroscope (Controller    *controller,
                                GeglRectangle *area,
                                gdouble        yaw,
                                gdouble        pitch,
                                gdouble        roll,
                                gdouble        zoom,
                                gboolean       invert)
{
  if (! controller->widget)
    return;

  g_signal_handlers_block_by_func (controller->widget,
                                   pika_filter_tool_gyroscope_changed,
                                   controller);

  g_object_set (controller->widget,
                "yaw",    yaw,
                "pitch",  pitch,
                "roll",   roll,
                "zoom",   zoom,
                "invert", invert,
                NULL);

  g_signal_handlers_unblock_by_func (controller->widget,
                                     pika_filter_tool_gyroscope_changed,
                                     controller);
}

static void
pika_filter_tool_gyroscope_changed (PikaToolWidget *widget,
                                    Controller     *controller)
{
  PikaFilterTool                  *filter_tool = controller->filter_tool;
  PikaControllerGyroscopeCallback  gyroscope_callback;
  gint                             off_x, off_y;
  GeglRectangle                    area;
  gdouble                          yaw;
  gdouble                          pitch;
  gdouble                          roll;
  gdouble                          zoom;
  gboolean                         invert;

  gyroscope_callback =
    (PikaControllerGyroscopeCallback) controller->creator_callback;

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

  g_object_get (widget,
                "yaw",    &yaw,
                "pitch",  &pitch,
                "roll",   &roll,
                "zoom",   &zoom,
                "invert", &invert,
                NULL);

  gyroscope_callback (controller->creator_data,
                      &area, yaw, pitch, roll, zoom, invert);
}

static void
pika_filter_tool_set_focus (Controller    *controller,
                            GeglRectangle *area,
                            PikaLimitType  type,
                            gdouble        x,
                            gdouble        y,
                            gdouble        radius,
                            gdouble        aspect_ratio,
                            gdouble        angle,
                            gdouble        inner_limit,
                            gdouble        midpoint)
{
  PikaTool     *tool;
  PikaDrawable *drawable;

  if (! controller->widget)
    return;

  tool     = PIKA_TOOL (controller->filter_tool);
  drawable = tool->drawables->data;

  if (drawable)
    {
      gint off_x, off_y;

      pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

      x += off_x + area->x;
      y += off_y + area->y;
    }

  g_signal_handlers_block_by_func (controller->widget,
                                   pika_filter_tool_focus_changed,
                                   controller);

  g_object_set (controller->widget,
                "type",         type,
                "x",            x,
                "y",            y,
                "radius",       radius,
                "aspect-ratio", aspect_ratio,
                "angle",        angle,
                "inner-limit",  inner_limit,
                "midpoint",     midpoint,
                NULL);

  g_signal_handlers_unblock_by_func (controller->widget,
                                     pika_filter_tool_focus_changed,
                                     controller);
}

static void
pika_filter_tool_focus_changed (PikaToolWidget *widget,
                                Controller     *controller)
{
  PikaFilterTool              *filter_tool = controller->filter_tool;
  PikaControllerFocusCallback  focus_callback;
  PikaLimitType                type;
  gdouble                      x,  y;
  gdouble                      radius;
  gdouble                      aspect_ratio;
  gdouble                      angle;
  gdouble                      inner_limit;
  gdouble                      midpoint;
  gint                         off_x, off_y;
  GeglRectangle                area;

  focus_callback = (PikaControllerFocusCallback) controller->creator_callback;

  g_object_get (widget,
                "type",         &type,
                "x",            &x,
                "y",            &y,
                "radius",       &radius,
                "aspect-ratio", &aspect_ratio,
                "angle",        &angle,
                "inner-limit",  &inner_limit,
                "midpoint",     &midpoint,
                NULL);

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

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

  focus_callback (controller->creator_data,
                  &area,
                  type,
                  x,
                  y,
                  radius,
                  aspect_ratio,
                  angle,
                  inner_limit,
                  midpoint);
}
