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

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

#include "libgwyui/icons.h"
#include "libgwyui/option-menus.h"
#include "libgwyui/graph.h"
#include "libgwyui/sci-text.h"
#include "libgwyui/combo-boxes.h"
#include "libgwyui/null-store.h"
#include "libgwyui/color-button.h"
#include "libgwyui/color-editor.h"
#include "libgwyui/color-dialog.h"
#include "libgwyui/cell-renderer-color.h"
#include "libgwyui/utils.h"
#include "libgwyui/graph-curve-model.h"
#include "libgwyui/graph-curve-dialog.h"
#include "libgwyui/graph-internal.h"

/* FIXME: Reverse dependence, change sanity.h to normal API and get rid of parts of it, as needed. */

enum {
    SGNL_NEXT,
    SGNL_PREVIOUS,
    NUM_SIGNALS
};

enum {
    RESPONSE_PREV = 1,
    RESPONSE_NEXT = 2,
};

struct _GwyGraphCurveDialogPrivate {
    GtkWidget *curvetype_menu;
    GtkWidget *color_button;
    GtkWidget *pointtype_menu;
    GtkWidget *linestyle_menu;
    GtkAdjustment *pointsize;
    GtkAdjustment *thickness;

    GtkWidget *scitext;

    GtkWidget *color_dialog;
    GtkWidget *color_selector;

    GtkWidget *prev;
    GtkWidget *next;
    gboolean prev_possible;
    gboolean next_possible;

    GwyGraphCurveModel *curve_model;
    gulong curve_notify_id;
};

typedef struct {
    const gchar *property_name;
    gint initial_value;
} ComboInfo;

static void       dispose                    (GObject *object);
static gboolean   delete                     (GtkWidget *widget,
                                              GdkEventAny *event);
static void       response                   (GtkDialog *gtkdialog,
                                              gint response_id);
static GtkWidget* graph_combo_box_new        (GwyGraphCurveDialog *dialog,
                                              const gchar *property,
                                              gboolean labels,
                                              GtkTreeModel *model,
                                              gint current);
static void       edit_color                 (GwyGraphCurveDialog *dialog);
static void       color_edited               (GwyColorEditor *colorsel,
                                              GwyGraphCurveDialog *dialog);
static void       color_selected             (GtkComboBox *combo,
                                              GwyGraphCurveDialog *dialog);
static void       description_changed        (GwySciText *scitext,
                                              GwyGraphCurveDialog *dialog);
static void       refresh                    (GwyGraphCurveDialog *dialog);
static void       curve_notify               (GwyGraphCurveDialog *dialog,
                                              const GParamSpec *pspec,
                                              GwyGraphCurveModel *cmodel);
static void       curvetype_changed          (GtkWidget *combo,
                                              GwyGraphCurveDialog *dialog);
static void       thickness_changed          (GtkAdjustment *adj,
                                              GwyGraphCurveDialog *dialog);
static void       pointsize_changed          (GtkAdjustment *adj,
                                              GwyGraphCurveDialog *dialog);
static GtkWidget* graph_color_combo_new      (void);
static void       graph_color_combo_select   (GtkComboBox *combo,
                                              const GwyRGBA *color);
static void       combo_set_current          (GtkWidget *combo,
                                              gint value);
static void       update_switcher_sensitivity(GwyGraphCurveDialog *dialog);

static guint signals[NUM_SIGNALS];
static GtkDialogClass *parent_class = NULL;

static G_DEFINE_QUARK(gwy-graph-combo-info, combo_info)

G_DEFINE_TYPE_WITH_CODE(GwyGraphCurveDialog, gwy_graph_curve_dialog, GTK_TYPE_DIALOG,
                        G_ADD_PRIVATE(GwyGraphCurveDialog))

