/*
 *  $Id: radio-buttons.c 28812 2025-11-05 18:59:42Z yeti-dn $
 *  Copyright (C) 2003-2024 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 <gtk/gtk.h>

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

#include "libgwyui/radio-buttons.h"

static G_DEFINE_QUARK(gwy-radiobuttons-key, radiobuttons)

static GSList*
gwy_radio_buttons_create_real(const GwyEnum *entries,
                              gint nentries,
                              GCallback callback,
                              gpointer cbdata,
                              gint current,
                              gboolean translate)
{
    if (nentries < 0) {
        for (nentries = 0; entries[nentries].name != NULL; nentries++)
            ;
    }

    GtkWidget *button = NULL, *curbutton = NULL;
    /* XXX: this relies on undocumented GtkRadioButton behaviour; we assume it puts the items into the group in the
     * reverse order. */
    for (gint i = nentries-1; i >= 0; i--) {
        const gchar *name = translate ? gwy_C(entries[i].name) : entries[i].name;
        GtkRadioButton *grb = GTK_RADIO_BUTTON(button);
        button = gtk_radio_button_new_with_mnemonic_from_widget(grb, name);
        g_object_set_qdata(G_OBJECT(button), radiobuttons_quark(), GINT_TO_POINTER(entries[i].value));
        if (entries[i].value == current)
            curbutton = button;
    }
    gwy_debug("current: %p", curbutton);

    if (curbutton)
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(curbutton), TRUE);

    if (callback) {
        for (GSList *group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); group; group = g_slist_next(group))
            g_signal_connect(group->data, "clicked", callback, cbdata);
    }

    return gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
}

/**
 * gwy_radio_buttons_create:
 * @entries: Radio button group items.
 * @nentries: The number of items.  A negative value means that @entries is terminated with a %NULL-named item.
 * @callback: A callback called when a button is selected (or %NULL for no callback).
 * @cbdata: User data passed to the callback.
 * @current: Value to be shown as currently selected (-1 to use what happens to be first).
 *
 * Creates a radio button group from an enum.
 *
 * Try to avoid -1 as an enum value.
 *
 * Returns: The newly created radio button group (a #GSList).  Iterate over the list and pack the widgets (the order
 *          is the same as in @entries).  The group is owned by the buttons and must not be freed.
 **/
GSList*
gwy_radio_buttons_create(const GwyEnum *entries,
                         gint nentries,
                         GCallback callback,
                         gpointer cbdata,
                         gint current)
{
    return gwy_radio_buttons_create_real(entries, nentries, callback, cbdata, current, TRUE);
}

/**
 * gwy_radio_buttons_createl:
 * @callback: A callback called when a button is selected (or %NULL for no callback).
 * @cbdata: User data passed to the callback.
 * @current: Value to be shown as currently selected (-1 to use what happens to be first).
 * @...: First item label, first item value, second item label, second item value, etc.  Terminated with %NULL.
 *
 * Creates a radio button group from a list of label/value pairs.
 *
 * Returns: The newly created radio button group (a #GSList).  Iterate over the list and pack the widgets (the order
 *          is the same as in @entries).  The group is owned by the buttons and must not be freed.
 **/
GSList*
gwy_radio_buttons_createl(GCallback callback,
                          gpointer cbdata,
                          gint current,
                          ...)
{
    va_list ap;
    va_start(ap, current);

    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, current);
    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);

    GSList *group = gwy_radio_buttons_create_real(entries, nentries, callback, cbdata, current, FALSE);
    g_free(entries);

    return group;
}

/**
 * gwy_radio_buttons_attach_to_grid:
 * @group: A radio button group.  Not necessarily created by gwy_radio_buttons_create().
 * @grid: A grid widget.
 * @colspan: The number of columns the radio buttons should span across.
 * @row: Table row to start attaching at.
 *
 * Attaches a group of radio buttons to grid rows.
 *
 * Returns: The row after the last attached radio button.
 **/
gint
gwy_radio_buttons_attach_to_grid(GSList *group,
                                 GtkGrid *grid,
                                 gint colspan,
                                 gint row)
{
    g_return_val_if_fail(GTK_IS_GRID(grid), row);

    while (group) {
        GtkWidget *widget = GTK_WIDGET(group->data);
        gtk_grid_attach(grid, widget, 0, row, colspan, 1);
        row++;
        group = g_slist_next(group);
    }

    return row;
}

