/*
 *  $Id: cell-renderer-gradient.c 15501 2013-10-26 20:11:05Z yeti-dn $
 *  Copyright (C) 2012-2025 David Nečas (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 "libgwyddion/macros.h"
#include "libgwyddion/utils.h"

#include "libgwyui/cell-renderer-gradient.h"
#include "libgwyui/cairo-utils.h"
#include "libgwyui/widget-impl-utils.h"

enum {
    ASPECT_RATIO = 5,
    MIN_HEIGHT = 1,
};

enum {
    PROP_0,
    PROP_GRADIENT,
    NUM_PROPERTIES
};

struct _GwyCellRendererGradientPrivate {
    GwyGradient *gradient;
    cairo_pattern_t *pattern;
    gulong gradient_data_changed_id;
    guint height;
};

static void               dispose              (GObject *object);
static void               get_property         (GObject *object,
                                                guint prop_id,
                                                GValue *value,
                                                GParamSpec *pspec);
static void               set_property         (GObject *object,
                                                guint prop_id,
                                                const GValue *value,
                                                GParamSpec *pspec);
static GtkSizeRequestMode get_request_mode     (GtkCellRenderer *renderer);
static void               get_preferred_width  (GtkCellRenderer *renderer,
                                                GtkWidget *widget,
                                                gint *minimum_size,
                                                gint *natural_size);
static void               get_preferred_height (GtkCellRenderer *renderer,
                                                GtkWidget *widget,
                                                gint *minimum_size,
                                                gint *natural_size);
static void               render               (GtkCellRenderer *cellrenderer,
                                                cairo_t *cr,
                                                GtkWidget *widget,
                                                const GdkRectangle *background_area,
                                                const GdkRectangle *cell_area,
                                                GtkCellRendererState flags);
static gboolean           set_gradient         (GwyCellRendererGradient *renderer,
                                                GwyGradient *gradient);
static void               gradient_data_changed(GwyCellRendererGradient *renderer,
                                                GwyGradient *gradient);
static void               ensure_pattern       (GwyCellRendererGradient *renderer);
static void               discard_pattern      (GwyCellRendererGradient *renderer);

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

G_DEFINE_TYPE_WITH_CODE(GwyCellRendererGradient, gwy_cell_renderer_gradient, GTK_TYPE_CELL_RENDERER,
                        G_ADD_PRIVATE(GwyCellRendererGradient));

static void
dispose(GObject *object)
{
    GwyCellRendererGradient *renderer = GWY_CELL_RENDERER_GRADIENT(object);
    discard_pattern(renderer);
    set_gradient(renderer, NULL);
    G_OBJECT_CLASS(parent_class)->dispose(object);
}

static void
gwy_cell_renderer_gradient_class_init(GwyCellRendererGradientClass *class)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(class);
    GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(class);

    parent_class = gwy_cell_renderer_gradient_parent_class;

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

    cell_class->get_request_mode = get_request_mode;
    cell_class->get_preferred_width = get_preferred_width;
    cell_class->get_preferred_height = get_preferred_height;
    cell_class->render = render;

    properties[PROP_GRADIENT] = g_param_spec_object("gradient", NULL,
                                                    "Gradient to render",
                                                    GWY_TYPE_GRADIENT, GWY_GPARAM_RWE);

    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static void
gwy_cell_renderer_gradient_init(GwyCellRendererGradient *renderer)
{
    GwyCellRendererGradientPrivate *priv;

    renderer->priv = priv = gwy_cell_renderer_gradient_get_instance_private(renderer);

    gint w, h;
    /* FIXME: This is wrong. Use a font-based estimate for the natural size. */
    gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &w, &h);
    priv->height = h;
}