static void
gwy_graph_curve_dialog_class_init(GwyGraphCurveDialogClass *klass)
{
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    GtkDialogClass *dialog_class = GTK_DIALOG_CLASS(klass);
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GType type = G_TYPE_FROM_CLASS(klass);

    parent_class = gwy_graph_curve_dialog_parent_class;

    gobject_class->dispose = dispose;
    widget_class->delete_event = delete;
    dialog_class->response = response;

    /**
     * GwyGraphCurveDialog::next:
     * @gwygraphareadialog: The #GwyGraphCurveDialog which received the signal.
     *
     * The ::next signal is emitted when the Next button is clicked and the next curve should be edited. The dialog
     * itself does not switch curves because it only knows about the curve currently being edited.
     **/
    signals[SGNL_NEXT] = g_signal_new("next", type,
                                      G_SIGNAL_RUN_FIRST,
                                      G_STRUCT_OFFSET(GwyGraphCurveDialogClass, next),
                                      NULL, NULL,
                                      g_cclosure_marshal_VOID__VOID,
                                      G_TYPE_NONE, 0);
    g_signal_set_va_marshaller(signals[SGNL_NEXT], type, g_cclosure_marshal_VOID__VOIDv);

    /**
     * GwyGraphCurveDialog::previous:
     * @gwygraphareadialog: The #GwyGraphCurveDialog which received the signal.
     *
     * The ::previous signal is emitted when the Prev button is clicked and the previous curve should be edited. The
     * dialog itself does not switch curves because it only knows about the curve currently being edited.
     **/
    signals[SGNL_PREVIOUS] = g_signal_new("previous", type,
                                          G_SIGNAL_RUN_FIRST,
                                          G_STRUCT_OFFSET(GwyGraphCurveDialogClass, previous),
                                          NULL, NULL,
                                          g_cclosure_marshal_VOID__VOID,
                                          G_TYPE_NONE, 0);
    g_signal_set_va_marshaller(signals[SGNL_PREVIOUS], type, g_cclosure_marshal_VOID__VOIDv);
}

static void
gwy_graph_curve_dialog_init(GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv;

    dialog->priv = priv = gwy_graph_curve_dialog_get_instance_private(dialog);

    gtk_window_set_title(GTK_WINDOW(dialog), _("Curve Properties"));
    priv->prev = gwy_add_button_to_dialog(GTK_DIALOG(dialog), _("Pre_v"), GWY_ICON_PREVIOUS, RESPONSE_PREV);
    priv->next = gwy_add_button_to_dialog(GTK_DIALOG(dialog), _("_Next"), GWY_ICON_NEXT, RESPONSE_NEXT);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(dialog), GWY_STOCK_CLOSE);
    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
    update_switcher_sensitivity(dialog);

    GtkWidget *grid = gtk_grid_new();
    gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
    gtk_grid_set_row_spacing(GTK_GRID(grid), 2);
    gtk_container_set_border_width(GTK_CONTAINER(grid), 4);
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), grid, FALSE, FALSE, 0);

    priv->curvetype_menu = gwy_enum_combo_box_new(gwy_graph_curve_mode_get_enum(), -1,
                                                  G_CALLBACK(curvetype_changed), dialog,
                                                  0, TRUE);

    gint row = 0;
    gwy_gtkgrid_attach_adjbar(GTK_GRID(grid), row++, _("Plot _style:"), NULL, G_OBJECT(priv->curvetype_menu),
                              GWY_HSCALE_WIDGET_NO_EXPAND);

    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
    priv->color_button = gwy_color_button_new();
    gwy_color_button_set_use_alpha(GWY_COLOR_BUTTON(priv->color_button), FALSE);
    gtk_widget_set_hexpand(priv->color_button, FALSE);
    gtk_box_pack_end(GTK_BOX(hbox), priv->color_button, FALSE, FALSE, 0);
    g_signal_connect_swapped(priv->color_button, "clicked", G_CALLBACK(edit_color), dialog);

    priv->color_selector = graph_color_combo_new();
    gtk_widget_set_hexpand(priv->color_selector, FALSE);
    gtk_box_pack_end(GTK_BOX(hbox), priv->color_selector, FALSE, FALSE, 0);
    g_signal_connect(priv->color_selector, "changed", G_CALLBACK(color_selected), dialog);

    gwy_gtkgrid_attach_adjbar(GTK_GRID(grid), row++, _("Pl_ot color:"), NULL, G_OBJECT(hbox), GWY_HSCALE_WIDGET);

    priv->pointtype_menu = graph_combo_box_new(dialog, "point-type", TRUE, _gwy_graph_get_point_type_store(),
                                              GWY_SYMBOL_SQUARE);
    gwy_gtkgrid_attach_adjbar(GTK_GRID(grid), row++, _("Point _type:"), NULL, G_OBJECT(priv->pointtype_menu),
                              GWY_HSCALE_WIDGET_NO_EXPAND);

    priv->pointsize = gtk_adjustment_new(6.0, 0.5, 50.0, 0.1, 5.0, 0);
    gwy_gtkgrid_attach_adjbar(GTK_GRID(grid), row++, _("_Point size:"), _("px"), G_OBJECT(priv->pointsize),
                              GWY_HSCALE_SQRT | GWY_HSCALE_SNAP);
    g_signal_connect(priv->pointsize, "value-changed", G_CALLBACK(pointsize_changed), dialog);

    priv->linestyle_menu = graph_combo_box_new(dialog, "line-style", FALSE, _gwy_graph_get_line_style_store(),
                                               GWY_LINE_SOLID);
    gwy_gtkgrid_attach_adjbar(GTK_GRID(grid), row++, _("_Line type:"), NULL, G_OBJECT(priv->linestyle_menu),
                              GWY_HSCALE_WIDGET_NO_EXPAND);

    priv->thickness = gtk_adjustment_new(1.0, 0.2, 30.0, 0.1, 5.0, 0);
    gwy_gtkgrid_attach_adjbar(GTK_GRID(grid), row++, _("Line t_hickness:"), _("px"), G_OBJECT(priv->thickness),
                              GWY_HSCALE_SQRT | GWY_HSCALE_SNAP);
    g_signal_connect(priv->thickness, "value-changed", G_CALLBACK(thickness_changed), dialog);

    GtkWidget *label = gtk_label_new_with_mnemonic(_("<b>Label Te_xt</b>"));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_widget_set_margin_top(label, 8);
    gtk_grid_attach(GTK_GRID(grid), label, 0, row++, 3, 1);

    priv->scitext = gwy_sci_text_new();
    GwySciText *scitext = GWY_SCI_TEXT(priv->scitext);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), gwy_sci_text_get_entry(scitext));
    //gtk_container_set_border_width(GTK_CONTAINER(priv->scitext), 4);
    g_signal_connect(priv->scitext, "edited", G_CALLBACK(description_changed), dialog);

    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), priv->scitext, FALSE, FALSE, 0);
    gtk_widget_show_all(priv->scitext);
}

