/*
 *  $Id: combo-boxes.c 29768 2026-04-10 14:54:03Z yeti-dn $
 *  Copyright (C) 2003-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>

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

#include "libgwyui/inventory-store.h"
#include "libgwyui/cell-renderer-color.h"
#include "libgwyui/combo-boxes.h"

typedef struct {
    const GwyEnum *const_entries;
    GwyEnum *entries;
    gint nentries;
    gboolean owns_strings;
    gulong handler_id;
    /* Used for curves. */
    GObject *model;
    gulong model_id1;
    gulong model_id2;
} EnumComboBoxInfo;

static void       cell_translate_func    (GtkCellLayout *cell_layout,
                                          GtkCellRenderer *renderer,
                                          GtkTreeModel *tree_model,
                                          GtkTreeIter *iter,
                                          gpointer data);
static gboolean   try_set_active         (GtkComboBox *combo,
                                          gint active);
static GwyEnum*   make_enum_for_unit     (gint from,
                                          gint to,
                                          GwyUnit *unit,
                                          gint *nentries);
static void       update_graph_ncurves   (GtkComboBox *combo);
static GwyEnum*   create_graph_curve_enum(GwyGraphModel *gmodel,
                                          gint *pncurves);
static GtkWidget* create_enum_combo      (GwyEnum *entries,
                                          const GwyEnum *const_entries,
                                          gint nentries,
                                          GCallback callback,
                                          gpointer cbdata,
                                          gint active,
                                          gboolean translate,
                                          gboolean owns_strings);
static void       set_combo_model        (GtkComboBox *combo,
                                          GwyEnum *entries,
                                          const GwyEnum *const_entries,
                                          gint nentries);

static G_DEFINE_QUARK(gwy-enum-combo-info, info)

/**
 * gwy_enum_combo_box_new:
 * @entries: An enum with choices.
 * @nentries: The number of items in @entries, may be -1 when @entries is terminated with %NULL enum name.
 * @callback: A callback called when a new choice is selected. It may be %NULL.
 * @cbdata: User data passed to the callback.
 * @active: The enum value to show as currently selected.  If it isn't equal to any @entries value, first item is
 *          selected.
 * @translate: Whether to apply translation function (gwy_sgettext()) to item names.
 *
 * Creates a combo box with choices from a enum.
 *
 * The array @entries must exist during the whole lifetime of the combo box because it is used directly as the model.
 *
 * Returns: A newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_enum_combo_box_new(const GwyEnum *entries,
                       gint nentries,
                       GCallback callback,
                       gpointer cbdata,
                       gint active,
                       gboolean translate)
{
    return create_enum_combo(NULL, entries, nentries, callback, cbdata, active, translate, FALSE);
}

/**
 * gwy_enum_combo_box_newl:
 * @callback: A callback called when a new choice is selected. It may be %NULL.
 * @cbdata: User data passed to the callback.
 * @active: The enum value to show as currently selected.  If it isn't equal to any @entries value, the first item is
 *          selected.
 * @...: First item label, first item value, second item label, second item value, etc.  Terminated with %NULL.
 *
 * Creates a combo box with choices from a list of label/value pairs.
 *
 * The string values passed as label texts must exist through the whole lifetime of the widget.
 *
 * Returns: A newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_enum_combo_box_newl(GCallback callback,
                        gpointer cbdata,
                        gint active,
                        ...)
{
    va_list ap;

    va_start(ap, active);

    gint nentries = 0;
    while (va_arg(ap, const gchar*)) {
        (void)va_arg(ap, gint);
        nentries++;
    }
    va_end(ap);

    GwyEnum *entries = g_new(GwyEnum, nentries);

    va_start(ap, active);
    for (gint i = 0; i < nentries; i++) {
        entries[i].name = va_arg(ap, const gchar*);
        entries[i].value = va_arg(ap, gint);
    }
    va_end(ap);

    return create_enum_combo(entries, NULL, nentries, callback, cbdata, active, FALSE, FALSE);
}

/**
 * gwy_enum_combo_box_set_active:
 * @combo: A combo box which was created with gwy_enum_combo_box_new().
 * @active: The enum value to show as currently selected.
 *
 * Sets the active combo box item by corresponding enum value.
 **/
