1 /* gtkutil functions.
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 #include "ip.h"
31
32 /*
33 #define DEBUG
34 */
35
36 /* All our tooltips.
37 */
38 static GtkTooltips *our_tooltips = NULL;
39
40 /* Set two adjustments together.
41 */
42 void
adjustments_set_value(GtkAdjustment * hadj,GtkAdjustment * vadj,float hval,float vval)43 adjustments_set_value( GtkAdjustment *hadj, GtkAdjustment *vadj,
44 float hval, float vval )
45 {
46 gboolean hchanged = FALSE;
47 gboolean vchanged = FALSE;
48
49 if( hval != hadj->value ) {
50 hadj->value = hval;
51 hchanged = TRUE;
52 }
53 if( vval != vadj->value ) {
54 vadj->value = vval;
55 vchanged = TRUE;
56 }
57
58 #ifdef DEBUG
59 if( hchanged )
60 printf( "adjustments_set_value: hadj = %g\n", hval );
61 if( vchanged )
62 printf( "adjustments_set_value: vadj = %g\n", vval );
63 #endif /*DEBUG*/
64
65 if( hchanged )
66 gtk_adjustment_value_changed( hadj );
67 if( vchanged )
68 gtk_adjustment_value_changed( vadj );
69 }
70
71 void *
object_destroy(void * obj)72 object_destroy( void *obj )
73 {
74 gtk_object_destroy( GTK_OBJECT( obj ) );
75
76 return( NULL );
77 }
78
79 /* Like g_free, but return NULL for list maps.
80 */
81 void *
null_g_free(void * obj)82 null_g_free( void *obj )
83 {
84 g_free( obj );
85
86 return( NULL );
87 }
88
89 /* Set visible/not.
90 */
91 void
widget_visible(GtkWidget * widget,gboolean visible)92 widget_visible( GtkWidget *widget, gboolean visible )
93 {
94 if( widget && visible )
95 gtk_widget_show( widget );
96 else if( widget && !visible )
97 gtk_widget_hide( widget );
98 }
99
100 /* Make a button widget.
101 */
102 GtkWidget *
build_button(const char * stock_id,GtkSignalFunc cb,gpointer user)103 build_button( const char *stock_id, GtkSignalFunc cb, gpointer user )
104 {
105 GtkWidget *but;
106
107 but = gtk_button_new_from_stock( stock_id );
108 GTK_WIDGET_SET_FLAGS( but, GTK_CAN_DEFAULT );
109 gtk_signal_connect( GTK_OBJECT( but ), "clicked", cb, user );
110
111 return( but );
112 }
113
114 /* Calculate the bounding box for a string rendered with a widget's default
115 * font. Set geo to a rect with 0,0 positioned on the left-hand baseline.
116 */
117 void
get_geo(GtkWidget * widget,const char * text,Rect * geo)118 get_geo( GtkWidget *widget, const char *text, Rect *geo )
119 {
120 PangoLayout *layout;
121 int width, height;
122
123 layout = gtk_widget_create_pango_layout( widget, text );
124 pango_layout_get_pixel_size( layout, &width, &height );
125 g_object_unref( layout );
126
127 /* FIXME ... we left/top to 0 for now.
128 */
129 geo->width = width;
130 geo->height = height;
131 geo->left = 0;
132 geo->top = 0;
133 }
134
135 /* Set a widget to a fixed size ... width in characters.
136 */
137 void
set_fixed(GtkWidget * widget,int nchars)138 set_fixed( GtkWidget *widget, int nchars )
139 {
140 Rect geo;
141
142 get_geo( widget, "8", &geo );
143 gtk_widget_set_size_request( widget, geo.width * nchars, geo.height );
144 }
145
146 /* Build a GtkEntry, with a widget width specified in characters.
147 */
148 GtkWidget *
build_entry(int nchars)149 build_entry( int nchars )
150 {
151 GtkWidget *entry;
152
153 entry = gtk_entry_new();
154 gtk_entry_set_width_chars( GTK_ENTRY( entry ), nchars );
155
156 return( entry );
157 }
158
159 /* Build a new menu.
160 */
161 GtkWidget *
menu_build(const char * name)162 menu_build( const char *name )
163 {
164 GtkWidget *menu;
165
166 menu = gtk_menu_new();
167 gtk_menu_set_title( GTK_MENU( menu ), name );
168
169 return( menu );
170 }
171
172 /* Add a menu item.
173 */
174 GtkWidget *
menu_add_but(GtkWidget * menu,const char * stock_id,GtkSignalFunc cb,void * user)175 menu_add_but( GtkWidget *menu,
176 const char *stock_id, GtkSignalFunc cb, void *user )
177 {
178 GtkWidget *but;
179
180 /* We don't provide an accel group for popup menus.
181 */
182 but = gtk_image_menu_item_new_from_stock( stock_id, NULL );
183 gtk_menu_shell_append( GTK_MENU_SHELL( menu ), but );
184 gtk_widget_show( but );
185 gtk_signal_connect( GTK_OBJECT( but ), "activate", cb, user );
186
187 return( but );
188 }
189
190 /* Add a toggle item.
191 */
192 GtkWidget *
menu_add_tog(GtkWidget * menu,const char * name,GtkSignalFunc cb,void * user)193 menu_add_tog( GtkWidget *menu, const char *name, GtkSignalFunc cb, void *user )
194 {
195 GtkWidget *tog;
196
197 tog = gtk_check_menu_item_new_with_mnemonic( name );
198 gtk_menu_shell_append( GTK_MENU_SHELL( menu ), tog );
199 gtk_widget_show( tog );
200 gtk_signal_connect( GTK_OBJECT( tog ), "toggled", cb, user );
201
202 return( tog );
203 }
204
205 /* Add a separator.
206 */
207 GtkWidget *
menu_add_sep(GtkWidget * menu)208 menu_add_sep( GtkWidget *menu )
209 {
210 GtkWidget *sep;
211
212 sep = gtk_menu_item_new();
213 gtk_widget_set_sensitive( GTK_WIDGET( sep ), FALSE );
214 gtk_menu_shell_append( GTK_MENU_SHELL( menu ), sep );
215 gtk_widget_show( sep );
216
217 return( sep );
218 }
219
220 /* Add a pullright.
221 */
222 GtkWidget *
menu_add_pullright(GtkWidget * menu,const char * stock_id)223 menu_add_pullright( GtkWidget *menu, const char *stock_id )
224 {
225 GtkWidget *pullright;
226 GtkWidget *subpane;
227
228 subpane = gtk_menu_new();
229 pullright = gtk_image_menu_item_new_from_stock( stock_id, NULL );
230 gtk_menu_item_set_submenu( GTK_MENU_ITEM( pullright ), subpane );
231 gtk_menu_shell_append( GTK_MENU_SHELL( menu ), pullright );
232 gtk_widget_show( pullright );
233
234 return( subpane );
235 }
236
237 /* Four quarks: each menu item has a quark linking back to the main pane,
238 * plus a quark for the user signal. The main pane has a quark linking to the
239 * widget the menu was popped from, and that has the userdata for this context.
240 * One more quark holds the popup in a host.
241 */
242 static GQuark quark_main = 0;
243 static GQuark quark_host = 0;
244 static GQuark quark_data = 0;
245 static GQuark quark_popup = 0;
246
247 /* Build a new popup menu.
248 */
249 GtkWidget *
popup_build(const char * name)250 popup_build( const char *name )
251 {
252 /* Build our quarks.
253 */
254 if( !quark_main ) {
255 quark_main = g_quark_from_static_string( "quark_main" );
256 quark_host = g_quark_from_static_string( "quark_host" );
257 quark_data = g_quark_from_static_string( "quark_data" );
258 quark_popup = g_quark_from_static_string( "quark_popup" );
259 }
260
261 return( menu_build( name ) );
262 }
263
264 /* Activate function for a popup menu item.
265 */
266 static void
popup_activate_cb(GtkWidget * item,PopupFunc cb)267 popup_activate_cb( GtkWidget *item, PopupFunc cb )
268 {
269 GtkWidget *qmain = gtk_object_get_data_by_id(
270 GTK_OBJECT( item ), quark_main );
271 GtkWidget *qhost = gtk_object_get_data_by_id(
272 GTK_OBJECT( qmain ), quark_host );
273 void *qdata = gtk_object_get_data_by_id(
274 GTK_OBJECT( qhost ), quark_data );
275
276 (*cb)( item, qhost, qdata );
277 }
278
279 /* Add a menu item to a popup.
280 */
281 GtkWidget *
popup_add_but(GtkWidget * popup,const char * name,PopupFunc cb)282 popup_add_but( GtkWidget *popup, const char *name, PopupFunc cb )
283 {
284 GtkWidget *but = menu_add_but( popup, name,
285 GTK_SIGNAL_FUNC( popup_activate_cb ), (void *) cb );
286
287 gtk_object_set_data_by_id( GTK_OBJECT( but ), quark_main, popup );
288
289 return( but );
290 }
291
292 /* Add a toggle item to a popup.
293 */
294 GtkWidget *
popup_add_tog(GtkWidget * popup,const char * name,PopupFunc cb)295 popup_add_tog( GtkWidget *popup, const char *name, PopupFunc cb )
296 {
297 GtkWidget *tog = menu_add_tog( popup, name,
298 GTK_SIGNAL_FUNC( popup_activate_cb ), (void *) cb );
299
300 gtk_object_set_data_by_id( GTK_OBJECT( tog ), quark_main, popup );
301
302 return( tog );
303 }
304
305 /* Add a pullright item to a popup. Return the empty sub-pane.
306 */
307 GtkWidget *
popup_add_pullright(GtkWidget * popup,const char * name)308 popup_add_pullright( GtkWidget *popup, const char *name )
309 {
310 GtkWidget *pullright = menu_add_pullright( popup, name );
311
312 gtk_object_set_data_by_id( GTK_OBJECT( pullright ), quark_main, popup );
313
314 return( pullright );
315 }
316
317 /* Show the popup.
318 */
319 void
popup_show(GtkWidget * host,GdkEvent * ev)320 popup_show( GtkWidget *host, GdkEvent *ev )
321 {
322 GtkWidget *popup = gtk_object_get_data_by_id(
323 GTK_OBJECT( host ), quark_popup );
324
325 gtk_object_set_data_by_id( GTK_OBJECT( popup ), quark_host, host );
326 gtk_menu_popup( GTK_MENU( popup ), NULL, NULL,
327 (GtkMenuPositionFunc) NULL, NULL, 3, ev->button.time );
328 }
329
330 /* Event handler for popupshow.
331 */
332 static gboolean
popup_handle_event(GtkWidget * host,GdkEvent * ev,gpointer dummy)333 popup_handle_event( GtkWidget *host, GdkEvent *ev, gpointer dummy )
334 {
335 gboolean handled = FALSE;
336
337 if( ev->type == GDK_BUTTON_PRESS && ev->button.button == 3 ) {
338 popup_show( host, ev );
339 handled = TRUE;
340 }
341 else if( ev->type == GDK_KEY_PRESS && ev->key.keyval == GDK_F10 &&
342 ev->key.state & GDK_SHIFT_MASK ) {
343 popup_show( host, ev );
344 handled = TRUE;
345 }
346
347 return( handled );
348 }
349
350 /* Link a host to a popup.
351 */
352 void
popup_link(GtkWidget * host,GtkWidget * popup,void * data)353 popup_link( GtkWidget *host, GtkWidget *popup, void *data )
354 {
355 gtk_object_set_data_by_id( GTK_OBJECT( host ), quark_popup, popup );
356 gtk_object_set_data_by_id( GTK_OBJECT( host ), quark_data, data );
357 }
358
359 /* Add a callback to show a popup.
360 */
361 guint
popup_attach(GtkWidget * host,GtkWidget * popup,void * data)362 popup_attach( GtkWidget *host, GtkWidget *popup, void *data )
363 {
364 guint sid;
365
366 popup_link( host, popup, data );
367
368 /* We can't just use gtk_menu_attach_to_widget(), since that can only
369 * attach a menu to a single widget. We want to be able to attach a
370 * single menu to meny widgets.
371 */
372 sid = gtk_signal_connect( GTK_OBJECT( host ), "event",
373 GTK_SIGNAL_FUNC( popup_handle_event ), NULL );
374
375 return( sid );
376 }
377
378 void
popup_detach(GtkWidget * host,guint sid)379 popup_detach( GtkWidget *host, guint sid )
380 {
381 gtk_signal_disconnect( GTK_OBJECT( host ), sid );
382 }
383
384 static void
set_tooltip_events(GtkWidget * wid)385 set_tooltip_events( GtkWidget *wid )
386 {
387 gtk_widget_add_events( wid,
388 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
389 }
390
391 /* Set the tooltip on a widget.
392 */
393 void
set_tooltip(GtkWidget * wid,const char * fmt,...)394 set_tooltip( GtkWidget *wid, const char *fmt, ... )
395 {
396 va_list ap;
397 char *txt;
398
399 if( !wid )
400 return;
401
402 if( !fmt )
403 fmt = "";
404
405 va_start( ap, fmt );
406 txt = g_strdup_vprintf( fmt, ap );
407 va_end( ap );
408
409 if( !our_tooltips )
410 our_tooltips = gtk_tooltips_new();
411
412 gtk_tooltips_set_tip( our_tooltips, wid, txt, NULL );
413
414 if( !GTK_WIDGET_REALIZED( wid ) )
415 gtk_signal_connect( GTK_OBJECT( wid ), "realize",
416 GTK_SIGNAL_FUNC( set_tooltip_events ), NULL );
417 else
418 set_tooltip_events( wid );
419
420 g_free( txt );
421 }
422
423 /* Track tooltips we generate with one of these.
424 */
425 typedef struct _TooltipGenerate {
426 GtkWidget *widget;
427
428 TooltipGenerateFn generate;
429 void *a;
430 void *b;
431
432 VipsBuf buf;
433 char txt[256];
434 } TooltipGenerate;
435
436 static void
tooltip_generate_free(GtkWidget * widget,TooltipGenerate * gen)437 tooltip_generate_free( GtkWidget *widget, TooltipGenerate *gen )
438 {
439 gen->widget = NULL;
440 gen->generate = NULL;
441 gen->a = NULL;
442 gen->b = NULL;
443
444 IM_FREE( gen );
445 }
446
447 static gboolean
tooltip_generate_rebuild(GtkWidget * widget,GdkEventCrossing * event,TooltipGenerate * gen)448 tooltip_generate_rebuild( GtkWidget *widget,
449 GdkEventCrossing *event, TooltipGenerate *gen )
450 {
451 gboolean handled = FALSE;
452
453 if( gen->widget ) {
454 vips_buf_rewind( &gen->buf );
455 gen->generate( widget, &gen->buf, gen->a, gen->b );
456 set_tooltip( gen->widget, "%s", vips_buf_all( &gen->buf ) );
457 }
458
459 return( handled );
460 }
461
462 static void
tooltip_generate_attach(GtkWidget * widget,TooltipGenerate * gen)463 tooltip_generate_attach( GtkWidget *widget, TooltipGenerate *gen )
464 {
465 /* Must have enter/leave.
466 */
467 gtk_widget_add_events( widget,
468 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK );
469
470 /* On enter, regenerate the tooltip.
471 */
472 g_signal_connect( widget, "enter_notify_event",
473 G_CALLBACK( tooltip_generate_rebuild ), gen );
474 }
475
476 /* Set a callback to be used to generate the tooltip.
477 */
478 void
set_tooltip_generate(GtkWidget * widget,TooltipGenerateFn generate,void * a,void * b)479 set_tooltip_generate( GtkWidget *widget,
480 TooltipGenerateFn generate, void *a, void *b )
481 {
482 TooltipGenerate *gen;
483
484 if( !(gen = INEW( NULL, TooltipGenerate )) )
485 return;
486
487 gen->widget = widget;
488 gen->generate = generate;
489 gen->a = a;
490 gen->b = b;
491 vips_buf_init_static( &gen->buf, gen->txt, 256 );
492 g_signal_connect( widget, "destroy",
493 G_CALLBACK( tooltip_generate_free ), gen );
494
495 if( !GTK_WIDGET_REALIZED( widget ) )
496 g_signal_connect( widget, "realize",
497 G_CALLBACK( tooltip_generate_attach ), gen );
498 else
499 tooltip_generate_attach( widget, gen );
500 }
501
502 /* Junk all tooltips, helps trim valgrind noise.
503 */
504 void
junk_tooltips(void)505 junk_tooltips( void )
506 {
507 if( our_tooltips )
508 g_object_ref_sink( GTK_OBJECT( our_tooltips ) );
509 }
510
511 /* Set a GtkEditable.
512 */
513 void
set_gentryv(GtkWidget * edit,const char * fmt,va_list ap)514 set_gentryv( GtkWidget *edit, const char *fmt, va_list ap )
515 {
516 char buf[1000];
517 gint position;
518 int i;
519 int len;
520
521 if( !edit )
522 return;
523
524 if( !fmt )
525 fmt = "";
526
527 (void) im_vsnprintf( buf, 1000, fmt, ap );
528
529 /* Filter out /n and /t ... they confuse gtkentry terribly
530 */
531 len = strlen( buf );
532 for( i = 0; i < len; i++ )
533 if( buf[i] == '\n' || buf[i] == '\t' )
534 buf[i] = ' ';
535
536 gtk_editable_delete_text( GTK_EDITABLE( edit ), 0, -1 );
537 position = 0;
538 gtk_editable_insert_text( GTK_EDITABLE( edit ),
539 buf, strlen( buf ), &position );
540 }
541
542 /* Set a GtkEditable.
543 */
544 void
set_gentry(GtkWidget * edit,const char * fmt,...)545 set_gentry( GtkWidget *edit, const char *fmt, ... )
546 {
547 va_list ap;
548
549 va_start( ap, fmt );
550 set_gentryv( edit, fmt, ap );
551 va_end( ap );
552 }
553
554 void
set_glabel(GtkWidget * label,const char * fmt,...)555 set_glabel( GtkWidget *label, const char *fmt, ... )
556 {
557 va_list ap;
558 char buf[1000];
559
560 va_start( ap, fmt );
561 (void) im_vsnprintf( buf, 1000, fmt, ap );
562 va_end( ap );
563
564 gtk_label_set_text( GTK_LABEL( label ), buf );
565 }
566
567 /* Like set_glabel(), but don't display multi-line strings (just display the
568 * first line).
569 */
570 void
set_glabel1(GtkWidget * label,const char * fmt,...)571 set_glabel1( GtkWidget *label, const char *fmt, ... )
572 {
573 va_list ap;
574 char txt[1000];
575 VipsBuf buf = VIPS_BUF_STATIC( txt );
576
577 va_start( ap, fmt );
578 vips_buf_vappendf( &buf, fmt, ap );
579 va_end( ap );
580
581 gtk_label_set_text( GTK_LABEL( label ), vips_buf_firstline( &buf ) );
582 }
583
584 /* Like set_glabel, but do it caption-style.
585 */
586 void
set_gcaption(GtkWidget * label,const char * fmt,...)587 set_gcaption( GtkWidget *label, const char *fmt, ... )
588 {
589 va_list ap;
590 char buf1[1000];
591 char buf2[1000];
592
593 va_start( ap, fmt );
594 (void) im_vsnprintf( buf1, 1000, fmt, ap );
595 va_end( ap );
596
597 escape_markup( buf1, buf2, 1000 );
598 (void) im_snprintf( buf1, 1000,
599 "<span size=\"smaller\">%s</span>", buf2 );
600 gtk_label_set_markup( GTK_LABEL( label ), buf1 );
601 }
602
603 gboolean
get_geditable_name(GtkWidget * text,char * out,int sz)604 get_geditable_name( GtkWidget *text, char *out, int sz )
605 {
606 char *name;
607 char *tname;
608
609 name = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
610 tname = trim_nonalpha( name );
611 if( !tname ) {
612 IM_FREEF( g_free, name );
613 error_top( _( "Bad identifier." ) );
614 error_sub(
615 _( "Enter an identifier. Identifiers start with "
616 "a letter, and then contain only letters, numbers, "
617 "apostrophy and underscore." ) );
618 return( FALSE );
619 }
620 im_strncpy( out, tname, sz );
621 g_free( name );
622
623 return( TRUE );
624 }
625
626 gboolean
get_geditable_string(GtkWidget * text,char * out,int sz)627 get_geditable_string( GtkWidget *text, char *out, int sz )
628 {
629 char *str;
630
631 str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
632 im_strncpy( out, str, sz );
633 g_free( str );
634
635 return( TRUE );
636 }
637
638 gboolean
get_geditable_filename(GtkWidget * text,char * out,int sz)639 get_geditable_filename( GtkWidget *text, char *out, int sz )
640 {
641 char *filename;
642 char *tfilename;
643
644 filename = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
645 tfilename = trim_white( filename );
646 if( !is_valid_filename( tfilename ) ) {
647 g_free( filename );
648 return( FALSE );
649 }
650 im_strncpy( out, tfilename, sz );
651 g_free( filename );
652
653 return( TRUE );
654 }
655
656 /* Get a geditable as a double.
657 */
658 gboolean
get_geditable_double(GtkWidget * text,double * out)659 get_geditable_double( GtkWidget *text, double *out )
660 {
661 char *txt;
662 char *end;
663 double t;
664
665 txt = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
666 t = strtod( txt, &end );
667 if( end == txt ) {
668 error_top( _( "Bad floating point number." ) );
669 error_sub( _( "\"%s\" is not a floating point number." ), txt );
670 g_free( txt );
671
672 return( FALSE );
673 }
674
675 if( strspn( end, WHITESPACE ) != strlen( end ) ) {
676 error_top( _( "Bad floating point number." ) );
677 error_sub( _( "Extra characters \"%s\" after number." ), end );
678 g_free( txt );
679
680 return( FALSE );
681 }
682 g_free( txt );
683
684 *out = t;
685
686 return( TRUE );
687 }
688
689 /* Get as int.
690 */
691 gboolean
get_geditable_int(GtkWidget * text,int * n)692 get_geditable_int( GtkWidget *text, int *n )
693 {
694 int i;
695 char *txt;
696
697 /* Parse values.
698 */
699 txt = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
700 if( sscanf( txt, "%i", &i ) != 1 ) {
701 error_top( _( "Bad integer." ) );
702 error_sub( _( "\"%s\" is not an integer." ), txt );
703 g_free( txt );
704 return( FALSE );
705 }
706 g_free( txt );
707 *n = i;
708
709 return( TRUE );
710 }
711
712 /* Get as unsigned int.
713 */
714 gboolean
get_geditable_uint(GtkWidget * text,int * n)715 get_geditable_uint( GtkWidget *text, int *n )
716 {
717 int i;
718
719 if( !get_geditable_int( text, &i ) || i < 0 ) {
720 error_top( _( "Bad unsigned integer." ) );
721 return( FALSE );
722 }
723 *n = i;
724
725 return( TRUE );
726 }
727
728 /* Get as positive int.
729 */
730 gboolean
get_geditable_pint(GtkWidget * text,int * n)731 get_geditable_pint( GtkWidget *text, int *n )
732 {
733 int i;
734
735 if( !get_geditable_int( text, &i ) || i <= 0 ) {
736 error_top( _( "Bad positive integer." ) );
737 return( FALSE );
738 }
739 *n = i;
740
741 return( TRUE );
742 }
743
744 /* Indent widget, label above.
745 */
746 GtkWidget *
build_glabelframe2(GtkWidget * widget,const char * name)747 build_glabelframe2( GtkWidget *widget, const char *name )
748 {
749 GtkWidget *lab;
750 GtkWidget *vb;
751 GtkWidget *hb;
752 GtkWidget *inv;
753 char buf[1000];
754
755 hb = gtk_hbox_new( FALSE, 2 );
756 inv = gtk_label_new( "" );
757 gtk_box_pack_start( GTK_BOX( hb ), inv, FALSE, FALSE, 15 );
758 gtk_box_pack_start( GTK_BOX( hb ), widget, TRUE, TRUE, 0 );
759
760 vb = gtk_vbox_new( FALSE, 2 );
761 im_snprintf( buf, 1000, _( "%s:" ), name );
762 lab = gtk_label_new( buf );
763 gtk_misc_set_alignment( GTK_MISC( lab ), 0.0, 0.5 );
764 gtk_box_pack_start( GTK_BOX( vb ), lab, FALSE, FALSE, 0 );
765 gtk_box_pack_start( GTK_BOX( vb ), hb, TRUE, TRUE, 0 );
766
767 return( vb );
768 }
769
770 /* Make a text field + label. Indent the text on a new line.
771 */
772 GtkWidget *
build_glabeltext3(GtkWidget * box,const char * label)773 build_glabeltext3( GtkWidget *box, const char *label )
774 {
775 GtkWidget *txt;
776 GtkWidget *vb;
777
778 txt = gtk_entry_new();
779 vb = build_glabelframe2( txt, label );
780 gtk_box_pack_start( GTK_BOX( box ), vb, FALSE, FALSE, 0 );
781
782 return( txt );
783 }
784
785 /* Make text field plus label .. use a sizegroup for alignment.
786 */
787 GtkWidget *
build_glabeltext4(GtkWidget * box,GtkSizeGroup * group,const char * text)788 build_glabeltext4( GtkWidget *box, GtkSizeGroup *group, const char *text )
789 {
790 GtkWidget *hbox;
791 GtkWidget *label;
792 GtkWidget *entry;
793 char buf[256];
794
795 hbox = gtk_hbox_new( FALSE, 12 );
796 im_snprintf( buf, 256, _( "%s:" ), text );
797 label = gtk_label_new( buf );
798 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
799 if( group )
800 gtk_size_group_add_widget( group, label );
801 gtk_box_pack_start( GTK_BOX( hbox ), label, FALSE, FALSE, 0 );
802
803 entry = gtk_entry_new();
804 gtk_box_pack_start( GTK_BOX( hbox ), entry, TRUE, TRUE, 0 );
805
806 gtk_box_pack_start( GTK_BOX( box ), hbox, FALSE, FALSE, 0 );
807
808 gtk_widget_show_all( hbox );
809
810 return( entry );
811 }
812
813 /* Make a labeled toggle.
814 */
815 GtkWidget *
build_gtoggle(GtkWidget * box,const char * caption)816 build_gtoggle( GtkWidget *box, const char *caption )
817 {
818 GtkWidget *hb;
819 GtkWidget *inv;
820 GtkWidget *toggle;
821
822 /* Indent left a bit.
823 */
824 inv = gtk_label_new( "" );
825 hb = gtk_hbox_new( FALSE, 0 );
826 gtk_box_pack_start( GTK_BOX( hb ), inv, FALSE, FALSE, 2 );
827 toggle = gtk_check_button_new_with_label( caption );
828 gtk_container_set_border_width( GTK_CONTAINER( toggle ), 4 );
829 gtk_box_pack_start( GTK_BOX( hb ), toggle, TRUE, TRUE, 0 );
830
831 gtk_box_pack_start( GTK_BOX( box ), hb, FALSE, FALSE, 0 );
832
833 return( toggle );
834 }
835
836 /* Make a label plus option menu.
837 */
838 GtkWidget *
build_goption(GtkWidget * box,GtkSizeGroup * group,const char * name,const char * item_names[],int nitem,GtkSignalFunc fn,void * value)839 build_goption( GtkWidget *box, GtkSizeGroup *group,
840 const char *name, const char *item_names[], int nitem,
841 GtkSignalFunc fn, void *value )
842 {
843 GtkWidget *hb;
844 GtkWidget *label;
845 GtkWidget *om;
846 int i;
847 char buf[1000];
848
849 hb = gtk_hbox_new( FALSE, 12 );
850 im_snprintf( buf, 1000, _( "%s:" ), name );
851 label = gtk_label_new( buf );
852 if( group )
853 gtk_size_group_add_widget( group, label );
854 gtk_box_pack_start( GTK_BOX( hb ), label, FALSE, TRUE, 0 );
855
856 om = gtk_combo_box_new_text();
857 gtk_box_pack_start( GTK_BOX( hb ), om, FALSE, TRUE, 0 );
858 set_tooltip( om, _( "Left-click to change value" ) );
859
860 for( i = 0; i < nitem; i++ )
861 gtk_combo_box_append_text( GTK_COMBO_BOX( om ),
862 _( item_names[i] ) );
863 if( fn )
864 gtk_signal_connect( GTK_OBJECT( om ), "changed", fn, value );
865 gtk_box_pack_start( GTK_BOX( box ), hb, FALSE, TRUE, 0 );
866 gtk_widget_show_all( hb );
867
868 return( om );
869 }
870
871 /* Register a widget as a filename drag receiver.
872 */
873 typedef struct {
874 GtkWidget *widget;
875 FiledropFunc fn;
876 void *client;
877 } FiledropInfo;
878
879 static gboolean
filedrop_trigger(FiledropInfo * fdi,const char * path)880 filedrop_trigger( FiledropInfo *fdi, const char *path )
881 {
882 char buf[FILENAME_MAX];
883 gboolean result;
884
885 im_strncpy( buf, path, FILENAME_MAX );
886 path_compact( buf );
887 result = fdi->fn( fdi->client, buf );
888
889 return( result );
890 }
891
892 static void
filedrop_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time,FiledropInfo * fdi)893 filedrop_drag_data_received( GtkWidget *widget,
894 GdkDragContext *context,
895 gint x, gint y, GtkSelectionData *data, guint info, guint time,
896 FiledropInfo *fdi )
897 {
898 gchar *sPath = NULL;
899 gchar *pFrom, *pTo;
900 gboolean result;
901
902 pFrom = strstr( (char *) data->data, "file:" );
903
904 while( pFrom ) {
905 #if !GLIB_CHECK_VERSION (2,0,0)
906 pFrom += 5; /* remove 'file:' */
907 #else
908 GError *error = NULL;
909 #endif
910
911 pTo = pFrom;
912 while( *pTo != 0 && *pTo != 0xd && *pTo != 0xa )
913 pTo += 1;
914
915 sPath = g_strndup( pFrom, pTo - pFrom );
916
917 #if !GLIB_CHECK_VERSION (2,0,0)
918 result = filedrop_trigger( fdi, sPath );
919 #else
920 /* format changed with Gtk+1.3, use conversion
921 */
922 pFrom = g_filename_from_uri( sPath, NULL, &error );
923 result = filedrop_trigger( fdi, pFrom );
924 g_free( pFrom );
925 #endif
926
927 g_free( sPath );
928
929 if( !result )
930 iwindow_alert( fdi->widget, GTK_MESSAGE_ERROR );
931
932 pFrom = strstr( pTo, "file:" );
933 }
934
935 gtk_drag_finish( context, TRUE, FALSE, time );
936 }
937
938 /* HB: file dnd stuff lent by The Gimp via Dia, not fully understood
939 * but working ...
940 */
941 enum {
942 TARGET_URI_LIST,
943 TARGET_TEXT_PLAIN
944 };
945
946 static void
filedrop_destroy(GtkWidget * widget,FiledropInfo * fdi)947 filedrop_destroy( GtkWidget *widget, FiledropInfo *fdi )
948 {
949 im_free( fdi );
950 }
951
952 void
filedrop_register(GtkWidget * widget,FiledropFunc fn,void * client)953 filedrop_register( GtkWidget *widget, FiledropFunc fn, void *client )
954 {
955 static GtkTargetEntry target_table[] = {
956 { "text/uri-list", 0, TARGET_URI_LIST },
957 { "text/plain", 0, TARGET_TEXT_PLAIN }
958 };
959
960 FiledropInfo *fdi = INEW( NULL, FiledropInfo );
961
962 fdi->widget = widget;
963 fdi->fn = fn;
964 fdi->client = client;
965 gtk_signal_connect( GTK_OBJECT( widget ), "destroy",
966 GTK_SIGNAL_FUNC( filedrop_destroy ), fdi );
967
968 gtk_drag_dest_set( GTK_WIDGET( widget ),
969 GTK_DEST_DEFAULT_ALL,
970 target_table, IM_NUMBER( target_table ),
971 GDK_ACTION_COPY |
972 /* That's all you need to get draggable URIs in GNOME and
973 * win32, but KDE needs these other flags too, apparently.
974 */
975 GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK );
976 gtk_signal_connect( GTK_OBJECT( widget ), "drag_data_received",
977 GTK_SIGNAL_FUNC( filedrop_drag_data_received ), fdi );
978 }
979
980 /* Add symbol drag to the target list.
981 */
982 void
set_symbol_drag_type(GtkWidget * widget)983 set_symbol_drag_type( GtkWidget *widget )
984 {
985 static const GtkTargetEntry targets[] = {
986 { "text/symbol", 0, TARGET_SYMBOL }
987 };
988
989 GtkTargetList *target_list;
990
991 if( !GTK_WIDGET_REALIZED( widget ) )
992 return;
993
994 /* We can't always set the dest types, since we're probably already a
995 * filedrop. Just add to the target list.
996 */
997 if( (target_list =
998 gtk_drag_dest_get_target_list( widget )) )
999 gtk_target_list_add_table( target_list,
1000 targets, IM_NUMBER( targets ) );
1001 else
1002 gtk_drag_dest_set( widget,
1003 GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_MOTION |
1004 GTK_DEST_DEFAULT_DROP,
1005 targets, IM_NUMBER( targets ),
1006 GDK_ACTION_COPY );
1007
1008 gtk_drag_source_set( widget,
1009 GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
1010 targets, IM_NUMBER( targets ),
1011 GDK_ACTION_COPY | GDK_ACTION_MOVE );
1012 }
1013
1014 typedef struct _Listen {
1015 GObject *gobject; /* This object */
1016 GObject *source; /* Listens for signals from this */
1017 GObject **zap; /* NULL this on destroy */
1018 const char *name; /* Signal name */
1019 GCallback gcallback; /* Call this handler */
1020
1021 guint name_sid;
1022 guint gobject_destroy_sid;
1023 guint source_destroy_sid;
1024 } Listen;
1025
1026 static void
listen_gobject_destroy_cb(GObject * gobject,Listen * listen)1027 listen_gobject_destroy_cb( GObject *gobject, Listen *listen )
1028 {
1029 /* gobject has gone ... source should no longer send us signals.
1030 */
1031 FREESID( listen->name_sid, listen->source );
1032 FREESID( listen->source_destroy_sid, listen->source );
1033
1034 g_free( listen );
1035 }
1036
1037 static void
listen_source_destroy_cb(GObject * gobject,Listen * listen)1038 listen_source_destroy_cb( GObject *gobject, Listen *listen )
1039 {
1040 /* Source has gone, these signals have been destroyed.
1041 */
1042 listen->name_sid = 0;
1043 listen->source_destroy_sid = 0;
1044
1045 /* Link broken, no need to auto-free us on gobject destroy.
1046 */
1047 FREESID( listen->gobject_destroy_sid, listen->gobject );
1048
1049 /* Zap gobject member pointer to source.
1050 */
1051 if( listen->zap ) {
1052 g_assert( !*(listen->zap) ||
1053 *(listen->zap) == listen->source );
1054
1055 *(listen->zap) = NULL;
1056 }
1057
1058 g_free( listen );
1059 }
1060
1061 void
listen_add(GObject * gobject,GObject ** zap,const char * name,GCallback gcallback)1062 listen_add( GObject *gobject, GObject **zap,
1063 const char *name, GCallback gcallback )
1064 {
1065 Listen *listen = g_new( Listen, 1 );
1066
1067 listen->gobject = gobject;
1068 listen->source = *zap;
1069 listen->zap = zap;
1070 listen->name = name;
1071 listen->gcallback = gcallback;
1072
1073 listen->name_sid = g_signal_connect( listen->source,
1074 listen->name, listen->gcallback, listen->gobject );
1075 listen->source_destroy_sid = g_signal_connect( listen->source,
1076 "destroy",
1077 G_CALLBACK( listen_source_destroy_cb ), listen );
1078 listen->gobject_destroy_sid = g_signal_connect( gobject, "destroy",
1079 G_CALLBACK( listen_gobject_destroy_cb ), listen );
1080 }
1081
1082 void
widget_update_pointer(GtkWidget * widget,GdkEvent * ev)1083 widget_update_pointer( GtkWidget *widget, GdkEvent *ev )
1084 {
1085 if( ev->type == GDK_MOTION_NOTIFY && ev->motion.is_hint ) {
1086 GdkDisplay *display = gtk_widget_get_display( widget );
1087 GdkScreen *screen;
1088 int x_root, y_root;
1089
1090 gdk_display_get_pointer( display,
1091 &screen, &x_root, &y_root, NULL );
1092 ev->motion.x_root = x_root;
1093 ev->motion.y_root = y_root;
1094 }
1095 }
1096
1097 void *
gobject_print(GObject * gobject)1098 gobject_print( GObject *gobject )
1099 {
1100 printf( "%s (%p)\n", G_OBJECT_TYPE_NAME( gobject ), gobject );
1101
1102 return( NULL );
1103 }
1104
1105 /* Get the default DPI.
1106 */
1107 int
get_dpi(void)1108 get_dpi( void )
1109 {
1110 GdkScreen *screen = gdk_screen_get_default();
1111
1112 if( screen ) {
1113 int width_pixels = gdk_screen_get_width( screen );
1114 int width_mm = gdk_screen_get_width_mm( screen );
1115
1116 return( width_pixels / (width_mm / 25.4) );
1117 }
1118 else
1119 return( 72 );
1120 }
1121
1122 GtkWidget *
image_new_from_file(const char * name)1123 image_new_from_file( const char *name )
1124 {
1125 GtkWidget *image;
1126 char *file;
1127
1128 if( (file = path_find_file( name )) ) {
1129 image = (GtkWidget *) callv_string_filename(
1130 (callv_string_fn) gtk_image_new_from_file,
1131 file, NULL, NULL, NULL );
1132 im_free( file );
1133 }
1134 else
1135 /* We get a broken image icon if this fails.
1136 */
1137 image = gtk_image_new_from_file( name );
1138
1139 return( image );
1140 }
1141
1142 void
vfatal(GError ** error)1143 vfatal( GError **error )
1144 {
1145 fprintf( stderr, PACKAGE ": fatal error\n" );
1146
1147 if( *error ) {
1148 fprintf( stderr, "%s\n", (*error)->message );
1149 IM_FREEF( g_error_free, *error );
1150 }
1151
1152 exit( -1 );
1153 }
1154
1155 char *
text_view_get_text(GtkTextView * text_view)1156 text_view_get_text( GtkTextView *text_view )
1157 {
1158 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( text_view );
1159 GtkTextIter start_iter;
1160 GtkTextIter end_iter;
1161 char *text;
1162
1163 gtk_text_buffer_get_start_iter( text_buffer, &start_iter );
1164 gtk_text_buffer_get_end_iter( text_buffer, &end_iter );
1165 text = gtk_text_buffer_get_text( text_buffer,
1166 &start_iter, &end_iter, FALSE );
1167
1168 return( text );
1169 }
1170
1171 void
text_view_set_text(GtkTextView * text_view,const char * text,gboolean editable)1172 text_view_set_text( GtkTextView *text_view,
1173 const char *text, gboolean editable )
1174 {
1175 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( text_view );
1176
1177 gtk_text_buffer_set_text( text_buffer, text ? text : "", -1 );
1178
1179 gtk_text_view_set_editable( text_view, editable );
1180 gtk_text_view_set_cursor_visible( text_view, editable );
1181 }
1182
1183 void
text_view_select_text(GtkTextView * text_view,int start,int end)1184 text_view_select_text( GtkTextView *text_view, int start, int end )
1185 {
1186 GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( text_view );
1187 GtkTextMark *mark = gtk_text_buffer_get_insert( text_buffer );
1188 GtkTextIter start_iter;
1189 GtkTextIter end_iter;
1190
1191 gtk_text_buffer_get_iter_at_offset( text_buffer, &start_iter, start );
1192 gtk_text_buffer_get_iter_at_offset( text_buffer, &end_iter, end );
1193 gtk_text_buffer_select_range( text_buffer, &start_iter, &end_iter );
1194 gtk_text_view_scroll_mark_onscreen( text_view, mark );
1195 }
1196
1197 /* If parent dies, kill us too. Parent can be anything, but child must be an
1198 * iobject.
1199 */
1200 typedef struct _DestroyIfDestroyed {
1201 GObject *child;
1202 GObject *parent;
1203 DestroyFn destroy_fn;
1204 } DestroyIfDestroyed;
1205
1206 static void destroy_if_destroyed_parent_cb( DestroyIfDestroyed *difd,
1207 GObject *parent );
1208 static void destroy_if_destroyed_child_cb( DestroyIfDestroyed *difd,
1209 GObject *child );
1210
1211 static void
destroy_if_destroyed_parent_cb(DestroyIfDestroyed * difd,GObject * parent)1212 destroy_if_destroyed_parent_cb( DestroyIfDestroyed *difd, GObject *parent )
1213 {
1214 GObject *child;
1215 DestroyFn destroy_fn;
1216
1217 #ifdef DEBUG
1218 printf( "destroy_if_destroyed_parent_cb: %p\n", difd );
1219 #endif /*DEBUG*/
1220
1221 /* Destroying the child will trigger the other half of difd, make sure
1222 * we remove the link first.
1223 */
1224 child = difd->child;
1225 destroy_fn = difd->destroy_fn;
1226 g_object_weak_unref( difd->child,
1227 (GWeakNotify) destroy_if_destroyed_child_cb, difd );
1228 destroy_fn( child );
1229
1230 difd->child = NULL;
1231 difd->parent = NULL;
1232 difd->destroy_fn = NULL;
1233 g_free( difd );
1234 }
1235
1236 static void
destroy_if_destroyed_child_cb(DestroyIfDestroyed * difd,GObject * child)1237 destroy_if_destroyed_child_cb( DestroyIfDestroyed *difd, GObject *child )
1238 {
1239 #ifdef DEBUG
1240 printf( "destroy_if_destroyed_child_cb: %p\n", difd );
1241 #endif /*DEBUG*/
1242
1243 g_object_weak_unref( difd->parent,
1244 (GWeakNotify) destroy_if_destroyed_parent_cb, difd );
1245
1246 difd->child = NULL;
1247 difd->parent = NULL;
1248 difd->destroy_fn = NULL;
1249 g_free( difd );
1250 }
1251
1252 void
destroy_if_destroyed(GObject * child,GObject * parent,DestroyFn destroy_fn)1253 destroy_if_destroyed( GObject *child, GObject *parent, DestroyFn destroy_fn )
1254 {
1255 DestroyIfDestroyed *difd = g_new( DestroyIfDestroyed, 1 );
1256
1257 #ifdef DEBUG
1258 printf( "destroy_if_destroyed %p: parent=%p, child=%p\n",
1259 difd, parent, child );
1260 #endif /*DEBUG*/
1261
1262 difd->child = child;
1263 difd->parent = parent;
1264 difd->destroy_fn = destroy_fn;
1265
1266 g_object_weak_ref( parent,
1267 (GWeakNotify) destroy_if_destroyed_parent_cb, difd );
1268 g_object_weak_ref( child,
1269 (GWeakNotify) destroy_if_destroyed_child_cb, difd );
1270 }
1271
1272 /* A 'safe' way to run a few events.
1273 */
1274 void
process_events(void)1275 process_events( void )
1276 {
1277 /* Max events we process before signalling a timeout. Without this we
1278 * can get stuck in event loops in some circumstances.
1279 */
1280 static const int max_events = 100;
1281
1282 /* Block too much recursion. 0 is from the top-level, 1 is from a
1283 * callback, we don't want any more than that.
1284 */
1285 if( g_main_depth() < 2 ) {
1286 int n;
1287
1288 #ifdef DEBUG
1289 printf( "progress_update: starting event dispatch\n" );
1290 #endif /*DEBUG*/
1291
1292 for( n = 0; n < max_events &&
1293 g_main_context_iteration( NULL, FALSE ); n++ )
1294 ;
1295
1296 #ifdef DEBUG
1297 printf( "progress_update: event dispatch done\n" );
1298 if( n == max_events )
1299 printf( "progress_update: event dispatch timeout\n" );
1300 #endif /*DEBUG*/
1301 }
1302 }
1303