1 /* display a caption/value label pair, on a click display the formula in an
2  * entry widget
3  */
4 
5 /*
6 
7     Copyright (C) 1991-2003 The National Gallery
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
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.
18 
19     You should have received a copy of the GNU General Public License along
20     with this program; if not, write to the Free Software Foundation, Inc.,
21     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 
23  */
24 
25 /*
26 
27     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
28 
29  */
30 
31 /*
32 #define DEBUG
33  */
34 
35 #include "ip.h"
36 
37 /* Our signals.
38  */
39 enum {
40 	EDIT,
41 	CHANGED,
42 	ACTIVATE,
43 	ENTER,
44 	LEAVE,
45 	LAST_SIGNAL
46 };
47 
48 static GtkEventBoxClass *parent_class = NULL;
49 
50 static guint formula_signals[LAST_SIGNAL] = { 0 };
51 
52 /* Formula needing a refresh.
53  */
54 static GSList *formula_refresh_all = NULL;
55 
56 /* The idle we add if there are any formula needing a refresh.
57  */
58 static gint formula_refresh_idle = 0;
59 
60 /* Unqueue a refresh.
61  */
62 static void
formula_refresh_unqueue(Formula * formula)63 formula_refresh_unqueue( Formula *formula )
64 {
65 	if( formula->refresh_queued ) {
66 		formula_refresh_all =
67 			g_slist_remove( formula_refresh_all, formula );
68 		formula->refresh_queued = FALSE;
69 
70 		if( !formula_refresh_all )
71 			IM_FREEF( g_source_remove, formula_refresh_idle );
72 	}
73 }
74 
75 /* Detect cancel in a text field.
76  */
77 static gboolean
formula_key_press_event_cb(GtkWidget * widget,GdkEventKey * ev,Formula * formula)78 formula_key_press_event_cb( GtkWidget *widget, GdkEventKey *ev,
79 	Formula *formula )
80 {
81 	gboolean handled;
82 
83 	handled = FALSE;
84 
85         if( ev->keyval == GDK_Escape ) {
86 		set_gentry( formula->entry, "%s", formula->expr );
87 
88 		/*
89 
90 			FIXME ... really we want to go back to the edit mode
91 			set by our environment (eg. if we're in a show_formula
92 			workspace, should stay in show formula).
93 
94 		 */
95 		formula_set_edit( formula, FALSE );
96 
97 		handled = TRUE;
98 	}
99 
100         return( handled );
101 }
102 
103 /* Activated!
104  */
105 static void
formula_activate(Formula * formula)106 formula_activate( Formula *formula )
107 {
108 	g_signal_emit( G_OBJECT( formula ), formula_signals[ACTIVATE], 0 );
109 }
110 
111 static void
formula_activate_cb(GtkWidget * wid,Formula * formula)112 formula_activate_cb( GtkWidget *wid, Formula *formula )
113 {
114 	formula_activate( formula );
115 }
116 
117 /* A char has changed in the entry (we will need scanning on activate).
118  */
119 static void
formula_changed(Formula * formula)120 formula_changed( Formula *formula )
121 {
122 	g_signal_emit( G_OBJECT( formula ), formula_signals[CHANGED], 0 );
123 }
124 
125 /* Add an edit box.
126  */
127 static void
formula_add_edit(Formula * formula)128 formula_add_edit( Formula *formula )
129 {
130         if( formula->entry_frame )
131                 return;
132 
133 	/* We need to use an alignment since if the left label is hidden we'll
134  	 * have nothing to hold us to the right height.
135 	 */
136         formula->entry_frame = gtk_alignment_new( 0.5, 0.5, 1, 1 );
137 	gtk_alignment_set_padding( GTK_ALIGNMENT( formula->entry_frame ),
138 		3, 3, 2, 2 );
139 	gtk_box_pack_start( GTK_BOX( formula->hbox ),
140 		formula->entry_frame, TRUE, TRUE, 0 );
141 
142         formula->entry = gtk_entry_new();
143         set_tooltip( formula->entry, _( "Press Escape to cancel edit, "
144                 "press Return to accept edit and recalculate" ) );
145         gtk_signal_connect( GTK_OBJECT( formula->entry ), "key_press_event",
146 		GTK_SIGNAL_FUNC( formula_key_press_event_cb ),
147 		GTK_OBJECT( formula ) );
148         gtk_signal_connect_object( GTK_OBJECT( formula->entry ), "changed",
149 		GTK_SIGNAL_FUNC( formula_changed ), GTK_OBJECT( formula ) );
150         gtk_signal_connect( GTK_OBJECT( formula->entry ), "activate",
151                 GTK_SIGNAL_FUNC( formula_activate_cb ), formula );
152 	gtk_container_add( GTK_CONTAINER( formula->entry_frame ),
153 		formula->entry );
154 	gtk_widget_show( formula->entry );
155 
156 	/* Tell everyone we are in edit mode ... used to add to resettable,
157 	 * for example.
158 	 */
159 	g_signal_emit( G_OBJECT( formula ), formula_signals[EDIT], 0 );
160 }
161 
162 static void
formula_refresh(Formula * formula)163 formula_refresh( Formula *formula )
164 {
165 #ifdef DEBUG
166 	printf( "formula_refresh\n" );
167 #endif /*DEBUG*/
168 
169 	/* Set edit mode.
170 	 */
171 	if( formula->edit ) {
172 		formula_add_edit( formula );
173                 gtk_widget_show( formula->entry_frame );
174                 gtk_widget_hide( formula->right_label );
175 		formula->changed = FALSE;
176 	}
177 	else {
178                 gtk_widget_show( formula->right_label );
179                 IM_FREEF( gtk_widget_destroy, formula->entry );
180                 IM_FREEF( gtk_widget_destroy, formula->entry_frame );
181 	}
182 
183 	/* Don't update the formula display if the user has edited the text ...
184 	 * we shouldn't destroy their work.
185 	 */
186 	if( formula->entry && formula->expr && !formula->changed ) {
187 		/* Make sure we don't trigger "changed" when we zap in new
188 		 * text.
189 		 */
190 		gtk_signal_handler_block_by_data(
191 			GTK_OBJECT( formula->entry ), formula );
192 		set_gentry( formula->entry, "%s", formula->expr );
193 		gtk_signal_handler_unblock_by_data(
194 			GTK_OBJECT( formula->entry ), formula );
195 	}
196 
197 	if( formula->caption ) {
198 		set_glabel( formula->left_label, _( "%s:" ), formula->caption );
199                 gtk_widget_show( formula->left_label );
200 	}
201 	else
202                 gtk_widget_hide( formula->left_label );
203 	if( formula->value )
204 		/* Just display the first line of the formula ... it can be
205 		 * mutiline for class members, for example.
206 		 */
207 		set_glabel1( formula->right_label, "%s", formula->value );
208 
209 	if( formula->edit && formula->needs_focus ) {
210 		if( formula->expr )
211 			gtk_editable_select_region(
212 				GTK_EDITABLE( formula->entry ), 0, -1 );
213 		gtk_widget_grab_focus( formula->entry );
214 		formula->needs_focus = FALSE;
215 	}
216 }
217 
218 static gboolean
formula_refresh_idle_cb(void)219 formula_refresh_idle_cb( void )
220 {
221 	formula_refresh_idle = 0;
222 
223 	while( formula_refresh_all ) {
224 		Formula *formula = FORMULA( formula_refresh_all->data );
225 
226 		formula_refresh_unqueue( formula );
227 		formula_refresh( formula );
228 	}
229 
230 	return( FALSE );
231 }
232 
233 static void
formula_refresh_queue(Formula * formula)234 formula_refresh_queue( Formula *formula )
235 {
236 	if( !formula->refresh_queued ) {
237 		formula_refresh_all =
238 			g_slist_prepend( formula_refresh_all, formula );
239 		formula->refresh_queued = TRUE;
240 
241 		if( !formula_refresh_idle )
242 			formula_refresh_idle = g_idle_add(
243 				(GSourceFunc) formula_refresh_idle_cb, NULL );
244 	}
245 }
246 
247 static void
formula_destroy(GtkObject * object)248 formula_destroy( GtkObject *object )
249 {
250 	Formula *formula;
251 
252 #ifdef DEBUG
253 	printf( "formula_destroy\n" );
254 #endif /*DEBUG*/
255 
256 	g_return_if_fail( object != NULL );
257 	g_return_if_fail( IS_FORMULA( object ) );
258 
259 	/* My instance destroy stuff.
260 	 */
261 	formula = FORMULA( object );
262 
263 	formula_refresh_unqueue( formula );
264 	IM_FREE( formula->caption );
265 	IM_FREE( formula->value );
266 	IM_FREE( formula->expr );
267 
268 	GTK_OBJECT_CLASS( parent_class )->destroy( object );
269 }
270 
271 /* Change edit mode.
272  */
273 void
formula_set_edit(Formula * formula,gboolean edit)274 formula_set_edit( Formula *formula, gboolean edit )
275 {
276 #ifdef DEBUG
277 	printf( "formula_set_edit: %d\n", edit );
278 #endif /*DEBUG*/
279 
280 	if( formula->edit != edit ) {
281 		formula->edit = edit;
282 		formula_refresh_queue( formula );
283 	}
284 
285 	/* Can't have been edited yet, whichever way we're turning edit.
286 	 */
287 	formula->changed = FALSE;
288 }
289 
290 /* Grab focus on next refresh.
291  */
292 void
formula_set_needs_focus(Formula * formula,gboolean needs_focus)293 formula_set_needs_focus( Formula *formula, gboolean needs_focus )
294 {
295 #ifdef DEBUG
296 	printf( "formula_set_needs_focus: %d\n", needs_focus );
297 #endif /*DEBUG*/
298 
299 	if( formula->needs_focus != needs_focus ) {
300 		formula->needs_focus = needs_focus;
301 		formula_refresh_queue( formula );
302 	}
303 }
304 
305 /* Change sensitive mode.
306  */
307 void
formula_set_sensitive(Formula * formula,gboolean sensitive)308 formula_set_sensitive( Formula *formula, gboolean sensitive )
309 {
310 #ifdef DEBUG
311 	printf( "formula_set_sensitive: %d\n", sensitive );
312 #endif /*DEBUG*/
313 
314 	if( formula->sensitive != sensitive ) {
315 		formula->sensitive = sensitive;
316 
317 		if( !formula->sensitive )
318 			formula_set_edit( formula, FALSE );
319 
320 		formula_refresh_queue( formula );
321 	}
322 }
323 
324 /* Re-read the text. TRUE if we saw a change.
325  */
326 gboolean
formula_scan(Formula * formula)327 formula_scan( Formula *formula )
328 {
329 	gboolean changed;
330 
331 #ifdef DEBUG
332 	printf( "formula_scan\n" );
333 #endif /*DEBUG*/
334 
335 	changed = FALSE;
336 
337 	/* Should be in edit mode.
338 	 */
339 	if( formula->edit &&
340 		formula->entry &&
341 		GTK_WIDGET_VISIBLE( formula->entry ) ) {
342 		const char *expr;
343 
344 		/* There should be some edited text.
345 		 */
346 		expr = gtk_entry_get_text( GTK_ENTRY( formula->entry ) );
347 		if( expr &&
348 			strspn( expr, WHITESPACE ) != strlen( expr ) ) {
349 			IM_SETSTR( formula->expr, expr );
350 			changed = TRUE;
351 		}
352 
353                 formula_set_edit( formula, FALSE );
354 	}
355 
356 	return( changed );
357 }
358 
359 static gboolean
formula_enter_notify_event(GtkWidget * widget,GdkEventCrossing * event)360 formula_enter_notify_event( GtkWidget *widget, GdkEventCrossing *event )
361 {
362 	GtkWidget *event_widget;
363 
364 	event_widget = gtk_get_event_widget( (GdkEvent *) event );
365 
366 	if( event_widget == widget && event->detail != GDK_NOTIFY_INFERIOR ) {
367 		gtk_widget_set_state( widget, GTK_STATE_PRELIGHT );
368 
369 		/* Tell people about our highlight change ... used to (eg.) set
370 		 * flash help.
371 		 */
372 		g_signal_emit( G_OBJECT( widget ), formula_signals[ENTER], 0 );
373 	}
374 
375 	return( FALSE );
376 }
377 
378 static gboolean
formula_leave_notify_event(GtkWidget * widget,GdkEventCrossing * event)379 formula_leave_notify_event( GtkWidget *widget, GdkEventCrossing *event )
380 {
381 	GtkWidget *event_widget;
382 
383 	event_widget = gtk_get_event_widget( (GdkEvent *) event );
384 
385 	if( event_widget == widget && event->detail != GDK_NOTIFY_INFERIOR ) {
386 		gtk_widget_set_state( widget, GTK_STATE_NORMAL );
387 
388 		/* Tell people about our highlight change ... used to (eg.) set
389 		 * flash help.
390 		 */
391 		g_signal_emit( G_OBJECT( widget ), formula_signals[LEAVE], 0 );
392 	}
393 
394 	return( FALSE );
395 }
396 
397 /* Event in us somewhere.
398  */
399 static gboolean
formula_button_press_event(GtkWidget * widget,GdkEventButton * event)400 formula_button_press_event( GtkWidget *widget, GdkEventButton *event )
401 {
402 	gboolean handled = FALSE;
403 
404 	if( event->type == GDK_BUTTON_PRESS ) {
405 		Formula *formula = FORMULA( widget );
406 
407 		if( event->button == 1 && formula->sensitive ) {
408 			if( !formula->edit ) {
409 				formula_set_edit( formula, TRUE );
410 				formula_set_needs_focus( formula, TRUE );
411 			}
412 
413 			handled = TRUE;
414 		}
415 	}
416 
417 	return( handled );
418 }
419 
420 static void
formula_real_changed(Formula * formula)421 formula_real_changed( Formula *formula )
422 {
423 #ifdef DEBUG
424 	printf( "formula_real_changed\n" );
425 #endif /*DEBUG*/
426 
427 	formula->changed = TRUE;
428 }
429 
430 static void
formula_class_init(FormulaClass * class)431 formula_class_init( FormulaClass *class )
432 {
433 	GtkObjectClass *gobject_class = (GtkObjectClass *) class;
434 	GtkWidgetClass *widget_class = (GtkWidgetClass *) class;
435 
436 	parent_class = g_type_class_peek_parent( class );
437 
438 	gobject_class->destroy = formula_destroy;
439 
440 	widget_class->enter_notify_event = formula_enter_notify_event;
441 	widget_class->leave_notify_event = formula_leave_notify_event;
442 	widget_class->button_press_event = formula_button_press_event;
443 
444 	/* Create signals.
445 	 */
446 	formula_signals[EDIT] = g_signal_new( "edit",
447 		G_OBJECT_CLASS_TYPE( gobject_class ),
448 		G_SIGNAL_RUN_FIRST,
449 		G_STRUCT_OFFSET( FormulaClass, changed ),
450 		NULL, NULL,
451 		g_cclosure_marshal_VOID__VOID,
452 		G_TYPE_NONE, 0 );
453 	formula_signals[CHANGED] = g_signal_new( "changed",
454 		G_OBJECT_CLASS_TYPE( gobject_class ),
455 		G_SIGNAL_RUN_FIRST,
456 		G_STRUCT_OFFSET( FormulaClass, changed ),
457 		NULL, NULL,
458 		g_cclosure_marshal_VOID__VOID,
459 		G_TYPE_NONE, 0 );
460 	formula_signals[ACTIVATE] = g_signal_new( "activate",
461 		G_OBJECT_CLASS_TYPE( gobject_class ),
462 		G_SIGNAL_RUN_FIRST,
463 		G_STRUCT_OFFSET( FormulaClass, activate ),
464 		NULL, NULL,
465 		g_cclosure_marshal_VOID__VOID,
466 		G_TYPE_NONE, 0 );
467 	formula_signals[ENTER] = g_signal_new( "enter",
468 		G_OBJECT_CLASS_TYPE( gobject_class ),
469 		G_SIGNAL_RUN_FIRST,
470 		G_STRUCT_OFFSET( FormulaClass, enter ),
471 		NULL, NULL,
472 		g_cclosure_marshal_VOID__VOID,
473 		G_TYPE_NONE, 0 );
474 	formula_signals[LEAVE] = g_signal_new( "leave",
475 		G_OBJECT_CLASS_TYPE( gobject_class ),
476 		G_SIGNAL_RUN_FIRST,
477 		G_STRUCT_OFFSET( FormulaClass, leave ),
478 		NULL, NULL,
479 		g_cclosure_marshal_VOID__VOID,
480 		G_TYPE_NONE, 0 );
481 
482 	/* Init methods.
483 	 */
484 	class->changed = formula_real_changed;
485 }
486 
487 static void
formula_init(Formula * formula)488 formula_init( Formula *formula )
489 {
490 	/* How annoying! To avoid vertical resizes on edit/view toggles we
491 	 * need to add differing amounts of padding to the label depending on
492 	 * the theme.
493 
494 	  	FIXME ... get this from the style somehow
495 
496 	 */
497 #ifdef OS_WIN32
498 	/* with either wimp theme or gtk default.
499 	 */
500 	const int vpadding = 7;
501 #else /*!OS_WIN32*/
502 	/* clearlooks
503 	 */
504 	const int vpadding = 8;
505 #endif /*OS_WIN32*/
506 
507 	formula->caption = NULL;
508 	formula->value = NULL;
509 	formula->expr = NULL;
510 	formula->edit = FALSE;
511 	formula->sensitive = TRUE;
512 	formula->changed = FALSE;
513 	formula->refresh_queued = FALSE;
514 	formula->needs_focus = FALSE;
515 
516 	formula->entry_frame = NULL;
517 
518 	gtk_widget_add_events( GTK_WIDGET( formula ),
519 		GDK_POINTER_MOTION_HINT_MASK );
520 
521 	formula->hbox = gtk_hbox_new( FALSE, 12 );
522 	gtk_container_add( GTK_CONTAINER( formula ), formula->hbox );
523         gtk_widget_show( formula->hbox );
524 
525         formula->left_label = gtk_label_new( "" );
526         gtk_misc_set_alignment( GTK_MISC( formula->left_label ), 0, 0.5 );
527         gtk_misc_set_padding( GTK_MISC( formula->left_label ), 2, vpadding );
528 	gtk_box_pack_start( GTK_BOX( formula->hbox ),
529 		formula->left_label, FALSE, FALSE, 2 );
530         gtk_widget_show( formula->left_label );
531 
532         formula->right_label = gtk_label_new( "" );
533         gtk_misc_set_alignment( GTK_MISC( formula->right_label ), 0, 0.5 );
534         gtk_misc_set_padding( GTK_MISC( formula->right_label ), 7, vpadding );
535 	gtk_box_pack_start( GTK_BOX( formula->hbox ),
536 		formula->right_label, TRUE, TRUE, 0 );
537         gtk_widget_show( formula->right_label );
538 }
539 
540 GtkType
formula_get_type(void)541 formula_get_type( void )
542 {
543 	static GtkType formula_type = 0;
544 
545 	if( !formula_type ) {
546 		static const GtkTypeInfo formula_info = {
547 			"Formula",
548 			sizeof( Formula ),
549 			sizeof( FormulaClass ),
550 			(GtkClassInitFunc) formula_class_init,
551 			(GtkObjectInitFunc) formula_init,
552 			/* reserved_1 */ NULL,
553 			/* reserved_2 */ NULL,
554 			(GtkClassInitFunc) NULL,
555 		};
556 
557 		formula_type = gtk_type_unique( GTK_TYPE_EVENT_BOX,
558 			&formula_info );
559 	}
560 
561 	return( formula_type );
562 }
563 
564 Formula *
formula_new(void)565 formula_new( void )
566 {
567 	Formula *formula = gtk_type_new( TYPE_FORMULA );
568 
569 	return( formula );
570 }
571 
572 void
formula_set_caption(Formula * formula,const char * caption)573 formula_set_caption( Formula *formula, const char *caption )
574 {
575 	if( !caption && formula->caption ) {
576 		IM_FREE( formula->caption );
577 		formula_refresh_queue( formula );
578 	}
579 	else if( caption && (!formula->caption ||
580 		strcmp( caption, formula->caption ) != 0) ) {
581 		IM_SETSTR( formula->caption, caption );
582 		formula_refresh_queue( formula );
583 	}
584 }
585 
586 void
formula_set_value_expr(Formula * formula,const char * value,const char * expr)587 formula_set_value_expr( Formula *formula, const char *value, const char *expr )
588 {
589 #ifdef DEBUG
590 	printf( "formula_set_value_expr: value=\"%s\", expr=\"%s\"\n",
591 		value, expr );
592 #endif /*DEBUG*/
593 
594 	if( value && (!formula->value ||
595 		strcmp( value, formula->value ) != 0) ) {
596 		IM_SETSTR( formula->value, value );
597 		formula_refresh_queue( formula );
598 	}
599 
600 	if( expr && (!formula->expr ||
601 		strcmp( expr, formula->expr ) != 0) ) {
602 		IM_SETSTR( formula->expr, expr );
603 		formula_refresh_queue( formula );
604 	}
605 }
606