void
gwy_enum_combo_box_set_active(GtkComboBox *combo,
                              gint active)
{
    if (!try_set_active(combo, active))
        g_warning("Enum value not between inventory enums");
}

/**
 * gwy_enum_combo_box_get_active:
 * @combo: A combo box which was created with gwy_enum_combo_box_new().
 *
 * Gets the enum value corresponding to currently active combo box item.
 *
 * Similarly to #GtkComboBox, the value -1 is returned when nothing active (which can happen when there are no
 * selectable items in the combo box).
 *
 * Returns: The selected enum value.
 **/
gint
gwy_enum_combo_box_get_active(GtkComboBox *combo)
{
    gint i = gtk_combo_box_get_active(combo);
    if (i < 0)
        return -1;

    GwyInventoryStore *store = GWY_INVENTORY_STORE(gtk_combo_box_get_model(combo));
    g_return_val_if_fail(GWY_IS_INVENTORY_STORE(store), -1);
    const GwyEnum *item = gwy_inventory_get_nth_item(gwy_inventory_store_get_inventory(store), i);
    g_return_val_if_fail(item, -1);

    return item->value;
}

/**
 * gwy_combo_box_metric_unit_new:
 * @callback: A callback called when a new choice is selected. It may be %NULL. Callbacks set later may get extra
 *            updates during model changes which are prevented for @callback.
 * @cbdata: User data passed to the callback.
 * @from: The exponent of 10 the menu should start at (a multiple of 3, will be rounded downward if isn't).
 * @to: The exponent of 10 the menu should end at (a multiple of 3, will be rounded upward if isn't).
 * @unit: The unit to be prefixed.
 * @active: The power of 10 to show as currently selected (a multiple of 3).
 *
 * Creates an enum combo box with SI power of 10 multiplies.
 *
 * The integer value is the power of 10.
 *
 * Returns: The newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_combo_box_metric_unit_new(GCallback callback,
                              gpointer cbdata,
                              gint from,
                              gint to,
                              GwyUnit *unit,
                              gint active)
{
    g_return_val_if_fail(GWY_IS_UNIT(unit), NULL);

    gint nentries;
    GwyEnum *entries = make_enum_for_unit(from, to, unit, &nentries);
    return create_enum_combo(entries, NULL, nentries, callback, cbdata, active, FALSE, TRUE);
}

/**
 * gwy_combo_box_metric_unit_set_unit:
 * @combo: A combo box which was created with gwy_combo_box_metric_unit_new().
 * @from: The exponent of 10 the menu should start at (a multiple of 3, will be rounded downward if isn't).
 * @to: The exponent of 10 the menu should end at (a multiple of 3, will be rounded upward if isn't).
 * @unit: The unit to be prefixed.
 *
 * Changes the unit selection displayed by a metric unit combo box.
 **/
void
gwy_combo_box_metric_unit_set_unit(GtkComboBox *combo,
                                   gint from,
                                   gint to,
                                   GwyUnit *unit)
{
    g_return_if_fail(GTK_IS_COMBO_BOX(combo));

    gint nentries;
    GwyEnum *entries = make_enum_for_unit(from, to, unit, &nentries);
    set_combo_model(GTK_COMBO_BOX(combo), entries, NULL, nentries);
}

static GwyEnum*
make_enum_for_unit(gint from, gint to, GwyUnit *unit, gint *nentries)
{
    from = from/3;
    to = (to + 2)/3;
    GWY_ORDER(gint, from, to);

    gint n = (to - from) + 1;
    GwyEnum *entries = g_new(GwyEnum, n + 1);
    GwyValueFormat *format = NULL;
    for (gint i = from; i <= to; i++) {
        format = gwy_unit_get_format_for_power10(unit, GWY_UNIT_FORMAT_MARKUP, 3*i, format);
        if (*format->units)
            entries[i - from].name = g_strdup(format->units);
        else
            entries[i - from].name = g_strdup("1");
        entries[i - from].value = 3*i;
    }
    entries[n].name = NULL;
    gwy_value_format_free(format);

    if (nentries)
        *nentries = n;

    return entries;
}

