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

#include "libgwyddion/macros.h"
#include "libgwyui/utils.h"

#include "libgwyapp/gwyapp.h"
#include "libgwyapp/internal.h"

struct _GwyAppGraphWindowPrivate {
    GtkWidget *menubar;
    GwySensitivityGroup *sens_group;

    GtkWidget *meta_widget;
    gulong meta_destroyed_id;

    GObject *object;
    gulong object_notify_id;
    gulong object_changed_id;

    GtkWidget *log_widget;
    gulong log_destroyed_id;

    // When showing something that is not naturally a graph (spectra)
    GwyGraphModel *gmodel;

    GwyFile *file;
    gulong item_changed_id;

    GwyDataKind data_kind;
    gint id;
    const gchar *accel_root;
};

static void         app_data_window_init          (GwyAppDataWindowInterface *iface);
static GwyFile*     app_data_window_get_file      (GwyAppDataWindow *window);
static GwyDataKind  app_data_window_get_data_kind (GwyAppDataWindow *window);
static gint         app_data_window_get_id        (GwyAppDataWindow *window);
static const gchar* app_data_window_get_accel_root(GwyAppDataWindow *window);
static void         finalize                      (GObject *object);
static void         dispose                       (GObject *object);
static gboolean     configured                    (GtkWidget *widget,
                                                   GdkEventConfigure *event);
static void         size_allocated                (GtkWidget *widget,
                                                   GdkRectangle *allocation);
static void         save_size                     (GwyAppGraphWindow *window,
                                                   const GdkRectangle *allocation);
static void         add_menubar                   (GwyAppGraphWindow *window,
                                                   GtkBox *vbox);
static GtkWidget*   create_view_menu              (GwyAppGraphWindow *window,
                                                   GtkAccelGroup *accel_group);
static void         show_meta_browser             (GwyAppGraphWindow *window);
static void         meta_destroyed                (GwyAppGraphWindow *window);
static void         show_log_browser              (GwyAppGraphWindow *window);
static void         log_destroyed                 (GwyAppGraphWindow *window);
static void         item_changed                  (GwyDict *container,
                                                   GQuark key,
                                                   GwyAppGraphWindow *window);
static void         object_notify                 (GObject *object,
                                                   const GParamSpec *pspec,
                                                   GwyAppGraphWindow *window);
static void         object_changed                (GObject *object,
                                                   GwyAppGraphWindow *window);
static void         update_sensitivity            (GwyAppGraphWindow *window);
static void         initial_setup                 (GwyAppGraphWindow *window);
static void         update_window_title           (GwyAppGraphWindow *window);

static GtkWindowClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyAppGraphWindow, gwy_app_graph_window, GWY_TYPE_GRAPH_WINDOW,
                        G_ADD_PRIVATE(GwyAppGraphWindow)
                        G_IMPLEMENT_INTERFACE(GWY_TYPE_APP_DATA_WINDOW, app_data_window_init))

static void
app_data_window_init(GwyAppDataWindowInterface *iface)
{
    iface->get_file       = app_data_window_get_file;
    iface->get_data_kind  = app_data_window_get_data_kind;
    iface->get_id         = app_data_window_get_id;
    iface->get_accel_root = app_data_window_get_accel_root;
}

static void
gwy_app_graph_window_class_init(GwyAppGraphWindowClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

    parent_class = gwy_app_graph_window_parent_class;

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

    widget_class->configure_event = configured;
    widget_class->size_allocate = size_allocated;
    /* TODO: Various button/key press events, popup-menu event (which we might not need if we have a menubar). */
}