static void
set_property(GObject *object,
             guint prop_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwyCellRendererGradient *renderer = GWY_CELL_RENDERER_GRADIENT(object);

    switch (prop_id) {
        case PROP_GRADIENT:
        set_gradient(renderer, g_value_get_object(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)
{
    GwyCellRendererGradientPrivate *priv = GWY_CELL_RENDERER_GRADIENT(object)->priv;

    switch (prop_id) {
        case PROP_GRADIENT:
        g_value_set_object(value, priv->gradient);
        break;

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

static GtkSizeRequestMode
get_request_mode(G_GNUC_UNUSED GtkCellRenderer *renderer)
{
    return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}

static gint
get_natural_height(GwyCellRendererGradient *renderer, GtkWidget *widget)
{
    GwyCellRendererGradientPrivate *priv = renderer->priv;
    if (!priv->height)
        priv->height = get_natural_text_height(widget);
    return priv->height;
}

static void
get_preferred_width(GtkCellRenderer *renderer, GtkWidget *widget, gint *minimum, gint *natural)
{
    gint xpad;
    gtk_cell_renderer_get_padding(renderer, &xpad, NULL);
    if (minimum)
        *minimum = ASPECT_RATIO*MIN_HEIGHT + 2*xpad;
    if (natural) {
        gint height = get_natural_height(GWY_CELL_RENDERER_GRADIENT(renderer), widget);
        *natural = ASPECT_RATIO*height + 2*xpad;
    }
}

static void
get_preferred_height(GtkCellRenderer *renderer, GtkWidget *widget, gint *minimum, gint *natural)
{
    gint ypad;
    gtk_cell_renderer_get_padding(renderer, &ypad, NULL);
    if (minimum)
        *minimum = MIN_HEIGHT + 2*ypad;
    if (natural) {
        gint height = get_natural_height(GWY_CELL_RENDERER_GRADIENT(renderer), widget);
        *natural = height + 2*ypad;
    }
}

// FIXME: Do we want to change the rendering depending on style flags?
static void
render(GtkCellRenderer *cellrenderer,
       cairo_t *cr,
       G_GNUC_UNUSED GtkWidget *widget,
       G_GNUC_UNUSED const GdkRectangle *background_area,
       const GdkRectangle *cell_area,
       G_GNUC_UNUSED GtkCellRendererState flags)
{
    gint xpad, ypad;
    gtk_cell_renderer_get_padding(cellrenderer, &xpad, &ypad);
    GdkRectangle rect = {
        .x = cell_area->x + xpad,
        .y = cell_area->y + ypad,
        .width = cell_area->width - 2*xpad,
        .height = cell_area->height - 2*ypad,
    };
    if (!gdk_rectangle_intersect(cell_area, &rect, NULL))
        return;

    GwyCellRendererGradient *renderer = GWY_CELL_RENDERER_GRADIENT(cellrenderer);
    GwyCellRendererGradientPrivate *priv = renderer->priv;

    ensure_pattern(renderer);
    // This can happen with a NULL gradient.
    if (!priv->pattern)
        return;

    cairo_matrix_t matrix;
    cairo_matrix_init_scale(&matrix, 1.0/rect.width, 1.0/rect.height);
    cairo_matrix_translate(&matrix, -rect.x, -rect.y);
    cairo_pattern_set_matrix(priv->pattern, &matrix);

    cairo_save(cr);
    cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
    cairo_set_source(cr, priv->pattern);
    cairo_fill(cr);
    cairo_restore(cr);
}

/**
 * gwy_cell_renderer_gradient_new:
 *
 * Creates a new gradient cell renderer.
 *
 * Return value: A new gradient cell renderer.
 **/
GtkCellRenderer*
gwy_cell_renderer_gradient_new(void)
{
    return g_object_new(GWY_TYPE_CELL_RENDERER_GRADIENT, NULL);
}

static gboolean
set_gradient(GwyCellRendererGradient *renderer,
             GwyGradient *gradient)
{
    GwyCellRendererGradientPrivate *priv = renderer->priv;
    if (!gwy_set_member_object(renderer, gradient, GWY_TYPE_GRADIENT, &priv->gradient,
                               "data-changed", G_CALLBACK(gradient_data_changed),
                               &priv->gradient_data_changed_id, G_CONNECT_SWAPPED,
                               NULL))
        return FALSE;

    g_object_notify_by_pspec(G_OBJECT(renderer), properties[PROP_GRADIENT]);
    discard_pattern(renderer);
    return TRUE;
}

static void
gradient_data_changed(GwyCellRendererGradient *renderer,
                      G_GNUC_UNUSED GwyGradient *gradient)
{
    discard_pattern(renderer);
}

static void
ensure_pattern(GwyCellRendererGradient *renderer)
{
    GwyCellRendererGradientPrivate *priv = renderer->priv;
    if (priv->pattern)
        return;

    if (priv->gradient)
        priv->pattern = gwy_cairo_pattern_create_gradient(priv->gradient, GTK_POS_RIGHT, FALSE);
}

static void
discard_pattern(GwyCellRendererGradient *renderer)
{
    GwyCellRendererGradientPrivate *priv = renderer->priv;
    if (priv->pattern) {
        cairo_pattern_destroy(priv->pattern);
        priv->pattern = NULL;
    }
}

/**
 * SECTION:cell-renderer-gradient
 * @title: GwyCellRendererGradient
 * @short_description: Renders a gradient in a cell
 *
 * A #GwyCellRendererGradient can be used to render a #GwyGradient in a cell of a #GtkTreeView or a similar cell-view
 * widget.
 */

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