/*
 *  $Id: graph-window.c 30028 2026-06-09 13:45:30Z yeti-dn $
 *  Copyright (C) 2004-2025 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 <string.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

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

#include "libgwyui/graph-window.h"
#include "libgwyui/graph-curves.h"
#include "libgwyui/graph-data.h"
#include "libgwyui/icons.h"
#include "libgwyui/utils.h"
#include "libgwyui/graph-window-measure-dialog.h"

#define ZOOM_FACTOR 1.3195

enum {
    DEFAULT_WIDTH = 550,
    DEFAULT_HEIGHT = 390,
};

struct _GwyGraphWindowPrivate {
    GwyGraphModel *graph_model;

    GtkWidget *notebook;
    GtkWidget *graph;
    GtkWidget *graphdata;
    GtkWidget *curves;

    GtkWidget *measure_dialog;

    GtkWidget *button_measure_points;
    GtkWidget *button_measure_lines;

    GtkWidget *button_zoom_in;
    GtkWidget *button_zoom_to_fit;

    GtkWidget *button_x_log;
    GtkWidget *button_y_log;

    GtkWidget *statusbar;
    GwyGraphStatusType last_status;
    gchar *dataname;

    /* TODO: Connect and handle! */
    gulong notify_title_id;
    gulong notify_model_id;
    gulong xlog_id;
    gulong ylog_id;
};

static void     finalize            (GObject *object);
static void     dispose             (GObject *object);
static void     destroy             (GtkWidget *widget);
static void     model_changed       (GwyGraphWindow *window);
static gboolean key_pressed         (GtkWidget *widget,
                                     GdkEventKey *event);
static gboolean motion_notify       (GwyGraphWindow *window,
                                     GdkEventMotion *event);
static void     measure             (GwyGraphWindow *window);
static void     zoom_in             (GwyGraphWindow *window);
static void     zoom_to_fit         (GwyGraphWindow *window);
static void     toggle_x_logscale   (GwyGraphWindow *window);
static void     toggle_y_logscale   (GwyGraphWindow *window);
static void     zoom_finished       (GwyGraphWindow *window);
static void     resize              (GwyGraphWindow *window,
                                     gint zoomtype);
static void     measure_finished    (GwyGraphWindow *window);
static void     update_title        (GwyGraphWindow *window);
static void     curves_row_activated(GwyGraphWindow *window,
                                     GtkTreePath *path,
                                     GtkTreeViewColumn *column);

static GtkWindowClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyGraphWindow, gwy_graph_window, GTK_TYPE_WINDOW,
                        G_ADD_PRIVATE(GwyGraphWindow))

static void
gwy_graph_window_class_init(GwyGraphWindowClass *klass)
{
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_graph_window_parent_class;

    gobject_class->finalize = finalize;
    gobject_class->dispose = dispose;

    widget_class->destroy = destroy;
    widget_class->key_press_event = key_pressed;
}

static gulong
setup_button(GtkWidget *button,
             const gchar *icon_name, const gchar *tooltip, GtkBox *box,
             const gchar *signal_name, GCallback callback, gpointer cbdata)
{
    gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR));
    gtk_box_pack_start(box, button, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text(button, tooltip);
    return g_signal_connect_swapped(button, signal_name, G_CALLBACK(callback), cbdata);
}

static void
statusbar_size_allocated(GtkWidget *statusbar, G_GNUC_UNUSED GtkAllocation *alloc, gpointer user_data)
{
    /* Clear the strut (so that users do not see it) and disconnect self. */
    gtk_label_set_markup(GTK_LABEL(statusbar), "");
    g_signal_handlers_disconnect_matched(statusbar, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
                                         0, 0, NULL, statusbar_size_allocated, user_data);
}

