[rhythmbox] add a composite widget for editing simple GObject properties



commit e6ce4f93ba67fb0ccbab4758e1fc58bbe1962414
Author: Jonathan Matthew <jonathan d14n org>
Date:   Mon Apr 23 08:45:46 2012 +1000

    add a composite widget for editing simple GObject properties
    
    This shouldn't be used if it can be avoided, but sometimes
    this is all you can do.

 widgets/Makefile.am                 |    6 +-
 widgets/rb-object-property-editor.c |  415 +++++++++++++++++++++++++++++++++++
 widgets/rb-object-property-editor.h |   67 ++++++
 3 files changed, 486 insertions(+), 2 deletions(-)
---
diff --git a/widgets/Makefile.am b/widgets/Makefile.am
index 9f8510d..e46096f 100644
--- a/widgets/Makefile.am
+++ b/widgets/Makefile.am
@@ -14,7 +14,8 @@ widgetinclude_HEADERS =					\
 	rb-song-info.h					\
 	rb-source-toolbar.h				\
 	rb-uri-dialog.h					\
-	rb-fading-image.h
+	rb-fading-image.h				\
+	rb-object-property-editor.h
 
 librbwidgets_la_SOURCES =				\
 	$(widgetinclude_HEADERS)			\
@@ -46,7 +47,8 @@ librbwidgets_la_SOURCES =				\
 	eggwrapbox-enums.c				\
 	eggwrapbox-enums.h				\
 	rb-source-toolbar.c				\
-	rb-fading-image.c
+	rb-fading-image.c				\
+	rb-object-property-editor.c
 
 INCLUDES =						\
 	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
