/* LIBPIKA - The PIKA Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * pikacolorselector.c
 * Copyright (C) 2002 Michael Natterer <mitch@gimp.org>
 *
 * based on:
 * Colour selector module
 * Copyright (C) 1999 Austin Donnelly <austin@greenend.org.uk>
 *
 * 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
 * Lesser 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 "libpikaconfig/pikaconfig.h"

#include "pikawidgetstypes.h"

#include "pikacolorselector.h"
#include "pikaicons.h"
#include "pikawidgetsmarshal.h"


/**
 * SECTION: pikacolorselector
 * @title: PikaColorSelector
 * @short_description: Pluggable PIKA color selector modules.
 * @see_also: #GModule, #GTypeModule, #PikaModule
 *
 * Functions and definitions for creating pluggable PIKA color
 * selector modules.
 **/


enum
{
  COLOR_CHANGED,
  CHANNEL_CHANGED,
  MODEL_VISIBLE_CHANGED,
  LAST_SIGNAL
};


struct _PikaColorSelectorPrivate
{
  gboolean model_visible[3];
};

#define GET_PRIVATE(obj) (((PikaColorSelector *) (obj))->priv)


static void   pika_color_selector_dispose (GObject *object);


G_DEFINE_TYPE_WITH_PRIVATE (PikaColorSelector, pika_color_selector,
                            GTK_TYPE_BOX)

#define parent_class pika_color_selector_parent_class

static guint selector_signals[LAST_SIGNAL] = { 0 };


static void
pika_color_selector_class_init (PikaColorSelectorClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = pika_color_selector_dispose;

  selector_signals[COLOR_CHANGED] =
    g_signal_new ("color-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaColorSelectorClass, color_changed),
                  NULL, NULL,
                  _pika_widgets_marshal_VOID__BOXED_BOXED,
                  G_TYPE_NONE, 2,
                  PIKA_TYPE_RGB,
                  PIKA_TYPE_RGB);

  selector_signals[CHANNEL_CHANGED] =
    g_signal_new ("channel-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaColorSelectorClass, channel_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  PIKA_TYPE_COLOR_SELECTOR_CHANNEL);

  selector_signals[MODEL_VISIBLE_CHANGED] =
    g_signal_new ("model-visible-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaColorSelectorClass, model_visible_changed),
                  NULL, NULL,
                  _pika_widgets_marshal_VOID__ENUM_BOOLEAN,
                  G_TYPE_NONE, 2,
                  PIKA_TYPE_COLOR_SELECTOR_MODEL,
                  G_TYPE_BOOLEAN);

  klass->name                  = "Unnamed";
  klass->help_id               = NULL;
  klass->icon_name             = PIKA_ICON_PALETTE;

  klass->set_toggles_visible   = NULL;
  klass->set_toggles_sensitive = NULL;
  klass->set_show_alpha        = NULL;
  klass->set_color             = NULL;
  klass->set_channel           = NULL;
  klass->set_model_visible     = NULL;
  klass->color_changed         = NULL;
  klass->channel_changed       = NULL;
  klass->model_visible_changed = NULL;
  klass->set_config            = NULL;
}

static void
pika_color_selector_init (PikaColorSelector *selector)
{
  PikaColorSelectorPrivate *priv;

  selector->priv = pika_color_selector_get_instance_private (selector);

  priv = GET_PRIVATE (selector);

  selector->toggles_visible   = TRUE;
  selector->toggles_sensitive = TRUE;
  selector->show_alpha        = TRUE;

  gtk_orientable_set_orientation (GTK_ORIENTABLE (selector),
                                  GTK_ORIENTATION_VERTICAL);

  pika_rgba_set (&selector->rgb, 0.0, 0.0, 0.0, 1.0);
  pika_rgb_to_hsv (&selector->rgb, &selector->hsv);

  selector->channel = PIKA_COLOR_SELECTOR_RED;

  priv->model_visible[PIKA_COLOR_SELECTOR_MODEL_RGB] = TRUE;
  priv->model_visible[PIKA_COLOR_SELECTOR_MODEL_LCH] = TRUE;
  priv->model_visible[PIKA_COLOR_SELECTOR_MODEL_HSV] = FALSE;
}

static void
pika_color_selector_dispose (GObject *object)
{
  pika_color_selector_set_config (PIKA_COLOR_SELECTOR (object), NULL);

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


/*  public functions  */

/**
 * pika_color_selector_new:
 * @selector_type: The #GType of the selector to create.
 * @rgb:           The initial color to be edited.
 * @hsv:           The same color in HSV.
 * @channel:       The selector's initial channel.
 *
 * Creates a new #PikaColorSelector widget of type @selector_type.
 *
 * Note that this is mostly internal API to be used by other widgets.
 *
 * Please use pika_color_selection_new() for the "PIKA-typical" color
 * selection widget. Also see pika_color_button_new().
 *
 * Retunn value: the new #PikaColorSelector widget.
 **/
GtkWidget *
pika_color_selector_new (GType                     selector_type,
                         const PikaRGB            *rgb,
                         const PikaHSV            *hsv,
                         PikaColorSelectorChannel  channel)
{
  PikaColorSelector *selector;

  g_return_val_if_fail (g_type_is_a (selector_type, PIKA_TYPE_COLOR_SELECTOR),
                        NULL);
  g_return_val_if_fail (rgb != NULL, NULL);
  g_return_val_if_fail (hsv != NULL, NULL);

  selector = g_object_new (selector_type, NULL);

  pika_color_selector_set_color (selector, rgb, hsv);
  pika_color_selector_set_channel (selector, channel);

  return GTK_WIDGET (selector);
}

/**
 * pika_color_selector_set_toggles_visible:
 * @selector: A #PikaColorSelector widget.
 * @visible:  The new @visible setting.
 *
 * Sets the @visible property of the @selector's toggles.
 *
 * This function has no effect if this @selector instance has no
 * toggles to switch channels.
 **/
void
pika_color_selector_set_toggles_visible (PikaColorSelector *selector,
                                         gboolean           visible)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  if (selector->toggles_visible != visible)
    {
      PikaColorSelectorClass *selector_class;

      selector->toggles_visible = visible ? TRUE : FALSE;

      selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

      if (selector_class->set_toggles_visible)
        selector_class->set_toggles_visible (selector, visible);
    }
}

/**
 * pika_color_selector_get_toggles_visible:
 * @selector: A #PikaColorSelector widget.
 *
 * Returns the @visible property of the @selector's toggles.
 *
 * Returns: %TRUE if the #PikaColorSelector's toggles are visible.
 *
 * Since: 2.10
 **/
gboolean
pika_color_selector_get_toggles_visible (PikaColorSelector *selector)
{
  g_return_val_if_fail (PIKA_IS_COLOR_SELECTOR (selector), FALSE);

  return selector->toggles_visible;
}

/**
 * pika_color_selector_set_toggles_sensitive:
 * @selector:  A #PikaColorSelector widget.
 * @sensitive: The new @sensitive setting.
 *
 * Sets the @sensitive property of the @selector's toggles.
 *
 * This function has no effect if this @selector instance has no
 * toggles to switch channels.
 **/
void
pika_color_selector_set_toggles_sensitive (PikaColorSelector *selector,
                                           gboolean           sensitive)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  if (selector->toggles_sensitive != sensitive)
    {
      PikaColorSelectorClass *selector_class;

      selector->toggles_sensitive = sensitive ? TRUE : FALSE;

      selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

      if (selector_class->set_toggles_sensitive)
        selector_class->set_toggles_sensitive (selector, sensitive);
    }
}

