//
// Fl_Native_File_Chooser_WIN32.cxx -- FLTK native OS file chooser widget
//
// Copyright 2004 by Greg Ercolano.
// API changes + filter improvements by Nathan Vander Wilt 2005
// FLTK2/WIN32 port by Greg Ercolano
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
// Please keep code 80 column compliant.
//
//      10        20        30        40        50        60        70
//       |         |         |         |         |         |         |
// 4567890123456789012345678901234567890123456789012345678901234567890123456789
//

// Any application to multi-folder implementation:
//     http://www.codeproject.com/dialog/selectfolder.asp
//

#include <stdio.h>		// debugging
#include "common.cxx"		// strnew/strfree/strapp/chrcat

#ifdef FLTK1
//
// FLTK1
//
#include <FL/Fl_Native_File_Chooser.H>
#define FNFC_CLASS Fl_Native_File_Chooser
#define FNFC_CTOR  Fl_Native_File_Chooser
#else
//
// FLTK2
//
#include <fltk/NativeFileChooser.h>
#include <fltk/run.h>
#define FNFC_CTOR  NativeFileChooser
#define FNFC_CLASS fltk::FNFC_CTOR
#endif

#define LCURLY_CHR	'{'
#define RCURLY_CHR	'}'
#define LBRACKET_CHR	'['
#define RBRACKET_CHR	']'
#define MAXFILTERS	80

// STATIC: PRINT WINDOWS 'DOUBLE NULL' STRING (DEBUG)
static void dnullprint(char *wp) {
    if ( ! wp ) return;
    for ( int t=0; true; t++ ) {
        if ( wp[t] == '\0' && wp[t+1] == '\0' ) {
	    printf("\\0\\0");
	    fflush(stdout);
	    return;
	} else if ( wp[t] == '\0' ) {
	    printf("\\0");
	} else { 
	    printf("%c",wp[t]);
	}
    }
}

// RETURN LENGTH OF DOUBLENULL STRING
//    Includes single nulls in count, excludes trailing doublenull.
//
//         1234 567
//         |||/\|||
//    IN: "one\0two\0\0"
//   OUT: 7
//
static int dnulllen(const char *wp) {
    int len = 0;
    while ( ! ( *(wp+0) == 0 && *(wp+1) == 0 ) )
        { ++wp; ++len; }
    return(len);
}

// STATIC: Append a string to another, leaving terminated with DOUBLE NULL.
//     Automatically handles extending length of string.
//     wp can be NULL (a new wp will be allocated and initialized).
//     string must be NULL terminated.
//     The pointer wp may be modified on return.
//
static void dnullcat(char*&wp, const char *string, int n = -1 ) {
    //DEBUG printf("DEBUG: dnullcat IN: <"); dnullprint(wp); printf(">\n");
    int inlen = ( n < 0 ) ? strlen(string) : n;
    if ( ! wp ) {
        wp = new char[inlen + 4];
	*(wp+0) = '\0';
	*(wp+1) = '\0';
    } else {
        int wplen = dnulllen(wp);
	// Make copy of wp into larger buffer
	char *tmp = new char[wplen + inlen + 4];
	memcpy(tmp, wp, wplen+2);	// copy of wp plus doublenull
	delete [] wp;			// delete old wp
	wp = tmp;			// use new copy
	//DEBUG printf("DEBUG: dnullcat COPY: <"); dnullprint(wp); printf("> (wplen=%d)\n", wplen);
    }

    // Find end of double null string
    //     *wp2 is left pointing at second null.
    //
    char *wp2 = wp;
    if ( *(wp2+0) != '\0' && *(wp2+1) != '\0' ) {
	for ( ; 1; wp2++ )
	    if ( *(wp2+0) == '\0' && *(wp2+1) == '\0' )
		{ wp2++; break; }
    }

    if ( n == -1 ) n = strlen(string);
    strncpy(wp2, string, n);

    // Leave string double-null terminated
    *(wp2+n+0) = '\0';
    *(wp2+n+1) = '\0';
    //DEBUG printf("DEBUG: dnullcat OUT: <"); dnullprint(wp); printf(">\n\n");
}