static void
dispose(GObject *object)
{
    GwyAppGraphWindow *window = GWY_APP_GRAPH_WINDOW(object);
    GwyAppGraphWindowPrivate *priv = window->priv;

    if (priv->gmodel) {
        gwy_set_member_object(window, NULL, GWY_TYPE_GRAPH_MODEL, &priv->object,
                              "data-changed", G_CALLBACK(object_changed), &priv->object_changed_id, 0,
                              NULL);
    }
    else {
        gwy_set_member_object(window, NULL, GWY_TYPE_GRAPH_MODEL, &priv->object,
                              "notify::n-curves", G_CALLBACK(object_notify), &priv->object_notify_id, 0,
                              NULL);
    }
    g_clear_signal_handler(&priv->item_changed_id, priv->file);

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

static void
finalize(GObject *object)
{
    GwyAppGraphWindow *window = GWY_APP_GRAPH_WINDOW(object);
    GwyAppGraphWindowPrivate *priv = window->priv;

    g_clear_object(&priv->sens_group);
    g_clear_object(&priv->gmodel);

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

static void
gwy_app_graph_window_init(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv;

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

    priv->sens_group = gwy_sensitivity_group_new();

    GtkWindow *gtkwindow = GTK_WINDOW(window);
    gtk_window_add_accel_group(gtkwindow, gwy_app_main_accel_group());
    GtkAccelGroup *accel_group = gwy_data_browser_accel_group();
    if (accel_group)
        gtk_window_add_accel_group(gtkwindow, accel_group);

    GtkWidget *curves = gwy_graph_window_get_graph_curves(GWY_GRAPH_WINDOW(window));
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(curves));
    gwy_app_set_tree_model_kind(model, "graph-curves");
}

/**
 * gwy_app_graph_window_new:
 * @file: (transfer none): A data file container.
 * @data_kind: Type of the data object.
 * @id: Data item id.
 *
 * Creates a new app image data window.
 *
 * The arguments must correspond to a valid data object in @file which can be visualised as a graph. Possible data
 * kinds include @GWY_FILE_GRAPH and @GWY_FILE_SPECTRA.
 *
 * Use #GwyAppDataWindow methods to query the parameters later.
 *
 * Returns: (transfer full) (constructor): A newly created window.
 **/
GtkWindow*
gwy_app_graph_window_new(GwyFile *file,
                         GwyDataKind data_kind,
                         gint id)
{
    static const GwyEnum help_sections[] = {
        { "graph-windows",        GWY_FILE_GRAPH,   },
        { "single-point-spectra", GWY_FILE_SPECTRA, },
    };
    static const GwyEnum menu_prefixes[] = {
        { "<graph>", GWY_FILE_GRAPH,   },
        { "<sps>",   GWY_FILE_SPECTRA, },
    };

    GwyAppGraphWindow *window = g_object_new(GWY_TYPE_APP_GRAPH_WINDOW, NULL);
    GwyAppGraphWindowPrivate *priv = window->priv;

    g_assert(GWY_IS_FILE(file));
    g_assert(data_kind == GWY_FILE_GRAPH || data_kind == GWY_FILE_SPECTRA);
    g_assert(id >= 0);
    priv->file = file;
    priv->data_kind = data_kind;
    priv->id = id;
    priv->accel_root = gwy_enum_to_string(data_kind, menu_prefixes, G_N_ELEMENTS(menu_prefixes));
    g_assert(priv->accel_root);

    if (data_kind == GWY_FILE_SPECTRA)
        priv->gmodel = gwy_graph_model_new();

    const gchar *helploc = gwy_enum_to_string(data_kind, help_sections, G_N_ELEMENTS(help_sections));
    if (helploc)
        gwy_help_add_to_window(GTK_WINDOW(window), helploc, NULL, GWY_HELP_DEFAULT);

    GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(window)));
    g_assert(GTK_IS_BOX(vbox));
    add_menubar(window, vbox);

    /* TODO: There are various popups and stuff. */

    initial_setup(window);
    priv->item_changed_id = g_signal_connect(file, "item-changed", G_CALLBACK(item_changed), window);

    return (GtkWindow*)window;
}

