1 /* a text item in a workspace
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 /*
31 #define DEBUG
32  */
33 
34 #include "ip.h"
35 
36 static HeapmodelClass *parent_class = NULL;
37 
38 static void
itext_finalize(GObject * gobject)39 itext_finalize( GObject *gobject )
40 {
41 	iText *itext;
42 
43 	g_return_if_fail( gobject != NULL );
44 	g_return_if_fail( IS_ITEXT( gobject ) );
45 
46 	itext = ITEXT( gobject );
47 
48 #ifdef DEBUG
49 	printf( "itext_destroy\n" );
50 #endif /*DEBUG*/
51 
52 	/* My instance destroy stuff.
53 	 */
54 	IM_FREE( itext->formula );
55 	IM_FREE( itext->formula_default );
56 	vips_buf_destroy( &itext->value );
57 	vips_buf_destroy( &itext->decompile );
58 
59 	G_OBJECT_CLASS( parent_class )->finalize( gobject );
60 }
61 
62 static void
itext_info(iObject * iobject,VipsBuf * buf)63 itext_info( iObject *iobject, VipsBuf *buf )
64 {
65 	iText *itext = ITEXT( iobject );
66 
67 	vips_buf_appends( buf, _( "Formula" ) );
68 	vips_buf_appendf( buf, ": %s\n", NN( itext->formula ) );
69 }
70 
71 /* Fwd ref this.
72  */
73 static gboolean itext_add_element( VipsBuf *buf,
74 	PElement *base, gboolean top, gboolean bracket );
75 
76 /* Sub-fn of below, callback for list print. Eval and print the item into
77  * the buffer, separating with commas as required.
78  */
79 static void *
itext_add_list(PElement * base,VipsBuf * buf,gboolean * first)80 itext_add_list( PElement *base, VipsBuf *buf, gboolean *first )
81 {
82 	Reduce *rc = reduce_context;
83 
84 	if( *first )
85 		*first = FALSE;
86 	else
87 		vips_buf_appends( buf, ", " );
88 
89 	/* Reduce the head, and print.
90 	 */
91 	if( !reduce_pelement( rc, reduce_spine, base ) )
92 		return( base );
93 	if( !itext_add_element( buf, base, FALSE, FALSE ) )
94 		return( base );
95 
96 	/* Buffer full? Abort list print.
97 	 */
98 	if( buf->full )
99 		return( base );
100 
101 	return( NULL );
102 }
103 
104 /* Sub-fn of below, callback for string print. Print the chars into the
105  * buffer.
106  */
107 static void *
itext_add_string(PElement * base,VipsBuf * buf)108 itext_add_string( PElement *base, VipsBuf *buf )
109 {
110 	Reduce *rc = reduce_context;
111 
112 	/* Reduce the head, and add the char.
113 	 */
114 	if( !reduce_pelement( rc, reduce_spine, base ) )
115 		return( base );
116 	if( PEISCHAR( base ) )
117 		/* Don't escape chars in string mode.
118 		 */
119 		vips_buf_appendf( buf, "%c", PEGETCHAR( base ) );
120 	else {
121 		/* Help! Fall back to ordinary item print.
122 		 */
123 		vips_buf_appends( buf, ", " );
124 		if( !itext_add_element( buf, base, FALSE, FALSE ) )
125 			return( base );
126 	}
127 
128 	/* Buffer full? Abort string print.
129 	 */
130 	if( buf->full )
131 		return( base );
132 
133 	return( NULL );
134 }
135 
136 /* Print a char ... we need to escape \n etc.
137  */
138 static void
itext_add_char(int ch,VipsBuf * buf)139 itext_add_char( int ch, VipsBuf *buf )
140 {
141 	char in[2];
142 	char out[3];
143 
144 	in[0] = ch;
145 	in[1] = '\0';
146 	my_strecpy( out, in, FALSE );
147 
148 	vips_buf_appends( buf, out );
149 }
150 
151 /* Print a complex.
152  */
153 static void
itext_add_complex(double rp,double ip,VipsBuf * buf)154 itext_add_complex( double rp, double ip, VipsBuf *buf )
155 {
156 	if( PRINT_CARTESIAN )
157 		vips_buf_appendf( buf, "(%.12g, %.12g)", rp, ip );
158 	else {
159 		if( rp == 0 ) {
160 			if( ip == 0 )
161 				vips_buf_appendf( buf, "0" );
162 			else
163 				vips_buf_appendf( buf, "%.12gj", ip );
164 		}
165 		else if( ip == 0 )
166 			vips_buf_appendf( buf, "%.12g", rp );
167 		else
168 			vips_buf_appendf( buf, "%.12g + %.12gj", rp, ip );
169 
170 	}
171 }
172 
173 /* Try to decompile.
174  */
175 static gboolean
itext_decompile_element(VipsBuf * buf,PElement * base,gboolean top)176 itext_decompile_element( VipsBuf *buf, PElement *base, gboolean top )
177 {
178 	Reduce *rc = reduce_context;
179 	gboolean result;
180 
181 	/* Set the value label for a tally entry.
182 	 */
183 	if( PEISNOVAL( base ) )
184 		vips_buf_appends( buf, _( "no value" ) );
185 	else if( PEISREAL( base ) )
186 		vips_buf_appendf( buf, "%g", PEGETREAL( base ) );
187 	else if( PEISBOOL( base ) )
188 		vips_buf_appends( buf, bool_to_char( PEGETBOOL( base ) ) );
189 	else if( PEISCHAR( base ) ) {
190 		vips_buf_appends( buf, "'" );
191 		itext_add_char( (int) PEGETCHAR( base ), buf );
192 		vips_buf_appends( buf, "'" );
193 	}
194 	else if( PEISCOMPLEX( base ) )
195 		itext_add_complex( PEGETREALPART( base ), PEGETIMAGPART( base ),
196 			buf );
197 	else if( PEISMANAGEDSTRING( base ) ) {
198 		Managedstring *managedstring = PEGETMANAGEDSTRING( base );
199 
200 		vips_buf_appendf( buf, "\"%s\"", managedstring->string );
201 	}
202 	else if( PEISELIST( base ) ) {
203 		vips_buf_appends( buf, "[ ]" );
204 	}
205 	else if( !heap_is_string( base, &result ) )
206 		/* Eval error.
207 		 */
208 		return( FALSE );
209 	else if( result ) {
210 		vips_buf_appends( buf, "\"" );
211 		if( heap_map_list( base,
212 			(heap_map_list_fn) itext_add_string, buf, NULL ) )
213 			return( FALSE );
214 		vips_buf_appends( buf, "\"" );
215 	}
216 	else if( PEISLIST( base ) ) {
217 		gboolean first = TRUE;
218 
219 		vips_buf_appends( buf, "[" );
220 		if( heap_map_list( base,
221 			(heap_map_list_fn) itext_add_list, buf, &first ) )
222 			return( FALSE );
223 		vips_buf_appends( buf, "]" );
224 	}
225 	else if( PEISIMAGE( base ) ) {
226 		Imageinfo *ii = PEGETII( base );
227 
228 		if( !top )
229 			vips_buf_appends( buf, "(" );
230 
231 		if( ii && IOBJECT( ii )->name )
232 			vips_buf_appendf( buf, "vips_image \"%s\"",
233 				IOBJECT( ii )->name );
234 		else
235 			vips_buf_appendf( buf, "vips_image <unknown>" );
236 
237 		if( !top )
238 			vips_buf_appends( buf, ")" );
239 	}
240 	else if( PEISMANAGED( base ) ) {
241 		Managed *managed;
242 
243 		if( !(managed = PEGETMANAGED( base )) )
244 			vips_buf_appendf( buf, "<NULL managed>" );
245 		else {
246 			vips_buf_appendf( buf, "<%s ",
247 				G_OBJECT_TYPE_NAME( managed ) );
248 			iobject_info( IOBJECT( managed ), buf );
249 			vips_buf_appends( buf, ">" );
250 		}
251 	}
252 	else if( PEISCLASS( base ) ) {
253 		Compile *compile = PEGETCLASSCOMPILE( base );
254 		PElement params;
255 		int i;
256 
257 		if( !top )
258 			vips_buf_appends( buf, "(" );
259 
260 		symbol_qualified_name( compile->sym, buf );
261 
262 		/* Skip over the secrets, then decompile all the args.
263 		 */
264 		PEGETCLASSSECRET( &params, base );
265 		for( i = 0; i < compile->nsecret; i++ ) {
266 			HeapNode *hn = PEGETVAL( &params );
267 
268 			PEPOINTRIGHT( hn, &params );
269 		}
270 
271 		for( i = 0; i < compile->nparam; i++ ) {
272 			HeapNode *hn = PEGETVAL( &params );
273 			HeapNode *sv = GETLEFT( hn );
274 			PElement value;
275 
276 			PEPOINTRIGHT( sv, &value );
277 			vips_buf_appends( buf, " " );
278 			if( !itext_decompile_element( buf, &value, FALSE ) )
279 				return( FALSE );
280 
281 			PEPOINTRIGHT( hn, &params );
282 		}
283 
284 		if( !top )
285 			vips_buf_appends( buf, ")" );
286 
287 	}
288 	else if( PEISSYMREF( base ) )
289 		vips_buf_appends( buf, IOBJECT( PEGETSYMREF( base ) )->name );
290 	else if( PEISTAG( base ) )
291 		vips_buf_appends( buf, PEGETTAG( base ) );
292 	else
293 		graph_pelement( rc->heap, buf, base, TRACE_FUNCTIONS );
294 
295 	return( TRUE );
296 }
297 
298 /* Little wrapper ... used for formatting error messages, etc. FALSE for eval
299  * error.
300  */
301 static gboolean
itext_decompile(Reduce * rc,VipsBuf * buf,PElement * root)302 itext_decompile( Reduce *rc, VipsBuf *buf, PElement *root )
303 {
304 	/* Evaluate and print off values.
305 	 */
306 	if( !reduce_pelement( rc, reduce_spine, root ) )
307 		return( FALSE );
308 
309 	if( !itext_decompile_element( buf, root, TRUE ) && !buf->full )
310 		/* Tally eval failed, and buffer is not full ... must
311 		 * have been an eval error.
312 		 */
313 		return( FALSE );
314 
315 	return( TRUE );
316 }
317 
318 /* Print function for computed values. top is TRUE only for the very top level
319  * output. bracket means we should bracket compound expressions.
320  */
321 static gboolean
itext_add_element(VipsBuf * buf,PElement * base,gboolean top,gboolean bracket)322 itext_add_element( VipsBuf *buf, PElement *base,
323 	gboolean top, gboolean bracket )
324 {
325 	gboolean result;
326 
327 	/* Set the value label for a tally entry.
328 	 */
329 	if( PEISNOVAL( base ) )
330 		vips_buf_appends( buf, _( "no value" ) );
331 	else if( PEISREAL( base ) )
332 		vips_buf_appendf( buf, "%.7g", PEGETREAL( base ) );
333 	else if( PEISBOOL( base ) )
334 		vips_buf_appends( buf, bool_to_char( PEGETBOOL( base ) ) );
335 	else if( PEISCHAR( base ) ) {
336 		vips_buf_appends( buf, "'" );
337 		itext_add_char( (int) PEGETCHAR( base ), buf );
338 		vips_buf_appends( buf, "'" );
339 	}
340 	else if( PEISCOMPLEX( base ) ) {
341 		itext_add_complex( PEGETREALPART( base ), PEGETIMAGPART( base ),
342 			buf );
343 	}
344 	else if( PEISMANAGEDSTRING( base ) ) {
345 		Managedstring *managedstring = PEGETMANAGEDSTRING( base );
346 
347 		if( !top )
348 			vips_buf_appends( buf, "\"" );
349 		vips_buf_appends( buf, managedstring->string );
350 		if( !top )
351 			vips_buf_appends( buf, "\"" );
352 	}
353 	else if( PEISELIST( base ) ) {
354 		vips_buf_appends( buf, "[ ]" );
355 	}
356 	else if( !heap_is_string( base, &result ) )
357 		/* Eval error.
358 		 */
359 		return( FALSE );
360 	else if( result ) {
361 		/* Only generate quotes for non-top-level string objects.
362 		 */
363 		if( !top )
364 			vips_buf_appends( buf, "\"" );
365 
366 		/* Print string contents.
367 		 */
368 		if( heap_map_list( base,
369 			(heap_map_list_fn) itext_add_string, buf, NULL ) )
370 			return( FALSE );
371 
372 		if( !top )
373 			vips_buf_appends( buf, "\"" );
374 	}
375 	else if( PEISLIST( base ) ) {
376 		gboolean first = TRUE;
377 
378 		vips_buf_appends( buf, "[" );
379 		if( heap_map_list( base,
380 			(heap_map_list_fn) itext_add_list, buf, &first ) )
381 			return( FALSE );
382 		vips_buf_appends( buf, "]" );
383 	}
384 	else if( PEISIMAGE( base ) ) {
385 		vips_buf_appendf( buf, "<" );
386 		vips_buf_appendi( buf, imageinfo_get( FALSE, PEGETII( base ) ) );
387 		vips_buf_appendf( buf, ">" );
388 	}
389 	else if( PEISMANAGED( base ) ) {
390 		Managed *managed = PEGETMANAGED( base );
391 
392 		vips_buf_appends( buf, "<" );
393 		iobject_info( IOBJECT( managed ), buf );
394 		vips_buf_appends( buf, ">" );
395 	}
396 	else if( PEISCLASS( base ) ) {
397 		Compile *compile = PEGETCLASSCOMPILE( base );
398 		PElement params;
399 		int i;
400 
401 		if( bracket && compile->nparam )
402 			vips_buf_appends( buf, "(" );
403 
404 		/* Name.
405 		 */
406 		symbol_qualified_name( compile->sym, buf );
407 
408 		/* Skip over the secrets, then value-ize all the args.
409 		 */
410 		PEGETCLASSSECRET( &params, base );
411 		for( i = 0; i < compile->nsecret; i++ ) {
412 			HeapNode *hn = PEGETVAL( &params );
413 
414 			PEPOINTRIGHT( hn, &params );
415 		}
416 
417 		for( i = 0; i < compile->nparam; i++ ) {
418 			HeapNode *hn = PEGETVAL( &params );
419 			HeapNode *sv = GETLEFT( hn );
420 			PElement value;
421 
422 			PEPOINTRIGHT( sv, &value );
423 			vips_buf_appends( buf, " " );
424 			if( !itext_add_element( buf, &value, FALSE, TRUE ) )
425 				return( FALSE );
426 
427 			PEPOINTRIGHT( hn, &params );
428 		}
429 
430 		if( bracket && compile->nparam )
431 			vips_buf_appends( buf, ")" );
432 	}
433 	else if( PEISSYMREF( base ) ) {
434 		Symbol *sym = PEGETSYMREF( base );
435 
436 		if( is_scope( sym ) ) {
437 			vips_buf_appendf( buf, "<scope '" );
438 			symbol_qualified_name( sym, buf );
439 			vips_buf_appendf( buf, "'>" );
440 		}
441 		else {
442 			vips_buf_appendf( buf, "<reference to symbol '" ),
443 			symbol_qualified_name( sym, buf );
444 			vips_buf_appendf( buf, "'>" );
445 		}
446 	}
447 	else if( PEISTAG( base ) )
448 		vips_buf_appendf( buf, ".%s", PEGETTAG( base ) );
449 	else {
450 		vips_buf_appendf( buf, "<" );
451 		vips_buf_appends( buf, _( "function" ) );
452 		vips_buf_appendf( buf, ">" );
453 	}
454 
455 	return( TRUE );
456 }
457 
458 /* Little wrapper ... used for formatting error messages, etc. FALSE for eval
459  * error.
460  */
461 gboolean
itext_value(Reduce * rc,VipsBuf * buf,PElement * root)462 itext_value( Reduce *rc, VipsBuf *buf, PElement *root )
463 {
464 	/* Evaluate and print off values.
465 	 */
466 	if( !reduce_pelement( rc, reduce_spine, root ) )
467 		return( FALSE );
468 
469 	if( !itext_add_element( buf, root, TRUE, FALSE ) && !buf->full )
470 		/* Tally eval failed, and buffer is not full ... must
471 		 * have been an eval error.
472 		 */
473 		return( FALSE );
474 
475 	return( TRUE );
476 }
477 
478 /* Same, but everror on eval fail.
479  */
480 void
itext_value_ev(Reduce * rc,VipsBuf * buf,PElement * root)481 itext_value_ev( Reduce *rc, VipsBuf *buf, PElement *root )
482 {
483 	if( !itext_value( rc, buf, root ) )
484 		reduce_throw( rc );
485 }
486 
487 /* Decompile an Expr.
488  */
489 static gboolean
itext_make_decompiled_string(Expr * expr,VipsBuf * buf)490 itext_make_decompiled_string( Expr *expr, VipsBuf *buf )
491 {
492 	/* Old error on this expression?
493 	 */
494 	if( expr->err ) {
495 		expr_error_get( expr );
496 		return( FALSE );
497 	}
498 
499 	/* Dirty? We can't print dirty values, since we might have pointers
500 	 * to deleted symbols in the heap (if we are dirty because one of our
501 	 * parents has been deleted).
502 
503 	 	FIXME ... this seem a bit restrictive :-( ... could just
504 		block reads of symbol pointers instead?
505 
506 	 */
507 	if( expr->sym->dirty ) {
508 		vips_buf_appendf( buf, _( "Dirty value" ) );
509 		return( TRUE );
510 	}
511 
512 	/* Evaluate and print off values.
513 	 */
514 	if( !itext_decompile( reduce_context, buf, &expr->root ) )
515 		return( FALSE );
516 
517 	return( TRUE );
518 }
519 
520 /* Make a value string from an Expr.
521  */
522 gboolean
itext_make_value_string(Expr * expr,VipsBuf * buf)523 itext_make_value_string( Expr *expr, VipsBuf *buf )
524 {
525 	/* Old error on this expression?
526 	 */
527 	if( expr->err ) {
528 		expr_error_get( expr );
529 		return( FALSE );
530 	}
531 
532 	/* Dirty? We can't print dirty values, since we might have pointers
533 	 * to deleted symbols in the heap (if we are dirty because one of our
534 	 * parents has been deleted).
535 
536 	 	FIXME ... this seem a bit restrictive :-( ... could just
537 		block reads of symbol pointers instead?
538 
539 	 */
540 	if( expr->sym->dirty ) {
541 		vips_buf_appendf( buf, _( "Dirty value" ) );
542 		return( TRUE );
543 	}
544 
545 	/* Evaluate and print off values.
546 	 */
547 	if( !itext_value( reduce_context, buf, &expr->root ) )
548 		return( FALSE );
549 
550 	return( TRUE );
551 }
552 
553 static void *
itext_update_model(Heapmodel * heapmodel)554 itext_update_model( Heapmodel *heapmodel )
555 {
556 	iText *itext = ITEXT( heapmodel );
557         Row *row = HEAPMODEL( itext )->row;
558 	Expr *expr = row->expr;
559 
560 #ifdef DEBUG
561 	printf( "itext_update_model: " );
562 	row_name_print( row );
563 	if( row->sym &&
564 		row->sym->dirty )
565 		printf( " (dirty)" );
566 	printf( "\n" );
567 #endif /*DEBUG*/
568 
569 	vips_buf_set_dynamic( &itext->value, LINELENGTH );
570 	vips_buf_set_dynamic( &itext->decompile, LINELENGTH );
571 	if( expr ) {
572 		if( !itext_make_value_string( expr, &itext->value ) ||
573 			!itext_make_decompiled_string( expr,
574 				&itext->decompile ) )
575 			expr_error_set( expr );
576 	}
577 
578 #ifdef DEBUG
579 	printf( "itext_update_model: " );
580 	row_name_print( row );
581 	printf( " has value: %s\n", vips_buf_all( &itext->value ) );
582 #endif /*DEBUG*/
583 
584 	/* If this is a non-edited row, update the source.
585 	 */
586 	if( !itext->edited || row == row->top_row ) {
587 		const char *new_formula;
588 
589 		if( expr && expr->compile && expr->compile->rhstext )
590 			new_formula = expr->compile->rhstext;
591 		else
592 			new_formula = vips_buf_all( &itext->decompile );
593 
594 		IM_SETSTR( itext->formula_default, new_formula );
595 
596 		/* Don't use itext_set_formula(), as we don't want to set
597 		 * _modified or recomp.
598 		 */
599 		IM_SETSTR( itext->formula, itext->formula_default );
600 	}
601 
602 	return( HEAPMODEL_CLASS( parent_class )->update_model( heapmodel ) );
603 }
604 
605 /* Build param lists.
606  */
607 static void *
itext_update_heap_sub(Symbol * sym,VipsBuf * buf)608 itext_update_heap_sub( Symbol *sym, VipsBuf *buf )
609 {
610 	vips_buf_appendf( buf, "%s ", IOBJECT( sym )->name );
611 
612 	return( NULL );
613 }
614 
615 /* heapmodel->modified is set ... parse, compile, and mark for recomp.
616  */
617 static void *
itext_update_heap(Heapmodel * heapmodel)618 itext_update_heap( Heapmodel *heapmodel )
619 {
620 	iText *itext = ITEXT( heapmodel );
621         Row *row = heapmodel->row;
622 	Expr *expr = row->expr;
623 
624 #ifdef DEBUG
625 	printf( "itext_update_heap: " );
626 	row_name_print( HEAPMODEL( itext )->row );
627 	printf( "\n" );
628 #endif /*DEBUG*/
629 
630 	/* We can have no modified text, but come here anyway. For example, we
631 	 * could try eval, find an error due to an undefined symbol, and have
632 	 * to retry later. Clearing the row error later will mark us modified,
633 	 * even though we have no text of our own.
634 	 */
635 	if( itext->formula ) {
636 		char txt[MAX_STRSIZE];
637 		VipsBuf buf = VIPS_BUF_STATIC( txt );
638 		ParseRhsSyntax syntax;
639 
640 		if( row->sym &&
641 			is_super( row->sym ) ) {
642 			/* A super member ... special syntax.
643 			 */
644 			vips_buf_appendf( &buf, "%s", itext->formula );
645 
646 			syntax = PARSE_SUPER;
647 		}
648 		else {
649 			/* Build a new params + '=' + rhs string.
650 			 */
651 			if( expr->compile )
652 				(void) slist_map( expr->compile->param,
653 					(SListMapFn) itext_update_heap_sub,
654 					&buf );
655 			vips_buf_appendf( &buf, "= %s;", itext->formula );
656 
657 			syntax = PARSE_PARAMS;
658 		}
659 
660 		/* Parse and compile.
661 		 */
662 		expr_error_clear( expr );
663 		attach_input_string( vips_buf_all( &buf ) );
664 		if( !parse_rhs( expr, syntax ) ) {
665 			expr_error_set( expr );
666 			return( heapmodel );
667 		}
668 	}
669 
670 	/* Mark for recomp.
671 	 */
672 	(void) expr_dirty( expr, link_serial_new() );
673 
674 	return( HEAPMODEL_CLASS( parent_class )->update_heap( heapmodel ) );
675 }
676 
677 static void *
itext_clear_edited(Heapmodel * heapmodel)678 itext_clear_edited( Heapmodel *heapmodel )
679 {
680 	iText *itext = ITEXT( heapmodel );
681 
682 #ifdef DEBUG
683 	printf( "itext_clear_edited: " );
684 	row_name_print( HEAPMODEL( itext )->row );
685 	printf( "\n" );
686 #endif /*DEBUG*/
687 
688 	if( itext->edited ) {
689 		itext_set_edited( itext, FALSE );
690 
691 		/*
692 
693 			FIXME ... formula_default is not always set for cloned
694 			edited rows! fix this properly
695 
696 		 */
697 		if( itext->formula_default )
698 			itext_set_formula( itext, itext->formula_default );
699 		else
700 			printf( "itext_clear_edited: FIXME!\n" );
701 
702 		if( heapmodel->row->expr )
703 			expr_dirty( heapmodel->row->expr, link_serial_new() );
704 
705 		/* Don't clear HEAPMODEL( itext )->modified, we want to make
706 		 * sure we re-parse and compile the default value to break any
707 		 * old links we might have.
708 		 */
709 	}
710 
711 	return( HEAPMODEL_CLASS( parent_class )->clear_edited( heapmodel ) );
712 }
713 
714 static void
itext_parent_add(iContainer * child)715 itext_parent_add( iContainer *child )
716 {
717 	iText *itext = ITEXT( child );
718 	Row *row;
719 
720 	g_assert( IS_RHS( child->parent ) );
721 
722 	ICONTAINER_CLASS( parent_class )->parent_add( child );
723 
724 	row = HEAPMODEL( itext )->row;
725 
726 #ifdef DEBUG
727 	printf( "itext_new: " );
728 	row_name_print( row );
729 	printf( "\n" );
730 #endif /*DEBUG*/
731 
732 	/* Top rows default to edited.
733 	 */
734 	if( row == row->top_row )
735 		itext->edited = TRUE;
736 }
737 
738 static gboolean
itext_load(Model * model,ModelLoadState * state,Model * parent,xmlNode * xnode)739 itext_load( Model *model,
740 	ModelLoadState *state, Model *parent, xmlNode *xnode )
741 {
742 	iText *itext = ITEXT( model );
743 
744 	char formula[MAX_STRSIZE];
745 	char formula2[MAX_STRSIZE];
746 
747 	g_assert( IS_RHS( parent ) );
748 
749 	if( get_sprop( xnode, "formula", formula, MAX_STRSIZE ) ) {
750 		model_loadstate_rewrite( state, formula, formula2 );
751 		itext_set_formula( itext, formula2 );
752 		itext_set_edited( itext, TRUE );
753 	}
754 
755 	return( MODEL_CLASS( parent_class )->load( model,
756 		state, parent, xnode ) );
757 }
758 
759 static View *
itext_view_new(Model * model,View * parent)760 itext_view_new( Model *model, View *parent )
761 {
762 	return( itextview_new() );
763 }
764 
765 static xmlNode *
itext_save(Model * model,xmlNode * xnode)766 itext_save( Model *model, xmlNode *xnode )
767 {
768 	iText *itext = ITEXT( model );
769 	Row *row = HEAPMODEL( model )->row;
770 
771 	xmlNode *xthis;
772 
773 	if( !(xthis = MODEL_CLASS( parent_class )->save( model, xnode )) )
774 		return( NULL );
775 
776 	if( itext->edited || row->top_row == row )
777 		if( !set_sprop( xthis, "formula", itext->formula ) )
778 			return( NULL );
779 
780 	return( xthis );
781 }
782 
783 static void
itext_class_init(iTextClass * class)784 itext_class_init( iTextClass *class )
785 {
786 	GObjectClass *gobject_class = (GObjectClass *) class;
787 	iObjectClass *iobject_class = (iObjectClass *) class;
788 	iContainerClass *icontainer_class = (iContainerClass *) class;
789 	ModelClass *model_class = (ModelClass *) class;
790 	HeapmodelClass *heapmodel_class = (HeapmodelClass *) class;
791 
792 	parent_class = g_type_class_peek_parent( class );
793 
794 	/* Create signals.
795 	 */
796 
797 	/* Init methods.
798 	 */
799 	gobject_class->finalize = itext_finalize;
800 
801 	iobject_class->info = itext_info;
802 
803 	icontainer_class->parent_add = itext_parent_add;
804 
805 	model_class->view_new = itext_view_new;
806 	model_class->save = itext_save;
807 	model_class->load = itext_load;
808 
809 	heapmodel_class->update_model = itext_update_model;
810 	heapmodel_class->update_heap = itext_update_heap;
811 	heapmodel_class->clear_edited = itext_clear_edited;
812 
813 	/* Static init.
814 	 */
815 	model_register_loadable( MODEL_CLASS( class ) );
816 }
817 
818 static void
itext_init(iText * itext)819 itext_init( iText *itext )
820 {
821 	Model *model = MODEL( itext );
822 
823 	model->display = FALSE;
824 
825 	itext->formula = NULL;
826 	itext->formula_default = NULL;
827 	vips_buf_init( &itext->value );
828 	vips_buf_init( &itext->decompile );
829 	vips_buf_set_dynamic( &itext->value, LINELENGTH );
830 	vips_buf_set_dynamic( &itext->decompile, LINELENGTH );
831 	itext->edited = FALSE;
832 
833 	/* Some defaults changed in _parent_add() above.
834 	 */
835 }
836 
837 GType
itext_get_type(void)838 itext_get_type( void )
839 {
840 	static GType type = 0;
841 
842 	if( !type ) {
843 		static const GTypeInfo info = {
844 			sizeof( iTextClass ),
845 			NULL,           /* base_init */
846 			NULL,           /* base_finalize */
847 			(GClassInitFunc) itext_class_init,
848 			NULL,           /* class_finalize */
849 			NULL,           /* class_data */
850 			sizeof( iText ),
851 			32,             /* n_preallocs */
852 			(GInstanceInitFunc) itext_init,
853 		};
854 
855 		type = g_type_register_static( TYPE_HEAPMODEL,
856 			"iText", &info, 0 );
857 	}
858 
859 	return( type );
860 }
861 
862 iText *
itext_new(Rhs * rhs)863 itext_new( Rhs *rhs )
864 {
865 	iText *itext;
866 
867 	itext = ITEXT( g_object_new( TYPE_ITEXT, NULL ) );
868 	icontainer_child_add( ICONTAINER( rhs ), ICONTAINER( itext ), -1 );
869 
870 	return( itext );
871 }
872 
873 void
itext_set_edited(iText * itext,gboolean edited)874 itext_set_edited( iText *itext, gboolean edited )
875 {
876 	Heapmodel *heapmodel = HEAPMODEL( itext );
877 
878 	if( itext->edited != edited ) {
879 #ifdef DEBUG
880 		printf( "itext_set_edited: " );
881 		row_name_print( heapmodel->row );
882 		printf( " %s\n", bool_to_char( edited ) );
883 #endif /*DEBUG*/
884 
885 		itext->edited = edited;
886 		iobject_changed( IOBJECT( itext ) );
887 	}
888 
889 	if( edited )
890 		heapmodel_set_modified( heapmodel, TRUE );
891 }
892 
893 gboolean
itext_set_formula(iText * itext,const char * formula)894 itext_set_formula( iText *itext, const char *formula )
895 {
896 	if( !itext->formula || strcmp( itext->formula, formula ) != 0 ) {
897 #ifdef DEBUG
898 		printf( "itext_set_formula: " );
899 		row_name_print( HEAPMODEL( itext )->row );
900 		printf( " \"%s\"\n", formula );
901 #endif /*DEBUG*/
902 
903 		IM_SETSTR( itext->formula, formula );
904 
905 		heapmodel_set_modified( HEAPMODEL( itext ), TRUE );
906 
907 		iobject_changed( IOBJECT( itext ) );
908 
909 		return( TRUE );
910 	}
911 
912 	return( FALSE );
913 }
914