/*
 *  $Id: graph-utils.c 30146 2026-06-25 12:51:18Z yeti-dn $
 *  Copyright (C) 2003-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 <stdlib.h>
#include <glib/gi18n-lib.h>

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

#include "libgwyui/graph-utils.h"
#include "libgwyui/cairo-utils.h"
#include "libgwyui/graph-internal.h"

#define GGP(x) GWY_SYMBOL_##x

typedef void (*SymbolDrawFunc)(cairo_t *cr,
                               gdouble x,
                               gdouble y,
                               gdouble halfside);

typedef struct {
    SymbolDrawFunc draw;
    gdouble size_factor;
    GwySymbolType type;
    gboolean do_stroke : 1;
    gboolean do_fill : 1;
} CurveSymbolInfo;

static void draw_star  (cairo_t *cr,
                        gdouble x,
                        gdouble y,
                        gdouble halfside);
static void draw_circle(cairo_t *cr,
                        gdouble x,
                        gdouble y,
                        gdouble halfside);

/* FIXME: use Gtk+ theme */
static const GwyRGBA selection_color = { 0.78, 0.73, 0.85, 1.0 };
static const GwyRGBA point_color = { 0.4, 0.4, 0.4, 1.0 };
static const GwyRGBA grid_color = { 0.90, 0.90, 0.90, 1.0 };

/* TODO GTK3 this is copied from Gwyddion3 but we should copy it more thoroughly. */
static const CurveSymbolInfo symbol_table[] = {
    { gwy_cairo_square,         0.84, GGP(SQUARE),                TRUE,  FALSE, },
    { gwy_cairo_cross,          1.2,  GGP(CROSS),                 TRUE,  FALSE, },
    { draw_circle,              1.0,  GGP(CIRCLE),                TRUE,  FALSE, },
    { draw_star,                1.0,  GGP(STAR),                  TRUE,  FALSE, },
    { gwy_cairo_times,          0.84, GGP(TIMES),                 TRUE,  FALSE, },
    { gwy_cairo_triangle_up,    1.0,  GGP(TRIANGLE_UP),           TRUE,  FALSE, },
    { gwy_cairo_triangle_down,  1.0,  GGP(TRIANGLE_DOWN),         TRUE,  FALSE, },
    { gwy_cairo_diamond,        1.2,  GGP(DIAMOND),               TRUE,  FALSE, },
    { gwy_cairo_square,         0.84, GGP(FILLED_SQUARE),         FALSE, TRUE,  },
    { draw_circle,              1.0,  GGP(DISC),                  FALSE, TRUE,  },
    { gwy_cairo_triangle_up,    1.0,  GGP(FILLED_TRIANGLE_UP),    FALSE, TRUE,  },
    { gwy_cairo_triangle_down,  1.0,  GGP(FILLED_TRIANGLE_DOWN),  FALSE, TRUE,  },
    { gwy_cairo_diamond,        1.2,  GGP(FILLED_DIAMOND),        FALSE, TRUE,  },
    { gwy_cairo_triangle_left,  1.0,  GGP(TRIANGLE_LEFT),         TRUE,  FALSE, },
    { gwy_cairo_triangle_left,  1.0,  GGP(FILLED_TRIANGLE_LEFT),  FALSE, TRUE,  },
    { gwy_cairo_triangle_right, 1.0,  GGP(TRIANGLE_RIGHT),        TRUE,  FALSE, },
    { gwy_cairo_triangle_right, 1.0,  GGP(FILLED_TRIANGLE_RIGHT), FALSE, TRUE,  },
    { gwy_cairo_asterisk,       1.1,  GGP(ASTERISK),              TRUE,  FALSE, },
};

static void
set_standard_line_attributes(cairo_t *cr, gdouble line_width)
{
    cairo_set_line_width(cr, line_width);
    cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
    cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
}

