1/*
2 * tkFileFilter.c --
3 *
4 *	Process the -filetypes option for the file dialogs on Windows and the
5 *	Mac.
6 *
7 * Copyright (c) 1996 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id$
13 */
14
15#include "tkInt.h"
16#include "tkFileFilter.h"
17
18static int		AddClause(Tcl_Interp *interp,
19			    FileFilter *filterPtr, Tcl_Obj *patternsObj,
20			    Tcl_Obj *ostypesObj, int isWindows);
21static void		FreeClauses(FileFilter *filterPtr);
22static void		FreeGlobPatterns(FileFilterClause *clausePtr);
23static void		FreeMacFileTypes(FileFilterClause *clausePtr);
24static FileFilter *	GetFilter(FileFilterList *flistPtr, CONST char *name);
25
26/*
27 *----------------------------------------------------------------------
28 *
29 * TkInitFileFilters --
30 *
31 *	Initializes a FileFilterList data structure. A FileFilterList must be
32 *	initialized EXACTLY ONCE before any calls to TkGetFileFilters() is
33 *	made. The usual flow of control is:
34 *		TkInitFileFilters(&flist);
35 *		    TkGetFileFilters(&flist, ...);
36 *		    TkGetFileFilters(&flist, ...);
37 *		    ...
38 *		TkFreeFileFilters(&flist);
39 *
40 * Results:
41 *	None.
42 *
43 * Side effects:
44 *	The fields in flistPtr are initialized.
45 *
46 *----------------------------------------------------------------------
47 */
48
49void
50TkInitFileFilters(
51    FileFilterList *flistPtr)	/* The structure to be initialized. */
52{
53    flistPtr->filters = NULL;
54    flistPtr->filtersTail = NULL;
55    flistPtr->numFilters = 0;
56}
57
58/*
59 *----------------------------------------------------------------------
60 *
61 * TkGetFileFilters --
62 *
63 *	This function is called by the Mac and Windows implementation of
64 *	tk_getOpenFile and tk_getSaveFile to translate the string value of the
65 *	-filetypes option into an easy-to-parse C structure (flistPtr). The
66 *	caller of this function will then use flistPtr to perform filetype
67 *	matching in a platform specific way.
68 *
69 *	flistPtr must be initialized (See comments in TkInitFileFilters).
70 *
71 * Results:
72 *	A standard TCL return value.
73 *
74 * Side effects:
75 *	The fields in flistPtr are changed according to 'types'.
76 *
77 *----------------------------------------------------------------------
78 */
79
80int
81TkGetFileFilters(
82    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */
83    FileFilterList *flistPtr,	/* Stores the list of file filters. */
84    Tcl_Obj *types,		/* Value of the -filetypes option. */
85    int isWindows)		/* True if we are running on Windows. */
86{
87    int listObjc;
88    Tcl_Obj ** listObjv = NULL;
89    int i;
90
91    if (types == NULL) {
92        return TCL_OK;
93    }
94
95    if (Tcl_ListObjGetElements(interp, types, &listObjc,
96	    &listObjv) != TCL_OK) {
97	return TCL_ERROR;
98    }
99    if (listObjc == 0) {
100	return TCL_OK;
101    }
102
103    /*
104     * Free the filter information that have been allocated the previous time;
105     * the -filefilters option may have been used more than once in the
106     * command line.
107     */
108    TkFreeFileFilters(flistPtr);
109
110    for (i = 0; i<listObjc; i++) {
111	/*
112	 * Each file type should have two or three elements: the first one is
113	 * the name of the type and the second is the filter of the type. The
114	 * third is the Mac OSType ID, but we don't care about them here.
115	 */
116
117	int count;
118	FileFilter *filterPtr;
119	Tcl_Obj **typeInfo;
120
121	if (Tcl_ListObjGetElements(interp, listObjv[i], &count,
122		&typeInfo) != TCL_OK) {
123	    return TCL_ERROR;
124	}
125
126	if (count != 2 && count != 3) {
127	    Tcl_AppendResult(interp, "bad file type \"",
128		    Tcl_GetString(listObjv[i]), "\", ",
129		    "should be \"typeName {extension ?extensions ...?} ",
130		    "?{macType ?macTypes ...?}?\"", NULL);
131	    return TCL_ERROR;
132	}
133
134	filterPtr = GetFilter(flistPtr, Tcl_GetString(typeInfo[0]));
135
136	if (AddClause(interp, filterPtr, typeInfo[1],
137		(count==2 ? NULL : typeInfo[2]), isWindows) != TCL_OK) {
138	    return TCL_ERROR;
139	}
140    }
141
142    return TCL_OK;
143}
144
145/*
146 *----------------------------------------------------------------------
147 *
148 * TkFreeFileFilters --
149 *
150 *	Frees the malloc'ed file filter information.
151 *
152 * Results:
153 *	None.
154 *
155 * Side effects:
156 *	The fields allocated by TkGetFileFilters() are freed.
157 *
158 *----------------------------------------------------------------------
159 */
160
161void
162TkFreeFileFilters(
163    FileFilterList *flistPtr)	/* List of file filters to free */
164{
165    FileFilter *filterPtr, *toFree;
166
167    filterPtr=flistPtr->filters;
168    while (filterPtr != NULL) {
169	toFree = filterPtr;
170	filterPtr = filterPtr->next;
171	FreeClauses(toFree);
172	ckfree((char*)toFree->name);
173	ckfree((char*)toFree);
174    }
175    flistPtr->filters = NULL;
176}
177
178/*
179 *----------------------------------------------------------------------
180 *
181 * AddClause --
182 *
183 *	Add one FileFilterClause to filterPtr.
184 *
185 * Results:
186 *	A standard TCL result.
187 *
188 * Side effects:
189 *	The list of filter clauses are updated in filterPtr.
190 *
191 *----------------------------------------------------------------------
192 */
193
194static int
195AddClause(
196    Tcl_Interp *interp,		/* Interpreter to use for error reporting. */
197    FileFilter *filterPtr,	/* Stores the new filter clause */
198    Tcl_Obj *patternsObj,	/* A Tcl list of glob patterns. */
199    Tcl_Obj *ostypesObj,	/* A Tcl list of Mac OSType strings. */
200    int isWindows)		/* True if we are running on Windows; False if
201				 * we are running on the Mac; Glob patterns
202				 * need to be processed differently on these
203				 * two platforms */
204{
205    Tcl_Obj **globList = NULL, **ostypeList = NULL;
206    int globCount, ostypeCount, i, code = TCL_OK;
207    FileFilterClause *clausePtr;
208    Tcl_Encoding macRoman = NULL;
209
210    if (Tcl_ListObjGetElements(interp, patternsObj,
211	    &globCount, &globList) != TCL_OK) {
212	code = TCL_ERROR;
213	goto done;
214    }
215    if (ostypesObj != NULL) {
216	if (Tcl_ListObjGetElements(interp, ostypesObj,
217		&ostypeCount, &ostypeList) != TCL_OK) {
218	    code = TCL_ERROR;
219	    goto done;
220	}
221
222	/*
223	 * We probably need this encoding now...
224	 */
225
226	macRoman = Tcl_GetEncoding(NULL, "macRoman");
227
228	/*
229	 * Might be cleaner to use 'Tcl_GetOSTypeFromObj' but that is actually
230	 * static to the MacOS X/Darwin version of Tcl, and would therefore
231	 * require further code refactoring.
232	 */
233
234	for (i=0; i<ostypeCount; i++) {
235	    int len;
236	    CONST char *strType = Tcl_GetStringFromObj(ostypeList[i], &len);
237
238	    /*
239	     * If len is < 4, it is definitely an error. If equal or longer,
240	     * we need to use the macRoman encoding to determine the correct
241	     * length (assuming there may be non-ascii characters, e.g.,
242	     * embedded nulls or accented characters in the string, the
243	     * macRoman length will be different).
244	     *
245	     * If we couldn't load the encoding, then we can't actually check
246	     * the correct length. But here we assume we're probably operating
247	     * on unix/windows with a minimal set of encodings and so don't
248	     * care about MacOS types. So we won't signal an error.
249	     */
250
251	    if (len >= 4 && macRoman != NULL) {
252		Tcl_DString osTypeDS;
253
254		/*
255		 * Convert utf to macRoman, since MacOS types are defined to
256		 * be 4 macRoman characters long
257		 */
258
259		Tcl_UtfToExternalDString(macRoman, strType, len, &osTypeDS);
260		len = Tcl_DStringLength(&osTypeDS);
261		Tcl_DStringFree(&osTypeDS);
262	    }
263	    if (len != 4) {
264		Tcl_AppendResult(interp, "bad Macintosh file type \"",
265			Tcl_GetString(ostypeList[i]), "\"", NULL);
266		code = TCL_ERROR;
267		goto done;
268	    }
269	}
270    }
271
272    /*
273     * Add the clause into the list of clauses
274     */
275
276    clausePtr = (FileFilterClause*)ckalloc(sizeof(FileFilterClause));
277    clausePtr->patterns     = NULL;
278    clausePtr->patternsTail = NULL;
279    clausePtr->macTypes     = NULL;
280    clausePtr->macTypesTail = NULL;
281
282    if (filterPtr->clauses == NULL) {
283	filterPtr->clauses = filterPtr->clausesTail = clausePtr;
284    } else {
285	filterPtr->clausesTail->next = clausePtr;
286	filterPtr->clausesTail = clausePtr;
287    }
288    clausePtr->next = NULL;
289
290    if (globCount > 0 && globList != NULL) {
291	for (i=0; i<globCount; i++) {
292	    GlobPattern *globPtr = (GlobPattern*)ckalloc(sizeof(GlobPattern));
293	    int len;
294
295	    CONST char *str = Tcl_GetStringFromObj(globList[i], &len);
296	    len = (len + 1) * sizeof(char);
297
298	    if (str[0] && str[0] != '*') {
299		/*
300		 * Prepend a "*" to patterns that do not have a leading "*"
301		 */
302
303		globPtr->pattern = (char*)ckalloc((unsigned int) len+1);
304		globPtr->pattern[0] = '*';
305		strcpy(globPtr->pattern+1, str);
306	    } else if (isWindows) {
307		if (strcmp(str, "*") == 0) {
308		    globPtr->pattern = (char*)ckalloc(4 * sizeof(char));
309		    strcpy(globPtr->pattern, "*.*");
310		} else if (strcmp(str, "") == 0) {
311		    /*
312		     * An empty string means "match all files with no
313		     * extensions"
314		     * BUG: "*." actually matches with all files on Win95
315		     */
316
317		    globPtr->pattern = (char *) ckalloc(3 * sizeof(char));
318		    strcpy(globPtr->pattern, "*.");
319		} else {
320		    globPtr->pattern = (char *) ckalloc((unsigned int) len);
321		    strcpy(globPtr->pattern, str);
322		}
323	    } else {
324		globPtr->pattern = (char *) ckalloc((unsigned int) len);
325		strcpy(globPtr->pattern, str);
326	    }
327
328	    /*
329	     * Add the glob pattern into the list of patterns.
330	     */
331
332	    if (clausePtr->patterns == NULL) {
333		clausePtr->patterns = clausePtr->patternsTail = globPtr;
334	    } else {
335		clausePtr->patternsTail->next = globPtr;
336		clausePtr->patternsTail = globPtr;
337	    }
338	    globPtr->next = NULL;
339	}
340    }
341    if (ostypeList != NULL && ostypeCount > 0) {
342	if (macRoman == NULL) {
343	    macRoman = Tcl_GetEncoding(NULL, "macRoman");
344	}
345	for (i=0; i<ostypeCount; i++) {
346	    Tcl_DString osTypeDS;
347	    int len;
348	    MacFileType *mfPtr = (MacFileType *) ckalloc(sizeof(MacFileType));
349	    CONST char *strType = Tcl_GetStringFromObj(ostypeList[i], &len);
350	    char *string;
351
352	    /*
353	     * Convert utf to macRoman, since MacOS types are defined to be 4
354	     * macRoman characters long
355	     */
356
357	    Tcl_UtfToExternalDString(macRoman, strType, len, &osTypeDS);
358	    string = Tcl_DStringValue(&osTypeDS);
359	    mfPtr->type = (OSType) string[0] << 24 | (OSType) string[1] << 16 |
360		    (OSType) string[2] <<  8 | (OSType) string[3];
361	    Tcl_DStringFree(&osTypeDS);
362
363	    /*
364	     * Add the Mac type pattern into the list of Mac types
365	     */
366
367	    if (clausePtr->macTypes == NULL) {
368		clausePtr->macTypes = clausePtr->macTypesTail = mfPtr;
369	    } else {
370		clausePtr->macTypesTail->next = mfPtr;
371		clausePtr->macTypesTail = mfPtr;
372	    }
373	    mfPtr->next = NULL;
374	}
375    }
376
377  done:
378    if (macRoman != NULL) {
379	Tcl_FreeEncoding(macRoman);
380    }
381    return code;
382}
383
384/*
385 *----------------------------------------------------------------------
386 *
387 * GetFilter --
388 *
389 *	Add one FileFilter to flistPtr.
390 *
391 * Results:
392 *	A standard TCL result.
393 *
394 * Side effects:
395 *	The list of filters are updated in flistPtr.
396 *
397 *----------------------------------------------------------------------
398 */
399
400static FileFilter *
401GetFilter(
402    FileFilterList *flistPtr,	/* The FileFilterList that contains the newly
403				 * created filter */
404    CONST char *name)		/* Name of the filter. It is usually displayed
405				 * in the "File Types" listbox in the file
406				 * dialogs. */
407{
408    FileFilter *filterPtr = flistPtr->filters;
409
410    for (; filterPtr; filterPtr=filterPtr->next) {
411	if (strcmp(filterPtr->name, name) == 0) {
412	    return filterPtr;
413	}
414    }
415
416    filterPtr = (FileFilter *) ckalloc(sizeof(FileFilter));
417    filterPtr->clauses = NULL;
418    filterPtr->clausesTail = NULL;
419    filterPtr->name = (char *) ckalloc((strlen(name)+1) * sizeof(char));
420    strcpy(filterPtr->name, name);
421
422    if (flistPtr->filters == NULL) {
423	flistPtr->filters = flistPtr->filtersTail = filterPtr;
424    } else {
425	flistPtr->filtersTail->next = filterPtr;
426	flistPtr->filtersTail = filterPtr;
427    }
428    filterPtr->next = NULL;
429
430    ++flistPtr->numFilters;
431    return filterPtr;
432}
433
434/*
435 *----------------------------------------------------------------------
436 *
437 * FreeClauses --
438 *
439 *	Frees the malloc'ed file type clause
440 *
441 * Results:
442 *	None.
443 *
444 * Side effects:
445 *	The list of clauses in filterPtr->clauses are freed.
446 *
447 *----------------------------------------------------------------------
448 */
449
450static void
451FreeClauses(
452    FileFilter *filterPtr)	/* FileFilter whose clauses are to be freed */
453{
454    FileFilterClause *clausePtr = filterPtr->clauses;
455
456    while (clausePtr != NULL) {
457	FileFilterClause *toFree = clausePtr;
458	clausePtr = clausePtr->next;
459
460	FreeGlobPatterns(toFree);
461	FreeMacFileTypes(toFree);
462	ckfree((char *) toFree);
463    }
464    filterPtr->clauses = NULL;
465    filterPtr->clausesTail = NULL;
466}
467
468/*
469 *----------------------------------------------------------------------
470 *
471 * FreeGlobPatterns --
472 *
473 *	Frees the malloc'ed glob patterns in a clause
474 *
475 * Results:
476 *	None.
477 *
478 * Side effects:
479 *	The list of glob patterns in clausePtr->patterns are freed.
480 *
481 *----------------------------------------------------------------------
482 */
483
484static void
485FreeGlobPatterns(
486    FileFilterClause *clausePtr)/* The clause whose patterns are to be freed*/
487{
488    GlobPattern *globPtr = clausePtr->patterns;
489
490    while (globPtr != NULL) {
491	GlobPattern *toFree = globPtr;
492	globPtr = globPtr->next;
493
494	ckfree((char *) toFree->pattern);
495	ckfree((char *) toFree);
496    }
497    clausePtr->patterns = NULL;
498}
499
500/*
501 *----------------------------------------------------------------------
502 *
503 * FreeMacFileTypes --
504 *
505 *	Frees the malloc'ed Mac file types in a clause
506 *
507 * Results:
508 *	None.
509 *
510 * Side effects:
511 *	The list of Mac file types in clausePtr->macTypes are freed.
512 *
513 *----------------------------------------------------------------------
514 */
515
516static void
517FreeMacFileTypes(
518    FileFilterClause *clausePtr)/* The clause whose mac types are to be
519				 * freed */
520{
521    MacFileType *mfPtr = clausePtr->macTypes;
522
523    while (mfPtr != NULL) {
524	MacFileType *toFree = mfPtr;
525	mfPtr = mfPtr->next;
526	ckfree((char *) toFree);
527    }
528    clausePtr->macTypes = NULL;
529}
530
531/*
532 * Local Variables:
533 * mode: c
534 * c-basic-offset: 4
535 * fill-column: 78
536 * End:
537 */
538