1 /* abstract base class for things which form the model half of a model/view
2  * pair
3  */
4 
5 /*
6 
7     Copyright (C) 1991-2003 The National Gallery
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License along
20     with this program; if not, write to the Free Software Foundation, Inc.,
21     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 
23  */
24 
25 /*
26 
27     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
28 
29  */
30 
31 /*
32 #define DEBUG
33  */
34 
35 #include "ip.h"
36 
37 /* Stuff from bison ... needed as we call the lexer directly to rewrite
38  * expressions.
39  */
40 #include "parse.h"
41 
42 /* Our signals.
43  */
44 enum {
45 	SIG_SCROLLTO,	/* Views should try to make themselves visible */
46 	SIG_LAYOUT,	/* Views should lay out their children */
47 	SIG_RESET,	/* Reset edit mode in views */
48 	SIG_FRONT,	/* Bring views to front */
49 	SIG_DISPLAY,	/* Display on/off */
50 	SIG_LAST
51 };
52 
53 static iContainerClass *parent_class = NULL;
54 
55 static guint model_signals[SIG_LAST] = { 0 };
56 
57 /* Base model ... built at startup.
58  */
59 static Model *model_base = NULL;
60 
61 /* All the model classes which can be built from XML.
62  */
63 static GSList *model_registered_loadable = NULL;
64 
65 /* The loadstate the lexer gets its rename stuff from.
66  */
67 ModelLoadState *model_loadstate = NULL;
68 
69 /* Rename list functions.
70  */
71 static void *
model_rename_destroy(ModelRename * rename)72 model_rename_destroy( ModelRename *rename )
73 {
74 	IM_FREE( rename->old_name );
75 	IM_FREE( rename->new_name );
76 	IM_FREE( rename );
77 
78 	return( NULL );
79 }
80 
81 static ModelRename *
model_rename_new(const char * old_name,const char * new_name)82 model_rename_new( const char *old_name, const char *new_name )
83 {
84 	ModelRename *rename;
85 
86 	if( !(rename = INEW( NULL, ModelRename )) )
87 		return( NULL );
88 	rename->old_name = im_strdup( NULL, old_name );
89 	rename->new_name = im_strdup( NULL, new_name );
90 	if( !rename->old_name || !rename->new_name ) {
91 		model_rename_destroy( rename );
92 		return( NULL );
93 	}
94 
95 	return( rename );
96 }
97 
98 gboolean
model_loadstate_rename_new(ModelLoadState * state,const char * old_name,const char * new_name)99 model_loadstate_rename_new( ModelLoadState *state,
100 	const char *old_name, const char *new_name )
101 {
102 	/* Make a rename, even if old_name == new_name, since we want to have
103 	 * new_name on the taken list.
104 	 */
105 	ModelRename *rename;
106 
107 	if( !(rename = model_rename_new( old_name, new_name )) )
108 		return( FALSE );
109 	state->renames = g_slist_prepend( state->renames, rename );
110 
111 	return( TRUE );
112 }
113 
114 static void *
model_loadstate_taken_sub(ModelRename * rename,const char * name)115 model_loadstate_taken_sub( ModelRename *rename, const char *name )
116 {
117 	if( strcmp( rename->new_name, name ) == 0 )
118 		return( rename );
119 
120 	return( NULL );
121 }
122 
123 /* Is something already being renamed to @name.
124  */
125 gboolean
model_loadstate_taken(ModelLoadState * state,const char * name)126 model_loadstate_taken( ModelLoadState *state, const char *name )
127 {
128 	return( slist_map( state->renames,
129 		(SListMapFn) model_loadstate_taken_sub, (char *) name ) !=
130 		NULL );
131 }
132 
133 gboolean
model_loadstate_column_rename_new(ModelLoadState * state,const char * old_name,const char * new_name)134 model_loadstate_column_rename_new( ModelLoadState *state,
135 	const char *old_name, const char *new_name )
136 {
137 	if( strcmp( old_name, new_name ) != 0 ) {
138 		ModelRename *rename;
139 
140 		if( !(rename = model_rename_new( old_name, new_name )) )
141 			return( FALSE );
142 		state->column_renames =
143 			g_slist_prepend( state->column_renames, rename );
144 	}
145 
146 	return( TRUE );
147 }
148 
149 /* Is something already being renamed to @name.
150  */
151 gboolean
model_loadstate_column_taken(ModelLoadState * state,const char * name)152 model_loadstate_column_taken( ModelLoadState *state, const char *name )
153 {
154 	return( !!slist_map( state->column_renames,
155 		(SListMapFn) model_loadstate_taken_sub, (char *) name ) );
156 }
157 
158 void
model_loadstate_destroy(ModelLoadState * state)159 model_loadstate_destroy( ModelLoadState *state )
160 {
161 	/* We are probably registered as the xml error handler ... unregister!
162 	 */
163 	xmlSetGenericErrorFunc( NULL, NULL );
164 
165 	IM_FREE( state->filename );
166 	IM_FREE( state->filename_user );
167 	IM_FREEF( xmlFreeDoc, state->xdoc );
168 	slist_map( state->renames,
169 		(SListMapFn) model_rename_destroy, NULL );
170 	slist_map( state->column_renames,
171 		(SListMapFn) model_rename_destroy, NULL );
172 	g_slist_free( state->renames );
173 
174 	if( state->old_dir ) {
175 		path_rewrite_add( state->old_dir, NULL, FALSE );
176 		IM_FREE( state->old_dir );
177 	}
178 
179 	IM_FREE( state );
180 }
181 
182 static void
model_loadstate_error(ModelLoadState * state,const char * fmt,...)183 model_loadstate_error( ModelLoadState *state, const char *fmt, ... )
184 {
185 	va_list ap;
186 
187 	va_start( ap, fmt );
188 	(void) vips_buf_vappendf( &state->error_log, fmt, ap );
189 	va_end( ap );
190 }
191 
192 static void
model_loadstate_error_get(ModelLoadState * state)193 model_loadstate_error_get( ModelLoadState *state )
194 {
195 	char *utf8;
196 
197 	utf8 = f2utf8( vips_buf_all( &state->error_log ) );
198 	error_top( _( "Load failed." ) );
199 	error_sub( _( "Unable to load from file \"%s\". Error log is:\n%s" ),
200 		state->filename, utf8 );
201 	g_free( utf8 );
202 }
203 
204 ModelLoadState *
model_loadstate_new(const char * filename,const char * filename_user)205 model_loadstate_new( const char *filename, const char *filename_user )
206 {
207 	ModelLoadState *state;
208 
209 	if( !(state = INEW( NULL, ModelLoadState )) )
210 		return( NULL );
211 	state->xdoc = NULL;
212 	state->renames = NULL;
213 	state->column_renames = NULL;
214 	state->major = MAJOR_VERSION;
215 	state->minor = MINOR_VERSION;
216 	state->micro = MICRO_VERSION;
217 	state->rewrite_path = FALSE;
218 	state->old_dir = FALSE;
219 
220 	state->filename = im_strdup( NULL, filename );
221 	if( filename_user )
222 		state->filename_user = im_strdup( NULL, filename_user );
223 	else
224 		state->filename_user = im_strdup( NULL, filename );
225 	if( !state->filename ||
226 		!state->filename_user ) {
227 		model_loadstate_destroy( state );
228 		return( NULL );
229 	}
230 
231 	vips_buf_init_static( &state->error_log,
232 		state->error_log_buffer, MAX_STRSIZE );
233 
234 	xmlSetGenericErrorFunc( state,
235 		(xmlGenericErrorFunc) model_loadstate_error );
236 	if( !(state->xdoc = (xmlDoc *) callv_string_filename(
237 		(callv_string_fn) xmlParseFile,
238 		state->filename, NULL, NULL, NULL )) ) {
239 		model_loadstate_error_get( state );
240 		model_loadstate_destroy( state );
241 		return( NULL );
242 	}
243 
244 	return( state );
245 }
246 
247 ModelLoadState *
model_loadstate_new_openfile(iOpenFile * of)248 model_loadstate_new_openfile( iOpenFile *of )
249 {
250 	ModelLoadState *state;
251 	char load_buffer[MAX_STRSIZE];
252 
253 	if( !(state = INEW( NULL, ModelLoadState )) )
254 		return( NULL );
255 	state->renames = NULL;
256 	state->xdoc = NULL;
257 	if( !(state->filename = im_strdup( NULL, of->fname )) ) {
258 		model_loadstate_destroy( state );
259 		return( NULL );
260 	}
261 	vips_buf_init_static( &state->error_log,
262 		state->error_log_buffer, MAX_STRSIZE );
263 
264 	xmlSetGenericErrorFunc( state,
265 		(xmlGenericErrorFunc) model_loadstate_error );
266 	if( !ifile_read_buffer( of, load_buffer, MAX_STRSIZE ) ) {
267 		model_loadstate_destroy( state );
268 		return( NULL );
269 	}
270 	if( !(state->xdoc = xmlParseMemory( load_buffer, MAX_STRSIZE )) ) {
271 		model_loadstate_error_get( state );
272 		model_loadstate_destroy( state );
273 		return( NULL );
274 	}
275 
276 	return( state );
277 }
278 
279 /* If old_name is on the global rewrite list, rewrite it! Called from the
280  * lexer.
281  */
282 char *
model_loadstate_rewrite_name(char * name)283 model_loadstate_rewrite_name( char *name )
284 {
285 	ModelLoadState *state = model_loadstate;
286 	GSList *i;
287 
288 	if( !state || !state->renames )
289 		return( NULL );
290 
291 	for( i = state->renames; i; i = i->next ) {
292 		ModelRename *rename = (ModelRename *) (i->data);
293 
294 		if( strcmp( name, rename->old_name ) == 0 )
295 			return( rename->new_name );
296 	}
297 
298 	return( NULL );
299 }
300 
301 /* Use the lexer to rewrite an expression, swapping all symbols on the rewrite
302  * list.
303  */
304 void
model_loadstate_rewrite(ModelLoadState * state,char * old_rhs,char * new_rhs)305 model_loadstate_rewrite( ModelLoadState *state, char *old_rhs, char *new_rhs )
306 {
307 	int yychar;
308 	extern int yylex( void );
309 
310 	model_loadstate = state;
311 	attach_input_string( old_rhs );
312 	if( setjmp( parse_error_point ) ) {
313 		/* Here for yyerror in lex. Just ignore errors --- the parser
314 		 * will spot them later anyway.
315 		 */
316 		model_loadstate = NULL;
317 		return;
318 	}
319 
320 	/* Lex and rewrite.
321 	 */
322 	state->rewrite_path = FALSE;
323 	while( (yychar = yylex()) > 0 ) {
324 		/* If we see an Image_file or Matrix_file token, rewrite the
325 		 * following token if it's a string constant.
326 		 */
327 		state->rewrite_path = FALSE;
328 		if( yychar == TK_IDENT &&
329 			strcmp( yylval.yy_name, "Image_file" ) == 0 )
330 			state->rewrite_path = TRUE;
331 		if( yychar == TK_IDENT &&
332 			strcmp( yylval.yy_name, "Matrix_file" ) == 0 )
333 			state->rewrite_path = TRUE;
334 
335 		free_lex( yychar );
336 	}
337 
338 	model_loadstate = NULL;
339 
340 	/* Take copy of lexed and rewritten stuff.
341 	 */
342 	im_strncpy( new_rhs, vips_buf_all( &lex_text ), MAX_STRSIZE );
343 }
344 
345 View *
model_view_new(Model * model,View * parent)346 model_view_new( Model *model, View *parent )
347 {
348 	ModelClass *model_class = MODEL_GET_CLASS( model );
349 	View *view;
350 
351 	if( !model_class->view_new )
352 		return( NULL );
353 
354 	view = model_class->view_new( model, parent );
355 	view_link( view, model, parent );
356 
357 	return( view );
358 }
359 
360 /* Register a model subclass as loadable ... what we allow when we load an
361  * XML node's children.
362  */
363 void
model_register_loadable(ModelClass * model_class)364 model_register_loadable( ModelClass *model_class )
365 {
366 	model_registered_loadable = g_slist_prepend( model_registered_loadable,
367 		model_class );
368 }
369 
370 void
model_scrollto(Model * model,ModelScrollPosition position)371 model_scrollto( Model *model, ModelScrollPosition position )
372 {
373 	g_assert( IS_MODEL( model ) );
374 
375 	g_signal_emit( G_OBJECT( model ),
376 		model_signals[SIG_SCROLLTO], 0, position );
377 }
378 
379 void
model_layout(Model * model)380 model_layout( Model *model )
381 {
382 	g_assert( IS_MODEL( model ) );
383 
384 	g_signal_emit( G_OBJECT( model ), model_signals[SIG_LAYOUT], 0 );
385 }
386 
387 void
model_front(Model * model)388 model_front( Model *model )
389 {
390 	g_assert( IS_MODEL( model ) );
391 
392 	g_signal_emit( G_OBJECT( model ), model_signals[SIG_FRONT], 0 );
393 }
394 
395 void
model_display(Model * model,gboolean display)396 model_display( Model *model, gboolean display )
397 {
398 	if( model ) {
399 		g_assert( IS_MODEL( model ) );
400 
401 		g_signal_emit( G_OBJECT( model ),
402 			model_signals[SIG_DISPLAY], 0, display );
403 	}
404 }
405 
406 void *
model_reset(Model * model)407 model_reset( Model *model )
408 {
409 	g_assert( IS_MODEL( model ) );
410 
411 	g_signal_emit( G_OBJECT( model ), model_signals[SIG_RESET], 0 );
412 
413 	return( NULL );
414 }
415 
416 void *
model_edit(GtkWidget * parent,Model * model)417 model_edit( GtkWidget *parent, Model *model )
418 {
419 	ModelClass *model_class = MODEL_GET_CLASS( model );
420 
421 	if( model_class->edit )
422 		model_class->edit( parent, model );
423 	else {
424 		error_top( _( "Not implemented." ) );
425 		error_sub( _( "_%s() not implemented for class \"%s\"." ),
426 			"edit",
427 			G_OBJECT_CLASS_NAME( model_class ) );
428 	}
429 
430 	return( NULL );
431 }
432 
433 void *
model_header(GtkWidget * parent,Model * model)434 model_header( GtkWidget *parent, Model *model )
435 {
436 	ModelClass *model_class = MODEL_GET_CLASS( model );
437 
438 	if( model_class->header )
439 		model_class->header( parent, model );
440 	else {
441 		error_top( _( "Not implemented." ) );
442 		error_sub( _( "_%s() not implemented for class \"%s\"." ),
443 			"header",
444 			G_OBJECT_CLASS_NAME( model_class ) );
445 	}
446 
447 	return( NULL );
448 }
449 
450 void *
model_save(Model * model,xmlNode * xnode)451 model_save( Model *model, xmlNode *xnode )
452 {
453 	ModelClass *model_class = MODEL_GET_CLASS( model );
454 
455 	if( model_save_test( model ) ) {
456 		if( model_class->save && !model_class->save( model, xnode ) )
457 			return( model );
458 	}
459 
460 	return( NULL );
461 }
462 
463 gboolean
model_save_test(Model * model)464 model_save_test( Model *model )
465 {
466 	ModelClass *model_class = MODEL_GET_CLASS( model );
467 
468 	if( model_class->save_test )
469 		return( model_class->save_test( model ) );
470 
471 	return( TRUE );
472 }
473 
474 void *
model_save_text(Model * model,iOpenFile * of)475 model_save_text( Model *model, iOpenFile *of )
476 {
477 	ModelClass *model_class = MODEL_GET_CLASS( model );
478 
479 	if( model_class->save_text && !model_class->save_text( model, of ) )
480 		return( model );
481 
482 	return( NULL );
483 }
484 
485 void *
model_load(Model * model,ModelLoadState * state,Model * parent,xmlNode * xnode)486 model_load( Model *model,
487 	ModelLoadState *state, Model *parent, xmlNode *xnode )
488 {
489 	ModelClass *model_class = MODEL_GET_CLASS( model );
490 
491 	if( model_class->load ) {
492 		if( !model_class->load( model, state, parent, xnode ) )
493 			return( model );
494 	}
495 	else {
496 		error_top( _( "Not implemented." ) );
497 		error_sub( _( "_%s() not implemented for class \"%s\"." ),
498 			"load",
499 			G_OBJECT_CLASS_NAME( model_class ) );
500 	}
501 
502 	return( NULL );
503 }
504 
505 void *
model_load_text(Model * model,Model * parent,iOpenFile * of)506 model_load_text( Model *model, Model *parent, iOpenFile *of )
507 {
508 	ModelClass *model_class = MODEL_GET_CLASS( model );
509 
510 	if( model_class->load_text ) {
511 		if( !model_class->load_text( model, parent, of ) )
512 			return( model );
513 	}
514 	else {
515 		error_top( "Not implemented." );
516 		error_sub( _( "_%s() not implemented for class \"%s\"." ),
517 			"load_text",
518 			G_OBJECT_CLASS_NAME( model_class ) );
519 	}
520 
521 	return( NULL );
522 }
523 
524 void *
model_empty(Model * model)525 model_empty( Model *model )
526 {
527 	ModelClass *model_class = MODEL_GET_CLASS( model );
528 
529 	if( model_class->empty )
530 		model_class->empty( model );
531 
532 	return( NULL );
533 }
534 
535 static void
model_real_scrollto(Model * model,ModelScrollPosition position)536 model_real_scrollto( Model *model, ModelScrollPosition position )
537 {
538 }
539 
540 static void
model_real_front(Model * model)541 model_real_front( Model *model )
542 {
543 }
544 
545 static void
model_real_display(Model * model,gboolean display)546 model_real_display( Model *model, gboolean display )
547 {
548 	if( display != model->display ) {
549 		model->display = display;
550 		iobject_changed( IOBJECT( model ) );
551 	}
552 }
553 
554 static xmlNode *
model_real_save(Model * model,xmlNode * xnode)555 model_real_save( Model *model, xmlNode *xnode )
556 {
557 	const char *tname = G_OBJECT_TYPE_NAME( model );
558 	xmlNode *xthis;
559 
560 	if( !(xthis = xmlNewChild( xnode, NULL, (xmlChar *) tname, NULL )) ) {
561 		error_top( _( "XML library error." ) );
562 		error_sub( _( "model_save: xmlNewChild() failed" ) );
563 		return( NULL );
564 	}
565 
566 	if( icontainer_map( ICONTAINER( model ),
567 		(icontainer_map_fn) model_save, xthis, NULL ) )
568 		return( NULL );
569 
570 	if( model->window_width != -1 ) {
571 		if( !set_iprop( xthis, "window_x", model->window_x ) ||
572 			!set_iprop( xthis, "window_y", model->window_y ) ||
573 			!set_iprop( xthis, "window_width",
574 				model->window_width ) ||
575 			!set_iprop( xthis, "window_height",
576 				model->window_height ) )
577 			return( NULL );
578 	}
579 
580 	return( xthis );
581 }
582 
583 static void *
model_new_xml_sub(ModelClass * model_class,ModelLoadState * state,Model * parent,xmlNode * xnode)584 model_new_xml_sub( ModelClass *model_class,
585 	ModelLoadState *state, Model *parent, xmlNode *xnode )
586 {
587 	GtkType type = GTK_CLASS_TYPE( model_class );
588 	const char *tname = gtk_type_name( type );
589 
590 	if( strcasecmp( (char *) xnode->name, tname ) == 0 ) {
591 		Model *model = MODEL( g_object_new( type, NULL ) );
592 
593 		if( model_load( model, state, parent, xnode ) ) {
594 			g_object_unref( model );
595 			return( model_class );
596 		}
597 
598 		return( NULL );
599 	}
600 
601 	return( NULL );
602 }
603 
604 gboolean
model_new_xml(ModelLoadState * state,Model * parent,xmlNode * xnode)605 model_new_xml( ModelLoadState *state, Model *parent, xmlNode *xnode )
606 {
607 	/*
608 
609 		FIXME ... slow! some sort of hash? time this at some point
610 
611 	 */
612 	if( slist_map3( model_registered_loadable,
613 		(SListMap3Fn) model_new_xml_sub, state, parent, xnode ) )
614 		return( FALSE );
615 
616 	return( TRUE );
617 }
618 
619 static gboolean
model_real_load(Model * model,ModelLoadState * state,Model * parent,xmlNode * xnode)620 model_real_load( Model *model,
621 	ModelLoadState *state, Model *parent, xmlNode *xnode )
622 {
623 	const char *tname = G_OBJECT_TYPE_NAME( model );
624 	xmlNode *i;
625 
626 	/* Should just be a sanity check.
627 	 */
628 	if( strcasecmp( (char *) xnode->name, tname ) != 0 ) {
629 		error_top( _( "XML load error." ) );
630 		error_sub( _( "Can't load node of type \"%s\" into "
631 			"object of type \"%s\"" ), xnode->name, tname );
632 		return( FALSE );
633 	}
634 
635 	(void) get_iprop( xnode, "window_x", &model->window_x );
636 	(void) get_iprop( xnode, "window_y", &model->window_y );
637 	(void) get_iprop( xnode, "window_width", &model->window_width );
638 	(void) get_iprop( xnode, "window_height", &model->window_height );
639 
640 	if( !ICONTAINER( model )->parent )
641 		icontainer_child_add( ICONTAINER( parent ),
642 			ICONTAINER( model ), -1 );
643 
644 	for( i = xnode->children; i; i = i->next )
645 		if( !model_new_xml( state, MODEL( model ), i ) )
646 			return( FALSE );
647 
648 #ifdef DEBUG
649 	printf( "model_real_load: finished loading %s (name = %s)\n",
650 		tname,
651 		NN( IOBJECT( model )->name ) );
652 #endif /*DEBUG*/
653 
654 	return( TRUE );
655 }
656 
657 static void
model_real_empty(Model * model)658 model_real_empty( Model *model )
659 {
660 	icontainer_map( ICONTAINER( model ),
661 		(icontainer_map_fn) icontainer_child_remove, NULL, NULL );
662 }
663 
664 static void
model_class_init(ModelClass * class)665 model_class_init( ModelClass *class )
666 {
667 	iObjectClass *object_class = IOBJECT_CLASS( class );
668 
669 	parent_class = g_type_class_peek_parent( class );
670 
671 	class->view_new = NULL;
672 	class->edit = NULL;
673 	class->scrollto = model_real_scrollto;
674 	class->layout = NULL;
675 	class->front = model_real_front;
676 	class->display = model_real_display;
677 	class->reset = NULL;
678 	class->save = model_real_save;
679 	class->save_test = NULL;
680 	class->save_text = NULL;
681 	class->load = model_real_load;
682 	class->load_text = NULL;
683 	class->empty = model_real_empty;
684 
685 	/* Create signals.
686 	 */
687 	model_signals[SIG_SCROLLTO] = g_signal_new( "scrollto",
688 		G_OBJECT_CLASS_TYPE( object_class ),
689 		G_SIGNAL_RUN_FIRST,
690 		G_STRUCT_OFFSET( ModelClass, scrollto ),
691 		NULL, NULL,
692 		g_cclosure_marshal_VOID__INT,
693 		G_TYPE_NONE, 1,
694 		G_TYPE_INT );
695 	model_signals[SIG_LAYOUT] = g_signal_new( "layout",
696 		G_OBJECT_CLASS_TYPE( object_class ),
697 		G_SIGNAL_RUN_FIRST,
698 		G_STRUCT_OFFSET( ModelClass, layout ),
699 		NULL, NULL,
700 		g_cclosure_marshal_VOID__VOID,
701 		G_TYPE_NONE, 0 );
702 	model_signals[SIG_FRONT] = g_signal_new( "front",
703 		G_OBJECT_CLASS_TYPE( object_class ),
704 		G_SIGNAL_RUN_FIRST,
705 		G_STRUCT_OFFSET( ModelClass, front ),
706 		NULL, NULL,
707 		g_cclosure_marshal_VOID__VOID,
708 		G_TYPE_NONE, 0 );
709 	model_signals[SIG_RESET] = g_signal_new( "reset",
710 		G_OBJECT_CLASS_TYPE( object_class ),
711 		G_SIGNAL_RUN_FIRST,
712 		G_STRUCT_OFFSET( ModelClass, reset ),
713 		NULL, NULL,
714 		g_cclosure_marshal_VOID__VOID,
715 		G_TYPE_NONE, 0 );
716 	model_signals[SIG_DISPLAY] = g_signal_new( "display",
717 		G_OBJECT_CLASS_TYPE( object_class ),
718 		G_SIGNAL_RUN_FIRST,
719 		G_STRUCT_OFFSET( ModelClass, display ),
720 		NULL, NULL,
721 		g_cclosure_marshal_VOID__BOOLEAN,
722 		G_TYPE_NONE, 1,
723 		G_TYPE_BOOLEAN );
724 }
725 
726 static void
model_init(Model * model)727 model_init( Model *model )
728 {
729 	model->display = TRUE;
730 
731 	/* Magic: -1 means none of these saved settings are valid. It'd be
732 	 * nice to do something better, but we'd break old workspaces.
733 	 */
734 	model->window_x = 0;
735 	model->window_y = 0;
736 	model->window_width = -1;
737 	model->window_height = 0;
738 }
739 
740 GType
model_get_type(void)741 model_get_type( void )
742 {
743 	static GType model_type = 0;
744 
745 	if( !model_type ) {
746 		static const GTypeInfo info = {
747 			sizeof( ModelClass ),
748 			NULL,           /* base_init */
749 			NULL,           /* base_finalize */
750 			(GClassInitFunc) model_class_init,
751 			NULL,           /* class_finalize */
752 			NULL,           /* class_data */
753 			sizeof( Model ),
754 			32,             /* n_preallocs */
755 			(GInstanceInitFunc) model_init,
756 		};
757 
758 		model_type = g_type_register_static( TYPE_ICONTAINER,
759 			"Model", &info, 0 );
760 	}
761 
762 	return( model_type );
763 }
764 
765 void
model_base_init(void)766 model_base_init( void )
767 {
768 	model_base = MODEL( g_object_new( TYPE_MODEL, NULL ) );
769 
770 	/* We have to init some of our other classes to get them registered
771 	 * with the class loader.
772 	 */
773 	(void) g_type_class_ref( TYPE_CLOCK );
774 	(void) g_type_class_ref( TYPE_COLOUR );
775 	(void) g_type_class_ref( TYPE_EXPRESSION );
776 	(void) g_type_class_ref( TYPE_FONTNAME );
777 	(void) g_type_class_ref( TYPE_GROUP );
778 	(void) g_type_class_ref( TYPE_IARROW );
779 	(void) g_type_class_ref( TYPE_IIMAGE );
780 	(void) g_type_class_ref( TYPE_IREGION );
781 	(void) g_type_class_ref( TYPE_ITEXT );
782 	(void) g_type_class_ref( TYPE_MATRIX );
783 	(void) g_type_class_ref( TYPE_NUMBER );
784 	(void) g_type_class_ref( TYPE_OPTION );
785 	(void) g_type_class_ref( TYPE_PATHNAME );
786 	(void) g_type_class_ref( TYPE_PLOT );
787 	(void) g_type_class_ref( TYPE_REAL );
788 	(void) g_type_class_ref( TYPE_SLIDER );
789 	(void) g_type_class_ref( TYPE_STRING );
790 	(void) g_type_class_ref( TYPE_TOGGLE );
791 	(void) g_type_class_ref( TYPE_VECTOR );
792 
793 	(void) g_type_class_ref( TYPE_RHS );
794 	(void) g_type_class_ref( TYPE_ROW );
795 	(void) g_type_class_ref( TYPE_SUBCOLUMN );
796 	(void) g_type_class_ref( TYPE_WORKSPACE );
797 	(void) g_type_class_ref( TYPE_COLUMN );
798 }
799 
800 typedef struct {
801 	iDialog *idlg;		/* The yesno we run */
802 	Model *model;		/* The model we watch */
803 	guint destroy_sid;	/* sid for the destroy */
804 	iWindowFn done_cb;	/* Call this at the end */
805 } ModelCheckDestroy;
806 
807 /* OK to destroy.
808  */
809 static void
model_check_destroy_sub(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)810 model_check_destroy_sub( iWindow *iwnd, void *client,
811 	iWindowNotifyFn nfn, void *sys )
812 {
813 	ModelCheckDestroy *mcd = (ModelCheckDestroy *) client;
814 
815 	mcd->idlg = NULL;
816 	IDESTROY( mcd->model );
817 	symbol_recalculate_all();
818 
819 	mcd->done_cb( iwnd, NULL, nfn, sys );
820 }
821 
822 /* The model we are watching has been killed, maybe by us.
823  */
824 static void
model_check_destroy_destroy_cb(Model * model,ModelCheckDestroy * mcd)825 model_check_destroy_destroy_cb( Model *model, ModelCheckDestroy *mcd )
826 {
827 	g_assert( IS_MODEL( model ) );
828 	g_assert( IS_MODEL( mcd->model ) );
829 	g_assert( !mcd->idlg || IS_IDIALOG( mcd->idlg ) );
830 
831 	mcd->model = NULL;
832 	mcd->destroy_sid = 0;
833 
834 	if( mcd->idlg ) {
835 		iWindow *iwnd = IWINDOW( mcd->idlg );
836 
837 		mcd->idlg = NULL;
838 		iwindow_kill( iwnd );
839 	}
840 }
841 
842 /* Our dialog is done.
843  */
844 static void
model_check_destroy_finished(void * client,iWindowResult result)845 model_check_destroy_finished( void *client, iWindowResult result )
846 {
847 	ModelCheckDestroy *mcd = (ModelCheckDestroy *) client;
848 
849 	FREESID( mcd->destroy_sid, mcd->model );
850 	IM_FREE( mcd );
851 }
852 
853 void
model_check_destroy(GtkWidget * parent,Model * model,iWindowFn done_cb)854 model_check_destroy( GtkWidget *parent, Model *model, iWindowFn done_cb )
855 {
856 	char txt[30];
857 	VipsBuf buf = VIPS_BUF_STATIC( txt );
858 	const char *name;
859 
860 	ModelCheckDestroy *mcd = INEW( NULL, ModelCheckDestroy );
861 
862 	mcd->idlg = NULL;
863 	mcd->model = model;
864 	mcd->done_cb = done_cb ? done_cb : iwindow_true_cb;
865 
866 	if( IS_SYMBOL( model ) ) {
867 		symbol_qualified_name( SYMBOL( model ), &buf );
868 		name = vips_buf_all( &buf );
869 	}
870 	else
871 		name = IOBJECT( model )->name;
872 
873 	mcd->idlg = box_yesno( parent,
874 		model_check_destroy_sub, iwindow_true_cb, mcd,
875 		model_check_destroy_finished, mcd,
876 		GTK_STOCK_DELETE,
877 		_( "Delete?" ),
878 		_( "Are you sure you want to delete %s \"%s\"?" ),
879 		IOBJECT_GET_CLASS_NAME( model ), name );
880 
881 	/* In case someone else kills this model before we do.
882 	 */
883 	mcd->destroy_sid = g_signal_connect( model, "destroy",
884 		G_CALLBACK( model_check_destroy_destroy_cb ), mcd );
885 }
886 
887 /* Useful for icontainer_map_all() ... trigger all heapmodel_clear_edited()
888  * methods.
889  */
890 void *
model_clear_edited(Model * model)891 model_clear_edited( Model *model )
892 {
893 	void *result;
894 
895 	if( IS_HEAPMODEL( model ) &&
896 		(result = heapmodel_clear_edited( HEAPMODEL( model ) )) )
897 		return( result );
898 
899 	return( NULL );
900 }
901