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