static void
gwy_graph_window_init(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv;

    priv = window->priv = gwy_graph_window_get_instance_private(window);

    gtk_window_set_default_size(GTK_WINDOW(window), DEFAULT_WIDTH, DEFAULT_HEIGHT);

    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add(GTK_CONTAINER(GTK_WINDOW(window)), vbox);

    priv->notebook = gtk_notebook_new();
    GtkNotebook *notebook = GTK_NOTEBOOK(priv->notebook);

    priv->graph = gwy_graph_new(NULL);
    GwyGraph *graph = GWY_GRAPH(priv->graph);
    priv->last_status = gwy_graph_get_status(graph);

    gtk_notebook_append_page(notebook, priv->graph, gtk_label_new(_("Graph")));

    GtkScrolledWindow *swindow_data = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
    priv->graphdata = gwy_graph_data_new();
    gtk_container_add(GTK_CONTAINER(swindow_data), priv->graphdata);
    gtk_notebook_append_page(notebook, GTK_WIDGET(swindow_data), gtk_label_new(_("Data")));

    GtkScrolledWindow *swindow_curves = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
    gtk_scrolled_window_set_policy(swindow_curves, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    priv->curves = gwy_graph_curves_new();
    g_signal_connect_swapped(priv->curves, "row-activated", G_CALLBACK(curves_row_activated), window);

    gtk_container_add(GTK_CONTAINER(swindow_curves), priv->curves);
    gtk_notebook_append_page(notebook, GTK_WIDGET(swindow_curves), gtk_label_new(_("Curves")));

    gtk_container_add(GTK_CONTAINER(vbox), priv->notebook);

    /* Add buttons */
    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

    GtkWidget *button;

    button = priv->button_measure_points = gtk_toggle_button_new();
    setup_button(button, GWY_ICON_GRAPH_MEASURE, _("Measure distances in graph"), GTK_BOX(hbox),
                 "toggled", G_CALLBACK(measure), window);
    button = priv->button_zoom_in = gtk_toggle_button_new();
    setup_button(button, GWY_ICON_GRAPH_ZOOM_IN,  _("Zoom in by mouse selection"), GTK_BOX(hbox),
                 "toggled", G_CALLBACK(zoom_in), window);
    button = priv->button_zoom_to_fit = gtk_button_new();
    setup_button(button, GWY_ICON_GRAPH_ZOOM_FIT, _("Zoom out to full curve"), GTK_BOX(hbox),
                 "clicked", G_CALLBACK(zoom_to_fit), window);

    button = priv->button_x_log = gtk_toggle_button_new();
    priv->xlog_id = setup_button(button, GWY_ICON_LOGSCALE_HORIZONTAL, _("Toggle logarithmic x axis"), GTK_BOX(hbox),
                                 "clicked", G_CALLBACK(toggle_x_logscale), window);
    button = priv->button_y_log = gtk_toggle_button_new();
    priv->ylog_id = setup_button(button, GWY_ICON_LOGSCALE_VERTICAL, _("Toggle logarithmic y axis"), GTK_BOX(hbox),
                                 "clicked", G_CALLBACK(toggle_y_logscale), window);

    priv->statusbar = gtk_label_new(NULL);
    gtk_widget_set_hexpand(priv->statusbar, TRUE);
    gtk_label_set_xalign(GTK_LABEL(priv->statusbar), 0.0);
    /* TODO GTK3 at some point we need to get the required height and do not allow it to shrink. */
    gtk_label_set_markup(GTK_LABEL(priv->statusbar), "(|)<sup>(0)</sup><sub>(0)</sub>");
    gtk_label_set_ellipsize(GTK_LABEL(priv->statusbar), PANGO_ELLIPSIZE_END);
    gtk_box_pack_start(GTK_BOX(hbox), priv->statusbar, TRUE, TRUE, 4);
    g_signal_connect(priv->statusbar, "size-allocate", G_CALLBACK(statusbar_size_allocated), priv->statusbar);

    GwyGraphArea *area = GWY_GRAPH_AREA(gwy_graph_get_area(graph));
    g_signal_connect_swapped(gwy_graph_get_area(graph), "motion-notify-event", G_CALLBACK(motion_notify), window);

    priv->measure_dialog = gwy_graph_window_measure_dialog_new(graph);
    g_signal_connect_swapped(priv->measure_dialog, "hide", G_CALLBACK(measure_finished), window);

    GwySelection *selection = gwy_graph_area_get_selection(area, GWY_GRAPH_STATUS_ZOOM);
    g_signal_connect_swapped(selection, "finished", G_CALLBACK(zoom_finished), window);

    g_signal_connect_swapped(graph, "notify::model", G_CALLBACK(model_changed), window);
    model_changed(window);
}

static void
finalize(GObject *object)
{
    GwyGraphWindowPrivate *priv = GWY_GRAPH_WINDOW(object)->priv;

    GWY_FREE(priv->dataname);

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

static void
dispose(GObject *object)
{
    GwyGraphWindowPrivate *priv = GWY_GRAPH_WINDOW(object)->priv;

    g_clear_signal_handler(&priv->notify_title_id, priv->graph_model);
    g_clear_signal_handler(&priv->notify_model_id, priv->graph);

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

static void
destroy(GtkWidget *widget)
{
    GwyGraphWindowPrivate *priv = GWY_GRAPH_WINDOW(widget)->priv;

    if (priv->measure_dialog) {
        gtk_widget_destroy(GTK_WIDGET(priv->measure_dialog));
        priv->measure_dialog = NULL;
    }

    GTK_WIDGET_CLASS(parent_class)->destroy(widget);
}

/**
 * gwy_graph_window_new:
 * @gmodel: A graph model to show.
 *
 * Creates a new window showing a graph.
 *
 * Returns: A newly created graph window as #GtkWidget.
 **/
GtkWidget*
gwy_graph_window_new(GwyGraphModel *gmodel)
{
    GwyGraphWindow *window = g_object_new(GWY_TYPE_GRAPH_WINDOW, NULL);
    GwyGraphWindowPrivate *priv = window->priv;

    gwy_graph_set_model(GWY_GRAPH(priv->graph), gmodel);
    update_title(window);
    model_changed(window);

    return GTK_WIDGET(window);
}

/**
 * gwy_graph_window_get_graph:
 * @window: A graph window.
 *
 * Gets the graph widget a graph window currently shows.
 *
 * Returns: The currently shown #GwyGraph.
 **/
GtkWidget*
gwy_graph_window_get_graph(GwyGraphWindow *window)
{
    g_return_val_if_fail(GWY_IS_GRAPH_WINDOW(window), NULL);
    return window->priv->graph;
}

/**
 * gwy_graph_window_get_graph_data:
 * @window: A graph window.
 *
 * Gets the graph graphdata widget of a graph window.
 *
 * Returns: The #GwyGraphData widget of this graph window.  Its model and column layout must be considered private.
 **/
GtkWidget*
gwy_graph_window_get_graph_data(GwyGraphWindow *window)
{
    g_return_val_if_fail(GWY_IS_GRAPH_WINDOW(window), NULL);

    return window->priv->graphdata;
}

/**
 * gwy_graph_window_get_graph_curves:
 * @window: A graph window.
 *
 * Gets the graph curves widget of a graph window.
 *
 * Returns: The #GwyGraphCurves widget of this graph window.  Its model and column layout must be considered private.
 **/
GtkWidget*
gwy_graph_window_get_graph_curves(GwyGraphWindow *window)
{
    g_return_val_if_fail(GWY_IS_GRAPH_WINDOW(window), NULL);
    return window->priv->curves;
}

/**
 * gwy_graph_window_set_data_name:
 * @window: A graph window.
 * @name: New data name.
 *
 * Sets the data name of a graph window.
 *
 * The data name is used in the window's title.
 **/
void
gwy_graph_window_set_data_name(GwyGraphWindow *window,
                               const gchar *name)
{
    g_return_if_fail(GWY_IS_GRAPH_WINDOW(window));

    GwyGraphWindowPrivate *priv = window->priv;
    if (!gwy_assign_string(&priv->dataname, name))
        return;
    update_title(window);
}

static void
update_logscale_button(GtkWidget *button,
                       GwyGraphModel *gmodel,
                       const gchar *prop_name,
                       gulong hid)
{
    gboolean logscale = FALSE;

    if (gmodel)
        g_object_get(gmodel, prop_name, &logscale, NULL);
    g_signal_handler_block(button, hid);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), logscale);
    g_signal_handler_unblock(button, hid);
}

static void
model_changed(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;
    GwyGraphModel *gmodel = gwy_graph_get_model(GWY_GRAPH(priv->graph));

    if (!gwy_set_member_object(window, gmodel, GWY_TYPE_GRAPH_MODEL, &priv->graph_model,
                               "notify::title", update_title, &priv->notify_title_id, G_CONNECT_SWAPPED,
                               NULL))
        return;

    gwy_graph_data_set_model(GWY_GRAPH_DATA(priv->graphdata), gmodel);
    gwy_graph_curves_set_model(GWY_GRAPH_CURVES(priv->curves), gmodel);

    update_logscale_button(priv->button_x_log, gmodel, "x-logarithmic", priv->xlog_id);
    update_logscale_button(priv->button_y_log, gmodel, "y-logarithmic", priv->ylog_id);
    update_title(window);
}

/* FIXME: Is this even useful? Maybe re-enable when we have Cairo export… */
#if 0
static void
copy_to_clipboard(GwyGraphWindow *window)
{
    GtkClipboard *clipboard;
    GdkDisplay *display;
    GdkPixbuf *pixbuf;
    GdkAtom atom;

    display = gtk_widget_get_display(GTK_WIDGET(window));
    atom = gdk_atom_intern("CLIPBOARD", FALSE);
    clipboard = gtk_clipboard_get_for_display(display, atom);
    pixbuf = gwy_graph_export_pixmap(GWY_GRAPH(priv->graph),
                                     FALSE, TRUE, TRUE);
    gtk_clipboard_set_image(clipboard, pixbuf);
    g_object_unref(pixbuf);
}
#endif

static gboolean
key_pressed(GtkWidget *widget,
            GdkEventKey *event)
{
    enum {
        important_mods = GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_RELEASE_MASK
    };

    GwyGraphWindow *window = GWY_GRAPH_WINDOW(widget);
    guint state, key;

    gwy_debug("state = %u, keyval = %u", event->state, event->keyval);
    state = event->state & important_mods;
    key = event->keyval;
    if (!state && (key == GDK_KEY_minus || key == GDK_KEY_KP_Subtract)) {
        resize(window, -1);
        return TRUE;
    }
    else if (!state && (key == GDK_KEY_equal || key == GDK_KEY_KP_Equal
                        || key == GDK_KEY_plus || key == GDK_KEY_KP_Add)) {
        resize(window, 1);
        return TRUE;
    }
    else if (!state && (key == GDK_KEY_Z || key == GDK_KEY_z || key == GDK_KEY_KP_Divide)) {
        resize(window, 0);
        return TRUE;
    }
#if 0
    else if (state == GDK_CONTROL_MASK && (key == GDK_KEY_C || key == GDK_KEY_c)) {
        copy_to_clipboard(window);
        return TRUE;
    }
#endif

    if (GTK_WIDGET_CLASS(parent_class)->key_press_event)
        return GTK_WIDGET_CLASS(parent_class)->key_press_event(widget, event);
    return FALSE;
}

static void
format_coordinate(GwyGraphAxis *axis, gdouble coord, GString *str)
{
    if (!gwy_graph_axis_is_logarithmic(axis)) {
        g_string_append_printf(str, "%.4f %s",
                               coord/gwy_graph_axis_get_magnification(axis),
                               gwy_graph_axis_get_magnification_string(axis));
        return;
    }

    guint pfxlen = str->len;
    g_string_append_printf(str, "%.5g", coord);
    gchar *p;
    if ((p = strchr(str->str + pfxlen, 'e'))) {
        guint pos = p - str->str;
        g_string_erase(str, pos, 1);
        g_string_insert(str, pos, "×10<sup>");
        pos += strlen("×10<sup>");
        if (str->str[pos] == '-')
            pos++;
        while ((str->str[pos] == '0' || str->str[pos] == '+') && g_ascii_isdigit(str->str[pos+1]))
            g_string_erase(str, pos, 1);
        while (g_ascii_isdigit(str->str[pos]))
            pos++;
        g_string_insert(str, pos, "</sup>");
    }

    GwyUnit *unit = gwy_graph_axis_get_unit(axis);
    gchar *u = gwy_unit_get_string(unit, GWY_UNIT_FORMAT_VFMARKUP);
    if (*u) {
        g_string_append_c(str, ' ');
        g_string_append(str, u);
    }
    g_free(u);
}

static gboolean
motion_notify(GwyGraphWindow *window, GdkEventMotion *event)
{
    GwyGraphWindowPrivate *priv = window->priv;
    GwyGraph *graph = GWY_GRAPH(priv->graph);

    gdouble x, y;
    gwy_graph_area_coords_widget_to_real(GWY_GRAPH_AREA(gwy_graph_get_area(graph)), event->x, event->y, &x, &y);

    GString *str = g_string_new(NULL);
    format_coordinate(GWY_GRAPH_AXIS(gwy_graph_get_axis(graph, GTK_POS_BOTTOM)), x, str);
    g_string_append(str, ", ");
    format_coordinate(GWY_GRAPH_AXIS(gwy_graph_get_axis(graph, GTK_POS_LEFT)), y, str);

    gtk_label_set_markup(GTK_LABEL(priv->statusbar), str->str);
    g_string_free(str, TRUE);

    return FALSE;
}

static void
measure(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;

    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->button_measure_points))) {
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->button_zoom_in), FALSE);
        gtk_widget_show_all(priv->measure_dialog);
    }
    else
        gtk_dialog_response(GTK_DIALOG(priv->measure_dialog), GTK_RESPONSE_CLOSE);
}

