/* 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
 *
 * pikabrusheditor.c
 * Copyright 1998 Jay Cox <jaycox@earthlink.net>
 *
 * 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 "libpikawidgets/pikawidgets.h"

#include "widgets-types.h"

#include "core/pika.h"
#include "core/pikabrushgenerated.h"
#include "core/pikacontext.h"

#include "pikabrusheditor.h"
#include "pikadocked.h"
#include "pikaview.h"
#include "pikaviewrenderer.h"

#include "pika-intl.h"


#define BRUSH_VIEW_SIZE 96


/*  local function prototypes  */

static void   pika_brush_editor_docked_iface_init (PikaDockedInterface *face);

static void   pika_brush_editor_constructed    (GObject            *object);

static void   pika_brush_editor_set_data       (PikaDataEditor     *editor,
                                                PikaData           *data);

static void   pika_brush_editor_set_context    (PikaDocked         *docked,
                                                PikaContext        *context);

static void   pika_brush_editor_update_brush   (GtkAdjustment      *adjustment,
                                                PikaBrushEditor    *editor);
static void   pika_brush_editor_update_shape   (GtkWidget          *widget,
                                                PikaBrushEditor    *editor);
static void   pika_brush_editor_notify_brush   (PikaBrushGenerated *brush,
                                                GParamSpec         *pspec,
                                                PikaBrushEditor    *editor);


G_DEFINE_TYPE_WITH_CODE (PikaBrushEditor, pika_brush_editor,
                         PIKA_TYPE_DATA_EDITOR,
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_DOCKED,
                                                pika_brush_editor_docked_iface_init))

#define parent_class pika_brush_editor_parent_class

static PikaDockedInterface *parent_docked_iface = NULL;


static void
pika_brush_editor_class_init (PikaBrushEditorClass *klass)
{
  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
  PikaDataEditorClass *editor_class = PIKA_DATA_EDITOR_CLASS (klass);

  object_class->constructed = pika_brush_editor_constructed;

  editor_class->set_data    = pika_brush_editor_set_data;
  editor_class->title       = _("Brush Editor");
}

static void
pika_brush_editor_docked_iface_init (PikaDockedInterface *iface)
{
  parent_docked_iface = g_type_interface_peek_parent (iface);

  if (! parent_docked_iface)
    parent_docked_iface = g_type_default_interface_peek (PIKA_TYPE_DOCKED);

  iface->set_context = pika_brush_editor_set_context;
}

static void
pika_brush_editor_init (PikaBrushEditor *editor)
{
  PikaDataEditor *data_editor = PIKA_DATA_EDITOR (editor);
  GtkWidget      *frame;
  GtkWidget      *hbox;
  GtkWidget      *label;
  GtkWidget      *box;
  GtkWidget      *scale;

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (editor), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  data_editor->view = pika_view_new_full_by_types (NULL,
                                                   PIKA_TYPE_VIEW,
                                                   PIKA_TYPE_BRUSH,
                                                   BRUSH_VIEW_SIZE,
                                                   BRUSH_VIEW_SIZE, 0,
                                                   FALSE, FALSE, TRUE);
  gtk_widget_set_size_request (data_editor->view, -1, BRUSH_VIEW_SIZE);
  pika_view_set_expand (PIKA_VIEW (data_editor->view), TRUE);
  gtk_container_add (GTK_CONTAINER (frame), data_editor->view);
  gtk_widget_show (data_editor->view);

  editor->shape_group = NULL;

  editor->options_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_box_pack_start (GTK_BOX (editor), editor->options_box, FALSE, FALSE, 0);
  gtk_widget_show (editor->options_box);

  /* Stock Box for the brush shape */
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
  gtk_box_pack_start (GTK_BOX (editor->options_box), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  label = gtk_label_new (_("Shape:"));
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  box = pika_enum_icon_box_new (PIKA_TYPE_BRUSH_GENERATED_SHAPE,
                                "pika-shape",
                                GTK_ICON_SIZE_MENU,
                                G_CALLBACK (pika_brush_editor_update_shape),
                                editor, NULL,
                                &editor->shape_group);
  gtk_box_pack_start (GTK_BOX (hbox), box, FALSE, FALSE, 0);
  gtk_widget_show (box);

  /*  brush radius scale  */
  editor->radius_data = gtk_adjustment_new (0.0, 0.1, 1000.0, 1.0, 10.0, 0.0);
  scale = pika_spin_scale_new (editor->radius_data, _("Radius"), 1);
  gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0);
  gtk_widget_show (scale);

  g_signal_connect (editor->radius_data, "value-changed",
                    G_CALLBACK (pika_brush_editor_update_brush),
                    editor);

  /*  number of spikes  */
  editor->spikes_data = gtk_adjustment_new (2.0, 2.0, 20.0, 1.0, 1.0, 0.0);
  scale = pika_spin_scale_new (editor->spikes_data, _("Spikes"), 0);
  gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0);
  gtk_widget_show (scale);

  g_signal_connect (editor->spikes_data, "value-changed",
                    G_CALLBACK (pika_brush_editor_update_brush),
                    editor);

  /*  brush hardness scale  */
  editor->hardness_data = gtk_adjustment_new (0.0, 0.0, 1.0, 0.01, 0.1, 0.0);
  scale = pika_spin_scale_new (editor->hardness_data, _("Hardness"), 2);
  gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0);
  gtk_widget_show (scale);

  g_signal_connect (editor->hardness_data, "value-changed",
                    G_CALLBACK (pika_brush_editor_update_brush),
                    editor);

  /*  brush aspect ratio scale  */
  editor->aspect_ratio_data = gtk_adjustment_new (0.0, 1.0, 20.0, 0.1, 1.0, 0.0);
  scale = pika_spin_scale_new (editor->aspect_ratio_data, _("Aspect ratio"), 1);
  gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0);
  gtk_widget_show (scale);

  g_signal_connect (editor->aspect_ratio_data,"value-changed",
                    G_CALLBACK (pika_brush_editor_update_brush),
                    editor);

  /*  brush angle scale  */
  editor->angle_data = gtk_adjustment_new (0.0, 0.0, 180.0, 0.1, 1.0, 0.0);
  scale = pika_spin_scale_new (editor->angle_data, _("Angle"), 1);
  gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0);
  gtk_widget_show (scale);

  g_signal_connect (editor->angle_data, "value-changed",
                    G_CALLBACK (pika_brush_editor_update_brush),
                    editor);

  /*  brush spacing  */
  editor->spacing_data = gtk_adjustment_new (0.0, 1.0, 5000.0, 1.0, 10.0, 0.0);
  scale = pika_spin_scale_new (editor->spacing_data, _("Spacing"), 1);
  pika_spin_scale_set_scale_limits (PIKA_SPIN_SCALE (scale), 1.0, 200.0);
  gtk_box_pack_start (GTK_BOX (editor->options_box), scale, FALSE, FALSE, 0);
  gtk_widget_show (scale);

  pika_help_set_help_data (scale, _("Percentage of width of brush"), NULL);

  g_signal_connect (editor->spacing_data, "value-changed",
                    G_CALLBACK (pika_brush_editor_update_brush),
                    editor);
}

