/*
 *  $Id: toolbox-editor.c 30074 2026-06-16 14:55:44Z yeti-dn $
 *  Copyright (C) 2016-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 <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include "libgwyddion/macros.h"
#include "libgwyui/icons.h"
#include "libgwyui/utils.h"
#include "libgwyui/combo-boxes.h"
#include "libgwyapp/gwyapp.h"
#include "libgwyapp/module-utils.h"
#include "libgwyapp/sanity.h"

#include "gwyddion/gwyddion.h"
#include "gwyddion/toolbox.h"

typedef enum {
    TOOLBOX_ICON_SOURCE_DEFAULT = 0,
    TOOLBOX_ICON_SOURCE_GWY = 1,
    TOOLBOX_ICON_SOURCE_SYSTEM = 2,
} ToolboxIconSource;

typedef struct {
    GwyAppActionType type;
    const gchar *name;
    const gchar *func;
    const gchar *id;
    const gchar *gid;
    GwyRunModeFlags mode;
} GwyToolboxEditorRow;

typedef struct {
    GwyToolboxSpec *spec;
    GtkWidget *dialogue;
    GtkTreeStore *toolbox_model;
    GtkTreeView *toolbox_view;
    GtkWidget *add_item;
    GtkWidget *add_group;
    GtkWidget *remove;
    GtkWidget *edit;
    GtkWidget *move_up;
    GtkWidget *move_down;
    GtkAdjustment *width;
    GtkListStore *icon_model_gwy;
    GtkListStore *icon_model_system;
    GtkListStore *icon_model_default;
    GwyToolboxItemSpec last_item_spec;
    gboolean do_save;
} GwyToolboxEditor;

typedef struct {
    GwyToolboxSpec *spec;
    guint i;
    GwyToolboxGroupSpec gspec;
    GtkWidget *dialogue;
    GtkWidget *title;
    GtkWidget *id;
    GtkWidget *message;
} GwyToolboxGroupEditor;

typedef struct {
    GwyToolboxEditor *editor;
    GwyToolboxItemSpec ispec;
    GtkWidget *dialogue;
    GtkWidget *type;
    GtkWidget *mode_label;
    GtkWidget *mode;
    GtkWidget *icon_source;
    GtkWidget *icon_source_label;
    GtkWidget *detail;
    GtkTreeView *function_view;
    GtkIconView *icon_view;
} GwyToolboxItemEditor;

static void          fill_toolbox_treestore          (GtkTreeStore *store,
                                                      GwyToolboxSpec *spec);
static void          free_toolbox_treestore          (GtkTreeStore *store);
static void          create_toolbox_tree_view        (GwyToolboxEditor *editor);
static void          toolbox_cell_renderer_icon      (GtkTreeViewColumn *column,
                                                      GtkCellRenderer *cell,
                                                      GtkTreeModel *model,
                                                      GtkTreeIter *iter,
                                                      gpointer userdata);
static void          toolbox_cell_renderer_icon_flags(GtkTreeViewColumn *column,
                                                      GtkCellRenderer *renderer,
                                                      GtkTreeModel *model,
                                                      GtkTreeIter *iter,
                                                      gpointer userdata);
static void          toolbox_cell_renderer_type      (GtkTreeViewColumn *column,
                                                      GtkCellRenderer *cell,
                                                      GtkTreeModel *model,
                                                      GtkTreeIter *iter,
                                                      gpointer userdata);
static void          toolbox_cell_renderer_name      (GtkTreeViewColumn *column,
                                                      GtkCellRenderer *cell,
                                                      GtkTreeModel *model,
                                                      GtkTreeIter *iter,
                                                      gpointer userdata);
static void          toolbox_cell_renderer_mode      (GtkTreeViewColumn *column,
                                                      GtkCellRenderer *cell,
                                                      GtkTreeModel *model,
                                                      GtkTreeIter *iter,
                                                      gpointer userdata);
static gboolean      edit_group_dialogue             (GwyToolboxEditor *editor,
                                                      GwyToolboxGroupSpec *gspec,
                                                      guint i);
static void          group_title_changed             (GwyToolboxGroupEditor *geditor,
                                                      GtkEntry *entry);
static void          group_id_changed                (GwyToolboxGroupEditor *geditor,
                                                      GtkEntry *entry);
static void          suggest_group_id                (GwyToolboxGroupEditor *geditor);
static void          group_title_transl_changed      (GwyToolboxGroupEditor *geditor,
                                                      GtkToggleButton *toggle);
static gboolean      edit_item_dialogue              (GwyToolboxEditor *editor,
                                                      GwyToolboxItemSpec *ispec);
static void          try_to_select_item_icon         (GwyToolboxItemEditor *ieditor,
                                                      GQuark qname);
static void          function_type_changed           (GtkComboBox *combo,
                                                      GwyToolboxItemEditor *ieditor);
static void          function_mode_changed           (GtkComboBox *combo,
                                                      GwyToolboxItemEditor *ieditor);
static void          function_icon_source_changed    (GtkComboBox *combo,
                                                      GwyToolboxItemEditor *ieditor);
static void          function_selection_changed      (GwyToolboxItemEditor *ieditor);
static void          function_icon_changed           (GwyToolboxItemEditor *ieditor);
static void          add_default_function_icon       (GtkListStore *store,
                                                      GwyToolboxItemSpec *ispec);
static void          create_icon_icon_view           (GwyToolboxItemEditor *ieditor);
static void          update_toolbox_sensitivity      (GwyToolboxEditor *editor);
static void          add_toolbox_item                (GwyToolboxEditor *editor);
static void          add_toolbox_group               (GwyToolboxEditor *editor);
static void          edit_item_or_group              (GwyToolboxEditor *editor);
static void          remove_from_toolbox             (GwyToolboxEditor *editor);
static void          move_up_in_toolbox              (GwyToolboxEditor *editor);
static void          move_down_in_toolbox            (GwyToolboxEditor *editor);
static void          width_changed                   (GwyToolboxEditor *editor);
static void          apply_toolbox_spec              (GwyToolboxEditor *editor);
static void          reset_toolbox_to_default        (GwyToolboxEditor *editor);
static GtkListStore* create_system_icon_list         (void);
static GtkListStore* create_gwy_icon_list            (void);
static GdkPixbuf*    render_icon                     (const gchar *icon_name,
                                                      GtkIconTheme *theme);

void
gwy_toolbox_editor(void)
{
    GwyToolboxSpec *spec;
    GtkWindow *toolbox;
    GtkBox *vbox, *hbox, *hbox2;
    GtkWidget *scwin, *label, *spin;
    GwyToolboxEditor editor;
    GtkTreeSelection *selection;
    GtkTreeIter iter;
    gint response;

    gwy_clear1(editor);
    toolbox = GTK_WINDOW(gwy_app_main_window());
    spec = g_object_get_data(G_OBJECT(toolbox), "gwy-app-toolbox-spec");
    g_return_if_fail(spec);
    editor.spec = gwy_toolbox_spec_duplicate(spec);

    editor.last_item_spec.type = GWY_APP_ACTION_TYPE_PLACEHOLDER;
    editor.last_item_spec.function = 0;
    editor.last_item_spec.icon = 0;
    editor.last_item_spec.mode = 0;

    editor.dialogue = gtk_dialog_new_with_buttons(_("Toolbox Editor"), toolbox, GTK_DIALOG_MODAL, NULL, NULL);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(editor.dialogue), GWY_STOCK_RESET);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(editor.dialogue), GWY_STOCK_APPLY);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(editor.dialogue), GWY_STOCK_CLOSE);
    gtk_window_set_default_size(GTK_WINDOW(editor.dialogue), 560, 480);

    hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(editor.dialogue))), GTK_WIDGET(hbox),
                       TRUE, TRUE, 0);

    editor.toolbox_model = gtk_tree_store_new(1, G_TYPE_POINTER);
    fill_toolbox_treestore(editor.toolbox_model, editor.spec);
    create_toolbox_tree_view(&editor);
    g_signal_connect_swapped(editor.toolbox_model, "row-changed", G_CALLBACK(update_toolbox_sensitivity), &editor);
    g_signal_connect_swapped(editor.toolbox_model, "row-deleted", G_CALLBACK(update_toolbox_sensitivity), &editor);
    g_signal_connect_swapped(editor.toolbox_model, "row-inserted", G_CALLBACK(update_toolbox_sensitivity), &editor);
    g_signal_connect_swapped(editor.toolbox_model, "rows-reordered", G_CALLBACK(update_toolbox_sensitivity), &editor);
    g_signal_connect_swapped(editor.toolbox_view, "row-activated", G_CALLBACK(edit_item_or_group), &editor);

    scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
    gtk_container_add(GTK_CONTAINER(scwin), GTK_WIDGET(editor.toolbox_view));
    gtk_box_pack_start(hbox, scwin, TRUE, TRUE, 0);

    selection = gtk_tree_view_get_selection(editor.toolbox_view);
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
    if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(editor.toolbox_model), &iter))
        gtk_tree_selection_select_iter(selection, &iter);
    g_signal_connect_swapped(selection, "changed", G_CALLBACK(update_toolbox_sensitivity), &editor);

    vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 2));
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
    gtk_box_pack_start(hbox, GTK_WIDGET(vbox), FALSE, FALSE, 0);

    editor.add_item = gwy_create_stock_like_button(_("_New Item"), GWY_ICON_GTK_ADD);
    gtk_box_pack_start(vbox, editor.add_item, FALSE, FALSE, 0);
    g_signal_connect_swapped(editor.add_item, "clicked", G_CALLBACK(add_toolbox_item), &editor);

    editor.add_group = gwy_create_stock_like_button(_("_New Group"), GWY_ICON_GTK_ADD);
    gtk_box_pack_start(vbox, editor.add_group, FALSE, FALSE, 0);
    g_signal_connect_swapped(editor.add_group, "clicked", G_CALLBACK(add_toolbox_group), &editor);

    editor.edit = gwy_create_stock_button(GWY_STOCK_EDIT);
    gtk_box_pack_start(vbox, editor.edit, FALSE, FALSE, 0);
    g_signal_connect_swapped(editor.edit, "clicked", G_CALLBACK(edit_item_or_group), &editor);

    editor.remove = gwy_create_stock_button(GWY_STOCK_REMOVE);
    gtk_box_pack_start(vbox, editor.remove, FALSE, FALSE, 0);
    g_signal_connect_swapped(editor.remove, "clicked", G_CALLBACK(remove_from_toolbox), &editor);

    editor.move_up = gwy_create_stock_like_button(_("Move _Up"), GWY_ICON_GTK_GO_UP);
    gtk_box_pack_start(vbox, editor.move_up, FALSE, FALSE, 0);
    g_signal_connect_swapped(editor.move_up, "clicked", G_CALLBACK(move_up_in_toolbox), &editor);

    editor.move_down = gwy_create_stock_like_button(_("Move _Down"), GWY_ICON_GTK_GO_DOWN);
    gtk_box_pack_start(vbox, editor.move_down, FALSE, FALSE, 0);
    g_signal_connect_swapped(editor.move_down, "clicked", G_CALLBACK(move_down_in_toolbox), &editor);

    hbox2 = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
    gtk_box_pack_start(vbox, GTK_WIDGET(hbox2), FALSE, FALSE, 8);

    label = gtk_label_new_with_mnemonic(_("_Width:"));
    gtk_box_pack_start(hbox2, label, FALSE, FALSE, 0);

    editor.width = GTK_ADJUSTMENT(gtk_adjustment_new(editor.spec->width, 1, 20, 1, 2, 0));
    g_signal_connect_swapped(editor.width, "value-changed", G_CALLBACK(width_changed), &editor);
    spin = gtk_spin_button_new(editor.width, 0, 0);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), spin);
    gtk_box_pack_start(hbox2, spin, FALSE, FALSE, 0);

    gtk_widget_show_all(editor.dialogue);
    update_toolbox_sensitivity(&editor);

    do {
       response = gtk_dialog_run(GTK_DIALOG(editor.dialogue));

       if (response == GWY_RESPONSE_RESET)
           reset_toolbox_to_default(&editor);

       if (response == GTK_RESPONSE_APPLY)
           apply_toolbox_spec(&editor);

    } while (response != GTK_RESPONSE_CLOSE && response != GTK_RESPONSE_DELETE_EVENT);

    if (response != GTK_RESPONSE_NONE)
        gtk_widget_destroy(editor.dialogue);

    free_toolbox_treestore(editor.toolbox_model);
    g_clear_object(&editor.icon_model_gwy);
    g_clear_object(&editor.icon_model_system);
    gwy_toolbox_spec_free(editor.spec);

    if (editor.do_save) {
        spec = g_object_get_data(G_OBJECT(toolbox), "gwy-app-toolbox-spec");
        g_return_if_fail(spec);
        gwy_save_toolbox_ui(spec, NULL);
    }
}

static void
fill_toolbox_row_for_group(GwyToolboxEditorRow *row,
                           const GwyToolboxGroupSpec *gspec)
{
    row->type = GWY_APP_ACTION_TYPE_GROUP;
    row->name = gspec->name;
    row->gid = g_quark_to_string(gspec->id);
}

static void
fill_toolbox_row_for_item(GwyToolboxEditorRow *row,
                          const GwyToolboxItemSpec *ispec)
{
    row->type = ispec->type;
    row->func = ispec->function ? g_quark_to_string(ispec->function) : NULL;
    row->id = ispec->icon ? g_quark_to_string(ispec->icon) : NULL;
    row->mode = ispec->mode;
}

static void
fill_toolbox_treestore(GtkTreeStore *store, GwyToolboxSpec *spec)
{
    const GwyToolboxGroupSpec *gspec;
    const GwyToolboxItemSpec *ispec;
    GArray *group, *item;
    GtkTreeIter giter, iiter;
    GwyToolboxEditorRow *row;
    guint i, j;

    group = spec->group;
    for (i = 0; i < group->len; i++) {
        gspec = &g_array_index(group, GwyToolboxGroupSpec, i);
        row = g_new0(GwyToolboxEditorRow, 1);
        fill_toolbox_row_for_group(row, gspec);
        gtk_tree_store_insert_with_values(store, &giter, NULL, i, 0, row, -1);
        item = gspec->item;
        for (j = 0; j < item->len; j++) {
            ispec = &g_array_index(item, GwyToolboxItemSpec, j);
            row = g_new0(GwyToolboxEditorRow, 1);
            fill_toolbox_row_for_item(row, ispec);
            gtk_tree_store_insert_with_values(store, &iiter, &giter, j, 0, row, -1);
        }
    }
}

static gboolean
free_toolbox_treestore_row(GtkTreeModel *model,
                           G_GNUC_UNUSED GtkTreePath *path,
                           GtkTreeIter *iter,
                           G_GNUC_UNUSED gpointer user_data)
{
    GwyToolboxEditorRow *row;
    gtk_tree_model_get(model, iter, 0, &row, -1);
    g_free(row);
    return FALSE;
}

static void
free_toolbox_treestore(GtkTreeStore *store)
{
    gtk_tree_model_foreach(GTK_TREE_MODEL(store), &free_toolbox_treestore_row, NULL);
    g_object_unref(store);
}

static GtkCellRenderer*
append_column(GtkTreeView *treeview, gboolean new_column,
              GType renderer_gtype, GtkTreeCellDataFunc cell_func, gpointer cell_data,
              gint fixed_width, gint fixed_height, gboolean expand)
{
    GtkTreeViewColumn *column;
    if (new_column) {
        column = gtk_tree_view_column_new();
        gtk_tree_view_column_set_expand(column, expand);
        gtk_tree_view_append_column(treeview, column);
    }
    else
        column = gtk_tree_view_get_column(treeview, gtk_tree_view_get_n_columns(treeview)-1);

    GtkCellRenderer *renderer = g_object_new(renderer_gtype, NULL);
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    if (fixed_width != -1 || fixed_height != -1)
        gtk_cell_renderer_set_fixed_size(renderer, fixed_width, fixed_height);
    gtk_tree_view_column_set_cell_data_func(column, renderer, cell_func, cell_data, NULL);

    return renderer;
}

static void
create_toolbox_tree_view(GwyToolboxEditor *editor)
{
    GtkTreeModel *model = GTK_TREE_MODEL(editor->toolbox_model);
    editor->toolbox_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
    gtk_tree_view_set_headers_visible(editor->toolbox_view, FALSE);

    gint width, height;
    gtk_icon_size_lookup(GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height);

    append_column(editor->toolbox_view, TRUE,
                  GTK_TYPE_CELL_RENDERER_PIXBUF, toolbox_cell_renderer_icon, NULL,
                  width, height, FALSE);
    append_column(editor->toolbox_view, FALSE,
                  GTK_TYPE_CELL_RENDERER_TEXT, toolbox_cell_renderer_icon_flags, NULL,
                  -1, height, FALSE);

    append_column(editor->toolbox_view, TRUE,
                  GTK_TYPE_CELL_RENDERER_TEXT, toolbox_cell_renderer_type, NULL,
                  -1, height, FALSE);
    append_column(editor->toolbox_view, TRUE,
                  GTK_TYPE_CELL_RENDERER_TEXT, toolbox_cell_renderer_name, NULL,
                  -1, height, TRUE);
    append_column(editor->toolbox_view, TRUE,
                  GTK_TYPE_CELL_RENDERER_TEXT, toolbox_cell_renderer_mode, NULL,
                  -1, height, TRUE);

    gtk_tree_view_expand_all(editor->toolbox_view);
}

static void
create_icon_icon_view(GwyToolboxItemEditor *ieditor)
{
    GtkTreeModel *model;
    GtkCellRenderer *renderer;
    GtkCellLayout *layout;
    gint width, height;

    model = GTK_TREE_MODEL(ieditor->editor->icon_model_default);
    ieditor->icon_view = GTK_ICON_VIEW(gtk_icon_view_new_with_model(model));
    g_object_set(ieditor->icon_view,
                 "column-spacing", 2,
                 "row-spacing", 2,
                 NULL);
    g_object_set(ieditor->icon_view, "item-padding", 2, NULL);
    layout = GTK_CELL_LAYOUT(ieditor->icon_view);
    gtk_icon_size_lookup(GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height);

    renderer = gtk_cell_renderer_pixbuf_new();
    gtk_cell_layout_pack_start(layout, renderer, FALSE);
    gtk_cell_renderer_set_fixed_size(renderer, width, height);
    gtk_cell_layout_add_attribute(layout, renderer, "pixbuf", 1);
    gtk_icon_view_set_selection_mode(ieditor->icon_view, GTK_SELECTION_BROWSE);
}

static void
toolbox_cell_renderer_icon(G_GNUC_UNUSED GtkTreeViewColumn *column,
                           GtkCellRenderer *renderer,
                           GtkTreeModel *model,
                           GtkTreeIter *iter,
                           G_GNUC_UNUSED gpointer userdata)
{
    GwyToolboxEditorRow *row;
    gtk_tree_model_get(model, iter, 0, &row, -1);

    const gchar *icon_name = row->id;
    if (!icon_name)
        icon_name = gwy_toolbox_action_icon_name(row->type, row->func);

    if (!icon_name) {
        g_object_set(renderer, "pixbuf", NULL, NULL);
        return;
    }

    GdkPixbuf *pixbuf = render_icon(icon_name, NULL);
    g_object_set(renderer, "pixbuf", pixbuf, NULL);
    g_clear_object(&pixbuf);
}

static void
toolbox_cell_renderer_icon_flags(G_GNUC_UNUSED GtkTreeViewColumn *column,
                                 GtkCellRenderer *renderer,
                                 GtkTreeModel *model,
                                 GtkTreeIter *iter,
                                 G_GNUC_UNUSED gpointer userdata)
{
    GwyToolboxEditorRow *row;

    gtk_tree_model_get(model, iter, 0, &row, -1);
    if (row->type <= 0 || !row->id) {
        g_object_set(renderer,
                     "text", NULL,
                     "weight-set", FALSE,
                     "scale-set", FALSE,
                     NULL);
    }
    else {
        g_object_set(renderer,
                     "markup", "<b>!</b>",
                     "weight", PANGO_WEIGHT_BOLD,
                     "weight-set", TRUE,
                     "scale", 1.2,
                     "scale-set", TRUE,
                     NULL);
    }
}

static void
toolbox_cell_renderer_type(G_GNUC_UNUSED GtkTreeViewColumn *column,
                           GtkCellRenderer *renderer,
                           GtkTreeModel *model,
                           GtkTreeIter *iter,
                           G_GNUC_UNUSED gpointer userdata)
{
    GwyToolboxEditorRow *row;
    const gchar *text;

    gtk_tree_model_get(model, iter, 0, &row, -1);
    text = gwy_toolbox_action_type_name(row->type);
    g_object_set(renderer, "text", text ? _(text) : NULL, NULL);
}

static void
toolbox_cell_renderer_name(G_GNUC_UNUSED GtkTreeViewColumn *column,
                           GtkCellRenderer *renderer,
                           GtkTreeModel *model,
                           GtkTreeIter *iter,
                           G_GNUC_UNUSED gpointer userdata)
{
    GwyToolboxEditorRow *row;
    const gchar *name;
    PangoWeight weight = PANGO_WEIGHT_NORMAL;
    PangoStyle style = PANGO_STYLE_NORMAL;

    gtk_tree_model_get(model, iter, 0, &row, -1);
    if (row->type == GWY_APP_ACTION_TYPE_GROUP) {
        name = row->name;
        weight = PANGO_WEIGHT_BOLD;
    }
    else {
        name = gwy_toolbox_action_nice_name(row->type, row->func);
        if (row->type == GWY_APP_ACTION_TYPE_TOOL && !row->func)
            style = PANGO_STYLE_ITALIC;
        else if (row->type == GWY_APP_ACTION_TYPE_PLACEHOLDER)
            style = PANGO_STYLE_ITALIC;
    }

    g_object_set(renderer,
                 "text", name ? name : row->func,
                 "weight-set", weight != PANGO_WEIGHT_NORMAL,
                 "weight", weight,
                 "style-set", style != PANGO_STYLE_NORMAL,
                 "style", style,
                 NULL);
}

static void
toolbox_cell_renderer_mode(G_GNUC_UNUSED GtkTreeViewColumn *column,
                           GtkCellRenderer *renderer,
                           GtkTreeModel *model,
                           GtkTreeIter *iter,
                           G_GNUC_UNUSED gpointer userdata)
{
    GwyToolboxEditorRow *row;

    gtk_tree_model_get(model, iter, 0, &row, -1);
    if (row->type == GWY_APP_ACTION_TYPE_GROUP
        || row->type == GWY_APP_ACTION_TYPE_TOOL
        || row->type == GWY_APP_ACTION_TYPE_PLACEHOLDER) {
        g_object_set(renderer, "text", NULL, NULL);
    }
    else {
        /* FIXME: We can translate this. But we need to use the strings used to build the combo, not this, which is
         * not translatable. */
        g_object_set(renderer, "text", gwy_toolbox_mode_name(row->mode), NULL);
    }
}

