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