/*
 *  $Id: color-swatch.c 28778 2025-11-04 16:47:31Z yeti-dn $
 *  Copyright (C) 2024 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@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 <gtk/gtk.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"

#include "libgwyui/color-swatch.h"
#include "libgwyui/cairo-utils.h"
#include "libgwyui/widget-impl-utils.h"

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

struct _GwyColorSwatchPrivate {
    GwyRGBA color;
    gboolean use_alpha;
};

static void     set_property        (GObject *object,
                                     guint param_id,
                                     const GValue *value,
                                     GParamSpec *pspec);
static void     get_property        (GObject *object,
                                     guint param_id,
                                     GValue *value,
                                     GParamSpec *pspec);
static void     get_preferred_width (GtkWidget *widget,
                                     gint *minimum,
                                     gint *natural);
static void     get_preferred_height(GtkWidget *widget,
                                     gint *minimum,
                                     gint *natural);
static gboolean draw                (GtkWidget *widget,
                                     cairo_t *cr);

static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GtkWidgetClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyColorSwatch, gwy_color_swatch, GTK_TYPE_WIDGET,
                        G_ADD_PRIVATE(GwyColorSwatch))

static void
gwy_color_swatch_class_init(GwyColorSwatchClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

    parent_class = gwy_color_swatch_parent_class;

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

    widget_class->draw = draw;
    widget_class->get_preferred_width = get_preferred_width;
    widget_class->get_preferred_height = get_preferred_height;

    properties[PROP_USE_ALPHA] = g_param_spec_boolean("use-alpha", NULL,
                                                      "Whether to display the color with an alpha value", FALSE,
                                                      GWY_GPARAM_RWE);
    properties[PROP_COLOR] = g_param_spec_boxed("color", NULL,
                                                "Color to display",
                                                GWY_TYPE_RGBA,
                                                GWY_GPARAM_RWE);

    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static void
gwy_color_swatch_init(GwyColorSwatch *swatch)
{
    GwyColorSwatchPrivate *priv;

    swatch->priv = priv = gwy_color_swatch_get_instance_private(swatch);
    /* The traditional default. */
    priv->color = (GwyRGBA){ 0.0, 0.0, 0.0, 1.0 };
    priv->use_alpha = FALSE;

    gtk_widget_set_has_window(GTK_WIDGET(swatch), FALSE);
}

