1
/* -*- mode: c; c-basic-offset: 4; -*-
3
* parameter-editor.c - Automatically constructs a GUI for editing the
4
* parameters of a ParameterHolder instance.
6
* Fyre - rendering and interactive exploration of chaotic functions
7
* Copyright (C) 2004-2005 David Trowbridge and Micah Dowty
9
* This program is free software; you can redistribute it and/or
10
* modify it under the terms of the GNU General Public License
11
* as published by the Free Software Foundation; either version 2
12
* of the License, or (at your option) any later version.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
* GNU General Public License for more details.
19
* You should have received a copy of the GNU General Public License
20
* along with this program; if not, write to the Free Software
21
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
#include "parameter-editor.h"
26
#include "color-button.h"
30
static void parameter_editor_class_init(ParameterEditorClass *klass);
31
static void parameter_editor_init(ParameterEditor *self);
32
static void parameter_editor_finalize(GObject *object);
34
static void parameter_editor_attach(ParameterEditor *self, ParameterHolder *holder);
35
static void parameter_editor_add_paramspec(ParameterEditor *self, GParamSpec *spec);
36
static void parameter_editor_add_group_heading(ParameterEditor *self, const gchar *group);
37
static void parameter_editor_add_row(ParameterEditor *self, GParamSpec *spec, GtkWidget *row);
38
static void parameter_editor_add_dependency(ParameterEditor *self, GtkWidget *widget, const gchar *dependency_name);
39
static void parameter_editor_add_labeled_row(ParameterEditor *self, GParamSpec *spec, GtkWidget *row);
41
static void parameter_editor_connect_notify(ParameterEditor *self,
43
const gchar *property_name,
46
static void parameter_editor_add_numeric(ParameterEditor *self, GParamSpec *spec);
47
static void parameter_editor_add_color(ParameterEditor *self, GParamSpec *spec);
48
static void parameter_editor_add_boolean(ParameterEditor *self, GParamSpec *spec);
49
static void parameter_editor_add_enum(ParameterEditor *self, GParamSpec *spec);
51
static void on_changed_numeric(GtkWidget *widget, ParameterEditor *self);
52
static void on_changed_color(GtkWidget *widget, ParameterEditor *self);
53
static void on_changed_boolean(GtkWidget *widget, ParameterEditor *self);
54
static void on_changed_enum(GtkWidget *widget, ParameterEditor *self);
56
static void on_notify_numeric(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget);
57
static void on_notify_color(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget);
58
static void on_notify_opacity(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget);
59
static void on_notify_boolean(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget);
60
static void on_notify_dependency(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget);
61
static void on_notify_enum(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget);
64
/************************************************************************************/
65
/**************************************************** Initialization / Finalization */
66
/************************************************************************************/
68
GType parameter_editor_get_type(void) {
69
static GType pe_type = 0;
72
static const GTypeInfo pe_info = {
73
sizeof(ParameterEditorClass),
75
NULL, /* base_finalize */
76
(GClassInitFunc) parameter_editor_class_init,
77
NULL, /* class_finalize */
78
NULL, /* class_data */
79
sizeof(ParameterEditor),
81
(GInstanceInitFunc) parameter_editor_init,
84
pe_type = g_type_register_static(GTK_TYPE_VBOX, "ParameterEditor", &pe_info, 0);
90
static void parameter_editor_class_init(ParameterEditorClass *klass) {
91
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
93
gobject_class->finalize = parameter_editor_finalize;
96
static void parameter_editor_init(ParameterEditor *self) {
97
self->label_sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
100
static void parameter_editor_finalize(GObject *object) {
101
ParameterEditor *self = PARAMETER_EDITOR(object);
104
g_object_unref(self->holder);
108
if (self->label_sizegroup) {
109
g_object_unref(self->label_sizegroup);
110
self->label_sizegroup = NULL;
113
if (self->previous_group) {
114
g_free(self->previous_group);
115
self->previous_group = NULL;
119
GtkWidget* parameter_editor_new(ParameterHolder *holder) {
120
ParameterEditor *self = g_object_new(parameter_editor_get_type(), NULL);
121
parameter_editor_attach(self, holder);
122
return GTK_WIDGET(self);
126
/************************************************************************************/
127
/********************************************************************* GUI Building */
128
/************************************************************************************/
130
static void parameter_editor_attach(ParameterEditor *self, ParameterHolder *holder) {
131
/* Attach this parameter editor to a holder, adding widgets for
132
* each paramspec in the holder with a PARAM_IN_GUI flag
134
GParamSpec** properties;
138
self->holder = g_object_ref(holder);
140
properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(holder), &n_properties);
141
for (i=0; i<n_properties; i++) {
142
if (properties[i]->flags & PARAM_IN_GUI)
143
parameter_editor_add_paramspec(self, properties[i]);
148
static void parameter_editor_add_paramspec(ParameterEditor *self, GParamSpec *spec) {
151
/* Get this parameter's group name, adding a new group header if it's changed */
152
group = param_spec_get_group(spec);
154
if ((!self->previous_group) || strcmp((void *) group, self->previous_group))
155
parameter_editor_add_group_heading(self, group);
156
if (self->previous_group)
157
g_free(self->previous_group);
158
self->previous_group = g_strdup(group);
161
/* Pick a type-dependent procedure for adding this parameter to the GUI */
163
if (spec->value_type == G_TYPE_DOUBLE)
164
parameter_editor_add_numeric(self, spec);
166
else if (spec->value_type == G_TYPE_UINT)
167
parameter_editor_add_numeric(self, spec);
169
else if (spec->value_type == GDK_TYPE_COLOR)
170
parameter_editor_add_color(self, spec);
172
else if (spec->value_type == G_TYPE_BOOLEAN)
173
parameter_editor_add_boolean(self, spec);
175
else if (g_type_is_a (spec->value_type, G_TYPE_ENUM))
176
parameter_editor_add_enum (self, spec);
179
g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
180
"Can't edit values of type %s",
181
g_type_name(spec->value_type));
184
static void parameter_editor_add_group_heading(ParameterEditor *self, const gchar *group) {
188
/* Add a separator if this isn't the first group */
189
if (self->previous_group)
190
gtk_box_pack_start(GTK_BOX(self), gtk_hseparator_new(), FALSE, FALSE, 4);
192
/* Make a label with the group name bold and centered */
193
markup = g_strdup_printf("<b>%s</b>", group);
194
label = gtk_label_new(NULL);
195
gtk_label_set_markup(GTK_LABEL(label), markup);
197
gtk_box_pack_start(GTK_BOX(self), label, FALSE, FALSE, 4);
200
static void parameter_editor_add_row(ParameterEditor *self, GParamSpec *spec, GtkWidget *row) {
203
gtk_box_pack_start(GTK_BOX(self), row, FALSE, FALSE, 2);
205
dep = param_spec_get_dependency(spec);
207
parameter_editor_add_dependency(self, row, dep);
210
static void parameter_editor_add_labeled_row(ParameterEditor *self, GParamSpec *spec, GtkWidget *row) {
211
GtkWidget *hbox, *label;
214
hbox = gtk_hbox_new(FALSE, 0);
215
text = g_strdup_printf("%s:", g_param_spec_get_nick(spec));
216
label = gtk_label_new(text);
218
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
219
gtk_size_group_add_widget(self->label_sizegroup, label);
220
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 6);
221
gtk_box_pack_start(GTK_BOX(hbox), row, TRUE, TRUE, 6);
223
parameter_editor_add_row(self, spec, hbox);
226
static void parameter_editor_add_dependency(ParameterEditor *self, GtkWidget *widget, const gchar *dependency_name) {
227
/* Add a notify callback to our dependency that enables or disables the given widget.
228
* Call the notify callback once right away to set up our initial sensitivity.
233
signal_name = g_strdup_printf("notify::%s", dependency_name);
234
g_signal_connect(self->holder, signal_name, G_CALLBACK(on_notify_dependency), widget);
237
spec = g_object_class_find_property(G_OBJECT_GET_CLASS(self->holder), dependency_name);
238
on_notify_dependency(self->holder, spec, widget);
242
/************************************************************************************/
243
/********************************************************************* Type Editors */
244
/************************************************************************************/
246
static void parameter_editor_connect_notify(ParameterEditor *self,
248
const gchar *property_name,
250
/* Connect to the notify::<property> signal so we can update the
251
* GUI automatically when someone else changes the property. The
252
* callback needs to know which widget is associated with this property,
253
* and it needs a pointer to our ParameterEditor instance. We do this by
254
* passing the widget as user_data but using g_object_set_data to
255
* attach the ParameterEditor to the widget.
258
signal_name = g_strdup_printf("notify::%s", property_name);
259
g_object_set_data(G_OBJECT(widget), "ParameterEditor", self);
260
g_signal_connect(self->holder, signal_name, func, widget);
264
static void parameter_editor_add_numeric(ParameterEditor *self, GParamSpec *spec) {
266
GtkObject *adjustment;
267
gdouble climb_rate = 0.1;
272
gdouble step_increment = 0.1;
273
gdouble page_increment = 0.1;
274
gdouble page_size = 0;
275
const ParameterIncrements *increments;
276
GValue gv, double_gv;
278
/* Look for upper and lower bounds in the GParamSpec to override our defaults */
279
if (G_IS_PARAM_SPEC_DOUBLE(spec)) {
280
GParamSpecDouble *s = G_PARAM_SPEC_DOUBLE(spec);
284
else if (G_IS_PARAM_SPEC_UINT(spec)) {
285
GParamSpecUInt *s = G_PARAM_SPEC_UINT(spec);
290
/* Look for a ParameterIncrements structure attached to this parameter.
291
* If we find one, set the increments and number of digits from it.
293
increments = param_spec_get_increments(spec);
295
digits = increments->digits;
296
step_increment = increments->step;
297
page_increment = increments->page;
298
climb_rate = increments->step;
301
/* Get the parameter's current value */
302
memset(&gv, 0, sizeof(gv));
303
memset(&double_gv, 0, sizeof(double_gv));
304
g_value_init(&gv, spec->value_type);
305
g_value_init(&double_gv, G_TYPE_DOUBLE);
306
g_object_get_property(G_OBJECT(self->holder), spec->name, &gv);
307
g_value_transform(&gv, &double_gv);
308
value = g_value_get_double(&double_gv);
310
g_value_unset(&double_gv);
312
adjustment = gtk_adjustment_new(value, lower, upper, step_increment, page_increment, page_size);
313
spinner = gtk_spin_button_new(GTK_ADJUSTMENT(adjustment), climb_rate, digits);
315
/* Set up our callback on change */
316
g_object_set_data(G_OBJECT(spinner), "ParamSpec", spec);
317
g_signal_connect(spinner, "value-changed", G_CALLBACK(on_changed_numeric), self);
319
parameter_editor_connect_notify(self, spinner, spec->name, G_CALLBACK(on_notify_numeric));
320
parameter_editor_add_labeled_row(self, spec, spinner);
323
static void parameter_editor_add_color(ParameterEditor *self, GParamSpec *spec) {
324
GtkWidget *color_button;
325
const gchar* opacity_property;
326
guint16 opacity = 0xFFFF;
327
GdkColor color = {0,0,0,0};
330
/* See if this color property has a matching opacity property */
331
opacity_property = g_param_spec_get_qdata(spec, g_quark_from_static_string("opacity-property"));
332
if (opacity_property) {
334
/* Get the current opacity */
335
memset(&gv, 0, sizeof(gv));
336
g_value_init(&gv, G_TYPE_UINT);
337
g_object_get_property(G_OBJECT(self->holder), opacity_property, &gv);
338
opacity = g_value_get_uint(&gv);
342
/* Get the current color */
343
memset(&gv, 0, sizeof(gv));
344
g_value_init(&gv, GDK_TYPE_COLOR);
345
g_object_get_property(G_OBJECT(self->holder), spec->name, &gv);
346
color = *(GdkColor*)g_value_get_boxed(&gv);
349
color_button = color_button_new_with_defaults(g_param_spec_get_nick(spec), &color, opacity);
351
/* Set up our callback on change */
352
g_object_set_data(G_OBJECT(color_button), "ParamSpec", spec);
353
g_signal_connect(color_button, "changed", G_CALLBACK(on_changed_color), self);
355
parameter_editor_connect_notify(self, color_button, spec->name, G_CALLBACK(on_notify_color));
356
if (opacity_property)
357
parameter_editor_connect_notify(self, color_button, opacity_property, G_CALLBACK(on_notify_opacity));
359
parameter_editor_add_labeled_row(self, spec, color_button);
362
static void parameter_editor_add_boolean(ParameterEditor *self, GParamSpec *spec) {
366
check = gtk_check_button_new();
368
/* Get the parameter's current value */
369
memset(&gv, 0, sizeof(gv));
370
g_value_init(&gv, spec->value_type);
371
g_object_get_property(G_OBJECT(self->holder), spec->name, &gv);
372
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), g_value_get_boolean(&gv));
375
/* Set up our callback on change */
376
g_object_set_data(G_OBJECT(check), "ParamSpec", spec);
377
g_signal_connect(check, "toggled", G_CALLBACK(on_changed_boolean), self);
379
parameter_editor_connect_notify(self, check, spec->name, G_CALLBACK(on_notify_boolean));
380
parameter_editor_add_labeled_row(self, spec, check);
383
static void parameter_editor_add_enum(ParameterEditor *self, GParamSpec *spec) {
388
#if (GTK_MINOR_VERSION < 4)
392
klass = (GEnumClass*) g_type_class_ref (spec->value_type);
394
#if (GTK_MINOR_VERSION >= 4)
395
combo = gtk_combo_box_new_text ();
396
for (i = 0; i < klass->n_values; i++)
400
value = g_enum_get_value (klass, i);
402
gtk_combo_box_append_text (GTK_COMBO_BOX (combo), value->value_nick);
405
combo = gtk_option_menu_new ();
406
menu = gtk_menu_new ();
408
for (i = 0; i < klass->n_values; i++)
412
value = g_enum_get_value (klass, i);
414
gtk_menu_shell_append (GTK_MENU_SHELL (menu), gtk_menu_item_new_with_label (value->value_nick));
417
gtk_option_menu_set_menu (GTK_OPTION_MENU (combo), menu);
420
/* Get the parameter's current value */
421
memset (&gv, 0, sizeof (gv));
422
g_value_init (&gv, spec->value_type);
423
g_object_get_property (G_OBJECT (self->holder), spec->name, &gv);
424
#if (GTK_MINOR_VERSION >= 4)
425
gtk_combo_box_set_active (GTK_COMBO_BOX (combo), g_value_get_enum (&gv));
427
gtk_option_menu_set_history (GTK_OPTION_MENU (combo), g_value_get_enum (&gv));
431
/* Set up our callback on change */
432
g_object_set_data (G_OBJECT (combo), "ParamSpec", spec);
433
g_signal_connect (combo, "changed", G_CALLBACK (on_changed_enum), self);
435
parameter_editor_connect_notify (self, combo, spec->name, G_CALLBACK (on_notify_enum));
436
parameter_editor_add_labeled_row (self, spec, combo);
440
/************************************************************************************/
441
/***************************************************************** Widget callbacks */
442
/************************************************************************************/
444
static void on_changed_numeric(GtkWidget *widget, ParameterEditor *self) {
445
GParamSpec *spec = g_object_get_data(G_OBJECT(widget), "ParamSpec");
446
GValue gv, double_gv;
448
if (self->suppress_changed)
451
/* Convert the current spinner value from a double to whatever type we need,
452
* and set the property from that converted value.
454
memset(&gv, 0, sizeof(gv));
455
memset(&double_gv, 0, sizeof(double_gv));
456
g_value_init(&gv, spec->value_type);
457
g_value_init(&double_gv, G_TYPE_DOUBLE);
458
g_value_set_double(&double_gv, gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget)));
459
g_value_transform(&double_gv, &gv);
461
self->suppress_notify = TRUE;
462
g_object_set_property(G_OBJECT(self->holder), spec->name, &gv);
463
self->suppress_notify = FALSE;
466
g_value_unset(&double_gv);
469
static void on_changed_color(GtkWidget *widget, ParameterEditor *self) {
470
GParamSpec *spec = g_object_get_data(G_OBJECT(widget), "ParamSpec");
472
const gchar* opacity_property;
475
if (self->suppress_changed)
478
opacity_property = g_param_spec_get_qdata(spec, g_quark_from_static_string("opacity-property"));
479
color_button_get_color(COLOR_BUTTON(widget), &c);
480
opacity = color_button_get_alpha(COLOR_BUTTON(widget));
482
/* Set both color and opacity if we have both. If we don't have
483
* an opacity property, opacity_property will be NULL and the opacity
484
* parameter will be ignored.
486
self->suppress_notify = TRUE;
487
g_object_set(self->holder,
489
opacity_property, opacity,
491
self->suppress_notify = FALSE;
494
static void on_changed_boolean(GtkWidget *widget, ParameterEditor *self) {
495
GParamSpec *spec = g_object_get_data(G_OBJECT(widget), "ParamSpec");
498
if (self->suppress_changed)
501
active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
503
self->suppress_notify = TRUE;
504
g_object_set(self->holder,
507
self->suppress_notify = FALSE;
510
static void on_changed_enum(GtkWidget *widget, ParameterEditor *self) {
511
GParamSpec *spec = g_object_get_data (G_OBJECT (widget), "ParamSpec");
514
if (self->suppress_changed)
517
#if (GTK_MINOR_VERSION >= 4)
518
active = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
520
active = gtk_option_menu_get_history (GTK_OPTION_MENU (widget));
523
self->suppress_notify = TRUE;
524
g_object_set (self->holder,
528
self->suppress_notify = FALSE;
532
/************************************************************************************/
533
/***************************************************************** Notify callbacks */
534
/************************************************************************************/
536
static void on_notify_numeric(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget) {
537
ParameterEditor *self = g_object_get_data(G_OBJECT(widget), "ParameterEditor");
538
GValue gv, double_gv;
540
if (self->suppress_notify)
543
/* Conver the current property value to a double and set our spin button */
544
memset(&gv, 0, sizeof(gv));
545
memset(&double_gv, 0, sizeof(double_gv));
546
g_value_init(&gv, spec->value_type);
547
g_value_init(&double_gv, G_TYPE_DOUBLE);
548
g_object_get_property(G_OBJECT(holder), spec->name, &gv);
549
g_value_transform(&gv, &double_gv);
551
self->suppress_changed = TRUE;
552
gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), g_value_get_double(&double_gv));
553
self->suppress_changed = FALSE;
556
g_value_unset(&double_gv);
559
static void on_notify_color(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget) {
560
ParameterEditor *self = g_object_get_data(G_OBJECT(widget), "ParameterEditor");
563
if (self->suppress_notify)
566
g_object_get(holder, spec->name, &c, NULL);
568
self->suppress_changed = TRUE;
569
color_button_set_color(COLOR_BUTTON(widget), c);
570
self->suppress_changed = FALSE;
573
static void on_notify_opacity(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget) {
574
ParameterEditor *self = g_object_get_data(G_OBJECT(widget), "ParameterEditor");
577
if (self->suppress_notify)
580
g_object_get(holder, spec->name, &opacity, NULL);
582
self->suppress_changed = TRUE;
583
color_button_set_alpha(COLOR_BUTTON(widget), opacity);
584
self->suppress_changed = FALSE;
587
static void on_notify_boolean(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget) {
588
ParameterEditor *self = g_object_get_data(G_OBJECT(widget), "ParameterEditor");
591
if (self->suppress_notify)
594
g_object_get(holder, spec->name, &active, NULL);
596
self->suppress_changed = TRUE;
597
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), active);
598
self->suppress_changed = FALSE;
601
static void on_notify_dependency(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget) {
603
g_object_get(holder, spec->name, &active, NULL);
604
gtk_widget_set_sensitive(widget, active);
607
static void on_notify_enum(ParameterHolder *holder, GParamSpec *spec, GtkWidget *widget) {
608
ParameterEditor *self = g_object_get_data (G_OBJECT (widget), "ParameterEditor");
611
if (self->suppress_notify)
614
memset (&gv, 0, sizeof (gv));
615
g_value_init (&gv, spec->value_type);
616
g_object_get_property (G_OBJECT (holder), spec->name, &gv);
618
self->suppress_changed = TRUE;
619
#if (GTK_MINOR_VERSION >= 4)
620
gtk_combo_box_set_active (GTK_COMBO_BOX (widget), g_value_get_enum (&gv));
622
gtk_option_menu_set_history (GTK_OPTION_MENU (widget), g_value_get_enum (&gv));
624
self->suppress_changed = FALSE;