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 }