static void
measure_finished(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->button_measure_points), FALSE);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->button_zoom_in), FALSE);
    gwy_graph_set_status(GWY_GRAPH(priv->graph), GWY_GRAPH_STATUS_PLAIN);
    gtk_widget_queue_draw(priv->graph);
}

static void
zoom_in(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;
    GwyGraph *graph = GWY_GRAPH(priv->graph);

    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->button_zoom_in))) {
        priv->last_status = gwy_graph_get_status(graph);
        gwy_graph_set_status(graph, GWY_GRAPH_STATUS_ZOOM);
    }
    else
        gwy_graph_set_status(graph, priv->last_status);
}

static void
zoom_to_fit(GwyGraphWindow *window)
{
    g_object_set(window->priv->graph_model,
                 "x-min-set", FALSE, "x-max-set", FALSE, "y-min-set", FALSE, "y-max-set", FALSE,
                 NULL);
}

static void
zoom_finished(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->button_zoom_in), FALSE);
    gwy_graph_set_status(GWY_GRAPH(priv->graph), priv->last_status);
}

static void
resize(GwyGraphWindow *window, gint zoomtype)
{
    GtkWidget *widget = GTK_WIDGET(window);
    gint w = DEFAULT_HEIGHT, h = DEFAULT_HEIGHT;

    gtk_window_get_size(GTK_WINDOW(window), &w, &h);
    if (zoomtype > 0) {
        gint scrwidth, scrheight;
        gwy_get_screen_size(widget, &scrwidth, &scrheight);

        w = GWY_ROUND(ZOOM_FACTOR*w);
        h = GWY_ROUND(ZOOM_FACTOR*h);
        if (w > 0.9*scrwidth || h > 0.9*scrheight) {
            if ((gdouble)w/scrwidth > (gdouble)h/scrheight) {
                h = GWY_ROUND(0.9*scrwidth*h/w);
                w = GWY_ROUND(0.9*scrwidth);
            }
            else {
                w = GWY_ROUND(0.9*scrheight*w/h);
                h = GWY_ROUND(0.9*scrheight);
            }
        }
    }
    else if (zoomtype < 0) {
        GtkAllocation allocation;
        gtk_widget_get_allocation(widget, &allocation);

        w = GWY_ROUND(w/ZOOM_FACTOR);
        h = GWY_ROUND(h/ZOOM_FACTOR);
        if (w < allocation.width || h < allocation.height) {
            if ((gdouble)w/allocation.width < (gdouble)h/allocation.height) {
                h = GWY_ROUND((gdouble)allocation.width*h/w);
                w = allocation.width;
            }
            else {
                w = GWY_ROUND((gdouble)allocation.height*w/h);
                h = allocation.height;
            }
        }
    }

    gtk_window_resize(GTK_WINDOW(window), w, h);
}