static void
set_property(GObject *object,
             guint param_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwyColorSwatch *swatch = GWY_COLOR_SWATCH(object);

    switch (param_id) {
        case PROP_USE_ALPHA:
        gwy_color_swatch_set_use_alpha(swatch, g_value_get_boolean(value));
        break;

        case PROP_COLOR:
        gwy_color_swatch_set_color(swatch, g_value_get_boxed(value));
        break;

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

static void
get_property(GObject *object,
             guint param_id,
             GValue *value,
             GParamSpec *pspec)
{
    GwyColorSwatchPrivate *priv = GWY_COLOR_SWATCH(object)->priv;

    switch (param_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;

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

static void
get_preferred_width(G_GNUC_UNUSED GtkWidget *widget,
                    gint *minimum,
                    gint *natural)
{
    *minimum = 4;
    *natural = 24;
}

static void
get_preferred_height(G_GNUC_UNUSED GtkWidget *widget,
                     gint *minimum,
                     gint *natural)
{
    *minimum = 4;
    *natural = 16;
}

static gboolean
draw(GtkWidget *widget, cairo_t *cr)
{
    GwyColorSwatchPrivate *priv = GWY_COLOR_SWATCH(widget)->priv;
    GtkStyleContext *context = gtk_widget_get_style_context(widget);
    GtkStateFlags state = gtk_style_context_get_state(context);
    gboolean is_insensitive = state & GTK_STATE_FLAG_INSENSITIVE;
    gint width = gtk_widget_get_allocated_width(widget);
    gint height = gtk_widget_get_allocated_height(widget);
    GwyRGBA color = priv->color;

    cairo_save(cr);
    if (is_insensitive)
        cairo_push_group(cr);

    if (priv->use_alpha && priv->color.a < 1.0) {
        GdkRectangle allocation;
        gtk_widget_get_allocation(widget, &allocation);
        gdouble x = 2.0*allocation.width*allocation.height/(allocation.width + allocation.height);
        gdouble check_size = fmin(0.2*x, pow(x, 2.0/3.0)/1.6);
        gint size = MAX(GWY_ROUND(check_size), 1);
        GwyRGBA dark = { 0.333, 0.333, 0.333, 1.0 }, light = { 0.667, 0.667, 0.667, 1.0 };

        gwy_cairo_checker_rectangle(cr, &light, &dark, width, height, size);
    }
    gwy_cairo_set_source_rgba(cr, &color);
    cairo_rectangle(cr, 0, 0, width, height);
    cairo_fill(cr);

    if (is_insensitive) {
        cairo_pop_group_to_source(cr);
        cairo_paint_with_alpha(cr, 0.6);
    }
    cairo_restore(cr);

    return FALSE;
}

/**
 * gwy_color_swatch_new:
 *
 * Creates a new color swatch.
 *
 * Returns: A new color swatch.
 **/
GtkWidget*
gwy_color_swatch_new (void)
{
    return g_object_new(GWY_TYPE_COLOR_SWATCH, NULL);
}

/**
 * gwy_color_swatch_set_color:
 * @swatch: A colour swatch.
 * @color: The colour to display as #GwyRGBA.
 *
 * Sets the current colour of a colour swatch.
 **/
void
gwy_color_swatch_set_color(GwyColorSwatch *swatch,
                           const GwyRGBA *color)
{
    g_return_if_fail(GWY_IS_COLOR_SWATCH(swatch));
    g_return_if_fail(color);
    if (gwy_rgba_equal(color, &swatch->priv->color))
        return;
    swatch->priv->color = *color;
    if (gtk_widget_is_drawable(GTK_WIDGET(swatch)))
        gtk_widget_queue_draw(GTK_WIDGET(swatch));
    g_object_notify_by_pspec(G_OBJECT(swatch), properties[PROP_COLOR]);
}

/**
 * gwy_color_swatch_get_color:
 * @swatch: A colour swatch.
 * @color: Location to fill in with the current colour.
 *
 * Obtains the current colour of a colour swatch.
 **/
void
gwy_color_swatch_get_color(GwyColorSwatch *swatch,
                           GwyRGBA *color)
{
    g_return_if_fail(GWY_IS_COLOR_SWATCH(swatch));
    g_return_if_fail(color);
    *color = swatch->priv->color;
}

/**
 * gwy_color_swatch_set_use_alpha:
 * @swatch: A colour swatch.
 * @use_alpha: %TRUE if colour swatch should visualise the alpha channel, %FALSE to ignore it.
 *
 * Sets whether the colour swatch should use the alpha channel.
 **/
void
gwy_color_swatch_set_use_alpha(GwyColorSwatch *swatch,
                               gboolean use_alpha)
{
    g_return_if_fail(GWY_IS_COLOR_SWATCH(swatch));

    GwyColorSwatchPrivate *priv = swatch->priv;
    if (!priv->use_alpha == !use_alpha)
        return;

    priv->use_alpha = !!use_alpha;
    if (gtk_widget_is_drawable(GTK_WIDGET(swatch)))
        gtk_widget_queue_draw(GTK_WIDGET(swatch));

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

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

/**
 * SECTION:color-swatch
 * @title: GwyColorSwatch
 * @short_description: A swatch displaying a colour
 *
 * The widget is a simple rectangle displaying a colour, with some support for visualisation of the alpha value. It is
 * usually not useful standalone, but can be a part of more complex colour manipulation widgets.
 **/

/* 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 : */