/**
 * gwy_radio_buttons_set_current:
 * @group: A radio button group created by gwy_radio_buttons_create().
 * @current: Value to be shown as currently selected.
 *
 * Sets currently selected radio button in @group based on integer item object
 * data (as set by gwy_radio_buttons_create()).
 *
 * Returns: %TRUE if current button was set, %FALSE if @current was not found.
 **/
gboolean
gwy_radio_buttons_set_current(GSList *group,
                              gint current)
{
    GtkWidget *button;

    if (!(button = gwy_radio_buttons_find(group, current)))
        return FALSE;

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
    return TRUE;
}

static inline gint
radio_button_value(gpointer button)
{
    g_return_val_if_fail(GTK_IS_RADIO_BUTTON(button), -1);
    gpointer value = g_object_get_qdata(G_OBJECT(button), radiobuttons_quark());
    return GPOINTER_TO_INT(value);
}

/**
 * gwy_radio_buttons_get_current:
 * @group: A radio button group created by gwy_radio_buttons_create().
 *
 * Gets the integer enum value corresponding to currently selected item.
 *
 * Returns: The enum value corresponding to currently selected item.  In the case of failure -1 is returned.
 **/
gint
gwy_radio_buttons_get_current(GSList *group)
{
    while (group) {
        if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data)))
            return radio_button_value(group->data);
        group = g_slist_next(group);
    }

    return -1;
}

/**
 * gwy_radio_buttons_find:
 * @group: A radio button group created by gwy_radio_buttons_create().
 * @value: The value associated with the button to find.
 *
 * Finds a radio button by its associated integer value.
 *
 * Returns: The radio button corresponding to @value, or %NULL on failure.
 **/
GtkWidget*
gwy_radio_buttons_find(GSList *group,
                       gint value)
{
    while (group) {
        if (radio_button_value(group->data) == value)
            return GTK_WIDGET(group->data);
        group = g_slist_next(group);
    }

    return NULL;
}

/**
 * gwy_radio_buttons_set_sensitive:
 * @group: A radio button group.  Not necessarily created by gwy_radio_buttons_create().
 * @sensitive: %TRUE to make all choices sensitive, %FALSE to make all insensitive.
 *
 * Sets the sensitivity of all radio buttons in a group.
 *
 * This function is useful to make the choice as a whole available/unavailable. Use gwy_radio_buttons_find() combined
 * with gtk_widget_set_sensitive() to manage sensitivity of individual options.
 **/
void
gwy_radio_buttons_set_sensitive(GSList *group,
                                gboolean sensitive)
{
    while (group) {
        g_return_if_fail(GTK_IS_RADIO_BUTTON(group->data));
        gtk_widget_set_sensitive(GTK_WIDGET(group->data), sensitive);
        group = g_slist_next(group);
    }
}

/**
 * gwy_radio_button_get_value:
 * @button: A radio button belonging to a group created by gwy_radio_buttons_create().
 *
 * Gets the integer value associated with a radio button.
 *
 * Returns: The integer value corresponding to @button.
 **/
gint
gwy_radio_button_get_value(GtkWidget *button)
{
    return radio_button_value(button);
}

/**
 * gwy_radio_button_set_value:
 * @button: A radio button to set associated value of.
 * @value: Value to associate.
 *
 * Sets the integer value associated with a radio button.
 *
 * This function allow to change associated radio button values after creation or even construct a radio button group
 * with associated integers without the help of gwy_radio_buttons_create().
 **/
void
gwy_radio_button_set_value(GtkWidget *button,
                           gint value)
{
    g_return_if_fail(GTK_IS_RADIO_BUTTON(button));
    g_object_set_qdata(G_OBJECT(button), radiobuttons_quark(), GINT_TO_POINTER(value));
}

/**
 * SECTION: radio-buttons
 * @title: Radiobuttons
 * @short_description: Radio button constructors for enums
 * @see_also: <link linkend="libgwyui-gwycombobox">gwycombobox</link>
 *            -- combo box constructors;
 *            <link linkend="libgwyui-check-boxes">checkboxes</link>
 *            -- check box constructors
 *
 * Groups of buttons associated with some integers can be easily constructed from #GwyEnum's with
 * gwy_radio_buttons_create().
 **/

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