/*
 *  $Id: color-editor.c 29990 2026-05-25 12:47:33Z yeti-dn $
 *  Copyright (C) 2025 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.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 2 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, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <math.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>

#include "libgwyddion/macros.h"

#include "libgwyui/utils.h"
#include "libgwyui/color-swatch.h"
#include "libgwyui/gradient-swatch.h"
#include "libgwyui/color-wheel.h"
#include "libgwyui/color-editor.h"

G_STATIC_ASSERT(sizeof(GwyRGBA) == sizeof(GdkRGBA));

enum {
    SGNL_COLOR_CHANGED,
    NUM_SIGNALS
};

enum {
    PROP_0,
    PROP_COLOR,
    PROP_PREVIOUS_COLOR,
    PROP_USE_ALPHA,
    NUM_PROPERTIES
};

struct _GwyColorEditorPrivate {
    gboolean use_alpha;
    gboolean previous_color_set;
    gboolean in_update;
    gboolean moved_wheel;
    gboolean moved_hsv;

    GwyRGBA color;
    GwyRGBA previous_color;

    GtkWidget *sliders;
    GtkWidget *wheel;
    GtkWidget *current_swatch;
    GtkWidget *previous_swatch;
    GtkWidget *rgb_label;
    GtkWidget *red_slider;
    GtkWidget *green_slider;
    GtkWidget *blue_slider;
    GtkWidget *alpha_slider;
    GtkWidget *hue_slider;
    GtkWidget *saturation_slider;
    GtkWidget *value_slider;
    GtkWidget *hex;
};

static void set_property         (GObject *object,
                                  guint prop_id,
                                  const GValue *value,
                                  GParamSpec *pspec);
static void get_property         (GObject *object,
                                  guint prop_id,
                                  GValue *value,
                                  GParamSpec *pspec);
static void wheel_color_changed  (GwyColorEditor *editor,
                                  GwyColorWheel *wheel);
static void slider_moved         (GwyColorEditor *editor,
                                  GtkAdjustment *adj);
static void hex_entered          (GwyColorEditor *editor,
                                  GtkEntry *entry);
static void update_color         (GwyColorEditor *editor);
static void format_hex_color     (GwyColorEditor *editor);
static void update_adjustment    (GtkWidget *slider,
                                  gdouble x);
static void update_alpha_gradient(GwyColorEditor *editor);
static void update_hue_gradient  (GwyColorEditor *editor);

static guint signals[NUM_SIGNALS];
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GtkBoxClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyColorEditor, gwy_color_editor, GTK_TYPE_BOX,
                        G_ADD_PRIVATE(GwyColorEditor))

static void
gwy_color_editor_class_init(GwyColorEditorClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GType type = G_TYPE_FROM_CLASS(klass);

    parent_class = gwy_color_editor_parent_class;

    gobject_class->set_property = set_property;
    gobject_class->get_property = get_property;

    /**
     * GwyColorEditor::color-changed:
     * @gwyeditor: The #GwyColorEditor which received the signal.
     *
     * The ::color-changed signal is emitted whenever the color is edited.
     **/
    signals[SGNL_COLOR_CHANGED] = g_signal_new("color-changed", type,
                                               G_SIGNAL_RUN_FIRST,
                                               G_STRUCT_OFFSET(GwyColorEditorClass, color_changed),
                                               NULL, NULL,
                                               g_cclosure_marshal_VOID__VOID,
                                               G_TYPE_NONE, 0);
    g_signal_set_va_marshaller(signals[SGNL_COLOR_CHANGED], type, g_cclosure_marshal_VOID__VOIDv);

    properties[PROP_COLOR] = g_param_spec_boxed("color", NULL,
                                                "Current color",
                                                GWY_TYPE_RGBA, GWY_GPARAM_RWE);
    properties[PROP_PREVIOUS_COLOR] = g_param_spec_boxed("previous-color", NULL,
                                                         "Previous color shown for comparison",
                                                         GWY_TYPE_RGBA, GWY_GPARAM_RWE);
    properties[PROP_USE_ALPHA] = g_param_spec_boolean("use-alpha", NULL,
                                                      "Whether to edit the color including the alpha value",
                                                      FALSE, GWY_GPARAM_RWE);

    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static GtkWidget*
