hythmbox-2.98/widgets/rb-object-property-editor.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2012 Jonathan Matthew <jonathan@d14n.org>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include <config.h>
 30 
 31 #include <widgets/rb-object-property-editor.h>
 32 #include <lib/rb-util.h>
 33 #include <lib/rb-debug.h>
 34 
 35 static void rb_object_property_editor_class_init (RBObjectPropertyEditorClass *klass);
 36 static void rb_object_property_editor_init (RBObjectPropertyEditor *editor);
 37 
 38 struct _RBObjectPropertyEditorPrivate
 39 {
 40 	GObject *object;
 41 	char **properties;
 42 
 43 	gboolean changed;
 44 	gulong notify_id;
 45 };
 46 
 47 G_DEFINE_TYPE (RBObjectPropertyEditor, rb_object_property_editor, GTK_TYPE_GRID);
 48 
 49 /**
 50  * SECTION:rb-object-property-editor
 51  * @short_description: builds widgetry for editing simple GObject properties
 52  *
 53  * RBObjectPropertyEditor can be used to provide an interface to edit
 54  * simple (boolean, integer, enum, float) properties of a GObject.
 55  */
 56 
 57 enum
 58 {
 59 	PROP_0,
 60 	PROP_OBJECT,
 61 	PROP_PROPERTIES
 62 };
 63 
 64 enum
 65 {
 66 	CHANGED,
 67 	LAST_SIGNAL
 68 };
 69 
 70 static guint signals[LAST_SIGNAL] = { 0 };
 71 
 72 static void
 73 impl_finalize (GObject *object)
 74 {
 75 	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
 76 
 77 	g_strfreev (editor->priv->properties);
 78 
 79 	G_OBJECT_CLASS (rb_object_property_editor_parent_class)->finalize (object);
 80 }
 81 
 82 static void
 83 impl_dispose (GObject *object)
 84 {
 85 	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
 86 
 87 	if (editor->priv->object != NULL) {
 88 		if (editor->priv->notify_id) {
 89 			g_signal_handler_disconnect (editor->priv->object,
 90 						     editor->priv->notify_id);
 91 			editor->priv->notify_id = 0;
 92 		}
 93 		g_object_unref (editor->priv->object);
 94 		editor->priv->object = NULL;
 95 	}
 96 
 97 	G_OBJECT_CLASS (rb_object_property_editor_parent_class)->dispose (object);
 98 }
 99 