static gboolean
edit_group_dialogue(GwyToolboxEditor *editor,
                    GwyToolboxGroupSpec *gspec, guint i)
{
    GwyToolboxGroupEditor geditor;
    GtkWidget *label, *button;
    GtkGrid *grid;
    gint response;
    const gchar *id;

    geditor.spec = editor->spec;
    geditor.i = i;
    geditor.gspec = *gspec;
    geditor.gspec.item = NULL;
    geditor.gspec.name = g_strdup(gspec->name);
    id = gspec->id ? g_quark_to_string(gspec->id) : "";

    geditor.dialogue = gtk_dialog_new_with_buttons(_("Toolbox Group"), GTK_WINDOW(editor->dialogue), GTK_DIALOG_MODAL,
                                                   NULL, NULL);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(geditor.dialogue), GWY_STOCK_CANCEL);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(geditor.dialogue), GWY_STOCK_OK);

    grid = GTK_GRID(gtk_grid_new());
    gtk_grid_set_row_spacing(grid, 2);
    gtk_grid_set_column_spacing(grid, 6);
    gtk_container_set_border_width(GTK_CONTAINER(grid), 4);
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(geditor.dialogue))), GTK_WIDGET(grid),
                       TRUE, TRUE, 0);
    label = gtk_label_new_with_mnemonic(_("_Title:"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(grid, label, 0, 0, 1, 1);

    geditor.title = gtk_entry_new();
    gtk_entry_set_width_chars(GTK_ENTRY(geditor.title), 24);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), geditor.title);
    gtk_entry_set_text(GTK_ENTRY(geditor.title), geditor.gspec.name);
    gtk_grid_attach_next_to(grid, geditor.title, label, GTK_POS_RIGHT, 1, 1);
    g_signal_connect_swapped(geditor.title, "changed", G_CALLBACK(group_title_changed), &geditor);

    label = gtk_label_new_with_mnemonic(_("_Id:"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(grid, label, 0, 1, 1, 1);

    geditor.id = gtk_entry_new();
    gtk_entry_set_width_chars(GTK_ENTRY(geditor.id), 24);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), geditor.id);
    gtk_entry_set_text(GTK_ENTRY(geditor.id), id);
    gtk_grid_attach_next_to(grid, geditor.id, label, GTK_POS_RIGHT, 1, 1);
    g_signal_connect_swapped(geditor.id, "changed", G_CALLBACK(group_id_changed), &geditor);

    button = gtk_button_new_with_mnemonic(_("_Suggest"));
    gtk_grid_attach_next_to(grid, button, geditor.id, GTK_POS_RIGHT, 1, 1);
    g_signal_connect_swapped(button, "clicked", G_CALLBACK(suggest_group_id), &geditor);

    button = gtk_check_button_new_with_mnemonic(_("Trans_latable title"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), geditor.gspec.translatable);
    gtk_grid_attach(grid, button, 0, 2, 1, 1);
    g_signal_connect_swapped(button, "toggled", G_CALLBACK(group_title_transl_changed), &geditor);

    geditor.message = gtk_label_new(NULL);
    gtk_widget_set_margin_top(geditor.message, 8);
    gtk_grid_attach(grid, geditor.message, 0, 3, 1, 1);

    group_id_changed(&geditor, GTK_ENTRY(geditor.id));
    gtk_widget_show_all(geditor.dialogue);
    response = gwy_run_subdialog(GTK_DIALOG(geditor.dialogue));
    if (response == GTK_RESPONSE_OK) {
        /* XXX: The caller must ensure the corresponding GwyToolboxEditorRow is updated! */
        GWY_SWAP(gchar*, gspec->name, geditor.gspec.name);
        gspec->translatable = geditor.gspec.translatable;
        id = gtk_entry_get_text(GTK_ENTRY(geditor.id));
        gspec->id = g_quark_from_string(id);
    }
    g_free(geditor.gspec.name);

    return (response == GTK_RESPONSE_OK);
}