make_label(const gchar *text)
{
    GtkWidget *label = gtk_label_new(text);
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_widget_set_hexpand(label, FALSE);
    return label;
}

static GtkWidget*
attach_slider(GtkGrid *grid, gint row,
              const gchar *name, const gchar *desc, gdouble upper,
              GwyColorEditor *editor)
{
    GtkWidget *label = make_label(desc);
    gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
    gtk_grid_attach(grid, label, 0, row, 1, 1);
    gtk_widget_show(label);

    GtkWidget *widget = gwy_gradient_swatch_new();
    gtk_widget_set_hexpand(widget, TRUE);
    GwyGradientSwatch *swatch = GWY_GRADIENT_SWATCH(widget);
    GtkAdjustment *adj = gtk_adjustment_new(0.0, 0.0, upper, 0.1, 1.0, 0.0);
    g_object_set_data(G_OBJECT(adj), "id", (gpointer)name);
    gwy_gradient_swatch_set_adjustment(swatch, adj);
    gwy_gradient_swatch_set_has_marker(swatch, TRUE);
    gwy_gradient_swatch_set_editable(swatch, TRUE);
    GwyGradient *gradient = g_object_new(GWY_TYPE_GRADIENT, "name", name, NULL);
    gwy_gradient_swatch_set_gradient(swatch, gradient);
    g_object_unref(gradient);
    gtk_grid_attach(grid, widget, 1, row, 1, 1);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
    gtk_widget_show(widget);

    GtkWidget *spin = gtk_spin_button_new(adj, 0.0, 1);
    gtk_grid_attach(grid, spin, 2, row, 1, 1);
    gtk_widget_show(spin);

    g_signal_connect_swapped(adj, "value-changed", G_CALLBACK(slider_moved), editor);

    return widget;
}