100 static void
101 notify_cb (GObject *object, GParamSpec *pspec, RBObjectPropertyEditor *editor)
102 {
103 	editor->priv->changed = TRUE;
104 }
105 
106 static void
107 focus_out_cb (GtkWidget *widget, GdkEvent *event, RBObjectPropertyEditor *editor)
108 {
109 	if (editor->priv->changed) {
110 		rb_debug ("emitting changed");
111 		g_signal_emit (editor, signals[CHANGED], 0);
112 		editor->priv->changed = FALSE;
113 	} else {
114 		rb_debug ("not emitting changed");
115 	}
116 }
117 
118 static GtkWidget *
119 create_boolean_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
120 {
121 	GtkWidget *control;
122 
123 	control = gtk_check_button_new ();
124 
125 	g_object_bind_property (editor->priv->object, property,
126 				control, "active",
127 				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
128 
129 	return control;
130 }
131 
132 static GtkWidget *
133 create_enum_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
134 {
135 	GParamSpecEnum *penum;
136 	GtkListStore *model;
137 	GtkCellRenderer *renderer;
138 	GtkWidget *control;
139 	int p;
140 
141 	control = gtk_combo_box_new ();
142 	penum = G_PARAM_SPEC_ENUM (pspec);
143 	renderer = gtk_cell_renderer_text_new ();
144 
145 	model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
146 	gtk_combo_box_set_model (GTK_COMBO_BOX (control), GTK_TREE_MODEL (model));
147 	for (p = 0; p < penum->enum_class->n_values; p++) {
148 		gtk_list_store_insert_with_values (model, NULL, p,
149 						   0, penum->enum_class->values[p].value_name,
150 						   1, p,
151 						   -1);
152 	}
153 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (control), renderer, TRUE);
154 	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (control), renderer, "text", 0, NULL);
155 
156 	g_object_bind_property (editor->priv->object, property,
157 				control, "active",
158 				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
159 
160 	return control;
161 }
162 
163 static GtkWidget *
164 create_int_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
165 {
166 	GParamSpecInt *pint;
167 	GtkWidget *control;
168 	GtkAdjustment *adjustment;
169 
170 	pint = G_PARAM_SPEC_INT (pspec);
171 
172 	adjustment = gtk_adjustment_new (pint->default_value,
173 					 pint->minimum,
174 					 pint->maximum + 1,
175 					 1.0,
176 					 1.0, 1.0);
177 
178 	control = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
179 	gtk_scale_set_digits (GTK_SCALE (control), 0);
180 
181 	g_object_bind_property (editor->priv->object, property,
182 				adjustment, "value",
183 				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
184 
185 	return control;
186 }
187 
188 static GtkWidget *
189 create_float_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
190 {
191 	GParamSpecFloat *pfloat;
192 	GtkWidget *control;
193 	GtkAdjustment *adjustment;
194 
195 	pfloat = G_PARAM_SPEC_FLOAT (pspec);
196 
197 	adjustment = gtk_adjustment_new (pfloat->default_value,
198 					 pfloat->minimum,
199 					 pfloat->maximum + pfloat->epsilon*2,
200 					 pfloat->epsilon*10,
201 					 0.1, 0.1);
202 
203 	control = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
204 
205 	g_object_bind_property (editor->priv->object, property,
206 				adjustment, "value",
207 				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
208 
209 	return control;
210 }
211 
212 static GtkWidget *
213 create_double_editor (RBObjectPropertyEditor *editor, const char *property, GParamSpec *pspec)
214 {
215 	GParamSpecDouble *pdouble;
216 	GtkWidget *control;
217 	GtkAdjustment *adjustment;
218 
219 	pdouble = G_PARAM_SPEC_DOUBLE (pspec);
220 
221 	adjustment = gtk_adjustment_new (pdouble->default_value,
222 					 pdouble->minimum,
223 					 pdouble->maximum + pdouble->epsilon*2,
224 					 pdouble->epsilon*10,
225 					 0.1, 0.1);
226 
227 	control = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adjustment);
228 
229 	g_object_bind_property (editor->priv->object, property,
230 				adjustment, "value",
231 				G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
232 
233 	return control;
234 }
235 
236 static void
237 impl_constructed (GObject *object)
238 {
239 	RBObjectPropertyEditor *editor;
240 	GObjectClass *klass;
241 	int i;
242 	int row;
243 
244 	RB_CHAIN_GOBJECT_METHOD (rb_object_property_editor_parent_class, constructed, object);
245 
246 	editor = RB_OBJECT_PROPERTY_EDITOR (object);
247 	klass = G_OBJECT_GET_CLASS (editor->priv->object);
248 
249 	editor->priv->notify_id = g_signal_connect (editor->priv->object, "notify", G_CALLBACK (notify_cb), editor);
250 
251 	row = 0;
252 	for (i = 0; editor->priv->properties[i] != NULL; i++) {
253 		GParamSpec *pspec;
254 		GtkWidget *label;
255 		GtkWidget *control;
256 		GType prop_type;
257 
258 		pspec = g_object_class_find_property (klass, editor->priv->properties[i]);
259 		if (pspec == NULL) {
260 			g_warning ("couldn't find property %s on object %s",
261 				   editor->priv->properties[i],
262 				   G_OBJECT_CLASS_NAME (klass));
263 			continue;
264 		}
265 
266 		prop_type = G_PARAM_SPEC_TYPE (pspec);
267 		if (prop_type == G_TYPE_PARAM_BOOLEAN) {
268 			control = create_boolean_editor (editor, editor->priv->properties[i], pspec);
269 		} else if (prop_type == G_TYPE_PARAM_ENUM) {
270 			control = create_enum_editor (editor, editor->priv->properties[i], pspec);
271 		} else if (prop_type == G_TYPE_PARAM_INT) {
272 			control = create_int_editor (editor, editor->priv->properties[i], pspec);
273 		} else if (prop_type == G_TYPE_PARAM_FLOAT) {
274 			control = create_float_editor (editor, editor->priv->properties[i], pspec);
275 		} else if (prop_type == G_TYPE_PARAM_DOUBLE) {
276 			control = create_double_editor (editor, editor->priv->properties[i], pspec);
277 		} else {
278 			/* can't do this */
279 			g_warning ("don't know how to edit %s", g_type_name (prop_type));
280 			continue;
281 		}
282 		g_signal_connect (control, "focus-out-event", G_CALLBACK (focus_out_cb), editor);
283 		gtk_widget_set_hexpand (control, TRUE);
284 
285 		label = gtk_label_new (g_param_spec_get_nick (pspec));
286 		gtk_widget_set_tooltip_text (label, g_param_spec_get_blurb (pspec));
287 
288 		gtk_grid_attach (GTK_GRID (editor),
289 				 label,
290 				 0, row, 1, 1);
291 		gtk_grid_attach (GTK_GRID (editor),
292 				 control,
293 				 1, row, 1, 1);
294 
295 		row++;
296 	}
297 }
298 
299 static void
300 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
301 {
302 	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
303 	switch (prop_id) {
304 	case PROP_OBJECT:
305 		editor->priv->object = g_value_dup_object (value);
306 		break;
307 	case PROP_PROPERTIES:
308 		editor->priv->properties = g_value_dup_boxed (value);
309 		break;
310 	default:
311 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
312 	}
313 }
314 
315 static void
316 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
317 {
318 	RBObjectPropertyEditor *editor = RB_OBJECT_PROPERTY_EDITOR (object);
319 	switch (prop_id) {
320 	case PROP_OBJECT:
321 		g_value_set_object (value, editor->priv->object);
322 		break;
323 	case PROP_PROPERTIES:
324 		g_value_set_boxed (value, editor->priv->properties);
325 		break;
326 	default:
327 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
328 	}
329 }
330 
331 static void
332 rb_object_property_editor_init (RBObjectPropertyEditor *editor)
333 {
334 	editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (editor,
335 						    RB_TYPE_OBJECT_PROPERTY_EDITOR,
336 						    RBObjectPropertyEditorPrivate);
337 }
338 
339 
340 static void
341 rb_object_property_editor_class_init (RBObjectPropertyEditorClass *klass)
342 {
343 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
344 
345 	object_class->constructed = impl_constructed;
346 	object_class->dispose = impl_dispose;
347 	object_class->finalize = impl_finalize;
348 	object_class->set_property = impl_set_property;
349 	object_class->get_property = impl_get_property;
350 
351 	/**
352 	 * RBObjectPropertyEditor::changed:
353 	 * @editor: the #RBObjectPropertyEditor
354 	 *
355 	 * Emitted when a property has been changed.
356 	 * This won't be emitted on every single change (use 'notify' on the
357 	 * object being edited for that), but rather when the edit widget
358 	 * for a property loses focus and the value was changed.
359 	 */
360 	signals[CHANGED] =
361 		g_signal_new ("changed",
362 			      G_OBJECT_CLASS_TYPE (object_class),
363 			      G_SIGNAL_RUN_LAST,
364 			      G_STRUCT_OFFSET (RBObjectPropertyEditorClass, changed),
365 			      NULL, NULL,
366 			      NULL,
367 			      G_TYPE_NONE,
368 			      0);
369 
370 	/**
371 	 * RBObjectPropertyEditor:object
372 	 *
373 	 * The object to edit.
374 	 */
375 	g_object_class_install_property (object_class,
376 					 PROP_OBJECT,
377 					 g_param_spec_object ("object",
378 							      "object",
379 							      "object to edit",
380 							      G_TYPE_OBJECT,
381 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
382 
383 	/**
384 	 * RBObjectPropertyEditor:properties
385 	 * 
386 	 * Array of names of properties to edit.
387 	 */
388 	g_object_class_install_property (object_class,
389 					 PROP_PROPERTIES,
390 					 g_param_spec_boxed ("properties",
391 							     "properties",
392 							     "properties to edit",
393 							     G_TYPE_STRV,
394 							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
395 
396 	g_type_class_add_private (klass, sizeof (RBObjectPropertyEditorPrivate));
397 }
398 
399 /**
400  * rb_object_property_editor_new:
401  * @object: the object to edit
402  * @properties: array of names of properties to edit
403  *
404  * Creates a property editor for the specified properties of an object.
405  *
406  * Return value: property editor widget.
407  */
408 GtkWidget *
409 rb_object_property_editor_new (GObject *object, char **properties)
410 {
411 	return GTK_WIDGET (g_object_new (RB_TYPE_OBJECT_PROPERTY_EDITOR,
412 					 "object", object,
413 					 "properties", properties,
414 					 "column-spacing", 6,
415 					 NULL));
416 }