# HG changeset patch
# Node ID ff7a551d0304
# Parent  ec766bf5a520
diff --git a/gplugin-gtk4/gplugin-gtk-plugin-settings-list.c b/gplugin-gtk4/gplugin-gtk-plugin-settings-list.c
--- a/gplugin-gtk4/gplugin-gtk-plugin-settings-list.c
+++ b/gplugin-gtk4/gplugin-gtk-plugin-settings-list.c
@@ -15,6 +15,8 @@
  * License along with this library; if not, see <https://www.gnu.org/licenses/>.
  */
 
+#include <glib/gi18n-lib.h>
+
 #include <gplugin.h>
 
 #include <gplugin-gtk-plugin-settings-list.h>
@@ -51,39 +53,419 @@ static GParamSpec *properties[N_PROPERTI
 };
 
 /******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static gboolean
+gplugin_gtk_plugin_settings_row_byte_to_double(
+	GValue *value,
+	GVariant *variant,
+	G_GNUC_UNUSED gpointer data)
+{
+	g_value_set_double(value, (gdouble)g_variant_get_byte(variant));
+	return TRUE;
+}
+
+static GVariant *
+gplugin_gtk_plugin_settings_row_double_to_byte(
+	const GValue *value,
+	G_GNUC_UNUSED const GVariantType *expected_type,
+	G_GNUC_UNUSED gpointer data)
+{
+	return g_variant_new_byte((char)g_value_get_double(value));
+}
+
+static gboolean
+gplugin_gtk_plugin_settings_row_enum_to_selected(
+	GValue *value,
+	GVariant *variant,
+	gpointer data)
+{
+	GListModel *model = data;
+	const char *string = NULL;
+	guint n_items;
+
+	string = g_variant_get_string(variant, NULL);
+	n_items = g_list_model_get_n_items(model);
+	for(guint index = 0; index < n_items; index++) {
+		GtkStringObject *obj = g_list_model_get_item(model, index);
+
+		if(g_str_equal(string, gtk_string_object_get_string(obj))) {
+			g_value_set_uint(value, index);
+			g_object_unref(obj);
+			return TRUE;
+		}
+
+		g_object_unref(obj);
+	}
+
+	return FALSE;
+}
+
+static GVariant *
+gplugin_gtk_plugin_settings_row_selected_to_enum(
+	const GValue *value,
+	G_GNUC_UNUSED const GVariantType *expected_type,
+	gpointer data)
+{
+	GListModel *model = data;
+	GtkStringObject *obj = NULL;
+	GVariant *result = NULL;
+
+	obj = g_list_model_get_item(model, g_value_get_uint(value));
+	result = g_variant_new_string(gtk_string_object_get_string(obj));
+	g_object_unref(obj);
+
+	return result;
+}
+
+/******************************************************************************
  * Helpers
  *****************************************************************************/
 static GtkWidget *