diff --git a/widgets/rb-object-property-editor.c b/widgets/rb-object-property-editor.c
new file mode 100644
index 0000000..f1844b3
--- /dev/null
+++ b/widgets/rb-object-property-editor.c
@@ -0,0 +1,415 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2012 Jonathan Matthew <jonathan d14n org>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <config.h>
+
+#include <widgets/rb-object-property-editor.h>
+#include <lib/rb-util.h>
+#include <lib/rb-debug.h>
+
+static void rb_object_property_editor_class_init (RBObjectPropertyEditorClass *klass);
+static void rb_object_property_editor_init (RBObjectPropertyEditor *editor);
+
+struct _RBObjectPropertyEditorPrivate
+{
+	GObject *object;
+	char **properties;
+
+	gboolean changed;
+	gulong notify_id;
+};
+
+G_DEFINE_TYPE (RBObjectPropertyEditor, rb_object_property_editor, GTK_TYPE_GRID);
+
+/**
+ * SECTION:rb-object-property-editor
+ * @short_description: builds widgetry for editing simple GObject properties
+ *
+ * RBObjectPropertyEditor can be used to provide an interface to edit
+ * simple (boolean, integer, enum, float) properties of a GObject.
+ */
+
+enum
+{
+	PROP_0,
+	PROP_OBJECT,
+	PROP_PROPERTIES
+};
+
+enum
+{
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+impl_finalize (GObject *object)
+{
+	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
+
+	g_strfreev (editor->priv->properties);
+
+	G_OBJECT_CLASS (rb_object_property_editor_parent_class)->finalize (object);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
+
+	if (editor->priv->object != NULL) {
+		if (editor->priv->notify_id) {
+			g_signal_handler_disconnect (editor->priv->object,
+						     editor->priv->notify_id);
+			editor->priv->notify_id = 0;
+		}
+		g_object_unref (editor->priv->object);
+		editor->priv->object = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_object_property_editor_parent_class)->dispose (object);
+}
+
+static void
+notify_cb (GObject *object, GParamSpec *pspec, RBObjectPropertyEditor *editor)
+{
+	editor->priv->changed = TRUE;
+}
+
+static void
+focus_out_cb (GtkWidget *widget, GdkEvent *event, RBObjectPropertyEditor *editor)
+{
+	if (editor->priv->changed) {
+		rb_debug ("emitting changed");
+		g_signal_emit (editor, signals[CHANGED], 0);
+		editor->priv->changed = FALSE;
+	} else {
+		rb_debug ("not emitting changed");
+	}
+}
+
+static GtkWidget *
+create_boolean_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
+{
+	GtkWidget *control;
+
+	control = gtk_check_button_new ();
+
+	g_object_bind_property (editor->priv->object, property,
+				control, "active",
+				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+	return control;
+}
+
+static GtkWidget *
+create_enum_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
+{
+	GParamSpecEnum *penum;
+	GtkListStore *model;
+	GtkCellRenderer *renderer;
+	GtkWidget *control;
+	int p;
+
+	control = gtk_combo_box_new ();
+	penum = G_PARAM_SPEC_ENUM (pspec);
+	renderer = gtk_cell_renderer_text_new ();
+
+	model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
+	gtk_combo_box_set_model (GTK_COMBO_BOX (control), GTK_TREE_MODEL (model));
+	for (p = 0; p < penum->enum_class->n_values; p++) {
+		gtk_list_store_insert_with_values (model, NULL, p,
+						   0, penum->enum_class->values[p].value_name,
+						   1, p,
+						   -1);
+	}
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (control), renderer, TRUE);
+	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (control), renderer, "text", 0, NULL);
+
+	g_object_bind_property (editor->priv->object, property,
+				control, "active",
+				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+	return control;
+}
+
+static GtkWidget *
+create_int_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
+{
+	GParamSpecInt *pint;
+	GtkWidget *control;
+	GtkAdjustment *adjustment;
+
+	pint = G_PARAM_SPEC_INT (pspec);
+
+	adjustment = gtk_adjustment_new (pint->default_value,
+					 pint->minimum,
+					 pint->maximum + 1,
+					 1.0,
+					 1.0, 1.0);
+
+	control = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
+	gtk_scale_set_digits (GTK_SCALE (control), 0);
+
+	g_object_bind_property (editor->priv->object, property,
+				adjustment, "value",
+				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+	return control;
+}
+
+static GtkWidget *
+create_float_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
+{
+	GParamSpecFloat *pfloat;
+	GtkWidget *control;
+	GtkAdjustment *adjustment;
+
+	pfloat = G_PARAM_SPEC_FLOAT (pspec);
+
+	adjustment = gtk_adjustment_new (pfloat->default_value,
+					 pfloat->minimum,
+					 pfloat->maximum + pfloat->epsilon*2,
+					 pfloat->epsilon*10,
+					 0.1, 0.1);
+
+	control = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
+
+	g_object_bind_property (editor->priv->object, property,
+				adjustment, "value",
+				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+	return control;
+}
+
+static GtkWidget *
+create_double_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
+{
+	GParamSpecDouble *pdouble;
+	GtkWidget *control;
+	GtkAdjustment *adjustment;
+
+	pdouble = G_PARAM_SPEC_DOUBLE (pspec);
+
+	adjustment = gtk_adjustment_new (pdouble->default_value,
+					 pdouble->minimum,
+					 pdouble->maximum + pdouble->epsilon*2,
+					 pdouble->epsilon*10,
+					 0.1, 0.1);
+
+	control = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
+
+	g_object_bind_property (editor->priv->object, property,
+				adjustment, "value",
+				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+	return control;
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBObjectPropertyEditor *editor;
+	GObjectClass *klass;
+	int i;
+	int row;
+
+	RB_CHAIN_GOBJECT_METHOD (rb_object_property_editor_parent_class, constructed, object);
+
+	editor = RB_OBJECT_PROPERTY_EDITOR (object);
+	klass = G_OBJECT_GET_CLASS (editor->priv->object);
+
+	editor->priv->notify_id = g_signal_connect (editor->priv->object, "notify", G_CALLBACK (notify_cb), editor);
+
+	row = 0;
+	for (i = 0; editor->priv->properties[i] != NULL; i++) {
+		GParamSpec *pspec;
+		GtkWidget *label;
+		GtkWidget *control;
+		GType prop_type;
+
+		pspec = g_object_class_find_property (klass, editor->priv->properties[i]);
+		if (pspec == NULL) {
+			g_warning ("couldn't find property %s on object %s",
+				   editor->priv->properties[i],
+				   G_OBJECT_CLASS_NAME (klass));
+			continue;
+		}
+
+		prop_type = G_PARAM_SPEC_TYPE (pspec);
+		if (prop_type == G_TYPE_PARAM_BOOLEAN) {
+			control = create_boolean_editor (editor, editor->priv->properties[i], pspec);
+		} else if (prop_type == G_TYPE_PARAM_ENUM) {
+			control = create_enum_editor (editor, editor->priv->properties[i], pspec);
+		} else if (prop_type == G_TYPE_PARAM_INT) {
+			control = create_int_editor (editor, editor->priv->properties[i], pspec);
+		} else if (prop_type == G_TYPE_PARAM_FLOAT) {
+			control = create_float_editor (editor, editor->priv->properties[i], pspec);
+		} else if (prop_type == G_TYPE_PARAM_DOUBLE) {
+			control = create_double_editor (editor, editor->priv->properties[i], pspec);
+		} else {
+			/* can't do this */
+			g_warning ("don't know how to edit %s", g_type_name (prop_type));
+			continue;
+		}
+		g_signal_connect (control, "focus-out-event", G_CALLBACK (focus_out_cb), editor);
+		gtk_widget_set_hexpand (control, TRUE);
+
+		label = gtk_label_new (g_param_spec_get_nick (pspec));
+		gtk_widget_set_tooltip_text (label, g_param_spec_get_blurb (pspec));
+
+		gtk_grid_attach (GTK_GRID (editor),
+				 label,
+				 0, row, 1, 1);
+		gtk_grid_attach (GTK_GRID (editor),
+				 control,
+				 1, row, 1, 1);
+
+		row++;
+	}
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
+	switch (prop_id) {
+	case PROP_OBJECT:
+		editor->priv->object = g_value_dup_object (value);
+		break;
+	case PROP_PROPERTIES:
+		editor->priv->properties = g_value_dup_boxed (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+	}
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
+	switch (prop_id) {
+	case PROP_OBJECT:
+		g_value_set_object (value, editor->priv->object);
+		break;
+	case PROP_PROPERTIES:
+		g_value_set_boxed (value, editor->priv->properties);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+	}
+}
+
+static void
+rb_object_property_editor_init (RBObjectPropertyEditor *editor)
+{
+	editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (editor,
+						    RB_TYPE_OBJECT_PROPERTY_EDITOR,
+						    RBObjectPropertyEditorPrivate);
+}
+
+
+static void
+rb_object_property_editor_class_init (RBObjectPropertyEditorClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->constructed = impl_constructed;
+	object_class->dispose = impl_dispose;
+	object_class->finalize = impl_finalize;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	/**
+	 * RBObjectPropertyEditor::changed:
+	 * @editor: the #RBObjectPropertyEditor
+	 *
+	 * Emitted when a property has been changed.
+	 * This won't be emitted on every single change (use 'notify' on the
+	 * object being edited for that), but rather when the edit widget
+	 * for a property loses focus and the value was changed.
+	 */
+	signals[CHANGED] =
+		g_signal_new ("changed",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBObjectPropertyEditorClass, changed),
+			      NULL, NULL,
+			      NULL,
+			      G_TYPE_NONE,
+			      0);
+
+	/**
+	 * RBObjectPropertyEditor:object
+	 *
+	 * The object to edit.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_OBJECT,
+					 g_param_spec_object ("object",
+							      "object",
+							      "object to edit",
+							      G_TYPE_OBJECT,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	/**
+	 * RBObjectPropertyEditor:properties
+	 * 
+	 * Array of names of properties to edit.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PROPERTIES,
+					 g_param_spec_boxed ("properties",
+							     "properties",
+							     "properties to edit",
+							     G_TYPE_STRV,
+							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (klass, sizeof (RBObjectPropertyEditorPrivate));
+}
+
+/**
+ * rb_object_property_editor_new:
+ * @object: the object to edit
+ * @properties: array of names of properties to edit
+ *
+ * Creates a property editor for the specified properties of an object.
+ *
+ * Return value: property editor widget.
+ */
+GtkWidget *
+rb_object_property_editor_new (GObject *object, char **properties)
+{
+	return GTK_WIDGET (g_object_new (RB_TYPE_OBJECT_PROPERTY_EDITOR,
+					 "object", object,
+					 "properties", properties,
+					 NULL));
+}
diff --git a/widgets/rb-object-property-editor.h b/widgets/rb-object-property-editor.h
new file mode 100644
index 0000000..8f019b5
--- /dev/null
+++ b/widgets/rb-object-property-editor.h
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (C) 2012  Jonathan Matthew <jonathan d14n org>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <gtk/gtk.h>
+
+#ifndef RB_OBJECT_PROPERTY_EDITOR_H
+#define RB_OBJECT_PROPERTY_EDITOR_H
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_OBJECT_PROPERTY_EDITOR         (rb_object_property_editor_get_type ())
+#define RB_OBJECT_PROPERTY_EDITOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_OBJECT_PROPERTY_EDITOR, RBObjectPropertyEditor))
+#define RB_OBJECT_PROPERTY_EDITOR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_OBJECT_PROPERTY_EDITOR, RBObjectPropertyEditorClass))
+#define RB_IS_OBJECT_PROPERTY_EDITOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_OBJECT_PROPERTY_EDITOR))
+#define RB_IS_OBJECT_PROPERTY_EDITOR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_OBJECT_PROPERTY_EDITOR))
+#define RB_OBJECT_PROPERTY_EDITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_OBJECT_PROPERTY_EDITOR, RBObjectPropertyEditorClass))
+
+typedef struct _RBObjectPropertyEditor RBObjectPropertyEditor;
+typedef struct _RBObjectPropertyEditorClass RBObjectPropertyEditorClass;
+typedef struct _RBObjectPropertyEditorPrivate RBObjectPropertyEditorPrivate;
+
+struct _RBObjectPropertyEditor
+{
+	GtkGrid parent;
+
+	RBObjectPropertyEditorPrivate *priv;
+};
+
+struct _RBObjectPropertyEditorClass
+{
+	GtkGridClass parent_class;
+
+	void (*changed) (RBObjectPropertyEditor *editor);
+};
+
+GType		rb_object_property_editor_get_type (void);
+
+GtkWidget *	rb_object_property_editor_new (GObject *object, char **properties);
+
+
+G_END_DECLS
+
+#endif /* RB_OBJECT_PROPERTY_EDITOR_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]