From d670ff9f8203f553400edca48d6e555b114118b4 Mon Sep 17 00:00:00 2001 From: Jehan Date: Fri, 4 Aug 2023 01:16:16 +0200 Subject: [PATCH] libgimpbase, libgimpwidgets: add a concept of insensitive choices in GimpChoice. This is used in the generated GUIs for GimpChoice arguments, but also for validation of property setting. New functions: * gimp_choice_set_sensitive() * gimp_string_combo_box_set_sensitivity() --- libgimpbase/gimpbase.def | 1 + libgimpbase/gimpchoice.c | 76 +++++++++++++++-- libgimpbase/gimpchoice.h | 4 + libgimpbase/gimpparamspecs.c | 56 ++++++++++--- libgimpwidgets/gimppropwidgets.c | 36 ++++---- libgimpwidgets/gimpstringcombobox.c | 122 ++++++++++++++++++++++++---- libgimpwidgets/gimpstringcombobox.h | 27 ++++-- libgimpwidgets/gimpwidgets.def | 1 + 8 files changed, 266 insertions(+), 57 deletions(-) diff --git a/libgimpbase/gimpbase.def b/libgimpbase/gimpbase.def index 6b0d192664..c81106b008 100644 --- a/libgimpbase/gimpbase.def +++ b/libgimpbase/gimpbase.def @@ -27,6 +27,7 @@ EXPORTS gimp_choice_list_nicks gimp_choice_new gimp_choice_new_with_values + gimp_choice_set_sensitive gimp_clone_type_get_type gimp_color_tag_get_type gimp_component_type_get_type diff --git a/libgimpbase/gimpchoice.c b/libgimpbase/gimpchoice.c index 92a59ee2f6..24a01af0b1 100644 --- a/libgimpbase/gimpchoice.c +++ b/libgimpbase/gimpchoice.c @@ -30,11 +30,18 @@ typedef struct _GimpChoiceDesc { - gchar *label; - gchar *help; - gint id; + gchar *label; + gchar *help; + gint id; + gboolean sensitive; } GimpChoiceDesc; +enum +{ + SENSITIVITY_CHANGED, + LAST_SIGNAL +}; + struct _GimpChoice { GObject parent_instance; @@ -53,12 +60,23 @@ G_DEFINE_TYPE (GimpChoice, gimp_choice, G_TYPE_OBJECT) #define parent_class gimp_choice_parent_class +static guint gimp_choice_signals[LAST_SIGNAL] = { 0 }; + static void gimp_choice_class_init (GimpChoiceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->finalize = gimp_choice_finalize; + gimp_choice_signals[SENSITIVITY_CHANGED] = + g_signal_new ("sensitivity-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, /*G_STRUCT_OFFSET (GimpChoiceClass, sensitivity_changed),*/ + NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + object_class->finalize = gimp_choice_finalize; } static void @@ -173,10 +191,11 @@ gimp_choice_add (GimpChoice *choice, g_return_if_fail (label != NULL); - desc = g_new0 (GimpChoiceDesc, 1); - desc->id = id; - desc->label = g_strdup (label); - desc->help = help != NULL ? g_strdup (help) : NULL; + desc = g_new0 (GimpChoiceDesc, 1); + desc->id = id; + desc->label = g_strdup (label); + desc->help = help != NULL ? g_strdup (help) : NULL; + desc->sensitive = TRUE; g_hash_table_insert (choice->choices, g_strdup (nick), desc); duplicate = g_list_find_custom (choice->keys, nick, (GCompareFunc) g_strcmp0); @@ -205,7 +224,13 @@ gboolean gimp_choice_is_valid (GimpChoice *choice, const gchar *nick) { - return (g_hash_table_lookup (choice->choices, nick) != NULL); + GimpChoiceDesc *desc; + + g_return_val_if_fail (GIMP_IS_CHOICE (choice), FALSE); + g_return_val_if_fail (nick != NULL, FALSE); + + desc = g_hash_table_lookup (choice->choices, nick); + return (desc != NULL && desc->sensitive); } /** @@ -328,6 +353,39 @@ gimp_choice_get_documentation (GimpChoice *choice, return FALSE; } +/** + * gimp_choice_set_sensitive: + * @choice: the %GimpChoice. + * @nick: the nick to lookup. + * + * Change the sensitivity of a possible @nick. Technically a non-sensitive @nick + * means it cannot be chosen anymore (so [method@Gimp.Choice.is_valid] will + * return %FALSE; nevertheless [method@Gimp.Choice.list_nicks] and other + * functions to get information about a choice will still function). + * + * Returns: %TRUE if @nick is found, %FALSE otherwise. + * + * Since: 3.0 + **/ +void +gimp_choice_set_sensitive (GimpChoice *choice, + const gchar *nick, + gboolean sensitive) +{ + GimpChoiceDesc *desc; + + g_return_if_fail (GIMP_IS_CHOICE (choice)); + g_return_if_fail (nick != NULL); + + desc = g_hash_table_lookup (choice->choices, nick); + g_return_if_fail (desc != NULL); + if (desc->sensitive != sensitive) + { + desc->sensitive = sensitive; + g_signal_emit (choice, gimp_choice_signals[SENSITIVITY_CHANGED], 0, nick); + } +} + /* Private functions */ diff --git a/libgimpbase/gimpchoice.h b/libgimpbase/gimpchoice.h index 783efca9f1..9002f1d2b6 100644 --- a/libgimpbase/gimpchoice.h +++ b/libgimpbase/gimpchoice.h @@ -63,6 +63,10 @@ gboolean gimp_choice_get_documentation (GimpChoice *choice, const gchar **label, const gchar **help); +void gimp_choice_set_sensitive (GimpChoice *choice, + const gchar *nick, + gboolean sensitive); + G_END_DECLS diff --git a/libgimpbase/gimpparamspecs.c b/libgimpbase/gimpparamspecs.c index 838819733f..0523d7cefe 100644 --- a/libgimpbase/gimpparamspecs.c +++ b/libgimpbase/gimpparamspecs.c @@ -29,14 +29,16 @@ * GIMP_TYPE_PARAM_CHOICE */ -static void gimp_param_choice_class_init (GParamSpecClass *klass); -static void gimp_param_choice_init (GParamSpec *pspec); -static void gimp_param_choice_finalize (GParamSpec *pspec); -static gboolean gimp_param_choice_validate (GParamSpec *pspec, - GValue *value); -static gint gimp_param_choice_values_cmp (GParamSpec *pspec, - const GValue *value1, - const GValue *value2); +static void gimp_param_choice_class_init (GParamSpecClass *klass); +static void gimp_param_choice_init (GParamSpec *pspec); +static void gimp_param_choice_value_set_default (GParamSpec *pspec, + GValue *value); +static void gimp_param_choice_finalize (GParamSpec *pspec); +static gboolean gimp_param_choice_validate (GParamSpec *pspec, + GValue *value); +static gint gimp_param_choice_values_cmp (GParamSpec *pspec, + const GValue *value1, + const GValue *value2); GType gimp_param_choice_get_type (void) @@ -66,10 +68,11 @@ gimp_param_choice_get_type (void) static void gimp_param_choice_class_init (GParamSpecClass *klass) { - klass->value_type = G_TYPE_STRING; - klass->finalize = gimp_param_choice_finalize; - klass->value_validate = gimp_param_choice_validate; - klass->values_cmp = gimp_param_choice_values_cmp; + klass->value_type = G_TYPE_STRING; + klass->value_set_default = gimp_param_choice_value_set_default; + klass->finalize = gimp_param_choice_finalize; + klass->value_validate = gimp_param_choice_validate; + klass->values_cmp = gimp_param_choice_values_cmp; } static void @@ -81,6 +84,15 @@ gimp_param_choice_init (GParamSpec *pspec) choice->default_value = NULL; } +static void +gimp_param_choice_value_set_default (GParamSpec *pspec, + GValue *value) +{ + GimpParamSpecChoice *cspec = GIMP_PARAM_SPEC_CHOICE (pspec); + + g_value_set_string (value, cspec->default_value); +} + static void gimp_param_choice_finalize (GParamSpec *pspec) { @@ -100,7 +112,25 @@ gimp_param_choice_validate (GParamSpec *pspec, if (! gimp_choice_is_valid (choice, strval)) { - g_value_set_string (value, spec_choice->default_value); + if (gimp_choice_is_valid (choice, spec_choice->default_value)) + { + g_value_set_string (value, spec_choice->default_value); + } + else + { + /* This might happen if the default value is set insensitive. Then we + * should just set any valid random nick. + */ + GList *nicks; + + nicks = gimp_choice_list_nicks (choice); + for (GList *iter = nicks; iter; iter = iter->next) + if (gimp_choice_is_valid (choice, (gchar *) iter->data)) + { + g_value_set_string (value, (gchar *) iter->data); + break; + } + } return TRUE; } diff --git a/libgimpwidgets/gimppropwidgets.c b/libgimpwidgets/gimppropwidgets.c index 612534a240..a79396850b 100644 --- a/libgimpwidgets/gimppropwidgets.c +++ b/libgimpwidgets/gimppropwidgets.c @@ -2543,6 +2543,11 @@ static void gimp_prop_string_combo_box_notify (GObject *config, GParamSpec *param_spec, GtkWidget *widget); +static gboolean + gimp_prop_choice_combo_box_is_sensitive (const gchar *nick, + GimpChoice *choice); + + /** * gimp_prop_string_combo_box_new: * @config: Object to which property is attached. @@ -2624,7 +2629,6 @@ gimp_prop_choice_combo_box_new (GObject *config, GimpParamSpecChoice *cspec; GtkWidget *combo_box; GtkListStore *store; - gchar *value; GList *values; GList *iter; @@ -2655,20 +2659,16 @@ gimp_prop_choice_combo_box_new (GObject *config, combo_box = gimp_string_combo_box_new (GTK_TREE_MODEL (store), 0, 1); g_object_unref (store); - g_object_get (config, - property_name, &value, - NULL); - gimp_string_combo_box_set_active (GIMP_STRING_COMBO_BOX (combo_box), value); + gimp_string_combo_box_set_sensitivity (GIMP_STRING_COMBO_BOX (combo_box), + (GimpStringSensitivityFunc) gimp_prop_choice_combo_box_is_sensitive, + cspec->choice, NULL); + g_signal_connect_swapped (cspec->choice, "sensitivity-changed", + G_CALLBACK (gtk_widget_queue_draw), + combo_box); - g_signal_connect (combo_box, "changed", - G_CALLBACK (gimp_prop_string_combo_box_callback), - config); - - set_param_spec (G_OBJECT (combo_box), combo_box, param_spec); - - connect_notify (config, property_name, - G_CALLBACK (gimp_prop_string_combo_box_notify), - combo_box); + g_object_bind_property (config, property_name, + combo_box, "value", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); gimp_widget_set_bound_property (combo_box, config, property_name); @@ -2676,6 +2676,7 @@ gimp_prop_choice_combo_box_new (GObject *config, return combo_box; } + static void gimp_prop_string_combo_box_callback (GtkWidget *widget, GObject *config) @@ -2723,6 +2724,13 @@ gimp_prop_string_combo_box_notify (GObject *config, g_free (value); } +static gboolean +gimp_prop_choice_combo_box_is_sensitive (const gchar *nick, + GimpChoice *choice) +{ + return gimp_choice_is_valid (choice, nick); +} + /*************************/ /* file chooser button */ diff --git a/libgimpwidgets/gimpstringcombobox.c b/libgimpwidgets/gimpstringcombobox.c index 245fb73d95..0af5b862be 100644 --- a/libgimpwidgets/gimpstringcombobox.c +++ b/libgimpwidgets/gimpstringcombobox.c @@ -53,23 +53,33 @@ enum struct _GimpStringComboBoxPrivate { - gint id_column; - gint label_column; - GtkCellRenderer *text_renderer; + gint id_column; + gint label_column; + GtkCellRenderer *text_renderer; + + GimpStringSensitivityFunc sensitivity_func; + gpointer sensitivity_data; + GDestroyNotify sensitivity_destroy; }; #define GET_PRIVATE(obj) (((GimpStringComboBox *) (obj))->priv) -static void gimp_string_combo_box_constructed (GObject *object); -static void gimp_string_combo_box_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec); -static void gimp_string_combo_box_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec); +static void gimp_string_combo_box_constructed (GObject *object); +static void gimp_string_combo_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_string_combo_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_string_combo_box_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GimpStringComboBoxPrivate *priv); G_DEFINE_TYPE_WITH_PRIVATE (GimpStringComboBox, gimp_string_combo_box, @@ -153,7 +163,8 @@ gimp_string_combo_box_class_init (GimpStringComboBoxClass *klass) "Value", "Value of active item", NULL, - GIMP_PARAM_READWRITE)); + GIMP_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY)); } static void @@ -176,6 +187,16 @@ gimp_string_combo_box_constructed (GObject *object) gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (object), cell, "text", priv->label_column, NULL); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (object), priv->text_renderer, + (GtkCellLayoutDataFunc) gimp_string_combo_box_cell_data_func, + priv, NULL); + + /* The "changed" signal of the GtkComboBox also triggers a "value" property + * notification. + */ + g_signal_connect (object, "changed", + G_CALLBACK (g_object_notify), + "value"); } static void @@ -322,7 +343,7 @@ gimp_string_combo_box_new (GtkTreeModel *model, * selected item in the @combo_box. * * Returns: %TRUE on success or %FALSE if there was no item for - * this value. + * this value. * * Since: 2.4 **/ @@ -345,6 +366,7 @@ gimp_string_combo_box_set_active (GimpStringComboBox *combo_box, if (gimp_string_model_lookup (model, column, id, &iter)) { gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter); + g_object_notify (G_OBJECT (combo_box), "value"); return TRUE; } @@ -353,6 +375,7 @@ gimp_string_combo_box_set_active (GimpStringComboBox *combo_box, else { gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), -1); + g_object_notify (G_OBJECT (combo_box), "value"); return TRUE; } @@ -392,3 +415,74 @@ gimp_string_combo_box_get_active (GimpStringComboBox *combo_box) return NULL; } + +/** + * gimp_string_combo_box_set_sensitivity: + * @combo_box: a #GimpStringComboBox + * @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 + * rows in the @combo_box. Use this if you want to set certain rows + * insensitive. + * + * Calling gtk_widget_queue_draw() on the @combo_box will cause the + * sensitivity to be updated. + * + * Since: 3.0 + **/ +void +gimp_string_combo_box_set_sensitivity (GimpStringComboBox *combo_box, + GimpStringSensitivityFunc func, + gpointer data, + GDestroyNotify destroy) +{ + GimpStringComboBoxPrivate *priv; + + g_return_if_fail (GIMP_IS_STRING_COMBO_BOX (combo_box)); + + priv = GET_PRIVATE (combo_box); + + if (priv->sensitivity_destroy) + { + GDestroyNotify d = priv->sensitivity_destroy; + + priv->sensitivity_destroy = NULL; + d (priv->sensitivity_data); + } + + priv->sensitivity_func = func; + priv->sensitivity_data = data; + priv->sensitivity_destroy = destroy; + + gtk_widget_queue_draw (GTK_WIDGET (combo_box)); +} + + +/* Private functions. */ + +static void +gimp_string_combo_box_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GimpStringComboBoxPrivate *priv) +{ + if (priv->sensitivity_func) + { + gchar *id; + gboolean sensitive; + + gtk_tree_model_get (tree_model, iter, + priv->id_column, &id, + -1); + + sensitive = priv->sensitivity_func (id, priv->sensitivity_data); + + g_object_set (cell, + "sensitive", sensitive, + NULL); + g_free (id); + } +} diff --git a/libgimpwidgets/gimpstringcombobox.h b/libgimpwidgets/gimpstringcombobox.h index df69ac54c9..c25fb24995 100644 --- a/libgimpwidgets/gimpstringcombobox.h +++ b/libgimpwidgets/gimpstringcombobox.h @@ -29,6 +29,15 @@ G_BEGIN_DECLS +/** + * GimpStringSensitivityFunc: + * @id: the string value from the column @id_column as passed to [ctor@StringComboBox.new]. + * @data: (closure): the data passed in [method@StringComboBox.set_sensitivity]. + */ +typedef gboolean (* GimpStringSensitivityFunc) (const gchar *id, + gpointer data); + + #define GIMP_TYPE_STRING_COMBO_BOX (gimp_string_combo_box_get_type ()) #define GIMP_STRING_COMBO_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STRING_COMBO_BOX, GimpStringComboBox)) #define GIMP_STRING_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STRING_COMBO_BOX, GimpStringComboBoxClass)) @@ -63,15 +72,19 @@ struct _GimpStringComboBoxClass }; -GType gimp_string_combo_box_get_type (void) G_GNUC_CONST; +GType gimp_string_combo_box_get_type (void) G_GNUC_CONST; -GtkWidget * gimp_string_combo_box_new (GtkTreeModel *model, - gint id_column, - gint label_column); -gboolean gimp_string_combo_box_set_active (GimpStringComboBox *combo_box, - const gchar *id); -gchar * gimp_string_combo_box_get_active (GimpStringComboBox *combo_box); +GtkWidget * gimp_string_combo_box_new (GtkTreeModel *model, + gint id_column, + gint label_column); +gboolean gimp_string_combo_box_set_active (GimpStringComboBox *combo_box, + const gchar *id); +gchar * gimp_string_combo_box_get_active (GimpStringComboBox *combo_box); +void gimp_string_combo_box_set_sensitivity (GimpStringComboBox *combo_box, + GimpStringSensitivityFunc func, + gpointer data, + GDestroyNotify destroy); G_END_DECLS diff --git a/libgimpwidgets/gimpwidgets.def b/libgimpwidgets/gimpwidgets.def index 84068befc0..1dad4cfa3f 100644 --- a/libgimpwidgets/gimpwidgets.def +++ b/libgimpwidgets/gimpwidgets.def @@ -472,6 +472,7 @@ EXPORTS gimp_string_combo_box_get_type gimp_string_combo_box_new gimp_string_combo_box_set_active + gimp_string_combo_box_set_sensitivity gimp_toggle_button_update gimp_uint_adjustment_update gimp_unit_combo_box_get_active