static void
set_dash_pattern(cairo_t *cr, GwyLineStyle line_style)
{
    gdouble line_width = cairo_get_line_width(cr);
    gdouble dashes[4];
    gint ndashes = 0;

    line_style %= (GWY_LINE_DASH_DOT + 1);

    if (line_style == GWY_LINE_DASH) {
        ndashes = 1;
        dashes[0] = 5*line_width;
    }
    else if (line_style == GWY_LINE_DASH_SPARSE) {
        ndashes = 2;
        dashes[0] = 3.3*line_width;
        dashes[1] = 6.7*line_width;
    }
    else if (line_style == GWY_LINE_DASH_DENSE) {
        ndashes = 2;
        dashes[0] = 6.7*line_width;
        dashes[1] = 3.3*line_width;
    }
    else if (line_style == GWY_LINE_DOT) {
        ndashes = 1;
        dashes[0] = line_width;
    }
    else if (line_style == GWY_LINE_DOT_SPARSE) {
        ndashes = 2;
        dashes[0] = line_width;
        dashes[1] = 2*line_width;
    }
    else if (line_style == GWY_LINE_DASH_DOT) {
        ndashes = 4;
        dashes[0] = 4*line_width;
        dashes[1] = 2*line_width;
        dashes[2] = line_width;
        dashes[3] = 2*line_width;
    }
    cairo_set_dash(cr, dashes, ndashes, 0.0);
}

static void
stroke_fill_symbol(const CurveSymbolInfo *syminfo,
                   cairo_t *cr)
{
    if (syminfo->do_fill) {
        if (syminfo->do_stroke)
            cairo_fill_preserve(cr);
        else
            cairo_fill(cr);
    }
    if (syminfo->do_stroke)
        cairo_stroke(cr);
}

static void
draw_star(cairo_t *cr,
          gdouble x,
          gdouble y,
          gdouble halfside)
{
    gwy_cairo_cross(cr, x, y, 1.2*halfside);
    gwy_cairo_times(cr, x, y, 0.84*halfside);
}

static void
draw_circle(cairo_t *cr,
            gdouble x,
            gdouble y,
            gdouble halfside)
{
    cairo_new_sub_path(cr);
    cairo_arc(cr, x, y, halfside, 0.0, 2.0*G_PI);
    cairo_close_path(cr);
}

static void
draw_vlines(cairo_t *cr,
            GwyGraphActiveAreaSpecs *specs,
            const gdouble *xcoords,
            guint n)
{
    for (guint i = 0; i < n; i++) {
        gdouble x = real2screen_x(specs, xcoords[i]);
        gwy_cairo_line(cr, x, specs->area.y, x, specs->area.y + specs->area.height);
    }
}

static void
draw_hlines(cairo_t *cr,
            GwyGraphActiveAreaSpecs *specs,
            const gdouble *ycoords,
            guint n)
{
    for (guint i = 0; i < n; i++) {
        gdouble y = real2screen_y(specs, ycoords[i]);
        gwy_cairo_line(cr, specs->area.x, y, specs->area.x + specs->area.width, y);
    }
}

/**
 * gwy_cairo_draw_symbols:
 * @cr: Cairo context.
 * @coords: Point coordinates, as concatenated pairs (x, y).
 * @n: The number of points.
 * @symbol_type: Symbol type.
 * @size: Symbol size.
 * @color: (nullable): Symbol color. If %NULL, the color is not set.
 *
 * Draws multiple graph symbols.
 **/
void
gwy_cairo_draw_symbols(cairo_t *cr,
                       const GwyXY *coords,
                       guint n,
                       GwySymbolType point_type,
                       gdouble size,
                       const GwyRGBA *color)
{
    g_return_if_fail(point_type < G_N_ELEMENTS(symbol_table));
    g_assert(symbol_table[point_type].type == point_type);

    const CurveSymbolInfo *syminfo = symbol_table + point_type;
    gdouble halfside = size * syminfo->size_factor;

    cairo_save(cr);
    if (color)
        gwy_cairo_set_source_rgba(cr, color);
    /* FIXME: This should also be controllable. */
    cairo_set_line_width(cr, fmax(size/16.0, 1.0));
    for (guint i = 0; i < n; i++) {
        gdouble xc = coords[i].x, yc = coords[i].y;
        syminfo->draw(cr, xc, yc, halfside);
        stroke_fill_symbol(syminfo, cr);
    }
    cairo_restore(cr);
}