// CTOR
FNFC_CLASS::FNFC_CTOR(int val) {
    _btype           = val;
    _options         = NO_OPTIONS;
    memset((void*)&_ofn, 0, sizeof(OPENFILENAME));
    _ofn.lStructSize = sizeof(OPENFILENAME);
    _ofn.hwndOwner   = NULL;
    memset((void*)&_binf, 0, sizeof(BROWSEINFO));
    _pathnames       = NULL;
    _tpathnames      = 0;
    _directory       = NULL;
    _title           = NULL;
    _filter          = NULL;
    _parsedfilt      = NULL;
    _nfilters        = 0;
    _preset_file     = NULL;
    _errmsg          = NULL;
}

// DTOR
FNFC_CLASS::~FNFC_CTOR() {
    //_pathnames                // managed by clear_pathnames()
    //_tpathnames               // managed by clear_pathnames()
    _directory   = strfree(_directory);
    _title       = strfree(_title);
    _filter      = strfree(_filter);
    //_parsedfilt               // managed by clear_filters()
    //_nfilters                 // managed by clear_filters()
    _preset_file = strfree(_preset_file);
    _errmsg      = strfree(_errmsg);
    clear_filters();
    clear_pathnames();
    ClearOFN();
    ClearBINF();
}

// SET TYPE OF BROWSER
void FNFC_CLASS::type(int val) {
    _btype = val;
}

// GET TYPE OF BROWSER
int FNFC_CLASS::type() const {
    return( _btype );
}

// SET OPTIONS
void FNFC_CLASS::options(int val) {
    _options = val;
}

// GET OPTIONS
int FNFC_CLASS::options() const {
    return(_options);
}

// PRIVATE: SET ERROR MESSAGE
void FNFC_CLASS::errmsg(const char *val) {
    _errmsg = strfree(_errmsg);
    _errmsg = strnew(val);
}

// FREE PATHNAMES ARRAY, IF IT HAS ANY CONTENTS
void FNFC_CLASS::clear_pathnames() {
    if ( _pathnames ) {
	while ( --_tpathnames >= 0 ) {
	    _pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]);
	}
	delete [] _pathnames;
	_pathnames = NULL;
    }
    _tpathnames = 0;
}

// SET A SINGLE PATHNAME
void FNFC_CLASS::set_single_pathname(const char *s) {
    clear_pathnames();
    _pathnames = new char*[1];
    _pathnames[0] = strnew(s);
    _tpathnames = 1;
}

// ADD PATHNAME TO EXISTING ARRAY
void FNFC_CLASS::add_pathname(const char *s) {
    if ( ! _pathnames ) {
        // Create first element in array
	++_tpathnames;
	_pathnames = new char*[_tpathnames];
    } else {
        // Grow array by 1
        char **tmp = new char*[_tpathnames+1];		// create new buffer
	memcpy((void*)tmp, (void*)_pathnames, 
	                   sizeof(char*)*_tpathnames);	// copy old
	delete [] _pathnames;				// delete old
	_pathnames = tmp;				// use new
	++_tpathnames;
    }
    _pathnames[_tpathnames-1] = strnew(s);
}

// FREE A PIDL (Pointer to IDentity List)
void FNFC_CLASS::FreePIDL(ITEMIDLIST *pidl) {
    IMalloc *imalloc = NULL;
    if ( SUCCEEDED(SHGetMalloc(&imalloc)) )
	{ imalloc->Free(pidl); imalloc->Release(); imalloc = NULL; }
}

// CLEAR MICROSOFT OFN (OPEN FILE NAME) CLASS
void FNFC_CLASS::ClearOFN() {
    // Free any previously allocated lpstrFile before zeroing out _ofn
    if ( _ofn.lpstrFile ) {
        _ofn.lpstrFile = strfree((char*)_ofn.lpstrFile);
    }
    if ( _ofn.lpstrInitialDir ) {
        _ofn.lpstrInitialDir = (LPCSTR)strfree((char*)_ofn.lpstrInitialDir);
    }
    _ofn.lpstrFilter = NULL;		// (deleted elsewhere)
    int temp = _ofn.nFilterIndex;	// keep the filter_value
    memset((void*)&_ofn, 0, sizeof(_ofn));
    _ofn.lStructSize  = sizeof(OPENFILENAME);
    _ofn.nFilterIndex = temp;
}

