// // 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 General Public License // along with this program. If not, see . #include // debugging #include "flnfc_common.cxx" // strnew/strfree/strapp/chrcat #include "FL/Native_File_Chooser.H" #define FNFC_CLASS Fl_Native_File_Chooser #define FNFC_CTOR Fl_Native_File_Chooser #define LCURLY_CHR '{' #define RCURLY_CHR '}' #define LBRACKET_CHR '[' #define RBRACKET_CHR ']' #define MAXFILTERS 80 #if 0 // 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]); } } } #endif // 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 #if defined(BIF_NONEWFOLDERBUTTON) // Version 6.0 if ( _btype == BROWSE_DIRECTORY ) _binf.ulFlags |= BIF_NONEWFOLDERBUTTON; _binf.ulFlags |= BIF_USENEWUI | BIF_SHAREABLE | 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_SHAREABLE | 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 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 0 ) { chrcat(wildcards[nwildcards-1], *in); } continue; case 'n': chrcat(name, *in); continue; case 'w': chrcat(wildprefix, *in); for ( t=0; t