static void
free_enum_cmbo_info(gpointer user_data, G_GNUC_UNUSED GObject *where_the_object_was)
{
    EnumComboBoxInfo *info = (EnumComboBoxInfo*)user_data;

    if (info->entries) {
        if (info->owns_strings)
            gwy_enum_freev(info->entries);
        else
            g_free(info->entries);
        info->entries = NULL;
    }
    if (info->model) {
        g_clear_signal_handler(&info->model_id1, info->model);
        g_clear_signal_handler(&info->model_id2, info->model);
        g_clear_object(&info->model);
    }
    g_free(info);
}

static void
render_curve_colour(G_GNUC_UNUSED GtkCellLayout *layout,
                    GtkCellRenderer *renderer,
                    GtkTreeModel *model,
                    GtkTreeIter *iter,
                    G_GNUC_UNUSED gpointer data)
{
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(renderer), info_quark());
    g_return_if_fail(info);
    GwyGraphModel *gmodel = GWY_GRAPH_MODEL(info->model);

    GwyEnum *item;
    gtk_tree_model_get(model, iter, 0, &item, -1);
    GwyGraphCurveModel *gcmodel = gwy_graph_model_get_curve(gmodel, item->value);
    g_return_if_fail(gcmodel);

    GwyRGBA *color;
    g_object_get(gcmodel, "color", &color, NULL);
    g_object_set(renderer, "color", color, NULL);
}

/**
 * gwy_combo_box_graph_curve_new:
 * @callback: A callback called when a new choice is selected. It may be %NULL. Callbacks set later may get extra
 *            updates during model changes which are prevented for @callback.
 * @cbdata: User data passed to the callback.
 * @gmodel: A graph model.
 * @current: Index of currently selected curve.
 *
 * Creates an enum combo box with curves from a graph model.
 *
 * The graph model can change and the combo box reflects such changes.
 *
 * Returns: The newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_combo_box_graph_curve_new(GCallback callback, gpointer cbdata,
                              GwyGraphModel *gmodel, gint current)
{
    gint ncurves;
    GwyEnum *curves = create_graph_curve_enum(gmodel, &ncurves);
    GtkWidget *combo = create_enum_combo(curves, NULL, ncurves, callback, cbdata, current, FALSE, TRUE);

    GtkCellRenderer *renderer = gwy_cell_renderer_color_new();
    /* Set the object data also on the renderer because the thing passed as the first argument to
     * render_curve_colour() is some internal GtkCellView, not the combo itself. */
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    g_object_set_qdata(G_OBJECT(renderer), info_quark(), info);
    info->model = g_object_ref(G_OBJECT(gmodel));

    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
    gtk_cell_layout_reorder(GTK_CELL_LAYOUT(combo), renderer, 0);
    gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer, render_curve_colour, NULL, NULL);

    info->model_id1 = g_signal_connect_swapped(gmodel, "notify::n-curves", G_CALLBACK(update_graph_ncurves), combo);
    info->model_id2 = g_signal_connect_swapped(gmodel, "curve-notify", G_CALLBACK(gtk_widget_queue_draw), combo);

    return combo;
}

static void
update_graph_ncurves(GtkComboBox *combo)
{
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    g_return_if_fail(info);
    gint ncurves;
    GwyEnum *curves = create_graph_curve_enum(GWY_GRAPH_MODEL(info->model), &ncurves);
    set_combo_model(combo, curves, NULL, ncurves);
}

static GwyEnum*
create_graph_curve_enum(GwyGraphModel *gmodel, gint *pncurves)
{
    gint ncurves = gwy_graph_model_get_n_curves(gmodel);
    GwyEnum *curves = g_new(GwyEnum, ncurves + 1);
    for (gint i = 0; i < ncurves; i++) {
        GwyGraphCurveModel *curve = gwy_graph_model_get_curve(gmodel, i);
        g_object_get(curve, "description", &curves[i].name, NULL);
        if (!curves[i].name || !*curves[i].name)
            curves[i].name = g_strdup_printf("%s %d", _("Untitled"), i+1);
        curves[i].value = i;
    }
    curves[ncurves].name = NULL;

    *pncurves = ncurves;
    return curves;
}

