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