// CLEAR MICROSOFT BINF (BROWSER INFO) CLASS
void FNFC_CLASS::ClearBINF() {
    if ( _binf.pidlRoot ) {
	FreePIDL((ITEMIDLIST*)_binf.pidlRoot);
	_binf.pidlRoot = NULL;
    }
    memset((void*)&_binf, 0, sizeof(_binf));
}

// CONVERT WINDOWS BACKSLASHES TO UNIX FRONTSLASHES
void FNFC_CLASS::Win2Unix(char *s) {
    for ( ; *s; s++ )
	if ( *s == '\\' ) *s = '/';
}

// CONVERT UNIX FRONTSLASHES TO WINDOWS BACKSLASHES
void FNFC_CLASS::Unix2Win(char *s) {
    for ( ; *s; s++ )
	if ( *s == '/' ) *s = '\\';
}

// SHOW FILE BROWSER
int FNFC_CLASS::showfile() {
    ClearOFN();
    clear_pathnames();
    size_t fsize = 2048;
    _ofn.Flags |= OFN_NOVALIDATE;	// prevent disabling of front slashes
    _ofn.Flags |= OFN_HIDEREADONLY;	// hide goofy readonly flag
    // USE NEW BROWSER
    _ofn.Flags |= OFN_EXPLORER;		// use newer explorer windows
    _ofn.Flags |= OFN_ENABLESIZING;	// allow window to be resized (hey, why not?)

    // XXX: The docs for OFN_NOCHANGEDIR says the flag is 'ineffective' on XP/2K/NT!
    //      But let's set it anyway..
    //
    _ofn.Flags |= OFN_NOCHANGEDIR;	// prevent dialog for messing up the cwd

    switch ( _btype ) {
	case BROWSE_DIRECTORY:
	case BROWSE_MULTI_DIRECTORY:
	case BROWSE_SAVE_DIRECTORY:
	    abort();			// never happens: handled by showdir()
	case BROWSE_FILE:
	    fsize = 65536;		// XXX: there must be a better way
	    break;
	case BROWSE_MULTI_FILE:
	    _ofn.Flags |= OFN_ALLOWMULTISELECT;
	    fsize = 65536;		// XXX: there must be a better way
	    break;
	case BROWSE_SAVE_FILE:
	    if ( options() & SAVEAS_CONFIRM && type() == BROWSE_SAVE_FILE ) {
		_ofn.Flags |= OFN_OVERWRITEPROMPT;
	    }
	    break;
    }
    // SPACE FOR RETURNED FILENAME
    _ofn.lpstrFile    = new char[fsize];
    _ofn.nMaxFile     = fsize-1;
    _ofn.lpstrFile[0] = '\0';
    _ofn.lpstrFile[1] = '\0';		// dnull
    // PARENT WINDOW
    _ofn.hwndOwner = GetForegroundWindow();
    // DIALOG TITLE
    _ofn.lpstrTitle = _title ? _title : NULL;
    // FILTER
    _ofn.lpstrFilter = _parsedfilt ? _parsedfilt : NULL;
    // PRESET FILE
    //     If set, supercedes _directory. See KB Q86920 for details
    //
    if ( _preset_file ) {
        size_t len = strlen(_preset_file);
	if ( len >= _ofn.nMaxFile ) {
	    char msg[80];
	    sprintf(msg, "preset_file() filename is too long: %ld is >=%ld", 
	        (long)len, (long)fsize);
	    return(-1);
	}
	strncpy(_ofn.lpstrFile, _preset_file, _ofn.nMaxFile);
	Unix2Win(_ofn.lpstrFile);
	_ofn.lpstrFile[len+0] = 0;	// multiselect needs dnull
	_ofn.lpstrFile[len+1] = 0;
    }
    if ( _directory ) {
	// PRESET DIR
	//     XXX: See KB Q86920 for doc bug:
	//     http://support.microsoft.com/default.aspx?scid=kb;en-us;86920
	//
	_ofn.lpstrInitialDir = strnew(_directory);
	Unix2Win((char*)_ofn.lpstrInitialDir);
    }
    // SAVE THE CURRENT DIRECTORY
    //     XXX: Save the cwd because GetOpenFileName() is probably going to
    //     change it, in spite of the OFN_NOCHANGEDIR flag, due to its docs
    //     saying the flag is 'ineffective'. %^(
    //
    char oldcwd[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, oldcwd);
    oldcwd[MAX_PATH-1] = '\0';
    // OPEN THE DIALOG WINDOW
    int err;
    if ( _btype == BROWSE_SAVE_FILE ) {
	err = GetSaveFileName(&_ofn);
    } else {
	err = GetOpenFileName(&_ofn);
    }
    if ( err == 0 ) {
	// EXTENDED ERROR CHECK
	int err = CommDlgExtendedError();
	// CANCEL?
	if ( err == 0 ) 
	    return(1);	// user hit 'cancel'
	// AN ERROR OCCURRED
	char msg[80];
	sprintf(msg, "CommDlgExtendedError() code=%d", err);
	errmsg(msg);
	// XXX: RESTORE CWD
	if ( oldcwd[0] ) SetCurrentDirectory(oldcwd);
	return(-1);
    }
    // XXX: RESTORE CWD
    if ( oldcwd[0] ) {
        SetCurrentDirectory(oldcwd);
    }
    // PREPARE PATHNAMES FOR RETURN
    switch ( _btype ) {
	case BROWSE_FILE: 
	case BROWSE_SAVE_FILE:
	    set_single_pathname(_ofn.lpstrFile);
	    Win2Unix(_pathnames[_tpathnames-1]);
	    break;
	case BROWSE_MULTI_FILE: {
	    // EXTRACT MULTIPLE FILENAMES
	    const char *dirname = _ofn.lpstrFile;
	    int dirlen = strlen(dirname);
	    if ( dirlen > 0 ) {
		// WALK STRING SEARCHING FOR 'DOUBLE-NULL'
		//     eg. "/dir/name\0foo1\0foo2\0foo3\0\0"
		//
		char pathname[2048]; 
		for ( const char *s = _ofn.lpstrFile + dirlen + 1; 
						      *s; s+= (strlen(s)+1)) {
		    strcpy(pathname, dirname);
		    strcat(pathname, "\\");
		    strcat(pathname, s);
		    add_pathname(pathname);
		    Win2Unix(_pathnames[_tpathnames-1]);
		}
	    }
	    // XXX
	    //    Work around problem where pasted forward-slash pathname
	    //    into the file browser causes new "Explorer" interface
	    //    not to grok forward slashes, passing back as a 'filename'..!
	    //
	    if ( _tpathnames == 0 ) {
		add_pathname(dirname); 
		Win2Unix(_pathnames[_tpathnames-1]);
	    }
	    break;
	}
	case BROWSE_DIRECTORY:
	case BROWSE_MULTI_DIRECTORY:
	case BROWSE_SAVE_DIRECTORY:
	    abort();			// never happens: handled by showdir()
    }
    return(0);
}