static void
gwy_color_editor_init(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv;
    GtkGrid *grid;

    editor->priv = priv = gwy_color_editor_get_instance_private(editor);
    priv->color = (GwyRGBA){ 0.0, 0.0, 0.0, 1.0 };
    priv->previous_color = (GwyRGBA){ 0.0, 0.0, 0.0, 1.0 };

    GtkBox *box = GTK_BOX(editor);
    gtk_orientable_set_orientation(GTK_ORIENTABLE(editor), GTK_ORIENTATION_HORIZONTAL);
    gtk_box_set_spacing(box, 12);

    priv->wheel = gwy_color_wheel_new();
    gtk_box_pack_start(box, priv->wheel, TRUE, TRUE, 0);
    g_signal_connect_swapped(priv->wheel, "color-changed", G_CALLBACK(wheel_color_changed), editor);

    GtkWidget *rightbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
    gtk_box_pack_start(box, rightbox, TRUE, TRUE, 0);

    priv->sliders = gtk_grid_new();
    grid = GTK_GRID(priv->sliders);
    gtk_grid_set_column_spacing(grid, 6);
    gtk_grid_set_row_spacing(grid, 2);
    gtk_box_pack_start(GTK_BOX(rightbox), priv->sliders, FALSE, TRUE, 0);
    gtk_widget_set_vexpand(priv->sliders, FALSE);

    priv->rgb_label = gwy_label_new_header(_("Red, Green &amp; Blue"));
    gtk_grid_attach(grid, priv->rgb_label, 0, 0, 2, 1);
    priv->red_slider = attach_slider(grid, 1, "red", _("R:"), 100.0, editor);
    priv->green_slider = attach_slider(grid, 2, "green", _("G:"), 100.0, editor);
    priv->blue_slider = attach_slider(grid, 3, "blue", _("B:"), 100.0, editor);
    GtkWidget *header = gwy_label_new_header(_("Hue, Saturation &amp; Value"));
    gtk_widget_set_margin_top(header, 8);
    gtk_grid_attach(grid, header, 0, 4, 2, 1);
    priv->hue_slider = attach_slider(grid, 5, "hue", _("H:"), 360.0, editor);
    priv->saturation_slider = attach_slider(grid, 6, "saturation", _("S:"), 100.0, editor);
    priv->value_slider = attach_slider(grid, 7, "value", _("V:"), 100.0, editor);
    update_hue_gradient(editor);

    header = gwy_label_new_header(_("Color"));
    gtk_widget_set_margin_top(header, 8);
    gtk_grid_attach(grid, header, 0, 8, 2, 1);

    GtkWidget *label = make_label(_("Hex:"));
    gtk_grid_attach(grid, label, 0, 9, 1, 1);

    priv->hex = gtk_entry_new();
    gtk_widget_set_hexpand(priv->hex, FALSE);
    gtk_entry_set_width_chars(GTK_ENTRY(priv->hex), 7);
    gwy_widget_set_activate_on_unfocus(priv->hex, TRUE);
    gtk_grid_attach(grid, priv->hex, 1, 9, 1, 1);
    g_signal_connect_swapped(priv->hex, "activate", G_CALLBACK(hex_entered), editor);

    grid = GTK_GRID(gtk_grid_new());
    /* Do not add any row spacing. We want the colour swatches to touch. */
    gtk_grid_set_column_spacing(grid, 6);
    gtk_box_pack_start(GTK_BOX(rightbox), GTK_WIDGET(grid), FALSE, TRUE, 0);

    gtk_grid_attach(grid, make_label(_("Current:")), 0, 0, 1, 1);
    gtk_grid_attach(grid, make_label(_("Previous:")), 0, 1, 1, 1);
    priv->current_swatch = gwy_color_swatch_new();
    gtk_widget_set_hexpand(priv->current_swatch, TRUE);
    gtk_grid_attach(grid, priv->current_swatch, 1, 0, 1, 1);
    priv->previous_swatch = gwy_color_swatch_new();
    gtk_widget_set_hexpand(priv->previous_swatch, TRUE);
    gtk_grid_attach(grid, priv->previous_swatch, 1, 1, 1, 1);

    gtk_widget_show_all(priv->wheel);
    gtk_widget_show_all(rightbox);

    update_color(editor);
}