static void
draw_curve_segment(cairo_t *cr,
                   const GwyXY *points,
                   guint n,
                   GwyLineStyle line_style,
                   gdouble line_width,
                   GwySymbolType point_type,
                   gdouble symbol_size)
{
    /* Line */
    if (n > 1 && line_width > 0.0) {
        cairo_save(cr);
        set_standard_line_attributes(cr, line_width);
        set_dash_pattern(cr, line_style);
        cairo_move_to(cr, points[0].x, points[0].y);
        for (guint i = 1; i < n; i++)
            cairo_line_to(cr, points[i].x, points[i].y);
        cairo_stroke(cr);
        cairo_restore(cr);
    }

    /* Symbols */
    if (n > 0 && symbol_size > 0.0)
        gwy_cairo_draw_symbols(cr, points, n, point_type, symbol_size, NULL);
}

/**
 * _gwy_graph_draw_curve:
 * @cr: Cairo context.
 * @specs: Specifications (boundaries) of the active area of the graph.
 * @gcmodel: Curve model of the curve to draw.
 *
 * Draws a single graph curve on a drawable.
 **/
void
_gwy_graph_draw_curve(cairo_t *cr,
                      GwyGraphActiveAreaSpecs *specs,
                      GwyGraphCurveModel *gcmodel)
{
    GwyGraphCurveModelPrivate *priv = gcmodel->priv;
    GwySymbolType point_type = priv->point_type;
    gdouble symbol_size = 0.0, line_width = 0.0;

    if (priv->mode == GWY_GRAPH_CURVE_LINE || priv->mode == GWY_GRAPH_CURVE_LINE_POINTS)
        line_width = priv->line_width;
    if (priv->mode == GWY_GRAPH_CURVE_POINTS || priv->mode == GWY_GRAPH_CURVE_LINE_POINTS)
        symbol_size = priv->point_size;

    if (!line_width && !symbol_size)
        return;

    cairo_save(cr);
    gwy_cairo_set_source_rgba(cr, &priv->color);

    GwyXY *points = g_new(GwyXY, priv->n);
    guint n = 0;
    for (guint i = 0; i < priv->n; i++) {
        points[n].x = real2screen_x(specs, priv->xdata[i]);
        points[n].y = real2screen_y(specs, priv->ydata[i]);
        /* Split the line into segments that do not stick insanely out of the area */
        if (points[n].x >= -1e6*specs->area.width
            && points[n].x <= 1e6*specs->area.width
            && points[n].y >= -1e6*specs->area.height
            && points[n].y <= 1e6*specs->area.height)
            n++;
        else if (n) {
            draw_curve_segment(cr, points, n, priv->line_style, line_width, point_type, symbol_size);
            n = 0;
        }
    }
    if (n)
        draw_curve_segment(cr, points, n, priv->line_style, line_width, point_type, symbol_size);
    g_free(points);

    cairo_restore(cr);
}

/**
 * gwy_cairo_draw_line:
 * @cr: Cairo context.
 * @x_from: x coordinate of the start point of the line
 * @y_from: y coordinate of the start point of the line
 * @x_to: x coordinate of the end point of the line
 * @y_to: y coordinate of the end point of the line
 * @line_style: Line style.
 * @width: Line width.
 * @color: (nullable): Line color. If %NULL, the color is not set.
 *
 * Draws a line segment on a drawable.
 **/
void
gwy_cairo_draw_line(cairo_t *cr,
                    gdouble x_from, gdouble y_from,
                    gdouble x_to, gdouble y_to,
                    GwyLineStyle line_style,
                    gdouble width,
                    const GwyRGBA *color)
{
    cairo_save(cr);
    if (color)
        gwy_cairo_set_source_rgba(cr, color);
    set_standard_line_attributes(cr, width);
    set_dash_pattern(cr, line_style);
    gwy_cairo_line(cr, x_from, y_from, x_to, y_to);
    cairo_stroke(cr);
    cairo_restore(cr);
}

/**
 * gwy_cairo_draw_symbol:
 * @cr: Cairo context.
 * @x: X coordinate of the point.
 * @y: Y coordinate of the point.
 * @symbol_type: Symbol type.
 * @size: Symbol size.
 * @color: (nullable): Symbol color. If %NULL, the color is not set.
 *
 * Draws a graph symbol on a drawable.
 *
 * Consider using gwy_cairo_draw_symbols() if you are drawing many symbols.
 **/
void
gwy_cairo_draw_symbol(cairo_t *cr,
                      gdouble x, gdouble y,
                      GwySymbolType symbol_type,
                      gdouble size,
                      const GwyRGBA *color)
{
    GwyXY point = { .x = x, .y = y };
    gwy_cairo_draw_symbols(cr, &point, 1, symbol_type, size, color);
}