static void
group_title_changed(GwyToolboxGroupEditor *geditor, GtkEntry *entry)
{
    g_free(geditor->gspec.name);
    geditor->gspec.name = g_strdup(gtk_entry_get_text(entry));
}

static void
group_id_changed(GwyToolboxGroupEditor *geditor, GtkEntry *entry)
{
    const GwyToolboxGroupSpec *gspec;
    const gchar *newid;
    GArray *group;
    const gchar *errmessage = NULL;

    newid = gtk_entry_get_text(entry);
    if (!gwy_ascii_strisident(newid, "_-", NULL))
        errmessage = _("Group id is not a valid identifier");

    if (!errmessage) {
        group = geditor->spec->group;
        for (guint i = 0; i < group->len; i++) {
            gspec = &g_array_index(group, GwyToolboxGroupSpec, i);
            if (i != geditor->i && gwy_strequal(g_quark_to_string(gspec->id), newid)) {
                errmessage = _("Group id is not unique");
                break;
            }
        }
    }

    gwy_set_message_label(GTK_LABEL(geditor->message), errmessage, GTK_MESSAGE_ERROR, FALSE);
    gtk_dialog_set_response_sensitive(GTK_DIALOG(geditor->dialogue), GTK_RESPONSE_OK, !errmessage);
}

