From 41201f08657d578566eff8a2440168bbb08f91ec Mon Sep 17 00:00:00 2001 From: Jehan Date: Fri, 10 Jun 2022 22:58:43 +0200 Subject: [PATCH] libgimpwidgets: new GimpIntRadioFrame object. A proper class for a frame containing radio buttons. This object has a "value" property and will therefore be very easy to use in various API binding a config property to a widget property. A GimpIntRadioFrame is also what gimp_prop_int_radio_frame_new() will return now. gimp_prop_int_radio_box_new() on the other hand is being removed. It is used nowhere and is unneeded. If someone really needs a non-labelled group of radio buttons, they can also create a GimpIntRadioFrame, remove the label and hide the borders. At least they will still be able to easily bind it to a config property. --- libgimpwidgets/Makefile.gi | 2 + libgimpwidgets/gimpintradioframe.c | 790 +++++++++++++++++++++++++++++ libgimpwidgets/gimpintradioframe.h | 128 +++++ libgimpwidgets/gimppropwidgets.c | 117 +---- libgimpwidgets/gimppropwidgets.h | 3 - libgimpwidgets/gimpwidgets.def | 1 - libgimpwidgets/gimpwidgets.h | 1 + libgimpwidgets/gimpwidgetstypes.h | 1 + libgimpwidgets/meson.build | 2 + 9 files changed, 938 insertions(+), 107 deletions(-) create mode 100644 libgimpwidgets/gimpintradioframe.c create mode 100644 libgimpwidgets/gimpintradioframe.h diff --git a/libgimpwidgets/Makefile.gi b/libgimpwidgets/Makefile.gi index d65c790c06..df4d0069bf 100644 --- a/libgimpwidgets/Makefile.gi +++ b/libgimpwidgets/Makefile.gi @@ -33,6 +33,7 @@ libgimpwidgets_introspectable_headers = \ ../libgimpwidgets/gimphintbox.h \ ../libgimpwidgets/gimpicons.h \ ../libgimpwidgets/gimpintcombobox.h \ + ../libgimpwidgets/gimpintradioframe.h \ ../libgimpwidgets/gimpintstore.h \ ../libgimpwidgets/gimplabelcolor.h \ ../libgimpwidgets/gimplabeled.h \ @@ -100,6 +101,7 @@ libgimpwidgets_introspectable = \ ../libgimpwidgets/gimphintbox.c \ ../libgimpwidgets/gimpicons.c \ ../libgimpwidgets/gimpintcombobox.c \ + ../libgimpwidgets/gimpintradioframe.c \ ../libgimpwidgets/gimpintstore.c \ ../libgimpwidgets/gimplabelcolor.c \ ../libgimpwidgets/gimplabeled.c \ diff --git a/libgimpwidgets/gimpintradioframe.c b/libgimpwidgets/gimpintradioframe.c new file mode 100644 index 0000000000..2e4eec3df0 --- /dev/null +++ b/libgimpwidgets/gimpintradioframe.c @@ -0,0 +1,790 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpintradioframe.c + * Copyright (C) 2022 Jehan + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#include "config.h" + +#include + +#include + +#include "libgimpbase/gimpbase.h" + +#include "gimpwidgetstypes.h" + +#include "gimpintradioframe.h" +#include "gimpintstore.h" + + +/** + * SECTION: gimpintradioframe + * @title: GimpIntRadioFrame + * @short_description: A widget providing radio buttons for integer + * values (e.g. enums). + * + * A widget providing a frame with title, containing grouped radio + * buttons, each associated with an integer value and random user data. + **/ + + +enum +{ + PROP_0, + PROP_VALUE, + PROP_STORE, +}; + + +typedef struct _GimpIntRadioFramePrivate GimpIntRadioFramePrivate; + +struct _GimpIntRadioFramePrivate +{ + gchar *label; + GimpIntStore *store; + GSList *group; + gint value; + + GtkWidget *box; + + GimpIntRadioFrameSensitivityFunc sensitivity_func; + gpointer sensitivity_data; + GDestroyNotify sensitivity_destroy; +}; + +#define GET_PRIVATE(obj) ((GimpIntRadioFramePrivate *) gimp_int_radio_frame_get_instance_private ((GimpIntRadioFrame *) obj)) + + +static void gimp_int_radio_frame_constructed (GObject *object); +static void gimp_int_radio_frame_finalize (GObject *object); +static void gimp_int_radio_frame_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_int_radio_frame_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gboolean gimp_int_radio_frame_draw (GtkWidget *widget, + cairo_t *cr, + gpointer user_data); + +static void gimp_int_radio_frame_fill (GimpIntRadioFrame *frame); +static void gimp_int_radio_frame_set_store (GimpIntRadioFrame *frame, + GimpIntStore *store); +static void gimp_int_radio_frame_update_sensitivity (GimpIntRadioFrame *frame); + +static void gimp_int_radio_frame_button_toggled (GtkToggleButton *button, + GimpIntRadioFrame *frame); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpIntRadioFrame, gimp_int_radio_frame, + GIMP_TYPE_FRAME) + +#define parent_class gimp_int_radio_frame_parent_class + + +static void +gimp_int_radio_frame_class_init (GimpIntRadioFrameClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_int_radio_frame_constructed; + object_class->finalize = gimp_int_radio_frame_finalize; + object_class->set_property = gimp_int_radio_frame_set_property; + object_class->get_property = gimp_int_radio_frame_get_property; + + /** + * GimpIntRadioFrame:value: + * + * The active value + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, PROP_VALUE, + g_param_spec_int ("value", + "Value", + "Value of active item", + G_MININT, G_MAXINT, 0, + GIMP_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY)); + + /** + * GimpIntRadioFrame:store: + * + * The %GimpIntStore from which the radio frame takes the values shown + * in the list. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_STORE, + g_param_spec_object ("store", + "GimpRadioFrame int store", + "The int store for the radio frame", + GIMP_TYPE_INT_STORE, + GIMP_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY)); +} + +static void +gimp_int_radio_frame_init (GimpIntRadioFrame *radio_frame) +{ + GimpIntRadioFramePrivate *priv = GET_PRIVATE (radio_frame); + + priv->box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_container_add (GTK_CONTAINER (radio_frame), priv->box); + gtk_widget_show (GTK_WIDGET (priv->box)); +} + +static void +gimp_int_radio_frame_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + g_signal_connect (object, "draw", + G_CALLBACK (gimp_int_radio_frame_draw), + NULL); +} + +static void +gimp_int_radio_frame_finalize (GObject *object) +{ + GimpIntRadioFramePrivate *priv = GET_PRIVATE (object); + + g_clear_pointer (&priv->label, g_free); + g_clear_object (&priv->store); + g_clear_pointer (&priv->group, g_slist_free); + + if (priv->sensitivity_destroy) + { + GDestroyNotify d = priv->sensitivity_destroy; + + priv->sensitivity_destroy = NULL; + d (priv->sensitivity_data); + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_int_radio_frame_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_VALUE: + gimp_int_radio_frame_set_active (GIMP_INT_RADIO_FRAME (object), + g_value_get_int (value)); + break; + case PROP_STORE: + gimp_int_radio_frame_set_store (GIMP_INT_RADIO_FRAME (object), + g_value_get_object (value)); + break; + + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_int_radio_frame_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpIntRadioFramePrivate *priv = GET_PRIVATE (object); + + switch (property_id) + { + case PROP_VALUE: + g_value_set_int (value, priv->value); + break; + case PROP_STORE: + g_value_set_object (value, priv->store); + break; + + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/* Public functions */ + +/** + * gimp_int_radio_frame_new_from_store: + * @title: the frame label. + * @store: (transfer full): the %GimpIntStore to generate radio buttons + * from. + * + * Creates a %GimpIntRadioFrame containing radio buttons for each item + * in the @store. The created widget takes ownership of @store. + * + * If you need to construct an empty #GimpIntRadioFrame, it's best to use + * g_object_new (GIMP_TYPE_INT_RADIO_FRAME, NULL). + * + * Returns: a new #GimpIntRadioFrame. + * + * Since: 3.0 + **/ +GtkWidget * +gimp_int_radio_frame_new_from_store (const gchar *title, + GimpIntStore *store) +{ + GtkWidget *radio_frame; + + radio_frame = g_object_new (GIMP_TYPE_INT_RADIO_FRAME, + "label", title, + "store", store, + NULL); + + return radio_frame; +} + +/** + * gimp_int_radio_frame_new: (skip) + * @first_label: the label of the first item + * @first_value: the value of the first item + * @...: a %NULL terminated list of more label, value pairs + * + * Creates a GtkComboBox that has integer values associated with each + * item. The items to fill the combo box with are specified as a %NULL + * terminated list of label/value pairs. + * + * If you need to construct an empty #GimpIntRadioFrame, it's best to use + * g_object_new (GIMP_TYPE_INT_RADIO_FRAME, NULL). + * + * Returns: a new #GimpIntRadioFrame. + * + * Since: 3.0 + **/ +GtkWidget * +gimp_int_radio_frame_new (const gchar *first_label, + gint first_value, + ...) +{ + GtkWidget *radio_frame; + va_list args; + + va_start (args, first_value); + + radio_frame = gimp_int_radio_frame_new_valist (first_label, first_value, args); + + va_end (args); + + return radio_frame; +} + +/** + * gimp_int_radio_frame_new_valist: (skip) + * @first_label: the label of the first item + * @first_value: the value of the first item + * @values: a va_list with more values + * + * A variant of gimp_int_radio_frame_new() that takes a va_list of + * label/value pairs. + * + * Returns: a new #GimpIntRadioFrame. + * + * Since: 3.0 + **/ +GtkWidget * +gimp_int_radio_frame_new_valist (const gchar *first_label, + gint first_value, + va_list values) +{ + GtkWidget *radio_frame; + GtkListStore *store; + + store = gimp_int_store_new_valist (first_label, first_value, values); + + radio_frame = g_object_new (GIMP_TYPE_INT_RADIO_FRAME, + "store", store, + NULL); + + return radio_frame; +} + +/** + * gimp_int_radio_frame_new_array: (rename-to gimp_int_radio_frame_new) + * @labels: (array zero-terminated=1): a %NULL-terminated array of labels. + * + * A variant of gimp_int_radio_frame_new() that takes an array of labels. + * The array indices are used as values. + * + * Returns: a new #GimpIntRadioFrame. + * + * Since: 3.0 + **/ +GtkWidget * +gimp_int_radio_frame_new_array (const gchar *labels[]) +{ + GtkWidget *frame; + GimpIntRadioFramePrivate *priv; + GtkListStore *store; + gint i; + + g_return_val_if_fail (labels != NULL, NULL); + + frame = g_object_new (GIMP_TYPE_INT_RADIO_FRAME, NULL); + priv = GET_PRIVATE (frame); + store = GTK_LIST_STORE (priv->store); + + for (i = 0; labels[i] != NULL; i++) + { + GtkTreeIter iter; + + if (labels[i]) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + GIMP_INT_STORE_VALUE, i, + GIMP_INT_STORE_LABEL, labels[i], + -1); + } + } + + return frame; +} + +/** + * gimp_int_radio_frame_prepend: (skip) + * @radio_frame: a #GimpIntRadioFrame + * @...: pairs of column number and value, terminated with -1 + * + * This function provides a convenient way to prepend items to a + * #GimpIntRadioFrame. It prepends a row to the @radio_frame's list store + * and calls gtk_list_store_set() for you. + * + * The column number must be taken from the enum #GimpIntStoreColumns. + * + * Since: 3.0 + **/ +void +gimp_int_radio_frame_prepend (GimpIntRadioFrame *radio_frame, + ...) +{ + GtkListStore *store; + GimpIntRadioFramePrivate *priv; + GtkTreeIter iter; + va_list args; + + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (radio_frame)); + + priv = GET_PRIVATE (radio_frame); + store = GTK_LIST_STORE (priv->store); + + va_start (args, radio_frame); + + gtk_list_store_prepend (store, &iter); + gtk_list_store_set_valist (store, &iter, args); + + va_end (args); +} + +/** + * gimp_int_radio_frame_append: (skip) + * @radio_frame: a #GimpIntRadioFrame + * @...: pairs of column number and value, terminated with -1 + * + * This function provides a convenient way to append items to a + * #GimpIntRadioFrame. It appends a row to the @radio_frame's list store + * and calls gtk_list_store_set() for you. + * + * The column number must be taken from the enum #GimpIntStoreColumns. + * + * Since: 3.0 + **/ +void +gimp_int_radio_frame_append (GimpIntRadioFrame *radio_frame, + ...) +{ + GtkListStore *store; + GimpIntRadioFramePrivate *priv; + GtkTreeIter iter; + va_list args; + + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (radio_frame)); + + priv = GET_PRIVATE (radio_frame); + store = GTK_LIST_STORE (priv->store); + + va_start (args, radio_frame); + + gtk_list_store_append (store, &iter); + gtk_list_store_set_valist (store, &iter, args); + + va_end (args); +} + +/** + * gimp_int_radio_frame_set_active: + * @radio_frame: a #GimpIntRadioFrame + * @value: an integer value + * + * Looks up the item that belongs to the given @value and makes it the + * selected item in the @radio_frame. + * + * Returns: %TRUE on success (value changed or not) or %FALSE if there + * was no item for this value. + * + * Since: 3.0 + **/ +gboolean +gimp_int_radio_frame_set_active (GimpIntRadioFrame *frame, + gint value) +{ + GimpIntRadioFramePrivate *priv = GET_PRIVATE (frame); + GtkWidget *button; + GSList *iter = priv->group; + + g_return_val_if_fail (GIMP_IS_INT_RADIO_FRAME (frame), FALSE); + + for (; iter; iter = g_slist_next (iter)) + { + button = GTK_WIDGET (iter->data); + + if (g_object_get_data (G_OBJECT (button), "gimp-radio-frame-value") == + GINT_TO_POINTER (value)) + { + if (! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + return TRUE; + } + } + + return FALSE; +} + +/** + * gimp_int_radio_frame_get_active: + * @radio_frame: a #GimpIntRadioFrame + * + * Returns: the value of the active item. + * + * Since:3.0 + **/ +gint +gimp_int_radio_frame_get_active (GimpIntRadioFrame *frame) +{ + GimpIntRadioFramePrivate *priv = GET_PRIVATE (frame); + + g_return_val_if_fail (GIMP_IS_INT_RADIO_FRAME (frame), FALSE); + + return priv->value; +} + +/** + * gimp_int_radio_frame_set_active_by_user_data: + * @radio_frame: a #GimpIntRadioFrame + * @user_data: an integer value + * + * Looks up the item that has the given @user_data and makes it the + * selected item in the @radio_frame. + * + * Returns: %TRUE on success or %FALSE if there was no item for + * this user-data. + * + * Since: 3.0 + **/ +gboolean +gimp_int_radio_frame_set_active_by_user_data (GimpIntRadioFrame *radio_frame, + gpointer user_data) +{ + GimpIntRadioFramePrivate *priv; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_INT_RADIO_FRAME (radio_frame), FALSE); + + priv = GET_PRIVATE (radio_frame); + + if (gimp_int_store_lookup_by_user_data (GTK_TREE_MODEL (priv->store), user_data, &iter)) + { + gint value; + + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter, + GIMP_INT_STORE_VALUE, &value, + -1); + gimp_int_radio_frame_set_active (radio_frame, value); + + return TRUE; + } + + return FALSE; +} + +/** + * gimp_int_radio_frame_get_active_user_data: + * @radio_frame: a #GimpIntRadioFrame + * @user_data: (out) (transfer none): return location for the gpointer value + * + * Retrieves the user-data of the selected (active) item in the @radio_frame. + * + * Returns: %TRUE if @user_data has been set or %FALSE if no item was + * active. + * + * Since: 3.0 + **/ +gboolean +gimp_int_radio_frame_get_active_user_data (GimpIntRadioFrame *radio_frame, + gpointer *user_data) +{ + GimpIntRadioFramePrivate *priv; + GtkTreeIter iter; + + g_return_val_if_fail (GIMP_IS_INT_RADIO_FRAME (radio_frame), FALSE); + g_return_val_if_fail (user_data != NULL, FALSE); + + priv = GET_PRIVATE (radio_frame); + + if (gimp_int_store_lookup_by_value (GTK_TREE_MODEL (priv->store), priv->value, &iter)) + { + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter, + GIMP_INT_STORE_USER_DATA, user_data, + -1); + return TRUE; + } + + return FALSE; +} + +/** + * gimp_int_radio_frame_set_sensitivity: + * @radio_frame: a #GimpIntRadioFrame + * @func: a function that returns a boolean value, or %NULL to unset + * @data: data to pass to @func + * @destroy: destroy notification for @data + * + * Sets a function that is used to decide about the sensitivity of radio + * buttons in the @radio_frame. Use this if you want to set certain + * radio buttons insensitive. + * + * Calling gtk_widget_queue_draw() on the @radio_frame will cause the + * sensitivity to be updated. + * + * Since: 3.0 + **/ +void +gimp_int_radio_frame_set_sensitivity (GimpIntRadioFrame *radio_frame, + GimpIntRadioFrameSensitivityFunc func, + gpointer data, + GDestroyNotify destroy) +{ + GimpIntRadioFramePrivate *priv; + + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (radio_frame)); + + priv = GET_PRIVATE (radio_frame); + + if (priv->sensitivity_destroy) + { + GDestroyNotify destroy = priv->sensitivity_destroy; + + priv->sensitivity_destroy = NULL; + destroy (priv->sensitivity_data); + } + + priv->sensitivity_func = func; + priv->sensitivity_data = data; + priv->sensitivity_destroy = destroy; +} + + +/* Private functions */ + +static gboolean +gimp_int_radio_frame_draw (GtkWidget *widget, + cairo_t *cr, + gpointer user_data) +{ + gimp_int_radio_frame_update_sensitivity (GIMP_INT_RADIO_FRAME (widget)); + + return FALSE; +} + +static void +gimp_int_radio_frame_fill (GimpIntRadioFrame *frame) +{ + GimpIntRadioFramePrivate *priv; + + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (frame)); + + priv = GET_PRIVATE (frame); + + g_clear_pointer (&priv->group, g_slist_free); + gtk_container_foreach (GTK_CONTAINER (priv->box), + (GtkCallback) gtk_widget_destroy, NULL); + + if (priv->store) + { + GtkTreeModel *model; + GSList *group = NULL; + GtkTreeIter iter; + gboolean iter_valid; + + model = GTK_TREE_MODEL (priv->store); + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + GtkWidget *button; + gchar *label; + gint value; + + gtk_tree_model_get (model, &iter, + GIMP_INT_STORE_LABEL, &label, + GIMP_INT_STORE_VALUE, &value, + -1); + + button = gtk_radio_button_new_with_mnemonic (group, label); + gtk_box_pack_start (GTK_BOX (priv->box), button, FALSE, FALSE, 0); + gtk_widget_show (button); + + g_free (label); + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + + g_object_set_data (G_OBJECT (button), "gimp-radio-frame-value", + GINT_TO_POINTER (value)); + + g_signal_connect (button, "toggled", + G_CALLBACK (gimp_int_radio_frame_button_toggled), + frame); + } + priv->group = g_slist_copy (group); + + gimp_int_radio_frame_set_active (frame, priv->value); + } +} + +static void +gimp_int_radio_frame_set_store (GimpIntRadioFrame *frame, + GimpIntStore *store) +{ + GimpIntRadioFramePrivate *priv; + + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (frame)); + g_return_if_fail (GIMP_IS_INT_STORE (store)); + + priv = GET_PRIVATE (frame); + + if (priv->store == store) + return; + + if (priv->store) + { + g_signal_handlers_disconnect_by_func (priv->store, + (GCallback) gimp_int_radio_frame_fill, + NULL); + g_object_unref (priv->store); + } + + priv->store = store; + + if (priv->store) + { + g_signal_connect_object (priv->store, "row-changed", + (GCallback) gimp_int_radio_frame_fill, + frame, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->store, "row-deleted", + (GCallback) gimp_int_radio_frame_fill, + frame, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->store, "row-inserted", + (GCallback) gimp_int_radio_frame_fill, + frame, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->store, "rows-reordered", + (GCallback) gimp_int_radio_frame_fill, + frame, G_CONNECT_SWAPPED); + } + + gimp_int_radio_frame_fill (frame); + + g_object_notify (G_OBJECT (frame), "store"); +} + +static void +gimp_int_radio_frame_update_sensitivity (GimpIntRadioFrame *frame) +{ + GimpIntRadioFramePrivate *priv = GET_PRIVATE (frame); + GSList *iter = priv->group; + + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (frame)); + + if (! priv->sensitivity_func) + return; + + for (; iter; iter = g_slist_next (iter)) + { + GtkWidget *button = GTK_WIDGET (iter->data); + GtkTreeIter tree_iter; + gint value; + + value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "gimp-radio-frame-value")); + + gtk_widget_set_sensitive (button, TRUE); + if (gimp_int_store_lookup_by_value (GTK_TREE_MODEL (priv->store), value, &tree_iter)) + { + gpointer user_data; + gint new_value = value; + + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &tree_iter, + GIMP_INT_STORE_USER_DATA, &user_data, + -1); + if (! priv->sensitivity_func (value, user_data, &new_value, + priv->sensitivity_data)) + { + if (new_value != value) + gimp_int_radio_frame_set_active (frame, new_value); + + gtk_widget_set_sensitive (button, FALSE); + } + } + } +} + +static void +gimp_int_radio_frame_button_toggled (GtkToggleButton *button, + GimpIntRadioFrame *frame) +{ + g_return_if_fail (GIMP_IS_INT_RADIO_FRAME (frame)); + g_return_if_fail (GTK_IS_RADIO_BUTTON (button)); + + if (gtk_toggle_button_get_active (button)) + { + GimpIntRadioFramePrivate *priv = GET_PRIVATE (frame); + gint value; + + value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "gimp-radio-frame-value")); + + if (priv->value != value) + { + priv->value = value; + g_object_notify (G_OBJECT (frame), "value"); + } + } +} diff --git a/libgimpwidgets/gimpintradioframe.h b/libgimpwidgets/gimpintradioframe.h new file mode 100644 index 0000000000..fed2e6d73f --- /dev/null +++ b/libgimpwidgets/gimpintradioframe.h @@ -0,0 +1,128 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpintradioframe.h + * Copyright (C) 2022 Jehan + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#if !defined (__GIMP_WIDGETS_H_INSIDE__) && !defined (GIMP_WIDGETS_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +#ifndef __GIMP_INT_RADIO_FRAME_H__ +#define __GIMP_INT_RADIO_FRAME_H__ + +G_BEGIN_DECLS + + +#define GIMP_TYPE_INT_RADIO_FRAME (gimp_int_radio_frame_get_type ()) +#define GIMP_INT_RADIO_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INT_RADIO_FRAME, GimpIntRadioFrame)) +#define GIMP_INT_RADIO_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INT_RADIO_FRAME, GimpIntRadioFrameClass)) +#define GIMP_IS_INT_RADIO_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INT_RADIO_FRAME)) +#define GIMP_IS_INT_RADIO_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INT_RADIO_FRAME)) +#define GIMP_INT_RADIO_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INT_RADIO_FRAME, GimpIntRadioFrameClass)) + + +typedef struct _GimpIntRadioFrameClass GimpIntRadioFrameClass; + +struct _GimpIntRadioFrame +{ + GimpFrame parent_instance; +}; + +struct _GimpIntRadioFrameClass +{ + GimpFrameClass parent_class; + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); + void (* _gimp_reserved5) (void); + void (* _gimp_reserved6) (void); + void (* _gimp_reserved7) (void); + void (* _gimp_reserved8) (void); +}; + + +/** + * GimpIntRadioFrameSensitivityFunc: + * @value: the value associated with a radio button. + * @user_data: the data associated with a radio button. + * @new_value: the value to check instead if the function returns %FALSE. + * @data: (closure): the data set in gimp_int_radio_frame_set_sensitivity() + * + * Signature for a function called on each radio button value and data, + * each time the %GimpIntRadioFrame is drawn, to make some radio button + * insensitive. + * If the function returns %FALSE, it usually means that the value is + * not a valid choice in current situation. In this case, you might want + * to toggle instead another value automatically. Set @new_value to the + * value to toggle. If you leave this untouched, the radio button will + * stay toggled despite being insensitive. This is up to you to decide + * whether this is meaningful. + * + * Returns: %TRUE if the button stays sensitive, %FALSE otherwise. + */ +typedef gboolean (* GimpIntRadioFrameSensitivityFunc) (gint value, + gpointer user_data, + gint *new_value, + gpointer data); + + + +GType gimp_int_radio_frame_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_int_radio_frame_new_from_store (const gchar *title, + GimpIntStore *store); +GtkWidget * gimp_int_radio_frame_new (const gchar *first_label, + gint first_value, + ...) G_GNUC_NULL_TERMINATED; +GtkWidget * gimp_int_radio_frame_new_valist (const gchar *first_label, + gint first_value, + va_list values); + +GtkWidget * gimp_int_radio_frame_new_array (const gchar *labels[]); + +void gimp_int_radio_frame_prepend (GimpIntRadioFrame *radio_frame, + ...); +void gimp_int_radio_frame_append (GimpIntRadioFrame *radio_frame, + ...); + +gboolean gimp_int_radio_frame_set_active (GimpIntRadioFrame *radio_frame, + gint value); +gint gimp_int_radio_frame_get_active (GimpIntRadioFrame *radio_frame); + +gboolean + gimp_int_radio_frame_set_active_by_user_data (GimpIntRadioFrame *radio_frame, + gpointer user_data); +gboolean + gimp_int_radio_frame_get_active_user_data (GimpIntRadioFrame *radio_frame, + gpointer *user_data); + +void gimp_int_radio_frame_set_sensitivity (GimpIntRadioFrame *radio_frame, + GimpIntRadioFrameSensitivityFunc func, + gpointer data, + GDestroyNotify destroy); + + +G_END_DECLS + +#endif /* __GIMP_INT_RADIO_FRAME_H__ */ diff --git a/libgimpwidgets/gimppropwidgets.c b/libgimpwidgets/gimppropwidgets.c index 1736123ecd..ed43aa2c5d 100644 --- a/libgimpwidgets/gimppropwidgets.c +++ b/libgimpwidgets/gimppropwidgets.c @@ -991,63 +991,9 @@ gimp_prop_int_radio_frame_new (GObject *config, const gchar *title, GimpIntStore *store) { - GParamSpec *param_spec; - GtkWidget *frame; - GtkWidget *box; - - g_return_val_if_fail (G_IS_OBJECT (config), NULL); - g_return_val_if_fail (G_IS_OBJECT (config), NULL); - g_return_val_if_fail (GIMP_IS_INT_STORE (store), NULL); - - param_spec = check_param_spec_w (config, property_name, - G_TYPE_PARAM_INT, G_STRFUNC); - if (! param_spec) - return NULL; - - if (! title) - title = g_param_spec_get_nick (param_spec); - - frame = gimp_frame_new (title); - - box = gimp_prop_int_radio_box_new (config, property_name, store); - gtk_container_add (GTK_CONTAINER (frame), box); - gtk_widget_show (box); - - gimp_widget_set_bound_property (frame, config, property_name); - - gtk_widget_show (frame); - - return frame; -} - -/** - * gimp_prop_int_radio_box_new: - * @config: Object to which property is attached. - * @property_name: Name of enum property controlled by the radio buttons. - * @store: #GimpIntStore holding list of labels, values, etc. - * - * Creates a group of radio buttons which function to set and display - * the specified int property. If you want to assign a label to the - * group of radio buttons, use gimp_prop_int_radio_frame_new() - * instead of this function. - * - * Returns: (transfer full): A #GtkBox containing the radio buttons. - * - * Since: 3.0 - */ -GtkWidget * -gimp_prop_int_radio_box_new (GObject *config, - const gchar *property_name, - GimpIntStore *store) -{ - GParamSpec *param_spec; - GtkWidget *vbox; - GtkWidget *button = NULL; - GtkTreeModel *model; - GtkTreeIter iter; - gboolean iter_valid; - GSList *group = NULL; - gint value; + GParamSpec *param_spec; + const gchar *tooltip; + GtkWidget *frame; g_return_val_if_fail (G_IS_OBJECT (config), NULL); g_return_val_if_fail (property_name != NULL, NULL); @@ -1058,56 +1004,21 @@ gimp_prop_int_radio_box_new (GObject *config, if (! param_spec) return NULL; - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + if (! title) + title = g_param_spec_get_nick (param_spec); - model = GTK_TREE_MODEL (store); + tooltip = g_param_spec_get_blurb (param_spec); - for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); - iter_valid; - iter_valid = gtk_tree_model_iter_next (model, &iter)) - { - gchar *label; + frame = gimp_int_radio_frame_new_from_store (title, store); + g_object_bind_property (config, property_name, + frame, "value", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + gimp_help_set_help_data (frame, tooltip, NULL); + gtk_widget_show (frame); - gtk_tree_model_get (model, &iter, - GIMP_INT_STORE_LABEL, &label, - GIMP_INT_STORE_VALUE, &value, - -1); + gimp_widget_set_bound_property (frame, config, property_name); - button = gtk_radio_button_new_with_mnemonic (group, label); - gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); - gtk_widget_show (button); - - g_free (label); - - group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); - - g_object_set_data (G_OBJECT (button), "gimp-item-data", - GINT_TO_POINTER (value)); - - g_signal_connect (button, "toggled", - G_CALLBACK (gimp_prop_radio_button_callback), - config); - } - - g_object_get (config, - property_name, &value, - NULL); - - gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (button), value); - - set_radio_spec (G_OBJECT (button), param_spec); - - connect_notify (config, property_name, - G_CALLBACK (gimp_prop_radio_button_notify), - button); - - g_object_set_data (G_OBJECT (vbox), "radio-button", button); - - gimp_widget_set_bound_property (vbox, config, property_name); - - gtk_widget_show (vbox); - - return vbox; + return frame; } diff --git a/libgimpwidgets/gimppropwidgets.h b/libgimpwidgets/gimppropwidgets.h index f84f200b89..8cbb9c2df9 100644 --- a/libgimpwidgets/gimppropwidgets.h +++ b/libgimpwidgets/gimppropwidgets.h @@ -63,9 +63,6 @@ GtkWidget * gimp_prop_int_radio_frame_new (GObject *config, const gchar *property_name, const gchar *title, GimpIntStore *store); -GtkWidget * gimp_prop_int_radio_box_new (GObject *config, - const gchar *property_name, - GimpIntStore *store); /* GParamGType */ diff --git a/libgimpwidgets/gimpwidgets.def b/libgimpwidgets/gimpwidgets.def index e9ecbe6053..9a4e4df713 100644 --- a/libgimpwidgets/gimpwidgets.def +++ b/libgimpwidgets/gimpwidgets.def @@ -362,7 +362,6 @@ EXPORTS gimp_prop_hscale_new gimp_prop_icon_image_new gimp_prop_int_combo_box_new - gimp_prop_int_radio_box_new gimp_prop_int_radio_frame_new gimp_prop_label_color_new gimp_prop_label_entry_new diff --git a/libgimpwidgets/gimpwidgets.h b/libgimpwidgets/gimpwidgets.h index 9756fd656d..889cdae3dd 100644 --- a/libgimpwidgets/gimpwidgets.h +++ b/libgimpwidgets/gimpwidgets.h @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include diff --git a/libgimpwidgets/gimpwidgetstypes.h b/libgimpwidgets/gimpwidgetstypes.h index 2e41789a64..449101282f 100644 --- a/libgimpwidgets/gimpwidgetstypes.h +++ b/libgimpwidgets/gimpwidgetstypes.h @@ -61,6 +61,7 @@ typedef struct _GimpFileEntry GimpFileEntry; typedef struct _GimpFrame GimpFrame; typedef struct _GimpHintBox GimpHintBox; typedef struct _GimpIntComboBox GimpIntComboBox; +typedef struct _GimpIntRadioFrame GimpIntRadioFrame; typedef struct _GimpIntStore GimpIntStore; typedef struct _GimpLabeled GimpLabeled; typedef struct _GimpLabelColor GimpLabelColor; diff --git a/libgimpwidgets/meson.build b/libgimpwidgets/meson.build index e047c2b42a..04125a7498 100644 --- a/libgimpwidgets/meson.build +++ b/libgimpwidgets/meson.build @@ -53,6 +53,7 @@ libgimpwidgets_sources_introspectable = files( 'gimphintbox.c', 'gimpicons.c', 'gimpintcombobox.c', + 'gimpintradioframe.c', 'gimpintstore.c', 'gimplabelcolor.c', 'gimplabeled.c', @@ -132,6 +133,7 @@ libgimpwidgets_headers_introspectable = files( 'gimphintbox.h', 'gimpicons.h', 'gimpintcombobox.h', + 'gimpintradioframe.h', 'gimplabelcolor.h', 'gimplabeled.h', 'gimplabelintwidget.h',