static gboolean
delete(GtkWidget *widget, G_GNUC_UNUSED GdkEventAny *event)
{
    GwyGraphCurveDialogPrivate *priv = GWY_GRAPH_CURVE_DIALOG(widget)->priv;

    if (priv->color_dialog) {
        gtk_widget_destroy(priv->color_dialog);
        priv->color_dialog = NULL;
    }
    gtk_widget_hide(widget);

    return TRUE;
}

static void
dispose(GObject *object)
{
    GwyGraphCurveDialogPrivate *priv = GWY_GRAPH_CURVE_DIALOG(object)->priv;

    if (priv->color_dialog) {
        gtk_widget_destroy(priv->color_dialog);
        priv->color_dialog = NULL;
    }
    g_clear_signal_handler(&priv->curve_notify_id, priv->curve_model);
    g_clear_object(&priv->curve_model);

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

/**
 * gwy_graph_curve_dialog_new: (constructor)
 *
 * Creates a new dialog for modifying properties of graph curves.
 *
 * The dialog is useless without a curve model. Use gwy_graph_curve_dialog_set_model() to set it, or create the dialog
 * directly with a model using gwy_graph_curve_dialog_new_with_model().
 *
 * Returns: (transfer full): A newly created dialog.
 **/
GtkWidget*
gwy_graph_curve_dialog_new(void)
{
    return gtk_widget_new(GWY_TYPE_GRAPH_CURVE_DIALOG, NULL);
}

/**
 * gwy_graph_curve_dialog_new_with_model: (constructor)
 * @cmodel: Graph curve model.
 *
 * Creates a new dialog for modifying properties of graph curves using a graph curve model.
 *
 * Returns: (transfer full): A newly created dialog.
 **/
GtkWidget*
gwy_graph_curve_dialog_new_with_model(GwyGraphCurveModel *cmodel)
{
    GwyGraphCurveDialog *dialog = g_object_new(GWY_TYPE_GRAPH_CURVE_DIALOG, NULL);
    gwy_graph_curve_dialog_set_model(dialog, cmodel);
    return GTK_WIDGET(dialog);
}

static void
combo_changed(GtkWidget *combo,
              GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;

    GtkTreeIter iter;
    if (!priv->curve_model || !gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter))
        return;

    ComboInfo *info = g_object_get_qdata(G_OBJECT(combo), combo_info_quark());
    g_return_if_fail(info->property_name);
    GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
    gint active;
    gtk_tree_model_get(model, &iter, GWY_GRAPH_COMBO_COLUMN_VALUE, &active, -1);
    g_object_set(priv->curve_model, info->property_name, active, NULL);
}

