1 /* ip's file selectors.
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 #include "ip.h"
31
32 /* Define for debugging output.
33 #define DEBUG
34 */
35
36 /* TIFF save possibilities. Needs to be kept in sync with the Option in
37 * preferences.
38 */
39 typedef enum {
40 TIFF_COMPRESSION_NONE = 0, /* No compression */
41 TIFF_COMPRESSION_LZW, /* Lempel-Ziv compression */
42 TIFF_COMPRESSION_DEFLATE, /* Zip (deflate) compression */
43 TIFF_COMPRESSION_PACKBITS, /* Packbits compression */
44 TIFF_COMPRESSION_JPEG, /* JPEG compression */
45 TIFF_COMPRESSION_CCITTFAX4 /* Fax compression */
46 } TiffCompression;
47
48 typedef enum {
49 TIFF_LAYOUT_STRIP = 0, /* Strip TIFF */
50 TIFF_LAYOUT_TILE /* Tiled TIFF */
51 } TiffLayout;
52
53 typedef enum {
54 TIFF_MULTIRES_FLAT = 0, /* Flat file */
55 TIFF_MULTIRES_PYRAMID /* Pyramidal TIFF */
56 } TiffMultires;
57
58 typedef enum {
59 TIFF_FORMAT_MANYBIT = 0, /* No bit reduction */
60 TIFF_FORMAT_ONEBIT /* Reduce to 1 bit, where poss */
61 } TiffFormat;
62
63 /* Keep a list of all filesels currently active ... we use this for refresh on
64 * new file.
65 */
66 static GSList *filesel_all = NULL;
67
68 static iDialogClass *parent_class = NULL;
69
70 /* For filesels which don't have a suggested filename, track the last dir we
71 * went to and use that as the start dir next time.
72 */
73 static char *filesel_last_dir = NULL;
74
75 static const char *icc_suffs[] = { ".icc", ".icm", NULL };
76 static const char *workspace_suffs[] = { ".ws", NULL };
77 static const char *rec_suffs[] = { ".rec", NULL };
78 static const char *mor_suffs[] = { ".mor", NULL };
79 static const char *con_suffs[] = { ".con", NULL };
80 static const char *mat_suffs[] = { ".mat", NULL };
81 static const char *def_suffs[] = { ".def", NULL };
82 static const char *all_suffs[] = { "", NULL };
83
84 FileselFileType
85 filesel_wfile_type =
86 { N_( "Workspace files (*.ws)" ), workspace_suffs },
87 filesel_rfile_type =
88 { N_( "Recombination matrix files (*.rec)" ), rec_suffs },
89 filesel_mfile_type =
90 { N_( "Morphology matrix files (*.mor)" ), mor_suffs },
91 filesel_cfile_type =
92 { N_( "Convolution matrix files (*.con)" ), con_suffs },
93 filesel_xfile_type =
94 { N_( "Matrix files (*.mat)" ), mat_suffs },
95 filesel_dfile_type =
96 { N_( "Definition files (*.def)" ), def_suffs },
97 filesel_ifile_type =
98 { N_( "ICC profiles (*.icc, *.icm)" ), icc_suffs },
99 filesel_allfile_type =
100 { N_( "All files (*)" ), all_suffs };
101
102 FileselFileType
103 *filesel_type_definition[] = {
104 &filesel_dfile_type, NULL
105 },
106 *filesel_type_workspace[] = {
107 &filesel_wfile_type, NULL
108 },
109
110 *filesel_type_matrix[] = {
111 &filesel_xfile_type, &filesel_cfile_type, &filesel_rfile_type,
112 &filesel_mfile_type, NULL
113 },
114
115 /* Set during startup.
116 */
117 **filesel_type_image = NULL,
118 **filesel_type_mainw = NULL,
119 **filesel_type_any = NULL;
120
121 static void *
build_vips_formats_sub(VipsFormatClass * format,GSList ** types)122 build_vips_formats_sub( VipsFormatClass *format, GSList **types )
123 {
124 FileselFileType *type = g_new( FileselFileType, 1 );
125 char txt[MAX_STRSIZE];
126 VipsBuf buf = VIPS_BUF_STATIC( txt );
127 const char **i;
128
129 vips_buf_appendf( &buf,
130 "%s ", VIPS_OBJECT_CLASS( format )->description );
131 /* Used as eg. "VIPS image files (*.v)"
132 */
133 vips_buf_appends( &buf, _( "image files" ) );
134 vips_buf_appends( &buf, " (" );
135 if( *format->suffs )
136 for( i = format->suffs; *i; i++ ) {
137 vips_buf_appendf( &buf, "*%s", *i );
138 if( i[1] )
139 vips_buf_appends( &buf, "; " );
140 }
141 else
142 /* No suffix means any allowed.
143 */
144 vips_buf_appendf( &buf, "*" );
145
146 vips_buf_appends( &buf, ")" );
147
148 type->name = g_strdup( vips_buf_all( &buf ) );
149 type->suffixes = format->suffs;
150
151 *types = g_slist_append( *types, type );
152
153 return( NULL );
154 }
155
156 /* Look at the registered VIPS formats, build a file type list. Call from
157 * filesel class init.
158 */
159 static FileselFileType **
build_image_file_type(void)160 build_image_file_type( void )
161 {
162 GSList *types;
163 FileselFileType **type_array;
164
165 types = NULL;
166 vips_format_map( (VSListMap2Fn) build_vips_formats_sub, &types, NULL );
167
168 type_array = (FileselFileType **) slist_to_array( types );
169 g_slist_free( types );
170
171 return( type_array );
172 }
173
174 /* Combine a NULL-terminated list of FileselFileType arrays into one.
175 */
176 static FileselFileType **
build_file_type_va(FileselFileType ** first,...)177 build_file_type_va( FileselFileType **first, ... )
178 {
179 va_list args;
180 int len;
181 FileselFileType **i;
182 FileselFileType **array;
183 int j;
184 int k;
185
186 /* Count total number of items.
187 */
188 len = 0;
189 va_start( args, first );
190 for( i = first; i; i = va_arg( args, FileselFileType ** ) )
191 len += array_len( (void **) i );
192 va_end( args );
193
194 /* Copy and NULL-terminate.
195 */
196 array = g_new( FileselFileType *, len + 1 );
197 va_start( args, first );
198 j = 0;
199 for( i = first; i; i = va_arg( args, FileselFileType ** ) )
200 for( k = 0; i[k]; k++ )
201 array[j++] = i[k];
202 va_end( args );
203 array[j] = NULL;
204
205 return( array );
206 }
207
208 /* Here from main() during startup. We can't just put this in class_init,
209 * because we want to be sure this happens early on.
210 */
211 void
filesel_startup(void)212 filesel_startup( void )
213 {
214 filesel_type_image = build_image_file_type();
215 filesel_type_mainw = build_file_type_va( filesel_type_image,
216 filesel_type_matrix, filesel_type_workspace, NULL );
217 filesel_type_any = build_file_type_va( filesel_type_mainw,
218 filesel_type_definition, NULL );
219 }
220
221 /* Is a file of type ... just look at the suffix.
222 */
223 gboolean
is_file_type(const FileselFileType * type,const char * filename)224 is_file_type( const FileselFileType *type, const char *filename )
225 {
226 const char **p;
227 const char *suf;
228
229 if( (suf = strrchr( filename, '.' )) ) {
230 for( p = type->suffixes; *p; p++ )
231 if( strcasecmp( suf, *p ) == 0 )
232 return( TRUE );
233 }
234
235 return( FALSE );
236 }
237
238 /* Map TIFF formats to char* for VIPS.
239 */
240 static char *
decode_tiff_compression(TiffCompression tc)241 decode_tiff_compression( TiffCompression tc )
242 {
243 switch( tc ) {
244 case TIFF_COMPRESSION_LZW: return( "lzw" );
245 case TIFF_COMPRESSION_DEFLATE: return( "deflate" );
246 case TIFF_COMPRESSION_PACKBITS: return( "packbits" );
247 case TIFF_COMPRESSION_JPEG: return( "jpeg" );
248 case TIFF_COMPRESSION_CCITTFAX4:return( "ccittfax4" );
249
250 case TIFF_COMPRESSION_NONE:
251 default:
252 return( "none" );
253 }
254 }
255
256 static char *
decode_tiff_layout(TiffLayout tf)257 decode_tiff_layout( TiffLayout tf )
258 {
259 switch( tf ) {
260 case TIFF_LAYOUT_TILE: return( "tile" );
261
262 case TIFF_LAYOUT_STRIP:
263 default:
264 return( "strip" );
265 }
266 }
267
268 static char *
decode_tiff_multires(TiffMultires tm)269 decode_tiff_multires( TiffMultires tm )
270 {
271 switch( tm ) {
272 case TIFF_MULTIRES_PYRAMID: return( "pyramid" );
273
274 case TIFF_MULTIRES_FLAT:
275 default:
276 return( "flat" );
277 }
278 }
279
280 static char *
decode_tiff_format(TiffFormat tm)281 decode_tiff_format( TiffFormat tm )
282 {
283 switch( tm ) {
284 case TIFF_FORMAT_ONEBIT: return( "onebit" );
285
286 case TIFF_FORMAT_MANYBIT:
287 default:
288 return( "manybit" );
289 }
290 }
291
292 /* Make a TIFF save format string.
293 */
294 static void
filesel_tiff_mode(char * out)295 filesel_tiff_mode( char *out )
296 {
297 char ctype[FILENAME_MAX];
298 char ltype[FILENAME_MAX];
299 char buf[FILENAME_MAX];
300
301 strcpy( ctype, decode_tiff_compression( IP_TIFF_COMPRESSION ) );
302 if( IP_TIFF_COMPRESSION == TIFF_COMPRESSION_JPEG ) {
303 im_snprintf( buf, FILENAME_MAX, ":%d", IP_TIFF_JPEG_Q );
304 strcat( ctype, buf );
305 }
306 if( IP_TIFF_COMPRESSION == TIFF_COMPRESSION_DEFLATE ||
307 IP_TIFF_COMPRESSION == TIFF_COMPRESSION_LZW ) {
308 im_snprintf( buf, FILENAME_MAX, ":%d", IP_TIFF_PREDICTOR + 1 );
309 strcat( ctype, buf );
310 }
311
312 strcpy( ltype, decode_tiff_layout( IP_TIFF_LAYOUT ) );
313 if( IP_TIFF_LAYOUT == TIFF_LAYOUT_TILE ) {
314 im_snprintf( buf, FILENAME_MAX, ":%dx%d",
315 IP_TIFF_TILE_WIDTH,
316 IP_TIFF_TILE_WIDTH );
317 strcat( ltype, buf );
318 }
319
320 im_snprintf( out, 256, "%s,%s,%s,%s,,,%s",
321 ctype, ltype,
322 decode_tiff_multires( IP_TIFF_MULTI_RES ),
323 decode_tiff_format( IP_TIFF_FORMAT ),
324 IP_TIFF_BIGTIFF ? "8" : "" );
325 }
326
327 /* Make a JPEG save format string.
328 */
329 static void
filesel_jpeg_mode(char * out)330 filesel_jpeg_mode( char *out )
331 {
332 char profile[FILENAME_MAX];
333
334 switch( IP_JPEG_ICC_PROFILE ) {
335 case 0:
336 /* Use embedded profile ... do nothing.
337 */
338 strcpy( profile, "" );
339
340 break;
341
342 case 1:
343 {
344 /* Embed from file.
345 */
346 char buf[FILENAME_MAX];
347 char buf2[FILENAME_MAX];
348
349 im_strncpy( buf, IP_JPEG_ICC_PROFILE_FILE, FILENAME_MAX );
350 expand_variables( buf, buf2 );
351 nativeize_path( buf2 );
352 im_snprintf( profile, FILENAME_MAX, ",%s", buf2 );
353
354 break;
355 }
356
357 case 2:
358 /* Don't attach a profile.
359 */
360 im_snprintf( profile, FILENAME_MAX, ",none" );
361 break;
362
363 default:
364 /* Again, do nothing.
365 */
366 strcpy( profile, "" );
367
368 break;
369 }
370
371 im_snprintf( out, 256, "%d%s", IP_JPEG_Q, profile );
372 }
373
374 /* Make a PNG save format string.
375 */
376 static void
filesel_png_mode(char * out)377 filesel_png_mode( char *out )
378 {
379 im_snprintf( out, 256, "%d,%d", IP_PNG_COMPRESSION, IP_PNG_INTERLACE );
380 }
381
382 /* Make a PPM save format string.
383 */
384 static void
filesel_ppm_mode(char * out)385 filesel_ppm_mode( char *out )
386 {
387 switch( IP_PPM_MODE ) {
388 case 0:
389 im_snprintf( out, 256, "binary" );
390 break;
391
392 default:
393 im_snprintf( out, 256, "ascii" );
394 break;
395 }
396 }
397
398 /* Make a CSV save format string.
399 */
400 static void
filesel_csv_mode(char * out)401 filesel_csv_mode( char *out )
402 {
403 /* We have to escape ":" and "," characters in the separator string.
404 */
405 char separator[256];
406
407 escape_mode( IP_CSV_SEPARATOR, separator, 256 );
408
409 im_snprintf( out, 256, "sep:%s", separator );
410 }
411
412 typedef void (*make_mode_fn)( char *buf );
413
414 typedef struct {
415 const char *caption_filter; /* nip2 column name for the format */
416 const char *name; /* vips nickname for the format */
417 make_mode_fn mode_fn; /* Build a mode string */
418 } FileselMode;
419
420 static FileselMode filesel_mode_table[] = {
421 { "JPEG", "jpeg", filesel_jpeg_mode },
422 { "PNG", "png", filesel_png_mode },
423 { "TIFF", "tiff", filesel_tiff_mode },
424 { "CSV", "csv", filesel_csv_mode },
425 { "PPM", "ppm", filesel_ppm_mode }
426 };
427
428 static FileselMode *
filesel_get_mode(const char * filename)429 filesel_get_mode( const char *filename )
430 {
431 int i;
432 VipsFormatClass *format;
433
434 if( (format = vips_format_for_name( filename )) ) {
435 VipsObjectClass *object_class = VIPS_OBJECT_CLASS( format );
436
437 for( i = 0; i < IM_NUMBER( filesel_mode_table ); i++ )
438 if( strcmp( filesel_mode_table[i].name,
439 object_class->nickname ) == 0 )
440 return( &filesel_mode_table[i] );
441 }
442 else
443 im_error_clear();
444
445 return( NULL );
446 }
447
448 /* Add our image save settings to the end of a filename. filename must be
449 * at least FILENAME_MAX characters in size.
450 */
451 void
filesel_add_mode(char * filename)452 filesel_add_mode( char *filename )
453 {
454 FileselMode *mode;
455
456 if( (mode = filesel_get_mode( filename )) ) {
457 char ext[256];
458 int l = strlen( filename );
459
460 mode->mode_fn( ext );
461 im_snprintf( filename + l, FILENAME_MAX - l, ":%s", ext );
462 }
463 }
464
465 static const char *
filesel_get_filter(const char * filename)466 filesel_get_filter( const char *filename )
467 {
468 FileselMode *mode;
469
470 if( (mode = filesel_get_mode( filename )) )
471 return( mode->caption_filter );
472
473 return( NULL );
474 }
475
476 static void
filesel_destroy(GtkObject * object)477 filesel_destroy( GtkObject *object )
478 {
479 Filesel *filesel;
480
481 g_return_if_fail( object != NULL );
482 g_return_if_fail( IS_FILESEL( object ) );
483
484 filesel = FILESEL( object );
485
486 filesel_all = g_slist_remove( filesel_all, filesel );
487 IM_FREEF( g_free, filesel->current_dir );
488
489 GTK_OBJECT_CLASS( parent_class )->destroy( object );
490 }
491
492 /* Update `space free' label.
493 */
494 static void
filesel_space_update(Filesel * filesel,const char * dirname)495 filesel_space_update( Filesel *filesel, const char *dirname )
496 {
497 double sz = find_space( dirname );
498
499 if( filesel->space ) {
500 if( sz < 0 )
501 set_glabel( filesel->space,
502 _( "Unable to determine "
503 "space free in \"%s\"." ),
504 dirname );
505 else {
506 char txt[MAX_STRSIZE];
507 VipsBuf buf = VIPS_BUF_STATIC( txt );
508
509 vips_buf_append_size( &buf, sz );
510 vips_buf_appendf( &buf, " " );
511 /* Expands to (eg.) '6GB free in "/pics/tmp"'
512 */
513 vips_buf_appendf( &buf, _( "free in \"%s\"" ), dirname );
514 set_glabel( filesel->space, "%s", vips_buf_all( &buf ) );
515 }
516 }
517 }
518
519 static void *
filesel_add_volume(const char * dir,Filesel * filesel)520 filesel_add_volume( const char *dir, Filesel *filesel )
521 {
522 char buf[FILENAME_MAX];
523
524 im_strncpy( buf, dir, FILENAME_MAX );
525 path_expand( buf );
526
527 gtk_file_chooser_add_shortcut_folder(
528 GTK_FILE_CHOOSER( filesel->chooser ), buf, NULL );
529
530 return( NULL );
531 }
532
533 static void
filesel_suffix_to_glob(const char * suffix,VipsBuf * patt)534 filesel_suffix_to_glob( const char *suffix, VipsBuf *patt )
535 {
536 int i;
537 char ch;
538
539 vips_buf_appends( patt, "*" );
540
541 for( i = 0; (ch = suffix[i]); i++ ) {
542 if( isalpha( ch ) ) {
543 vips_buf_appends( patt, "[" );
544 vips_buf_appendf( patt, "%c", toupper( ch ) );
545 vips_buf_appendf( patt, "%c", tolower( ch ) );
546 vips_buf_appends( patt, "]" );
547 }
548 else
549 vips_buf_appendf( patt, "%c", ch );
550 }
551 }
552
553 /* Make a shell glob from a filetype.
554 */
555 void
filesel_make_patt(FileselFileType * type,VipsBuf * patt)556 filesel_make_patt( FileselFileType *type, VipsBuf *patt )
557 {
558 int i;
559
560 /* Only use {} braces if there's more than one suffix to match.
561 */
562 if( type->suffixes[1] )
563 vips_buf_appends( patt, "{" );
564
565 for( i = 0; type->suffixes[i]; i++ ) {
566 if( i > 0 )
567 vips_buf_appends( patt, "," );
568
569 filesel_suffix_to_glob( type->suffixes[i], patt );
570 }
571
572 if( type->suffixes[1] )
573 vips_buf_appends( patt, "}" );
574 }
575
576 static char *
filesel_get_dir(Filesel * filesel)577 filesel_get_dir( Filesel *filesel )
578 {
579 return( gtk_file_chooser_get_current_folder(
580 GTK_FILE_CHOOSER( filesel->chooser ) ) );
581 }
582
583 static void
filesel_dir_enter(Filesel * filesel)584 filesel_dir_enter( Filesel *filesel )
585 {
586 char *dir = filesel_get_dir( filesel );
587
588 if( !filesel->current_dir ||
589 (dir && strcmp( filesel->current_dir, dir ) != 0) ) {
590 filesel_space_update( filesel, dir );
591 if( !filesel->start_name )
592 IM_SETSTR( filesel_last_dir, dir );
593
594 filesel->current_dir = dir;
595 dir = NULL;
596 }
597
598 g_free( dir );
599 }
600
601 /* New dir entered signal.
602 */
603 static void
filesel_current_folder_changed_cb(GtkWidget * widget,gpointer data)604 filesel_current_folder_changed_cb( GtkWidget *widget, gpointer data )
605 {
606 filesel_dir_enter( FILESEL( data ) );
607 }
608
609 /* Update file info display.
610 */
611 static void
filesel_info_update(Filesel * filesel,const char * name)612 filesel_info_update( Filesel *filesel, const char *name )
613 {
614 if( filesel->info ) {
615 char txt[MAX_STRSIZE];
616 VipsBuf buf = VIPS_BUF_STATIC( txt );
617
618 get_image_info( &buf, name );
619 set_glabel( filesel->info, "%s", vips_buf_firstline( &buf ) );
620 }
621 }
622
623 int
filesel_get_filetype(Filesel * filesel)624 filesel_get_filetype( Filesel *filesel )
625 {
626 int type;
627 GtkFileFilter *filter;
628
629 type = filesel->default_type;
630
631 if( filesel->chooser &&
632 (filter = gtk_file_chooser_get_filter(
633 GTK_FILE_CHOOSER( filesel->chooser ) )) ) {
634 int i;
635
636 for( i = 0; filesel->filter[i]; i++ )
637 if( filter == filesel->filter[i] )
638 break;
639 g_assert( filesel->filter[i] );
640
641 type = i;
642 }
643
644 #ifdef DEBUG
645 printf( "filesel_get_filetype: %d\n", type );
646 #endif /*DEBUG*/
647
648 return( type );
649 }
650
651 /* Find the index of the type which matches this filename.
652 */
653 static int
filesel_find_file_type(FileselFileType ** type,const char * filename)654 filesel_find_file_type( FileselFileType **type, const char *filename )
655 {
656 int i, j;
657
658 for( i = 0; type[i]; i++ )
659 for( j = 0; type[i]->suffixes[j]; j++ )
660 if( is_casepostfix( type[i]->suffixes[j], filename ) )
661 return( i );
662
663 return( -1 );
664 }
665
666 static void
filesel_set_filter(Filesel * filesel,GtkFileFilter * filter)667 filesel_set_filter( Filesel *filesel, GtkFileFilter *filter )
668 {
669 #ifdef DEBUG
670 printf( "filesel_set_filter: %p\n", filter );
671 #endif /*DEBUG*/
672
673 g_assert( filter );
674
675 gtk_file_chooser_set_filter( GTK_FILE_CHOOSER( filesel->chooser ),
676 filter );
677 }
678
679 static void
filesel_set_filetype_from_filename(Filesel * filesel,const char * name)680 filesel_set_filetype_from_filename( Filesel *filesel, const char *name )
681 {
682 int type;
683 int i;
684 char *p;
685
686 /* If we're showing "all", any filename is OK, so don't change the file
687 * type.
688 */
689 type = filesel_get_filetype( filesel );
690 if( type == filesel->ntypes - 1 )
691 return;
692
693 /* If we've not got a sensible filename, don't bother.
694 */
695 if( (p = strrchr( name, G_DIR_SEPARATOR )) &&
696 strspn( p + 1, " \n\t" ) == strlen( p + 1 ) )
697 return;
698
699 if( (i = filesel_find_file_type( filesel->type, name )) >= 0 )
700 filesel_set_filter( filesel, filesel->filter[i] );
701 else
702 /* No match, or no suffix. Set the last type (should be "All").
703 */
704 filesel_set_filter( filesel,
705 filesel->filter[filesel->ntypes - 1] );
706 }
707
708 gboolean
filesel_set_filename(Filesel * filesel,const char * name)709 filesel_set_filename( Filesel *filesel, const char *name )
710 {
711 char buf[FILENAME_MAX];
712
713 if( !is_valid_filename( name ) )
714 return( FALSE );
715
716 im_strncpy( buf, name, FILENAME_MAX );
717 path_expand( buf );
718
719 #ifdef DEBUG
720 printf( "filesel_set_filename: %s\n", buf );
721 #endif /*DEBUG*/
722
723 /* set_filename() will only select existing files, we need to be able
724 * to set any filename (eg. for increment filename), so we have to
725 * set_current_name() as well.
726 */
727 gtk_file_chooser_set_filename(
728 GTK_FILE_CHOOSER( filesel->chooser ), buf );
729
730 if( filesel->save )
731 gtk_file_chooser_set_current_name(
732 GTK_FILE_CHOOSER( filesel->chooser ),
733 im_skip_dir( buf ) );
734
735 filesel->start_name = TRUE;
736
737 /* We have to set this after setting the filename.
738 */
739 filesel_set_filetype_from_filename( filesel, buf );
740
741 return( TRUE );
742 }
743
744 /* Read the filename out ... test for sanity.
745 */
746 char *
filesel_get_filename(Filesel * filesel)747 filesel_get_filename( Filesel *filesel )
748 {
749 char *name;
750 char tmp[FILENAME_MAX];
751
752 name = gtk_file_chooser_get_filename(
753 GTK_FILE_CHOOSER( filesel->chooser ) );
754
755 #ifdef DEBUG
756 printf( "filesel_get_filename: %s\n", name );
757 #endif /*DEBUG*/
758
759 if( !name ) {
760 error_top( _( "Bad filename." ) );
761 error_sub( _( "No file selected." ) );
762 return( NULL );
763 }
764 if( !is_valid_filename( name ) ) {
765 g_free( name );
766 return( NULL );
767 }
768
769 /* Rewrite to compact form, eg. "$HOME/fred".
770 */
771 im_strncpy( tmp, name, FILENAME_MAX );
772 path_compact( tmp );
773 g_free( name );
774
775 return( g_strdup( tmp ) );
776 }
777
778 /* Get filename multi ... map over the selected filenames.
779 */
780 void *
filesel_map_filename_multi(Filesel * filesel,FileselMapFn fn,void * a,void * b)781 filesel_map_filename_multi( Filesel *filesel,
782 FileselMapFn fn, void *a, void *b )
783 {
784 GSList *names = gtk_file_chooser_get_filenames(
785 GTK_FILE_CHOOSER( filesel->chooser ) );
786 GSList *p;
787
788 for( p = names; p; p = p->next ) {
789 char *filename = (char *) p->data;
790
791 char tmp[FILENAME_MAX];
792 void *res;
793
794 im_strncpy( tmp, filename, FILENAME_MAX );
795 path_compact( tmp );
796
797 if( (res = fn( filesel, tmp, a, b )) ) {
798 IM_FREEF( slist_free_all, names );
799 return( res );
800 }
801 }
802 IM_FREEF( slist_free_all, names );
803
804 return( NULL );
805 }
806
807 /* New file selected signal.
808 */
809 static void
filesel_selection_changed_cb(GtkWidget * widget,gpointer data)810 filesel_selection_changed_cb( GtkWidget *widget, gpointer data )
811 {
812 Filesel *filesel = FILESEL( data );
813 char *filename;
814
815 #ifdef DEBUG
816 printf( "filesel_selection_changed_cb: %s\n",
817 NN( IWINDOW( filesel )->title ) );
818 #endif /*DEBUG*/
819
820 if( (filename = filesel_get_filename( filesel )) ) {
821 #ifdef DEBUG
822 printf( "filesel_selection_changed_cb: %s - \"%s\"\n",
823 NN( IWINDOW( filesel )->title ), filename );
824 #endif /*DEBUG*/
825
826 filesel_info_update( filesel, filename );
827 g_free( filename );
828 }
829 }
830
831 static void
filesel_file_activated_cb(GtkWidget * widget,gpointer data)832 filesel_file_activated_cb( GtkWidget *widget, gpointer data )
833 {
834 idialog_done_trigger( IDIALOG( data ), 0 );
835 }
836
837 /* Increment filename on OK.
838 */
839 static void
filesel_auto_incr_cb(GtkWidget * tog,Filesel * filesel)840 filesel_auto_incr_cb( GtkWidget *tog, Filesel *filesel )
841 {
842 filesel->incr = GTK_TOGGLE_BUTTON( tog )->active;
843
844 if( filesel->incr )
845 idialog_set_pinup( IDIALOG( filesel ), TRUE );
846 }
847
848 static void
filesel_update_preview_cb(GtkFileChooser * chooser,Filesel * filesel)849 filesel_update_preview_cb( GtkFileChooser *chooser, Filesel *filesel )
850 {
851 char *filename;
852
853 if( (filename = gtk_file_chooser_get_preview_filename(
854 GTK_FILE_CHOOSER( filesel->chooser ) )) ) {
855 preview_set_filename( filesel->preview, filename );
856 g_free( filename );
857 }
858 }
859
860 static GtkFileFilter *
file_filter_from_file_type(FileselFileType * type)861 file_filter_from_file_type( FileselFileType *type )
862 {
863 GtkFileFilter *filter;
864 int j;
865
866 filter = gtk_file_filter_new();
867 gtk_file_filter_set_name( filter, _( type->name ) );
868
869 if( type->suffixes[0] )
870 for( j = 0; type->suffixes[j]; j++ ) {
871 char txt[FILENAME_MAX];
872 VipsBuf buf = VIPS_BUF_STATIC( txt );
873
874 filesel_suffix_to_glob( type->suffixes[j], &buf );
875 gtk_file_filter_add_pattern( filter,
876 vips_buf_all( &buf ) );
877 }
878 else
879 /* No suffix list means any suffix allowed.
880 */
881 gtk_file_filter_add_pattern( filter, "*" );
882
883 return( filter );
884 }
885
886 static void
filesel_add_filter(Filesel * filesel,FileselFileType * type,int i)887 filesel_add_filter( Filesel *filesel, FileselFileType *type, int i )
888 {
889 filesel->filter[i] = file_filter_from_file_type( type );
890
891 #ifdef DEBUG
892 printf( "filesel_add_filter: %p (%d)\n", filesel->filter[i], i );
893 #endif /*DEBUG*/
894
895 gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( filesel->chooser ),
896 filesel->filter[i] );
897
898 if( i == filesel->default_type )
899 filesel_set_filter( filesel, filesel->filter[i] );
900 }
901
902 static void
filesel_build(GtkWidget * widget)903 filesel_build( GtkWidget *widget )
904 {
905 Filesel *filesel = FILESEL( widget );
906 iDialog *idlg = IDIALOG( widget );
907
908 int i;
909 FileselFileType *type;
910 GtkWidget *vb;
911 GtkWidget *tog;
912
913 #ifdef DEBUG
914 printf( "filesel_build: %s\n", NN( IWINDOW( filesel )->title ) );
915 #endif /*DEBUG*/
916
917 /* Call all builds in superclasses.
918 */
919 if( IWINDOW_CLASS( parent_class )->build )
920 IWINDOW_CLASS( parent_class )->build( widget );
921
922 filesel->chooser = gtk_file_chooser_widget_new( filesel->save ?
923 GTK_FILE_CHOOSER_ACTION_SAVE :
924 GTK_FILE_CHOOSER_ACTION_OPEN );
925 gtk_file_chooser_set_select_multiple(
926 GTK_FILE_CHOOSER( filesel->chooser ), filesel->multi );
927 gtk_box_pack_start( GTK_BOX( idlg->work ),
928 filesel->chooser, TRUE, TRUE, 0 );
929 gtk_widget_show( filesel->chooser );
930
931 /* Add data path to volumes.
932 */
933 slist_map( PATH_SEARCH,
934 (SListMapFn) filesel_add_volume, filesel );
935
936 /* Add all the supported file types. Add "all" to the end.
937 */
938 for( i = 0; (type = filesel->type[i]); i++ )
939 filesel_add_filter( filesel, type, i );
940 filesel_add_filter( filesel, &filesel_allfile_type, i );
941
942 /* Spot changes.
943 */
944 gtk_signal_connect( GTK_OBJECT( filesel->chooser ),
945 "current-folder-changed",
946 GTK_SIGNAL_FUNC( filesel_current_folder_changed_cb ), filesel );
947 gtk_signal_connect( GTK_OBJECT( filesel->chooser ),
948 "selection-changed",
949 GTK_SIGNAL_FUNC( filesel_selection_changed_cb ), filesel );
950 gtk_signal_connect( GTK_OBJECT( filesel->chooser ),
951 "file-activated",
952 GTK_SIGNAL_FUNC( filesel_file_activated_cb ), filesel );
953
954 /* Pack extra widgets.
955 */
956 vb = gtk_vbox_new( FALSE, 6 );
957 gtk_file_chooser_set_extra_widget(
958 GTK_FILE_CHOOSER( filesel->chooser ), vb );
959 gtk_widget_show( vb );
960
961 /* Space free label.
962 */
963 if( filesel->save ) {
964 filesel->space = gtk_label_new( "" );
965 gtk_misc_set_alignment( GTK_MISC( filesel->space ), 0, 0.5 );
966 gtk_box_pack_start( GTK_BOX( vb ),
967 filesel->space, FALSE, FALSE, 0 );
968 gtk_widget_show( filesel->space );
969 }
970
971 /* File info label.
972 */
973 if( !filesel->save ) {
974 filesel->info = gtk_label_new( "" );
975 gtk_misc_set_alignment( GTK_MISC( filesel->info ), 0, 0.5 );
976 gtk_box_pack_start( GTK_BOX( vb ),
977 filesel->info, FALSE, FALSE, 0 );
978 gtk_widget_show( filesel->info );
979 }
980
981 /* Auto-increment toggle.
982 */
983 if( filesel->save ) {
984 tog = gtk_check_button_new_with_label(
985 _( "Increment filename" ) );
986 gtk_signal_connect( GTK_OBJECT( tog ), "toggled",
987 GTK_SIGNAL_FUNC( filesel_auto_incr_cb ), filesel );
988 gtk_box_pack_start( GTK_BOX( vb ), tog, FALSE, FALSE, 0 );
989 gtk_widget_show( tog );
990 set_tooltip( tog,
991 _( "After Save, add 1 to the last number in the "
992 "file name" ) );
993 }
994
995 if( filesel->imls ) {
996 filesel->preview = preview_new();
997 gtk_file_chooser_set_preview_widget(
998 GTK_FILE_CHOOSER( filesel->chooser ),
999 GTK_WIDGET( filesel->preview ) );
1000 gtk_signal_connect( GTK_OBJECT( filesel->chooser ),
1001 "update-preview",
1002 GTK_SIGNAL_FUNC( filesel_update_preview_cb ), filesel );
1003 gtk_widget_show( GTK_WIDGET( filesel->preview ) );
1004 gtk_file_chooser_set_preview_widget_active(
1005 GTK_FILE_CHOOSER( filesel->chooser ), TRUE );
1006 }
1007
1008 if( filesel_last_dir )
1009 gtk_file_chooser_set_current_folder(
1010 GTK_FILE_CHOOSER( filesel->chooser ),
1011 filesel_last_dir );
1012
1013 /* Save boxes can be much smaller.
1014 */
1015 if( !filesel->save )
1016 gtk_window_set_default_size( GTK_WINDOW( filesel ), 600, 500 );
1017 }
1018
1019 static void
filesel_class_init(FileselClass * class)1020 filesel_class_init( FileselClass *class )
1021 {
1022 GtkObjectClass *object_class = (GtkObjectClass *) class;
1023 iWindowClass *iwindow_class = (iWindowClass *) class;
1024
1025 object_class->destroy = filesel_destroy;
1026
1027 iwindow_class->build = filesel_build;
1028
1029 parent_class = g_type_class_peek_parent( class );
1030 }
1031
1032 /* Increment filename. If there's no number there now, assume zero.
1033 */
1034 static void
filesel_increment_filename(Filesel * filesel)1035 filesel_increment_filename( Filesel *filesel )
1036 {
1037 char *filename;
1038
1039 if( (filename = filesel_get_filename( filesel )) ) {
1040 char name[FILENAME_MAX];
1041
1042 im_strncpy( name, filename, FILENAME_MAX );
1043 g_free( filename );
1044 increment_filename( name );
1045
1046 (void) filesel_set_filename( filesel, name );
1047 }
1048 }
1049
1050 static void *
filesel_refresh(Filesel * filesel)1051 filesel_refresh( Filesel *filesel )
1052 {
1053 char *dir;
1054
1055 if( (dir = gtk_file_chooser_get_current_folder(
1056 GTK_FILE_CHOOSER( filesel->chooser ) )) ) {
1057 gtk_file_chooser_set_current_folder(
1058 GTK_FILE_CHOOSER( filesel->chooser ), dir );
1059 g_free( dir );
1060 }
1061
1062 return( NULL );
1063 }
1064
1065 /* There may be a new file ... ask all fsb's to refresh.
1066 */
1067 void
filesel_refresh_all(void)1068 filesel_refresh_all( void )
1069 {
1070 (void) slist_map( filesel_all, (SListMapFn) filesel_refresh, NULL );
1071 }
1072
1073 static void
filesel_init(Filesel * filesel)1074 filesel_init( Filesel *filesel )
1075 {
1076 int i;
1077
1078 #ifdef DEBUG
1079 printf( "filesel_init: %s\n", NN( IWINDOW( filesel )->title ) );
1080 #endif /*DEBUG*/
1081
1082 filesel->chooser = NULL;
1083 filesel->space = NULL;
1084 filesel->info = NULL;
1085 filesel->preview = NULL;
1086 for( i = 0; i < FILESEL_MAX_FILTERS; i++ )
1087 filesel->filter[i] = NULL;
1088 filesel->incr = FALSE;
1089 filesel->imls = FALSE;
1090 filesel->save = FALSE;
1091 filesel->multi = FALSE;
1092 filesel->start_name = FALSE;
1093 filesel->type = NULL;
1094 filesel->default_type = 0;
1095 filesel->type_pref = NULL;
1096 filesel->current_dir = NULL;
1097 filesel->done_cb = NULL;
1098 filesel->client = NULL;
1099
1100 idialog_set_callbacks( IDIALOG( filesel ),
1101 iwindow_true_cb, NULL, NULL, NULL );
1102 idialog_set_pinup( IDIALOG( filesel ), TRUE );
1103 idialog_set_nosep( IDIALOG( filesel ), TRUE );
1104 idialog_set_button_focus( IDIALOG( filesel ), FALSE );
1105 idialog_set_help_tag( IDIALOG( filesel ), "sec:loadsave" );
1106
1107 filesel_all = g_slist_prepend( filesel_all, filesel );
1108 }
1109
1110 GtkType
filesel_get_type(void)1111 filesel_get_type( void )
1112 {
1113 static GtkType type = 0;
1114
1115 if( !type ) {
1116 static const GtkTypeInfo info = {
1117 "Filesel",
1118 sizeof( Filesel ),
1119 sizeof( FileselClass ),
1120 (GtkClassInitFunc) filesel_class_init,
1121 (GtkObjectInitFunc) filesel_init,
1122 /* reserved_1 */ NULL,
1123 /* reserved_2 */ NULL,
1124 (GtkClassInitFunc) NULL,
1125 };
1126
1127 type = gtk_type_unique( TYPE_IDIALOG, &info );
1128 }
1129
1130 return( type );
1131 }
1132
1133 GtkWidget *
filesel_new(void)1134 filesel_new( void )
1135 {
1136 Filesel *filesel = (Filesel *) gtk_type_new( TYPE_FILESEL );
1137
1138 iwindow_set_size_prefs( IWINDOW( filesel ),
1139 "FILESEL_WINDOW_WIDTH", "FILESEL_WINDOW_HEIGHT" );
1140
1141 return( GTK_WIDGET( filesel ) );
1142 }
1143
1144 void
filesel_set_done(Filesel * filesel,iWindowFn done_cb,void * client)1145 filesel_set_done( Filesel *filesel, iWindowFn done_cb, void *client )
1146 {
1147 filesel->done_cb = done_cb;
1148 filesel->client = client;
1149 }
1150
1151 /* Back from the user function ... unset the hourglass, and update.
1152 */
1153 static void
filesel_trigger2(void * sys,iWindowResult result)1154 filesel_trigger2( void *sys, iWindowResult result )
1155 {
1156 iWindowSusp *susp = (iWindowSusp *) sys;
1157 Filesel *filesel = FILESEL( susp->client );
1158
1159 progress_end();
1160
1161 /* If this is a save, assume that there is now a new file,
1162 * and ask all fsb's to update.
1163 */
1164 if( filesel->save && result != IWINDOW_ERROR )
1165 filesel_refresh_all();
1166
1167 if( result != IWINDOW_YES ) {
1168 /* Failure ... bomb out.
1169 */
1170 iwindow_susp_return( susp, result );
1171 return;
1172 }
1173
1174 /* Increment the filename, if required.
1175 */
1176 if( filesel->incr ) {
1177 filesel_increment_filename( filesel );
1178 filesel_refresh( filesel );
1179 }
1180
1181 /* Success!
1182 */
1183 iwindow_susp_return( susp, result );
1184 }
1185
1186 /* Start of user done ... shut down our suspension, and set the hglass.
1187 */
1188 static void
filesel_trigger(Filesel * filesel,iWindow * iwnd,iWindowNotifyFn nfn,void * sys)1189 filesel_trigger( Filesel *filesel, iWindow *iwnd,
1190 iWindowNotifyFn nfn, void *sys )
1191 {
1192 /* Suspend the callback for a bit.
1193 */
1194 iWindowSusp *susp = iwindow_susp_new( NULL, iwnd, filesel, nfn, sys );
1195
1196 /* If there's a filetype pref, update it.
1197 */
1198 if( filesel->type_pref )
1199 prefs_set( filesel->type_pref,
1200 "%d", filesel_get_filetype( filesel ) );
1201
1202 progress_begin();
1203 filesel->done_cb( IWINDOW( filesel ),
1204 filesel->client, filesel_trigger2, susp );
1205 }
1206
1207 static void
filesel_prefs_ok_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)1208 filesel_prefs_ok_cb( iWindow *iwnd, void *client,
1209 iWindowNotifyFn nfn, void *sys )
1210 {
1211 Filesel *filesel = FILESEL( client );
1212
1213 /* Force a recalc, in case we've changed the autorecalc
1214 * settings. Also does a scan on any widgets.
1215 */
1216 symbol_recalculate_all_force( TRUE );
1217
1218 filesel_trigger( filesel, iwnd, nfn, sys );
1219 }
1220
1221 static void
filesel_prefs(Filesel * filesel,iWindow * iwnd,const char * caption_filter,iWindowNotifyFn nfn,void * sys)1222 filesel_prefs( Filesel *filesel, iWindow *iwnd,
1223 const char *caption_filter,
1224 iWindowNotifyFn nfn, void *sys )
1225 {
1226 Prefs *prefs;
1227
1228 if( !(prefs = prefs_new( caption_filter )) ) {
1229 nfn( sys, IWINDOW_ERROR );
1230 return;
1231 }
1232
1233 /* Expands to (eg.) "TIFF Save Preferences".
1234 */
1235 iwindow_set_title( IWINDOW( prefs ),
1236 _( "%s Save Preferences" ), caption_filter );
1237 iwindow_set_parent( IWINDOW( prefs ), GTK_WIDGET( iwnd ) );
1238 idialog_set_callbacks( IDIALOG( prefs ),
1239 iwindow_true_cb, NULL, NULL, filesel );
1240 idialog_add_ok( IDIALOG( prefs ), filesel_prefs_ok_cb, GTK_STOCK_SAVE );
1241 idialog_set_notify( IDIALOG( prefs ), nfn, sys );
1242 iwindow_build( IWINDOW( prefs ) );
1243
1244 gtk_widget_show( GTK_WIDGET( prefs ) );
1245 }
1246
1247 /* We have a filename and it's OK to overwrite. Is it a type for which we have
1248 * to offer preferences?
1249 */
1250 static void
filesel_yesno_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)1251 filesel_yesno_cb( iWindow *iwnd, void *client,
1252 iWindowNotifyFn nfn, void *sys )
1253 {
1254 Filesel *filesel = FILESEL( client );
1255 char *filename;
1256 const char *caption_filter;
1257
1258 if( filesel->save ) {
1259 if( !(filename = filesel_get_filename( filesel )) )
1260 nfn( sys, IWINDOW_ERROR );
1261 else {
1262 if( (caption_filter = filesel_get_filter( filename )) )
1263 filesel_prefs( filesel, iwnd, caption_filter,
1264 nfn, sys );
1265 else
1266 filesel_trigger( filesel, iwnd, nfn, sys );
1267
1268 g_free( filename );
1269 }
1270 }
1271 else
1272 filesel_trigger( filesel, iwnd, nfn, sys );
1273 }
1274
1275 static void
filesel_done_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)1276 filesel_done_cb( iWindow *iwnd, void *client, iWindowNotifyFn nfn, void *sys )
1277 {
1278 Filesel *filesel = FILESEL( iwnd );
1279 char *filename;
1280
1281 #ifdef DEBUG
1282 printf( "filesel_done\n" );
1283 #endif /*DEBUG*/
1284
1285 if( !(filename = filesel_get_filename( filesel )) )
1286 nfn( sys, IWINDOW_ERROR );
1287 else if( isdir( "%s", filename ) ) {
1288 nfn( sys, IWINDOW_NO );
1289 }
1290 else {
1291 /* File exists and we are saving? Do a yesno before we carry on.
1292 */
1293 if( filesel->save && existsf( "%s", filename ) ) {
1294 box_yesno( GTK_WIDGET( filesel ),
1295 filesel_yesno_cb, iwindow_true_cb, filesel,
1296 nfn, sys,
1297 _( "Overwrite" ),
1298 _( "Overwrite file?" ),
1299 _( "File \"%s\" exists. "
1300 "OK to overwrite?" ), filename );
1301 }
1302 else
1303 /* Just call the user function directly.
1304 */
1305 filesel_yesno_cb( iwnd, filesel, nfn, sys );
1306
1307 g_free( filename );
1308 }
1309 }
1310
1311 void
filesel_set_flags(Filesel * filesel,gboolean imls,gboolean save)1312 filesel_set_flags( Filesel *filesel, gboolean imls, gboolean save )
1313 {
1314 filesel->imls = imls;
1315 filesel->save = save;
1316
1317 idialog_add_ok( IDIALOG( filesel ), filesel_done_cb,
1318 save ? GTK_STOCK_SAVE : GTK_STOCK_OPEN );
1319 }
1320
1321 void
filesel_set_filetype(Filesel * filesel,FileselFileType ** type,int default_type)1322 filesel_set_filetype( Filesel *filesel,
1323 FileselFileType **type, int default_type )
1324 {
1325 /* Reset the widget, if it's there.
1326 */
1327 if( filesel->chooser )
1328 filesel_set_filter( filesel, filesel->filter[default_type] );
1329
1330 filesel->type = type;
1331 filesel->ntypes = array_len( (void **) type );
1332 filesel->default_type = default_type;
1333 }
1334
1335 void
filesel_set_filetype_pref(Filesel * filesel,const char * type_pref)1336 filesel_set_filetype_pref( Filesel *filesel,
1337 const char *type_pref )
1338 {
1339 filesel->type_pref = type_pref;
1340 }
1341
1342 void
filesel_set_multi(Filesel * filesel,gboolean multi)1343 filesel_set_multi( Filesel *filesel, gboolean multi )
1344 {
1345 filesel->multi = multi;
1346 }
1347