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