static void
item_changed(GwyDict *container, GQuark key, GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    GwyFileKeyParsed parsed;

    const gchar *strkey = g_quark_to_string(key);
    gwy_debug("<%s>", strkey);
    gboolean parsed_ok = gwy_parse_file_key(strkey, &parsed);
    g_return_if_fail(parsed_ok);

    GwyDataKind data_kind = parsed.data_kind;
    gint id = parsed.id;
    GwyFilePiece piece = parsed.piece;
    if (data_kind == GWY_FILE_NONE && piece == GWY_FILE_PIECE_FILENAME) {
        update_window_title(window);
        return;
    }

    /* FIXME: This is simplistic. */
    if (data_kind != priv->data_kind || id != priv->id) {
        return;
    }

    GwyFile *file = GWY_FILE(container);
    GwyGraphWindow *datawindow = GWY_GRAPH_WINDOW(window);
    GwyGraph *graph = GWY_GRAPH(gwy_graph_window_get_graph(datawindow));
    if (data_kind == GWY_FILE_GRAPH && piece == GWY_FILE_PIECE_NONE) {
        GwyGraphModel *gmodel = gwy_file_get_graph(file, id);
        gwy_graph_set_model(graph, gmodel);
        gwy_set_member_object(window, gmodel, GWY_TYPE_GRAPH_MODEL, &priv->object,
                              "notify::n-curves", G_CALLBACK(object_notify), &priv->object_notify_id, 0,
                              NULL);

        GwyMenuSensFlags sensflags = GWY_MENU_FLAG_GRAPH;
        gwy_sensitivity_group_set_state(priv->sens_group, sensflags, priv->object ? sensflags : 0);
    }
    else if (data_kind == GWY_FILE_SPECTRA && piece == GWY_FILE_PIECE_NONE) {
        GwySpectra *spectra = gwy_file_get_spectra(file, id);
        gwy_set_member_object(window, spectra, GWY_TYPE_SPECTRA, &priv->object,
                              "data-changed", G_CALLBACK(object_changed), &priv->object_changed_id, 0,
                              NULL);
        _gwy_app_fill_spectra_graph(spectra, priv->gmodel);
        gwy_graph_set_model(graph, priv->gmodel);

        GwyMenuSensFlags sensflags = GWY_MENU_FLAG_SPECTRA;
        gwy_sensitivity_group_set_state(priv->sens_group, sensflags, priv->object ? sensflags : 0);
    }
}

static void
object_notify(G_GNUC_UNUSED GObject *object,
              G_GNUC_UNUSED const GParamSpec *pspec,
              GwyAppGraphWindow *window)
{
    update_sensitivity(window);
}

static void
object_changed(G_GNUC_UNUSED GObject *object,
               GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    if (priv->data_kind == GWY_FILE_SPECTRA)
        _gwy_app_fill_spectra_graph(priv->object ? GWY_SPECTRA(priv->object) : NULL, priv->gmodel);
}

static void
update_sensitivity(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    if (priv->data_kind == GWY_FILE_GRAPH) {
        GwyMenuSensFlags sensflags = GWY_MENU_FLAG_GRAPH_CURVE;
        GObject *object = priv->object;
        gint ncurves = object ? gwy_graph_model_get_n_curves(GWY_GRAPH_MODEL(object)) : 0;
        gwy_sensitivity_group_set_state(priv->sens_group, sensflags, ncurves ? sensflags : 0);
    }
}

static void
initial_setup(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    GwyDataKind data_kind = priv->data_kind;
    gint id = priv->id;
    GwyFileKeyParsed parsed = { .data_kind = data_kind, .id = id, .piece = GWY_FILE_PIECE_NONE, .suffix = NULL };
    GwyDict *container = GWY_DICT(priv->file);

    item_changed(container, gwy_form_file_key(&parsed), window);
    parsed.piece = GWY_FILE_PIECE_VIEW;
    update_window_title(window);
    update_sensitivity(window);
    _gwy_restore_window_size(GTK_WINDOW(window), container, gwy_form_string_file_key(&parsed));
}

