/* LIBPIKA - The PIKA Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * pikalabelcolor.c
 * Copyright (C) 2022 Jehan
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

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

#include "libpikacolor/pikacolor.h"
#include "libpikamath/pikamath.h"
#include "libpikabase/pikabase.h"

#include "pikawidgets.h"
#include "pikawidgets-private.h"


/**
 * SECTION: pikalabelcolor
 * @title: PikaLabelColor
 * @short_description: Widget containing a color widget and a label.
 *
 * This widget is a subclass of #PikaLabeled with a #GtkColor.
 **/

enum
{
  VALUE_CHANGED,
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_VALUE,
  PROP_EDITABLE,
  N_PROPS
};
static GParamSpec *object_props[N_PROPS] = { NULL, };


typedef struct _PikaLabelColorPrivate
{
  GtkWidget *area;
  gboolean   editable;
} PikaLabelColorPrivate;

static void        pika_label_color_constructed       (GObject       *object);
static void        pika_label_color_set_property      (GObject       *object,
                                                       guint          property_id,
                                                       const GValue  *value,
                                                       GParamSpec    *pspec);
static void        pika_label_color_get_property      (GObject       *object,
                                                       guint          property_id,
                                                       GValue        *value,
                                                       GParamSpec    *pspec);

static GtkWidget * pika_label_color_populate          (PikaLabeled   *color,
                                                       gint          *x,
                                                       gint          *y,
                                                       gint          *width,
                                                       gint          *height);

G_DEFINE_TYPE_WITH_PRIVATE (PikaLabelColor, pika_label_color, PIKA_TYPE_LABELED)

#define parent_class pika_label_color_parent_class

static guint pika_label_color_signals[LAST_SIGNAL] = { 0 };