// Used by SHBrowseForFolder(), sets initial selected dir.
// Ref: Usenet: microsoft.public.vc.mfc, Dec 8 2000, 1:38p David Lowndes
//              Subject: How to specify to select an initial folder .."
//
int CALLBACK FNFC_CLASS::Dir_CB(HWND win, UINT msg, 
                                            LPARAM param, LPARAM data) {
    switch (msg) {
	case BFFM_INITIALIZED:
	    if (data) ::SendMessage(win, BFFM_SETSELECTION, TRUE, data);
	    break;
	case BFFM_SELCHANGED:
	    TCHAR path[MAX_PATH];
	    if ( SHGetPathFromIDList((ITEMIDLIST*)param, path) ) {
	        ::SendMessage(win, BFFM_ENABLEOK, 0, 1);
	    } else {
		//disable ok button if not a path
	        ::SendMessage(win, BFFM_ENABLEOK, 0, 0);
	    }
	    break;
	case BFFM_VALIDATEFAILED:
	    // we could pop up an annoying message here. 
	    // also needs set ulFlags |= BIF_VALIDATE
	    break;
	default:
	    break;
    }
    return(0);
}

// SHOW DIRECTORY BROWSER
int FNFC_CLASS::showdir() {
    OleInitialize(NULL);	// init needed by BIF_USENEWUI
    ClearBINF();
    clear_pathnames();
    // PARENT WINDOW
    _binf.hwndOwner = GetForegroundWindow();
    // DIALOG TITLE
    _binf.lpszTitle = _title ? _title : NULL;
    // FLAGS
    _binf.ulFlags = 0; 		// initialize

    // TBD: make sure matches to runtime system, if need be.
    //( what if _WIN32_IE doesn't match system? does the program not run? )
    //
    // TBD: match all 3 types of directories
    //
    // NOTE: *Don't* use BIF_SHAREABLE. It /disables/ mapped network shares
    //       from being visible in BROWSE_DIRECTORY mode. 
    //       See Walter Garm's comments in ./TODO.

#if defined(BIF_NONEWFOLDERBUTTON)				// Version 6.0
    if ( _btype == BROWSE_DIRECTORY ) _binf.ulFlags |= BIF_NONEWFOLDERBUTTON;
    _binf.ulFlags |= BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
#elif defined(BIF_USENEWUI)					// Version 5.0
    if ( _btype == BROWSE_DIRECTORY ) _binf.ulFlags |= BIF_EDITBOX;
    else if ( _btype == BROWSE_SAVE_DIRECTORY ) _binf.ulFlags |= BIF_USENEWUI;
    _binf.ulFlags |= BIF_RETURNONLYFSDIRS;
#elif defined(BIF_EDITBOX)					// Version 4.71
    _binf.ulFlags |= BIF_RETURNONLYFSDIRS | BIF_EDITBOX;
#else								// Version Old
    _binf.ulFlags |= BIF_RETURNONLYFSDIRS;
#endif

    // BUFFER
    char displayname[MAX_PATH];
    _binf.pszDisplayName = displayname;
    // PRESET DIR
    char presetname[MAX_PATH];
    if ( _directory ) {
	strcpy(presetname, _directory);
	Unix2Win(presetname);
	_binf.lParam = (LPARAM)presetname;
    }
    else _binf.lParam = 0;
    _binf.lpfn = Dir_CB;
    // OPEN BROWSER
    ITEMIDLIST *pidl = SHBrowseForFolder(&_binf);
    // CANCEL?
    if ( pidl == NULL ) return(1);

    // GET THE PATHNAME(S) THE USER SELECTED
    // TBD: expand NetHood shortcuts from this PIDL??
    // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shbrowseforfolder.asp

    TCHAR path[MAX_PATH];
    if ( SHGetPathFromIDList(pidl, path) ) {
        Win2Unix(path);
	add_pathname(path);
    }
    FreePIDL(pidl);
    if ( !strlen(path) ) return(1);             // don't return empty pathnames
    return(0);
}