/**
 * gwy_combo_box_lawn_curve_new:
 * @callback: A callback called when a new choice is selected. It may be %NULL. Callbacks set later may get extra
 *            updates during model changes which are prevented for @callback.
 * @cbdata: User data passed to the callback.
 * @lawn: A lawn curve map object.
 * @current: Index of currently selected curve.
 *
 * Creates an enum combo box with curves from a lawn curve map object.
 *
 * This function is intended for selection of curves from static lawn objects.  The lawn object is not permitted to
 * change.
 *
 * Returns: The newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_combo_box_lawn_curve_new(GCallback callback, gpointer cbdata,
                             GwyLawn *lawn, gint current)
{

    gint ncurves = gwy_lawn_get_n_curves(lawn);
    GwyEnum *curves = g_new(GwyEnum, ncurves + 1);
    for (gint i = 0; i < ncurves; i++) {
        const gchar *label = gwy_lawn_get_curve_label(lawn, i);
        curves[i].name = (label && *label ? g_strdup(label) : g_strdup_printf("%s %d", _("Untitled"), i+1));
        curves[i].value = i;
    }
    curves[ncurves].name = NULL;

    GtkWidget *combo = create_enum_combo(curves, NULL, ncurves, callback, cbdata, current, FALSE, TRUE);
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    info->model = g_object_ref(G_OBJECT(lawn));

    /* TODO: Connect to lawn changes and update the combo model. For this, the lawn needs to emit signals in the first
     * place. */

    return combo;
}

/**
 * gwy_combo_box_lawn_segment_new:
 * @callback: A callback called when a new choice is selected. It may be %NULL. Callbacks set later may get extra
 *            updates during model changes which are prevented for @callback.
 * @cbdata: User data passed to the callback.
 * @lawn: A lawn curve map object.
 * @current: Index of currently selected segment.
 *
 * Creates an enum combo box with segments from a lawn curve map object.
 *
 * This function is intended for selection of segments from static lawn objects.  The lawn object is not permitted to
 * change.
 *
 * Returns: The newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_combo_box_lawn_segment_new(GCallback callback,
                               gpointer cbdata,
                               GwyLawn *lawn,
                               gint current)
{
    gint nsegments = gwy_lawn_get_n_segments(lawn);
    GwyEnum *segments = g_new(GwyEnum, nsegments + 1);
    for (gint i = 0; i < nsegments; i++) {
        const gchar *label = gwy_lawn_get_segment_label(lawn, i);
        segments[i].name = (label && *label ? g_strdup(label) : g_strdup_printf("%s %d", _("Segment"), i+1));
        segments[i].value = i;
    }
    segments[nsegments].name = NULL;

    GtkWidget *combo = create_enum_combo(segments, NULL, nsegments, callback, cbdata, current, FALSE, TRUE);
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    info->model = g_object_ref(G_OBJECT(lawn));

    /* TODO: Connect to lawn changes and update the combo model. For this, the lawn needs to emit signals in the first
     * place. */

    return combo;
}

/**
 * gwy_combo_box_scatter_data_new:
 * @callback: A callback called when a new choice is selected. It may be %NULL. Callbacks set later may get extra
 *            updates during model changes which are prevented for @callback.
 * @cbdata: User data passed to the callback.
 * @scatter: A scattered data object.
 * @current: Index of currently selected value.
 *
 * Creates an enum combo box with data from a scattered XYZ data object.
 *
 * This function is intended for selection of data from static scattered data objects.  The scatter object is not
 * permitted to change.
 *
 * Returns: The newly created combo box as #GtkWidget.
 **/
