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