static void
set_property(GObject *object,
             guint prop_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwyColorEditor *editor = GWY_COLOR_EDITOR(object);

    switch (prop_id) {
        case PROP_USE_ALPHA:
        gwy_color_editor_set_use_alpha(editor, g_value_get_boolean(value));
        break;

        case PROP_COLOR:
        gwy_color_editor_set_color(editor, g_value_get_boxed(value));
        break;

        case PROP_PREVIOUS_COLOR:
        gwy_color_editor_set_previous_color(editor, g_value_get_boxed(value));
        break;

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

static void
get_property(GObject *object,
             guint prop_id,
             GValue *value,
             GParamSpec *pspec)
{
    GwyColorEditorPrivate *priv = GWY_COLOR_EDITOR(object)->priv;

    switch (prop_id) {
        case PROP_USE_ALPHA:
        g_value_set_boolean(value, priv->use_alpha);
        break;

        case PROP_COLOR:
        g_value_set_boxed(value, &priv->color);
        break;

        case PROP_PREVIOUS_COLOR:
        g_value_set_boxed(value, &priv->previous_color);
        break;

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

/**
 * gwy_color_editor_new:
 *
 * Creates a new scientific text entry.
 *
 * Returns: A newly created scientific text entry.
 **/
GtkWidget*
gwy_color_editor_new(void)
{
    return gtk_widget_new(GWY_TYPE_COLOR_EDITOR, NULL);
}

/**
 * gwy_color_editor_set_color:
 * @editor: A colour editor.
 * @color: The colour to edit.
 *
 * Sets the current colour of a colour editor.
 *
 * The first time this function is called it also sets the previous colour (if it has not been set yet).
 **/
void
gwy_color_editor_set_color(GwyColorEditor *editor,
                           const GwyRGBA *color)
{
    g_return_if_fail(GWY_IS_COLOR_EDITOR(editor));
    g_return_if_fail(color);
    GwyColorEditorPrivate *priv = editor->priv;
    if (!priv->previous_color_set)
        gwy_color_editor_set_previous_color(editor, color);
    if (gwy_rgba_equal(color, &priv->color))
        return;
    priv->color = *color;
    update_color(editor);
}

/**
 * gwy_color_editor_get_color:
 * @editor: A colour editor.
 * @color: Location to fill in with the current colour.
 *
 * Obtains the current colour of a colour editor.
 **/
void
gwy_color_editor_get_color(GwyColorEditor *editor,
                           GwyRGBA *color)
{
    g_return_if_fail(GWY_IS_COLOR_EDITOR(editor));
    g_return_if_fail(color);
    *color = editor->priv->color;
}

/**
 * gwy_color_editor_set_previous_color:
 * @editor: A colour editor.
 * @color: The colour to display as the previous colour.
 *
 * Sets the previous colour of a colour editor.
 **/
void
gwy_color_editor_set_previous_color(GwyColorEditor *editor,
                                    const GwyRGBA *color)
{
    g_return_if_fail(GWY_IS_COLOR_EDITOR(editor));
    g_return_if_fail(color);
    GwyColorEditorPrivate *priv = editor->priv;
    priv->previous_color_set = TRUE;
    if (gwy_rgba_equal(color, &priv->previous_color))
        return;
    priv->previous_color = *color;
    gwy_color_swatch_set_color(GWY_COLOR_SWATCH(priv->previous_swatch), color);
    g_object_notify_by_pspec(G_OBJECT(editor), properties[PROP_PREVIOUS_COLOR]);
}

/**
 * gwy_color_editor_get_previous_color:
 * @editor: A colour editor.
 * @color: Location to fill in with the previous colour.
 *
 * Obtains the previous colour of a colour editor.
 **/
void
gwy_color_editor_get_previous_color(GwyColorEditor *editor,
                                    GwyRGBA *color)
{
    g_return_if_fail(GWY_IS_COLOR_EDITOR(editor));
    g_return_if_fail(color);
    *color = editor->priv->previous_color;
}

/**
 * gwy_color_editor_set_use_alpha:
 * @editor: A colour editor.
 * @use_alpha: %TRUE if colour editor should include the alpha channel, %FALSE to ignore it.
 *
 * Sets whether the colour editor should include the alpha channel.
 *
 * If the editor includes the alpha channel, it has an alpha slider, the current and previous colour swatches use a
 * visualisation suitable for partially transparent colours, and the hex colour entry has an 8digit format (as opposed
 * to 6digit).
 *
 * If the editor does not include the alpha channel the user cannot modify it. So the alpha values of colours passed
 * to the editor with gwy_color_editor_set_color() are preserved.
 **/
void
gwy_color_editor_set_use_alpha(GwyColorEditor *editor,
                               gboolean use_alpha)
{
    g_return_if_fail(GWY_IS_COLOR_EDITOR(editor));

    GwyColorEditorPrivate *priv = editor->priv;
    if (!priv->use_alpha == !use_alpha)
        return;

    priv->use_alpha = !!use_alpha;
    gwy_color_swatch_set_use_alpha(GWY_COLOR_SWATCH(priv->current_swatch), priv->use_alpha);
    gwy_color_swatch_set_use_alpha(GWY_COLOR_SWATCH(priv->previous_swatch), priv->use_alpha);
    const gchar *rgb_text;
    GtkGrid *grid = GTK_GRID(priv->sliders);
    if (priv->use_alpha) {
        g_assert(!priv->alpha_slider);
        rgb_text = _("Red, Green, Blue &amp; Alpha");
        gtk_grid_insert_row(grid, 4);
        priv->alpha_slider = attach_slider(grid, 4, "alpha", _("A:"), 100.0, editor);
        gwy_gradient_swatch_set_use_alpha(GWY_GRADIENT_SWATCH(priv->alpha_slider), TRUE);
        update_adjustment(priv->alpha_slider, priv->color.a);
        update_alpha_gradient(editor);
    }
    else {
        g_assert(priv->alpha_slider);
        rgb_text = _("Red, Green &amp; Blue");
        gtk_grid_remove_row(grid, 4);
        priv->alpha_slider = NULL;
    }
    format_hex_color(editor);
    gchar *s = g_strconcat("<b>", rgb_text, "</b>", NULL);
    gtk_label_set_markup(GTK_LABEL(priv->rgb_label), s);
    g_free(s);

    g_object_notify_by_pspec(G_OBJECT(editor), properties[PROP_USE_ALPHA]);
}

/**
 * gwy_color_editor_get_use_alpha:
 * @editor: A colour editor.
 *
 * Reports whether the colour editor uses the alpha channel.
 *
 * Returns: %TRUE if the colour editor visualises alpha channel, %FALSE if it ignores it.
 **/
gboolean
gwy_color_editor_get_use_alpha(GwyColorEditor *editor)
{
    g_return_val_if_fail(GWY_IS_COLOR_EDITOR(editor), FALSE);
    return editor->priv->use_alpha;
}

static void
wheel_color_changed(GwyColorEditor *editor, GwyColorWheel *wheel)
{
    GwyColorEditorPrivate *priv = editor->priv;
    if (priv->in_update)
        return;

    GwyRGBA color;
    gwy_color_wheel_get_color(wheel, &color);

    /* Discard the alpha component as the wheel does not have any. It preserves the alpha we give to it, but this is
     * safer. */
    priv->color.r = color.r;
    priv->color.g = color.g;
    priv->color.b = color.b;
    priv->moved_wheel = TRUE;
    update_color(editor);
}

static void
slider_moved(GwyColorEditor *editor, GtkAdjustment *adj)
{
    GwyColorEditorPrivate *priv = editor->priv;
    if (priv->in_update)
        return;

    const gchar *name = g_object_get_data(G_OBJECT(adj), "id");
    gdouble x = gtk_adjustment_get_value(adj)/gtk_adjustment_get_upper(adj);
    GwyRGBA *color = &priv->color;

    if (gwy_strequal(name, "red"))
        color->r = x;
    else if (gwy_strequal(name, "green"))
        color->g = x;
    else if (gwy_strequal(name, "blue"))
        color->b = x;
    else if (gwy_strequal(name, "alpha"))
        color->a = x;
    else {
        gdouble h, s, v;
        gtk_rgb_to_hsv(color->r, color->g, color->b, &h, &s, &v);
        if (gwy_strequal(name, "hue"))
            gtk_hsv_to_rgb(x, s, v, &color->r, &color->g, &color->b);
        else if (gwy_strequal(name, "saturation"))
            gtk_hsv_to_rgb(h, x, v, &color->r, &color->g, &color->b);
        else if (gwy_strequal(name, "value"))
            gtk_hsv_to_rgb(h, s, x, &color->r, &color->g, &color->b);
        else
            g_return_if_reached();
        priv->moved_hsv = TRUE;
    }

    update_color(editor);
}

static void
hex_entered(GwyColorEditor *editor, GtkEntry *entry)
{
    GwyColorEditorPrivate *priv = editor->priv;
    const gchar *text = gtk_entry_get_text(entry);
    if (text[0] == '#')
        text++;
    guint pixel, len = strlen(text);
    GwyRGBA *color = &priv->color;
    if (sscanf(text, "%x", &pixel) == 1) {
        /* gdk_rgba_parse() requires writing the #, cannot parse hex forms with alpha and has a different order of
         * components anyway. Try reading back the same format as we print first. */
        if (len == 3) {
            color->a = 1.0;
            color->r = (pixel >> 8)/15.0;
            color->g = ((pixel >> 4) & 0xf)/15.0;
            color->b = (pixel & 0xf)/15.0;
        }
        else if (len == 4) {
            color->a = ((pixel >> 12) & 0xf)/15.0;
            color->r = ((pixel >> 8) & 0xf)/15.0;
            color->g = ((pixel >> 4) & 0xf)/15.0;
            color->b = (pixel & 0xf)/15.0;
        }
        else if (len == 6) {
            color->a = 1.0;
            color->r = (pixel >> 16)/255.0;
            color->g = ((pixel >> 8) & 0xff)/255.0;
            color->b = (pixel & 0xff)/255.0;
        }
        else if (len == 8) {
            color->a = (pixel >> 24)/255.0;
            color->r = ((pixel >> 16) & 0xff)/255.0;
            color->g = ((pixel >> 8) & 0xff)/255.0;
            color->b = (pixel & 0xff)/255.0;
        }
        else
            format_hex_color(editor);
    }
    else
        format_hex_color(editor);
}

static void
update_adjustment(GtkWidget *slider, gdouble x)
{
    GtkAdjustment *adj = gwy_gradient_swatch_get_adjustment(GWY_GRADIENT_SWATCH(slider));
    gtk_adjustment_set_value(adj, x*gtk_adjustment_get_upper(adj));
}

static void
update_red_gradient(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv = editor->priv;
    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->red_slider));
    GwyRGBA color = priv->color;
    color.r = 0.0;
    gwy_gradient_set_point_color(gradient, 0, &color);
    color.r = 1.0;
    gwy_gradient_set_point_color(gradient, 1, &color);
}

static void
update_green_gradient(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv = editor->priv;
    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->green_slider));
    GwyRGBA color = priv->color;
    color.g = 0.0;
    gwy_gradient_set_point_color(gradient, 0, &color);
    color.g = 1.0;
    gwy_gradient_set_point_color(gradient, 1, &color);
}

