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