/**
 * pika_color_selector_get_toggles_sensitive:
 * @selector: A #PikaColorSelector widget.
 *
 * Returns the @sensitive property of the @selector's toggles.
 *
 * Returns: %TRUE if the #PikaColorSelector's toggles are sensitive.
 *
 * Since: 2.10
 **/
gboolean
pika_color_selector_get_toggles_sensitive (PikaColorSelector *selector)
{
  g_return_val_if_fail (PIKA_IS_COLOR_SELECTOR (selector), FALSE);

  return selector->toggles_sensitive;
}

/**
 * pika_color_selector_set_show_alpha:
 * @selector:   A #PikaColorSelector widget.
 * @show_alpha: The new @show_alpha setting.
 *
 * Sets the @show_alpha property of the @selector widget.
 **/
void
pika_color_selector_set_show_alpha (PikaColorSelector *selector,
                                    gboolean           show_alpha)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  if (show_alpha != selector->show_alpha)
    {
      PikaColorSelectorClass *selector_class;

      selector->show_alpha = show_alpha ? TRUE : FALSE;

      selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

      if (selector_class->set_show_alpha)
        selector_class->set_show_alpha (selector, show_alpha);
    }
}

/**
 * pika_color_selector_get_show_alpha:
 * @selector: A #PikaColorSelector widget.
 *
 * Returns the @selector's @show_alpha property.
 *
 * Returns: %TRUE if the #PikaColorSelector has alpha controls.
 *
 * Since: 2.10
 **/
gboolean
pika_color_selector_get_show_alpha (PikaColorSelector *selector)
{
  g_return_val_if_fail (PIKA_IS_COLOR_SELECTOR (selector), FALSE);

  return selector->show_alpha;
}