// RETURNS:
//    0 - user picked a file
//    1 - user cancelled
//   -1 - failed; errmsg() has reason
//
int FNFC_CLASS::show() {
    if ( _btype == BROWSE_DIRECTORY || 
         _btype == BROWSE_MULTI_DIRECTORY || 
	 _btype == BROWSE_SAVE_DIRECTORY ) {
	return(showdir());
    } else {
	return(showfile());
    }
}

// RETURN ERROR MESSAGE
const char *FNFC_CLASS::errmsg() const {
    return(_errmsg ? _errmsg : "No error");
}

// GET FILENAME
const char* FNFC_CLASS::filename() const {
    if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]);
    return("");
}

// GET FILENAME FROM LIST OF FILENAMES
const char* FNFC_CLASS::filename(int i) const {
    if ( _pathnames && i < _tpathnames ) return(_pathnames[i]);
    return("");
}

// GET TOTAL FILENAMES CHOSEN
int FNFC_CLASS::count() const {
    return(_tpathnames);
}

// PRESET PATHNAME
//     Can be NULL if no preset is desired.
//
void FNFC_CLASS::directory(const char *val) {
    _directory = strfree(_directory);
    _directory = strnew(val);
}

// GET PRESET PATHNAME
//    Can return NULL if none set.
//
const char *FNFC_CLASS::directory() const {
    return(_directory);
}