/**
 * _gwy_graph_draw_selection_points:
 * @cr: Cairo context.
 * @specs: Specifications (boundaries) of the active area of the graph.
 * @selection: A selection of type #GwySelectionPoint.
 *
 * Draws selection points on a drawable.
 **/
void
_gwy_graph_draw_selection_points(cairo_t *cr,
                                 GwyGraphActiveAreaSpecs *specs,
                                 GwySelectionPoint *selection)
{
    g_return_if_fail(GWY_IS_SELECTION_POINT(selection));
    GwySelection *sel = GWY_SELECTION(selection);

    gint n = gwy_selection_get_n_objects(sel);
    if (!n)
        return;

    GwyXY *points = g_new(GwyXY, n);
    for (gint i = 0; i < n; i++) {
        gdouble selection_data[2];
        gwy_selection_get_object(sel, i, selection_data);

        points[i].x = real2screen_x(specs, selection_data[0]);
        points[i].y = real2screen_y(specs, selection_data[1]);
    }

    gwy_cairo_draw_symbols(cr, points, n, GWY_SYMBOL_CROSS, 6.0, &point_color);
}

/**
 * _gwy_graph_draw_selection_areas:
 * @cr: Cairo context.
 * @specs: Specifications (boundaries) of the active area of the graph.
 * @selection: A selection of type #GwySelectionRectangle.
 *
 * Draws selected area on a drawable.
 **/
void
_gwy_graph_draw_selection_areas(cairo_t *cr,
                                GwyGraphActiveAreaSpecs *specs,
                                GwySelectionRectangle *selection)
{
    g_return_if_fail(GWY_IS_SELECTION_RECTANGLE(selection));
    GwySelection *sel = GWY_SELECTION(selection);

    gint n = gwy_selection_get_n_objects(sel);
    if (!n)
        return;

    cairo_save(cr);
    gwy_cairo_set_source_rgba(cr, &selection_color);

    for (gint i = 0; i < n; i++) {
        gdouble selection_data[4];
        gwy_selection_get_object(sel, i, selection_data);

        gdouble xmin = real2screen_x(specs, selection_data[0]);
        gdouble ymin = real2screen_y(specs, selection_data[1]);
        gdouble xmax = real2screen_x(specs, selection_data[2]);
        gdouble ymax = real2screen_y(specs, selection_data[3]);

        cairo_rectangle(cr, fmin(xmin, xmax), fmin(ymin, ymax), fabs(xmax - xmin), fabs(ymax - ymin));
    }

    cairo_stroke(cr);
    cairo_restore(cr);
}

/**
 * _gwy_graph_draw_selection_ranges:
 * @cr: Cairo context.
 * @specs: Specifications (boundaries) of the active area of the graph.
 * @selection: A selection of type #GwySelectionRange.
 *
 * Draws selected x-area on a drawable.
 **/
void
_gwy_graph_draw_selection_ranges(cairo_t *cr,
                                 GwyGraphActiveAreaSpecs *specs,
                                 GwySelectionRange *selection)
{
    g_return_if_fail(GWY_IS_SELECTION_RANGE(selection));
    GwySelection *sel = GWY_SELECTION(selection);
    GwyOrientation orientation = gwy_selection_range_get_orientation(selection);

    gint n = gwy_selection_get_n_objects(sel);
    if (!n)
        return;

    cairo_save(cr);
    gwy_cairo_set_source_rgba(cr, &selection_color);

    for (gint i = 0; i < n; i++) {
        gdouble selection_data[2];
        gwy_selection_get_object(sel, i, selection_data);

        if (orientation == GWY_ORIENTATION_HORIZONTAL) {
            gdouble xmin = real2screen_x(specs, selection_data[0]);
            gdouble xmax = real2screen_x(specs, selection_data[1]);

            cairo_rectangle(cr, fmin(xmin, xmax), specs->area.y, fabs(xmax - xmin), specs->area.height);
        }
        else {
            gdouble ymin = real2screen_y(specs, selection_data[0]);
            gdouble ymax = real2screen_y(specs, selection_data[1]);

            cairo_rectangle(cr, specs->area.x, fmin(ymin, ymax), specs->area.width, fabs(ymax - ymin));
        }
    }

    cairo_fill(cr);
    cairo_restore(cr);
}