static void
pika_brush_editor_constructed (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->constructed (object);

  pika_docked_set_show_button_bar (PIKA_DOCKED (object), FALSE);
}

static void
pika_brush_editor_set_data (PikaDataEditor *editor,
                            PikaData       *data)
{
  PikaBrushEditor         *brush_editor = PIKA_BRUSH_EDITOR (editor);
  PikaBrushGeneratedShape  shape        = PIKA_BRUSH_GENERATED_CIRCLE;
  gdouble                  radius       = 0.0;
  gint                     spikes       = 2;
  gdouble                  hardness     = 0.0;
  gdouble                  ratio        = 0.0;
  gdouble                  angle        = 0.0;
  gdouble                  spacing      = 0.0;

  if (editor->data)
    g_signal_handlers_disconnect_by_func (editor->data,
                                          pika_brush_editor_notify_brush,
                                          editor);

  PIKA_DATA_EDITOR_CLASS (parent_class)->set_data (editor, data);

  if (editor->data)
    g_signal_connect (editor->data, "notify",
                      G_CALLBACK (pika_brush_editor_notify_brush),
                      editor);

  pika_view_set_viewable (PIKA_VIEW (editor->view), PIKA_VIEWABLE (data));

  if (editor->data)
    {
      spacing = pika_brush_get_spacing (PIKA_BRUSH (editor->data));

      if (PIKA_IS_BRUSH_GENERATED (editor->data))
        {
          PikaBrushGenerated *brush = PIKA_BRUSH_GENERATED (editor->data);

          shape    = pika_brush_generated_get_shape        (brush);
          radius   = pika_brush_generated_get_radius       (brush);
          spikes   = pika_brush_generated_get_spikes       (brush);
          hardness = pika_brush_generated_get_hardness     (brush);
          ratio    = pika_brush_generated_get_aspect_ratio (brush);
          angle    = pika_brush_generated_get_angle        (brush);
        }
    }

  gtk_widget_set_sensitive (brush_editor->options_box,
                            editor->data_editable);

  pika_int_radio_group_set_active (GTK_RADIO_BUTTON (brush_editor->shape_group),
                                   shape);

  gtk_adjustment_set_value (brush_editor->radius_data,       radius);
  gtk_adjustment_set_value (brush_editor->spikes_data,       spikes);
  gtk_adjustment_set_value (brush_editor->hardness_data,     hardness);
  gtk_adjustment_set_value (brush_editor->aspect_ratio_data, ratio);
  gtk_adjustment_set_value (brush_editor->angle_data,        angle);
  gtk_adjustment_set_value (brush_editor->spacing_data,      spacing);
}

static void
pika_brush_editor_set_context (PikaDocked  *docked,
                               PikaContext *context)
{
  PikaDataEditor *data_editor = PIKA_DATA_EDITOR (docked);

  parent_docked_iface->set_context (docked, context);

  pika_view_renderer_set_context (PIKA_VIEW (data_editor->view)->renderer,
                                  context);
}