static GtkWidget*
graph_combo_box_new(GwyGraphCurveDialog *dialog,
                    const gchar *property,
                    gboolean labels,
                    GtkTreeModel *model,
                    gint current)
{
    GtkWidget *combo = gtk_combo_box_new_with_model(model);
    gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(combo), 1);
    ComboInfo *info = g_new(ComboInfo, 1);
    info->initial_value = current;
    info->property_name = property;
    g_object_set_qdata(G_OBJECT(combo), combo_info_quark(), info);
    g_object_weak_ref(G_OBJECT(combo), (GWeakNotify)g_free, info);
    combo_set_current(combo, current);

    GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
    gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "pixbuf", GWY_GRAPH_COMBO_COLUMN_PIXBUF);
    if (labels) {
        renderer = gtk_cell_renderer_text_new();
        gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
        gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text", GWY_GRAPH_COMBO_COLUMN_NAME);
    }

    g_signal_connect(combo, "changed", G_CALLBACK(combo_changed), dialog);

    return combo;
}

static void
combo_set_current(GtkWidget *combo, gint value)
{
    GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
    ComboInfo *info = g_object_get_qdata(G_OBJECT(combo), combo_info_quark());

    if (!model) {
        info->initial_value = value;
        return;
    }

    GtkTreeIter iter;
    if (gtk_tree_model_get_iter_first(model, &iter)) {
        do {
            gint v;
            gtk_tree_model_get(model, &iter, GWY_GRAPH_COMBO_COLUMN_VALUE, &v, -1);
            if (v == value) {
                gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
                break;
            }
        } while (gtk_tree_model_iter_next(model, &iter));
    }
}

static void
refresh(GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->color_selector));
    gwy_null_store_set_model(GWY_NULL_STORE(model), priv->curve_model, NULL);
    gwy_null_store_row_changed(GWY_NULL_STORE(model), 0);
    update_switcher_sensitivity(dialog);

    if (priv->curve_model == NULL)
        return;

    GwyGraphCurveModel *cmodel = priv->curve_model;

    GwyRGBA color;
    gwy_graph_curve_model_get_color(cmodel, &color);
    graph_color_combo_select(GTK_COMBO_BOX(priv->color_selector), &color);
    gwy_color_button_set_color(GWY_COLOR_BUTTON(priv->color_button), &color);

    if (priv->color_dialog) {
        GtkWidget *editor = gwy_color_dialog_get_editor(GWY_COLOR_DIALOG(priv->color_dialog));
        g_signal_handlers_block_by_func(editor, color_edited, dialog);
        gwy_color_editor_set_previous_color(GWY_COLOR_EDITOR(editor), &color);
        gwy_color_editor_set_color(GWY_COLOR_EDITOR(editor), &color);
        g_signal_handlers_unblock_by_func(editor, color_edited, dialog);
    }

    GwyGraphCurveMode mode;
    GwySymbolType point_type;
    GwyLineStyle line_style;
    gdouble point_size, line_width;
    gchar *description;
    g_object_get(cmodel,
                 "mode", &mode,
                 "point-size", &point_size, "line-width", &line_width,
                 "point-type", &point_type, "line-style", &line_style,
                 "description", &description,
                 NULL);
    gwy_enum_combo_box_set_active(GTK_COMBO_BOX(priv->curvetype_menu), mode);
    combo_set_current(priv->pointtype_menu, point_type);
    combo_set_current(priv->linestyle_menu, line_style);
    gtk_adjustment_set_value(GTK_ADJUSTMENT(priv->pointsize), point_size);
    gtk_adjustment_set_value(GTK_ADJUSTMENT(priv->thickness), line_width);
    gwy_sci_text_set_markup(GWY_SCI_TEXT(priv->scitext), description);
}