static void
add_unichar_if_ident(GString *str, gunichar uc)
{
    if (uc <= 0x20 || uc >= 0x80)
        return;

    if (g_ascii_isalpha(uc)
        || (str->len && (uc == '_' || uc == '-' || g_ascii_isdigit(uc))))
        g_string_append_c(str, g_ascii_tolower(uc));
}

static void
suggest_group_id(GwyToolboxGroupEditor *geditor)
{
    GString *suggestion;
    gunichar *name_chars;
    glong name_len, i;
    guint decomp_len, j;
    gunichar buf[18];

    name_chars = g_utf8_to_ucs4(geditor->gspec.name, -1, NULL, &name_len, NULL);
    if (!name_chars)
        return;

    suggestion = g_string_new(NULL);
    for (i = 0; i < name_len; i++) {
        decomp_len = g_unichar_fully_decompose(name_chars[i], TRUE, buf, G_N_ELEMENTS(buf));
        for (j = 0; j < decomp_len; j++)
            add_unichar_if_ident(suggestion, buf[j]);
    }
    gtk_entry_set_text(GTK_ENTRY(geditor->id), suggestion->str);
    g_string_free(suggestion, TRUE);
    g_free(name_chars);
}

static void
group_title_transl_changed(GwyToolboxGroupEditor *geditor,
                           GtkToggleButton *toggle)
{
    geditor->gspec.translatable = gtk_toggle_button_get_active(toggle);
}