GtkWidget*
gwy_combo_box_scatter_data_new(GCallback callback,
                               gpointer cbdata,
                               GwyScatter *scatter,
                               gint current)
{
    gint nvalues = gwy_scatter_get_n_values(scatter);
    GwyEnum *values = g_new(GwyEnum, nvalues + 1);
    for (gint i = 0; i < nvalues; i++) {
        const gchar *label = gwy_scatter_get_data_label(scatter, i);
        values[i].name = (label && *label ? g_strdup(label) : g_strdup_printf("%s %d", _("Value"), i+1));
        values[i].value = i;
    }
    values[nvalues].name = NULL;

    GtkWidget *combo = create_enum_combo(values, NULL, nvalues, callback, cbdata, current, FALSE, TRUE);
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    info->model = g_object_ref(G_OBJECT(scatter));

    /* TODO: Connect to scatter changes and update the combo model. For this, the scatter needs to emit signals in the
     * first place. */

    return combo;
}

static GtkWidget*
create_enum_combo(GwyEnum *entries,
                  const GwyEnum *const_entries,
                  gint nentries,
                  GCallback callback,
                  gpointer cbdata,
                  gint active,
                  gboolean translate,
                  gboolean owns_strings)
{
    GtkWidget *combo = gtk_combo_box_new();
    g_return_val_if_fail(!nentries || (!entries ^ !const_entries), combo);
    g_return_val_if_fail(!owns_strings || !const_entries, combo);

    EnumComboBoxInfo *info = g_new0(EnumComboBoxInfo, 1);
    info->owns_strings = owns_strings;
    g_object_set_qdata(G_OBJECT(combo), info_quark(), info);
    g_object_weak_ref(G_OBJECT(combo), free_enum_cmbo_info, info);
    set_combo_model(GTK_COMBO_BOX(combo), entries, const_entries, nentries);

    GtkCellLayout *layout = GTK_CELL_LAYOUT(combo);
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    gtk_cell_layout_pack_start(layout, renderer, FALSE);
    if (translate)
        gtk_cell_layout_set_cell_data_func(layout, renderer, cell_translate_func, NULL, NULL);
    else
        gtk_cell_layout_add_attribute(layout, renderer, "markup", 1);

    if (!try_set_active(GTK_COMBO_BOX(combo), active))
        gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
    if (callback)
        info->handler_id = g_signal_connect(combo, "changed", callback, cbdata);

    return combo;
}

static void
set_combo_model(GtkComboBox *combo,
                GwyEnum *entries,
                const GwyEnum *const_entries,
                gint nentries)
{
    g_return_if_fail(!nentries || (!entries ^ !const_entries));
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    g_return_if_fail(info);

    /* Do not return immediately when const_entries matches the info->const_entries. Someone can be passing the same
     * array but chaning nentries. */

    GtkTreeModel *oldmodel = gtk_combo_box_get_model(combo);
    gchar *active_name = NULL;
    gboolean active_changed = TRUE;

    if (nentries < 0) {
        nentries = 0;
        if (entries) {
            while (entries[nentries].name)
                nentries++;
        }
        else if (const_entries) {
            while (const_entries[nentries].name)
                nentries++;
        }
    }

    /* Find out the name of currently selected item to preserve it. Item name works for choosers like graph curves;
     * values would only make sense if we were keeping the numerical values but giving them different names. */
    if (oldmodel) {
        g_assert(GWY_IS_INVENTORY_STORE(oldmodel));
        g_object_ref(oldmodel);
        GtkTreeIter iter;
        if (gtk_combo_box_get_active_iter(combo, &iter))
            gtk_tree_model_get(oldmodel, &iter, 1, &active_name, -1);
    }

    GtkTreeModel *newmodel = NULL;
    GwyInventoryStore *store = NULL;
    if (nentries) {
        GwyInventory *inventory = gwy_enum_inventory_new(entries ? entries : const_entries, nentries);
        store = gwy_inventory_store_new(inventory);

        g_assert(gwy_inventory_store_get_column_by_name(store, "name") == 1);
        g_assert(gwy_inventory_store_get_column_by_name(store, "value") == 2);

        newmodel = GTK_TREE_MODEL(store);
        g_object_unref(inventory);
    }

    /* Carefully try to select the same item. And if we succeed, pretend nothing has happened. Otherwise emit
     * the "changed" signal when everything is done. */
    if (info->handler_id)
        g_signal_handler_block(combo, info->handler_id);

    if (info->entries) {
        /* Entries are not reference counted. So once you pass non-const entries to this function, you do not own it
         * any more. It is an error to pass the same array again. */
        g_assert(entries != info->entries);
        gwy_enum_freev(info->entries);
    }

    info->nentries = nentries;
    if (entries)
        info->entries = entries;
    else
        info->const_entries = const_entries;

    gtk_combo_box_set_model(combo, newmodel);

    if (active_name && newmodel) {
        GtkTreeIter iter;
        if (gwy_inventory_store_get_iter(store, active_name, &iter)) {
            gtk_combo_box_set_active_iter(combo, &iter);
            active_changed = FALSE;
        }
        else if (gtk_tree_model_get_iter_first(newmodel, &iter))
            gtk_combo_box_set_active_iter(combo, &iter);
    }

    if (info->handler_id)
        g_signal_handler_unblock(combo, info->handler_id);
    if (active_changed)
        g_signal_emit_by_name(combo, "changed", NULL);

    g_clear_object(&newmodel);
    g_clear_object(&oldmodel);
}