static void
edit_color(GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (!priv->curve_model)
        return;

    GtkWidget *color_dialog = priv->color_dialog = gwy_color_dialog_new(GTK_WINDOW(dialog), FALSE);
    g_object_add_weak_pointer(G_OBJECT(color_dialog), (gpointer*)&priv->color_dialog);
    gtk_window_set_title(GTK_WINDOW(color_dialog), _("Set Curve Color"));
    GwyRGBA color;
    gwy_graph_curve_model_get_color(priv->curve_model, &color);
    GtkWidget *editor = gwy_color_dialog_get_editor(GWY_COLOR_DIALOG(color_dialog));
    gwy_color_editor_set_color(GWY_COLOR_EDITOR(editor), &color);
    g_signal_connect(editor, "color-changed", G_CALLBACK(color_edited), dialog);
    gtk_widget_show(color_dialog);
}

static void
response(GtkDialog *dialog,
         gint response_id)
{
    GwyGraphCurveDialogPrivate *priv = GWY_GRAPH_CURVE_DIALOG(dialog)->priv;
    if (!priv->curve_model)
        return;

    /* The parent must handle this. */
    if (response_id == RESPONSE_PREV) {
        g_signal_emit(dialog, signals[SGNL_PREVIOUS], 0);
        return;
    }
    if (response_id == RESPONSE_NEXT) {
        g_signal_emit(dialog, signals[SGNL_NEXT], 0);
        return;
    }
}

static void
color_edited(GwyColorEditor *editor, GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (!priv->curve_model)
        return;

    GwyRGBA color;
    gwy_color_editor_get_color(editor, &color);
    color.a = 1.0;
    g_object_set(priv->curve_model, "color", &color, NULL);
    refresh(dialog);
}

static void
color_selected(GtkComboBox *combo,
               GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    gint i = gtk_combo_box_get_active(combo);
    if (!i || !priv->curve_model)
        return;

    const GwyRGBA *color = gwy_graph_get_preset_color(i-1);
    g_object_set(priv->curve_model, "color", color, NULL);
    refresh(dialog);
}

static void
description_changed(GwySciText *scitext, GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (priv->curve_model)
        g_object_set(priv->curve_model, "description", gwy_sci_text_get_markup(GWY_SCI_TEXT(scitext)), NULL);
}

/**
 * gwy_graph_curve_dialog_set_model:
 * @dialog: A graph curve property dialog.
 * @cmodel: Graph curve model to switch to.
 *
 * Switches a graph curve property dialog to a new curve model.
 **/
void
gwy_graph_curve_dialog_set_model(GwyGraphCurveDialog *dialog,
                                 GwyGraphCurveModel *cmodel)
{
    g_return_if_fail(GWY_IS_GRAPH_CURVE_DIALOG(dialog));

    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (!gwy_set_member_object(dialog, cmodel, GWY_TYPE_GRAPH_CURVE_MODEL, &priv->curve_model,
                               "notify", G_CALLBACK(curve_notify), &priv->curve_notify_id, G_CONNECT_SWAPPED,
                               NULL))
        return;

    if (priv->color_dialog && !cmodel) {
        gtk_widget_destroy(priv->color_dialog);
        priv->color_dialog = NULL;
    }
    refresh(dialog);
}

/**
 * gwy_graph_curve_dialog_get_model:
 * @dialog: A graph curve property dialog.
 *
 * Gets the curve model of a graph curve property dialog.
 *
 * Returns: The curve model.
 **/
GwyGraphCurveModel*
gwy_graph_curve_dialog_get_model(GwyGraphCurveDialog *dialog)
{
    g_return_val_if_fail(GWY_IS_GRAPH_CURVE_DIALOG(dialog), NULL);
    return dialog->priv->curve_model;
}

static void
curve_notify(GwyGraphCurveDialog *dialog,
             const GParamSpec *pspec,
             G_GNUC_UNUSED GwyGraphCurveModel *cmodel)
{
    /* TODO Update controls if this is an independent change (not started by us). */
    /* This and not-in-update
    if (cmodel == dialog->priv->curve_model)
        refresh(dialog);
        */
}