static void
update_blue_gradient(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv = editor->priv;
    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->blue_slider));
    GwyRGBA color = priv->color;
    color.b = 0.0;
    gwy_gradient_set_point_color(gradient, 0, &color);
    color.b = 1.0;
    gwy_gradient_set_point_color(gradient, 1, &color);
}

static void
update_alpha_gradient(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv = editor->priv;
    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->alpha_slider));
    GwyRGBA color = priv->color;
    color.a = 0.0;
    gwy_gradient_set_point_color(gradient, 0, &color);
    color.a = 1.0;
    gwy_gradient_set_point_color(gradient, 1, &color);
}

/* This one is actually fixed. We only need to call the function once to initialise the gradient. */
static void
update_hue_gradient(GwyColorEditor *editor)
{
    enum { nsamples = 6*5 + 1 };
    GwyGradientPoint samples[nsamples];

    for (guint i = 0; i < nsamples; i++) {
        samples[i].x = i/(nsamples - 1.0);
        gtk_hsv_to_rgb(samples[i].x, 1.0, 1.0, &samples[i].color.r, &samples[i].color.g, &samples[i].color.b);
        samples[i].color.a = 1.0;
    }
    GwyColorEditorPrivate *priv = editor->priv;

    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->hue_slider));
    gwy_gradient_set_points(gradient, nsamples, samples);
}