/**
 * _gwy_graph_draw_selection_lines:
 * @cr: Cairo context.
 * @specs: Specifications (boundaries) of the active area of the graph.
 * @selection: a #GwySelectionAxis structure
 *
 * Draws selected lines on a drawable.
 **/
void
_gwy_graph_draw_selection_lines(cairo_t *cr,
                                GwyGraphActiveAreaSpecs *specs,
                                GwySelectionAxis *selection)
{
    g_return_if_fail(GWY_IS_SELECTION_AXIS(selection));
    GwySelection *sel = GWY_SELECTION(selection);
    GwyOrientation orientation = gwy_selection_axis_get_orientation(selection);

    gint n = gwy_selection_get_n_objects(sel);
    if (!n)
        return;

    cairo_save(cr);
    gwy_cairo_set_source_rgba(cr, &selection_color);
    set_standard_line_attributes(cr, 1.0);

    gdouble *coords = g_new(gdouble, n);
    gwy_selection_get_data(sel, coords);
    if (orientation == GWY_ORIENTATION_HORIZONTAL)
        draw_vlines(cr, specs, coords, n);
    else
        draw_hlines(cr, specs, coords, n);
    g_free(coords);

    cairo_stroke(cr);
    cairo_restore(cr);
}

/**
 * _gwy_graph_draw_grid:
 * @cr: Cairo context.
 * @specs: Specifications (boundaries) of the active area of the graph.
 * @x_grid_data: Array of grid data for the x-axis, it can be %NULL if @nxdata is zero.
 * @nxdata: Number of x grid positions.
 * @y_grid_data: Array of grid data for the y-axis, it can be %NULL if @nydata is zero.
 * @nydata: Number of y grid positions.
 *
 * Draws an array of grid lines on a drawable.
 **/
void
_gwy_graph_draw_grid(cairo_t *cr,
                     GwyGraphActiveAreaSpecs *specs,
                     const gdouble *x_grid_data,
                     guint nxdata,
                     const gdouble *y_grid_data,
                     guint nydata)
{
    if (!nxdata && !nydata)
        return;

    cairo_save(cr);
    gwy_cairo_set_source_rgba(cr, &grid_color);
    set_standard_line_attributes(cr, 1.0);

    draw_vlines(cr, specs, x_grid_data, nxdata);
    draw_hlines(cr, specs, y_grid_data, nydata);

    cairo_stroke(cr);
    cairo_restore(cr);
}

GtkTreeModel*
_gwy_graph_get_point_type_store(void)
{
    static const GwyEnum point_types[] = {
        { N_("Square"),              GWY_SYMBOL_SQUARE,                },
        { N_("Circle"),              GWY_SYMBOL_CIRCLE,                },
        { N_("Diamond"),             GWY_SYMBOL_DIAMOND,               },
        { N_("Cross"),               GWY_SYMBOL_CROSS,                 },
        { N_("Diagonal cross"),      GWY_SYMBOL_TIMES,                 },
        { N_("Asterisk"),            GWY_SYMBOL_ASTERISK,              },
        { N_("Star"),                GWY_SYMBOL_STAR,                  },
        { N_("Triangle up"),         GWY_SYMBOL_TRIANGLE_UP,           },
        { N_("Triangle down"),       GWY_SYMBOL_TRIANGLE_DOWN,         },
        { N_("Triangle left"),       GWY_SYMBOL_TRIANGLE_LEFT,         },
        { N_("Triangle right"),      GWY_SYMBOL_TRIANGLE_RIGHT,        },
        { N_("Full square"),         GWY_SYMBOL_FILLED_SQUARE,         },
        { N_("Disc"),                GWY_SYMBOL_FILLED_CIRCLE,         },
        { N_("Full diamond"),        GWY_SYMBOL_FILLED_DIAMOND,        },
        { N_("Full triangle up"),    GWY_SYMBOL_FILLED_TRIANGLE_UP,    },
        { N_("Full triangle down"),  GWY_SYMBOL_FILLED_TRIANGLE_DOWN,  },
        { N_("Full triangle left"),  GWY_SYMBOL_FILLED_TRIANGLE_LEFT,  },
        { N_("Full triangle right"), GWY_SYMBOL_FILLED_TRIANGLE_RIGHT, },
    };
    static const GwyRGBA fg = { 0.0, 0.0, 0.0, 1.0 };
    static GtkListStore *store = NULL;

    if (store)
        return GTK_TREE_MODEL(store);

    store = gtk_list_store_new(3, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF);
    g_object_add_weak_pointer(G_OBJECT(store), (gpointer*)&store);

    gint width, height;
    gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
    width |= 1;
    height |= 1;
    gdouble size = 0.4*height;

    cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
    cairo_t *cr = cairo_create(surface);

    cairo_set_line_width(cr, 1.0);
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    for (guint i = 0; i < G_N_ELEMENTS(point_types); i++) {
        const GwyEnum *pt = point_types + i;
        cairo_paint(cr);
        gwy_cairo_draw_symbol(cr, 0.5*width, 0.5*height, pt->value, size, &fg);

        GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
        GtkTreeIter iter;
        gtk_list_store_insert_with_values(store, &iter, G_MAXINT,
                                          GWY_GRAPH_COMBO_COLUMN_VALUE, pt->value,
                                          GWY_GRAPH_COMBO_COLUMN_PIXBUF, pixbuf,
                                          GWY_GRAPH_COMBO_COLUMN_NAME, gwy_C(pt->name),
                                          -1);
        g_object_unref(pixbuf);
    }

    cairo_destroy(cr);
    cairo_surface_destroy(surface);

    return GTK_TREE_MODEL(store);
}