/**
 * pika_color_selector_set_color:
 * @selector: A #PikaColorSelector widget.
 * @rgb:      The new color.
 * @hsv:      The same color in HSV.
 *
 * Sets the color shown in the @selector widget.
 **/
void
pika_color_selector_set_color (PikaColorSelector *selector,
                               const PikaRGB     *rgb,
                               const PikaHSV     *hsv)
{
  PikaColorSelectorClass *selector_class;

  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));
  g_return_if_fail (rgb != NULL);
  g_return_if_fail (hsv != NULL);

  selector->rgb = *rgb;
  selector->hsv = *hsv;

  selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

  if (selector_class->set_color)
    selector_class->set_color (selector, rgb, hsv);

  pika_color_selector_emit_color_changed (selector);
}

/**
 * pika_color_selector_get_color:
 * @selector: A #PikaColorSelector widget.
 * @rgb:      (out caller-allocates): Return location for the color.
 * @hsv:      (out caller-allocates): Return location for the same same
 *            color in HSV.
 *
 * Retrieves the color shown in the @selector widget.
 *
 * Since: 2.10
 **/
void
pika_color_selector_get_color (PikaColorSelector *selector,
                               PikaRGB           *rgb,
                               PikaHSV           *hsv)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));
  g_return_if_fail (rgb != NULL);
  g_return_if_fail (hsv != NULL);

  *rgb = selector->rgb;
  *hsv = selector->hsv;
}

/**
 * pika_color_selector_set_channel:
 * @selector: A #PikaColorSelector widget.
 * @channel:  The new @channel setting.
 *
 * Sets the @channel property of the @selector widget.
 *
 * Changes between displayed channels if this @selector instance has
 * the ability to show different channels.
 * This will also update the color model if needed.
 **/
void
pika_color_selector_set_channel (PikaColorSelector        *selector,
                                 PikaColorSelectorChannel  channel)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  if (channel != selector->channel)
    {
      PikaColorSelectorClass *selector_class;
      PikaColorSelectorModel  model = -1;

      selector->channel = channel;

      switch (channel)
        {
        case PIKA_COLOR_SELECTOR_RED:
        case PIKA_COLOR_SELECTOR_GREEN:
        case PIKA_COLOR_SELECTOR_BLUE:
          model = PIKA_COLOR_SELECTOR_MODEL_RGB;
          break;

        case PIKA_COLOR_SELECTOR_HUE:
        case PIKA_COLOR_SELECTOR_SATURATION:
        case PIKA_COLOR_SELECTOR_VALUE:
          model = PIKA_COLOR_SELECTOR_MODEL_HSV;
          break;

        case PIKA_COLOR_SELECTOR_LCH_LIGHTNESS:
        case PIKA_COLOR_SELECTOR_LCH_CHROMA:
        case PIKA_COLOR_SELECTOR_LCH_HUE:
          model = PIKA_COLOR_SELECTOR_MODEL_LCH;
          break;

        case PIKA_COLOR_SELECTOR_ALPHA:
          /* Alpha channel does not change the color model. */
          break;

        default:
          /* Should not happen. */
          g_return_if_reached ();
          break;
        }

      selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

      if (selector_class->set_channel)
        selector_class->set_channel (selector, channel);

      pika_color_selector_emit_channel_changed (selector);

      if (model != -1)
        {
          /*  make visibility of LCH and HSV mutuallky exclusive  */
          if (model == PIKA_COLOR_SELECTOR_MODEL_HSV)
            {
              pika_color_selector_set_model_visible (selector,
                                                     PIKA_COLOR_SELECTOR_MODEL_LCH,
                                                     FALSE);
            }
          else if (model == PIKA_COLOR_SELECTOR_MODEL_LCH)
            {
              pika_color_selector_set_model_visible (selector,
                                                     PIKA_COLOR_SELECTOR_MODEL_HSV,
                                                     FALSE);
            }

          pika_color_selector_set_model_visible (selector, model, TRUE);
        }
    }
}

/**
 * pika_color_selector_get_channel:
 * @selector: A #PikaColorSelector widget.
 *
 * Returns the @selector's current channel.
 *
 * Returns: The #PikaColorSelectorChannel currently shown by the
 * @selector.
 *
 * Since: 2.10
 **/
PikaColorSelectorChannel
pika_color_selector_get_channel (PikaColorSelector *selector)
{
  g_return_val_if_fail (PIKA_IS_COLOR_SELECTOR (selector),
                        PIKA_COLOR_SELECTOR_RED);

  return selector->channel;
}