static gboolean
edit_item_dialogue(GwyToolboxEditor *editor, GwyToolboxItemSpec *ispec)
{
    GwyToolboxItemEditor ieditor;
    GtkWidget *label, *scwin, *shrink;
    GtkBox *hbox, *vbox;
    ToolboxIconSource iconsource;

    ieditor.editor = editor;
    ieditor.ispec = *ispec;
    // Do a lazy init and only create the ‘default’ icon list. Enumerating the system icons in particular can take
    // some time the first time we do it.
    if (!editor->icon_model_default)
        editor->icon_model_default = gtk_list_store_new(2, G_TYPE_UINT, GDK_TYPE_PIXBUF);

    ieditor.dialogue = gtk_dialog_new_with_buttons(_("Toolbox Item"), GTK_WINDOW(editor->dialogue), GTK_DIALOG_MODAL,
                                                   NULL, NULL);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(ieditor.dialogue), GWY_STOCK_CANCEL);
    gwy_stock_add_button_to_dialog(GTK_DIALOG(ieditor.dialogue), GWY_STOCK_OK);
    gtk_window_set_default_size(GTK_WINDOW(ieditor.dialogue), 640, 480);

    hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(ieditor.dialogue))), GTK_WIDGET(hbox),
                       TRUE, TRUE, 0);

    GtkGrid *grid = GTK_GRID(gtk_grid_new());
    gtk_grid_set_row_spacing(grid, 2);
    gtk_grid_set_column_spacing(grid, 6);
    gtk_container_set_border_width(GTK_CONTAINER(grid), 4);
    gtk_box_pack_start(hbox, GTK_WIDGET(grid), TRUE, TRUE, 0);

    label = gtk_label_new_with_mnemonic(_("Function _type:"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(grid, label, 0, 0, 1, 1);
    ieditor.type = gwy_enum_combo_box_newl(G_CALLBACK(function_type_changed), &ieditor, ieditor.ispec.type,
                                           _("Placeholder"), GWY_APP_ACTION_TYPE_PLACEHOLDER,
                                           _("Builtin"), GWY_APP_ACTION_TYPE_BUILTIN,
                                           _("Image"), GWY_APP_ACTION_TYPE_IMAGE,
                                           _("Graph"), GWY_APP_ACTION_TYPE_GRAPH,
                                           _("Synthetic"), GWY_APP_ACTION_TYPE_SYNTH,
                                           _("Volume Data"), GWY_APP_ACTION_TYPE_VOLUME,
                                           _("XYZ Data"), GWY_APP_ACTION_TYPE_XYZ,
                                           _("Curve Maps"), GWY_APP_ACTION_TYPE_CMAP,
                                           _("Spectra"), GWY_APP_ACTION_TYPE_SPS,
                                           _("Tool"), GWY_APP_ACTION_TYPE_TOOL,
                                           NULL);
    gtk_grid_attach_next_to(grid, ieditor.type, label, GTK_POS_RIGHT, 1, 1);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), ieditor.type);

    label = ieditor.mode_label = gtk_label_new_with_mnemonic(_("_Mode:"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(grid, label, 0, 1, 1, 1);

    ieditor.mode = gwy_enum_combo_box_newl(G_CALLBACK(function_mode_changed), &ieditor, ieditor.ispec.mode,
                                           _("Default"), 0,
                                           _("Interactive"), GWY_RUN_INTERACTIVE,
                                           _("Non-interactive"), GWY_RUN_IMMEDIATE,
                                           NULL);
    gtk_grid_attach_next_to(grid, ieditor.mode, label, GTK_POS_RIGHT, 1, 1);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), ieditor.mode);

    label = ieditor.icon_source_label = gtk_label_new_with_mnemonic(_("Use _icon from:"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(grid, label, 0, 2, 1, 1);

    iconsource = TOOLBOX_ICON_SOURCE_DEFAULT;
    ieditor.icon_source = gwy_enum_combo_box_newl(G_CALLBACK(function_icon_source_changed), &ieditor, iconsource,
                                                  _("Default"), TOOLBOX_ICON_SOURCE_DEFAULT,
                                                  "Gwyddion", TOOLBOX_ICON_SOURCE_GWY,
                                                  _("System"), TOOLBOX_ICON_SOURCE_SYSTEM,
                                                  NULL);
    gtk_grid_attach_next_to(grid, ieditor.icon_source, label, GTK_POS_RIGHT, 1, 1);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), ieditor.mode);

    create_icon_icon_view(&ieditor);
    gtk_icon_view_set_selection_mode(ieditor.icon_view, GTK_SELECTION_BROWSE);
    g_signal_connect_swapped(ieditor.icon_view, "selection-changed", G_CALLBACK(function_icon_changed), &ieditor);
    scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_set_hexpand(scwin, TRUE);
    gtk_widget_set_vexpand(scwin, TRUE);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
    gtk_container_add(GTK_CONTAINER(scwin), GTK_WIDGET(ieditor.icon_view));
    gtk_grid_attach(grid, scwin, 0, 3, 2, 1);

    vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 2));
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
    gtk_box_pack_start(hbox, GTK_WIDGET(vbox), TRUE, TRUE, 0);

    label = gtk_label_new(_("Functions"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_box_pack_start(vbox, label, FALSE, FALSE, 0);

    ieditor.function_view = GTK_TREE_VIEW(gwy_function_list_new(NULL));
    gwy_function_list_set_show_remaning_tools(GWY_FUNCTION_LIST(ieditor.function_view),
                                              !editor->spec->seen_tool_placeholder);
    GtkTreeSelection *selection = gtk_tree_view_get_selection(ieditor.function_view);
    g_signal_connect_swapped(selection, "changed", G_CALLBACK(function_selection_changed), &ieditor);

    scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
    gtk_container_add(GTK_CONTAINER(scwin), GTK_WIDGET(ieditor.function_view));
    gtk_box_pack_start(vbox, scwin, TRUE, TRUE, 0);

    ieditor.detail = gtk_label_new(NULL);
    gtk_label_set_xalign(GTK_LABEL(ieditor.detail), 0.0);
    gtk_label_set_ellipsize(GTK_LABEL(ieditor.detail), PANGO_ELLIPSIZE_END);

    shrink = gwy_shrink_wrap_new(GTK_SIZE_GROUP_HORIZONTAL);
    gtk_container_add(GTK_CONTAINER(shrink), ieditor.detail);
    gtk_box_pack_start(vbox, shrink, FALSE, FALSE, 0);

    function_type_changed(GTK_COMBO_BOX(ieditor.type), &ieditor);
    if (ispec->mode)
        gwy_enum_combo_box_set_active(GTK_COMBO_BOX(ieditor.mode), ispec->mode);
    gwy_function_list_select(GWY_FUNCTION_LIST(ieditor.function_view), g_quark_to_string(ispec->function), TRUE);
    try_to_select_item_icon(&ieditor, ispec->icon);

    gtk_widget_show_all(ieditor.dialogue);
    gint response = gwy_run_subdialog(GTK_DIALOG(ieditor.dialogue));
    if (response == GTK_RESPONSE_OK) {
        *ispec = ieditor.ispec;
    }

    return response == GTK_RESPONSE_OK;
}

static void
try_to_select_item_icon(GwyToolboxItemEditor *ieditor, GQuark qname)
{
    ToolboxIconSource iconsource = TOOLBOX_ICON_SOURCE_DEFAULT;

    if (!qname) {
        gwy_enum_combo_box_set_active(GTK_COMBO_BOX(ieditor->icon_source), TOOLBOX_ICON_SOURCE_DEFAULT);
    }
    else {
        GwyToolboxEditor *editor = ieditor->editor;
        const gchar *name = g_quark_to_string(qname);

        if (g_str_has_prefix(name, "gwy_")) {
            iconsource = TOOLBOX_ICON_SOURCE_GWY;
            if (!editor->icon_model_gwy)
                editor->icon_model_gwy = create_gwy_icon_list();
        }
        else {
            iconsource = TOOLBOX_ICON_SOURCE_SYSTEM;
            if (!editor->icon_model_system)
                editor->icon_model_system = create_system_icon_list();
        }
        gwy_enum_combo_box_set_active(GTK_COMBO_BOX(ieditor->icon_source), iconsource);
    }

    GtkTreeModel *model = gtk_icon_view_get_model(ieditor->icon_view);
    GtkTreeIter iter;
    if (!gtk_tree_model_get_iter_first(model, &iter))
        return;

    do {
        GQuark quark;
        gtk_tree_model_get(model, &iter, 0, &quark, -1);
        /* This select the first icon for the DEFAULT source. */
        if (iconsource == TOOLBOX_ICON_SOURCE_DEFAULT || quark == qname) {
            GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
            gtk_icon_view_select_path(ieditor->icon_view, path);
            gtk_icon_view_scroll_to_path(ieditor->icon_view, path, TRUE, 0.5, 0.0);
            gtk_tree_path_free(path);
            return;
        }
    } while (gtk_tree_model_iter_next(model, &iter));
}

static void
function_type_changed(GtkComboBox *combo, GwyToolboxItemEditor *ieditor)
{
    GwyAppActionType type = gwy_enum_combo_box_get_active(combo);
    GwyToolboxEditor *editor = ieditor->editor;

    ieditor->ispec.type = type;
    gwy_function_list_set_kind(GWY_FUNCTION_LIST(ieditor->function_view), gwy_toolbox_action_type_name(type));
    if (type == GWY_APP_ACTION_TYPE_PLACEHOLDER) {
        gtk_widget_set_sensitive(ieditor->icon_source, FALSE);
        gtk_widget_set_sensitive(ieditor->icon_source_label, FALSE);
        gtk_widget_set_sensitive(GTK_WIDGET(ieditor->icon_view), FALSE);
        gtk_widget_set_sensitive(ieditor->mode, FALSE);
        gtk_widget_set_sensitive(ieditor->mode_label, FALSE);
        gtk_widget_set_sensitive(GTK_WIDGET(ieditor->function_view), FALSE);
        gtk_icon_view_set_model(ieditor->icon_view, GTK_TREE_MODEL(editor->icon_model_default));
        gtk_list_store_clear(editor->icon_model_default);
        gwy_enum_combo_box_set_active(GTK_COMBO_BOX(ieditor->icon_source), TOOLBOX_ICON_SOURCE_DEFAULT);
        gwy_enum_combo_box_set_active(GTK_COMBO_BOX(ieditor->mode), 0);
        ieditor->ispec.function = 0;
        return;
    }

    gtk_widget_set_sensitive(ieditor->icon_source, TRUE);
    gtk_widget_set_sensitive(ieditor->icon_source_label, TRUE);
    gtk_widget_set_sensitive(GTK_WIDGET(ieditor->icon_view), TRUE);
    gtk_widget_set_sensitive(GTK_WIDGET(ieditor->function_view), TRUE);
}

static void
function_mode_changed(GtkComboBox *combo, GwyToolboxItemEditor *ieditor)
{
    GwyRunModeFlags mode = gwy_enum_combo_box_get_active(combo);
    ieditor->ispec.mode = mode;
}

static void
function_icon_source_changed(GtkComboBox *combo, GwyToolboxItemEditor *ieditor)
{
    ToolboxIconSource iconsource = gwy_enum_combo_box_get_active(combo);
    GwyToolboxEditor *editor = ieditor->editor;
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreePath *path;

    if (iconsource == TOOLBOX_ICON_SOURCE_GWY) {
        gtk_icon_view_set_model(ieditor->icon_view, GTK_TREE_MODEL(editor->icon_model_gwy));
    }
    else if (iconsource == TOOLBOX_ICON_SOURCE_SYSTEM) {
        gtk_icon_view_set_model(ieditor->icon_view, GTK_TREE_MODEL(editor->icon_model_system));
    }
    else {
        gtk_list_store_clear(editor->icon_model_default);
        add_default_function_icon(editor->icon_model_default, &ieditor->ispec);
        gtk_icon_view_set_model(ieditor->icon_view, GTK_TREE_MODEL(editor->icon_model_default));
    }

    model = gtk_icon_view_get_model(ieditor->icon_view);
    if (gtk_tree_model_get_iter_first(model, &iter)) {
        path = gtk_tree_model_get_path(model, &iter);
        gtk_icon_view_select_path(ieditor->icon_view, path);
        gtk_tree_path_free(path);
    }
    else
        function_icon_changed(ieditor);
}

static void
function_selection_changed(GwyToolboxItemEditor *ieditor)
{
    GwyToolboxEditor *editor = ieditor->editor;

    const gchar *name = gwy_function_list_get_selected(GWY_FUNCTION_LIST(ieditor->function_view));
    ieditor->ispec.function = name ? g_quark_from_string(name) : 0;

    /* Update run modes, make the chooser unavailable if there is nothing to choose from. */
    GwyRunModeFlags modes = 0;
    if (name)
        modes = gwy_toolbox_action_run_modes(ieditor->ispec.type, name);

    /* We only care if the function has both run modes.  Otherwise there is nothing to choose from and we enforce the
     * default mode. */
    if ((modes & (GWY_RUN_IMMEDIATE | GWY_RUN_INTERACTIVE)) != (GWY_RUN_IMMEDIATE | GWY_RUN_INTERACTIVE))
        modes = 0;

    gtk_widget_set_sensitive(ieditor->mode_label, !!modes);
    gtk_widget_set_sensitive(ieditor->mode, !!modes);
    if (!modes) {
        gwy_enum_combo_box_set_active(GTK_COMBO_BOX(ieditor->mode), 0);
        /* XXX: Implied? */
        ieditor->ispec.mode = 0;
    }

    /* Update the icon, if set to default for the function. */
    ToolboxIconSource iconsource = gwy_enum_combo_box_get_active(GTK_COMBO_BOX(ieditor->icon_source));
    if (iconsource == TOOLBOX_ICON_SOURCE_DEFAULT) {
        gtk_list_store_clear(editor->icon_model_default);
        add_default_function_icon(editor->icon_model_default, &ieditor->ispec);
    }

    /* Update function information. */
    const gchar *detail = gwy_toolbox_action_detail(ieditor->ispec.type, name);
    gtk_label_set_text(GTK_LABEL(ieditor->detail), detail ? detail : "");
}

static void
function_icon_changed(GwyToolboxItemEditor *ieditor)
{
    GtkTreeModel *model;
    GList *items;
    GtkTreePath *path;
    GtkTreeIter iter;

    model = gtk_icon_view_get_model(ieditor->icon_view);
    if (model == GTK_TREE_MODEL(ieditor->editor->icon_model_default)) {
        ieditor->ispec.icon = 0;
        return;
    }

    items = gtk_icon_view_get_selected_items(ieditor->icon_view);
    if (!model || !g_list_length(items)) {
        ieditor->ispec.icon = 0;
        return;
    }

    path = (GtkTreePath*)items->data;
    gtk_tree_model_get_iter(model, &iter, path);
    g_list_foreach(items, (GFunc)gtk_tree_path_free, NULL);
    g_list_free(items);

    gtk_tree_model_get(model, &iter, 0, &ieditor->ispec.icon, -1);
}

static void
add_default_function_icon(GtkListStore *store, GwyToolboxItemSpec *ispec)
{
    const gchar *icon_name;
    GtkTreeIter iter;

    if (!ispec->function)
        return;

    icon_name = gwy_toolbox_action_icon_name(ispec->type, g_quark_to_string(ispec->function));
    if (!icon_name)
        return;

    GdkPixbuf *pixbuf = render_icon(icon_name, NULL);
    if (!pixbuf)
        return;

    gtk_list_store_insert_with_values(store, &iter, G_MAXINT,
                                      0, g_quark_from_string(icon_name),
                                      1, pixbuf,
                                      -1);
    g_object_unref(pixbuf);
}

static guint
toolbox_model_iter_indices(GtkTreeModel *model, GtkTreeIter *iter,
                           guint *i, guint *j)
{
    GtkTreePath *path;
    gint depth;
    gint *indices;

    path = gtk_tree_model_get_path(model, iter);
    depth = gtk_tree_path_get_depth(path);
    indices = gtk_tree_path_get_indices(path);
    if (depth == 1) {
        *i = indices[0];
        *j = G_MAXUINT;
    }
    else if (depth == 2) {
        *i = indices[0];
        *j = indices[1];
    }
    else {
        *i = *j = 0;
        g_return_val_if_reached(0);
    }

    gtk_tree_path_free(path);

    return depth;
}

static gboolean
tree_model_iter_is_first(GtkTreeModel *model, GtkTreeIter *iter)
{
    GtkTreePath *path;
    gint depth;
    gint *indices;
    gboolean is_first;

    path = gtk_tree_model_get_path(model, iter);
    depth = gtk_tree_path_get_depth(path);
    g_return_val_if_fail(depth, TRUE);
    indices = gtk_tree_path_get_indices(path);
    is_first = (indices[depth-1] == 0);
    gtk_tree_path_free(path);

    return is_first;
}

static gboolean
tree_model_iter_is_last(GtkTreeModel *model, GtkTreeIter *iter)
{
    GtkTreeIter myiter = *iter;

    return !gtk_tree_model_iter_next(model, &myiter);
}

static void
update_toolbox_sensitivity(GwyToolboxEditor *editor)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
        gtk_widget_set_sensitive(editor->add_item, FALSE);
        gtk_widget_set_sensitive(editor->remove, FALSE);
        gtk_widget_set_sensitive(editor->edit, FALSE);
        gtk_widget_set_sensitive(editor->move_up, FALSE);
        gtk_widget_set_sensitive(editor->move_down, FALSE);
        return;
    }

    gtk_widget_set_sensitive(editor->add_item, TRUE);
    gtk_widget_set_sensitive(editor->remove, TRUE);
    gtk_widget_set_sensitive(editor->edit, TRUE);
    gtk_widget_set_sensitive(editor->move_up, !tree_model_iter_is_first(model, &iter));
    gtk_widget_set_sensitive(editor->move_down, !tree_model_iter_is_last(model, &iter));
}