// SET TITLE
//     Can be NULL if no title desired.
//
void FNFC_CLASS::title(const char *val) {
    _title = strfree(_title);
    _title = strnew(val);
}

// GET TITLE
//    Can return NULL if none set.
//
const char *FNFC_CLASS::title() const {
    return(_title);
}

// SET FILTER
//     Can be NULL if no filter needed
//
void FNFC_CLASS::filter(const char *val) {
    _filter = strfree(_filter);
    clear_filters();
    if ( val ) {
	_filter = strnew(val);
	parse_filter(_filter);
    }
    add_filter("All Files", "*.*");	// always include 'all files' option

#ifdef DEBUG
    nullprint(_parsedfilt);
#endif /*DEBUG*/
}

// GET FILTER
//    Can return NULL if none set.
//
const char *FNFC_CLASS::filter() const {
    return(_filter);
}

// CLEAR FILTERS
void FNFC_CLASS::clear_filters() {
    _nfilters = 0;
    _parsedfilt = strfree(_parsedfilt);
}

// ADD A FILTER
void FNFC_CLASS::add_filter(
	   const char *name_in,	    // name of filter (optional: can be null)
	   const char *winfilter    // windows style filter (eg. "*.cxx;*.h")
	  ) {
    // No name? Make one..
    char name[1024];
    if ( !name_in || name_in[0] == '\0' ) {
	sprintf(name, "%.*s Files", sizeof(name)-10, winfilter);
    } else {
        sprintf(name, "%.*s", sizeof(name)-10, name_in);
    }
    dnullcat(_parsedfilt, name);
    dnullcat(_parsedfilt, winfilter);
    _nfilters++;
    //DEBUG printf("DEBUG: ADD FILTER name=<%s> winfilter=<%s>\n", name, winfilter);
}