static void
add_menubar(GwyAppGraphWindow *window, GtkBox *vbox)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    GwyDataKind data_kind = priv->data_kind;
    GtkWidget *funcmenu = NULL, *item;
    const gchar *label = NULL;
    if (data_kind == GWY_FILE_GRAPH) {
        funcmenu = gwy_app_graph_menu(priv->sens_group);
        label = _("_Graph");
    }
    // TODO: Once we have some spectra functions, add their menu.

    GtkAccelGroup *accel_group = NULL;
    if (funcmenu) {
        accel_group = gtk_menu_get_accel_group(GTK_MENU(funcmenu));
        if (accel_group)
            gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
    }

    priv->menubar = gtk_menu_bar_new();
    gtk_box_pack_start(vbox, priv->menubar, FALSE, FALSE, 0);
    gtk_box_reorder_child(vbox, priv->menubar, 0);

    if (funcmenu) {
        item = gtk_menu_item_new_with_mnemonic(label);
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), funcmenu);
        gtk_menu_shell_append(GTK_MENU_SHELL(priv->menubar), item);
    }

    if (funcmenu) {
        GtkWidget *recentmenu = gwy_app_create_recent_func_menu(GWY_APP_DATA_WINDOW(window),
                                                                accel_group, priv->sens_group);
        item = gtk_menu_item_new_with_mnemonic(("_Recent"));
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), recentmenu);
        gtk_menu_shell_append(GTK_MENU_SHELL(priv->menubar), item);
    }

    item = gtk_menu_item_new_with_mnemonic(("Vie_w"));
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), create_view_menu(window, accel_group));
    gtk_menu_shell_append(GTK_MENU_SHELL(priv->menubar), item);
}

static GtkWidget*
append_menu_item(GwyAppGraphWindow *window, GtkMenuShell *menushell,
                 const gchar *label, const gchar *icon_name, gboolean is_check,
                 const gchar *accel_path_tail,
                 GCallback callback)
{
    g_assert(!icon_name || !is_check);
    GwyAppGraphWindowPrivate *priv = window->priv;
    GtkWidget *item;
    if (icon_name)
        item = gwy_create_image_menu_item(label, icon_name, FALSE);
    else if (is_check)
        item = gtk_check_menu_item_new_with_mnemonic(label);
    else
        item = gtk_menu_item_new_with_mnemonic(label);

    if (accel_path_tail) {
        gchar *path = g_strconcat(priv->accel_root, accel_path_tail, NULL);
        gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), path);
        g_free(path);
    }
    gtk_menu_shell_append(menushell, item);
    if (callback)
        g_signal_connect_swapped(item, "activate", callback, window);

    return item;
}

/* FIXME: This should probably be a small toolbar, which could include the ‘Change Preview’ functions for volume
 * and curve map data. */
