1 /* A set of workspaces loaded and saved from a ws file.
2  */
3 
4 /*
5 
6     Copyright (C) 1991-2003 The National Gallery
7 
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License along
19     with this program; if not, write to the Free Software Foundation, Inc.,
20     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21 
22  */
23 
24 /*
25 
26     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
27 
28  */
29 
30 /*
31 #define DEBUG
32  */
33 
34 #include "ip.h"
35 
36 static FilemodelClass *parent_class = NULL;
37 
38 void
workspacegroup_set_load_type(Workspacegroup * wsg,WorkspacegroupLoadType load_type)39 workspacegroup_set_load_type( Workspacegroup *wsg,
40 	WorkspacegroupLoadType load_type )
41 {
42 	wsg->load_type = load_type;
43 }
44 
45 void
workspacegroup_set_save_type(Workspacegroup * wsg,WorkspacegroupSaveType save_type)46 workspacegroup_set_save_type( Workspacegroup *wsg,
47 	WorkspacegroupSaveType save_type )
48 {
49 	wsg->save_type = save_type;
50 }
51 
52 Workspace *
workspacegroup_get_workspace(Workspacegroup * wsg)53 workspacegroup_get_workspace( Workspacegroup *wsg )
54 {
55 	if( ICONTAINER( wsg )->current )
56 		return( WORKSPACE( ICONTAINER( wsg )->current ) );
57 	if( ICONTAINER( wsg )->children )
58 		return( WORKSPACE( ICONTAINER( wsg )->children->data ) );
59 
60 	return( NULL );
61 }
62 
63 static Workspace *
workspacegroup_workspace_pick(Workspacegroup * wsg)64 workspacegroup_workspace_pick( Workspacegroup *wsg )
65 {
66 	Workspace *ws;
67 
68 	if( (ws = workspacegroup_get_workspace( wsg )) )
69 		return( ws );
70 
71 	if( ICONTAINER( wsg )->children ) {
72 		ws = WORKSPACE( ICONTAINER( wsg )->children->data );
73 		icontainer_current( ICONTAINER( wsg ), ICONTAINER( ws ) );
74 
75 		return( ws );
76 	}
77 
78 	ws = workspace_new_blank( wsg );
79 
80 	(void) workspace_column_pick( ws );
81 
82 	return( ws );
83 }
84 
85 Workspace *
workspacegroup_map(Workspacegroup * wsg,workspace_map_fn fn,void * a,void * b)86 workspacegroup_map( Workspacegroup *wsg, workspace_map_fn fn, void *a, void *b )
87 {
88 	return( (Workspace *) icontainer_map( ICONTAINER( wsg ),
89 		(icontainer_map_fn) fn, a, b ) );
90 }
91 
92 static void *
workspacegroup_is_empty_sub(Workspace * ws,gboolean * empty)93 workspacegroup_is_empty_sub( Workspace *ws, gboolean *empty )
94 {
95 	if( !workspace_is_empty( ws ) ) {
96 		*empty = FALSE;
97 		return( ws );
98 	}
99 
100 	return( NULL );
101 }
102 
103 gboolean
workspacegroup_is_empty(Workspacegroup * wsg)104 workspacegroup_is_empty( Workspacegroup *wsg )
105 {
106 	gboolean empty;
107 
108 	empty = TRUE;
109 	(void) workspacegroup_map( wsg,
110 		(workspace_map_fn) workspacegroup_is_empty_sub, &empty, NULL );
111 
112 	return( empty );
113 }
114 
115 static void *
workspacegroup_get_n_objects_sub(Workspace * ws,int * n_objects)116 workspacegroup_get_n_objects_sub( Workspace *ws, int *n_objects )
117 {
118 	Compile *compile = ws->sym->expr->compile;
119 
120 	*n_objects += g_slist_length( ICONTAINER( compile )->children );
121 
122 	return( NULL );
123 }
124 
125 int
workspacegroup_get_n_objects(Workspacegroup * wsg)126 workspacegroup_get_n_objects( Workspacegroup *wsg )
127 {
128 	int n_objects;
129 
130 	n_objects = 0;
131 	workspacegroup_map( wsg,
132 		(workspace_map_fn) workspacegroup_get_n_objects_sub,
133 			&n_objects, NULL );
134 
135 	return( n_objects );
136 }
137 
138 static void
workspacegroup_dispose(GObject * gobject)139 workspacegroup_dispose( GObject *gobject )
140 {
141 	Workspacegroup *wsg;
142 
143 	g_return_if_fail( gobject != NULL );
144 	g_return_if_fail( IS_WORKSPACEGROUP( gobject ) );
145 
146 	wsg = WORKSPACEGROUP( gobject );
147 
148 #ifdef DEBUG
149 	printf( "workspacegroup_dispose %s\n", IOBJECT( wsg )->name );
150 #endif /*DEBUG*/
151 
152 	IM_FREEF( g_source_remove, wsg->autosave_timeout );
153 
154 	G_OBJECT_CLASS( parent_class )->dispose( gobject );
155 }
156 
157 static View *
workspacegroup_view_new(Model * model,View * parent)158 workspacegroup_view_new( Model *model, View *parent )
159 {
160 	return( workspacegroupview_new() );
161 }
162 
163 static void *
workspacegroup_save_sub(iContainer * icontainer,void * a,void * b)164 workspacegroup_save_sub( iContainer *icontainer, void *a, void *b )
165 {
166 	Workspace *ws = WORKSPACE( icontainer );
167 	xmlNode *xnode = (xmlNode *) a;
168 	Workspacegroup *wsg = WORKSPACEGROUP( b );
169 
170 	/* Only save all workspaces in save-all mode.
171 	 */
172 	if( wsg->save_type != WORKSPACEGROUP_SAVE_ALL &&
173 		WORKSPACE( ICONTAINER( wsg )->current ) != ws )
174 		return( NULL );
175 
176 	return( model_save( MODEL( ws ), xnode ) );
177 }
178 
179 static xmlNode *
workspacegroup_save(Model * model,xmlNode * xnode)180 workspacegroup_save( Model *model, xmlNode *xnode )
181 {
182 	/* We normally chain up like this:
183 	 *
184 	 * 	xthis = MODEL_CLASS( parent_class )->save( model, xnode )
185 	 *
186 	 * but that will make a workspacegroup holding our workspaces. Instead
187 	 * we want to save all our workspaces directly to xnode with nothing
188 	 * about us in there.
189 	 *
190 	 * See model_real_save().
191 	 */
192 
193 	if( icontainer_map( ICONTAINER( model ),
194 		workspacegroup_save_sub, xnode, model ) )
195 		return( NULL );
196 
197 	return( xnode );
198 }
199 
200 /* Loops over xml trees follow this pattern.
201  */
202 #define FOR_ALL_XML( ROOT, CHILD, CHILD_NAME ) { \
203 	xmlNode *CHILD; \
204 	\
205 	for( CHILD = ROOT->children; CHILD; CHILD = CHILD->next ) { \
206 		if( strcmp( (char *) CHILD->name, CHILD_NAME ) != 0 ) \
207 			continue;
208 
209 #define FOR_ALL_XML_END } }
210 
211 static void
workspacegroup_rename_workspace_node(Workspacegroup * wsg,ModelLoadState * state,xmlNode * xws)212 workspacegroup_rename_workspace_node( Workspacegroup *wsg,
213 	ModelLoadState *state, xmlNode *xws )
214 {
215 	Workspaceroot *wsr = wsg->wsr;
216 
217 	char name[MAX_STRSIZE];
218 	char new_name[MAX_STRSIZE];
219 
220 	if( !get_sprop( xws, "name", name, MAX_STRSIZE ) )
221 		return;
222 
223 	strcpy( new_name, name );
224 	while( compile_lookup( wsr->sym->expr->compile, new_name ) ||
225 		model_loadstate_taken( state, new_name ) )
226 		increment_name( new_name );
227 
228 	(void) set_sprop( xws, "name", new_name );
229 	(void) model_loadstate_rename_new( state, name, new_name );
230 }
231 
232 /* Does a scrap of XML need compat defs?
233  */
234 static gboolean
workspacegroup_xml_needs_compat(ModelLoadState * state,xmlNode * xws,int * best_major,int * best_minor)235 workspacegroup_xml_needs_compat( ModelLoadState *state, xmlNode *xws,
236 	int *best_major, int *best_minor )
237 {
238 	int major;
239 	int minor;
240 
241 	/* What version is the XML expecting? A combination of the version of
242 	 * nip that saved the file, and any compat notes on the workspace XML
243 	 */
244 	if( !get_iprop( xws, "major", &major ) ||
245 		!get_iprop( xws, "minor", &minor ) ) {
246 		/* Fall back to the version number in the xml header.
247 		 */
248 		major = state->major;
249 		minor = state->minor;
250 	}
251 
252 	/* Find the best set of compat we have.
253 	 */
254 	return( workspace_have_compat( major, minor, best_major, best_minor ) );
255 }
256 
257 /* Load all workspaces into this wsg.
258  */
259 static gboolean
workspacegroup_load_new(Workspacegroup * wsg,ModelLoadState * state,xmlNode * xroot)260 workspacegroup_load_new( Workspacegroup *wsg,
261 	ModelLoadState *state, xmlNode *xroot )
262 {
263 	Workspace *first_ws;
264 
265 	/* Rename ... new names for any workspaces which clash.
266 	 */
267 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
268 		workspacegroup_rename_workspace_node( wsg, state, xws );
269 	} FOR_ALL_XML_END
270 
271 	/* _front() the first ws we load. Needed for things like duplicate ws
272 	 * and merge wses.
273 	 */
274 	first_ws = NULL;
275 
276 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
277 		char name[MAX_STRSIZE];
278 		Workspace *ws;
279 		int major;
280 		int minor;
281 
282 		column_set_offset( WORKSPACEVIEW_MARGIN_LEFT,
283 			WORKSPACEVIEW_MARGIN_TOP );
284 
285 		if( !get_sprop( xws, "name", name, FILENAME_MAX ) ||
286 			!(ws = workspace_new( wsg, name )) )
287 			return( FALSE );
288 
289 		if( workspacegroup_xml_needs_compat( state, xws,
290 			&major, &minor ) &&
291 			!workspace_load_compat( ws, major, minor ) )
292 			return( FALSE );
293 
294 		if( model_load( MODEL( ws ), state, MODEL( wsg ), xws ) )
295 			return( FALSE );
296 
297 		if( !first_ws )
298 			first_ws = ws;
299 	} FOR_ALL_XML_END
300 
301 	if( first_ws )
302 		icontainer_current( ICONTAINER( wsg ), ICONTAINER( first_ws ) );
303 
304 	return( TRUE );
305 }
306 
307 static void
workspacegroup_rename_row_node(Workspace * ws,ModelLoadState * state,const char * col_name,xmlNode * xrow)308 workspacegroup_rename_row_node( Workspace *ws,
309 	ModelLoadState *state, const char *col_name, xmlNode *xrow )
310 {
311 	char old_name[MAX_STRSIZE];
312 	char new_name[MAX_STRSIZE];
313 
314 	if( !get_sprop( xrow, "name", old_name, MAX_STRSIZE ) )
315 		return;
316 
317 	im_snprintf( new_name, MAX_STRSIZE, "%s1", col_name );
318 	while( compile_lookup( ws->sym->expr->compile, new_name ) ||
319 		model_loadstate_taken( state, new_name ) )
320 		increment_name( new_name );
321 
322 	(void) set_sprop( xrow, "name", new_name );
323 	(void) model_loadstate_rename_new( state, old_name, new_name );
324 
325 #ifdef DEBUG
326 	printf( "workspacegroup_rename_row_node: renaming "
327 		"'%s' to '%s'\n", old_name, new_name );
328 #endif
329 }
330 
331 /* Rename column if there's one of that name in workspace.
332  */
333 static void
workspacegroup_rename_column_node(Workspacegroup * wsg,Workspace * ws,ModelLoadState * state,xmlNode * xcol)334 workspacegroup_rename_column_node( Workspacegroup *wsg,
335 	Workspace *ws, ModelLoadState *state, xmlNode *xcol )
336 {
337 	char name[MAX_STRSIZE];
338 	char new_name[256];
339 
340 	if( !get_sprop( xcol, "name", name, MAX_STRSIZE ) )
341 		return;
342 
343 	im_strncpy( new_name, name, 256 );
344 	while( workspace_column_find( ws, new_name ) ||
345 		model_loadstate_column_taken( state, new_name ) )
346 		workspace_column_name_new( ws, new_name );
347 
348 	if( strcmp( name, new_name ) != 0 ) {
349 #ifdef DEBUG
350 		printf( "workspace_rename_column_node: renaming column "
351 			"%s to %s\n", name, new_name );
352 #endif /*DEBUG*/
353 
354 		(void) set_sprop( xcol, "name", new_name );
355 		(void) model_loadstate_column_rename_new( state,
356 			name, new_name );
357 
358 		/* And allocate new names for all rows in the subcolumn.
359 		 */
360 		FOR_ALL_XML( xcol, xsub, "Subcolumn" ) {
361 			FOR_ALL_XML( xsub, xrow, "Row" ) {
362 				workspacegroup_rename_row_node( ws, state,
363 					new_name, xrow );
364 			} FOR_ALL_XML_END
365 		} FOR_ALL_XML_END
366 	}
367 }
368 
369 /* Load at column level ... rename columns which clash with
370  * columns in the current workspace. Also look out for clashes
371  * with columns we will load.
372  */
373 static gboolean
workspacegroup_load_columns(Workspacegroup * wsg,ModelLoadState * state,xmlNode * xroot)374 workspacegroup_load_columns( Workspacegroup *wsg,
375 	ModelLoadState *state, xmlNode *xroot )
376 {
377 	Workspace *ws = workspacegroup_workspace_pick( wsg );
378 
379 	int xml_major;
380 	int xml_minor;
381 	gboolean found;
382 	int ws_major;
383 	int ws_minor;
384 
385 	/* Look for any compat problems.
386 	 */
387 	found = FALSE;
388 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
389 		if( workspacegroup_xml_needs_compat( state, xws,
390 			&xml_major, &xml_minor ) ) {
391 			found = TRUE;
392 			break;
393 		}
394 	} FOR_ALL_XML_END
395 
396 	workspace_get_version( ws, &ws_major, &ws_minor );
397 	if( found &&
398 		(xml_major != ws_major ||
399 		xml_minor != ws_minor) ) {
400 		error_top( _( "Version mismatch." ) );
401 		error_sub( _( "File \"%s\" needs version %d.%d. Merging "
402 			"into this tab may cause compatibility problems." ),
403 			state->filename, xml_major, xml_minor );
404 		iwindow_alert( GTK_WIDGET( wsg->iwnd ), GTK_MESSAGE_INFO );
405 	}
406 
407 	/* Search all the columns we will load for their names and add rename
408 	 * rules.
409 	 */
410 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
411 		FOR_ALL_XML( xws, xcol, "Column" ) {
412 			workspacegroup_rename_column_node( wsg, ws,
413 				state, xcol );
414 		} FOR_ALL_XML_END
415 	} FOR_ALL_XML_END
416 
417 	/* Load those columns.
418 	 */
419 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
420 		FOR_ALL_XML( xws, xcol, "Column" ) {
421 			if( !model_new_xml( state, MODEL( ws ), xcol ) )
422 				return( FALSE );
423 		} FOR_ALL_XML_END
424 	} FOR_ALL_XML_END
425 
426 	return( TRUE );
427 }
428 
429 /* Load at row level ... merge into the current column.
430  */
431 static gboolean
workspacegroup_load_rows(Workspacegroup * wsg,ModelLoadState * state,xmlNode * xroot)432 workspacegroup_load_rows( Workspacegroup *wsg,
433 	ModelLoadState *state, xmlNode *xroot )
434 {
435 	Workspace *ws = workspacegroup_workspace_pick( wsg );
436 	Column *col = workspace_column_pick( ws );
437 
438 	int xml_major;
439 	int xml_minor;
440 	gboolean found;
441 	int ws_major;
442 	int ws_minor;
443 
444 	/* Look for any compat problems.
445 	 */
446 	found = FALSE;
447 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
448 		if( workspacegroup_xml_needs_compat( state, xws,
449 			&xml_major, &xml_minor ) ) {
450 			found = TRUE;
451 			break;
452 		}
453 	} FOR_ALL_XML_END
454 
455 	workspace_get_version( ws, &ws_major, &ws_minor );
456 	if( found &&
457 		(xml_major != ws_major ||
458 		xml_minor != ws_minor) ) {
459 		error_top( _( "Version mismatch." ) );
460 		error_sub( _( "File \"%s\" needs version %d.%d. Merging "
461 			"into this tab may cause compatibility problems." ),
462 			state->filename, xml_major, xml_minor );
463 		iwindow_alert( GTK_WIDGET( wsg->iwnd ), GTK_MESSAGE_INFO );
464 	}
465 
466 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
467 		FOR_ALL_XML( xws, xcol, "Column" ) {
468 			FOR_ALL_XML( xcol, xsub, "Subcolumn" ) {
469 				FOR_ALL_XML( xsub, xrow, "Row" ) {
470 					workspacegroup_rename_row_node( ws,
471 						state, IOBJECT( col )->name,
472 						xrow );
473 				} FOR_ALL_XML_END
474 			} FOR_ALL_XML_END
475 		} FOR_ALL_XML_END
476 	} FOR_ALL_XML_END
477 
478 	FOR_ALL_XML( xroot, xws, "Workspace" ) {
479 		FOR_ALL_XML( xws, xcol, "Column" ) {
480 			FOR_ALL_XML( xcol, xsub, "Subcolumn" ) {
481 				FOR_ALL_XML( xsub, xrow, "Row" ) {
482 					if( !model_new_xml( state,
483 						MODEL( col->scol ),
484 						xrow ) )
485 						return( FALSE );
486 				} FOR_ALL_XML_END
487 			} FOR_ALL_XML_END
488 		} FOR_ALL_XML_END
489 	} FOR_ALL_XML_END
490 
491 	return( TRUE );
492 }
493 
494 static gboolean
workspacegroup_top_load(Filemodel * filemodel,ModelLoadState * state,Model * parent,xmlNode * xroot)495 workspacegroup_top_load( Filemodel *filemodel,
496 	ModelLoadState *state, Model *parent, xmlNode *xroot )
497 {
498 	Workspacegroup *wsg = WORKSPACEGROUP( filemodel );
499 
500 	xmlNode *xnode;
501 	char name[FILENAME_MAX];
502 
503 #ifdef DEBUG
504 	printf( "workspacegroup_top_load: from %s\n", state->filename );
505 #endif /*DEBUG*/
506 
507 	/* The top node should be the first workspace. Get the filename this
508 	 * workspace was saved as so we can work out how to rewrite embedded
509 	 * filenames.
510 	 *
511 	 * The filename field can be missing.
512 	 */
513 	if( (xnode = get_node( xroot, "Workspace" )) &&
514 		get_sprop( xnode, "filename", name, FILENAME_MAX ) ) {
515 		char *new_dir;
516 
517 		/* The old filename could be non-native, so we must rewrite
518 		 * to native form first so g_path_get_dirname() can work.
519 		 */
520 		path_compact( name );
521 
522 		state->old_dir = g_path_get_dirname( name );
523 
524 		new_dir = g_path_get_dirname( state->filename_user );
525 		path_rewrite_add( state->old_dir, new_dir, FALSE );
526 		g_free( new_dir );
527 	}
528 
529 	switch( wsg->load_type ) {
530 	case WORKSPACEGROUP_LOAD_NEW:
531 		if( !workspacegroup_load_new( wsg, state, xroot ) )
532 			return( FALSE );
533 		break;
534 
535 	case WORKSPACEGROUP_LOAD_COLUMNS:
536 		if( !workspacegroup_load_columns( wsg, state, xroot ) )
537 			return( FALSE );
538 		break;
539 
540 	case WORKSPACEGROUP_LOAD_ROWS:
541 		if( !workspacegroup_load_rows( wsg, state, xroot ) )
542 			return( FALSE );
543 		break;
544 
545 	default:
546 		g_assert( FALSE );
547 	}
548 
549 	return( FILEMODEL_CLASS( parent_class )->top_load( filemodel,
550 		state, parent, xnode ) );
551 }
552 
553 static gboolean
workspacegroup_top_save(Filemodel * filemodel,const char * filename)554 workspacegroup_top_save( Filemodel *filemodel, const char *filename )
555 {
556 	gboolean result;
557 
558 #ifdef DEBUG
559 	printf( "workspacegroup_top_save: %s to %s\n",
560 		NN( IOBJECT( filemodel )->name ), filename );
561 #endif /*DEBUG*/
562 
563 	if( (result = FILEMODEL_CLASS( parent_class )->
564 		top_save( filemodel, filename )) )
565 		/* This will add save-as files to recent too. Don't note
566 		 * auto_load on recent, since it won't have been loaded by the
567 		 * user.
568 		 */
569 		if( !filemodel->auto_load )
570 			mainw_recent_add( &mainw_recent_workspace, filename );
571 
572 	return( result );
573 }
574 
575 /* Backup the last WS_RETAIN workspaces.
576  */
577 #define WS_RETAIN (10)
578 
579 /* Array of names of workspace files we are keeping.
580  */
581 static char *retain_files[WS_RETAIN] = { NULL };
582 
583 /* On safe exit, remove all ws checkmarks.
584  */
585 void
workspacegroup_autosave_clean(void)586 workspacegroup_autosave_clean( void )
587 {
588 	int i;
589 
590 	for( i = 0; i < WS_RETAIN; i++ ) {
591 		if( retain_files[i] ) {
592 			unlinkf( "%s", retain_files[i] );
593 			IM_FREE( retain_files[i] );
594 		}
595 	}
596 }
597 
598 /* Save the workspace to one of our temp files.
599  */
600 static gboolean
workspacegroup_checkmark_timeout(Workspacegroup * wsg)601 workspacegroup_checkmark_timeout( Workspacegroup *wsg )
602 {
603 	/* The next one we allocate.
604 	 */
605 	static int retain_next = 0;
606 
607 	wsg->autosave_timeout = 0;
608 
609 	if( !AUTO_WS_SAVE )
610 		return( FALSE );
611 
612 	/* Don't backup auto loaded workspace (eg. preferences). These are
613 	 * system things and don't need it.
614 	 */
615 	if( FILEMODEL( wsg )->auto_load )
616 		return( FALSE );
617 
618 	/* Do we have a name for this retain file?
619 	 */
620 	if( !retain_files[retain_next] ) {
621 		char filename[FILENAME_MAX];
622 
623 		/* No name yet - make one up.
624 		 */
625 		if( !temp_name( filename, "ws" ) )
626 			return( FALSE );
627 		retain_files[retain_next] = im_strdup( NULL, filename );
628 	}
629 
630 	if( !filemodel_top_save( FILEMODEL( wsg ), retain_files[retain_next] ) )
631 		return( FALSE );
632 
633 	retain_next = (retain_next + 1) % WS_RETAIN;
634 
635 	return( FALSE );
636 }
637 
638 /* Save the workspace to one of our temp files. Don't save directly (pretty
639  * slow), instead set a timeout and save when we're quiet for >1s.
640  */
641 static void
workspacegroup_checkmark(Workspacegroup * wsg)642 workspacegroup_checkmark( Workspacegroup *wsg )
643 {
644 	if( !AUTO_WS_SAVE )
645 		return;
646 	if( FILEMODEL( wsg )->auto_load )
647 		return;
648 
649 	IM_FREEF( g_source_remove, wsg->autosave_timeout );
650 	wsg->autosave_timeout = g_timeout_add( 1000,
651 		(GSourceFunc) workspacegroup_checkmark_timeout, wsg );
652 }
653 
654 typedef struct {
655 	/* Best so far filename.
656 	 */
657 	char filename[FILENAME_MAX];
658 
659 	/* Best-so-far file date.
660 	 */
661 	time_t time;
662 } AutoRecover;
663 
664 /* This file any better than the previous best candidate? Subfn of below.
665  */
666 static void *
workspacegroup_test_file(const char * name,void * a,void * b,void * c)667 workspacegroup_test_file( const char *name, void *a, void *b, void *c )
668 {
669 	AutoRecover *recover = (AutoRecover *) a;
670 
671 	char buf[FILENAME_MAX];
672 	time_t time;
673 	int i;
674 
675 	im_strncpy( buf, name, FILENAME_MAX );
676 	path_expand( buf );
677 	for( i = 0; i < WS_RETAIN; i++ )
678 		if( retain_files[i] &&
679 			strcmp( buf, retain_files[i] ) == 0 )
680 			return( NULL );
681 	if( !(time = mtime( "%s", buf )) )
682 		return( NULL );
683 	if( recover->time > 0 && time < recover->time )
684 		return( NULL );
685 
686 	strcpy( recover->filename, buf );
687 	recover->time = time;
688 
689 	return( NULL );
690 }
691 
692 /* Search for the most recent "*.ws" file
693  * in the tmp area owned by us, with a size > 0, that's not in our
694  * retain_files[] set.
695  */
696 char *
workspacegroup_autosave_recover(void)697 workspacegroup_autosave_recover( void )
698 {
699 	AutoRecover recover;
700 
701 	strcpy( recover.filename, "" );
702 	recover.time = 0;
703 	(void) path_map_dir( PATH_TMP, "*.ws",
704 		(path_map_fn) workspacegroup_test_file, &recover );
705 
706 	if( !recover.time )
707 		return( NULL );
708 
709 	return( g_strdup( recover.filename ) );
710 }
711 
712 static void
workspacegroup_set_modified(Filemodel * filemodel,gboolean modified)713 workspacegroup_set_modified( Filemodel *filemodel, gboolean modified )
714 {
715 	Workspacegroup *wsg = WORKSPACEGROUP( filemodel );
716 
717 	workspacegroup_checkmark( wsg );
718 
719 	FILEMODEL_CLASS( parent_class )->set_modified( filemodel, modified );
720 }
721 
722 static void
workspacegroup_class_init(WorkspacegroupClass * class)723 workspacegroup_class_init( WorkspacegroupClass *class )
724 {
725 	GObjectClass *gobject_class = (GObjectClass *) class;
726 	iObjectClass *iobject_class = (iObjectClass *) class;
727 	ModelClass *model_class = (ModelClass *) class;
728 	FilemodelClass *filemodel_class = (FilemodelClass *) class;
729 
730 	parent_class = g_type_class_peek_parent( class );
731 
732 	/* Create signals.
733 	 */
734 
735 	/* Init methods.
736 	 */
737 	gobject_class->dispose = workspacegroup_dispose;
738 
739 	iobject_class->user_name = _( "Workspace" );
740 
741 	/* ->load() is done by workspace_top_load().
742 	 */
743 	model_class->view_new = workspacegroup_view_new;
744 	model_class->save = workspacegroup_save;
745 
746 	filemodel_class->filetype = filesel_type_workspace;
747 	filemodel_class->top_load = workspacegroup_top_load;
748 	filemodel_class->top_save = workspacegroup_top_save;
749 	filemodel_class->set_modified = workspacegroup_set_modified;
750 }
751 
752 static void
workspacegroup_init(Workspacegroup * wsg)753 workspacegroup_init( Workspacegroup *wsg )
754 {
755 }
756 
757 GType
workspacegroup_get_type(void)758 workspacegroup_get_type( void )
759 {
760 	static GType type = 0;
761 
762 	if( !type ) {
763 		static const GTypeInfo info = {
764 			sizeof( WorkspacegroupClass ),
765 			NULL,           /* base_init */
766 			NULL,           /* base_finalize */
767 			(GClassInitFunc) workspacegroup_class_init,
768 			NULL,           /* class_finalize */
769 			NULL,           /* class_data */
770 			sizeof( Workspacegroup ),
771 			32,             /* n_preallocs */
772 			(GInstanceInitFunc) workspacegroup_init,
773 		};
774 
775 		type = g_type_register_static( TYPE_FILEMODEL,
776 			"Workspacegroup", &info, 0 );
777 	}
778 
779 	return( type );
780 }
781 
782 static void
workspacegroup_link(Workspacegroup * wsg,Workspaceroot * wsr)783 workspacegroup_link( Workspacegroup *wsg, Workspaceroot *wsr )
784 {
785 	icontainer_child_add( ICONTAINER( wsr ), ICONTAINER( wsg ), -1 );
786 	wsg->wsr = wsr;
787 	filemodel_register( FILEMODEL( wsg ) );
788 }
789 
790 Workspacegroup *
workspacegroup_new(Workspaceroot * wsr)791 workspacegroup_new( Workspaceroot *wsr )
792 {
793 	Workspacegroup *wsg;
794 
795 #ifdef DEBUG
796 	printf( "workspacegroup_new:\n" );
797 #endif /*DEBUG*/
798 
799 	wsg = WORKSPACEGROUP( g_object_new( TYPE_WORKSPACEGROUP, NULL ) );
800 	/* Changed later.
801 	 */
802 	iobject_set( IOBJECT( wsg ), "untitled", _( "Empty workspace" ) );
803 	workspacegroup_link( wsg, wsr );
804 	filemodel_set_modified( FILEMODEL( wsg ), FALSE );
805 
806 	return( wsg );
807 }
808 
809 /* Make the blank workspacegroup we present the user with (in the absence of
810  * anything else).
811  */
812 Workspacegroup *
workspacegroup_new_blank(Workspaceroot * wsr,const char * name)813 workspacegroup_new_blank( Workspaceroot *wsr, const char *name )
814 {
815 	Workspacegroup *wsg;
816 
817 	if( !(wsg = workspacegroup_new( wsr )) )
818 		return( NULL );
819 	iobject_set( IOBJECT( wsg ), name, NULL );
820 	(void) workspacegroup_workspace_pick( wsg );
821 	filemodel_set_modified( FILEMODEL( wsg ), FALSE );
822 
823 	return( wsg );
824 }
825 
826 Workspacegroup *
workspacegroup_new_filename(Workspaceroot * wsr,const char * filename)827 workspacegroup_new_filename( Workspaceroot *wsr, const char *filename )
828 {
829 	Workspacegroup *wsg;
830 	char name[FILENAME_MAX];
831 
832 	if( !(wsg = workspacegroup_new( wsr )) )
833 		return( NULL );
834 	name_from_filename( filename, name );
835 	iobject_set( IOBJECT( wsg ), name, _( "Default empty workspace" ) );
836 	filemodel_set_filename( FILEMODEL( wsg ), filename );
837 	filemodel_set_modified( FILEMODEL( wsg ), FALSE );
838 
839 	return( wsg );
840 }
841 
842 /* Load a file as a workspacegroup.
843  */
844 Workspacegroup *
workspacegroup_new_from_file(Workspaceroot * wsr,const char * filename,const char * filename_user)845 workspacegroup_new_from_file( Workspaceroot *wsr,
846 	const char *filename, const char *filename_user )
847 {
848 	Workspacegroup *wsg;
849 
850 	if( !(wsg = workspacegroup_new( wsr )) )
851 		return( NULL );
852 
853 	workspacegroup_set_load_type( wsg, WORKSPACEGROUP_LOAD_NEW );
854 	if( !filemodel_load_all( FILEMODEL( wsg ),
855 		MODEL( wsr ), filename, filename_user ) )
856 		return( NULL );
857 
858 	filemodel_set_filename( FILEMODEL( wsg ), filename_user );
859 	filemodel_set_modified( FILEMODEL( wsg ), FALSE );
860 
861 	if( filename_user ) {
862 		char name[FILENAME_MAX];
863 
864 		name_from_filename( filename_user, name );
865 		iobject_set( IOBJECT( wsg ), name, NULL );
866 	}
867 	else
868 		iobject_set( IOBJECT( wsg ), "untitled", NULL );
869 
870 	return( wsg );
871 }
872 
873 /* New workspacegroup from a file.
874  */
875 Workspacegroup *
workspacegroup_new_from_openfile(Workspaceroot * wsr,iOpenFile * of)876 workspacegroup_new_from_openfile( Workspaceroot *wsr, iOpenFile *of )
877 {
878 	Workspacegroup *wsg;
879 	char name[FILENAME_MAX];
880 
881 #ifdef DEBUG
882 	printf( "workspacegroup_new_from_openfile: %s\n", of->fname );
883 #endif /*DEBUG*/
884 
885 	if( !(wsg = workspacegroup_new( wsr )) )
886 		return( NULL );
887 
888 	workspacegroup_set_load_type( wsg, WORKSPACEGROUP_LOAD_NEW );
889 	if( !filemodel_load_all_openfile( FILEMODEL( wsg ),
890 		MODEL( wsr ), of ) ) {
891 		g_object_unref( G_OBJECT( wsg ) );
892 		return( NULL );
893 	}
894 
895 	filemodel_set_filename( FILEMODEL( wsg ), of->fname );
896 	filemodel_set_modified( FILEMODEL( wsg ), FALSE );
897 
898 	name_from_filename( of->fname, name );
899 	iobject_set( IOBJECT( wsg ), name, NULL );
900 
901 	return( wsg );
902 }
903 
904 /* Merge into workspacegroup as a set of new workspaces.
905  */
906 gboolean
workspacegroup_merge_workspaces(Workspacegroup * wsg,const char * filename)907 workspacegroup_merge_workspaces( Workspacegroup *wsg, const char *filename )
908 {
909 	workspacegroup_set_load_type( wsg, WORKSPACEGROUP_LOAD_NEW );
910 	if( !filemodel_load_all( FILEMODEL( wsg ), MODEL( wsg->wsr ),
911 		filename, NULL ) )
912 		return( FALSE );
913 
914 	filemodel_set_modified( FILEMODEL( wsg ), TRUE );
915 
916 	return( TRUE );
917 }
918 
919 /* Merge into the current workspace as a set of columns.
920  */
921 gboolean
workspacegroup_merge_columns(Workspacegroup * wsg,const char * filename)922 workspacegroup_merge_columns( Workspacegroup *wsg, const char *filename )
923 {
924 	Workspace *ws;
925 
926 	if( (ws = workspacegroup_get_workspace( wsg )) )
927 		/* We'll do a layout after load, so just load to a huge x and
928 		 * we'll be OK.
929 		 */
930 		column_set_offset(
931 			2 * IM_RECT_RIGHT( &ws->area ) +
932 				WORKSPACEVIEW_MARGIN_LEFT,
933 			WORKSPACEVIEW_MARGIN_TOP );
934 
935 	workspacegroup_set_load_type( wsg, WORKSPACEGROUP_LOAD_COLUMNS );
936 	if( !filemodel_load_all( FILEMODEL( wsg ), MODEL( wsg->wsr ),
937 		filename, NULL ) )
938 		return( FALSE );
939 
940 	filemodel_set_modified( FILEMODEL( wsg ), TRUE );
941 
942 	return( TRUE );
943 }
944 
945 /* Merge into the current workspace as a set of rows.
946  */
947 gboolean
workspacegroup_merge_rows(Workspacegroup * wsg,const char * filename)948 workspacegroup_merge_rows( Workspacegroup *wsg, const char *filename )
949 {
950 	workspacegroup_set_load_type( wsg, WORKSPACEGROUP_LOAD_ROWS );
951 	if( !filemodel_load_all( FILEMODEL( wsg ), MODEL( wsg->wsr ),
952 		filename, NULL ) )
953 		return( FALSE );
954 
955 	filemodel_set_modified( FILEMODEL( wsg ), TRUE );
956 
957 	return( TRUE );
958 }
959 
960 /* Save just the selected objects in the current workspace.
961  */
962 gboolean
workspacegroup_save_selected(Workspacegroup * wsg,const char * filename)963 workspacegroup_save_selected( Workspacegroup *wsg, const char *filename )
964 {
965 	workspacegroup_set_save_type( wsg, WORKSPACEGROUP_SAVE_SELECTED );
966 	if( !filemodel_top_save( FILEMODEL( wsg ), filename ) ) {
967 		unlinkf( "%s", filename );
968 
969 		return( FALSE );
970 	}
971 
972 	return( TRUE );
973 }
974 
975 /* Save just the current workspace.
976  */
977 gboolean
workspacegroup_save_current(Workspacegroup * wsg,const char * filename)978 workspacegroup_save_current( Workspacegroup *wsg, const char *filename )
979 {
980 	workspacegroup_set_save_type( wsg, WORKSPACEGROUP_SAVE_WORKSPACE );
981 	if( !filemodel_top_save( FILEMODEL( wsg ), filename ) ) {
982 		unlinkf( "%s", filename );
983 
984 		return( FALSE );
985 	}
986 
987 	return( TRUE );
988 }
989 
990 /* Save an entire workspacegroup.
991  */
992 gboolean
workspacegroup_save_all(Workspacegroup * wsg,const char * filename)993 workspacegroup_save_all( Workspacegroup *wsg, const char *filename )
994 {
995 	workspacegroup_set_save_type( wsg, WORKSPACEGROUP_SAVE_ALL );
996 	if( !filemodel_top_save( FILEMODEL( wsg ), filename ) ) {
997 		unlinkf( "%s", filename );
998 
999 		return( FALSE );
1000 	}
1001 
1002 	return( TRUE );
1003 }
1004 
1005 Workspacegroup *
workspacegroup_duplicate(Workspacegroup * wsg)1006 workspacegroup_duplicate( Workspacegroup *wsg )
1007 {
1008 	Workspaceroot *wsr = wsg->wsr;
1009 
1010 	Workspacegroup *new_wsg;
1011 	char filename[FILENAME_MAX];
1012 
1013 	if( !temp_name( filename, "ws" ) ||
1014 		!workspacegroup_save_all( wsg, filename ) )
1015 		return( NULL );
1016 
1017 	if( !(new_wsg = workspacegroup_new_from_file( wsr,
1018 		filename, FILEMODEL( wsg )->filename )) ) {
1019 		unlinkf( "%s", filename );
1020 		return( NULL );
1021 	}
1022 	unlinkf( "%s", filename );
1023 
1024 	return( new_wsg );
1025 }
1026