1 /* Manage workspace objects.
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_VERBOSE
32 #define DEBUG
33  */
34 
35 #include "ip.h"
36 
37 static ModelClass *parent_class = NULL;
38 
39 static GSList *workspace_all = NULL;
40 
41 static GSList *workspace_needs_layout = NULL;
42 
43 void
workspace_set_needs_layout(Workspace * ws,gboolean needs_layout)44 workspace_set_needs_layout( Workspace *ws, gboolean needs_layout )
45 {
46 #ifdef DEBUG_VERBOSE
47 	printf( "workspace_set_needs_layout: %p %s %d\n",
48 		ws, NN( IOBJECT( ws )->name ), needs_layout );
49 #endif /*DEBUG_VERBOSE*/
50 
51 	if( !ws->needs_layout &&
52 		needs_layout &&
53 		!ws->in_dispose ) {
54 		g_assert( !g_slist_find( workspace_needs_layout, ws ) );
55 
56 		ws->needs_layout = TRUE;
57 		workspace_needs_layout = g_slist_prepend(
58 			workspace_needs_layout, ws );
59 	}
60 
61 	if( ws->needs_layout && !needs_layout ) {
62 		g_assert( g_slist_find( workspace_needs_layout, ws ) );
63 
64 		ws->needs_layout = FALSE;
65 		workspace_needs_layout = g_slist_remove(
66 			workspace_needs_layout, ws );
67 	}
68 }
69 
70 GSList *
workspace_get_needs_layout()71 workspace_get_needs_layout()
72 {
73 	return( workspace_needs_layout );
74 }
75 
76 Workspacegroup *
workspace_get_workspacegroup(Workspace * ws)77 workspace_get_workspacegroup( Workspace *ws )
78 {
79 	iContainer *parent;
80 
81 	if( (parent = ICONTAINER( ws )->parent) )
82 		return( WORKSPACEGROUP( parent ) );
83 
84 	return( NULL );
85 }
86 
87 Workspaceroot *
workspace_get_workspaceroot(Workspace * ws)88 workspace_get_workspaceroot( Workspace *ws )
89 {
90 	return( workspace_get_workspacegroup( ws )->wsr );
91 }
92 
93 void
workspace_set_modified(Workspace * ws,gboolean modified)94 workspace_set_modified( Workspace *ws, gboolean modified )
95 {
96 	Workspacegroup *wsg;
97 
98 	if( (wsg = workspace_get_workspacegroup( ws )) )
99 		filemodel_set_modified( FILEMODEL( wsg ), modified );
100 }
101 
102 static void *
workspace_map_sub(Workspacegroup * wsg,workspace_map_fn fn,void * a,void * b)103 workspace_map_sub( Workspacegroup *wsg, workspace_map_fn fn, void *a, void *b )
104 {
105 	g_assert( IS_WORKSPACEGROUP( wsg ) );
106 
107 	return( icontainer_map( ICONTAINER( wsg ),
108 		(icontainer_map_fn) fn, a, b ) );
109 }
110 
111 /* Over all workspaces.
112  */
113 void *
workspace_map(workspace_map_fn fn,void * a,void * b)114 workspace_map( workspace_map_fn fn, void *a, void *b )
115 {
116 	return( icontainer_map3( ICONTAINER( main_workspaceroot ),
117 		(icontainer_map3_fn) workspace_map_sub,
118 		fn, a, b ) );
119 }
120 
121 /* Map across the columns in a workspace.
122  */
123 void *
workspace_map_column(Workspace * ws,column_map_fn fn,void * a)124 workspace_map_column( Workspace *ws, column_map_fn fn, void *a )
125 {
126 	return( icontainer_map( ICONTAINER( ws ),
127 		(icontainer_map_fn) fn, a, NULL ) );
128 }
129 
130 /* Map across a Workspace, applying to the symbols of the top-level rows.
131  */
132 void *
workspace_map_symbol(Workspace * ws,symbol_map_fn fn,void * a)133 workspace_map_symbol( Workspace *ws, symbol_map_fn fn, void *a )
134 {
135 	return( icontainer_map( ICONTAINER( ws ),
136 		(icontainer_map_fn) column_map_symbol, (void *) fn, a ) );
137 }
138 
139 static void *
workspace_is_empty_sub(Symbol * sym)140 workspace_is_empty_sub( Symbol *sym )
141 {
142 	return( sym );
143 }
144 
145 /* Does a workspace contain no rows?
146  */
147 gboolean
workspace_is_empty(Workspace * ws)148 workspace_is_empty( Workspace *ws )
149 {
150 	return( workspace_map_symbol( ws,
151 		(symbol_map_fn) workspace_is_empty_sub, NULL ) == NULL );
152 }
153 
154 /* Map a function over all selected rows in a workspace.
155  */
156 void *
workspace_selected_map(Workspace * ws,row_map_fn fn,void * a,void * b)157 workspace_selected_map( Workspace *ws, row_map_fn fn, void *a, void *b )
158 {
159 	return( slist_map2( ws->selected, (SListMap2Fn) fn, a, b ) );
160 }
161 
162 static void *
workspace_selected_map_sym_sub(Row * row,symbol_map_fn fn,void * a)163 workspace_selected_map_sym_sub( Row *row, symbol_map_fn fn, void *a )
164 {
165 	return( fn( row->sym, a, NULL, NULL ) );
166 }
167 
168 /* Map a function over all selected symbols in a workspace.
169  */
170 void *
workspace_selected_map_sym(Workspace * ws,symbol_map_fn fn,void * a,void * b)171 workspace_selected_map_sym( Workspace *ws,
172 	symbol_map_fn fn, void *a, void *b )
173 {
174 	return( workspace_selected_map( ws,
175 		(row_map_fn) workspace_selected_map_sym_sub, (void *) fn, a ) );
176 }
177 
178 /* Are there any selected rows?
179  */
180 gboolean
workspace_selected_any(Workspace * ws)181 workspace_selected_any( Workspace *ws )
182 {
183 	return( ws->selected != NULL );
184 }
185 
186 /* Number of selected rows.
187  */
188 int
workspace_selected_num(Workspace * ws)189 workspace_selected_num( Workspace *ws )
190 {
191 	return( g_slist_length( ws->selected ) );
192 }
193 
194 static void *
workspace_selected_sym_sub(Row * row,Symbol * sym)195 workspace_selected_sym_sub( Row *row, Symbol *sym )
196 {
197 	if( row->sym == sym )
198 		return( row );
199 
200 	return( NULL );
201 }
202 
203 /* Is sym selected?
204  */
205 gboolean
workspace_selected_sym(Workspace * ws,Symbol * sym)206 workspace_selected_sym( Workspace *ws, Symbol *sym )
207 {
208 	return( workspace_selected_map( ws,
209 		(row_map_fn) workspace_selected_sym_sub, sym, NULL ) != NULL  );
210 }
211 
212 /* Is just one row selected? If yes, return it.
213  */
214 Row *
workspace_selected_one(Workspace * ws)215 workspace_selected_one( Workspace *ws )
216 {
217 	int len = g_slist_length( ws->selected );
218 
219 	if( len == 1 )
220 		return( (Row *)(ws->selected->data) );
221 	else if( len == 0 ) {
222 		error_top( _( "No objects selected." ) );
223 		error_sub( _( "Select exactly one object and try again." ) );
224 		return( NULL );
225 	}
226 	else {
227 		error_top( _( "More than one object selected." ) );
228 		error_sub( _( "Select exactly one object and try again." ) );
229 		return( NULL );
230 	}
231 }
232 
233 static void *
workspace_deselect_all_sub(Column * col)234 workspace_deselect_all_sub( Column *col )
235 {
236 	col->last_select = NULL;
237 
238 	return( NULL );
239 }
240 
241 /* Deselect all rows.
242  */
243 void
workspace_deselect_all(Workspace * ws)244 workspace_deselect_all( Workspace *ws )
245 {
246 	(void) workspace_selected_map( ws,
247 		(row_map_fn) row_deselect, NULL, NULL );
248 	(void) workspace_map_column( ws,
249 		(column_map_fn) workspace_deselect_all_sub, NULL );
250 }
251 
252 /* Track this while we build a names list.
253  */
254 typedef struct {
255 	VipsBuf *buf;
256 	const char *separator;
257 	gboolean first;
258 } NamesInfo;
259 
260 /* Add a name to a string for a symbol.
261  */
262 static void *
workspace_selected_names_sub(Row * row,NamesInfo * names)263 workspace_selected_names_sub( Row *row, NamesInfo *names )
264 {
265 	if( !names->first )
266 		vips_buf_appends( names->buf, names->separator );
267 
268 	/* Hack: if this is a matrix with selected cells, use an extract to
269 	 * get those cells out. We should really have a row method for this I
270 	 * guess :-(
271 	 */
272 	if( row->child_rhs && row->child_rhs->graphic &&
273 		IS_MATRIX( row->child_rhs->graphic ) &&
274 		MATRIX( row->child_rhs->graphic )->selected ) {
275 		Matrix *matrix = MATRIX( row->child_rhs->graphic );
276 
277 		vips_buf_appends( names->buf, "(" );
278 		row_qualified_name( row, names->buf );
279 		vips_buf_appendf( names->buf, ".extract %d %d %d %d)",
280 			matrix->range.left,
281 			matrix->range.top,
282 			matrix->range.width,
283 			matrix->range.height );
284 	}
285 	else
286 		row_qualified_name( row, names->buf );
287 
288 	names->first = FALSE;
289 
290 	return( NULL );
291 }
292 
293 /* Add a list of selected symbol names to a string.
294  */
295 void
workspace_selected_names(Workspace * ws,VipsBuf * buf,const char * separator)296 workspace_selected_names( Workspace *ws, VipsBuf *buf, const char *separator )
297 {
298 	NamesInfo names;
299 
300         names.buf = buf;
301         names.separator = separator;
302         names.first = TRUE;
303 
304 	(void) workspace_selected_map( ws,
305 		(row_map_fn) workspace_selected_names_sub, &names, NULL );
306 }
307 
308 void
workspace_column_names(Column * col,VipsBuf * buf,const char * separator)309 workspace_column_names( Column *col, VipsBuf *buf, const char *separator )
310 {
311 	NamesInfo names;
312 
313         names.buf = buf;
314         names.separator = separator;
315         names.first = TRUE;
316 
317 	(void) column_map( col,
318 		(row_map_fn) workspace_selected_names_sub, &names, NULL );
319 }
320 
321 /* Select all objects in all columns.
322  */
323 void
workspace_select_all(Workspace * ws)324 workspace_select_all( Workspace *ws )
325 {
326 	(void) icontainer_map( ICONTAINER( ws ),
327 		(icontainer_map_fn) column_select_symbols, NULL, NULL );
328 }
329 
330 /* Is there just one column, and is it empty?
331  */
332 Column *
workspace_is_one_empty(Workspace * ws)333 workspace_is_one_empty( Workspace *ws )
334 {
335 	GSList *children = ICONTAINER( ws )->children;
336 	Column *col;
337 
338 	if( g_slist_length( children ) != 1 )
339 		return( NULL );
340 
341 	col = COLUMN( children->data );
342 	if( !column_is_empty( col ) )
343 		return( NULL );
344 
345 	return( col );
346 }
347 
348 /* Search for a column by name.
349  */
350 Column *
workspace_column_find(Workspace * ws,const char * name)351 workspace_column_find( Workspace *ws, const char *name )
352 {
353 	Model *model;
354 
355 	if( !(model = icontainer_map( ICONTAINER( ws ),
356 		(icontainer_map_fn) iobject_test_name, (void *) name, NULL )) )
357 		return( NULL );
358 
359 	return( COLUMN( model ) );
360 }
361 
362 /* Return the column for a name ... an existing column, or a new one.
363  */
364 Column *
workspace_column_get(Workspace * ws,const char * name)365 workspace_column_get( Workspace *ws, const char *name )
366 {
367 	Column *col;
368 
369 	/* Exists?
370 	 */
371 	if( (col = workspace_column_find( ws, name )) )
372 		return( col );
373 
374 	/* No - build new column and return a pointer to that.
375 	 */
376 	return( column_new( ws, name ) );
377 }
378 
379 /* Make up a new column name. Check for not already in workspace.
380  */
381 void
workspace_column_name_new(Workspace * ws,char * name)382 workspace_column_name_new( Workspace *ws, char *name )
383 {
384 	do {
385 		number_to_string( ws->next++, name );
386 	} while( workspace_column_find( ws, name ) );
387 }
388 
389 Column *
workspace_get_column(Workspace * ws)390 workspace_get_column( Workspace *ws )
391 {
392 	if( ICONTAINER( ws )->current )
393 		return( COLUMN( ICONTAINER( ws )->current ) );
394 
395 	return( NULL );
396 }
397 
398 /* Select a column. Can select NULL for no current col in this ws.
399  */
400 void
workspace_column_select(Workspace * ws,Column * col)401 workspace_column_select( Workspace *ws, Column *col )
402 {
403 	icontainer_current( ICONTAINER( ws ), ICONTAINER( col ) );
404 }
405 
406 /* Make sure we have a column selected ... pick one of the existing columns; if
407  * there are none, make a column.
408  */
409 Column *
workspace_column_pick(Workspace * ws)410 workspace_column_pick( Workspace *ws )
411 {
412 	Column *col;
413 
414 	if( (col = workspace_get_column( ws )) )
415 		return( col );
416 	if( (col = COLUMN( icontainer_get_nth_child(
417 		ICONTAINER( ws ), 0 ) )) ) {
418 		workspace_column_select( ws, col );
419 		return( col );
420 	}
421 
422 	/* Make an empty column ... always at the top left.
423 	 */
424 	col = column_new( ws, "A" );
425 	col->x = WORKSPACEVIEW_MARGIN_LEFT;
426 	col->y = WORKSPACEVIEW_MARGIN_TOP;
427 	workspace_column_select( ws, col );
428 
429 	return( col );
430 }
431 
432 /* Make and select a column. Used for "new column" UI actions.
433  */
434 Column *
workspace_column_new(Workspace * ws)435 workspace_column_new( Workspace *ws )
436 {
437 	char new_name[MAX_STRSIZE];
438 	Column *old_col;
439 	Column *col;
440 
441 	workspace_column_name_new( ws, new_name );
442 	if( !(col = column_new( ws, new_name )) )
443 		return( NULL );
444 
445 	/* Position just to right of currently selected column.
446 	 */
447 	if( (old_col = workspace_get_column( ws )) ) {
448 		col->x = old_col->x + 50;
449 		col->y = old_col->y;
450 	}
451 
452 	workspace_column_select( ws, col );
453 	column_scrollto( col, MODEL_SCROLL_TOP );
454 
455 	return( col );
456 }
457 
458 /* Make a new symbol, part of the current column.
459  */
460 static Symbol *
workspace_add_symbol(Workspace * ws)461 workspace_add_symbol( Workspace *ws )
462 {
463 	Column *col = workspace_column_pick( ws );
464 	Symbol *sym;
465 	char *name;
466 
467 	name = column_name_new( col );
468 	sym = symbol_new( ws->sym->expr->compile, name );
469 	IM_FREE( name );
470 
471 	return( sym );
472 }
473 
474 /* Make up a new definition.
475  */
476 Symbol *
workspace_add_def(Workspace * ws,const char * str)477 workspace_add_def( Workspace *ws, const char *str )
478 {
479 	Column *col = workspace_column_pick( ws );
480 	Symbol *sym;
481 	char *name;
482 
483 #ifdef DEBUG
484 	printf( "workspace_add_def: %s\n", str );
485 #endif /*DEBUG*/
486 
487         if( !str || strspn( str, WHITESPACE ) == strlen( str ) )
488 		return( NULL );
489 
490 	/* Try parsing as a "fred = 12" style def.
491 	 */
492 	attach_input_string( str );
493 	if( (name = parse_test_define()) ) {
494 		sym = symbol_new( ws->sym->expr->compile, name );
495 		IM_FREE( name );
496 		attach_input_string( str +
497 			IM_CLIP( 0, input_state.charpos - 1, strlen( str ) ) );
498 	}
499 	else {
500 		/* That didn't work. Make a sym from the col name.
501 		 */
502 		sym = workspace_add_symbol( ws );
503 		attach_input_string( str );
504 	}
505 
506 	if( !symbol_user_init( sym ) ||
507 		!parse_rhs( sym->expr, PARSE_RHS ) ) {
508 		/* Another parse error.
509 		 */
510 		expr_error_get( sym->expr );
511 
512 		/* Block changes to error_string ... symbol_destroy()
513 		 * can set this for compound objects.
514 		 */
515 		error_block();
516 		IDESTROY( sym );
517 		error_unblock();
518 
519 		return( NULL );
520 	}
521 
522 	/* If we're redefining a sym, it might have a row already.
523 	 */
524 	if( !sym->expr->row )
525 		(void) row_new( col->scol, sym, &sym->expr->root );
526 	symbol_made( sym );
527 	workspace_set_modified( ws, TRUE );
528 
529 	return( sym );
530 }
531 
532 /* Make up a new definition, recalc and scroll to make it visible.
533  */
534 Symbol *
workspace_add_def_recalc(Workspace * ws,const char * str)535 workspace_add_def_recalc( Workspace *ws, const char *str )
536 {
537 	Column *col = workspace_column_pick( ws );
538 
539 	Symbol *sym;
540 
541 #ifdef DEBUG
542 	printf( "workspace_add_def_recalc: %s\n", str );
543 #endif /*DEBUG*/
544 
545 	if( !(sym = workspace_add_def( ws, str )) )
546 		return( NULL );
547 
548 	if( !symbol_recalculate_check( sym ) ) {
549 		/* Eval error.
550 		 */
551 		expr_error_get( sym->expr );
552 		error_block();
553 		IDESTROY( sym );
554 		error_unblock();
555 
556 		return( NULL );
557 	}
558 
559 	/* Jump to column containing object.
560 	 */
561 	column_scrollto( col, MODEL_SCROLL_BOTTOM );
562 
563 	return( sym );
564 }
565 
566 gboolean
workspace_load_file_buf(VipsBuf * buf,const char * filename)567 workspace_load_file_buf( VipsBuf *buf, const char *filename )
568 {
569 	if( callv_string_filenamef(
570 		(callv_string_fn) vips_format_for_file,
571 		"%s", filename ) )
572 		vips_buf_appends( buf, "Image_file" );
573 	else
574 		vips_buf_appends( buf, "Matrix_file" );
575 
576 	vips_buf_appends( buf, " \"" );
577 	vips_buf_appendsc( buf, TRUE, filename );
578 	vips_buf_appends( buf, "\"" );
579 
580 	return( TRUE );
581 }
582 
583 /* Load a matrix or image. Don't recalc: you need to recalc later to test for
584  * success/fail. See eg. workspace_add_def_recalc()
585  */
586 Symbol *
workspace_load_file(Workspace * ws,const char * filename)587 workspace_load_file( Workspace *ws, const char *filename )
588 {
589 	char txt[MAX_STRSIZE];
590 	VipsBuf buf = VIPS_BUF_STATIC( txt );
591 	Symbol *sym;
592 
593 	if( !workspace_load_file_buf( &buf, filename ) )
594 		return( NULL );
595 	if( !(sym = workspace_add_def( ws, vips_buf_all( &buf ) )) )
596 		return( NULL );
597 	mainw_recent_add( &mainw_recent_image, filename );
598 
599 	return( sym );
600 }
601 
602 static void
workspace_dispose(GObject * gobject)603 workspace_dispose( GObject *gobject )
604 {
605 	Workspace *ws;
606 
607 #ifdef DEBUG
608 	printf( "workspace_dispose: %p %s\n",
609 		gobject, NN( IOBJECT( gobject )->name ) );
610 #endif /*DEBUG*/
611 
612 	g_return_if_fail( gobject != NULL );
613 	g_return_if_fail( IS_WORKSPACE( gobject ) );
614 
615 	ws = WORKSPACE( gobject );
616 
617 	workspace_set_needs_layout( ws, FALSE );
618 	ws->in_dispose = TRUE;
619 
620 	UNREF( ws->kitg );
621 	UNREF( ws->local_kitg );
622 	IDESTROY( ws->sym );
623 
624 	G_OBJECT_CLASS( parent_class )->dispose( gobject );
625 }
626 
627 static void
workspace_finalize(GObject * gobject)628 workspace_finalize( GObject *gobject )
629 {
630 	Workspace *ws;
631 
632 #ifdef DEBUG
633 	printf( "workspace_finalize: %p %s\n",
634 		gobject, NN( IOBJECT( gobject )->name ) );
635 #endif /*DEBUG*/
636 
637 	g_return_if_fail( gobject != NULL );
638 	g_return_if_fail( IS_WORKSPACE( gobject ) );
639 
640 	ws = WORKSPACE( gobject );
641 
642 	IM_FREE( ws->status );
643 	IM_FREE( ws->local_defs );
644 
645 	workspace_all = g_slist_remove( workspace_all, ws );
646 
647 	G_OBJECT_CLASS( parent_class )->finalize( gobject );
648 }
649 
650 static void
workspace_changed(iObject * iobject)651 workspace_changed( iObject *iobject )
652 {
653 	Workspace *ws;
654 	Workspacegroup *wsg;
655 
656 #ifdef DEBUG_VERBOSE
657 	printf( "workspace_changed: %s\n", NN( iobject->name ) );
658 #endif /*DEBUG_VERBOSE*/
659 
660 	g_return_if_fail( iobject != NULL );
661 	g_return_if_fail( IS_WORKSPACE( iobject ) );
662 
663 	ws = WORKSPACE( iobject );
664 	wsg = workspace_get_workspacegroup( ws );
665 
666 	/* Signal changed on our workspacegroup, if we're the current object.
667 	 */
668 	if( wsg &&
669 		ICONTAINER( wsg )->current == ICONTAINER( iobject ) )
670 		iobject_changed( IOBJECT( wsg ) );
671 
672 	IOBJECT_CLASS( parent_class )->changed( iobject );
673 }
674 
675 static void
workspace_child_add(iContainer * parent,iContainer * child,int pos)676 workspace_child_add( iContainer *parent, iContainer *child, int pos )
677 {
678 	Workspace *ws = WORKSPACE( parent );
679 	Column *col = COLUMN( child );
680 
681 	ICONTAINER_CLASS( parent_class )->child_add( parent, child, pos );
682 
683 	if( col->selected )
684 		workspace_column_select( ws, col );
685 }
686 
687 static void
workspace_child_remove(iContainer * parent,iContainer * child)688 workspace_child_remove( iContainer *parent, iContainer *child )
689 {
690 	Workspace *ws = WORKSPACE( parent );
691 
692 	workspace_set_modified( ws, TRUE );
693 
694 	ICONTAINER_CLASS( parent_class )->child_remove( parent, child );
695 }
696 
697 static void
workspace_current(iContainer * parent,iContainer * child)698 workspace_current( iContainer *parent, iContainer *child )
699 {
700 	Workspace *ws = WORKSPACE( parent );
701 	Column *col = COLUMN( child );
702 	Column *current = workspace_get_column( ws );
703 
704 	if( current )
705 		current->selected = FALSE;
706 	if( col )
707 		col->selected = TRUE;
708 
709 	ICONTAINER_CLASS( parent_class )->current( parent, child );
710 }
711 
712 static void
workspace_link(Workspace * ws,Workspacegroup * wsg,const char * name)713 workspace_link( Workspace *ws, Workspacegroup *wsg, const char *name )
714 {
715 	Workspaceroot *wsr = wsg->wsr;
716 
717 	Symbol *sym;
718 
719 #ifdef DEBUG
720 	printf( "workspace_link: naming ws %p as %s\n", ws, name );
721 #endif /*DEBUG*/
722 
723 	sym = symbol_new_defining( wsr->sym->expr->compile, name );
724 
725 	ws->sym = sym;
726 	sym->type = SYM_WORKSPACE;
727 	sym->ws = ws;
728 	sym->expr = expr_new( sym );
729 	(void) compile_new( sym->expr );
730 	symbol_made( sym );
731 	iobject_set( IOBJECT( ws ), name, NULL );
732 
733 	ws->local_kitg = toolkitgroup_new( ws->sym );
734 	g_object_ref( G_OBJECT( ws->local_kitg ) );
735 	iobject_sink( IOBJECT( ws->local_kitg ) );
736 }
737 
738 static const char *
workspacemode_to_char(WorkspaceMode mode)739 workspacemode_to_char( WorkspaceMode mode )
740 {
741 	switch( mode ) {
742 	case WORKSPACE_MODE_REGULAR:
743 		return( "WORKSPACE_MODE_REGULAR" );
744 
745 	case WORKSPACE_MODE_FORMULA:
746 		return( "WORKSPACE_MODE_FORMULA" );
747 
748 	case WORKSPACE_MODE_NOEDIT:
749 		return( "WORKSPACE_MODE_NOEDIT" );
750 
751 	default:
752 		return( NULL );
753 	}
754 }
755 
756 static WorkspaceMode
char_to_workspacemode(const char * mode)757 char_to_workspacemode( const char *mode )
758 {
759 	if( strcasecmp( mode, "WORKSPACE_MODE_REGULAR" ) == 0 )
760 		return( WORKSPACE_MODE_REGULAR );
761 	else if( strcasecmp( mode, "WORKSPACE_MODE_FORMULA" ) == 0 )
762 		return( WORKSPACE_MODE_FORMULA );
763 	else if( strcasecmp( mode, "WORKSPACE_MODE_NOEDIT" ) == 0 )
764 		return( WORKSPACE_MODE_NOEDIT );
765 	else
766 		return( (WorkspaceMode) -1 );
767 }
768 
769 static View *
workspace_view_new(Model * model,View * parent)770 workspace_view_new( Model *model, View *parent )
771 {
772 	return( workspaceview_new() );
773 }
774 
775 static gboolean
workspace_load(Model * model,ModelLoadState * state,Model * parent,xmlNode * xnode)776 workspace_load( Model *model,
777 	ModelLoadState *state, Model *parent, xmlNode *xnode )
778 {
779 	Workspace *ws = WORKSPACE( model );
780 	char buf[FILENAME_MAX];
781 	char *txt;
782 
783 	g_assert( IS_WORKSPACEGROUP( parent ) );
784 
785 	/* "view" is optional, for backwards compatibility.
786 	 */
787 	if( get_sprop( xnode, "view", buf, FILENAME_MAX ) ) {
788 		WorkspaceMode mode = char_to_workspacemode( buf );
789 
790 		if( (int) mode >= 0 )
791 			/* Could call workspace_set_mode(), but this is only a
792 			 * load, so so what.
793 			 */
794 			ws->mode = mode;
795 	}
796 
797 	/* Also optional.
798 	 */
799 	(void) get_dprop( xnode, "scale", &ws->scale );
800 	(void) get_dprop( xnode, "offset", &ws->offset );
801 
802 	(void) get_bprop( xnode, "locked", &ws->locked );
803 
804 	(void) get_bprop( xnode, "lpane_open", &ws->lpane_open );
805 	(void) get_iprop( xnode, "lpane_position", &ws->lpane_position );
806 	(void) get_bprop( xnode, "rpane_open", &ws->rpane_open );
807 	(void) get_iprop( xnode, "rpane_position", &ws->rpane_position );
808 
809 	if( get_sprop( xnode, "name", buf, FILENAME_MAX ) ) {
810 		IM_SETSTR( IOBJECT( ws )->name, buf );
811 	}
812 	if( get_sprop( xnode, "caption", buf, FILENAME_MAX ) ) {
813 		IM_SETSTR( IOBJECT( ws )->caption, buf );
814 	}
815 
816 	/* Don't use get_sprop() and avoid a limit on def size.
817 	 */
818 	if( (txt = (char *) xmlGetProp( xnode, (xmlChar *) "local_defs" )) ) {
819 		(void) workspace_local_set( ws, txt );
820 		IM_FREEF( xmlFree, txt );
821 	}
822 
823 	(void) get_iprop( xnode, "major", &ws->major );
824 	(void) get_iprop( xnode, "minor", &ws->minor );
825 
826 	if( !MODEL_CLASS( parent_class )->load( model, state, parent, xnode ) )
827 		return( FALSE );
828 
829 	return( TRUE );
830 }
831 
832 static xmlNode *
workspace_save(Model * model,xmlNode * xnode)833 workspace_save( Model *model, xmlNode *xnode )
834 {
835 	Workspace *ws = WORKSPACE( model );
836 	Workspacegroup *wsg = workspace_get_workspacegroup( ws );
837 	xmlNode *xthis;
838 
839 	if( !(xthis = MODEL_CLASS( parent_class )->save( model, xnode )) )
840 		return( NULL );
841 
842 	if( !set_sprop( xthis, "view", workspacemode_to_char( ws->mode ) ) ||
843 		!set_dprop( xthis, "scale", ws->scale ) ||
844 		!set_dprop( xthis, "offset", ws->offset ) ||
845 		!set_sprop( xthis, "locked", bool_to_char( ws->locked ) ) ||
846 		!set_iprop( xthis, "lpane_position", ws->lpane_position ) ||
847 		!set_sprop( xthis, "lpane_open",
848 			bool_to_char( ws->lpane_open ) ) ||
849 		!set_iprop( xthis, "rpane_position", ws->rpane_position ) ||
850 		!set_sprop( xthis, "rpane_open",
851 			bool_to_char( ws->rpane_open ) ) ||
852 		!set_sprop( xthis, "local_defs", ws->local_defs ) ||
853 		!set_sprop( xthis, "name", IOBJECT( ws )->name ) ||
854 		!set_sprop( xthis, "caption", IOBJECT( ws )->caption ) )
855 		return( NULL );
856 
857 	/* We have to save our workspacegroup's filename here for compt with
858 	 * older nip2.
859 	 */
860 	if( !set_sprop( xthis, "filename", FILEMODEL( wsg )->filename ) )
861 		return( NULL );
862 
863 	if( !set_iprop( xthis, "major", ws->major ) ||
864 		!set_iprop( xthis, "minor", ws->minor ) )
865 		return( NULL );
866 
867 	return( xthis );
868 }
869 
870 static void
workspace_empty(Model * model)871 workspace_empty( Model *model )
872 {
873 	Workspace *ws = WORKSPACE( model );
874 
875 	/* Make sure this gets reset.
876 	 */
877 	ws->area.left = 0;
878 	ws->area.top = 0;
879 	ws->area.width = 0;
880 	ws->area.height = 0;
881 
882 	MODEL_CLASS( parent_class )->empty( model );
883 }
884 
885 static void *
workspace_load_toolkit(const char * filename,Toolkitgroup * toolkitgroup)886 workspace_load_toolkit( const char *filename, Toolkitgroup *toolkitgroup )
887 {
888 	if( !toolkit_new_from_file( toolkitgroup, filename ) )
889 		iwindow_alert( NULL, GTK_MESSAGE_ERROR );
890 
891 	return( NULL );
892 }
893 
894 /* The compat modes this version of nip2 has. Search the compat dir and make a
895  * list of these things.
896  */
897 #define MAX_COMPAT (100)
898 static int compat_major[MAX_COMPAT];
899 static int compat_minor[MAX_COMPAT];
900 static int n_compat = 0;
901 
902 static void *
workspace_build_compat_fn(const char * filename)903 workspace_build_compat_fn( const char *filename )
904 {
905 	char *basename;
906 	int major;
907 	int minor;
908 
909 	basename = g_path_get_basename( filename );
910 
911 	if( sscanf( basename, "%d.%d", &major, &minor ) != 2 ) {
912 		g_free( basename );
913 		return( NULL );
914 	}
915 	g_free( basename );
916 
917 	compat_major[n_compat] = major;
918 	compat_minor[n_compat] = minor;
919 	n_compat += 1;
920 
921 #ifdef DEBUG
922 	printf( "workspace_build_compat_fn: found major = %d, minor = %d\n",
923 		major, minor );
924 #endif /*DEBUG*/
925 
926 	return( NULL );
927 }
928 
929 /* Build the list of ws compatibility defs we have.
930  */
931 static void
workspace_build_compat(void)932 workspace_build_compat( void )
933 {
934 	if( n_compat > 0 )
935 		return;
936 
937 	path_map_dir( "$VIPSHOME/share/" PACKAGE "/compat", "*.*",
938 		(path_map_fn) workspace_build_compat_fn, NULL );
939 }
940 
941 /* Given a major/minor (eg. read from a ws header), return non-zero if we have
942  * a set of compat defs.
943  */
944 int
workspace_have_compat(int major,int minor,int * best_major,int * best_minor)945 workspace_have_compat( int major, int minor, int *best_major, int *best_minor )
946 {
947 	int i;
948 	int best;
949 
950 #ifdef DEBUG
951 	printf( "workspace_have_compat: searching for %d.%d\n", major, minor );
952 #endif /*DEBUG*/
953 
954 	/* Sets of ws compatibility defs cover themselves and any earlier
955 	 * releases, as far back as the next set of compat defs. We need to
956 	 * search for the smallest compat version that's greater than the
957 	 * version number in the file.
958 	 */
959 	workspace_build_compat();
960 	best = -1;
961 	for( i = 0; i < n_compat; i++ )
962 		if( major <= compat_major[i] && minor <= compat_minor[i] )
963 			/* Found a possible compat set, is it better than the
964 			 * best we've seen so far?
965 			 */
966 			if( best == -1 ||
967 				compat_major[i] < compat_major[best] ||
968 				compat_minor[i] < compat_minor[best] )
969 				best = i;
970 	if( best == -1 )
971 		return( 0 );
972 
973 #ifdef DEBUG
974 	printf( "\tfound %d.%d\n", compat_major[best], compat_minor[best] );
975 #endif /*DEBUG*/
976 
977 	if( best_major )
978 		*best_major = compat_major[best];
979 	if( best_minor )
980 		*best_minor = compat_minor[best];
981 
982 	return( 1 );
983 }
984 
985 void
workspace_get_version(Workspace * ws,int * major,int * minor)986 workspace_get_version( Workspace *ws, int *major, int *minor )
987 {
988 	*major = ws->major;
989 	*minor = ws->minor;
990 }
991 
992 gboolean
workspace_load_compat(Workspace * ws,int major,int minor)993 workspace_load_compat( Workspace *ws, int major, int minor )
994 {
995 	char pathname[FILENAME_MAX];
996 	GSList *path;
997 	int best_major;
998 	int best_minor;
999 
1000 	if( workspace_have_compat( major, minor, &best_major, &best_minor ) ) {
1001 		/* Make a private toolkitgroup local to this workspace to
1002 		 * hold the compatibility defs we are planning to load.
1003 		 */
1004 		UNREF( ws->kitg );
1005 		ws->kitg = toolkitgroup_new( ws->sym );
1006 		g_object_ref( G_OBJECT( ws->kitg ) );
1007 		iobject_sink( IOBJECT( ws->kitg ) );
1008 
1009 		im_snprintf( pathname, FILENAME_MAX,
1010 			"$VIPSHOME/share/" PACKAGE "/compat/%d.%d",
1011 			best_major, best_minor );
1012 		path = path_parse( pathname );
1013 		if( path_map( path, "*.def",
1014 			(path_map_fn) workspace_load_toolkit, ws->kitg ) ) {
1015 			path_free2( path );
1016 			return( FALSE );
1017 		}
1018 		path_free2( path );
1019 
1020 #ifdef DEBUG
1021 		printf( "workspace_load_compat: loaded %d.%d\n",
1022 			best_major, best_minor );
1023 #endif /*DEBUG*/
1024 
1025 		ws->compat_major = best_major;
1026 		ws->compat_minor = best_minor;
1027 	}
1028 	else {
1029 #ifdef DEBUG
1030 		printf( "workspace_load_compat: no compat necessary\n" );
1031 #endif /*DEBUG*/
1032 
1033 		/* No compat defs necessary for this ws.
1034 		 */
1035 		ws->compat_major = 0;
1036 		ws->compat_minor = 0;
1037 	}
1038 
1039 	return( TRUE );
1040 }
1041 
1042 static void
workspace_class_init(WorkspaceClass * class)1043 workspace_class_init( WorkspaceClass *class )
1044 {
1045 	GObjectClass *gobject_class = G_OBJECT_CLASS( class );
1046 	iObjectClass *iobject_class = IOBJECT_CLASS( class );
1047 	iContainerClass *icontainer_class = (iContainerClass *) class;
1048 	ModelClass *model_class = (ModelClass *) class;
1049 
1050 	parent_class = g_type_class_peek_parent( class );
1051 
1052 	/* Create signals.
1053 	 */
1054 
1055 	/* Init methods.
1056 	 */
1057 	gobject_class->dispose = workspace_dispose;
1058 	gobject_class->finalize = workspace_finalize;
1059 
1060 	iobject_class->changed = workspace_changed;
1061 	iobject_class->user_name = _( "Tab" );
1062 
1063 	icontainer_class->child_add = workspace_child_add;
1064 	icontainer_class->child_remove = workspace_child_remove;
1065 	icontainer_class->current = workspace_current;
1066 
1067 	model_class->view_new = workspace_view_new;
1068 	model_class->load = workspace_load;
1069 	model_class->save = workspace_save;
1070 	model_class->empty = workspace_empty;
1071 
1072 	/* Static init.
1073 	 */
1074 	model_register_loadable( MODEL_CLASS( class ) );
1075 }
1076 
1077 static void
workspace_init(Workspace * ws)1078 workspace_init( Workspace *ws )
1079 {
1080 	ws->sym = NULL;
1081 
1082 	/* We default to using the main toolkitgroup for our definitions.
1083 	 * Unref and load private defs if we need compatibility.
1084 	 */
1085 	ws->kitg = main_toolkitgroup;
1086 	g_object_ref( G_OBJECT( ws->kitg ) );
1087 
1088 	ws->next = 0;
1089 	ws->selected = NULL;
1090 	ws->errors = NULL;
1091         ws->mode = WORKSPACE_MODE_REGULAR;
1092 
1093 	ws->major = MAJOR_VERSION;
1094 	ws->minor = MINOR_VERSION;
1095 
1096 	ws->compat_major = 0;
1097 	ws->compat_minor = 0;
1098 
1099 	ws->area.left = 0;
1100 	ws->area.top = 0;
1101 	ws->area.width = 0;
1102 	ws->area.height = 0;
1103 	ws->vp = ws->area;
1104 
1105 	ws->lpane_open = WORKSPACE_LPANE_OPEN;
1106 	ws->lpane_position = WORKSPACE_LPANE_POSITION;
1107 	ws->rpane_open = WORKSPACE_RPANE_OPEN;
1108 	ws->rpane_position = WORKSPACE_RPANE_POSITION;
1109 
1110 	ws->status = NULL;
1111 
1112 	ws->scale = 1.0;
1113 	ws->offset = 0.0;
1114 
1115 	ws->local_defs = im_strdupn( _(
1116 		"// private definitions for this tab\n" ) );
1117 	ws->local_kitg = NULL;
1118 	ws->local_kit = NULL;
1119 
1120 	workspace_all = g_slist_prepend( workspace_all, ws );
1121 }
1122 
1123 GType
workspace_get_type(void)1124 workspace_get_type( void )
1125 {
1126 	static GType workspace_type = 0;
1127 
1128 	if( !workspace_type ) {
1129 		static const GTypeInfo info = {
1130 			sizeof( WorkspaceClass ),
1131 			NULL,           /* base_init */
1132 			NULL,           /* base_finalize */
1133 			(GClassInitFunc) workspace_class_init,
1134 			NULL,           /* class_finalize */
1135 			NULL,           /* class_data */
1136 			sizeof( Workspace ),
1137 			32,             /* n_preallocs */
1138 			(GInstanceInitFunc) workspace_init,
1139 		};
1140 
1141 		workspace_type = g_type_register_static( TYPE_MODEL,
1142 			"Workspace", &info, 0 );
1143 	}
1144 
1145 	return( workspace_type );
1146 }
1147 
1148 Workspace *
workspace_new(Workspacegroup * wsg,const char * name)1149 workspace_new( Workspacegroup *wsg, const char *name )
1150 {
1151 	Workspaceroot *wsr = wsg->wsr;
1152 
1153 	Workspace *ws;
1154 
1155 #ifdef DEBUG
1156 	printf( "workspace_new: %s\n", name );
1157 #endif /*DEBUG*/
1158 
1159 	if( compile_lookup( wsr->sym->expr->compile, name ) ) {
1160 		error_top( _( "Name clash." ) );
1161 		error_sub( _( "Can't create workspace \"%s\". "
1162 			"A symbol with that name already exists." ), name );
1163 		return( NULL );
1164 	}
1165 
1166 	ws = WORKSPACE( g_object_new( TYPE_WORKSPACE, NULL ) );
1167 	workspace_link( ws, wsg, name );
1168 	icontainer_child_add( ICONTAINER( wsg ), ICONTAINER( ws ), -1 );
1169 
1170 	return( ws );
1171 }
1172 
1173 /* Make the blank workspace we present the user with (in the absence of
1174  * anything else).
1175  */
1176 Workspace *
workspace_new_blank(Workspacegroup * wsg)1177 workspace_new_blank( Workspacegroup *wsg )
1178 {
1179 	char name[256];
1180 	Workspace *ws;
1181 
1182 	workspaceroot_name_new( wsg->wsr, name );
1183 	if( !(ws = workspace_new( wsg, name )) )
1184 		return( NULL );
1185 
1186 	/* Make an empty column.
1187 	 */
1188 	(void) workspace_column_pick( ws );
1189 
1190 	icontainer_current( ICONTAINER( wsg ), ICONTAINER( ws ) );
1191 
1192 	iobject_set( IOBJECT( ws ), NULL, _( "Default empty tab" ) );
1193 
1194 	return( ws );
1195 }
1196 
1197 /* Get the bottom row from the current column.
1198  */
1199 static Row *
workspace_get_bottom(Workspace * ws)1200 workspace_get_bottom( Workspace *ws )
1201 {
1202 	return( column_get_bottom( workspace_column_pick( ws ) ) );
1203 }
1204 
1205 gboolean
workspace_add_action(Workspace * ws,const char * name,const char * action,int nparam)1206 workspace_add_action( Workspace *ws,
1207 	const char *name, const char *action, int nparam )
1208 {
1209 	Column *col = workspace_column_pick( ws );
1210 	char txt[1024];
1211 	VipsBuf buf = VIPS_BUF_STATIC( txt );
1212 
1213 	/* Are there any selected symbols?
1214 	 */
1215 	vips_buf_appends( &buf, action );
1216 	if( nparam > 0 && workspace_selected_any( ws ) ) {
1217 		if( nparam != workspace_selected_num( ws ) ) {
1218 			error_top( _( "Wrong number of arguments." ) );
1219 			error_sub( _( "%s needs %d arguments, "
1220 				"there are %d selected." ),
1221 				name, nparam,
1222 				workspace_selected_num( ws ) );
1223 			return( FALSE );
1224 		}
1225 
1226 		vips_buf_appends( &buf, " " );
1227 		workspace_selected_names( ws, &buf, " " );
1228 		if( vips_buf_is_full( &buf ) ) {
1229 			error_top( _( "Overflow error." ) );
1230 			error_sub( _( "Too many names selected." ) );
1231 			return( FALSE );
1232 		}
1233 
1234 		if( !workspace_add_def_recalc( ws, vips_buf_all( &buf ) ) )
1235 			return( FALSE );
1236 		workspace_deselect_all( ws );
1237 	}
1238 	else {
1239 		/* Try to use the previous n items in this column as the
1240 		 * arguments.
1241 		 */
1242 		if( !column_add_n_names( col, name, &buf, nparam ) ||
1243 			!workspace_add_def_recalc( ws, vips_buf_all( &buf ) ) )
1244 			return( FALSE );
1245 	}
1246 
1247 	return( TRUE );
1248 }
1249 
1250 int
workspace_number(void)1251 workspace_number( void )
1252 {
1253 	return( g_slist_length( workspace_all ) );
1254 }
1255 
1256 static void *
workspace_row_dirty(Row * row,int serial)1257 workspace_row_dirty( Row *row, int serial )
1258 {
1259 	return( expr_dirty( row->expr, serial ) );
1260 }
1261 
1262 /* Recalculate selected items.
1263  */
1264 gboolean
workspace_selected_recalc(Workspace * ws)1265 workspace_selected_recalc( Workspace *ws )
1266 {
1267 	if( workspace_selected_map( ws,
1268 		(row_map_fn) workspace_row_dirty,
1269 		 GINT_TO_POINTER( link_serial_new() ), NULL ) )
1270 		return( FALSE );
1271 
1272 	/* Recalc even if autorecomp is off.
1273 	 */
1274 	symbol_recalculate_all_force( TRUE );
1275 
1276 	workspace_deselect_all( ws );
1277 
1278 	return( TRUE );
1279 }
1280 
1281 static void *
workspace_selected_remove2(Row * row)1282 workspace_selected_remove2( Row *row )
1283 {
1284 	if( row != row->top_row )
1285 		return( row );
1286 
1287 	return( NULL );
1288 }
1289 
1290 static void *
workspace_selected_remove3(Row * row,int * nsel)1291 workspace_selected_remove3( Row *row, int *nsel )
1292 {
1293 	if( row->selected )
1294 		*nsel += 1;
1295 
1296 	return( NULL );
1297 }
1298 
1299 static void *
workspace_selected_remove4(Column * col,GSList ** cs)1300 workspace_selected_remove4( Column *col, GSList **cs )
1301 {
1302 	int nsel = 0;
1303 
1304 	(void) column_map( col,
1305 		(row_map_fn) workspace_selected_remove3, &nsel, NULL );
1306 	if( nsel > 0 )
1307 		*cs = g_slist_prepend( *cs, col );
1308 
1309 	return( NULL );
1310 }
1311 
1312 static void *
workspace_selected_remove5(Column * col)1313 workspace_selected_remove5( Column *col )
1314 {
1315 	Subcolumn *scol = col->scol;
1316 	int nmembers = g_slist_length( ICONTAINER( scol )->children );
1317 
1318 	if( nmembers > 0 )
1319 		icontainer_pos_renumber( ICONTAINER( scol ) );
1320 	else
1321 		IDESTROY( col );
1322 
1323 	return( NULL );
1324 }
1325 
1326 /* Remove selected items.
1327  *
1328  * 0. check all objects to be destroyed are top level rows
1329  * 1. look for and note all columns containing items we are going to delete
1330  * 2. loop over selected items, and delete them one-by-one.
1331  * 3. loop over the columns we noted in 1 and delete empty ones
1332  * 4. renumber affected columns
1333  */
1334 static gboolean
workspace_selected_remove(Workspace * ws)1335 workspace_selected_remove( Workspace *ws )
1336 {
1337 	Row *row;
1338 	GSList *cs = NULL;
1339 
1340 	if( (row = (Row *) workspace_selected_map( ws,
1341 		(row_map_fn) workspace_selected_remove2, NULL, NULL )) ) {
1342 		error_top( _( "You can only remove top level rows." ) );
1343 		error_sub( _( "Not all selected objects are top level "
1344 			"rows." ) );
1345 		return( FALSE );
1346 	}
1347 
1348 	(void) workspace_map_column( ws,
1349 		(column_map_fn) workspace_selected_remove4, &cs );
1350 	(void) workspace_selected_map_sym( ws,
1351 		(symbol_map_fn) iobject_destroy, NULL, NULL );
1352 	(void) slist_map( cs,
1353 		(SListMapFn) workspace_selected_remove5, NULL );
1354 
1355 	IM_FREEF( g_slist_free, cs );
1356 	symbol_recalculate_all();
1357 	workspace_set_modified( ws, TRUE );
1358 
1359 	return( TRUE );
1360 }
1361 
1362 /* Callback for workspace_selected_remove_yesno. Remove selected items.
1363  */
1364 static void
workspace_selected_remove_yesno_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)1365 workspace_selected_remove_yesno_cb( iWindow *iwnd, void *client,
1366 	iWindowNotifyFn nfn, void *sys )
1367 {
1368 	Workspace *ws = WORKSPACE( client );
1369 
1370 	if( workspace_selected_remove( ws ) )
1371 		nfn( sys, IWINDOW_YES );
1372 	else
1373 		nfn( sys, IWINDOW_ERROR );
1374 }
1375 
1376 /* Ask before removing selected.
1377  */
1378 void
workspace_selected_remove_yesno(Workspace * ws,GtkWidget * parent)1379 workspace_selected_remove_yesno( Workspace *ws, GtkWidget *parent )
1380 {
1381 	char txt[30];
1382 	VipsBuf buf = VIPS_BUF_STATIC( txt );
1383 
1384         if( !workspace_selected_any( ws ) )
1385 		return;
1386 
1387 	workspace_selected_names( ws, &buf, ", " );
1388 
1389 	box_yesno( parent,
1390 		workspace_selected_remove_yesno_cb, iwindow_true_cb, ws,
1391 		iwindow_notify_null, NULL,
1392 		GTK_STOCK_DELETE,
1393 		_( "Delete selected objects?" ),
1394 		_( "Are you sure you want to delete %s?" ), vips_buf_all( &buf ) );
1395 }
1396 
1397 /* Sub fn of below ... add a new index expression.
1398  */
1399 static gboolean
workspace_ungroup_add_index(Row * row,const char * fmt,int i)1400 workspace_ungroup_add_index( Row *row, const char *fmt, int i )
1401 {
1402 	static char txt[200];
1403 	static VipsBuf buf = VIPS_BUF_STATIC( txt );
1404 
1405 	vips_buf_rewind( &buf );
1406 	row_qualified_name( row, &buf );
1407 	vips_buf_appendf( &buf, fmt, i );
1408 	if( !workspace_add_def_recalc( row->ws, vips_buf_all( &buf ) ) )
1409 		return( FALSE );
1410 
1411 	return( TRUE );
1412 }
1413 
1414 static void *
workspace_ungroup_row(Row * row)1415 workspace_ungroup_row( Row *row )
1416 {
1417 	PElement *root = &row->expr->root;
1418 	gboolean result;
1419 	PElement value;
1420 	int length;
1421 	int i;
1422 
1423 	if( !heap_is_instanceof( CLASS_GROUP, root, &result ) )
1424 		return( row );
1425 	if( result ) {
1426 		if( !class_get_member( root, MEMBER_VALUE, NULL, &value ) ||
1427 			(length = heap_list_length_max( &value, 100 )) < 0 )
1428 			return( row );
1429 
1430 		for( i = 0; i < length; i++ )
1431 			if( !workspace_ungroup_add_index( row,
1432 				".value?%d", i ) )
1433 				return( row );
1434 	}
1435 	else {
1436 		if( !heap_is_list( root, &result ) )
1437 			return( row );
1438 		if( result ) {
1439 			if( (length = heap_list_length_max( root, 100 )) < 0 )
1440 				return( row );
1441 
1442 			for( i = 0; i < length; i++ )
1443 				if( !workspace_ungroup_add_index( row,
1444 					"?%d", i ) )
1445 					return( row );
1446 		}
1447 		else {
1448 			char txt[100];
1449 			VipsBuf buf = VIPS_BUF_STATIC( txt );
1450 
1451 			row_qualified_name( row, &buf );
1452 			error_top( _( "Unable to ungroup." ) );
1453 			error_sub( _( "Row \"%s\" is not a Group or a list." ),
1454 				vips_buf_all( &buf ) );
1455 
1456 			return( row );
1457 		}
1458 	}
1459 
1460 	return( NULL );
1461 }
1462 
1463 /* Ungroup the selected object(s), or the bottom object.
1464  */
1465 gboolean
workspace_selected_ungroup(Workspace * ws)1466 workspace_selected_ungroup( Workspace *ws )
1467 {
1468 	if( !workspace_selected_any( ws ) ) {
1469 		Row *row;
1470 
1471 		if( (row = workspace_get_bottom( ws )) ) {
1472 			if( workspace_ungroup_row( row ) )
1473 				return( FALSE );
1474 
1475 			symbol_recalculate_all();
1476 		}
1477 	}
1478 	else {
1479 		/* Ungroup selected symbols.
1480 		 */
1481 		if( workspace_selected_map( ws,
1482 			(row_map_fn) workspace_ungroup_row, NULL, NULL ) ) {
1483 			symbol_recalculate_all();
1484 			return( FALSE );
1485 		}
1486 		symbol_recalculate_all();
1487 		workspace_deselect_all( ws );
1488 	}
1489 
1490 	return( TRUE );
1491 }
1492 
1493 /* Group the selected object(s).
1494  */
1495 gboolean
workspace_selected_group(Workspace * ws)1496 workspace_selected_group( Workspace *ws )
1497 {
1498 	char txt[MAX_STRSIZE];
1499 	VipsBuf buf = VIPS_BUF_STATIC( txt );
1500 
1501 	if( !workspace_selected_any( ws ) ) {
1502 		Row *row;
1503 
1504 		if( (row = workspace_get_bottom( ws )) )
1505 			row_select( row );
1506 	}
1507 
1508 	vips_buf_appends( &buf, "Group [" );
1509 	workspace_selected_names( ws, &buf, "," );
1510 	vips_buf_appends( &buf, "]" );
1511 	if( !workspace_add_def_recalc( ws, vips_buf_all( &buf ) ) )
1512 		return( FALSE );
1513 	workspace_deselect_all( ws );
1514 
1515 	return( TRUE );
1516 }
1517 
1518 static Row *
workspace_test_error(Row * row,Workspace * ws,int * found)1519 workspace_test_error( Row *row, Workspace *ws, int *found )
1520 {
1521 	g_assert( row->err );
1522 
1523 	/* Found next?
1524 	 */
1525 	if( *found )
1526 		return( row );
1527 
1528 	if( row == ws->last_error ) {
1529 		/* Found the last one ... return the next one.
1530 		 */
1531 		*found = 1;
1532 		return( NULL );
1533 	}
1534 
1535 	return( NULL );
1536 }
1537 
1538 /* FALSE for no errors.
1539  */
1540 gboolean
workspace_next_error(Workspace * ws)1541 workspace_next_error( Workspace *ws )
1542 {
1543 	char txt[MAX_LINELENGTH];
1544 	VipsBuf buf = VIPS_BUF_STATIC( txt );
1545 
1546 	int found;
1547 
1548 	if( !ws->errors )
1549 		return( FALSE );
1550 
1551 	/* Search for the one after the last one.
1552 	 */
1553 	found = 0;
1554 	ws->last_error = (Row *) slist_map2( ws->errors,
1555 		(SListMap2Fn) workspace_test_error, ws, &found );
1556 
1557 	/* NULL? We've hit end of table, start again.
1558 	 */
1559 	if( !ws->last_error ) {
1560 		found = 1;
1561 		ws->last_error = (Row *) slist_map2( ws->errors,
1562 			(SListMap2Fn) workspace_test_error, ws, &found );
1563 	}
1564 
1565 	/* *must* have one now.
1566 	 */
1567 	g_assert( ws->last_error && ws->last_error->err );
1568 
1569 	model_scrollto( MODEL( ws->last_error ), MODEL_SCROLL_TOP );
1570 
1571 	row_qualified_name( ws->last_error->expr->row, &buf );
1572 	vips_buf_appends( &buf, ": " );
1573 	vips_buf_appends( &buf, ws->last_error->expr->error_top );
1574 	workspace_set_status( ws, "%s", vips_buf_firstline( &buf ) );
1575 
1576 	return( TRUE );
1577 }
1578 
1579 void
workspace_set_status(Workspace * ws,const char * fmt,...)1580 workspace_set_status( Workspace *ws, const char *fmt, ... )
1581 {
1582 	va_list ap;
1583 	char buf[256];
1584 
1585 	va_start( ap, fmt );
1586 	(void) im_vsnprintf( buf, 256, fmt, ap );
1587 	va_end( ap );
1588 
1589 	IM_SETSTR( ws->status, buf );
1590 	iobject_changed( IOBJECT( ws ) );
1591 }
1592 
1593 void
workspace_set_mode(Workspace * ws,WorkspaceMode mode)1594 workspace_set_mode( Workspace *ws, WorkspaceMode mode )
1595 {
1596 	if( ws->mode != mode ) {
1597 		ws->mode = mode;
1598 
1599 		/* Rebuild all the views. Yuk! It would be better to get the
1600 		 * views that change with workspace mode to watch the
1601 		 * enclosing workspace and update on that. But we'd have
1602 		 * connections from almost every object in the ws. We don't
1603 		 * change mode very often, so just loop over them all.
1604 		 */
1605 		icontainer_map_all( ICONTAINER( ws ),
1606 			(icontainer_map_fn) iobject_changed, NULL );
1607 	}
1608 }
1609 
1610 /* New ws private defs.
1611  */
1612 gboolean
workspace_local_set(Workspace * ws,const char * txt)1613 workspace_local_set( Workspace *ws, const char *txt )
1614 {
1615 	/* New kit for defs ... will destroy any old defs, since we can't have
1616 	 * two kits with the same name. Don't register it, we don't want it
1617 	 * to be autosaved on quit.
1618 	 */
1619 	ws->local_kit = toolkit_new( ws->local_kitg, "Workspace Locals" );
1620 	filemodel_unregister( FILEMODEL( ws->local_kit ) );
1621 	IM_SETSTR( ws->local_defs, txt );
1622 	iobject_changed( IOBJECT( ws ) );
1623 
1624 	workspace_set_modified( ws, TRUE );
1625 	attach_input_string( txt );
1626 	if( !parse_toplevel( ws->local_kit, 0 ) )
1627 		return( FALSE );
1628 
1629 	return( TRUE );
1630 }
1631 
1632 gboolean
workspace_local_set_from_file(Workspace * ws,const char * fname)1633 workspace_local_set_from_file( Workspace *ws, const char *fname )
1634 {
1635 	iOpenFile *of;
1636 	char *txt;
1637 
1638 	if( !(of = ifile_open_read( "%s", fname )) )
1639 		return( FALSE );
1640 	if( !(txt = ifile_read( of )) ) {
1641 		ifile_close( of );
1642 		return( FALSE );
1643 	}
1644 	if( !workspace_local_set( ws, txt ) ) {
1645 		g_free( txt );
1646 		ifile_close( of );
1647 		return( FALSE );
1648 	}
1649 
1650 	filemodel_set_filename( FILEMODEL( ws->local_kit ), fname );
1651 
1652 	g_free( txt );
1653 	ifile_close( of );
1654 
1655 	return( TRUE );
1656 }
1657 
1658 static gint
workspace_jump_name_compare(iContainer * a,iContainer * b)1659 workspace_jump_name_compare( iContainer *a, iContainer *b )
1660 {
1661 	int la = strlen( IOBJECT( a )->name );
1662 	int lb = strlen( IOBJECT( b )->name );
1663 
1664 	/* Smaller names first.
1665 	 */
1666 	if( la == lb )
1667 		return( strcmp( IOBJECT( a )->name, IOBJECT( b )->name ) );
1668 	else
1669 		return( la - lb );
1670 }
1671 
1672 static void
workspace_jump_column_cb(GtkWidget * item,Column * column)1673 workspace_jump_column_cb( GtkWidget *item, Column *column )
1674 {
1675 	column_scrollto( column, MODEL_SCROLL_TOP );
1676 }
1677 
1678 static void *
workspace_jump_build(Column * column,GtkWidget * menu)1679 workspace_jump_build( Column *column, GtkWidget *menu )
1680 {
1681 	GtkWidget *item;
1682 	char txt[256];
1683 	VipsBuf buf = VIPS_BUF_STATIC( txt );
1684 
1685 	vips_buf_appendf( &buf, "%s - %s",
1686 		IOBJECT( column )->name, IOBJECT( column )->caption );
1687 	item = gtk_menu_item_new_with_label( vips_buf_all( &buf ) );
1688 	g_signal_connect( item, "activate",
1689 		G_CALLBACK( workspace_jump_column_cb ), column );
1690 	gtk_menu_append( GTK_MENU( menu ), item );
1691 	gtk_widget_show( item );
1692 
1693 	return( NULL );
1694 }
1695 
1696 /* Update a menu with the set of current columns.
1697  */
1698 void
workspace_jump_update(Workspace * ws,GtkWidget * menu)1699 workspace_jump_update( Workspace *ws, GtkWidget *menu )
1700 {
1701 	GSList *columns;
1702 
1703 	gtk_container_foreach( GTK_CONTAINER( menu ),
1704 		(GtkCallback) gtk_widget_destroy, NULL );
1705 
1706 	columns = icontainer_get_children( ICONTAINER( ws ) );
1707 
1708         columns = g_slist_sort( columns,
1709 		(GCompareFunc) workspace_jump_name_compare );
1710 	slist_map( columns, (SListMapFn) workspace_jump_build, menu );
1711 
1712 	g_slist_free( columns );
1713 }
1714 
1715 /* Merge file into this workspace.
1716  */
1717 gboolean
workspace_merge_file(Workspace * ws,const char * filename)1718 workspace_merge_file( Workspace *ws, const char *filename )
1719 {
1720 	Workspacegroup *wsg = workspace_get_workspacegroup( ws );
1721 
1722 	icontainer_current( ICONTAINER( wsg ), ICONTAINER( ws ) );
1723 
1724 	return( workspacegroup_merge_columns( wsg, filename ) );
1725 }
1726 
1727 /* Duplicate selected rows in this workspace.
1728  */
1729 gboolean
workspace_selected_duplicate(Workspace * ws)1730 workspace_selected_duplicate( Workspace *ws )
1731 {
1732 	Workspacegroup *wsg = workspace_get_workspacegroup( ws );
1733 
1734 	char filename[FILENAME_MAX];
1735 
1736 	if( !workspace_selected_any( ws ) ) {
1737 		Row *row;
1738 
1739 		if( (row = workspace_get_bottom( ws )) )
1740 			row_select( row );
1741 	}
1742 
1743 	if( !temp_name( filename, "ws" ) )
1744 		return( FALSE );
1745 	if( !workspace_selected_save( ws, filename ) )
1746 		return( FALSE );
1747 
1748         progress_begin();
1749 
1750 	if( !workspacegroup_merge_rows( wsg, filename ) ) {
1751 		progress_end();
1752 		unlinkf( "%s", filename );
1753 
1754 		return( FALSE );
1755 	}
1756 	unlinkf( "%s", filename );
1757 
1758 	symbol_recalculate_all();
1759 	workspace_deselect_all( ws );
1760 	column_scrollto( workspace_get_column( ws ), MODEL_SCROLL_BOTTOM );
1761 
1762 	progress_end();
1763 
1764 	return( TRUE );
1765 }
1766 
1767 /* Bounding box of columns to be saved. Though we only really set top/left.
1768  */
1769 static void *
workspace_selected_save_box(Column * col,Rect * box)1770 workspace_selected_save_box( Column *col, Rect *box )
1771 {
1772 	if( model_save_test( MODEL( col ) ) ) {
1773 		if( im_rect_isempty( box ) ) {
1774 			box->left = col->x;
1775 			box->top = col->y;
1776 			box->width = 100;
1777 			box->height = 100;
1778 		}
1779 		else {
1780 			box->left = IM_MIN( box->left, col->x );
1781 			box->top = IM_MIN( box->top, col->y );
1782 		}
1783 	}
1784 
1785 	return( NULL );
1786 }
1787 
1788 /* Save just the selected objects.
1789  */
1790 gboolean
workspace_selected_save(Workspace * ws,const char * filename)1791 workspace_selected_save( Workspace *ws, const char *filename )
1792 {
1793 	Workspacegroup *wsg = workspace_get_workspacegroup( ws );
1794 
1795 	Rect box = { 0 };
1796 
1797 	icontainer_current( ICONTAINER( wsg ), ICONTAINER( ws ) );
1798 
1799 	workspace_map_column( ws,
1800 		(column_map_fn) workspace_selected_save_box,
1801 		&box );
1802 
1803 	filemodel_set_offset( FILEMODEL( wsg ), box.left, box.top );
1804 
1805 	if( !workspacegroup_save_selected( wsg, filename ) )
1806 		return( FALSE );
1807 
1808 	return( TRUE );
1809 }
1810 
1811 gboolean
workspace_rename(Workspace * ws,const char * name,const char * caption)1812 workspace_rename( Workspace *ws, const char *name, const char *caption )
1813 {
1814 	if( !symbol_rename( ws->sym, name ) )
1815 		return( FALSE );
1816 	iobject_set( IOBJECT( ws ), IOBJECT( ws->sym )->name, caption );
1817 	workspace_set_modified( ws, TRUE );
1818 
1819 	symbol_recalculate_all();
1820 
1821 	return( TRUE );
1822 }
1823 
1824 void
workspace_set_locked(Workspace * ws,gboolean locked)1825 workspace_set_locked( Workspace *ws, gboolean locked )
1826 {
1827 	if( ws->locked != locked ) {
1828 		ws->locked = locked;
1829 		iobject_changed( IOBJECT( ws ) );
1830 		workspace_set_modified( ws, TRUE );
1831 	}
1832 }
1833 
1834 gboolean
workspace_duplicate(Workspace * ws)1835 workspace_duplicate( Workspace *ws )
1836 {
1837 	Workspacegroup *wsg = workspace_get_workspacegroup( ws );
1838 
1839 	char filename[FILENAME_MAX];
1840 
1841 	if( !temp_name( filename, "ws" ) )
1842 		return( FALSE );
1843 	icontainer_current( ICONTAINER( wsg ), ICONTAINER( ws ) );
1844 	if( !workspacegroup_save_current( wsg, filename ) )
1845 		return( FALSE );
1846 
1847         progress_begin();
1848 
1849 	if( !workspacegroup_merge_workspaces( wsg, filename ) ) {
1850 		progress_end();
1851 		unlinkf( "%s", filename );
1852 
1853 		return( FALSE );
1854 	}
1855 	unlinkf( "%s", filename );
1856 
1857 	symbol_recalculate_all();
1858 
1859 	progress_end();
1860 
1861 	return( TRUE );
1862 }
1863