1 /* a slider with an entry widget
2 */
3
4 /*
5
6 Copyright (C) 1991-2003 The National Gallery
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22 */
23
24 /*
25
26 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
27
28 */
29
30 /*
31 #define DEBUG
32 */
33
34 #include "ip.h"
35
36 /* Our signals.
37 */
38 enum {
39 CHANGED,
40 ACTIVATE,
41 SLIDER_CHANGED,
42 TEXT_CHANGED,
43 LAST_SIGNAL
44 };
45
46 static GtkHBoxClass *parent_class = NULL;
47
48 static guint tslider_signals[LAST_SIGNAL] = { 0 };
49
50 /* Are two doubles more or less equal. We need this when we check the sliders
51 * for update to stop loops. The 0.0001 is a bit of a fudge :-(
52 */
53 #define DEQ( A, B ) (ABS((A) - (B)) < 0.0001)
54
55 static void
tslider_destroy(GtkObject * object)56 tslider_destroy( GtkObject *object )
57 {
58 Tslider *tslider;
59
60 g_return_if_fail( object != NULL );
61 g_return_if_fail( IS_TSLIDER( object ) );
62
63 tslider = TSLIDER( object );
64
65 #ifdef DEBUG
66 printf( "tslider_destroy: %p\n", tslider );
67 #endif /*DEBUG*/
68
69 /* My instance destroy stuff.
70 */
71 if( tslider->adj ) {
72 gtk_signal_disconnect_by_data( GTK_OBJECT( tslider->adj ),
73 (gpointer) tslider );
74 tslider->adj = NULL;
75 }
76
77 GTK_OBJECT_CLASS( parent_class )->destroy( object );
78 }
79
80 /* Map a value to a slider position.
81 */
82 static double
tslider_value_to_slider(Tslider * tslider,double value)83 tslider_value_to_slider( Tslider *tslider, double value )
84 {
85 /* Map our range to 0-1.
86 */
87 const double scale = 1.0 / (tslider->to - tslider->from);
88 const double to01 = (value - tslider->from) * scale;
89
90 /* Pass through user fn.
91 */
92 const double mapped = tslider->value_to_slider(
93 tslider->from, tslider->to, to01 );
94 const double nvalue = mapped / scale + tslider->from;
95
96 #ifdef DEBUG
97 printf( "tslider_value_to_slider: %g, to %g\n", value, nvalue );
98 #endif /*DEBUG*/
99
100 /* Map back to main range.
101 */
102 return( nvalue );
103 }
104
105 /* Map a slider position to a value.
106 */
107 static double
tslider_slider_to_value(Tslider * tslider,double value)108 tslider_slider_to_value( Tslider *tslider, double value )
109 {
110 /* Map our range to 0-1.
111 */
112 const double scale = 1.0 / (tslider->to - tslider->from);
113 const double to01 = (value - tslider->from) * scale;
114
115 /* Pass through user fn.
116 */
117 const double mapped = tslider->slider_to_value(
118 tslider->from, tslider->to, to01 );
119 const double nvalue = mapped / scale + tslider->from;
120
121 #ifdef DEBUG
122 printf( "tslider_slider_to_value: %g, to %g\n", value, nvalue );
123 #endif /*DEBUG*/
124
125 /* Map back to main range.
126 */
127 return( nvalue );
128 }
129
130 /* from/to/value have changed ... update the widgets.
131 */
132 static void
tslider_real_changed(Tslider * tslider)133 tslider_real_changed( Tslider *tslider )
134 {
135 GtkAdjustment *adj = tslider->adj;
136 GtkWidget *entry = tslider->entry;
137
138 #ifdef DEBUG
139 printf( "tslider_real_changed: %p, val = %g\n",
140 tslider, tslider->value );
141 #endif /*DEBUG*/
142
143 if( tslider->auto_link )
144 tslider->svalue = tslider_value_to_slider( tslider,
145 tslider->value );
146
147 gtk_signal_handler_block_by_data( GTK_OBJECT( adj ), tslider );
148 gtk_signal_handler_block_by_data( GTK_OBJECT( entry ), tslider );
149
150 /* Some libc's hate out-of-bounds precision, so clip, just in case.
151 */
152 set_gentry( tslider->entry, "%.*f",
153 IM_CLIP( 0, tslider->digits, 100 ), tslider->value );
154 gtk_scale_set_digits( GTK_SCALE( tslider->slider ), tslider->digits );
155
156 if( !DEQ( tslider->from, tslider->last_from ) ||
157 !DEQ( tslider->to, tslider->last_to ) ) {
158 double range = tslider->to - tslider->from;
159
160 adj->step_increment = range / 100;
161 adj->page_increment = range / 10;
162 adj->page_size = range / 10;
163
164 adj->lower = tslider->from;
165 adj->upper = tslider->to + adj->page_size;
166
167 tslider->last_to = tslider->to;
168 tslider->last_from = tslider->from;
169
170 gtk_adjustment_changed( adj );
171 }
172
173 if( !DEQ( tslider->svalue, tslider->last_svalue ) ) {
174 adj->value = tslider->svalue;
175 tslider->last_svalue = tslider->svalue;
176
177 gtk_adjustment_value_changed( adj );
178 }
179
180 gtk_signal_handler_unblock_by_data( GTK_OBJECT( adj ), tslider );
181 gtk_signal_handler_unblock_by_data( GTK_OBJECT( entry ), tslider );
182 }
183
184 static void
tslider_class_init(TsliderClass * class)185 tslider_class_init( TsliderClass *class )
186 {
187 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
188 GtkObjectClass *object_class = (GtkObjectClass *) class;
189
190 parent_class = g_type_class_peek_parent( class );
191
192 object_class->destroy = tslider_destroy;
193
194 class->changed = tslider_real_changed;
195 class->slider_changed = NULL;
196 class->activate = NULL;
197
198 /* Create signals.
199 */
200 tslider_signals[CHANGED] = g_signal_new( "changed",
201 G_OBJECT_CLASS_TYPE( gobject_class ),
202 G_SIGNAL_RUN_FIRST,
203 G_STRUCT_OFFSET( TsliderClass, changed ),
204 NULL, NULL,
205 g_cclosure_marshal_VOID__VOID,
206 G_TYPE_NONE, 0 );
207 tslider_signals[ACTIVATE] = g_signal_new( "activate",
208 G_OBJECT_CLASS_TYPE( gobject_class ),
209 G_SIGNAL_RUN_FIRST,
210 G_STRUCT_OFFSET( TsliderClass, activate ),
211 NULL, NULL,
212 g_cclosure_marshal_VOID__VOID,
213 G_TYPE_NONE, 0 );
214 tslider_signals[SLIDER_CHANGED] = g_signal_new( "slider_changed",
215 G_OBJECT_CLASS_TYPE( gobject_class ),
216 G_SIGNAL_RUN_FIRST,
217 G_STRUCT_OFFSET( TsliderClass, slider_changed ),
218 NULL, NULL,
219 g_cclosure_marshal_VOID__VOID,
220 G_TYPE_NONE, 0 );
221 tslider_signals[TEXT_CHANGED] = g_signal_new( "text_changed",
222 G_OBJECT_CLASS_TYPE( gobject_class ),
223 G_SIGNAL_RUN_FIRST,
224 G_STRUCT_OFFSET( TsliderClass, text_changed ),
225 NULL, NULL,
226 g_cclosure_marshal_VOID__VOID,
227 G_TYPE_NONE, 0 );
228
229 /* Init methods.
230 */
231 }
232
233 /* From/to/value have changed ... tell everyone.
234 */
235 void
tslider_changed(Tslider * tslider)236 tslider_changed( Tslider *tslider )
237 {
238 #ifdef DEBUG
239 printf( "tslider_changed\n" );
240 #endif /*DEBUG*/
241
242 g_signal_emit( G_OBJECT( tslider ), tslider_signals[CHANGED], 0 );
243 }
244
245 /* Activated!
246 */
247 static void
tslider_activate(Tslider * tslider)248 tslider_activate( Tslider *tslider )
249 {
250 #ifdef DEBUG
251 printf( "tslider_activate\n" );
252 #endif /*DEBUG*/
253
254 g_signal_emit( G_OBJECT( tslider ), tslider_signals[ACTIVATE], 0 );
255 }
256
257 /* Just the slider changed.
258 */
259 static void
tslider_slider_changed(Tslider * tslider)260 tslider_slider_changed( Tslider *tslider )
261 {
262 #ifdef DEBUG
263 printf( "tslider_slider_changed\n" );
264 #endif /*DEBUG*/
265
266 g_signal_emit( G_OBJECT( tslider ),
267 tslider_signals[SLIDER_CHANGED], 0 );
268 }
269
270 /* Text has been touched.
271 */
272 static void
tslider_text_changed(Tslider * tslider)273 tslider_text_changed( Tslider *tslider )
274 {
275 #ifdef DEBUG
276 printf( "tslider_text_changed\n" );
277 #endif /*DEBUG*/
278
279 g_signal_emit( G_OBJECT( tslider ), tslider_signals[TEXT_CHANGED], 0 );
280 }
281
282 /* Enter in entry widget
283 */
284 static void
tslider_value_activate_cb(GtkWidget * entry,Tslider * tslider)285 tslider_value_activate_cb( GtkWidget *entry, Tslider *tslider )
286 {
287 double value;
288
289 if( !get_geditable_double( entry, &value ) ) {
290 iwindow_alert( entry, GTK_MESSAGE_ERROR );
291 return;
292 }
293
294 if( tslider->value != value ) {
295 tslider->value = value;
296
297 if( tslider->auto_link )
298 tslider_changed( tslider );
299 else
300 tslider_activate( tslider );
301 }
302 }
303
304 /* Drag on slider.
305 */
306 static void
tslider_value_changed_cb(GtkAdjustment * adj,Tslider * tslider)307 tslider_value_changed_cb( GtkAdjustment *adj, Tslider *tslider )
308 {
309 #ifdef DEBUG
310 printf( "tslider_value_changed_cb\n" );
311 #endif /*DEBUG*/
312
313 if( tslider->svalue != adj->value ) {
314 tslider->svalue = adj->value;
315
316 if( tslider->auto_link ) {
317 tslider->value =
318 tslider_slider_to_value( tslider, adj->value );
319
320 tslider_changed( tslider );
321 }
322 else
323 tslider_slider_changed( tslider );
324 }
325 }
326
327 /* Text has changed (and may need to be scanned later).
328 */
329 static void
tslider_text_changed_cb(GtkWidget * widget,Tslider * tslider)330 tslider_text_changed_cb( GtkWidget *widget, Tslider *tslider )
331 {
332 #ifdef DEBUG
333 printf( "tslider_text_changed_cb\n" );
334 #endif /*DEBUG*/
335
336 tslider_text_changed( tslider );
337 }
338
339 /* Default identity conversion.
340 */
341 static double
tslider_conversion_id(double from,double to,double value)342 tslider_conversion_id( double from, double to, double value )
343 {
344 return( value );
345 }
346
347 static gboolean
tslider_scroll_cb(GtkWidget * wid,GdkEvent * event,Tslider * tslider)348 tslider_scroll_cb( GtkWidget *wid, GdkEvent *event, Tslider *tslider )
349 {
350 gboolean handled;
351
352 handled = FALSE;
353
354 /* Stop any other scroll handlers running. We don't want the scroll
355 * wheel to change widgets while we're moving.
356 */
357 if( tslider->ignore_scroll )
358 handled = TRUE;
359
360 return( handled );
361 }
362
363 static void
tslider_init(Tslider * tslider)364 tslider_init( Tslider *tslider )
365 {
366 #ifdef DEBUG
367 printf( "tslider_init: %p\n", tslider );
368 #endif /*DEBUG*/
369
370 /* Any old start values ... overridden later.
371 */
372 tslider->from = -1;
373 tslider->to = -1;
374 tslider->value = -1;
375 tslider->svalue = -1;
376 tslider->digits = -1;
377 tslider->last_to = -1;
378 tslider->last_from = -1;
379 tslider->last_svalue = -1;
380 tslider->ignore_scroll = TRUE;
381
382 gtk_box_set_spacing( GTK_BOX( tslider ), 2 );
383
384 tslider->entry = build_entry( 5 );
385 gtk_entry_set_max_length( GTK_ENTRY( tslider->entry ), 10 );
386 set_tooltip( tslider->entry, _( "Slider value ... edit!" ) );
387 gtk_box_pack_start( GTK_BOX( tslider ),
388 tslider->entry, FALSE, FALSE, 0 );
389 gtk_signal_connect( GTK_OBJECT( tslider->entry ), "activate",
390 GTK_SIGNAL_FUNC( tslider_value_activate_cb ), tslider );
391 gtk_signal_connect( GTK_OBJECT( tslider->entry ), "changed",
392 GTK_SIGNAL_FUNC( tslider_text_changed_cb ), tslider );
393 gtk_widget_show( tslider->entry );
394
395 tslider->slider = gtk_hscale_new( NULL );
396 tslider->adj = gtk_range_get_adjustment( GTK_RANGE( tslider->slider ) );
397 gtk_range_set_update_policy( GTK_RANGE( tslider->slider ),
398 GTK_UPDATE_CONTINUOUS );
399 #ifdef DEBUG
400 gtk_range_set_update_policy( GTK_RANGE( tslider->slider ),
401 GTK_UPDATE_DISCONTINUOUS );
402 #endif /*DEBUG*/
403 gtk_scale_set_draw_value( GTK_SCALE( tslider->slider ), FALSE );
404 gtk_widget_set_size_request( GTK_WIDGET( tslider->slider ), 100, -1 );
405 gtk_box_pack_start( GTK_BOX( tslider ),
406 tslider->slider, TRUE, TRUE, 0 );
407 set_tooltip( tslider->slider, _( "Left-drag to set number" ) );
408 gtk_signal_connect( GTK_OBJECT( tslider->adj ), "value_changed",
409 GTK_SIGNAL_FUNC( tslider_value_changed_cb ), tslider );
410 g_signal_connect( tslider->slider, "scroll-event",
411 G_CALLBACK( tslider_scroll_cb ), tslider );
412 gtk_widget_show( tslider->slider );
413
414 tslider->auto_link = TRUE;
415 tslider->slider_to_value = tslider_conversion_id;
416 tslider->value_to_slider = tslider_conversion_id;
417 }
418
419 GtkType
tslider_get_type(void)420 tslider_get_type( void )
421 {
422 static GtkType tslider_type = 0;
423
424 if( !tslider_type ) {
425 static const GtkTypeInfo sinfo = {
426 "Tslider",
427 sizeof( Tslider ),
428 sizeof( TsliderClass ),
429 (GtkClassInitFunc) tslider_class_init,
430 (GtkObjectInitFunc) tslider_init,
431 /* reserved_1 */ NULL,
432 /* reserved_2 */ NULL,
433 (GtkClassInitFunc) NULL,
434 };
435
436 tslider_type = gtk_type_unique( GTK_TYPE_HBOX, &sinfo );
437 }
438
439 return( tslider_type );
440 }
441
442 Tslider *
tslider_new()443 tslider_new()
444 {
445 Tslider *tslider = gtk_type_new( TYPE_TSLIDER );
446
447 return( tslider );
448 }
449
450 void
tslider_set_conversions(Tslider * tslider,tslider_fn value_to_slider,tslider_fn slider_to_value)451 tslider_set_conversions( Tslider *tslider,
452 tslider_fn value_to_slider, tslider_fn slider_to_value )
453 {
454 tslider->value_to_slider = value_to_slider;
455 tslider->slider_to_value = slider_to_value;
456
457 tslider->auto_link = value_to_slider && slider_to_value;
458 }
459
460 double
tslider_log_value_to_slider(double from,double to,double value)461 tslider_log_value_to_slider( double from, double to, double value )
462 {
463 /* What does 1.0 map to on our [0,1] scale?
464 */
465 const double mapped1 = (1.0 - from) / (to - from);
466
467 /* We want an exponent which maps the mid point on the slider to 1.
468 */
469 const double a = log( mapped1 ) / log( 0.5 );
470
471 const double nvalue = pow( value, 1.0 / a );
472
473 return( nvalue );
474 }
475
476 double
tslider_log_slider_to_value(double from,double to,double value)477 tslider_log_slider_to_value( double from, double to, double value )
478 {
479 /* What does 1.0 map to on our [0,1] scale?
480 */
481 const double mapped1 = (1.0 - from) / (to - from);
482
483 /* We want an exponent which maps the mid point on the slider to 1.
484 */
485 const double a = log( mapped1 ) / log( 0.5 );
486
487 const double nvalue = pow( value, a );
488
489 return( nvalue );
490 }
491
492 void
tslider_set_ignore_scroll(Tslider * tslider,gboolean ignore_scroll)493 tslider_set_ignore_scroll( Tslider *tslider, gboolean ignore_scroll )
494 {
495 tslider->ignore_scroll = ignore_scroll;
496 }
497