/*  public functions  */

GtkWidget *
pika_brush_editor_new (PikaContext     *context,
                       PikaMenuFactory *menu_factory)
{
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  return g_object_new (PIKA_TYPE_BRUSH_EDITOR,
                       "menu-factory",    menu_factory,
                       "menu-identifier", "<BrushEditor>",
                       "ui-path",         "/brush-editor-popup",
                       "data-factory",    context->pika->brush_factory,
                       "context",         context,
                       "data",            pika_context_get_brush (context),
                       NULL);
}


/*  private functions  */

static void
pika_brush_editor_update_brush (GtkAdjustment   *adjustment,
                                PikaBrushEditor *editor)
{
  PikaBrushGenerated *brush;
  gdouble             value;

  if (! PIKA_IS_BRUSH_GENERATED (PIKA_DATA_EDITOR (editor)->data))
    return;

  brush = PIKA_BRUSH_GENERATED (PIKA_DATA_EDITOR (editor)->data);

  g_signal_handlers_block_by_func (brush,
                                   pika_brush_editor_notify_brush,
                                   editor);

  value = gtk_adjustment_get_value (adjustment);

  if (adjustment == editor->radius_data)
    {
      if (value != pika_brush_generated_get_radius (brush))
        pika_brush_generated_set_radius (brush, value);
    }
  else if (adjustment == editor->spikes_data)
    {
      if (ROUND (value) != pika_brush_generated_get_spikes (brush))
        pika_brush_generated_set_spikes (brush, ROUND (value));
    }
  else if (adjustment == editor->hardness_data)
    {
      if (value != pika_brush_generated_get_hardness (brush))
        pika_brush_generated_set_hardness (brush, value);
    }
  else if (adjustment == editor->aspect_ratio_data)
    {
      if (value != pika_brush_generated_get_aspect_ratio (brush))
        pika_brush_generated_set_aspect_ratio (brush, value);
    }
  else if (adjustment == editor->angle_data)
    {
      if (value != pika_brush_generated_get_angle (brush))
        pika_brush_generated_set_angle (brush, value);
    }
  else if (adjustment == editor->spacing_data)
    {
      if (value != pika_brush_get_spacing (PIKA_BRUSH (brush)))
        pika_brush_set_spacing (PIKA_BRUSH (brush), value);
    }

  g_signal_handlers_unblock_by_func (brush,
                                     pika_brush_editor_notify_brush,
                                     editor);
}

static void
pika_brush_editor_update_shape (GtkWidget       *widget,
                                PikaBrushEditor *editor)
{
  PikaBrushGenerated *brush;

  if (! PIKA_IS_BRUSH_GENERATED (PIKA_DATA_EDITOR (editor)->data))
    return;

  brush = PIKA_BRUSH_GENERATED (PIKA_DATA_EDITOR (editor)->data);

  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    {
      PikaBrushGeneratedShape shape;

      shape = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
                                                  "pika-item-data"));

      if (pika_brush_generated_get_shape (brush) != shape)
        pika_brush_generated_set_shape (brush, shape);
    }
}

static void
pika_brush_editor_notify_brush (PikaBrushGenerated *brush,
                                GParamSpec         *pspec,
                                PikaBrushEditor    *editor)
{
  GtkAdjustment *adj   = NULL;
  gdouble        value = 0.0;

  if (! strcmp (pspec->name, "shape"))
    {
      g_signal_handlers_block_by_func (editor->shape_group,
                                       pika_brush_editor_update_shape,
                                       editor);

      pika_int_radio_group_set_active (GTK_RADIO_BUTTON (editor->shape_group),
                                       brush->shape);

      g_signal_handlers_unblock_by_func (editor->shape_group,
                                         pika_brush_editor_update_shape,
                                         editor);
    }
  else if (! strcmp (pspec->name, "radius"))
    {
      adj   = editor->radius_data;
      value = pika_brush_generated_get_radius (brush);
    }
  else if (! strcmp (pspec->name, "spikes"))
    {
      adj   = editor->spikes_data;
      value = pika_brush_generated_get_spikes (brush);
    }
  else if (! strcmp (pspec->name, "hardness"))
    {
      adj   = editor->hardness_data;
      value = pika_brush_generated_get_hardness (brush);
    }
  else if (! strcmp (pspec->name, "angle"))
    {
      adj   = editor->angle_data;
      value = pika_brush_generated_get_angle (brush);
    }
  else if (! strcmp (pspec->name, "aspect-ratio"))
    {
      adj   = editor->aspect_ratio_data;
      value = pika_brush_generated_get_aspect_ratio (brush);
    }
  else if (! strcmp (pspec->name, "spacing"))
    {
      adj   = editor->spacing_data;
      value = pika_brush_get_spacing (PIKA_BRUSH (brush));
    }

  if (adj)
    {
      g_signal_handlers_block_by_func (adj,
                                       pika_brush_editor_update_brush,
                                       editor);

      gtk_adjustment_set_value (adj, value);

      g_signal_handlers_unblock_by_func (adj,
                                         pika_brush_editor_update_brush,
                                         editor);
    }
}