static void
toggle_x_logscale(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;
    if (!priv->graph_model)
        return;
    gboolean state;
    g_object_get(priv->graph_model, "x-logarithmic", &state, NULL);
    g_object_set(priv->graph_model, "x-logarithmic", !state, NULL);
}

static void
toggle_y_logscale(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;
    if (!priv->graph_model)
        return;
    gboolean state;
    g_object_get(priv->graph_model, "y-logarithmic", &state, NULL);
    g_object_set(priv->graph_model, "y-logarithmic", !state, NULL);
}

static void
update_title(GwyGraphWindow *window)
{
    GwyGraphWindowPrivate *priv = window->priv;
    gchar *graphtitle;
    if (!priv->graph_model)
        graphtitle = g_strdup(_("Empty"));
    else
        g_object_get(priv->graph_model, "title", &graphtitle, NULL);

    const gchar *dataname = priv->dataname && *priv->dataname ? priv->dataname : _("Untitled");
    gchar *title = g_strdup_printf("%s [%s] (%s)", dataname, graphtitle, g_get_application_name());
    gtk_window_set_title(GTK_WINDOW(window), title);
    g_free(title);
}

G_GNUC_UNUSED
static void
curves_row_activated(GwyGraphWindow *window,
                     GtkTreePath *path,
                     G_GNUC_UNUSED GtkTreeViewColumn *column)
{
    GwyGraphWindowPrivate *priv = window->priv;
    GwyGraphArea *area = GWY_GRAPH_AREA(gwy_graph_get_area(GWY_GRAPH(priv->graph)));
    const gint *indices = gtk_tree_path_get_indices(path);
    gwy_graph_area_edit_curve(area, indices[0]);
}

/**
 * SECTION: graph-window
 * @title: GwyGraphWindow
 * @short_description: Graph display window
 *
 * #GwyGraphWindow encapsulates a #GwyGraph together with other controls and graph data view. You can create a graph
 * window for a graph with gwy_graph_window_new().
 **/

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