static void
curvetype_changed(GtkWidget *combo, GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (!priv->curve_model)
        return;

    GwyGraphCurveMode ctype = gwy_enum_combo_box_get_active(GTK_COMBO_BOX(combo));
    g_object_set(priv->curve_model, "mode", ctype, NULL);
}

static void
thickness_changed(GtkAdjustment *adj, GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (!priv->curve_model)
        return;

    gdouble line_width = gtk_adjustment_get_value(adj);
    g_object_set(priv->curve_model, "line-width", line_width, NULL);
}

static void
pointsize_changed(GtkAdjustment *adj, GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    if (!priv->curve_model)
        return;

    gdouble point_size = gtk_adjustment_get_value(adj);
    g_object_set(priv->curve_model, "point-size", point_size, NULL);
}

static void
render_graph_color(G_GNUC_UNUSED GtkCellLayout *cell_layout,
                   GtkCellRenderer *renderer,
                   GtkTreeModel *model,
                   GtkTreeIter *iter,
                   G_GNUC_UNUSED gpointer user_data)
{
    GwyRGBA color = { 0.0, 0.0, 0.0, 1.0 };
    gint row;

    gtk_tree_model_get(model, iter, 0, &row, -1);
    if (row)
        color = *gwy_graph_get_preset_color(row-1);
    else {
        GwyGraphCurveModel *cmodel = gwy_null_store_get_model(GWY_NULL_STORE(model));
        if (cmodel)
            gwy_graph_curve_model_get_color(cmodel, &color);
    }
    g_object_set(renderer, "color", &color, NULL);
}

static GtkWidget*
graph_color_combo_new(void)
{
    gint width, height;
    gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
    height |= 1;
    width = MAX(width, (gint)(1.618*height)) | 1;

    GwyNullStore *store = gwy_null_store_new(gwy_graph_get_n_preset_colors() + 1);
    GtkWidget *combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
    g_object_unref(store);
    gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(combo), 4);

    GtkCellRenderer *renderer = gwy_cell_renderer_color_new();
    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
    gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer, render_graph_color, NULL, NULL);

    return combo;
}

static void
graph_color_combo_select(GtkComboBox *combo, const GwyRGBA *color)
{

    guint i, n = gwy_graph_get_n_preset_colors();
    for (i = 0; i < n; i++) {
        const GwyRGBA *preset = gwy_graph_get_preset_color(i);
        if (fabs(color->r - preset->r) + fabs(color->g - preset->g) + fabs(color->b - preset->b) < 1e-5)
            break;
    }
    if (i < n)
        gtk_combo_box_set_active(combo, i+1);
    else
        gtk_combo_box_set_active(combo, 0);
}

/**
 * gwy_graph_curve_dialog_set_switching:
 * @dialog: A graph curve property dialog.
 * @prev_possible: %TRUE if there is a previous curve and switching to the previous curve should be allowed.
 * @next_possible: %TRUE if there is a next curve and switching to the next curve should be allowed.
 *
 * Controls whether switching to previous and next curve is allowed in a graph curve properties dialog.
 **/
void
gwy_graph_curve_dialog_set_switching(GwyGraphCurveDialog *dialog,
                                     gboolean prev_possible,
                                     gboolean next_possible)
{
    g_return_if_fail(GWY_IS_GRAPH_CURVE_DIALOG(dialog));
    GwyGraphCurveDialogPrivate *priv = dialog->priv;

    priv->prev_possible = prev_possible;
    priv->next_possible = next_possible;
    update_switcher_sensitivity(dialog);
}

static void
update_switcher_sensitivity(GwyGraphCurveDialog *dialog)
{
    GwyGraphCurveDialogPrivate *priv = dialog->priv;
    gboolean prev_sens = priv->prev_possible && priv->curve_model;
    gboolean next_sens = priv->next_possible && priv->curve_model;

    gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), RESPONSE_PREV, prev_sens);
    gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), RESPONSE_NEXT, next_sens);
}

/**
 * SECTION: graph-curve-dialog
 * @title: GwyGraphCurveDialog
 * @short_description: Modify properties of graph curves
 **/

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