static void
add_toolbox_item(GwyToolboxEditor *editor)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter, parent;
    guint i, j, depth;
    GwyToolboxItemSpec ispec;
    GwyToolboxEditorRow *row;
    gboolean ok;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
        return;

    depth = toolbox_model_iter_indices(model, &iter, &i, &j);
    if (depth == 1) {
        parent = iter;
        j = 0;
    }
    else {
        gtk_tree_model_iter_parent(model, &parent, &iter);
        j++;
    }

    ispec = editor->last_item_spec;
    ok = edit_item_dialogue(editor, &ispec);
    editor->last_item_spec = ispec;
    if (!ok)
        return;

    gwy_toolbox_spec_add_item(editor->spec, &ispec, i, j);
    row = g_new0(GwyToolboxEditorRow, 1);
    fill_toolbox_row_for_item(row, &ispec);
    gtk_tree_store_insert_with_values(editor->toolbox_model, &iter, &parent, j, 0, row, -1);
}

static void
add_toolbox_group(GwyToolboxEditor *editor)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter;
    guint i, j;
    GwyToolboxGroupSpec gspec;
    GwyToolboxEditorRow *row;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
        toolbox_model_iter_indices(model, &iter, &i, &j);
        i++;
    }
    else
        i = 0;

    gwy_clear1(gspec);
    gspec.name = g_strdup("");
    if (!edit_group_dialogue(editor, &gspec, G_MAXUINT)) {
        g_free(gspec.name);
        return;
    }

    gwy_toolbox_spec_add_group(editor->spec, &gspec, i);
    row = g_new0(GwyToolboxEditorRow, 1);
    fill_toolbox_row_for_group(row, &gspec);
    gtk_tree_store_insert_with_values(editor->toolbox_model, &iter, NULL, i, 0, row, -1);
}

static void
edit_item_or_group(GwyToolboxEditor *editor)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter;
    GtkTreePath *path;
    guint depth, i, j;
    GwyToolboxGroupSpec *gspec;
    GwyToolboxItemSpec *ispec;
    GwyToolboxEditorRow *row;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
        return;

    depth = toolbox_model_iter_indices(model, &iter, &i, &j);
    gtk_tree_model_get(model, &iter, 0, &row, -1);
    if (depth == 1) {
        gspec = &g_array_index(editor->spec->group, GwyToolboxGroupSpec, i);
        if (edit_group_dialogue(editor, gspec, i)) {
            fill_toolbox_row_for_group(row, gspec);
            path = gtk_tree_model_get_path(model, &iter);
            gtk_tree_model_row_changed(model, path, &iter);
            gtk_tree_path_free(path);
        }
    }
    else if (depth == 2) {
        gspec = &g_array_index(editor->spec->group, GwyToolboxGroupSpec, i);
        ispec = &g_array_index(gspec->item, GwyToolboxItemSpec, j);
        if (edit_item_dialogue(editor, ispec)) {
            fill_toolbox_row_for_item(row, ispec);
            path = gtk_tree_model_get_path(model, &iter);
            gtk_tree_model_row_changed(model, path, &iter);
            gtk_tree_path_free(path);
        }
    }
}

static void
remove_from_toolbox(GwyToolboxEditor *editor)
{
    GwyToolboxEditorRow *row;
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter;
    guint i, j, depth;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
        return;

    depth = toolbox_model_iter_indices(model, &iter, &i, &j);
    if (depth == 1)
        gwy_toolbox_spec_remove_group(editor->spec, i);
    else if (depth == 2)
        gwy_toolbox_spec_remove_item(editor->spec, i, j);
    else
        g_return_if_reached();

    gtk_tree_model_get(model, &iter, 0, &row, -1);
    gtk_tree_store_remove(editor->toolbox_model, &iter);
    g_free(row);
}

static void
move_up_in_toolbox(GwyToolboxEditor *editor)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter, otheriter, parent;
    guint i, j, depth;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
        return;

    depth = toolbox_model_iter_indices(model, &iter, &i, &j);
    if (depth == 1) {
        gwy_toolbox_spec_move_group(editor->spec, i, TRUE);
        gtk_tree_model_iter_nth_child(model, &otheriter, NULL, i-1);
        gtk_tree_store_swap(editor->toolbox_model, &iter, &otheriter);
    }
    else if (depth == 2) {
        gwy_toolbox_spec_move_item(editor->spec, i, j, TRUE);
        gtk_tree_model_iter_parent(model, &parent, &iter);
        gtk_tree_model_iter_nth_child(model, &otheriter, &parent, j-1);
        gtk_tree_store_swap(editor->toolbox_model, &iter, &otheriter);
    }
    else
        g_return_if_reached();
}

static void
move_down_in_toolbox(GwyToolboxEditor *editor)
{
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter iter, otheriter;
    guint i, j, depth;

    selection = gtk_tree_view_get_selection(editor->toolbox_view);
    if (!gtk_tree_selection_get_selected(selection, &model, &iter))
        return;

    depth = toolbox_model_iter_indices(model, &iter, &i, &j);
    if (depth == 1)
        gwy_toolbox_spec_move_group(editor->spec, i, FALSE);
    else if (depth == 2)
        gwy_toolbox_spec_move_item(editor->spec, i, j, FALSE);
    else
        g_return_if_reached();

    otheriter = iter;
    gtk_tree_model_iter_next(model, &otheriter);
    gtk_tree_store_swap(editor->toolbox_model, &iter, &otheriter);
}