static GtkWidget*
create_view_menu(GwyAppGraphWindow *window, GtkAccelGroup *accel_group)
{
    GtkWidget *menu = gtk_menu_new();
    GtkMenuShell *shell = GTK_MENU_SHELL(menu);
    if (accel_group)
        gtk_menu_set_accel_group(GTK_MENU(menu), accel_group);

    GtkWidget *item;

    item = append_menu_item(window, shell, _("_Metadata..."), NULL, FALSE,
                            "/View/Metadata", G_CALLBACK(show_meta_browser));
    const gchar *path = gtk_menu_item_get_accel_path(GTK_MENU_ITEM(item));
    gtk_accel_map_add_entry(path, GDK_KEY_B, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
    gtk_accel_map_lock_path(path);

    append_menu_item(window, shell, _("_Log..."), NULL, FALSE, "/View/Log", G_CALLBACK(show_log_browser));

    return menu;
}

static void
show_curve(GwyGraphModel *gmodel,
           GwySpectra *spectra,
           gint id)
{
    GwyLine *line = gwy_spectra_get_spectrum(spectra, id);
    guint n = gwy_graph_model_get_n_curves(gmodel);
    GwyGraphCurveModel *gcmodel = gwy_graph_curve_model_new();
    gchar *desc = g_strdup_printf("%s %d", gwy_spectra_get_title(spectra), id + 1);
    const GwyRGBA *rgba = gwy_graph_get_preset_color(n);
    g_object_set(gcmodel,
                 "mode", GWY_GRAPH_CURVE_LINE,
                 "description", desc,
                 "color", rgba,
                 NULL);
    g_free(desc);
    gwy_graph_curve_model_set_data_from_line(gcmodel, line, 0, 0);
    gwy_graph_model_add_curve(gmodel, gcmodel);
    g_object_unref(gcmodel);

    if (!n)
        gwy_graph_model_set_units_from_line(gmodel, line);
}

void
_gwy_app_fill_spectra_graph(GwySpectra *spectra, GwyGraphModel *gmodel)
{
    gwy_graph_model_remove_all_curves(gmodel);
    if (!spectra)
        return;

    g_return_if_fail(GWY_IS_SPECTRA(spectra));

    g_object_set(gmodel, "title", gwy_spectra_get_title(spectra), NULL);

    const gchar *label;
    if (!(label = gwy_spectra_get_spectrum_x_label(spectra)))
        label = "x";
    gwy_graph_model_set_axis_label(gmodel, GTK_POS_BOTTOM, label);

    if (!(label = gwy_spectra_get_spectrum_y_label(spectra)))
        label = "y";
    gwy_graph_model_set_axis_label(gmodel, GTK_POS_LEFT, label);

    guint nspec = gwy_spectra_get_n_spectra(spectra);
    gboolean anysel = FALSE;
    for (guint i = 0; i < nspec; i++) {
        if (gwy_spectra_get_spectrum_selected(spectra, i)) {
            anysel = TRUE;
            break;
        }
    }

    for (guint i = 0; i < nspec; i++) {
        if (!anysel || gwy_spectra_get_spectrum_selected(spectra, i))
            show_curve(gmodel, spectra, i);
    }
}

static void
show_meta_browser(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    if (!priv->meta_widget) {
        priv->meta_widget = gwy_meta_browser_new(priv->file, priv->data_kind, priv->id);
        priv->meta_destroyed_id = g_signal_connect_swapped(priv->meta_widget, "destroy",
                                                           G_CALLBACK(meta_destroyed), window);
    }
    gtk_window_present(GTK_WINDOW(priv->meta_widget));
}

static void
meta_destroyed(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    g_clear_signal_handler(&priv->meta_destroyed_id, priv->meta_widget);
    priv->meta_widget = NULL;
}

static void
show_log_browser(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    if (!priv->log_widget) {
        priv->log_widget = gwy_log_browser_new(priv->file, priv->data_kind, priv->id);
        priv->log_destroyed_id = g_signal_connect_swapped(priv->log_widget, "destroy",
                                                          G_CALLBACK(log_destroyed), window);
    }
    gtk_window_present(GTK_WINDOW(priv->log_widget));
}

static void
log_destroyed(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    g_clear_signal_handler(&priv->log_destroyed_id, priv->log_widget);
    priv->log_widget = NULL;
}

static void
update_window_title(GwyAppGraphWindow *window)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    gchar *basename;
    const gchar *filename;
    if (gwy_dict_gis_string(GWY_DICT(priv->file), gwy_file_key_filename(), &filename))
        basename = g_path_get_basename(filename);
    else
        basename = g_strdup(_("Untitled"));

    gwy_graph_window_set_data_name(GWY_GRAPH_WINDOW(window), basename);
    g_free(basename);
}