/**
 * pika_color_selector_set_model_visible:
 * @selector: A #PikaColorSelector widget.
 * @model:    The affected #PikaColorSelectorModel.
 * @visible:  The new visible setting.
 *
 * Sets the @model visible/invisible on the @selector widget.
 *
 * Toggles visibility of displayed models if this @selector instance
 * has the ability to show different color models.
 *
 * Since: 2.10
 **/
void
pika_color_selector_set_model_visible (PikaColorSelector      *selector,
                                       PikaColorSelectorModel  model,
                                       gboolean                visible)
{
  PikaColorSelectorPrivate *priv;

  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  priv = GET_PRIVATE (selector);

  visible = visible ? TRUE : FALSE;

  if (visible != priv->model_visible[model])
    {
      PikaColorSelectorClass *selector_class;

      priv->model_visible[model] = visible;

      selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

      if (selector_class->set_model_visible)
        selector_class->set_model_visible (selector, model, visible);

      pika_color_selector_emit_model_visible_changed (selector, model);
    }
}

/**
 * pika_color_selector_get_model_visible:
 * @selector: A #PikaColorSelector widget.
 * @model:    The #PikaColorSelectorModel.
 *
 * Returns: whether @model is visible in @selector.
 *
 * Since: 2.10
 **/
gboolean
pika_color_selector_get_model_visible (PikaColorSelector      *selector,
                                       PikaColorSelectorModel  model)
{
  PikaColorSelectorPrivate *priv;

  g_return_val_if_fail (PIKA_IS_COLOR_SELECTOR (selector), FALSE);

  priv = GET_PRIVATE (selector);

  return priv->model_visible[model];
}

/**
 * pika_color_selector_emit_color_changed:
 * @selector: A #PikaColorSelector widget.
 *
 * Emits the "color-changed" signal.
 */
void
pika_color_selector_emit_color_changed (PikaColorSelector *selector)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  g_signal_emit (selector, selector_signals[COLOR_CHANGED], 0,
                 &selector->rgb, &selector->hsv);
}

/**
 * pika_color_selector_emit_channel_changed:
 * @selector: A #PikaColorSelector widget.
 *
 * Emits the "channel-changed" signal.
 */
void
pika_color_selector_emit_channel_changed (PikaColorSelector *selector)
{
  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  g_signal_emit (selector, selector_signals[CHANNEL_CHANGED], 0,
                 selector->channel);
}

/**
 * pika_color_selector_emit_model_visible_changed:
 * @selector: A #PikaColorSelector widget.
 * @model:    The #PikaColorSelectorModel where visibility changed.
 *
 * Emits the "model-visible-changed" signal.
 *
 * Since: 2.10
 */
void
pika_color_selector_emit_model_visible_changed (PikaColorSelector      *selector,
                                                PikaColorSelectorModel  model)
{
  PikaColorSelectorPrivate *priv;

  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));

  priv = GET_PRIVATE (selector);

  g_signal_emit (selector, selector_signals[MODEL_VISIBLE_CHANGED], 0,
                 model, priv->model_visible[model]);
}

/**
 * pika_color_selector_set_config:
 * @selector: a #PikaColorSelector widget.
 * @config:   a #PikaColorConfig object.
 *
 * Sets the color management configuration to use with this color selector.
 *
 * Since: 2.4
 */
void
pika_color_selector_set_config (PikaColorSelector *selector,
                                PikaColorConfig   *config)
{
  PikaColorSelectorClass *selector_class;

  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));
  g_return_if_fail (config == NULL || PIKA_IS_COLOR_CONFIG (config));

  selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

  if (selector_class->set_config)
    selector_class->set_config (selector, config);
}

/**
 * pika_color_selector_set_simulation
 * @selector: a #PikaColorSelector widget.
 * @profile:  a #PikaColorProfile object.
 * @intent:   a #PikaColorRenderingIntent enum.
 * @bpc:      a gboolean.
 *
 * Sets the simulation options to use with this color selector.
 *
 * Since: 3.0
 */
void
pika_color_selector_set_simulation (PikaColorSelector *selector,
                                    PikaColorProfile  *profile,
                                    PikaColorRenderingIntent intent,
                                    gboolean           bpc)
{
  PikaColorSelectorClass *selector_class;

  g_return_if_fail (PIKA_IS_COLOR_SELECTOR (selector));
  g_return_if_fail (profile == NULL || PIKA_IS_COLOR_PROFILE (profile));

  selector_class = PIKA_COLOR_SELECTOR_GET_CLASS (selector);

  if (selector_class->set_simulation)
    selector_class->set_simulation (selector, profile, intent, bpc);
}