GtkTreeModel*
_gwy_graph_get_line_style_store(void)
{
    static const GwyEnum line_styles[] = {
        { gwy_NC("line-style", "Solid"),         GWY_LINE_SOLID,       },
        { gwy_NC("line-style", "Dash"),          GWY_LINE_DASH,        },
        { gwy_NC("line-style", "Dash (sparse)"), GWY_LINE_DASH_SPARSE, },
        { gwy_NC("line-style", "Dash (dense)"),  GWY_LINE_DASH_DENSE,  },
        { gwy_NC("line-style", "Dot"),           GWY_LINE_DOT,         },
        { gwy_NC("line-style", "Dot (sparse)"),  GWY_LINE_DOT_SPARSE,  },
        { gwy_NC("line-style", "Dash-dot"),      GWY_LINE_DASH_DOT,    },
    };
    static const GwyRGBA fg = { 0.0, 0.0, 0.0, 1.0 };
    static GtkListStore *store = NULL;

    if (store)
        return GTK_TREE_MODEL(store);

    store = gtk_list_store_new(3, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF);
    g_object_add_weak_pointer(G_OBJECT(store), (gpointer*)&store);

    gint width, height;
    gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
    width = 3.5*height;
    height |= 1;

    cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
    cairo_t *cr = cairo_create(surface);

    cairo_set_line_width(cr, 1.0);
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    for (guint i = 0; i < G_N_ELEMENTS(line_styles); i++) {
        const GwyEnum *ls = line_styles + i;
        cairo_paint(cr);
        gwy_cairo_draw_line(cr, 1, 0.5*height, width-1, 0.5*height, ls->value, 3.0, &fg);

        GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
        GtkTreeIter iter;
        gtk_list_store_insert_with_values(store, &iter, G_MAXINT,
                                          GWY_GRAPH_COMBO_COLUMN_VALUE, ls->value,
                                          GWY_GRAPH_COMBO_COLUMN_PIXBUF, pixbuf,
                                          GWY_GRAPH_COMBO_COLUMN_NAME, gwy_C(ls->name),
                                          -1);
        g_object_unref(pixbuf);
    }

    cairo_destroy(cr);
    cairo_surface_destroy(surface);

    return GTK_TREE_MODEL(store);
}

/**
 * SECTION: graph-utils
 * @title: Graph utilities
 * @short_description: Common graph functions and utilities
 **/

/**
 * GwyGraphActiveAreaSpecs:
 * @xmin: X offset of the active area with respect to drawable left border.
 * @ymin: Y offset of the active area with respect to drawable top border.
 * @width: Active area width pixels.
 * @height: Active area height in pixels.
 * @real_xmin: Minimum x value in real units.
 * @real_ymin: Minimum y value in real units.
 * @real_width: Area width in real units.
 * @real_height: Area height in real units.
 * @log_x: %TRUE if x-axis is logarithmic.
 * @log_y: %TRUE if y-axis is logarithmic.
 *
 * Graph area specification (for graph drawing primitives).
 **/

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