static gboolean
try_set_active(GtkComboBox *combo, gint active)
{
    EnumComboBoxInfo *info = g_object_get_qdata(G_OBJECT(combo), info_quark());
    g_return_val_if_fail(info, FALSE);
    const GwyEnum *entries = info->const_entries ? info->const_entries : info->entries;
    for (gint i = 0; i < info->nentries; i++) {
        if (entries[i].value == active) {
            gtk_combo_box_set_active(combo, i);
            return TRUE;
        }
    }
    return FALSE;
}

static void
cell_translate_func(G_GNUC_UNUSED GtkCellLayout *cell_layout,
                    GtkCellRenderer *renderer,
                    GtkTreeModel *tree_model,
                    GtkTreeIter *iter,
                    G_GNUC_UNUSED gpointer data)
{
    const GwyEnum *enum_item;

    gtk_tree_model_get(tree_model, iter, 0, &enum_item, -1);
    g_object_set(renderer, "markup", gwy_C(enum_item->name), NULL);
}

/**
 * SECTION: combo-boxes
 * @title: Combo boxes
 * @short_description: Common combo box constructors
 * @see_also: <link linkend="libgwyui-gwyradiobuttons">gwyradiobuttons</link> -- radio button constructors
 *
 * Combo boxes can be easily constructed from #GwyEnum's with gwy_enum_combo_box_new().   Here's an example of
 * construction of a combo box with three items:
 * <informalexample><programlisting>
 * typedef enum {
 *     MY_ENUM_FOO, MY_ENUM_BAR, MY_ENUM_BAZ
 * } MyEnum;
 *
 * static GwyEnum my_enum_fields[] = {
 *     { N_("Foo"), MY_ENUM_FOO },
 *     { N_("Bar"), MY_ENUM_BAR },
 *     { N_("Baz"), MY_ENUM_BAZ },
 * };
 *
 * static void
 * menu_callback(GtkWidget *combo, gpointer cbdata)
 * {
 *     MyEnum value;
 *
 *     value = gwy_enum_combo_box_get_active(GTK_COMBO_BOX(combo));
 *     ...
 * }
 *
 * static void
 * function(void) {
 *     GtkWidget *combo;
 *     ...
 *
 *     combo = gwy_enum_combo_box_new(fields, G_N_ELEMENTS(fields),
 *                                    G_CALLBACK(menu_callback), NULL,
 *                                    MY_ENUM_FOO, TRUE);
 *     ...
 * }
 * </programlisting></informalexample>
 *
 * Many common Gwyddion enumerations have companion function returning corresponding #GwyEnum, see for example <link
 * linkend="libgwyprocess-gwyprocessenums">gwyprocessenums</link>, making combo box construction even easier.
 *
 * For example, a combo box with possible interpolation types can be constructed:
 * <informalexample><programlisting>
 * combo = gwy_enum_combo_box_new(gwy_interpolation_type_get_enum(), -1,
 *                                G_CALLBACK(menu_callback), NULL,
 *                                GWY_INTERPOLATION_LINEAR, TRUE);
 * </programlisting></informalexample>
 **/

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