static void
width_changed(GwyToolboxEditor *editor)
{
    editor->spec->width = gwy_adjustment_get_int(editor->width);
}

static void
apply_toolbox_spec(GwyToolboxEditor *editor)
{
    GwyToolboxSpec *spec;

    /* We work on a copy.  But invoking gwy_toolbox_rebuild_to_spec() also makes the passed spec the actual primary
     * spec.  So pass a copy. */
    spec = gwy_toolbox_spec_duplicate(editor->spec);
    gwy_toolbox_rebuild_to_spec(spec);
    editor->do_save = TRUE;
}

static void
reset_toolbox_to_default(GwyToolboxEditor *editor)
{
    GwyToolboxSpec *spec = gwy_parse_toolbox_ui(TRUE);
    GtkTreeStore *store;

    if (!spec)
        return;

    store = gtk_tree_store_new(1, G_TYPE_POINTER);
    fill_toolbox_treestore(store, spec);
    GWY_SWAP(GwyToolboxSpec*, spec, editor->spec);
    GWY_SWAP(GtkTreeStore*, store, editor->toolbox_model);
    gtk_tree_view_set_model(editor->toolbox_view, GTK_TREE_MODEL(editor->toolbox_model));
    gtk_tree_view_expand_all(editor->toolbox_view);

    free_toolbox_treestore(store);
    gwy_toolbox_spec_free(spec);
}

static GtkListStore*
create_icon_list(const gchar **icon_names, guint nicons)
{
    GtkIconTheme *theme = gtk_icon_theme_get_default();
    GtkListStore *store;
    GtkTreeIter iter;

    store = gtk_list_store_new(2, G_TYPE_UINT, GDK_TYPE_PIXBUF);
    for (guint i = 0; i < nicons; i++) {
        GdkPixbuf *pixbuf = render_icon(icon_names[i], theme);
        if (!pixbuf)
            continue;

        gtk_list_store_insert_with_values(store, &iter, G_MAXINT,
                                          0, g_quark_from_string(icon_names[i]),
                                          1, pixbuf,
                                          -1);
        g_object_unref(pixbuf);
    }

    return store;
}

static GdkPixbuf*
render_icon(const gchar *icon_name, GtkIconTheme *theme)
{
    if (!theme)
        theme = gtk_icon_theme_get_default();

    return gtk_icon_theme_load_icon(theme, icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR,
                                    GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_DIR_LTR,
                                    NULL);
}

static GtkListStore*
create_system_icon_list(void)
{
    GList *actions = gtk_icon_theme_list_icons(gtk_icon_theme_get_default(), "Actions");
    GPtrArray *icons = g_ptr_array_new_full(0, g_free);
    for (GList *l = actions; l; l = g_list_next(l)) {
        // We add our icons to the default theme run-time. Meaning we have to separate them here.
        gchar *icon_name = l->data;
        if (g_str_has_prefix(icon_name, "gwy_"))
            g_free(icon_name);
        else
            g_ptr_array_add(icons, icon_name);
    }
    g_list_free(actions);

    GtkListStore *store = create_icon_list((const gchar**)icons->pdata, icons->len);
    g_ptr_array_free(icons, TRUE);
    return store;
}