static gboolean
configured(GtkWidget *widget, GdkEventConfigure *event)
{
    GdkRectangle allocation = { .x = event->x, .y = event->y, .width = event->width, .height = event->height };
    save_size(GWY_APP_GRAPH_WINDOW(widget), &allocation);
    return GTK_WIDGET_CLASS(parent_class)->configure_event(widget, event);
}

static void
size_allocated(GtkWidget *widget, GdkRectangle *allocation)
{
    save_size(GWY_APP_GRAPH_WINDOW(widget), allocation);
    GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
}

static void
save_size(GwyAppGraphWindow *window, const GdkRectangle *allocation)
{
    GwyAppGraphWindowPrivate *priv = window->priv;
    /* Someone created us with plain g_object_new(). */
    g_return_if_fail(priv->file);
    GtkWidget *widget = gwy_graph_window_get_graph(GWY_GRAPH_WINDOW(window));
    _gwy_save_widget_screen_size(widget, allocation, priv->file, priv->data_kind, priv->id, TRUE);
}

/* FIXME: Where should it go? */
/* Use generic arguments and low-level key manipulation to make it useful with weird things like 3D views, program
 * windows with size in settings (not in the file), etc. */
void
_gwy_restore_window_size(GtkWindow *window,
                         GwyDict *container, const gchar *prefix)
{
    if (!container || !prefix)
        return;

    gsize len = strlen(prefix);
    gchar key[len + 16];
    memcpy(key, prefix, len);

    gdouble relsize;
    strcpy(key + len, "/relative-size");
    if (!gwy_dict_gis_double_by_name(container, key, &relsize))
        return;

    gint w, h;
    strcpy(key + len, "/width");
    if (!gwy_dict_gis_int32_by_name(container, key, &w))
        return;
    strcpy(key + len, "/height");
    if (!gwy_dict_gis_int32_by_name(container, key, &h))
        return;

    if (w <= 0 || h <= 0 || relsize <= 0.0)
        return;

    gint scw, sch;
    gwy_get_screen_size(GTK_WIDGET(window), &scw, &sch);
    gdouble newrelsize = MAX(w/(gdouble)scw, h/(gdouble)sch);
    gwy_debug("restoring generic window size: relsize %g, size %dx%d, newrelsize %g",
              relsize, w, h, newrelsize);

    /* If the window will be small we can just apply the saved zoom.  Should it be larger though, we must check if it
     * is not too large and better show it at defaut size than huge. */
    if (newrelsize > 1.2*relsize || newrelsize > 0.9)
        return;
    gtk_window_set_default_size(window, w, h);
}

static GwyFile*
app_data_window_get_file(GwyAppDataWindow *window)
{
    g_return_val_if_fail(GWY_IS_APP_GRAPH_WINDOW(window), NULL);
    return GWY_APP_GRAPH_WINDOW(window)->priv->file;
}

static GwyDataKind
app_data_window_get_data_kind(GwyAppDataWindow *window)
{
    g_return_val_if_fail(GWY_IS_APP_GRAPH_WINDOW(window), GWY_FILE_NONE);
    return GWY_APP_GRAPH_WINDOW(window)->priv->data_kind;
}

static gint
app_data_window_get_id(GwyAppDataWindow *window)
{
    g_return_val_if_fail(GWY_IS_APP_GRAPH_WINDOW(window), -1);
    return GWY_APP_GRAPH_WINDOW(window)->priv->id;
}

static const gchar*
app_data_window_get_accel_root(GwyAppDataWindow *window)
{
    g_return_val_if_fail(GWY_IS_APP_GRAPH_WINDOW(window), "<NONE>");
    return GWY_APP_GRAPH_WINDOW(window)->priv->accel_root;
}

/**
 * SECTION: app-graph-window
 * @title: GwyAppGraphWindow
 * @short_description: Windows for data shown as graphs
 *
 * #GwyAppGraphWindow wraps #GwyGraphWindow and updates it to changes in the file.
 **/

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