// CONVERT FLTK STYLE PATTERN MATCHES TO WINDOWS 'DOUBLENULL' PATTERN
//    Handles:
//        IN              OUT
//        -----------     -----------------------------
//        *.{ma,mb}       "*.{ma,mb} Files\0*.ma;*.mb\0\0"
//        *.[abc]         "*.[abc] Files\0*.a;*.b;*.c\0\0"
//        *.txt           "*.txt Files\0*.txt\0\0"
//        C Files\t*.[ch] "C Files\0*.c;*.h\0\0"
//
//    Example:
//         IN: "*.{ma,mb}"
//        OUT: "*.ma;*.mb Files\0*.ma;*.mb\0All Files\0*.*\0\0"
//              ---------------  ---------  ---------  ---
//                     |             |          |       |
//                   Title       Wildcards    Title    Wildcards
//
// Parsing Mode:
//         IN:"C Files\t*.{cxx,h}"
//             |||||||  |||||||||
//       mode: nnnnnnn  ww{{{{{{{
//             \_____/  \_______/
//              Name     Wildcard
//
void FNFC_CLASS::parse_filter(const char *in) {
    clear_filters();
    if ( ! in ) return;

    int has_name = strchr(in, '\t') ? 1 : 0;

    char mode = has_name ? 'n' : 'w';	// parse mode: n=name, w=wildcard
    int nwildcards = 0;
    char wildcards[MAXFILTERS][1024];	// parsed wildcards (can be several)
    char wildprefix[512] = "";
    char name[512] = "";

    // Init
    int t;
    for ( t=0; t<MAXFILTERS; t++ ) {
        wildcards[t][0] = '\0';
    }

    // Parse
    for ( ; 1; in++ ) {

        //// DEBUG
        //// printf("WORKING ON '%c': mode=<%c> name=<%s> wildprefix=<%s> nwildcards=%d wildcards[n]=<%s>\n",
	////        *in, mode, name, wildprefix, nwildcards, wildcards[nwildcards]);

        switch (*in) {
	    case ',':
	    case '|':
	        if ( mode == LCURLY_CHR ) {
		    // create new wildcard, copy in prefix
		    strcat(wildcards[nwildcards++], wildprefix);
		    continue;
		} else {
		    goto regchar;
		}
		continue;

	    // FINISHED PARSING A NAME?
	    case '\t':
	        if ( mode != 'n' ) goto regchar;
		// finish parsing name? switch to wildcard mode
		mode = 'w';
		break;

	    // ESCAPE NEXT CHAR
	    case '\\':
	        ++in;
		goto regchar;

	    // FINISHED PARSING ONE OF POSSIBLY SEVERAL FILTERS?
	    case '\r':
	    case '\n':
	    case '\0':
	    {
	        if ( mode == 'w' ) {		// finished parsing wildcard?
		    if ( nwildcards == 0 ) {
		        strcpy(wildcards[nwildcards++], wildprefix);
		    }
		    // Append wildcards in Microsoft's "*.one;*.two" format
		    char comp[4096] = "";
		    for ( t=0; t<nwildcards; t++ ) {
			if ( t != 0 ) strcat(comp, ";");
			strcat(comp, wildcards[t]);
		    }
		    // Add if not empty
		    if ( comp[0] ) {
			add_filter(name, comp);
		    }
		}
		// RESET
		for ( t=0; t<MAXFILTERS; t++ ) {
		    wildcards[t][0] = '\0';
		}
		nwildcards = 0;
		wildprefix[0] = name[0] = '\0';
		mode = strchr(in,'\t') ? 'n' : 'w';
		// DONE?
		if ( *in == '\0' ) return;	// done
		continue;			// not done yet, more filters
	    }

	    // STARTING A WILDCARD?
	    case LBRACKET_CHR:
	    case LCURLY_CHR:
	        mode = *in;
		if ( *in == LCURLY_CHR ) {
		    // create new wildcard
		    strcat(wildcards[nwildcards++], wildprefix);
		}
		continue;

	    // ENDING A WILDCARD?
	    case RBRACKET_CHR:
	    case RCURLY_CHR:
		mode = 'w';	// back to wildcard mode
		continue;

	    // ALL OTHER NON-SPECIAL CHARACTERS
	    default:
	    regchar:		// handle regular char
                switch ( mode ) {
	            case LBRACKET_CHR: 
			// create new wildcard
		        ++nwildcards;
			// copy in prefix
			strcpy(wildcards[nwildcards-1], wildprefix);
			// append search char
			chrcat(wildcards[nwildcards-1], *in);
			continue;

	            case LCURLY_CHR:
		        if ( nwildcards > 0 ) {
			    chrcat(wildcards[nwildcards-1], *in);
			}
			continue;

	            case 'n':
		        chrcat(name, *in);
			continue;

	            case 'w':
			chrcat(wildprefix, *in);
		        for ( t=0; t<nwildcards; t++ ) {
			    chrcat(wildcards[t], *in);
			}
			continue;
	        }
		break;
	}
    }
}

// SET 'CURRENTLY SELECTED FILTER'
void FNFC_CLASS::filter_value(int i) {
    _ofn.nFilterIndex = i + 1;
}

// RETURN VALUE OF 'CURRENTLY SELECTED FILTER'
int FNFC_CLASS::filter_value() const {
    return(_ofn.nFilterIndex ? _ofn.nFilterIndex-1 : _nfilters+1);
}

// PRESET FILENAME FOR 'SAVE AS' CHOOSER
void FNFC_CLASS::preset_file(const char* val) {
    _preset_file = strfree(_preset_file);
    _preset_file = strnew(val);
}

// GET PRESET FILENAME FOR 'SAVE AS' CHOOSER
const char* FNFC_CLASS::preset_file() const {
    return(_preset_file);
}