static void
pika_label_color_class_init (PikaLabelColorClass *klass)
{
  GObjectClass     *object_class  = G_OBJECT_CLASS (klass);
  PikaLabeledClass *labeled_class = PIKA_LABELED_CLASS (klass);
  PikaRGB           black;

  pika_label_color_signals[VALUE_CHANGED] =
    g_signal_new ("value-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaLabelColorClass, value_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  object_class->constructed  = pika_label_color_constructed;
  object_class->set_property = pika_label_color_set_property;
  object_class->get_property = pika_label_color_get_property;

  labeled_class->populate    = pika_label_color_populate;

  /**
   * PikaLabelColor:value:
   *
   * The currently set value.
   *
   * Since: 3.0
   **/
  pika_rgba_set (&black, 0.0, 0.0, 0.0, 1.0);
  object_props[PROP_VALUE] = pika_param_spec_rgb ("value",
                                                  "Color",
                                                  "The displayed color",
                                                  TRUE, &black,
                                                  PIKA_PARAM_READWRITE    |
                                                  G_PARAM_CONSTRUCT);

  /**
   * PikaLabelColor:editable:
   *
   * Whether the color can be edited.
   *
   * Since: 3.0
   **/
  object_props[PROP_EDITABLE] = g_param_spec_boolean ("editable",
                                                      "Whether the color can be edited",
                                                      "Whether the color can be edited",
                                                      FALSE,
                                                      PIKA_PARAM_READWRITE);

  g_object_class_install_properties (object_class, N_PROPS, object_props);
}

static void
pika_label_color_init (PikaLabelColor *color)
{
  PikaLabelColorPrivate *priv = pika_label_color_get_instance_private (color);
  PikaRGB                black;

  pika_rgba_set (&black, 0.0, 0.0, 0.0, 1.0);
  priv->editable = FALSE;
  priv->area = pika_color_area_new (&black, PIKA_COLOR_AREA_SMALL_CHECKS,
                                    GDK_BUTTON1_MASK | GDK_BUTTON2_MASK);

  /* Typically for a labelled color area, a small square next to your
   * label is probably what you want to display.
   */
  gtk_widget_set_size_request (priv->area, 20, 20);
}

static void
pika_label_color_constructed (GObject *object)
{
  PikaLabelColor        *color = PIKA_LABEL_COLOR (object);
  PikaLabelColorPrivate *priv  = pika_label_color_get_instance_private (color);

  G_OBJECT_CLASS (parent_class)->constructed (object);

  /* This is important to make this object into a property widget. It
   * will allow config object to bind the "value" property of this
   * widget, and therefore be updated automatically.
   */
  g_object_bind_property (G_OBJECT (priv->area), "color",
                          G_OBJECT (color),      "value",
                          G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
}

static void
pika_label_color_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  PikaLabelColor        *lcolor = PIKA_LABEL_COLOR (object);
  PikaLabelColorPrivate *priv  = pika_label_color_get_instance_private (lcolor);

  switch (property_id)
    {
    case PROP_VALUE:
        {
          PikaRGB *new_color;
          PikaRGB *color;

          new_color = g_value_get_boxed (value);

          g_object_get (priv->area,
                        "color", &color,
                        NULL);

          /* Avoid looping forever since we have bound this widget's
           * "value" property with the color button "value" property.
           */
          if (pika_rgba_distance (color, new_color) >= PIKA_RGBA_EPSILON)
            {
              g_object_set (priv->area, "color", new_color, NULL);
              g_signal_emit (object, pika_label_color_signals[VALUE_CHANGED], 0);
            }
          g_boxed_free (PIKA_TYPE_RGB, color);
        }
      break;
    case PROP_EDITABLE:
      if (priv->editable != g_value_get_boolean (value))
        {
          const gchar       *dialog_title;
          PikaLabeled       *labeled;
          PikaRGB           *color;
          PikaColorAreaType  type;
          gboolean           attached;

          labeled = PIKA_LABELED (lcolor);
          /* Reuse the label contents (without mnemonics) as dialog
           * title for the color selection. This is why the "editable"
           * property must not be a G_PARAM_CONSTRUCT.
           */
          dialog_title = gtk_label_get_text (GTK_LABEL (pika_labeled_get_label (labeled)));

          attached = (gtk_widget_get_parent (priv->area) != NULL);
          g_object_get (priv->area,
                        "type",  &type,
                        "color", &color,
                        NULL);

          gtk_widget_destroy (priv->area);

          priv->editable = g_value_get_boolean (value);
          if (priv->editable)
            priv->area = pika_color_button_new (dialog_title,
                                                20, 20, color, type);
          else
            priv->area = pika_color_area_new (color, type,
                                              GDK_BUTTON1_MASK | GDK_BUTTON2_MASK);

          g_boxed_free (PIKA_TYPE_RGB, color);

          gtk_widget_set_size_request (priv->area, 20, 20);
          g_object_bind_property (G_OBJECT (priv->area), "color",
                                  G_OBJECT (lcolor),     "value",
                                  G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);

          if (attached)
            {
              gtk_grid_attach (GTK_GRID (lcolor), priv->area, 1, 0, 1, 1);
              gtk_widget_show (priv->area);
              g_signal_emit_by_name (object, "mnemonic-widget-changed", priv->area);
            }
        }
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_label_color_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  PikaLabelColor        *color = PIKA_LABEL_COLOR (object);
  PikaLabelColorPrivate *priv  = pika_label_color_get_instance_private (color);

  switch (property_id)
    {
    case PROP_VALUE:
      g_object_get_property (G_OBJECT (priv->area), "color", value);
      break;
    case PROP_EDITABLE:
      g_value_set_boolean (value, priv->editable);
      break;


    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static GtkWidget *
pika_label_color_populate (PikaLabeled *labeled,
                           gint        *x,
                           gint        *y,
                           gint        *width,
                           gint        *height)
{
  PikaLabelColor        *color = PIKA_LABEL_COLOR (labeled);
  PikaLabelColorPrivate *priv = pika_label_color_get_instance_private (color);

  gtk_grid_attach (GTK_GRID (color), priv->area, 1, 0, 1, 1);
  /* Make sure the label and color won't be glued next to each other's. */
  gtk_grid_set_column_spacing (GTK_GRID (color),
                               4 * gtk_widget_get_scale_factor (GTK_WIDGET (color)));
  gtk_widget_show (priv->area);

  return priv->area;
}


/* Public Functions */


/**
 * pika_label_color_new:
 * @label:  The text for the #GtkLabel.
 * @color:  The color displayed.
 *
 * Creates a #PikaLabelColor which contains a widget and displays a
 * color area. By default, the color area is of type
 * %PIKA_COLOR_AREA_SMALL_CHECKS, which means transparency of @color
 * will be shown.
 *
 * Moreover in the non-editable case, the color is draggable to other
 * widgets accepting color drops with buttons 1 and 2.
 * In the editable case, the @label is reused as the color chooser's
 * dialog title.
 *
 * If you wish to customize any of these default behaviors, get the
 * #PikaColorArea or #PikaColorButton with pika_label_color_get_color_widget().
 *
 * Returns: (transfer full): The new #PikaLabelColor widget.
 **/
GtkWidget *
pika_label_color_new (const gchar   *label,
                      const PikaRGB *color,
                      gboolean       editable)
{
  GtkWidget *labeled;

  labeled = g_object_new (PIKA_TYPE_LABEL_COLOR,
                          "label",    label,
                          "value",    color,
                          "editable", editable,
                          NULL);

  return labeled;
}

/**
 * pika_label_color_set_value:
 * @color: The #GtkLabelColor.
 * @value: A new value.
 *
 * This function sets the value in the #GtkColor inside @color.
 **/
void
pika_label_color_set_value (PikaLabelColor *color,
                            const PikaRGB  *value)
{
  g_return_if_fail (PIKA_IS_LABEL_COLOR (color));

  g_object_set (color,
                "value", value,
                NULL);
}

/**
 * pika_label_color_get_value:
 * @color: The #GtkLabelColor.
 * @value: (out callee-allocates): The color to assign to the color area.
 *
 * This function returns the value shown by @color.
 **/
void
pika_label_color_get_value (PikaLabelColor *color,
                            PikaRGB        *value)
{
  PikaLabelColorPrivate *priv = pika_label_color_get_instance_private (color);

  g_return_if_fail (PIKA_IS_LABEL_COLOR (color));

  g_object_get (priv->area,
                "color", &value,
                NULL);
}

/**
 * pika_label_color_set_editable:
 * @color:    The #GtkLabelColor.
 * @editable: Whether the color should be editable.
 *
 * Changes the editability of the color.
 **/
void
pika_label_color_set_editable (PikaLabelColor *color,
                               gboolean        editable)
{
  g_return_if_fail (PIKA_IS_LABEL_COLOR (color));

  g_object_set (color, "editable", editable, NULL);
}

/**
 * pika_label_color_is_editable:
 * @color: The #GtkLabelColor.
 *
 * This function tells whether the color widget allows to edit the
 * color.
 * Returns: %TRUE if the color is editable.
 **/
gboolean
pika_label_color_is_editable (PikaLabelColor *color)
{
  PikaLabelColorPrivate *priv = pika_label_color_get_instance_private (color);

  g_return_val_if_fail (PIKA_IS_LABEL_COLOR (color), FALSE);

  priv = pika_label_color_get_instance_private (color);

  return PIKA_IS_COLOR_SELECT (priv->area);
}

/**
 * pika_label_color_get_color_widget:
 * @color: The #PikaLabelColor
 *
 * This function returns the color widget packed in @color, which can be
 * either a #PikaColorButton (if the @color is editable) or a
 * #PikaColorArea otherwise.
 *
 * Returns: (transfer none): The color widget packed in @color.
 **/
GtkWidget *
pika_label_color_get_color_widget (PikaLabelColor *color)
{
  PikaLabelColorPrivate *priv = pika_label_color_get_instance_private (color);

  g_return_val_if_fail (PIKA_IS_LABEL_COLOR (color), NULL);

  return priv->area;
}