+gplugin_gtk_plugin_settings_row_new_for_enum(
+	GSettings *settings,
+	const char *name,
+	GVariant *range_value)
+{
+	GtkWidget *dropdown = NULL;
+	const gchar **allowed_values = NULL;
+	GListModel *model = NULL;
+
+	allowed_values = g_variant_get_strv(range_value, NULL);
+	dropdown = gtk_drop_down_new_from_strings(allowed_values);
+	g_free(allowed_values);
+
+	model = gtk_drop_down_get_model(GTK_DROP_DOWN(dropdown));
+	g_settings_bind_with_mapping(
+		settings,
+		name,
+		dropdown,
+		"selected",
+		G_SETTINGS_BIND_DEFAULT,
+		gplugin_gtk_plugin_settings_row_enum_to_selected,
+		gplugin_gtk_plugin_settings_row_selected_to_enum,
+		g_object_ref(model),
+		g_object_unref);
+
+	return dropdown;
+}
+
+static GtkWidget *
+gplugin_gtk_plugin_settings_row_new_for_flags(
+	G_GNUC_UNUSED GSettings *settings,
+	G_GNUC_UNUSED const char *name,
+	G_GNUC_UNUSED GVariant *range_value)
+{
+	GtkWidget *dropdown = NULL;
+
+	/* TODO: Implement this. */
+	dropdown = gtk_label_new("Unimplemented flags setting");
+
+	return dropdown;
+}
+
+static GtkWidget *
+gplugin_gtk_plugin_settings_row_new_for_range(
+	GSettings *settings,
+	const char *name,
+	GVariant *range_value)
+{
+	GtkWidget *spin = NULL;
+	const char *type = NULL;
+	gdouble min, max, step;
+
+	/* By default, for integers, pick a step of 1, which in GtkSpinButton
+	 * limits it to displaying only integers. */
+	step = 1.0;
+
+	/* The range value is a tuple with a pair of the type of the expected value
+	 * (e.g., "(ii)" for int16 or "(dd)" for double), so look at second
+	 * character. */
+	type = g_variant_get_type_string(range_value);
+	switch(type[1]) {
+		case 'y': {
+			guint8 mini, maxi;
+			g_variant_get_child(range_value, 0, "y", &mini);
+			g_variant_get_child(range_value, 1, "y", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 'n': {
+			gint16 mini, maxi;
+			g_variant_get_child(range_value, 0, "n", &mini);
+			g_variant_get_child(range_value, 1, "n", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 'q': {
+			guint16 mini, maxi;
+			g_variant_get_child(range_value, 0, "q", &mini);
+			g_variant_get_child(range_value, 1, "q", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 'i': {
+			gint32 mini, maxi;
+			g_variant_get_child(range_value, 0, "i", &mini);
+			g_variant_get_child(range_value, 1, "i", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 'u': {
+			guint32 mini, maxi;
+			g_variant_get_child(range_value, 0, "u", &mini);
+			g_variant_get_child(range_value, 1, "u", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 'x': {
+			gint64 mini, maxi;
+			g_variant_get_child(range_value, 0, "x", &mini);
+			g_variant_get_child(range_value, 1, "x", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 't': {
+			guint64 mini, maxi;
+			g_variant_get_child(range_value, 0, "t", &mini);
+			g_variant_get_child(range_value, 1, "t", &maxi);
+			min = (gdouble)mini;
+			max = (gdouble)maxi;
+		} break;
+
+		case 'd':
+			/* For doubles, arbitrarily pick a step of 1% of the range. */
+			g_variant_get_child(range_value, 0, "d", &min);
+			g_variant_get_child(range_value, 1, "d", &max);
+			step = (max - min) / 100;
+			break;
+
+		default:
+			g_warning("Unknown range type: %s", type);
+			min = 0.0;
+			max = 1.0;
+			break;
+	}
+
+	spin = gtk_spin_button_new_with_range(min, max, step);
+	if(type[1] == 'y') {
+		/* For some reason, Gio's default bindings understand
+		 * double-to-int conversions for any bit size other than
+		 * bytes, so manually set up mappings for bytes... */
+		g_settings_bind_with_mapping(
+			settings,
+			name,
+			spin,
+			"value",
+			G_SETTINGS_BIND_DEFAULT,
+			gplugin_gtk_plugin_settings_row_byte_to_double,
+			gplugin_gtk_plugin_settings_row_double_to_byte,
+			NULL,
+			NULL);
+	} else {
+		/* ... and use default binding everywhere else. */
+		g_settings_bind(settings, name, spin, "value", G_SETTINGS_BIND_DEFAULT);
+	}
+
+	return spin;
+}
+
+static GtkWidget *
+gplugin_gtk_plugin_settings_row_new_for_type(
+	GSettings *settings,
+	const char *name,
+	GVariant *range_value)
+{
+	GtkWidget *entry = NULL;
+	char type;
+	gdouble min, max;
+
+	/* The range value is an array with type of the expected value (e.g., "ab"
+	 * for boolean or "as" for string), so look at second character. */
+	type = g_variant_get_type_string(range_value)[1];
+
+	switch(type) {
+		/* Boolean */
+		case 'b':
+			entry = gtk_switch_new();
+			gtk_widget_set_halign(entry, GTK_ALIGN_END);
+			gtk_widget_set_valign(entry, GTK_ALIGN_CENTER);
+
+			g_settings_bind(
+				settings,
+				name,
+				entry,
+				"active",
+				G_SETTINGS_BIND_DEFAULT);
+			break;
+
+		/* Integral numbers */
+		case 'y':
+		case 'n':
+		case 'q':
+		case 'i':
+		case 'u':
+		case 'x':
+		case 't':
+			/* Set range for the spin button. */
+			switch(type) {
+				case 'y':
+					min = 0.0;
+					max = G_MAXUINT8;
+					break;
+				case 'n':
+					min = G_MININT16;
+					max = G_MAXINT16;
+					break;
+				case 'q':
+					min = 0.0;
+					max = G_MAXUINT16;
+					break;
+				case 'i':
+					min = G_MININT32;
+					max = G_MAXINT32;
+					break;
+				case 'u':
+					min = 0.0;
+					max = G_MAXUINT32;
+					break;
+				case 'x':
+					min = G_MININT64;
+					max = (gdouble)G_MAXINT64;
+					break;
+				case 't':
+					min = 0.0;
+					max = (gdouble)G_MAXUINT64;
+					break;
+			}
+
+			entry = gtk_spin_button_new_with_range(min, max, 1.0);
+			if(type == 'y') {
+				/* For some reason, Gio's default bindings understand
+				 * double-to-int conversions for any bit size other than
+				 * bytes, so manually set up mappings for bytes... */
+				g_settings_bind_with_mapping(
+					settings,
+					name,
+					entry,
+					"value",
+					G_SETTINGS_BIND_DEFAULT,
+					gplugin_gtk_plugin_settings_row_byte_to_double,
+					gplugin_gtk_plugin_settings_row_double_to_byte,
+					NULL,
+					NULL);
+			} else {
+				/* ... and use default binding everywhere else. */
+				g_settings_bind(
+					settings,
+					name,
+					entry,
+					"value",
+					G_SETTINGS_BIND_DEFAULT);
+			}
+
+			break;
+
+		/* Double numbers */
+		case 'd':
+			entry = gtk_spin_button_new(NULL, 1.0, 5);
+			g_settings_bind(
+				settings,
+				name,
+				entry,
+				"value",
+				G_SETTINGS_BIND_DEFAULT);
+			break;
+
+		/* Strings */
+		case 's':
+			entry = gtk_entry_new();
+			g_settings_bind(
+				settings,
+				name,
+				gtk_entry_get_buffer(GTK_ENTRY(entry)),
+				"text",
+				G_SETTINGS_BIND_DEFAULT);
+			break;
+
+		default:
+			entry = gtk_label_new(_("Unknown setting type"));
+			break;
+	}
+
+	return entry;
+}
+
+static GtkWidget *
 gplugin_gtk_plugin_settings_row_new_for_key(
 	GSettings *settings,
 	const gchar *name,
-	GSettingsSchemaKey *key)
+	GSettingsSchemaKey *key,
+	GtkSizeGroup *sg)
 {
-	/* TODO: Make real widgets. */
 	GtkWidget *widget = NULL;
 	GtkWidget *label = NULL;
 	GtkWidget *entry = NULL;
-	GVariant *value = NULL;
-	char *value_str = NULL;
+	const char *summary = NULL;
+	GVariant *range = NULL;
+	gchar *range_type = NULL;
+	GVariant *range_value = NULL;
 
 	widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
 
-	label = gtk_label_new(g_settings_schema_key_get_name(key));
+	summary = g_settings_schema_key_get_summary(key);
+	label = gtk_label_new((summary != NULL) ? summary : name);
 	gtk_label_set_xalign(GTK_LABEL(label), 0);
 	gtk_box_append(GTK_BOX(widget), label);
+	gtk_size_group_add_widget(sg, label);
 
-	value = g_settings_get_value(settings, name);
-	value_str = g_variant_print(value, TRUE);
-	g_variant_unref(value);
+	gtk_widget_set_tooltip_text(
+		widget,
+		g_settings_schema_key_get_description(key));
+
+	range = g_settings_schema_key_get_range(key);
+	g_variant_get_child(range, 0, "s", &range_type);
+	g_variant_get_child(range, 1, "v", &range_value);
 
-	entry = gtk_entry_new();
-	gtk_editable_set_text(GTK_EDITABLE(entry), value_str);
-	gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
+	if(g_str_equal(range_type, "enum")) {
+		entry = gplugin_gtk_plugin_settings_row_new_for_enum(
+			settings,
+			name,
+			range_value);
+	} else if(g_str_equal(range_type, "flags")) {
+		entry = gplugin_gtk_plugin_settings_row_new_for_flags(
+			settings,
+			name,
+			range_value);
+	} else if(g_str_equal(range_type, "range")) {
+		entry = gplugin_gtk_plugin_settings_row_new_for_range(
+			settings,
+			name,
+			range_value);
+	} else if(g_str_equal(range_type, "type")) {
+		entry = gplugin_gtk_plugin_settings_row_new_for_type(
+			settings,
+			name,
+			range_value);
+	} else {
+		/* No other documented types for g_settings_schema_key_get_range. */
+		g_warn_if_reached();
+		entry = gtk_label_new(_("Unknown setting type"));
+	}
+
+	g_variant_unref(range_value);
+	g_free(range_type);
+	g_variant_unref(range);
+
 	gtk_widget_set_hexpand(entry, TRUE);
 	gtk_box_append(GTK_BOX(widget), entry);
 
-	g_free(value_str);
-
 	return widget;
 }
 
@@ -94,6 +476,7 @@ gplugin_gtk_plugin_settings_list_refresh
 {
 	GSettingsSchema *schema = NULL;
 	gchar **names = NULL;
+	GtkSizeGroup *sg = NULL;
 
 	while(list->rows) {
 		gtk_list_box_remove(list->list_box, list->rows->data);
@@ -106,6 +489,7 @@ gplugin_gtk_plugin_settings_list_refresh
 
 	g_object_get(G_OBJECT(settings), "settings-schema", &schema, NULL);
 	names = g_settings_schema_list_keys(schema);
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
 	for(gint i = 0; names[i] != NULL; i++) {
 		GSettingsSchemaKey *key = NULL;
@@ -116,7 +500,8 @@ gplugin_gtk_plugin_settings_list_refresh
 		widget = gplugin_gtk_plugin_settings_row_new_for_key(
 			settings,
 			names[i],
-			key);
+			key,
+			sg);
 
 		row = gtk_list_box_row_new();
 		gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), widget);