static GtkListStore*
create_gwy_icon_list(void)
{
    /* The following generated part is updated by running utils/stockgen.py */
    static const gchar *icon_names[] = { /* {{{ */
/* @@@ GENERATED ICON LIST BEGIN @@@ */
    GWY_ICON_3D_BASE,
    GWY_ICON_ARITHMETIC,
    GWY_ICON_BINNING,
    GWY_ICON_BOLD,
    GWY_ICON_CANTILEVER,
    GWY_ICON_COLOR_RANGE,
    GWY_ICON_COLOR_RANGE_ADAPTIVE,
    GWY_ICON_COLOR_RANGE_AUTO,
    GWY_ICON_COLOR_RANGE_FIXED,
    GWY_ICON_COLOR_RANGE_FULL,
    GWY_ICON_CONVOLUTION,
    GWY_ICON_CONVOLVE,
    GWY_ICON_CORRECT_AFFINE,
    GWY_ICON_CORRELATION_LENGTH,
    GWY_ICON_CORRELATION_MASK,
    GWY_ICON_CROP,
    GWY_ICON_CROSS_PROFILE,
    GWY_ICON_CURVATURE,
    GWY_ICON_CWT,
    GWY_ICON_DATA_MEASURE,
    GWY_ICON_DECONVOLVE,
    GWY_ICON_DISCONNECTED,
    GWY_ICON_DISPLACEMENT_FIELD,
    GWY_ICON_DISTANCE,
    GWY_ICON_DISTANCE_TRANSFORM,
    GWY_ICON_DISTRIBUTION_ANGLE,
    GWY_ICON_DISTRIBUTION_SLOPE,
    GWY_ICON_DRIFT,
    GWY_ICON_DWT,
    GWY_ICON_EDGE,
    GWY_ICON_ENFORCE_DISTRIBUTION,
    GWY_ICON_ENTROPY,
    GWY_ICON_EXTEND,
    GWY_ICON_EXTRACT_PATH,
    GWY_ICON_FACET_ANALYSIS,
    GWY_ICON_FACET_LEVEL,
    GWY_ICON_FACET_MEASURE,
    GWY_ICON_FAVOURITE,
    GWY_ICON_FFT,
    GWY_ICON_FFT_2D,
    GWY_ICON_FFT_FILTER_1D,
    GWY_ICON_FFT_FILTER_2D,
    GWY_ICON_FILTER,
    GWY_ICON_FIND_PEAKS,
    GWY_ICON_FIT_SHAPE,
    GWY_ICON_FIX_ZERO,
    GWY_ICON_FLIP_DIAGONALLY,
    GWY_ICON_FLIP_HORIZONTALLY,
    GWY_ICON_FLIP_VERTICALLY,
    GWY_ICON_FRACTAL,
    GWY_ICON_FRACTAL_CORRECTION,
    GWY_ICON_FRACTAL_MEASURE,
    GWY_ICON_FREQUENCY_SPLIT,
    GWY_ICON_GL_MATERIAL,
    GWY_ICON_GRADIENT_HORIZONTAL,
    GWY_ICON_GRADIENT_VERTICAL,
    GWY_ICON_GRAIN_BOUNDING_BOX,
    GWY_ICON_GRAIN_CIRCUMCIRCLE,
    GWY_ICON_GRAIN_CORRELATION,
    GWY_ICON_GRAIN_INSCRIBED_BOX,
    GWY_ICON_GRAIN_INSCRIBED_CIRCLE,
    GWY_ICON_GRAINS,
    GWY_ICON_GRAINS_EDGE,
    GWY_ICON_GRAINS_EDGE_REMOVE,
    GWY_ICON_GRAINS_GRAPH,
    GWY_ICON_GRAINS_MEASURE,
    GWY_ICON_GRAINS_OTSU,
    GWY_ICON_GRAINS_REMOVE,
    GWY_ICON_GRAINS_STATISTICS,
    GWY_ICON_GRAINS_WATER,
    GWY_ICON_GRAPH,
    GWY_ICON_GRAPH_ALIGN,
    GWY_ICON_GRAPH_CUT,
    GWY_ICON_GRAPH_DOS,
    GWY_ICON_GRAPH_EXPORT_ASCII,
    GWY_ICON_GRAPH_EXPORT_PNG,
    GWY_ICON_GRAPH_EXPORT_VECTOR,
    GWY_ICON_GRAPH_FD,
    GWY_ICON_GRAPH_FILTER,
    GWY_ICON_GRAPH_FUNCTION,
    GWY_ICON_GRAPH_HALFGAUSS,
    GWY_ICON_GRAPH_LEVEL,
    GWY_ICON_GRAPH_MEASURE,
    GWY_ICON_GRAPH_PALETTE,
    GWY_ICON_GRAPH_PERIOD_MEASURE,
    GWY_ICON_GRAPH_POINTER,
    GWY_ICON_GRAPH_RULER,
    GWY_ICON_GRAPH_STATISTICS,
    GWY_ICON_GRAPH_TERRACE_MEASURE,
    GWY_ICON_GRAPH_VERTICAL,
    GWY_ICON_GRAPH_ZOOM_FIT,
    GWY_ICON_GRAPH_ZOOM_IN,
    GWY_ICON_GRAPH_ZOOM_OUT,
    GWY_ICON_HOLD_SELECTION_CLEAR,
    GWY_ICON_HOLD_SELECTION_REPLACE,
    GWY_ICON_HOUGH,
    GWY_ICON_IMAGE_RELATION,
    GWY_ICON_IMMERSE,
    GWY_ICON_ISO_ROUGHNESS,
    GWY_ICON_ITALIC,
    GWY_ICON_LESS,
    GWY_ICON_LEVEL,
    GWY_ICON_LEVEL_FLATTEN_BASE,
    GWY_ICON_LEVEL_MEDIAN,
    GWY_ICON_LEVEL_TRIANGLE,
    GWY_ICON_LIGHT_ROTATE,
    GWY_ICON_LIMIT_RANGE,
    GWY_ICON_LINE_LEVEL,
    GWY_ICON_LOAD_DEBUG,
    GWY_ICON_LOAD_INFO,
    GWY_ICON_LOAD_WARNING,
    GWY_ICON_LOCAL_SLOPE,
    GWY_ICON_LOGSCALE_HORIZONTAL,
    GWY_ICON_LOGSCALE_VERTICAL,
    GWY_ICON_MARK_OUTLIERS,
    GWY_ICON_MARK_SCARS,
    GWY_ICON_MARK_WITH,
    GWY_ICON_MASK,
    GWY_ICON_MASK_ADD,
    GWY_ICON_MASK_CIRCLE,
    GWY_ICON_MASK_CIRCLE_EXCLUSIVE,
    GWY_ICON_MASK_CIRCLE_INCLUSIVE,
    GWY_ICON_MASK_DISTRIBUTE,
    GWY_ICON_MASK_EDITOR,
    GWY_ICON_MASK_EXCLUDE,
    GWY_ICON_MASK_EXCLUDE_CIRCLE,
    GWY_ICON_MASK_EXTRACT,
    GWY_ICON_MASK_FILL_DRAW,
    GWY_ICON_MASK_FILL_ERASE,
    GWY_ICON_MASK_GROW,
    GWY_ICON_MASK_INTERSECT,
    GWY_ICON_MASK_INVERT,
    GWY_ICON_MASK_LINE,
    GWY_ICON_MASK_MORPH,
    GWY_ICON_MASK_NOISIFY,
    GWY_ICON_MASK_PAINT_DRAW,
    GWY_ICON_MASK_PAINT_ERASE,
    GWY_ICON_MASK_RECT_EXCLUSIVE,
    GWY_ICON_MASK_RECT_INCLUSIVE,
    GWY_ICON_MASK_REMOVE,
    GWY_ICON_MASK_SET,
    GWY_ICON_MASK_SHIFT,
    GWY_ICON_MASK_SHRINK,
    GWY_ICON_MASK_SUBTRACT,
    GWY_ICON_MASK_THIN,
    GWY_ICON_MEASURE_LATTICE,
    GWY_ICON_MERGE,
    GWY_ICON_MFM_CONVERT_TO_FORCE,
    GWY_ICON_MFM_CURRENT_LINE,
    GWY_ICON_MFM_FIELD_FIND_SHIFT,
    GWY_ICON_MFM_FIELD_SHIFT,
    GWY_ICON_MFM_PARALLEL,
    GWY_ICON_MFM_PERPENDICULAR,
    GWY_ICON_MORE,
    GWY_ICON_MUTUAL_CROP,
    GWY_ICON_NEURAL_APPLY,
    GWY_ICON_NEURAL_TRAIN,
    GWY_ICON_NEXT,
    GWY_ICON_NULL_OFFSETS,
    GWY_ICON_PALETTES,
    GWY_ICON_PATH_LEVEL,
    GWY_ICON_PERSPECTIVE_DISTORT,
    GWY_ICON_POINTER_MEASURE,
    GWY_ICON_POLY_DISTORT,
    GWY_ICON_POLYNOM,
    GWY_ICON_POLYNOM_LEVEL,
    GWY_ICON_PREVIOUS,
    GWY_ICON_PROFILE,
    GWY_ICON_PROFILE_MULTIPLE,
    GWY_ICON_PSDF_LOG_PHI,
    GWY_ICON_PSDF_SECTION,
    GWY_ICON_PYGWY,
    GWY_ICON_RADIAL_PROFILE,
    GWY_ICON_RANK_FILTER,
    GWY_ICON_RASTERIZE,
    GWY_ICON_REMOVE_UNDER_MASK,
    GWY_ICON_REVOLVE_ARC,
    GWY_ICON_REVOLVE_SPHERE,
    GWY_ICON_ROTATE,
    GWY_ICON_ROTATE_180,
    GWY_ICON_ROTATE_3D,
    GWY_ICON_ROTATE_90_CCW,
    GWY_ICON_ROTATE_90_CW,
    GWY_ICON_SCALE,
    GWY_ICON_SCALE_HORIZONTALLY,
    GWY_ICON_SCALE_VERTICALLY,
    GWY_ICON_SCARS,
    GWY_ICON_SCIENTIFIC_NUMBER_FORMAT,
    GWY_ICON_SELECTIONS,
    GWY_ICON_SHADER,
    GWY_ICON_SPECTRUM,
    GWY_ICON_SPOT_REMOVE,
    GWY_ICON_SQUARE_SAMPLES,
    GWY_ICON_STAT_QUANTITIES,
    GWY_ICON_STITCH,
    GWY_ICON_STRAIGHTEN_PATH,
    GWY_ICON_SUBSCRIPT,
    GWY_ICON_SUPERSCRIPT,
    GWY_ICON_SYNTHETIC_ANNEAL,
    GWY_ICON_SYNTHETIC_BALLISTIC_DEPOSITION,
    GWY_ICON_SYNTHETIC_BROWNIAN_MOTION,
    GWY_ICON_SYNTHETIC_COLUMNAR,
    GWY_ICON_SYNTHETIC_DIFFUSION,
    GWY_ICON_SYNTHETIC_DISCS,
    GWY_ICON_SYNTHETIC_DOMAINS,
    GWY_ICON_SYNTHETIC_DUNES,
    GWY_ICON_SYNTHETIC_FIBRES,
    GWY_ICON_SYNTHETIC_LATTICE,
    GWY_ICON_SYNTHETIC_LINE_NOISE,
    GWY_ICON_SYNTHETIC_NOISE,
    GWY_ICON_SYNTHETIC_OBJECTS,
    GWY_ICON_SYNTHETIC_PARTICLES,
    GWY_ICON_SYNTHETIC_PATTERN,
    GWY_ICON_SYNTHETIC_PHASES,
    GWY_ICON_SYNTHETIC_PILEUP,
    GWY_ICON_SYNTHETIC_PLATEAUS,
    GWY_ICON_SYNTHETIC_SPECTRAL,
    GWY_ICON_SYNTHETIC_TURING_PATTERN,
    GWY_ICON_SYNTHETIC_WAVES,
    GWY_ICON_TERRACE_MEASURE,
    GWY_ICON_TILT,
    GWY_ICON_TIP_DILATION,
    GWY_ICON_TIP_EROSION,
    GWY_ICON_TIP_ESTIMATION,
    GWY_ICON_TIP_INDENT_ANALYZE,
    GWY_ICON_TIP_LATERAL_FORCE,
    GWY_ICON_TIP_MAP,
    GWY_ICON_TIP_MODEL,
    GWY_ICON_TIP_PID,
    GWY_ICON_TRANSLATE_PERIODICALLY,
    GWY_ICON_UNROTATE,
    GWY_ICON_VALUE_INVERT,
    GWY_ICON_VOLUME,
    GWY_ICON_VOLUME_ARITHMETIC,
    GWY_ICON_VOLUME_CALIBRATE,
    GWY_ICON_VOLUME_DIMENSIONS,
    GWY_ICON_VOLUME_FD,
    GWY_ICON_VOLUME_INVERT,
    GWY_ICON_VOLUME_KMEANS,
    GWY_ICON_VOLUME_KMEDIANS,
    GWY_ICON_VOLUME_LINE_STATS,
    GWY_ICON_VOLUME_PLANE_STATS,
    GWY_ICON_VOLUME_SLICE,
    GWY_ICON_VOLUME_SWAP_AXES,
    GWY_ICON_VOLUMIZE,
    GWY_ICON_VOLUMIZE_LAYERS,
    GWY_ICON_WRAP_VALUE,
    GWY_ICON_XY_DENOISE,
    GWY_ICON_XYZIZE,
    GWY_ICON_ZERO_MAXIMUM,
    GWY_ICON_ZERO_MEAN,
    GWY_ICON_ZERO_UNDER_MASK,
    GWY_ICON_ZOOM_1_1,
    GWY_ICON_ZOOM_FIT,
    GWY_ICON_ZOOM_IN,
    GWY_ICON_ZOOM_OUT,
    GWY_ICON_GWYDDION,
/* @@@ GENERATED ICON LIST END @@@ */
    }; /* }}} */

    return create_icon_list(icon_names, G_N_ELEMENTS(icon_names));
}

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