1 /* make and manage base windows ... dialog (messagebox, file box), top
2  * level windows
3  */
4 
5 /*
6 
7     Copyright (C) 1991-2001, 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 
33     build interface:
34 
35 	iwnd = iwindow_new( type );
36 	iwindow_set_*( iwnd, ... );
37 	iwindow_build( iwnd );
38 
39     destroy interface:
40 
41     	iwindow_kill()
42 
43 		'cancellable' kill ... user popdown can return IWINDOW_ERROR
44 		or IWINDOW_NO to prevent popdown
45 
46 	gtk_widget_destroy()
47 
48 		non-cancellable ... popdown is not called
49 
50 	so ... don't free() in popdown, subclass iwnd and free() in _destroy()
51 
52  */
53 
54 /*
55 #define DEBUG
56  */
57 
58 #include "ip.h"
59 
60 /* Cursor bitmaps.
61  */
62 #include "BITMAPS/dropper_src.xbm"
63 #include "BITMAPS/dropper_msk.xbm"
64 #include "BITMAPS/magin_src.xbm"
65 #include "BITMAPS/magout_src.xbm"
66 #include "BITMAPS/mag_msk.xbm"
67 #include "BITMAPS/watch_1.xbm"
68 #include "BITMAPS/watch_2.xbm"
69 #include "BITMAPS/watch_3.xbm"
70 #include "BITMAPS/watch_4.xbm"
71 #include "BITMAPS/watch_5.xbm"
72 #include "BITMAPS/watch_6.xbm"
73 #include "BITMAPS/watch_7.xbm"
74 #include "BITMAPS/watch_8.xbm"
75 #include "BITMAPS/watch_msk.xbm"
76 
77 static GtkWindowClass *parent_class = NULL;
78 
79 /* List of all iwindows.
80  */
81 static GSList *iwindow_all = NULL;
82 
83 /* All our cursors.
84  */
85 static GdkCursor *iwindow_cursor[IWINDOW_SHAPE_LAST] = { NULL };
86 
87 #ifdef DEBUG
88 /* Human-readable names for cursor shapes.
89  */
90 static const char *iwindow_cursor_name[] = {
91 	"IWINDOW_SHAPE_DROPPER",
92 	"IWINDOW_SHAPE_PEN",
93 	"IWINDOW_SHAPE_SMUDGE",
94 	"IWINDOW_SHAPE_SMEAR",
95 	"IWINDOW_SHAPE_TEXT",
96 	"IWINDOW_SHAPE_RECT",
97 	"IWINDOW_SHAPE_FLOOD",
98 	"IWINDOW_SHAPE_MOVE",
99 	"IWINDOW_SHAPE_EDIT",
100 	"IWINDOW_SHAPE_MAGIN",
101 	"IWINDOW_SHAPE_MAGOUT",
102 	"IWINDOW_SHAPE_TOP",
103 	"IWINDOW_SHAPE_BOTTOM",
104 	"IWINDOW_SHAPE_LEFT",
105 	"IWINDOW_SHAPE_RIGHT",
106 	"IWINDOW_SHAPE_TOPRIGHT",
107 	"IWINDOW_SHAPE_TOPLEFT",
108 	"IWINDOW_SHAPE_BOTTOMRIGHT",
109 	"IWINDOW_SHAPE_BOTTOMLEFT",
110 	"IWINDOW_SHAPE_HGLASS1",
111 	"IWINDOW_SHAPE_HGLASS2",
112 	"IWINDOW_SHAPE_HGLASS3",
113 	"IWINDOW_SHAPE_HGLASS4",
114 	"IWINDOW_SHAPE_HGLASS5",
115 	"IWINDOW_SHAPE_HGLASS6",
116 	"IWINDOW_SHAPE_HGLASS7",
117 	"IWINDOW_SHAPE_HGLASS8",
118 	"IWINDOW_SHAPE_NONE"
119 };
120 #endif /*DEBUG*/
121 
122 int
iwindow_number(void)123 iwindow_number( void )
124 {
125 	return( g_slist_length( iwindow_all ) );
126 }
127 
128 /* Pick an iwindow at random. Used if we need a window for a dialog, and we're
129  * not sure which to pick. During shutdown we can have no windows.
130  */
131 iWindow *
iwindow_pick_one(void)132 iwindow_pick_one( void )
133 {
134 	if( !iwindow_all )
135 		return( NULL );
136 
137 	return( IWINDOW( iwindow_all->data ) );
138 }
139 
140 /* Over all windows.
141  */
142 void *
iwindow_map_all(iWindowMapFn fn,void * a)143 iwindow_map_all( iWindowMapFn fn, void *a )
144 {
145 	return( slist_map( iwindow_all, (SListMapFn) fn, a ) );
146 }
147 
148 /* Make a custom cursor ... source, mask, width, height and hot spot position.
149  */
150 static GdkCursor *
iwindow_make_cursor_data(guchar * src_bits,guchar * msk_bits,int w,int h,int x,int y)151 iwindow_make_cursor_data( guchar *src_bits, guchar *msk_bits,
152 	int w, int h, int x, int y )
153 {
154 	GdkPixmap *src;
155 	GdkPixmap *msk;
156 	GdkCursor *cursor;
157 	GdkColor fg = { 0, 255 << 8, 255 << 8, 255 << 8 };
158 	GdkColor bg = { 0, 0, 0, 0 };
159 
160 	src = gdk_bitmap_create_from_data( NULL,
161 		(const char *) src_bits, w, h );
162 	msk = gdk_bitmap_create_from_data( NULL,
163 		(const char *) msk_bits, w, h );
164 	cursor = gdk_cursor_new_from_pixmap( src, msk, &fg, &bg, x, y );
165 	gdk_pixmap_unref( src );
166 	gdk_pixmap_unref( msk );
167 
168 	return( cursor );
169 }
170 
171 /* Build all the cursors.
172  */
173 static void
iwindow_make_cursors(void)174 iwindow_make_cursors( void )
175 {
176 	/* Init standard cursors with this table.
177 	 */
178 	static GdkCursorType standards[] = {
179 		GDK_CURSOR_IS_PIXMAP,	/* IWINDOW_SHAPE_DROPPER */
180 		GDK_PENCIL,		/* IWINDOW_SHAPE_PEN */
181 		GDK_HAND2,		/* IWINDOW_SHAPE_SMUDGE */
182 		GDK_SPIDER,		/* IWINDOW_SHAPE_SMEAR */
183 		GDK_GOBBLER,		/* IWINDOW_SHAPE_TEXT */
184 		GDK_SIZING,		/* IWINDOW_SHAPE_RECT */
185 		GDK_TREK,		/* IWINDOW_SHAPE_FLOOD */
186 		GDK_FLEUR,		/* IWINDOW_SHAPE_MOVE */
187 		GDK_CROSSHAIR,		/* IWINDOW_SHAPE_EDIT */
188 		GDK_CURSOR_IS_PIXMAP, 	/* IWINDOW_SHAPE_MAGIN */
189 		GDK_CURSOR_IS_PIXMAP, 	/* IWINDOW_SHAPE_MAGOUT */
190 		GDK_TOP_SIDE,		/* IWINDOW_SHAPE_TOP */
191 		GDK_BOTTOM_SIDE,	/* IWINDOW_SHAPE_BOTTOM */
192 		GDK_LEFT_SIDE,		/* IWINDOW_SHAPE_LEFT */
193 		GDK_RIGHT_SIDE,		/* IWINDOW_SHAPE_RIGHT */
194 		GDK_TOP_RIGHT_CORNER,	/* IWINDOW_SHAPE_TOPRIGHT */
195 		GDK_TOP_LEFT_CORNER,	/* IWINDOW_SHAPE_TOPLEFT */
196 		GDK_BOTTOM_RIGHT_CORNER,/* IWINDOW_SHAPE_BOTTOMRIGHT, */
197 		GDK_BOTTOM_LEFT_CORNER,	/* IWINDOW_SHAPE_BOTTOMLEFT */
198 	};
199 
200 	/* All the bits for the rotating cursor.
201 	 */
202 	static guchar *watch_bits[] = {
203 		watch_1_bits,
204 		watch_2_bits,
205 		watch_3_bits,
206 		watch_4_bits,
207 		watch_5_bits,
208 		watch_6_bits,
209 		watch_7_bits,
210 		watch_8_bits,
211 	};
212 
213 	int i;
214 
215 	if( iwindow_cursor[0] )
216 		return;
217 
218 	/* Easy ones first.
219 	 */
220 	for( i = 0; i < IM_NUMBER( standards ); i++ )
221 		if( standards[i] != GDK_CURSOR_IS_PIXMAP )
222 			iwindow_cursor[i] = gdk_cursor_new( standards[i] );
223 
224 	/* Custom cursors.
225 	 */
226 	iwindow_cursor[IWINDOW_SHAPE_DROPPER] = iwindow_make_cursor_data(
227 		dropper_src_bits, dropper_msk_bits,
228 		dropper_src_width, dropper_src_height, 0, 15 );
229 	iwindow_cursor[IWINDOW_SHAPE_MAGIN] = iwindow_make_cursor_data(
230 		magin_src_bits, mag_msk_bits,
231 		mag_msk_width, mag_msk_height, 6, 6 );
232 	iwindow_cursor[IWINDOW_SHAPE_MAGOUT] = iwindow_make_cursor_data(
233 		magout_src_bits, mag_msk_bits,
234 		mag_msk_width, mag_msk_height, 6, 6 );
235 
236 	/* The hglasses.
237 	 */
238 	for( i = 0; i < IM_NUMBER( watch_bits ); i++ )
239 		iwindow_cursor[IWINDOW_SHAPE_HGLASS1 + i] =
240 			iwindow_make_cursor_data(
241 				watch_bits[i], watch_msk_bits,
242 				watch_1_width, watch_1_height, 7, 7 );
243 }
244 
245 /* Get the work window.
246  */
247 static GdkWindow *
iwindow_get_work_window(iWindow * iwnd)248 iwindow_get_work_window( iWindow *iwnd )
249 {
250 	if( iwnd->work_window )
251 		return( iwnd->work_window );
252 	else
253 		return( GTK_WIDGET( iwnd )->window );
254 }
255 
256 /* Update the cursor for a window.
257  */
258 static void *
iwindow_cursor_update(iWindow * iwnd)259 iwindow_cursor_update( iWindow *iwnd )
260 {
261 	if( GTK_WIDGET_REALIZED( GTK_WIDGET( iwnd ) ) ) {
262 		GSList *p;
263 		iWindowShape best_shape;
264 		int best_priority;
265 
266 		/* Global shape set? Use that for the whole window.
267 		 */
268 		if( iwnd->shape != IWINDOW_SHAPE_NONE ) {
269 			gdk_window_set_cursor( GTK_WIDGET( iwnd )->window,
270 				iwindow_cursor[iwnd->shape] );
271 			gdk_window_set_cursor( iwindow_get_work_window( iwnd ),
272 				iwindow_cursor[iwnd->shape] );
273 			gdk_flush();
274 
275 			return( NULL );
276 		}
277 
278 		/* No global shape ... make sure there's no global cursor on
279 		 * this window.
280 		 */
281 		gdk_window_set_cursor( GTK_WIDGET( iwnd )->window, NULL );
282 		gdk_window_set_cursor( iwindow_get_work_window( iwnd ), NULL );
283 
284 		/* And set the work area to the highest priority non-NONE
285 		 * shape we can find .
286 
287 			FIXME ... could avoid the search if we sorted the
288 			context list by priority on each context_new(),
289 			but not very important.
290 
291 		 */
292 		best_shape = IWINDOW_SHAPE_NONE;
293 		best_priority = -1;
294 		for( p = iwnd->contexts; p; p = p->next ) {
295 			iWindowCursorContext *cntxt =
296 				(iWindowCursorContext *) p->data;
297 
298 			if( cntxt->shape != IWINDOW_SHAPE_NONE &&
299 				cntxt->priority > best_priority ) {
300 				best_shape = cntxt->shape;
301 				best_priority = cntxt->priority;
302 			}
303 		}
304 
305 		/* Pref to disable crosshair.
306 		 */
307 		if( best_shape == IWINDOW_SHAPE_EDIT && !DISPLAY_CROSSHAIR )
308 			best_shape = IWINDOW_SHAPE_NONE;
309 
310 		gdk_window_set_cursor(
311 			iwindow_get_work_window( iwnd ),
312 			iwindow_cursor[best_shape] );
313 		gdk_flush();
314 	}
315 
316 	return( NULL );
317 }
318 
319 /* Set a global cursor for a window.
320  */
321 static void *
iwindow_cursor_set(iWindow * iwnd,iWindowShape * shape)322 iwindow_cursor_set( iWindow *iwnd, iWindowShape *shape )
323 {
324 	if( iwnd->shape != *shape ) {
325 		iwnd->shape = *shape;
326 		iwindow_cursor_update( iwnd );
327 	}
328 
329 	return( NULL );
330 }
331 
332 static gboolean hourglass_showing = FALSE;
333 
334 static void
hourglass_begin(void)335 hourglass_begin( void )
336 {
337 	hourglass_showing = TRUE;
338 }
339 
340 static void
hourglass_update(void)341 hourglass_update( void )
342 {
343 	if( hourglass_showing ) {
344 		static iWindowShape shape = IWINDOW_SHAPE_HGLASS1;
345 
346 		iwindow_map_all( (iWindowMapFn) iwindow_cursor_set, &shape );
347 
348 		shape += 1;
349 		if( shape > IWINDOW_SHAPE_HGLASS8 )
350 			shape = IWINDOW_SHAPE_HGLASS1;
351 	}
352 }
353 
354 static void
hourglass_end(void)355 hourglass_end( void )
356 {
357 	if( hourglass_showing ) {
358 		iWindowShape shape = IWINDOW_SHAPE_NONE;
359 
360 		iwindow_map_all( (iWindowMapFn) iwindow_cursor_set, &shape );
361 		hourglass_showing = FALSE;
362 	}
363 }
364 
365 iWindowCursorContext *
iwindow_cursor_context_new(iWindow * iwnd,int priority,const char * name)366 iwindow_cursor_context_new( iWindow *iwnd, int priority, const char *name )
367 {
368 	iWindowCursorContext *cntxt = INEW( NULL, iWindowCursorContext );
369 
370 #ifdef DEBUG
371 	printf( "iwindow_cursor_context_new: %s\n", name );
372 #endif /*DEBUG*/
373 
374 	cntxt->iwnd = iwnd;
375 	cntxt->priority = priority;
376 	cntxt->name = name;
377 	cntxt->shape = IWINDOW_SHAPE_NONE;
378 	iwnd->contexts = g_slist_prepend( iwnd->contexts, cntxt );
379 
380 	return( cntxt );
381 }
382 
383 void
iwindow_cursor_context_destroy(iWindowCursorContext * cntxt)384 iwindow_cursor_context_destroy( iWindowCursorContext *cntxt )
385 {
386 	iWindow *iwnd = cntxt->iwnd;
387 
388 	iwnd->contexts = g_slist_remove( iwnd->contexts, cntxt );
389 	IM_FREE( cntxt );
390 	iwindow_cursor_update( iwnd );
391 }
392 
393 void
iwindow_cursor_context_set_cursor(iWindowCursorContext * cntxt,iWindowShape shape)394 iwindow_cursor_context_set_cursor( iWindowCursorContext *cntxt,
395 	iWindowShape shape )
396 {
397 	if( cntxt->shape != shape ) {
398 #ifdef DEBUG
399 		printf( "iwindow_cursor_context_set_cursor: %s = %s\n",
400 			cntxt->name, iwindow_cursor_name[shape] );
401 #endif /*DEBUG*/
402 
403 		cntxt->shape = shape;
404 		iwindow_cursor_update( cntxt->iwnd );
405 	}
406 }
407 
408 iWindowSusp *
iwindow_susp_new(iWindowFn fn,iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)409 iwindow_susp_new( iWindowFn fn,
410 	iWindow *iwnd, void *client, iWindowNotifyFn nfn, void *sys )
411 {
412         iWindowSusp *susp;
413 
414         if( !(susp = INEW( NULL, iWindowSusp )) )
415 		return( NULL );
416 
417         susp->fn = fn;
418         susp->iwnd = iwnd;
419         susp->client = client;
420         susp->nfn = nfn;
421         susp->sys = sys;
422 
423         return( susp );
424 }
425 
426 /* Trigger a suspension's reply, and free it.
427  */
428 void
iwindow_susp_return(void * sys,iWindowResult result)429 iwindow_susp_return( void *sys, iWindowResult result )
430 {
431 	iWindowSusp *susp = IWINDOW_SUSP( sys );
432 
433         susp->nfn( susp->sys, result );
434 
435         im_free( susp );
436 }
437 
438 void
iwindow_susp_trigger(iWindowSusp * susp)439 iwindow_susp_trigger( iWindowSusp *susp )
440 {
441 	susp->fn( susp->iwnd, susp->client, susp->nfn, susp->sys );
442 	im_free( susp );
443 }
444 
445 /* Compose two iWindowFns ... if this one succeeded, trigger the next in turn.
446  * Otherwise bail out.
447  */
448 void
iwindow_susp_comp(void * sys,iWindowResult result)449 iwindow_susp_comp( void *sys, iWindowResult result )
450 {
451 	iWindowSusp *susp = IWINDOW_SUSP( sys );
452 
453 	if( result == IWINDOW_YES )
454 		iwindow_susp_trigger( susp );
455 	else
456 		iwindow_susp_return( sys, result );
457 }
458 
459 /* Null window callback.
460  */
461 void
iwindow_true_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)462 iwindow_true_cb( iWindow *iwnd, void *client, iWindowNotifyFn nfn, void *sys )
463 {
464 	nfn( sys, IWINDOW_YES );
465 }
466 
467 void
iwindow_false_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)468 iwindow_false_cb( iWindow *iwnd, void *client, iWindowNotifyFn nfn, void *sys )
469 {
470 	nfn( sys, IWINDOW_NO );
471 }
472 
473 /* Null notify callback.
474  */
iwindow_notify_null(void * client,iWindowResult result)475 void iwindow_notify_null( void *client, iWindowResult result ) { }
476 
477 /* Final end of a window. Destroy!
478  */
479 static void
iwindow_final_death(iWindow * iwnd)480 iwindow_final_death( iWindow *iwnd )
481 {
482 #ifdef DEBUG
483 	printf( "iwindow_final_death: %s\n", iwnd->title );
484 #endif /*DEBUG*/
485 
486 	g_assert( iwnd->pending == 0 && iwnd->destroy );
487 
488 	/* Clean up.
489 	 */
490 	gtk_widget_destroy( GTK_WIDGET( iwnd ) );
491 }
492 
493 /* A notify comes back ... adjust the pending count. If this is a zombie and
494  * this is the final pending, it's final death.
495  */
496 void
iwindow_notify_return(iWindow * iwnd)497 iwindow_notify_return( iWindow *iwnd )
498 {
499 #ifdef DEBUG
500 	printf( "iwindow_notify_return: %s (pending = %d)\n",
501 		iwnd->title, iwnd->pending );
502 #endif /*DEBUG*/
503 
504 	g_assert( iwnd->pending > 0 );
505 
506 	iwnd->pending--;
507 	if( iwnd->destroy && iwnd->pending == 0 ) {
508 #ifdef DEBUG
509 		printf( "iwindow_notify_return: zombie death %s\n",
510 			iwnd->title );
511 #endif /*DEBUG*/
512 		iwindow_final_death( iwnd );
513 	}
514 }
515 
516 /* Send a notify off, tell the client to come back to back.
517  */
518 void
iwindow_notify_send(iWindow * iwnd,iWindowFn fn,void * client,iWindowNotifyFn back,void * sys)519 iwindow_notify_send( iWindow *iwnd,
520 	iWindowFn fn, void *client, iWindowNotifyFn back, void *sys )
521 {
522 #ifdef DEBUG
523 	printf( "iwindow_notify_send: %s (pending = %d)\n",
524 		iwnd->title, iwnd->pending );
525 #endif /*DEBUG*/
526 
527 	iwnd->pending++;
528 	if( fn )
529 		fn( iwnd, client, back, sys );
530 	else
531 		back( sys, IWINDOW_YES );
532 }
533 
534 static void
iwindow_finalize(GObject * gobject)535 iwindow_finalize( GObject *gobject )
536 {
537 	iWindow *iwnd = IWINDOW( gobject );
538 
539 #ifdef DEBUG
540 	printf( "iwindow_finalize: %s\n", iwnd->title );
541 #endif /*DEBUG*/
542 
543 	/* My instance destroy stuff.
544 	 */
545 	iwindow_all = g_slist_remove( iwindow_all, iwnd );
546 	IM_FREE( iwnd->title );
547 
548 	G_OBJECT_CLASS( parent_class )->finalize( gobject );
549 
550 	/* Last window and we've got through startup? Quit the application.
551 	 */
552 	if( iwindow_number() == 0 &&
553 		!main_starting )
554 		main_quit_test();
555 }
556 
557 static void
iwindow_destroy(GtkObject * gobject)558 iwindow_destroy( GtkObject *gobject )
559 {
560 	iWindow *iwnd = IWINDOW( gobject );
561 
562 #ifdef DEBUG
563 	printf( "iwindow_destroy: %s\n", iwnd->title );
564 #endif /*DEBUG*/
565 
566 	/* My instance destroy stuff.
567 	 */
568 	FREESID( iwnd->parent_unmap_sid, iwnd->parent_window );
569 	UNREF( iwnd->action_group );
570 	UNREF( iwnd->ui_manager );
571 
572 	/* Now we've destroyed, we must stop popdown from being called, since
573 	 * the view will have junked a lot of stuff.
574 	 */
575 	iwnd->destroy = TRUE;
576 
577 	GTK_OBJECT_CLASS( parent_class )->destroy( gobject );
578 }
579 
580 static void
iwindow_popdown_notify(iWindow * iwnd,iWindowResult result)581 iwindow_popdown_notify( iWindow *iwnd, iWindowResult result )
582 {
583 #ifdef DEBUG
584 	printf( "iwindow_popdown_notify: %p %s\n", iwnd, iwnd->title );
585 #endif /*DEBUG*/
586 
587 	if( result == IWINDOW_ERROR )
588 		iwindow_alert( GTK_WIDGET( iwnd ), GTK_MESSAGE_ERROR );
589 	else if( result == IWINDOW_YES )
590 		iwindow_kill( iwnd );
591 
592 	if( result != IWINDOW_YES ) {
593 #ifdef DEBUG
594 		printf( "iwindow_popdown_notify: %s: kill cancelled!\n",
595 			iwnd->title );
596 #endif /*DEBUG*/
597 
598 		/* Cancel popdown.
599 		 */
600 		iwnd->destroy = FALSE;
601 	}
602 	else {
603 		/* Popdown confirmed! Trigger class popdown. _real_popdown()
604 		 * does an unmap to hide the window during the rest of the
605 		 * destroy.
606 		 */
607 		IWINDOW_GET_CLASS( iwnd )->popdown( GTK_WIDGET( iwnd ) );
608 	}
609 
610 	calli_string_filenamef( (calli_string_fn) gtk_accel_map_save,
611 		"%s" G_DIR_SEPARATOR_S "accel_map", get_savedir() );
612 
613 	/* If this is the final pending response and ->destroy is true, this
614 	 * will destroy the window.
615 	 */
616 	iwindow_notify_return( iwnd );
617 }
618 
619 static gboolean
iwindow_delete_event(GtkWidget * widget,GdkEventAny * event)620 iwindow_delete_event( GtkWidget *widget, GdkEventAny *event )
621 {
622 	iWindow *iwnd = IWINDOW( widget );
623 
624 #ifdef DEBUG
625 	printf( "iwindow_delete_event: %s\n", iwnd->title );
626 #endif /*DEBUG*/
627 
628 	if( !iwnd->destroy ) {
629 #ifdef DEBUG
630 		printf( "iwindow_delete_event: starting destroy\n" );
631 #endif /*DEBUG*/
632 
633 		iwindow_kill( iwnd );
634 	}
635 
636 	/* Never delete here ... wait for iwindow_popdown_notify to
637 	 * confirm the kill.
638 	 */
639 	return( TRUE );
640 }
641 
642 static gboolean
iwindow_configure_event(GtkWidget * widget,GdkEventConfigure * event)643 iwindow_configure_event( GtkWidget *widget, GdkEventConfigure *event )
644 {
645 	iWindow *iwnd = IWINDOW( widget );
646 
647 	if( iwnd->width_pref ) {
648 		/* Save window size in global prefs.
649 		 */
650 		prefs_set( iwnd->width_pref, "%d", event->width );
651 		prefs_set( iwnd->height_pref, "%d", event->height );
652 	}
653 
654 	return( GTK_WIDGET_CLASS( parent_class )->
655 		configure_event( widget, event ) );
656 }
657 
658 /* Our parent has been destroyed, kill us too.
659  */
660 static void
iwindow_parent_unmap_cb(GtkWidget * par,iWindow * iwnd)661 iwindow_parent_unmap_cb( GtkWidget *par, iWindow *iwnd )
662 {
663 #ifdef DEBUG
664 	printf( "iwindow_parent_unmap_cb: %s\n", iwnd->title );
665 #endif /*DEBUG*/
666 
667 	/* Here for dead parent ... if parent is dead, we won't need to remove
668 	 * the dead-dad signal.
669 	 */
670 	iwnd->parent_unmap_sid = 0;
671 
672 	iwindow_kill( iwnd );
673 }
674 
675 static GtkActionEntry iwnd_actions[] = {
676 	/* Common menus.
677 	 */
678 	{ "FileMenu", NULL, N_( "_File" ) },
679 	{ "NewMenu", NULL, N_( "_New" ) },
680 	{ "EditMenu", NULL, N_( "_Edit" ) },
681 	{ "ViewMenu", NULL, N_( "_View" ) },
682 	{ "HelpMenu", NULL, N_( "_Help" ) },
683 
684 	/* Common items.
685 	 */
686 	{ "Close",
687 		GTK_STOCK_CLOSE, N_( "_Close" ), NULL,
688 		N_( "Close" ),
689 		G_CALLBACK( iwindow_kill_action_cb ) },
690 
691 	{ "Quit",
692 		GTK_STOCK_QUIT, N_( "_Quit" ), "<control>q",
693 		N_( "Quit nip2" ),
694 		G_CALLBACK( main_quit_test ) },
695 	{ "Guide",
696 		GTK_STOCK_HELP, N_( "_Contents" ), "F1",
697 		N_( "Open the users guide" ),
698 		G_CALLBACK( mainw_guide_action_cb ) },
699 
700 	{ "About",
701 		NULL, N_( "_About" ), NULL,
702 		N_( "About this program" ),
703 		G_CALLBACK( mainw_about_action_cb ) },
704 
705 	{ "Homepage",
706 		NULL, N_( "_Website" ), NULL,
707 		N_( "Open the VIPS Homepage" ),
708 		G_CALLBACK( mainw_homepage_action_cb ) }
709 };
710 
711 static void
iwindow_real_build(GtkWidget * widget)712 iwindow_real_build( GtkWidget *widget )
713 {
714 	iWindow *iwnd = IWINDOW( widget );
715 	GdkScreen *screen = gtk_widget_get_screen( GTK_WIDGET( iwnd ) );
716 
717 	GtkAccelGroup *accel_group;
718 
719 #ifdef DEBUG
720 	printf( "iwindow_real_build: %s\n", iwnd->title );
721 #endif /*DEBUG*/
722 
723         gtk_container_set_border_width( GTK_CONTAINER( iwnd ), 0 );
724 
725         iwnd->work = gtk_vbox_new( FALSE, 0 );
726         gtk_container_add( GTK_CONTAINER( iwnd ), iwnd->work );
727 
728 	/* Use the type name (eg. "Imageview") for the name of the
729 	 * actiongroup.
730 	 */
731 	iwnd->action_group = gtk_action_group_new( G_OBJECT_TYPE_NAME( iwnd ) );
732 	gtk_action_group_set_translation_domain( iwnd->action_group,
733 		GETTEXT_PACKAGE );
734 	gtk_action_group_add_actions( iwnd->action_group,
735 		iwnd_actions, G_N_ELEMENTS( iwnd_actions ),
736 		GTK_WINDOW( iwnd ) );
737 
738 	iwnd->ui_manager = gtk_ui_manager_new();
739 	gtk_ui_manager_insert_action_group( iwnd->ui_manager,
740 		iwnd->action_group, 0 );
741 
742 	accel_group = gtk_ui_manager_get_accel_group( iwnd->ui_manager );
743 	gtk_window_add_accel_group( GTK_WINDOW( iwnd ), accel_group );
744 
745 	/* Call per-instance build.
746 	 */
747         if( iwnd->build )
748 		iwnd->build( iwnd, iwnd->work,
749 			iwnd->build_a, iwnd->build_b, iwnd->build_c );
750 
751 	if( iwnd->title )
752 		gtk_window_set_title( GTK_WINDOW( iwnd ), iwnd->title );
753 
754 	if( iwnd->width_pref ) {
755 		int width = watch_int_get( main_watchgroup,
756 			iwnd->width_pref, 640 );
757 		int height = watch_int_get( main_watchgroup,
758 			iwnd->height_pref, 480 );
759 
760 		gtk_window_set_default_size( GTK_WINDOW( iwnd ),
761 			IM_MIN( width, gdk_screen_get_width( screen ) ),
762 			IM_MIN( height, gdk_screen_get_height( screen ) ) );
763 	}
764 
765 	/* Link to parent.
766 	 */
767         if( iwnd->parent_window ) {
768 		if( IWINDOW_GET_CLASS( iwnd )->transient &&
769 			iwnd->parent_window &&
770 			iwnd != iwnd->parent_window )
771 			gtk_window_set_transient_for( GTK_WINDOW( iwnd ),
772 				GTK_WINDOW( iwnd->parent_window ) );
773 
774 		/* We watch our parent's "unmap" rather than "destroy" since
775 		 * we use gtk_widget_unmap() to hide killed windows during
776 		 * popdown (see iwindow_popdown_notify()).
777 		 */
778 		iwnd->parent_unmap_sid = gtk_signal_connect(
779 			GTK_OBJECT( iwnd->parent_window ),
780 			"unmap",
781 			GTK_SIGNAL_FUNC( iwindow_parent_unmap_cb ), iwnd );
782 
783 		/* Show the parent. For example, if this is the ^Q
784 		 * save-or-quit dialog and the parent is a mainw, we want to
785 		 * pop the mainw up.
786 		 */
787 		gtk_window_present( GTK_WINDOW( iwnd->parent_window ) );
788 	}
789 
790         gtk_widget_show( iwnd->work );
791 }
792 
793 static void
iwindow_real_popdown(GtkWidget * widget)794 iwindow_real_popdown( GtkWidget *widget )
795 {
796 	gtk_widget_unmap( widget );
797 }
798 
799 static void
iwindow_class_init(iWindowClass * class)800 iwindow_class_init( iWindowClass *class )
801 {
802 	GObjectClass *object_class = (GObjectClass *) class;
803 	GtkObjectClass *gobject_class = (GtkObjectClass *) class;
804 	GtkWidgetClass *widget_class = (GtkWidgetClass *) class;
805 
806 	parent_class = g_type_class_peek_parent( class );
807 
808 	/* Init methods.
809 	 */
810 	object_class->finalize = iwindow_finalize;
811 
812 	gobject_class->destroy = iwindow_destroy;
813 
814 	widget_class->delete_event = iwindow_delete_event;
815 	widget_class->configure_event = iwindow_configure_event;
816 
817 	class->build = iwindow_real_build;
818 	class->popdown = iwindow_real_popdown;
819 
820 	class->transient = FALSE;
821 
822 	/* Create signals.
823 	 */
824 
825 	/* Static class data init.
826 	 */
827 	iwindow_make_cursors();
828 
829 	/* Link to busy signals.
830 	 */
831 	g_signal_connect( progress_get(), "begin", hourglass_begin, NULL );
832 	g_signal_connect( progress_get(), "update", hourglass_update, NULL );
833 	g_signal_connect( progress_get(), "end", hourglass_end, NULL );
834 }
835 
836 static void
iwindow_init(iWindow * iwnd)837 iwindow_init( iWindow *iwnd )
838 {
839 #ifdef DEBUG
840 	printf( "iwindow_init: %s\n", iwnd->title );
841 #endif /*DEBUG*/
842 
843 	iwnd->work = NULL;
844 
845 	iwnd->parent = NULL;
846 	iwnd->parent_window = NULL;
847         iwnd->parent_unmap_sid = 0;
848 
849 	/* Might as well.
850 	 */
851 	iwnd->accel_group = gtk_accel_group_new();
852 	gtk_window_add_accel_group( GTK_WINDOW( iwnd ), iwnd->accel_group );
853 	g_object_unref( iwnd->accel_group );
854 	iwnd->infobar = NULL;
855 
856 	iwnd->title = NULL;
857 
858 	iwnd->build = NULL;
859 	iwnd->popdown = iwindow_true_cb;
860 
861 	iwnd->destroy = FALSE;
862 	iwnd->pending = 0;
863 
864 	iwnd->shape = IWINDOW_SHAPE_NONE;
865 	iwnd->contexts = NULL;
866 	iwnd->work_window = NULL;
867 
868 	iwnd->width_pref = NULL;
869 	iwnd->height_pref = NULL;
870 
871 	iwindow_all = g_slist_prepend( iwindow_all, iwnd );
872 }
873 
874 GtkType
iwindow_get_type(void)875 iwindow_get_type( void )
876 {
877 	static GtkType type = 0;
878 
879 	if( !type ) {
880 		static const GtkTypeInfo info = {
881 			"iWindow",
882 			sizeof( iWindow ),
883 			sizeof( iWindowClass ),
884 			(GtkClassInitFunc) iwindow_class_init,
885 			(GtkObjectInitFunc) iwindow_init,
886 			/* reserved_1 */ NULL,
887 			/* reserved_2 */ NULL,
888 			(GtkClassInitFunc) NULL,
889 		};
890 
891 		type = gtk_type_unique( GTK_TYPE_WINDOW, &info );
892 	}
893 
894 	return( type );
895 }
896 
897 GtkWidget *
iwindow_new(GtkWindowType type)898 iwindow_new( GtkWindowType type )
899 {
900 	iWindow *iwnd = gtk_type_new( TYPE_IWINDOW );
901 	GtkWindow *gwnd = GTK_WINDOW( iwnd );
902 
903 	/* Init superclass.
904 	 */
905 	gwnd->type = type;
906 
907 	return( GTK_WIDGET( iwnd ) );
908 }
909 
910 void
iwindow_set_title(iWindow * iwnd,const char * title,...)911 iwindow_set_title( iWindow *iwnd, const char *title, ... )
912 {
913 	va_list ap;
914 	char buf[1024];
915 
916         va_start( ap, title );
917         (void) im_vsnprintf( buf, 1024, title, ap );
918         va_end( ap );
919 
920 	if( !iwnd->title || strcmp( iwnd->title, buf ) != 0 ) {
921 		IM_SETSTR( iwnd->title, buf );
922 		gtk_window_set_title( GTK_WINDOW( iwnd ), iwnd->title );
923 	}
924 }
925 
926 void
iwindow_set_build(iWindow * iwnd,iWindowBuildFn build,void * build_a,void * build_b,void * build_c)927 iwindow_set_build( iWindow *iwnd,
928 	iWindowBuildFn build, void *build_a, void *build_b, void *build_c )
929 {
930 	iwnd->build = build;
931 	iwnd->build_a = build_a;
932 	iwnd->build_b = build_b;
933 	iwnd->build_c = build_c;
934 }
935 
936 void
iwindow_set_popdown(iWindow * iwnd,iWindowFn popdown,void * popdown_a)937 iwindow_set_popdown( iWindow *iwnd, iWindowFn popdown, void *popdown_a )
938 {
939 	iwnd->popdown = popdown;
940 	iwnd->popdown_a = popdown_a;
941 }
942 
943 void
iwindow_set_size_prefs(iWindow * iwnd,const char * width_pref,const char * height_pref)944 iwindow_set_size_prefs( iWindow *iwnd,
945 	const char *width_pref, const char *height_pref )
946 {
947 	iwnd->width_pref = width_pref;
948 	iwnd->height_pref = height_pref;
949 }
950 
951 void
iwindow_set_work_window(iWindow * iwnd,GdkWindow * work_window)952 iwindow_set_work_window( iWindow *iwnd, GdkWindow *work_window )
953 {
954 	iwnd->work_window = work_window;
955 	iwindow_cursor_update( iwnd );
956 }
957 
958 void
iwindow_set_parent(iWindow * iwnd,GtkWidget * parent)959 iwindow_set_parent( iWindow *iwnd, GtkWidget *parent )
960 {
961 	g_assert( !iwnd->parent );
962 
963 	iwnd->parent = parent;
964 
965 	/* Get parent window now, we sometimes need it after parent has been
966 	 * destroyed.
967 	 */
968 	if( parent )
969 		iwnd->parent_window =
970 			IWINDOW( iwindow_get_root( GTK_WIDGET( parent ) ) );
971 }
972 
973 void *
iwindow_kill(iWindow * iwnd)974 iwindow_kill( iWindow *iwnd )
975 {
976 #ifdef DEBUG
977 	printf( "iwindow_kill: %p %s\n", iwnd, iwnd->title );
978 #endif /*DEBUG*/
979 
980 	if( !iwnd->destroy ) {
981 #ifdef DEBUG
982 		printf( "... starting destroy for %s\n", iwnd->title );
983 #endif /*DEBUG*/
984 
985 		iwnd->destroy = TRUE;
986 
987 		/* Don't kill directly, wait for popdown_notify to do it.
988 		 */
989 		iwindow_notify_send( iwnd, iwnd->popdown, iwnd->popdown_a,
990 			(iWindowNotifyFn) iwindow_popdown_notify, iwnd );
991 	}
992 
993 	return( NULL );
994 }
995 
996 /* ... as an action.
997  */
998 void
iwindow_kill_action_cb(GtkAction * action,iWindow * iwnd)999 iwindow_kill_action_cb( GtkAction *action, iWindow *iwnd )
1000 {
1001 	iwindow_kill( iwnd );
1002 }
1003 
1004 void
iwindow_build(iWindow * iwnd)1005 iwindow_build( iWindow *iwnd )
1006 {
1007 #ifdef DEBUG
1008 	printf( "iwindow_build: %s\n", iwnd->title );
1009 #endif /*DEBUG*/
1010 
1011 	IWINDOW_GET_CLASS( iwnd )->build( GTK_WIDGET( iwnd ) );
1012 }
1013 
1014 /* Get the enclosing window for a widget.
1015  */
1016 GtkWidget *
iwindow_get_root(GtkWidget * widget)1017 iwindow_get_root( GtkWidget *widget )
1018 {
1019 	GtkWidget *toplevel = gtk_widget_get_toplevel( widget );
1020 	GtkWidget *child = gtk_bin_get_child( GTK_BIN( toplevel ) );
1021 
1022 	/* If this is a menu pane, get the widget that popped this menu up.
1023 	 */
1024 	if( GTK_IS_MENU( child ) ) {
1025 		GtkWidget *parent =
1026 			gtk_menu_get_attach_widget( GTK_MENU( child ) );
1027 
1028 		return( iwindow_get_root( parent ) );
1029 	}
1030 	else
1031 		return( toplevel );
1032 }
1033 
1034 /* Get the enclosing no-parent window for a widget.
1035  */
1036 GtkWidget *
iwindow_get_root_noparent(GtkWidget * widget)1037 iwindow_get_root_noparent( GtkWidget *widget )
1038 {
1039 	GtkWidget *toplevel = iwindow_get_root( widget );
1040 
1041 	/* If this is a transient, get the window we popped up from.
1042 	 */
1043 	if( IS_IWINDOW( toplevel ) && IWINDOW( toplevel )->parent )
1044 		return( iwindow_get_root_noparent(
1045 			IWINDOW( toplevel )->parent ) );
1046 	else
1047 		return( toplevel );
1048 }
1049 
1050 void
iwindow_alert(GtkWidget * parent,GtkMessageType type)1051 iwindow_alert( GtkWidget *parent, GtkMessageType type )
1052 {
1053 	GtkWidget *toplevel;
1054 
1055 	if( !parent )
1056 		parent = GTK_WIDGET( mainw_pick_one() );
1057 
1058 	if( parent &&
1059 		(toplevel = iwindow_get_root( parent )) &&
1060 		IS_IWINDOW( toplevel ) &&
1061 		IWINDOW( toplevel )->infobar )
1062 		infobar_set( IWINDOW( toplevel )->infobar, type,
1063 			error_get_top(), "%s", error_get_sub() );
1064 	else
1065 		switch( type ) {
1066 		case GTK_MESSAGE_INFO:
1067 			box_info( parent,
1068 				error_get_top(), "%s", error_get_sub() );
1069 			break;
1070 
1071 		case GTK_MESSAGE_ERROR:
1072 			box_alert( parent );
1073 			break;
1074 
1075 		default:
1076 			break;
1077 		}
1078 }
1079 
1080