1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include <stdio.h>
20 #include <math.h>
21 #include <unistd.h>
22 #include <string.h>
23 
24 #include "GxRegler.h"
25 #include "GxControlParameter.h"
26 #include <gdk/gdkkeysyms.h>
27 
28 #include "regler_marshal.cc"
29 
30 
31 #define P_(s) (s)   // FIXME -> gettext
32 #define I_(s) (s)   // FIXME -> gettext
33 
34 /****************************************************************
35  ** GxRegler
36  */
37 
38 struct _GxReglerPrivate
39 {
40 	GtkRequisition value_req;
41 	gdouble last_step;
42     int round_digits;
43 	GtkAdjustment *adjustment;
44 	gchar *var_id;
45 	GtkLabel *label;
46 	gboolean show_value:1;
47 	GtkPositionType value_position:2;
48 	gdouble value_xalign;
49 	PangoLayout *value_layout;
50 };
51 
52 enum {
53   VALUE_ENTRY,
54   FORMAT_VALUE,
55   INPUT_VALUE,
56   LAST_SIGNAL
57 };
58 
59 enum {
60 	PROP_VAR_ID = 1,
61 	PROP_SHOW_VALUE,
62 	PROP_VALUE_POSITION,
63 	PROP_VALUE_XALIGN,
64 	PROP_LABEL_REF,
65 	PROP_DIGITS,
66 };
67 
68 static guint signals[LAST_SIGNAL];
69 
70 static void gx_regler_class_init(GxReglerClass *klass);
71 static void gx_regler_init(GxRegler *regler);
72 static void gx_control_parameter_interface_init (GxControlParameterIface *iface);
73 static void gx_regler_finalize(GObject*);
74 static void gx_regler_destroy(GtkWidget *object);
75 static void gx_regler_set_property(
76 	GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
77 static void gx_regler_get_property(
78 	GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
79 static gboolean gx_regler_button_release (GtkWidget *widget, GdkEventButton *event);
80 static gboolean gx_regler_scroll (GtkWidget *widget, GdkEventScroll *event);
81 static gboolean gx_regler_change_value(GtkRange *range, GtkScrollType scroll, gdouble value);
82 static void gx_regler_value_changed(GtkRange *range);
83 static gboolean gx_regler_value_entry(GxRegler *regler, GdkRectangle *rect, GdkEventButton *event);
84 static void gx_regler_change_adjustment(GxRegler *regler, GtkAdjustment *adjustment);
85 static void gx_regler_adjustment_notified(GObject *gobject, GParamSpec *pspec);
86 static void gx_regler_move_slider(GtkRange *range, GtkScrollType scroll);
87 
G_DEFINE_ABSTRACT_TYPE_WITH_CODE(GxRegler,gx_regler,GTK_TYPE_RANGE,G_ADD_PRIVATE (GxRegler)G_IMPLEMENT_INTERFACE (GX_TYPE_CONTROL_PARAMETER,gx_control_parameter_interface_init))88 G_DEFINE_ABSTRACT_TYPE_WITH_CODE(GxRegler, gx_regler, GTK_TYPE_RANGE,
89 								 G_ADD_PRIVATE(GxRegler)
90                                  G_IMPLEMENT_INTERFACE(GX_TYPE_CONTROL_PARAMETER,
91                                                        gx_control_parameter_interface_init))
92 
93 static void gx_regler_value_changed(GtkRange *range)
94 {
95 	gtk_widget_queue_draw(GTK_WIDGET(range));
96 }
97 
98 
99 static void
gx_regler_cp_configure(GxControlParameter * self,const gchar * group,const gchar * name,gdouble lower,gdouble upper,gdouble step)100 gx_regler_cp_configure(GxControlParameter *self, const gchar* group, const gchar *name, gdouble lower, gdouble upper, gdouble step)
101 {
102 	g_return_if_fail(GX_IS_REGLER(self));
103 	GxRegler *regler = GX_REGLER(self);
104 	if (regler->priv->label) {
105 		gtk_label_set_text(regler->priv->label, name);
106 	}
107 	GtkRange *range = GTK_RANGE(self);
108 	gtk_range_set_range(range, lower, upper);
109 	gtk_range_set_increments(range, step, 10*step);
110 }
111 
112 static gdouble
gx_regler_cp_get_value(GxControlParameter * self)113 gx_regler_cp_get_value(GxControlParameter *self)
114 {
115 	return gtk_range_get_value(GTK_RANGE(self));
116 }
117 
118 static void
gx_regler_cp_set_value(GxControlParameter * self,gdouble value)119 gx_regler_cp_set_value(GxControlParameter *self, gdouble value)
120 {
121 	gtk_range_set_value(GTK_RANGE(self), value);
122 }
123 
124 static void
gx_control_parameter_interface_init(GxControlParameterIface * iface)125 gx_control_parameter_interface_init(GxControlParameterIface *iface)
126 {
127   iface->cp_configure = gx_regler_cp_configure;
128   iface->cp_set_value = gx_regler_cp_set_value;
129   iface->cp_get_value = gx_regler_cp_get_value;
130 }
131 
gx_boolean_handled_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer dummy)132 static gboolean gx_boolean_handled_accumulator(
133 	GSignalInvocationHint *ihint, GValue *return_accu,
134 	const GValue *handler_return, gpointer dummy)
135 {
136   gboolean continue_emission;
137   gboolean signal_handled;
138 
139   signal_handled = g_value_get_boolean (handler_return);
140   g_value_set_boolean (return_accu, signal_handled);
141   continue_emission = !signal_handled;
142 
143   return continue_emission;
144 }
145 
single_string_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer dummy)146 static gboolean single_string_accumulator(
147 	GSignalInvocationHint *ihint, GValue *return_accu,
148 	const GValue *handler_return, gpointer dummy)
149 {
150 	gboolean continue_emission;
151 	const gchar *str;
152 
153 	str = g_value_get_string(handler_return);
154 	g_value_set_string(return_accu, str);
155 	continue_emission = str == NULL;
156 	return continue_emission;
157 }
158 
159 #define add_slider_binding(binding_set, keyval, mask, scroll)              \
160   gtk_binding_entry_add_signal (binding_set, keyval, mask,                 \
161                                 I_("move-slider"), 1, \
162                                 GTK_TYPE_SCROLL_TYPE, scroll)
163 
gx_regler_class_init(GxReglerClass * klass)164 static void gx_regler_class_init(GxReglerClass *klass)
165 {
166 	GObjectClass   *gobject_class = G_OBJECT_CLASS(klass);
167 	GtkRangeClass *range_class = (GtkRangeClass*)klass;
168 	GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
169 	GtkBindingSet  *binding_set;
170 
171 	gobject_class->finalize = gx_regler_finalize;
172 	gobject_class->set_property = gx_regler_set_property;
173 	gobject_class->get_property = gx_regler_get_property;
174 
175 	widget_class->destroy = gx_regler_destroy;
176 	widget_class->button_release_event = gx_regler_button_release;
177 	widget_class->scroll_event = gx_regler_scroll;
178 
179 	range_class->value_changed = gx_regler_value_changed;
180 	range_class->change_value = gx_regler_change_value;
181 	range_class->move_slider = gx_regler_move_slider;
182 
183 	klass->value_entry = gx_regler_value_entry;
184 
185 	gtk_widget_class_set_css_name(widget_class, "gx-regler");
186 
187 	signals[VALUE_ENTRY] =
188 		g_signal_new (I_("value-entry"),
189 		              G_OBJECT_CLASS_TYPE (klass),
190 		              G_SIGNAL_RUN_LAST,
191 		              G_STRUCT_OFFSET (GxReglerClass, value_entry),
192 		              gx_boolean_handled_accumulator, NULL,
193 		              marshal_BOOLEAN__BOXED_BOXED,
194 		              G_TYPE_BOOLEAN, 2, GDK_TYPE_RECTANGLE,
195 		              GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
196 
197 	signals[FORMAT_VALUE] =
198 		g_signal_new (I_("format-value"),
199 					  G_TYPE_FROM_CLASS (gobject_class),
200 					  G_SIGNAL_RUN_LAST,
201 					  G_STRUCT_OFFSET (GxReglerClass, format_value),
202 					  single_string_accumulator, NULL,
203 					  marshal_STRING__DOUBLE,
204 					  G_TYPE_STRING, 1,
205 					  G_TYPE_DOUBLE);
206 
207 	signals[INPUT_VALUE] =
208 		g_signal_new (I_("input-value"),
209 					  G_TYPE_FROM_CLASS (gobject_class),
210 					  G_SIGNAL_RUN_LAST,
211 					  G_STRUCT_OFFSET (GxReglerClass, input_value),
212 					  NULL, NULL,
213 					  NULL,
214 					  G_TYPE_INT, 2,
215 					  G_TYPE_POINTER, //FIXME: GtkEntry*, gdouble*
216 					  G_TYPE_POINTER);
217 
218     gtk_widget_class_install_style_property(
219 		widget_class,
220 		g_param_spec_int("border-radius",
221             P_("Border Radius"),
222             P_("The radius of the corners in pixels"),
223             0, 100, 0,
224             GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
225 
226     gtk_widget_class_install_style_property(
227 		widget_class,
228 		g_param_spec_float("bevel",
229             P_("Bevel"),
230             P_("The bevel effect"),
231             -1.0, 1.0, 0.0,
232             GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
233 
234 	gtk_widget_class_install_style_property(
235 		widget_class,
236 		g_param_spec_boolean("show-value",
237 		                     P_("show value"),
238 		                     P_("display the value"),
239 		                     TRUE,
240 		                     GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
241 	gtk_widget_class_install_style_property(
242 		widget_class,
243 		g_param_spec_int("value-spacing",P_("Value spacing"),
244 		                 P_("Distance of value display"),
245 		                 0, 100, 5, GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
246 	gtk_widget_class_install_style_property(
247 		widget_class,
248 		g_param_spec_boxed("value-border",
249 		                   P_("Value Spacing"),
250 		                   P_("Extra space for value display"),
251 		                   GTK_TYPE_BORDER,
252 		                   GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
253 
254 	g_object_class_install_property(
255 		gobject_class, PROP_SHOW_VALUE,
256 		g_param_spec_boolean("show-value",
257 		                     P_("show value"),
258 		                     P_("display the value"),
259 		                     TRUE,
260 		                     GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
261 	g_object_class_install_property(
262 		gobject_class, PROP_LABEL_REF,
263 		g_param_spec_object("label-ref",
264 		                    P_("Label ref"),
265 		                    P_("GtkLabel for caption"),
266 		                    GTK_TYPE_LABEL,
267 		                    GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
268 	g_object_class_install_property(
269 		gobject_class, PROP_VALUE_POSITION,
270 		g_param_spec_enum("value-position",
271 		                  P_("Value Position"),
272 		                  P_("The position of the value display"),
273 		                  GTK_TYPE_POSITION_TYPE,
274 		                  GTK_POS_BOTTOM,
275 		                  GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
276 	g_object_class_install_property(
277 		gobject_class, PROP_VALUE_XALIGN,
278 		g_param_spec_double("value-xalign",
279 		                    P_("Value Alignment"),
280 		                    P_("The horizontal position of the value (0..1)"),
281 		                    0, 1, 0.5,
282 		                    GParamFlags(G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS)));
283 	g_object_class_install_property(
284 		gobject_class, PROP_DIGITS,
285 		g_param_spec_int("digits",
286 		                 P_("Digits"),
287 		                 P_("Number of digits for display"),
288 		                 0, 10, 1,
289 		                 GParamFlags(G_PARAM_READABLE|G_PARAM_STATIC_STRINGS)));
290 	g_object_class_override_property(gobject_class, PROP_VAR_ID, "var-id");
291 
292 	binding_set = gtk_binding_set_by_class(klass);
293 
294 	add_slider_binding (binding_set, GDK_KEY_Left, (GdkModifierType)0,
295 	                    GTK_SCROLL_STEP_LEFT);
296 
297 	add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
298 	                    GTK_SCROLL_PAGE_LEFT);
299 
300 	add_slider_binding (binding_set, GDK_KEY_KP_Left, (GdkModifierType)0,
301 	                    GTK_SCROLL_STEP_LEFT);
302 
303 	add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
304 	                    GTK_SCROLL_PAGE_LEFT);
305 
306 	add_slider_binding (binding_set, GDK_KEY_Right, (GdkModifierType)0,
307 	                    GTK_SCROLL_STEP_RIGHT);
308 
309 	add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
310 	                    GTK_SCROLL_PAGE_RIGHT);
311 
312 	add_slider_binding (binding_set, GDK_KEY_KP_Right, (GdkModifierType)0,
313 	                    GTK_SCROLL_STEP_RIGHT);
314 
315 	add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
316 	                    GTK_SCROLL_PAGE_RIGHT);
317 
318 	add_slider_binding (binding_set, GDK_KEY_Up, (GdkModifierType)0,
319 	                    GTK_SCROLL_STEP_UP);
320 
321 	add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
322 	                    GTK_SCROLL_PAGE_UP);
323 
324 	add_slider_binding (binding_set, GDK_KEY_KP_Up, (GdkModifierType)0,
325 	                    GTK_SCROLL_STEP_UP);
326 
327 	add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
328 	                    GTK_SCROLL_PAGE_UP);
329 
330 	add_slider_binding (binding_set, GDK_KEY_Down, (GdkModifierType)0,
331 	                    GTK_SCROLL_STEP_DOWN);
332 
333 	add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
334 	                    GTK_SCROLL_PAGE_DOWN);
335 
336 	add_slider_binding (binding_set, GDK_KEY_KP_Down, (GdkModifierType)0,
337 	                    GTK_SCROLL_STEP_DOWN);
338 
339 	add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
340 	                    GTK_SCROLL_PAGE_DOWN);
341 
342 	add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
343 	                    GTK_SCROLL_PAGE_LEFT);
344 
345 	add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
346 	                    GTK_SCROLL_PAGE_LEFT);
347 
348 	add_slider_binding (binding_set, GDK_KEY_Page_Up, (GdkModifierType)0,
349 	                    GTK_SCROLL_PAGE_UP);
350 
351 	add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, (GdkModifierType)0,
352 	                    GTK_SCROLL_PAGE_UP);
353 
354 	add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
355 	                    GTK_SCROLL_PAGE_RIGHT);
356 
357 	add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
358 	                    GTK_SCROLL_PAGE_RIGHT);
359 
360 	add_slider_binding (binding_set, GDK_KEY_Page_Down, (GdkModifierType)0,
361 	                    GTK_SCROLL_PAGE_DOWN);
362 
363 	add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, (GdkModifierType)0,
364 	                    GTK_SCROLL_PAGE_DOWN);
365 
366 	/* Logical bindings (vs. visual bindings above) */
367 
368 	add_slider_binding (binding_set, GDK_KEY_plus, (GdkModifierType)0,
369 	                    GTK_SCROLL_STEP_FORWARD);
370 
371 	add_slider_binding (binding_set, GDK_KEY_minus, (GdkModifierType)0,
372 	                    GTK_SCROLL_STEP_BACKWARD);
373 
374 	add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
375 	                    GTK_SCROLL_PAGE_FORWARD);
376 
377 	add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
378 	                    GTK_SCROLL_PAGE_BACKWARD);
379 
380 
381 	add_slider_binding (binding_set, GDK_KEY_KP_Add, (GdkModifierType)0,
382 	                    GTK_SCROLL_STEP_FORWARD);
383 
384 	add_slider_binding (binding_set, GDK_KEY_KP_Subtract, (GdkModifierType)0,
385 	                    GTK_SCROLL_STEP_BACKWARD);
386 
387 	add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
388 	                    GTK_SCROLL_PAGE_FORWARD);
389 
390 	add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
391 	                    GTK_SCROLL_PAGE_BACKWARD);
392 
393 
394 	add_slider_binding (binding_set, GDK_KEY_Home, (GdkModifierType)0,
395 	                    GTK_SCROLL_START);
396 
397 	add_slider_binding (binding_set, GDK_KEY_KP_Home, (GdkModifierType)0,
398 	                    GTK_SCROLL_START);
399 
400 	add_slider_binding (binding_set, GDK_KEY_End, (GdkModifierType)0,
401 	                    GTK_SCROLL_END);
402 
403 	add_slider_binding (binding_set, GDK_KEY_KP_End, (GdkModifierType)0,
404 	                    GTK_SCROLL_END);
405 }
406 
407 
gx_regler_destroy(GtkWidget * object)408 static void gx_regler_destroy(GtkWidget *object)
409 {
410 	GxRegler *regler = GX_REGLER(object);
411 	if (regler->priv->label) {
412 		g_object_unref(regler->priv->label);
413 		regler->priv->label = NULL;
414 	}
415 	gx_regler_change_adjustment(regler, NULL);
416 	g_signal_handlers_disconnect_by_func(
417 		regler, (gpointer)gx_regler_adjustment_notified, NULL);
418 	GTK_WIDGET_CLASS(gx_regler_parent_class)->destroy(object);
419 }
420 
gx_regler_finalize(GObject * object)421 static void gx_regler_finalize(GObject *object)
422 {
423 	GxRegler *regler = GX_REGLER(object);
424 	g_free(regler->priv->var_id);
425 	if (regler->priv->value_layout) {
426 		g_object_unref(regler->priv->value_layout);
427 	}
428 	G_OBJECT_CLASS(gx_regler_parent_class)->finalize(object);
429 }
430 
step_back(GtkRange * range)431 static void step_back(GtkRange *range)
432 {
433 	gdouble newval;
434 	gboolean handled;
435 
436 	GtkAdjustment *adjustment = gtk_range_get_adjustment(range);
437 	newval = gtk_adjustment_get_value(adjustment) - gtk_adjustment_get_step_increment(adjustment);
438 	g_signal_emit_by_name(range, "change-value", GTK_SCROLL_STEP_BACKWARD, newval, &handled);
439 }
440 
step_forward(GtkRange * range)441 static void step_forward(GtkRange *range)
442 {
443 	gdouble newval;
444 	gboolean handled;
445 
446 	GtkAdjustment *adjustment = gtk_range_get_adjustment(range);
447 	newval = gtk_adjustment_get_value(adjustment) + gtk_adjustment_get_step_increment(adjustment);
448 	g_signal_emit_by_name(range, "change-value", GTK_SCROLL_STEP_FORWARD, newval, &handled);
449 }
450 
451 
page_back(GtkRange * range)452 static void page_back(GtkRange *range)
453 {
454 	gdouble newval;
455 	gboolean handled;
456 
457 	GtkAdjustment *adjustment = gtk_range_get_adjustment(range);
458 	newval = gtk_adjustment_get_value(adjustment) - gtk_adjustment_get_page_increment(adjustment);
459 	g_signal_emit_by_name(range, "change-value", GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
460 }
461 
page_forward(GtkRange * range)462 static void page_forward(GtkRange *range)
463 {
464 	gdouble newval;
465 	gboolean handled;
466 
467 	GtkAdjustment *adjustment = gtk_range_get_adjustment(range);
468 	newval = gtk_adjustment_get_value(adjustment) + gtk_adjustment_get_page_increment(adjustment);
469 	g_signal_emit_by_name(range, "change-value", GTK_SCROLL_PAGE_FORWARD, newval, &handled);
470 }
471 
scroll_begin(GtkRange * range)472 static void scroll_begin(GtkRange *range)
473 {
474 	gboolean handled;
475 	g_signal_emit_by_name(range, "change-value", GTK_SCROLL_START,
476 	              gtk_adjustment_get_lower(gtk_range_get_adjustment(range)),
477 	              &handled);
478 }
479 
scroll_end(GtkRange * range)480 static void scroll_end(GtkRange *range)
481 {
482 	gdouble newval;
483 	gboolean handled;
484 
485 	GtkAdjustment *adjustment = gtk_range_get_adjustment(range);
486 	newval = gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment);
487 	g_signal_emit_by_name(range, "change-value", GTK_SCROLL_END, newval, &handled);
488 }
489 
should_invert(GtkRange * range)490 static gboolean should_invert(GtkRange *range)
491 {
492 	gboolean inverted = gtk_range_get_inverted(range);
493 	if (gtk_orientable_get_orientation(GTK_ORIENTABLE(range)) == GTK_ORIENTATION_HORIZONTAL) {
494 		gboolean flippable = gtk_range_get_flippable(range);
495 		return
496 			(inverted && !flippable) ||
497 			(inverted && flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
498 			(!inverted && flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
499 	} else {
500 		return inverted;
501 	}
502 }
503 
gx_regler_scroll(GtkRange * range,GtkScrollType scroll)504 static gboolean gx_regler_scroll(GtkRange *range, GtkScrollType scroll)
505 {
506 	gdouble old_value = gtk_range_get_value(range);
507 
508 	switch (scroll) {
509     case GTK_SCROLL_STEP_DOWN:
510 	case GTK_SCROLL_STEP_LEFT:
511       if (should_invert (range))
512         step_forward (range);
513       else
514         step_back (range);
515       break;
516 
517     case GTK_SCROLL_STEP_UP:
518     case GTK_SCROLL_STEP_RIGHT:
519       if (should_invert (range))
520         step_back (range);
521       else
522         step_forward (range);
523       break;
524 
525 
526     case GTK_SCROLL_STEP_BACKWARD:
527       step_back (range);
528       break;
529 
530     case GTK_SCROLL_STEP_FORWARD:
531       step_forward (range);
532       break;
533 
534     case GTK_SCROLL_PAGE_DOWN:
535     case GTK_SCROLL_PAGE_LEFT:
536       if (should_invert (range))
537         page_forward (range);
538       else
539         page_back (range);
540       break;
541 
542     case GTK_SCROLL_PAGE_UP:
543     case GTK_SCROLL_PAGE_RIGHT:
544       if (should_invert (range))
545         page_back (range);
546       else
547         page_forward (range);
548       break;
549 
550     case GTK_SCROLL_PAGE_BACKWARD:
551       page_back (range);
552       break;
553 
554     case GTK_SCROLL_PAGE_FORWARD:
555       page_forward (range);
556       break;
557 
558     case GTK_SCROLL_START:
559       scroll_begin (range);
560       break;
561 
562     case GTK_SCROLL_END:
563       scroll_end (range);
564       break;
565 
566     case GTK_SCROLL_JUMP:
567     case GTK_SCROLL_NONE:
568       break;
569     }
570 
571     return gtk_range_get_value(range) != old_value;
572 }
573 
gx_regler_move_slider(GtkRange * range,GtkScrollType scroll)574 static void gx_regler_move_slider(GtkRange *range, GtkScrollType scroll)
575 {
576 	gboolean cursor_only;
577 	g_object_get(gtk_widget_get_settings(GTK_WIDGET(range)),
578 	             "gtk-keynav-cursor-only", &cursor_only,
579 	             NULL);
580 	if (cursor_only) {
581 		GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(range));
582 		if (gtk_orientable_get_orientation(GTK_ORIENTABLE(range)) == GTK_ORIENTATION_HORIZONTAL) {
583 			if (scroll == GTK_SCROLL_STEP_UP || scroll == GTK_SCROLL_STEP_DOWN) {
584 				if (toplevel) {
585 					gtk_widget_child_focus(toplevel,
586 					                       scroll == GTK_SCROLL_STEP_UP ?
587 					                       GTK_DIR_UP : GTK_DIR_DOWN);
588 				}
589 				return;
590 			}
591 		} else {
592 			if (scroll == GTK_SCROLL_STEP_LEFT || scroll == GTK_SCROLL_STEP_RIGHT) {
593 				if (toplevel) {
594 					gtk_widget_child_focus(toplevel,
595 					                       scroll == GTK_SCROLL_STEP_LEFT ?
596 					                       GTK_DIR_LEFT : GTK_DIR_RIGHT);
597 				}
598 				return;
599 			}
600 		}
601 	}
602 	if (!gx_regler_scroll (range, scroll)) {
603 		gtk_widget_error_bell(GTK_WIDGET(range));
604 	}
605 }
606 
ensure_digits(GxRegler * regler)607 static void ensure_digits(GxRegler *regler)
608 {
609 	GxReglerPrivate *priv = regler->priv;
610 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(regler));
611 	if (!adj) {
612 		return;
613 	}
614 	gdouble v = gtk_adjustment_get_step_increment(adj);
615 	if (v == priv->last_step) {
616 		return;
617 	}
618     priv->last_step = v;
619 	gint n = 0;
620 	if (v > 0.0) {
621         while (v < 1.0 - 1e-3) {
622             v *= 10;
623             n++;
624         }
625     }
626     priv->round_digits = n;
627 }
628 
gx_regler_change_value(GtkRange * range,GtkScrollType scroll,gdouble value)629 static gboolean gx_regler_change_value(GtkRange *range, GtkScrollType scroll, gdouble value)
630 {
631 	g_assert(GX_IS_REGLER(range));
632 	return GTK_RANGE_CLASS(gx_regler_parent_class)->change_value(range, scroll, value);
633 }
634 
635 /****************************************************************
636  ** calculate the knop pointer with dead zone
637  */
638 
639 /*
640 ** when this function gets inlined (e.g. -O3) in GxKnob.c:_gx_knob_pointer_event
641 ** bad things will happen, at least with g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3 (funny
642 ** lockup in malloc semaphore after returning from first signal handler...)
643 ** I'm don't know how to generate assembler output commented with original C code,
644 ** without its rather unreadable, and anyhow I don't really want to debug the
645 ** compiler. Inlining for this function can also be prevented with
646 **  __attribute__((noinline)) for the function definition in the header file. /ad
647 */
_approx_in_rectangle(gdouble x,gdouble y,GdkRectangle * rect)648 gboolean _approx_in_rectangle(gdouble x, gdouble y, GdkRectangle *rect)
649 {
650 	const int off = 5;
651 	if (rect->width == 0 || rect->height == 0) {
652 		return FALSE;
653 	}
654 	else if (x >= rect->x-off && x < rect->x + rect->width + off &&
655 	    y >= rect->y-off && y < rect->y + rect->height + off) {
656 		return TRUE;
657 	}
658 	return FALSE;
659 }
660 
gx_regler_ensure_layout(GxRegler * regler)661 static void gx_regler_ensure_layout(GxRegler *regler)
662 {
663 	if (regler->priv->show_value && !regler->priv->value_layout) {
664 		regler->priv->value_layout = gtk_widget_create_pango_layout(GTK_WIDGET(regler), NULL);
665 	}
666 }
667 
668 #ifndef min
669 #define min(x, y) ((x) < (y) ? (x) : (y))
670 #endif
671 #ifndef max
672 #define max(x, y) ((x) < (y) ? (y) : (x))
673 #endif
674 
_gx_regler_get_step_pos(GxRegler * regler,gint step)675 gdouble _gx_regler_get_step_pos(GxRegler *regler, gint step)
676 {
677 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(regler));
678 	gdouble lower = gtk_adjustment_get_lower(adj);
679 	double df = gtk_adjustment_get_upper(adj) - lower;
680 	if (df == 0.0) {
681 		return 0.0;
682 	} else {
683 		return (gtk_adjustment_get_value(adj) - lower) * step / df;
684 	}
685 }
686 
_gx_regler_get_positions(GxRegler * regler,GdkRectangle * image_rect,GdkRectangle * value_rect,gboolean hfill)687 void _gx_regler_get_positions(GxRegler *regler, GdkRectangle *image_rect,
688                               GdkRectangle *value_rect, gboolean hfill)
689 {
690 	GtkWidget *widget = GTK_WIDGET(regler);
691 	GtkStyleContext *style = gtk_widget_get_style_context(widget);
692 	GtkAllocation allocation;
693 	gtk_widget_get_allocation(widget, &allocation);
694 	gint x = 0;
695 	gint y = 0;
696 	gint width = 0;
697 	gint height =  0;
698 	if (image_rect) {
699 	    width = image_rect->width;
700 	    height = image_rect->height;
701 	}
702 	gboolean show_value;
703 	gtk_style_context_get_style(style, "show-value", &show_value, NULL);
704 	if (regler->priv->show_value && show_value) {
705 		GxReglerPrivate *priv = regler->priv;
706 		gint text_width = priv->value_req.width;
707 		gint text_height = priv->value_req.height;
708 		gint text_x = 0, text_y = 0;
709 		gint value_spacing;
710 		gtk_style_context_get_style(style, "value-spacing", &value_spacing, NULL);
711 		switch (priv->value_position) {
712 		case GTK_POS_LEFT:
713 			text_x = x + (allocation.width - width - text_width - value_spacing) / 2;
714 			text_y = y + (allocation.height - text_height) / 2;
715 			if (image_rect) {
716 			    image_rect->x = x + (allocation.width - width + text_width + value_spacing) / 2;
717 			    image_rect->y = y + (allocation.height - height) / 2;
718 			}
719 			break;
720 		case GTK_POS_RIGHT:
721 			text_x = x + value_spacing + (allocation.width + width - text_width - value_spacing) / 2;
722 			text_y = y + (allocation.height - text_height) / 2;
723 			if (image_rect) {
724 			    image_rect->x = x + (allocation.width - width - text_width - value_spacing) / 2;
725 			    image_rect->y = y + (allocation.height - height) / 2;
726 			}
727 			break;
728 		case GTK_POS_TOP:
729 			text_x = x + (allocation.width - text_width) / 2;
730 			text_y = y + (allocation.height - height - text_height - value_spacing) / 2;
731 			if (image_rect) {
732 			    image_rect->x = x + (allocation.width - width) / 2;
733 			    image_rect->y = y + (allocation.height - height + text_height + value_spacing) / 2;
734 			}
735 			break;
736 		case GTK_POS_BOTTOM:
737 			text_x = x + (allocation.width - text_width) / 2;
738 			text_y = y + value_spacing + (allocation.height + height - text_height - value_spacing) / 2;
739 			if (image_rect) {
740 			    image_rect->x = x + (allocation.width - width) / 2;
741 			    image_rect->y = y + (allocation.height - height - text_height - value_spacing) / 2;
742 			}
743 			break;
744 		}
745 		if (value_rect) {
746 		    if (hfill) {
747 			value_rect->x = 0;
748 			value_rect->width = allocation.width;
749 		    } else {
750 			value_rect->x = text_x;
751 			value_rect->width = text_width;
752 		    }
753 		    value_rect->y = text_y;
754 		    value_rect->height = text_height;
755 		}
756 	} else {
757 	    if (image_rect) {
758 		image_rect->x = x + (allocation.width - width) / 2;
759 		image_rect->y = y + (allocation.height - height) / 2;
760 	    }
761 		if (value_rect) {
762 			value_rect->x = value_rect->y = value_rect->width = value_rect->height = 0;
763 		}
764 	}
765 }
766 
_gx_regler_check_display_popup(GxRegler * regler,GdkRectangle * image_rect,GdkRectangle * value_rect,GdkEventButton * event)767 gboolean _gx_regler_check_display_popup(GxRegler *regler, GdkRectangle *image_rect,
768 					GdkRectangle *value_rect, GdkEventButton *event)
769 {
770 	// check if value entry popup requested
771 	gdouble x = event->x;
772 	gdouble y = event->y;
773 	GdkRectangle *rect = NULL;
774 	if (image_rect && _approx_in_rectangle(x, y, image_rect)) {
775 		if (event->button == 3) {
776 			rect = image_rect;
777 		}
778 	} else if (value_rect && _approx_in_rectangle(x, y, value_rect)) {
779 		if (event->button == 1 || event->button == 3) {
780 			rect = value_rect;
781 		} else {
782 			return TRUE;
783 		}
784 	} else {
785 		return TRUE;
786 	}
787 	if (rect) {
788 		gboolean ret;
789 		g_signal_emit_by_name(regler, "value-entry", rect, event, &ret);
790 		return TRUE;
791 	}
792 	return FALSE;
793 }
794 
_gx_regler_format_value(GxRegler * regler,gdouble value)795 static gchar* _gx_regler_format_value(GxRegler *regler, gdouble value)
796 {
797 	gchar *fmt = NULL;
798 	g_signal_emit(regler, signals[FORMAT_VALUE], 0, value, &fmt);
799 	if (fmt) {
800 		return fmt;
801 	} else {
802 		int rd = regler->priv->round_digits;
803 		if (rd < 0) {
804 			rd = 0;
805 		}
806 		return g_strdup_printf ("%0.*f", rd, value);
807 	}
808 }
809 
810 
811 static const GtkBorder default_value_border = { 6, 6, 3, 3 };
812 
get_value_border(GtkStyleContext * style,GtkBorder * value_border)813 static void get_value_border(GtkStyleContext *style, GtkBorder *value_border)
814 {
815     GtkBorder *tmp_border;
816     gtk_style_context_get_style(style, "value-border", &tmp_border, NULL);
817     if (tmp_border) {
818 	*value_border = *tmp_border;
819 	gtk_border_free(tmp_border);
820     } else {
821 	*value_border = default_value_border;
822     }
823 }
824 
_gx_regler_simple_display_value(GxRegler * regler,cairo_t * cr,GdkRectangle * rect)825 void _gx_regler_simple_display_value(GxRegler *regler, cairo_t *cr, GdkRectangle *rect)
826 {
827 	if (!regler->priv->show_value) {
828 		return;
829 	}
830 	GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(regler));
831 	gboolean show_value;
832 	gtk_style_context_get_style(style, "show-value", &show_value, NULL);
833 	if (!show_value) {
834 		return;
835 	}
836     PangoLayout *l = regler->priv->value_layout;
837     PangoRectangle logical_rect;
838 	gchar *txt;
839 	ensure_digits(regler);
840 	txt = _gx_regler_format_value(regler, gtk_range_get_value(GTK_RANGE(regler)));
841     pango_layout_set_text(l, txt, -1);
842     g_free (txt);
843     pango_layout_get_pixel_extents(l, NULL, &logical_rect);
844     GtkBorder border;
845     get_value_border(style, &border);
846     gtk_render_layout(style, cr,
847                       rect->x + (rect->width - logical_rect.width + border.left - border.right) / 2,
848                       rect->y + border.top, regler->priv->value_layout);
849 }
850 
851 #define DISPLAY_BORDER_SIZE 2
852 
_gx_regler_display_value(GxRegler * regler,cairo_t * cr,GdkRectangle * rect)853 void _gx_regler_display_value(GxRegler *regler, cairo_t *cr, GdkRectangle *rect)
854 {
855 	if (!regler->priv->show_value) {
856 		return;
857 	}
858 	gboolean show_value;
859     GtkWidget * widget = GTK_WIDGET(regler);
860 	GtkStyleContext *style = gtk_widget_get_style_context(widget);
861 	GtkStateFlags state_flags = gtk_widget_get_state_flags(widget);
862 	gtk_style_context_get_style(style, "show-value", &show_value, NULL);
863 	if (!show_value) {
864 		return;
865 	}
866 	double x0 = rect->x + DISPLAY_BORDER_SIZE;
867 	double y0 = rect->y + DISPLAY_BORDER_SIZE;
868 	double w  = rect->width - 2 * DISPLAY_BORDER_SIZE;
869 	double h  = rect->height - 2 * DISPLAY_BORDER_SIZE;
870     gint rad;
871     GtkStyleContext *sc = gtk_widget_get_style_context(widget);
872     gtk_widget_style_get(widget, "border-radius", &rad, NULL);
873 
874     gx_draw_inset(cr, x0, y0, w, h, rad, DISPLAY_BORDER_SIZE);
875     gtk_render_background(sc, cr, x0, y0, w, h);
876     gx_draw_glass(cr, x0, y0, w, h, rad);
877 
878 	gchar *txt;
879 	ensure_digits(regler);
880 	txt = _gx_regler_format_value(regler, gtk_range_get_value(GTK_RANGE(regler)));
881     GdkRGBA color;
882     gtk_style_context_get_color(style, state_flags, &color);
883     cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
884 
885     PangoLayout *l = regler->priv->value_layout;
886     pango_layout_set_text(l, txt, -1);
887     g_free(txt);
888     PangoRectangle logical_rect;
889     pango_layout_get_pixel_extents(l, NULL, &logical_rect);
890     GtkBorder border;
891     get_value_border(style, &border);
892     gdouble off = border.left + regler->priv->value_xalign *
893 		(w - logical_rect.width - (border.left + border.right));
894     cairo_move_to(cr, x0+off, y0+border.top);
895     pango_cairo_show_layout(cr, l);
896 }
897 
898 /*
899  * _gx_regler_calc_size_request()
900  * calculate size of value display and add it to *out_width and *out_height
901  *
902  * setting of with_border when using
903  *   _gx_regler_simple_display_value: false
904  *   _gx_regler_display_value():      true
905  *
906  * value-spacing shifts the displayed value (padding between regler and value display)
907  * to make acts as padding around the displayed value between border
908  *
909  * value-border is the padding around the displayed value (and inside the border when with_border is set)
910  */
_gx_regler_calc_size_request(GxRegler * regler,gint * out_width,gint * out_height,gboolean with_border)911 void _gx_regler_calc_size_request(GxRegler *regler, gint *out_width, gint *out_height, gboolean with_border)
912 {
913 	if (!regler->priv->show_value) {
914 		return;
915 	}
916 	GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(regler));
917 	gboolean show_value;
918 	gtk_style_context_get_style(style, "show-value", &show_value, NULL);
919 	if (!show_value) {
920 		return;
921 	}
922 	gx_regler_ensure_layout(regler);
923 	PangoRectangle logical_rect1, logical_rect2;
924 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(regler));
925 	gchar *txt;
926 	ensure_digits(regler);
927 	txt = _gx_regler_format_value(regler, gtk_adjustment_get_lower(adj));
928 	pango_layout_set_text(regler->priv->value_layout, txt, -1);
929 	g_free(txt);
930 	pango_layout_get_pixel_extents(regler->priv->value_layout, NULL, &logical_rect1);
931 	txt = _gx_regler_format_value(regler, gtk_adjustment_get_upper(adj));
932 	pango_layout_set_text(regler->priv->value_layout, txt, -1);
933 	g_free(txt);
934 	pango_layout_get_pixel_extents(regler->priv->value_layout, NULL, &logical_rect2);
935 	GtkBorder border;
936 	get_value_border(style, &border);
937 	gint height = max(logical_rect1.height,logical_rect2.height)
938 	    + border.top + border.bottom + 2 * DISPLAY_BORDER_SIZE;
939 	gint width = max(logical_rect1.width,logical_rect2.width)
940 	    + border.left + border.right + 2 * DISPLAY_BORDER_SIZE;
941 	GxReglerPrivate *priv = regler->priv;
942 	priv->value_req.width = width;
943 	priv->value_req.height = height;
944 	gint value_spacing;
945 	gtk_style_context_get_style(style, "value-spacing", &value_spacing, NULL);
946 	switch (regler->priv->value_position) {
947 	case GTK_POS_LEFT:
948 	case GTK_POS_RIGHT:
949 		*out_width += width + value_spacing;
950 		if (height > *out_height) {
951 			*out_height = height;
952 		}
953 		break;
954 	case GTK_POS_TOP:
955 	case GTK_POS_BOTTOM:
956 		*out_height += height + value_spacing;
957 		if (width > *out_width) {
958 			*out_width = width;
959 		}
960 		break;
961 	}
962 }
963 
964 /****************************************************************
965  ** set value from key bindings
966  */
967 
gx_regler_set_value(GtkWidget * widget,GdkScrollDirection dir_down)968 static void gx_regler_set_value (GtkWidget *widget, GdkScrollDirection dir_down)
969 {
970 	g_assert(GX_IS_REGLER(widget));
971 
972 	if (dir_down != GDK_SCROLL_DOWN && dir_down != GDK_SCROLL_UP) {
973 		return;
974 	}
975 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
976 	gdouble adj_value = gtk_adjustment_get_value(adj);
977 	gdouble lower = gtk_adjustment_get_lower(adj);
978 	gdouble upper = gtk_adjustment_get_upper(adj);
979 	gdouble step_increment = gtk_adjustment_get_step_increment(adj);
980 
981 	int oldstep = (int)(0.5f + (adj_value - lower) / step_increment);
982 	int step;
983 	int nsteps = (int)(0.5f + (upper - lower) / step_increment);
984 	if (dir_down == GDK_SCROLL_DOWN)
985 		step = oldstep - 1;
986 	else
987 		step = oldstep + 1;
988 	gdouble value = lower + step * (upper - lower) / nsteps;
989 	gtk_widget_grab_focus(widget);
990 	gtk_range_set_value(GTK_RANGE(widget), value);
991 }
992 
993 /****************************************************************
994  ** mouse button pressed set value
995  */
996 
gx_regler_relay_value(GtkAdjustment * src,GtkAdjustment * dst)997 static void gx_regler_relay_value(GtkAdjustment *src, GtkAdjustment *dst) {
998 	gtk_adjustment_set_value(dst, gtk_adjustment_get_value(src));
999 }
1000 
gx_regler_spinner_input(GtkSpinButton * spin_button,gdouble * new_val,GxRegler * regler)1001 static gint gx_regler_spinner_input(GtkSpinButton *spin_button, gdouble *new_val, GxRegler *regler)
1002 {
1003 	gint ret;
1004 	g_signal_emit(regler, signals[INPUT_VALUE], 0, spin_button, new_val, &ret);
1005 	return ret;
1006 }
1007 
gx_regler_spinner_output(GtkSpinButton * spinner,GxRegler * regler)1008 static gboolean gx_regler_spinner_output(GtkSpinButton *spinner, GxRegler *regler) {
1009 	gchar *fmt;
1010 	gdouble value = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(spinner));
1011 	fmt = _gx_regler_format_value(regler, value);
1012 	gtk_entry_set_text (GTK_ENTRY(spinner), fmt);
1013 	g_free(fmt);
1014     return TRUE;
1015 }
1016 
gx_regler_get_nchars(GxRegler * regler,gdouble value)1017 static int gx_regler_get_nchars(GxRegler *regler, gdouble value)
1018 {
1019     gchar *txt = _gx_regler_format_value(regler, value);
1020     int nchars = strlen(txt);
1021     g_free(txt);
1022     return nchars;
1023 }
1024 
gx_regler_value_entry(GxRegler * regler,GdkRectangle * rect,GdkEventButton * event)1025 static gboolean gx_regler_value_entry(GxRegler *regler, GdkRectangle *rect, GdkEventButton *event)
1026 {
1027 	if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) {
1028 		return FALSE;
1029 	}
1030 	g_assert(GX_IS_REGLER(regler));
1031 	GtkAdjustment *dst_adj = gtk_range_get_adjustment(GTK_RANGE(regler));
1032 	GtkWidget *popover = gtk_popover_new(GTK_WIDGET(regler));
1033 	gtk_popover_set_pointing_to(GTK_POPOVER(popover), rect);
1034 	ensure_digits(regler);
1035     gdouble lower = gtk_adjustment_get_lower(dst_adj);
1036     gdouble upper = gtk_adjustment_get_upper(dst_adj);
1037 	GtkAdjustment *src_adj = GTK_ADJUSTMENT(
1038 		gtk_adjustment_new(
1039 			gtk_adjustment_get_value(dst_adj), lower, upper,
1040 			gtk_adjustment_get_step_increment(dst_adj),
1041 			gtk_adjustment_get_page_increment(dst_adj),
1042 			gtk_adjustment_get_page_size(dst_adj)));
1043 	GtkWidget *spinner = gtk_spin_button_new(
1044 		src_adj, gtk_adjustment_get_step_increment(src_adj),
1045         regler->priv->round_digits);
1046     gtk_entry_set_width_chars(GTK_ENTRY(spinner), max(gx_regler_get_nchars(regler, lower),
1047                                                       gx_regler_get_nchars(regler, upper)));
1048 	g_signal_connect(spinner, "output", G_CALLBACK(gx_regler_spinner_output), regler);
1049 	g_signal_connect(spinner, "input", G_CALLBACK(gx_regler_spinner_input), regler);
1050 	g_signal_connect(src_adj, "value-changed", G_CALLBACK(gx_regler_relay_value), dst_adj);
1051 	gtk_container_add(GTK_CONTAINER(popover), spinner);
1052 
1053 	g_signal_connect_object(spinner, "activate", G_CALLBACK(gtk_widget_destroy),
1054 				popover, (GConnectFlags)(G_CONNECT_AFTER|G_CONNECT_SWAPPED));
1055     g_signal_connect(popover, "hide", G_CALLBACK(gtk_widget_destroy), popover);
1056 
1057 	gtk_widget_show(spinner);
1058 
1059 	gtk_popover_popup(GTK_POPOVER(popover));
1060 	return FALSE;
1061 }
1062 
1063 /****************************************************************
1064  ** mouse button release
1065  */
1066 
gx_regler_button_release(GtkWidget * widget,GdkEventButton * event)1067 static gboolean gx_regler_button_release (GtkWidget *widget, GdkEventButton *event)
1068 {
1069 	g_assert(GX_IS_REGLER(widget));
1070 	if (gtk_widget_has_grab(widget))
1071 		gtk_grab_remove(widget);
1072 	return FALSE;
1073 }
1074 
1075 /****************************************************************
1076  ** set value from mouseweel
1077  */
1078 
gx_regler_scroll(GtkWidget * widget,GdkEventScroll * event)1079 static gboolean gx_regler_scroll (GtkWidget *widget, GdkEventScroll *event)
1080 {
1081 	usleep(5000);
1082 
1083 	GdkScrollDirection direction;
1084 	gdouble delta_x, delta_y;
1085 	if (event->direction != GDK_SCROLL_SMOOTH) {
1086 		gx_regler_set_value(widget, event->direction);
1087 	} else if (gdk_event_get_scroll_direction((GdkEvent*)event, &direction)) {
1088 		gx_regler_set_value(widget, direction);
1089 	} else if (gdk_event_get_scroll_deltas((GdkEvent*)event, &delta_x, &delta_y)) {
1090 		GdkScrollDirection vdir = delta_y > 0.0 ? GDK_SCROLL_UP : GDK_SCROLL_DOWN;
1091 		if (abs(delta_y) > abs(delta_x)) {
1092 			gx_regler_set_value(widget, vdir);
1093 		}
1094 	}
1095 
1096 	return TRUE;
1097 }
1098 
1099 /****************************************************************
1100  ** init the Regler type/size
1101  */
1102 
gx_regler_adjustment_changed(GtkAdjustment * adjustment,gpointer data)1103 static void gx_regler_adjustment_changed(GtkAdjustment *adjustment, gpointer data)
1104 {
1105 	gtk_widget_queue_resize(GTK_WIDGET(data));
1106 }
1107 
gx_regler_change_adjustment(GxRegler * regler,GtkAdjustment * adjustment)1108 static void gx_regler_change_adjustment(GxRegler *regler, GtkAdjustment *adjustment)
1109 {
1110 	GxReglerPrivate *priv = regler->priv;
1111 	if (adjustment == priv->adjustment) {
1112 		return;
1113 	}
1114 	if (priv->adjustment) {
1115       g_signal_handlers_disconnect_by_func(
1116 	      priv->adjustment, (gpointer)gx_regler_adjustment_changed, regler);
1117       g_object_unref(priv->adjustment);
1118       priv->adjustment = NULL;
1119 	}
1120 	if (!adjustment) {
1121 		return;
1122 	}
1123 	priv->adjustment = adjustment;
1124 	g_object_ref_sink(adjustment);
1125 	g_signal_connect(adjustment, "changed", G_CALLBACK(gx_regler_adjustment_changed), regler);
1126 	gtk_widget_queue_resize(GTK_WIDGET(regler));
1127 }
1128 
gx_regler_adjustment_notified(GObject * gobject,GParamSpec * pspec)1129 static void gx_regler_adjustment_notified(GObject *gobject, GParamSpec *pspec)
1130 {
1131 	GxRegler *regler = GX_REGLER(gobject);
1132 	gx_regler_change_adjustment(regler, gtk_range_get_adjustment(&regler->parent));
1133 }
1134 
gx_regler_init(GxRegler * regler)1135 static void gx_regler_init(GxRegler *regler)
1136 {
1137 	regler->priv = (GxReglerPrivate*)gx_regler_get_instance_private(regler);
1138     regler->priv->last_step = -1;
1139     regler->priv->round_digits = 0;
1140 	regler->priv->value_position = GTK_POS_BOTTOM;
1141 	regler->priv->show_value = TRUE;
1142 	regler->priv->value_xalign = 0.5;
1143 	gtk_widget_set_can_focus(GTK_WIDGET(regler), TRUE);
1144 	gtk_widget_set_receives_default(GTK_WIDGET(regler), TRUE);
1145 	gtk_widget_set_has_window(GTK_WIDGET(regler), FALSE);
1146 	g_signal_connect(regler, "notify::adjustment", G_CALLBACK(gx_regler_adjustment_notified), NULL);
1147 }
1148 
1149 /****************************************************************
1150  ** Properties
1151  */
1152 
gx_regler_set_var_id(GxRegler * regler,const gchar * str)1153 static void gx_regler_set_var_id(GxRegler *regler, const gchar *str)
1154 {
1155 	g_free(regler->priv->var_id);
1156 	regler->priv->var_id = g_strdup(str ? str : "");
1157 	g_object_notify(G_OBJECT(regler), "var-id");
1158 }
1159 
gx_regler_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1160 static void gx_regler_set_property (
1161 	GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
1162 {
1163 	GxRegler *regler;
1164 
1165 	regler = GX_REGLER (object);
1166 
1167 	switch(prop_id) {
1168 	case PROP_VAR_ID:
1169 		gx_regler_set_var_id (regler, g_value_get_string (value));
1170 		break;
1171 	case PROP_SHOW_VALUE:
1172 		gx_regler_set_show_value(regler, g_value_get_boolean(value));
1173 		break;
1174 	case PROP_VALUE_POSITION:
1175 		gx_regler_set_value_position(regler, GtkPositionType(g_value_get_enum(value)));
1176 		break;
1177 	case PROP_VALUE_XALIGN:
1178 		regler->priv->value_xalign = g_value_get_double(value);
1179 		gtk_widget_queue_draw(GTK_WIDGET(object));
1180 		g_object_notify(object, "value-xalign");
1181 		break;
1182 	case PROP_LABEL_REF:
1183 		gx_regler_set_label_ref(regler, GTK_LABEL(g_value_get_object(value)));
1184 		break;
1185 	default:
1186 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1187 		break;
1188 	}
1189 }
1190 
gx_regler_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1191 static void gx_regler_get_property(
1192 	GObject *object, guint prop_id, GValue *value, GParamSpec*pspec)
1193 {
1194 	GxRegler *regler;
1195 
1196 	regler = GX_REGLER(object);
1197 
1198 	switch(prop_id) {
1199 	case PROP_VAR_ID:
1200 		g_value_set_string (value, regler->priv->var_id);
1201 		break;
1202 	case PROP_SHOW_VALUE:
1203 		g_value_set_boolean(value, regler->priv->show_value);
1204 		break;
1205 	case PROP_VALUE_POSITION:
1206 		g_value_set_enum(value, regler->priv->value_position);
1207 		break;
1208 	case PROP_VALUE_XALIGN:
1209 		g_value_set_double(value, regler->priv->value_xalign);
1210 		break;
1211 	case PROP_LABEL_REF:
1212 		g_value_set_object(value, regler->priv->label);
1213 		break;
1214 	case PROP_DIGITS:
1215 		g_value_set_int(value, regler->priv->round_digits);
1216 		break;
1217 	default:
1218 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1219 		break;
1220 	}
1221 }
1222 
1223 /*
1224 ** getter / setter
1225 */
1226 
gx_regler_set_show_value(GxRegler * regler,gboolean value)1227 void gx_regler_set_show_value(GxRegler *regler, gboolean value)
1228 {
1229 	g_return_if_fail(GX_IS_REGLER(regler));
1230 	regler->priv->show_value = value;
1231 	gtk_widget_queue_resize(GTK_WIDGET(regler));
1232 	g_object_notify(G_OBJECT(regler), "show-value");
1233 }
1234 
gx_regler_get_show_value(GxRegler * regler)1235 gboolean gx_regler_get_show_value(GxRegler *regler)
1236 {
1237 	g_return_val_if_fail(GX_IS_REGLER(regler), 0);
1238 	return regler->priv->show_value;
1239 }
1240 
gx_regler_set_value_position(GxRegler * regler,GtkPositionType value)1241 void gx_regler_set_value_position(GxRegler *regler, GtkPositionType value)
1242 {
1243 	g_return_if_fail(GX_IS_REGLER(regler));
1244 	regler->priv->value_position = value;
1245 	gtk_widget_queue_resize(GTK_WIDGET(regler));
1246 	g_object_notify(G_OBJECT(regler), "value-position");
1247 }
1248 
gx_regler_get_value_position(GxRegler * regler)1249 GtkPositionType gx_regler_get_value_position(GxRegler *regler)
1250 {
1251 	g_return_val_if_fail(GX_IS_REGLER(regler), GTK_POS_BOTTOM);
1252 	return regler->priv->value_position;
1253 }
1254 
gx_regler_set_label_ref(GxRegler * regler,GtkLabel * label)1255 void gx_regler_set_label_ref(GxRegler *regler, GtkLabel *label)
1256 {
1257 	g_return_if_fail(GX_IS_REGLER(regler));
1258 	if (regler->priv->label) {
1259 		g_object_unref(regler->priv->label);
1260 		regler->priv->label = NULL;
1261 	}
1262 	if (label) {
1263 		g_return_if_fail(GTK_IS_LABEL(label));
1264 		regler->priv->label = label;
1265 		g_object_ref(label);
1266 	}
1267 	g_object_notify(G_OBJECT(regler), "label-ref");
1268 }
1269 
1270 
gx_regler_get_label_ref(GxRegler * regler)1271 GtkLabel *gx_regler_get_label_ref(GxRegler *regler)
1272 {
1273 	g_return_val_if_fail(GX_IS_REGLER(regler), 0);
1274 	return regler->priv->label;
1275 }
1276