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