static void
update_saturation_gradient(GwyColorEditor *editor, gdouble h, gdouble v)
{
    GwyColorEditorPrivate *priv = editor->priv;
    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->saturation_slider));
    GwyRGBA color;
    color.a = 1.0;
    gtk_hsv_to_rgb(h, 0.0, v, &color.r, &color.g, &color.b);
    gwy_gradient_set_point_color(gradient, 0, &color);
    gtk_hsv_to_rgb(h, 1.0, v, &color.r, &color.g, &color.b);
    gwy_gradient_set_point_color(gradient, 1, &color);
}

static void
update_value_gradient(GwyColorEditor *editor, gdouble h, gdouble s)
{
    GwyColorEditorPrivate *priv = editor->priv;
    GwyGradient *gradient = gwy_gradient_swatch_get_gradient(GWY_GRADIENT_SWATCH(priv->value_slider));
    GwyRGBA color;
    color.a = 1.0;
    gtk_hsv_to_rgb(h, s, 0.0, &color.r, &color.g, &color.b);
    gwy_gradient_set_point_color(gradient, 0, &color);
    gtk_hsv_to_rgb(h, s, 1.0, &color.r, &color.g, &color.b);
    gwy_gradient_set_point_color(gradient, 1, &color);
}

static void
update_color(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv = editor->priv;
    priv->in_update = TRUE;

    GwyColorWheel *wheel = GWY_COLOR_WHEEL(priv->wheel);
    gdouble wheel_hue = gwy_color_wheel_get_hue(wheel);
    gwy_color_wheel_set_color(wheel, &priv->color);
    if (priv->moved_hsv) {
        GtkAdjustment *adj = gwy_gradient_swatch_get_adjustment(GWY_GRADIENT_SWATCH(priv->hue_slider));
        wheel_hue = gtk_adjustment_get_value(adj)/gtk_adjustment_get_upper(adj);
        gwy_color_wheel_set_hue(wheel, wheel_hue);
    }

    GwyRGBA *color = &priv->color;
    update_adjustment(priv->red_slider, color->r);
    update_red_gradient(editor);
    update_adjustment(priv->green_slider, color->g);
    update_green_gradient(editor);
    update_adjustment(priv->blue_slider, color->b);
    update_blue_gradient(editor);
    if (priv->use_alpha) {
        update_adjustment(priv->alpha_slider, color->a);
        update_alpha_gradient(editor);
    }

    gdouble h, s, v;
    gtk_rgb_to_hsv(color->r, color->g, color->b, &h, &s, &v);
    if (priv->moved_wheel || priv->moved_hsv)
        h = wheel_hue;

    update_adjustment(priv->hue_slider, h);
    update_adjustment(priv->saturation_slider, s);
    update_saturation_gradient(editor, h, v);
    update_adjustment(priv->value_slider, v);
    update_value_gradient(editor, h, s);

    format_hex_color(editor);
    gwy_color_swatch_set_color(GWY_COLOR_SWATCH(priv->current_swatch), &priv->color);
    if (!priv->previous_color_set)
        gwy_color_swatch_set_color(GWY_COLOR_SWATCH(priv->previous_swatch), &priv->color);
    priv->in_update = FALSE;
    priv->moved_wheel = priv->moved_hsv = FALSE;
    g_object_notify_by_pspec(G_OBJECT(editor), properties[PROP_COLOR]);
    g_signal_emit(editor, signals[SGNL_COLOR_CHANGED], 0);
}

static void
format_hex_color(GwyColorEditor *editor)
{
    GwyColorEditorPrivate *priv = editor->priv;
    gchar hexcode[9];
    if (priv->use_alpha) {
        gwy_rgba_to_hex8(&priv->color, hexcode);
        hexcode[8] = '\0';
    }
    else {
        gwy_rgba_to_hex6(&priv->color, hexcode);
        hexcode[6] = '\0';
    }
    gtk_entry_set_text(GTK_ENTRY(priv->hex), hexcode);
}

/**
 * SECTION: color-editor
 * @title: GwyColorEditor
 * @short_description: Colour editor with HSV wheel and component sliders
 *
 * #GwyColorEditor is a standard colour editor with a hue/saturation/value wheel and colour component sliders. It
 * shows the previous (or original) colour set gwy_color_editor_set_previous_color() (or simply the first
 * gwy_color_editor_set_color()) for comparison.
 *
 * Frequently the editor is used via #GwyColorDialog to adjust a colour. However, it can also be used on its own.
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
