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