// "$Id: Fl_Native_File_Chooser_MAC.cxx 7354 2010-03-29 11:07:29Z matt $" // // FLTK native OS file chooser widget // // Copyright 1998-2005 by Bill Spitzak and others. // Copyright 2004 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 report all bugs and problems to: // // http://www.fltk.org/str.php // // TODO: // o When doing 'open file', only dir is preset, not filename. // Possibly 'preset_file' could be used to select the filename. // #ifndef FL_DOXYGEN // PREVENT DOXYGEN'S USE OF THIS FILE #include "Fl_Native_File_Chooser_common.cxx" // strnew/strfree/strapp/chrcat #include // dirname(3) #include // stat(2) #include // stat(2) #include #include #include // FREE PATHNAMES ARRAY, IF IT HAS ANY CONTENTS void Fl_Native_File_Chooser::clear_pathnames() { if ( _pathnames ) { while ( --_tpathnames >= 0 ) { _pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]); } delete [] _pathnames; _pathnames = NULL; } _tpathnames = 0; } // SET A SINGLE PATHNAME void Fl_Native_File_Chooser::set_single_pathname(const char *s) { clear_pathnames(); _pathnames = new char*[1]; _pathnames[0] = strnew(s); _tpathnames = 1; } // CONSTRUCTOR Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) { _btype = val; _panel = NULL; _options = NO_OPTIONS; _pathnames = NULL; _tpathnames = 0; _title = NULL; _filter = NULL; _filt_names = NULL; memset(_filt_patt, 0, sizeof(char*) * MAXFILTERS); _filt_total = 0; _filt_value = 0; _directory = NULL; _preset_file = NULL; _errmsg = NULL; } // DESTRUCTOR Fl_Native_File_Chooser::~Fl_Native_File_Chooser() { // _opts // nothing to manage // _options // nothing to manage // _keepstate // nothing to manage // _tempitem // nothing to manage clear_pathnames(); _directory = strfree(_directory); _title = strfree(_title); _preset_file = strfree(_preset_file); _filter = strfree(_filter); //_filt_names // managed by clear_filters() //_filt_patt[i] // managed by clear_filters() //_filt_total // managed by clear_filters() clear_filters(); //_filt_value // nothing to manage _errmsg = strfree(_errmsg); } // GET TYPE OF BROWSER int Fl_Native_File_Chooser::type() const { return(_btype); } // SET OPTIONS void Fl_Native_File_Chooser::options(int val) { _options = val; } // GET OPTIONS int Fl_Native_File_Chooser::options() const { return(_options); } // SHOW THE BROWSER WINDOW // Returns: // 0 - user picked a file // 1 - user cancelled // -1 - failed; errmsg() has reason // int Fl_Native_File_Chooser::show() { // Make sure fltk interface updates before posting our dialog Fl::flush(); // POST BROWSER int err = post(); _filt_total = 0; return(err); } // SET ERROR MESSAGE // Internal use only. // void Fl_Native_File_Chooser::errmsg(const char *msg) { _errmsg = strfree(_errmsg); _errmsg = strnew(msg); } // RETURN ERROR MESSAGE const char *Fl_Native_File_Chooser::errmsg() const { return(_errmsg ? _errmsg : "No error"); } // GET FILENAME const char* Fl_Native_File_Chooser::filename() const { if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]); return(""); } // GET FILENAME FROM LIST OF FILENAMES const char* Fl_Native_File_Chooser::filename(int i) const { if ( _pathnames && i < _tpathnames ) return(_pathnames[i]); return(""); } // GET TOTAL FILENAMES CHOSEN int Fl_Native_File_Chooser::count() const { return(_tpathnames); } // PRESET PATHNAME // Value can be NULL for none. // void Fl_Native_File_Chooser::directory(const char *val) { _directory = strfree(_directory); _directory = strnew(val); } // GET PRESET PATHNAME // Returned value can be NULL if none set. // const char* Fl_Native_File_Chooser::directory() const { return(_directory); } // SET TITLE // Value can be NULL if no title desired. // void Fl_Native_File_Chooser::title(const char *val) { _title = strfree(_title); _title = strnew(val); } // GET TITLE // Returned value can be NULL if none set. // const char *Fl_Native_File_Chooser::title() const { return(_title); } // SET FILTER // Can be NULL if no filter needed // void Fl_Native_File_Chooser::filter(const char *val) { _filter = strfree(_filter); _filter = strnew(val); // Parse filter user specified // IN: _filter = "C Files\t*.{cxx,h}\nText Files\t*.txt" // OUT: _filt_names = "C Files\tText Files" // _filt_patt[0] = "*.{cxx,h}" // _filt_patt[1] = "*.txt" // _filt_total = 2 // parse_filter(_filter); } // GET FILTER // Returned value can be NULL if none set. // const char *Fl_Native_File_Chooser::filter() const { return(_filter); } // CLEAR ALL FILTERS // Internal use only. // void Fl_Native_File_Chooser::clear_filters() { _filt_names = strfree(_filt_names); for (int i=0; i<_filt_total; i++) { _filt_patt[i] = strfree(_filt_patt[i]); } _filt_total = 0; } // PARSE USER'S FILTER SPEC // Parses user specified filter ('in'), // breaks out into _filt_patt[], _filt_names, and _filt_total. // // Handles: // IN: OUT:_filt_names OUT: _filt_patt // ------------------------------------ ------------------ --------------- // "*.{ma,mb}" "*.{ma,mb} Files" "*.{ma,mb}" // "*.[abc]" "*.[abc] Files" "*.[abc]" // "*.txt" "*.txt Files" "*.c" // "C Files\t*.[ch]" "C Files" "*.[ch]" // "C Files\t*.[ch]\nText Files\t*.cxx" "C Files" "*.[ch]" // // Parsing Mode: // IN:"C Files\t*.{cxx,h}" // ||||||| ||||||||| // mode: nnnnnnn wwwwwwwww // \_____/ \_______/ // Name Wildcard // void Fl_Native_File_Chooser::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=title, w=wildcard char wildcard[1024] = ""; // parsed wildcard char name[1024] = ""; // Parse filter user specified for ( ; 1; in++ ) { //// DEBUG //// printf("WORKING ON '%c': mode=<%c> name=<%s> wildcard=<%s>\n", //// *in, mode, name, wildcard); switch (*in) { // FINISHED PARSING NAME? case '\t': if ( mode != 'n' ) goto regchar; mode = 'w'; break; // ESCAPE NEXT CHAR case '\\': ++in; goto regchar; // FINISHED PARSING ONE OF POSSIBLY SEVERAL FILTERS? case '\r': case '\n': case '\0': // TITLE // If user didn't specify a name, make one // if ( name[0] == '\0' ) { sprintf(name, "%.*s Files", (int)sizeof(name)-10, wildcard); } // APPEND NEW FILTER TO LIST if ( wildcard[0] ) { // Add to filtername list // Tab delimit if more than one. We later break // tab delimited string into CFArray with // CFStringCreateArrayBySeparatingStrings() // if ( _filt_total ) { _filt_names = strapp(_filt_names, "\t"); } _filt_names = strapp(_filt_names, name); // Add filter to the pattern array _filt_patt[_filt_total++] = strnew(wildcard); } // RESET wildcard[0] = name[0] = '\0'; mode = strchr(in, '\t') ? 'n' : 'w'; // DONE? if ( *in == '\0' ) return; // done else continue; // not done yet, more filters // Parse all other chars default: // handle all non-special chars regchar: // handle regular char switch ( mode ) { case 'n': chrcat(name, *in); continue; case 'w': chrcat(wildcard, *in); continue; } break; } } //NOTREACHED } // SET PRESET FILE // Value can be NULL for none. // void Fl_Native_File_Chooser::preset_file(const char* val) { _preset_file = strfree(_preset_file); _preset_file = strnew(val); } // PRESET FILE // Returned value can be NULL if none set. // const char* Fl_Native_File_Chooser::preset_file() { return(_preset_file); } #import #define UNLIKELYPREFIX "___fl_very_unlikely_prefix_" #ifndef MAC_OS_X_VERSION_10_6 #define MAC_OS_X_VERSION_10_6 1060 #endif int Fl_Native_File_Chooser::get_saveas_basename(void) { char *q = strdup( [[(NSSavePanel*)_panel filename] fileSystemRepresentation] ); id delegate = [(NSSavePanel*)_panel delegate]; if (delegate != nil) { const char *d = [[(NSSavePanel*)_panel directory] fileSystemRepresentation]; int l = strlen(d) + 1; int lu = strlen(UNLIKELYPREFIX); // Remove UNLIKELYPREFIX between directory and filename parts memmove(q + l, q + l + lu, strlen(q + l + lu) + 1); } set_single_pathname( q ); free(q); return 0; } // SET THE TYPE OF BROWSER void Fl_Native_File_Chooser::type(int val) { _btype = val; switch (_btype) { case BROWSE_FILE: case BROWSE_MULTI_FILE: case BROWSE_DIRECTORY: case BROWSE_MULTI_DIRECTORY: _panel = [NSOpenPanel openPanel]; break; case BROWSE_SAVE_DIRECTORY: case BROWSE_SAVE_FILE: _panel = [NSSavePanel savePanel]; break; } } @interface FLopenDelegate : NSObject #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 #endif { NSPopUpButton *nspopup; char **filter_pattern; } - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern; - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename; @end @implementation FLopenDelegate - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern { nspopup = popup; filter_pattern = pattern; return self; } - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename { if ( [nspopup indexOfSelectedItem] == [nspopup numberOfItems] - 1) return YES; const char *pathname = [filename fileSystemRepresentation]; if ( fl_filename_isdir(pathname) ) return YES; if ( fl_filename_match(pathname, filter_pattern[ [nspopup indexOfSelectedItem] ]) ) return YES; return NO; } @end @interface FLsaveDelegate : NSObject #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 #endif { } - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag; @end @implementation FLsaveDelegate - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag { if (! okFlag) return filename; // User has clicked save, and no overwrite confirmation should occur. // To get the latter, we need to change the name we return (hence the prefix): return [@ UNLIKELYPREFIX stringByAppendingString:filename]; } @end static NSPopUpButton *createPopupAccessory(NSSavePanel *panel, const char *filter, const char *title, int rank) { NSPopUpButton *popup; NSRect rectview = NSMakeRect(5, 5, 350, 30 ); NSView *view = [[[NSView alloc] initWithFrame:rectview] autorelease]; NSRect rectbox = NSMakeRect(0, 3, 50, 1 ); NSBox *box = [[[NSBox alloc] initWithFrame:rectbox] autorelease]; NSRect rectpop = NSMakeRect(60, 0, 250, 30 ); popup = [[[NSPopUpButton alloc ] initWithFrame:rectpop pullsDown:NO] autorelease]; [view addSubview:box]; [view addSubview:popup]; [box setBorderType:NSNoBorder]; NSString *nstitle = [[NSString alloc] initWithUTF8String:title]; [box setTitle:nstitle]; [nstitle release]; NSFont *font = [NSFont controlContentFontOfSize:NSRegularControlSize]; [box setTitleFont:font]; [box sizeToFit]; CFStringRef tab = CFSTR("\n"); CFStringRef tmp_cfs; tmp_cfs = CFStringCreateWithCString(NULL, filter, kCFStringEncodingASCII); CFArrayRef array = CFStringCreateArrayBySeparatingStrings(NULL, tmp_cfs, tab); CFRelease(tmp_cfs); CFRelease(tab); [popup addItemsWithTitles:(NSArray*)array]; NSMenuItem *item = [popup itemWithTitle:@""]; if (item) [popup removeItemWithTitle:@""]; CFRelease(array); [popup selectItemAtIndex:rank]; [panel setAccessoryView:view]; return popup; } // POST BROWSER // Internal use only. // Assumes '_opts' has been initialized. // // Returns: // 0 - user picked a file // 1 - user cancelled // -1 - failed; errmsg() has reason // int Fl_Native_File_Chooser::post() { // INITIALIZE BROWSER if ( _filt_total == 0 ) { // Make sure they match _filt_value = 0; // TBD: move to someplace more logical? } NSAutoreleasePool *localPool; localPool = [[NSAutoreleasePool alloc] init]; int retval; NSString *nstitle = [NSString stringWithUTF8String: (_title ? _title : "No Title")]; [(NSSavePanel*)_panel setTitle:nstitle]; switch (_btype) { case BROWSE_MULTI_FILE: [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES]; break; case BROWSE_MULTI_DIRECTORY: [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES]; case BROWSE_DIRECTORY: [(NSOpenPanel*)_panel setCanChooseDirectories:YES]; break; case BROWSE_SAVE_DIRECTORY: [(NSSavePanel*)_panel setCanCreateDirectories:YES]; break; } // SHOW THE DIALOG if ( [(NSSavePanel*)_panel isKindOfClass:[NSOpenPanel class]] ) { NSPopUpButton *popup = nil; if (_filt_total) { char *p; p = _filter; char *q; q = new char[strlen(p) + 1]; char *r, *s, *t; t = q; do { // copy to t what is in _filter removing what is between \t and \n, if any r = strchr(p, '\n'); if (!r) r = p + strlen(p) - 1; s = strchr(p, '\t'); if (s && s < r) { memcpy(q, p, s - p); q += s - p; *(q++) = '\n'; } else { memcpy(q, p, r - p + 1); q += r - p + 1; } *q = 0; p = r + 1; } while(*p); popup = createPopupAccessory((NSSavePanel*)_panel, t, "Enable:", 0); delete t; [[popup menu] addItem:[NSMenuItem separatorItem]]; [popup addItemWithTitle:@"All Documents"]; [popup setAction:@selector(validateVisibleColumns)]; [popup setTarget:(NSObject*)_panel]; static FLopenDelegate *openDelegate = nil; if (openDelegate == nil) { // not to be ever freed openDelegate = [[FLopenDelegate alloc] init]; } [openDelegate setPopup:popup filter_pattern:_filt_patt]; [(NSOpenPanel*)_panel setDelegate:openDelegate]; } NSString *dir = nil; NSString *fname = nil; NSString *preset = nil; if (_preset_file) { preset = [[NSString alloc] initWithUTF8String:_preset_file]; if (strchr(_preset_file, '/') != NULL) dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]]; fname = [preset lastPathComponent]; } if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory]; retval = [(NSOpenPanel*)_panel runModalForDirectory:dir file:fname types:nil]; [dir release]; [preset release]; if (_filt_total) { _filt_value = [popup indexOfSelectedItem]; } if ( retval == NSOKButton ) { clear_pathnames(); NSArray *array = [(NSOpenPanel*)_panel filenames]; _tpathnames = [array count]; _pathnames = new char*[_tpathnames]; for(int i = 0; i < _tpathnames; i++) { _pathnames[i] = strnew([(NSString*)[array objectAtIndex:i] fileSystemRepresentation]); } } } else { NSString *dir = nil; NSString *fname = nil; NSString *preset = nil; NSPopUpButton *popup = nil; if ( !(_options & SAVEAS_CONFIRM) ) { static FLsaveDelegate *saveDelegate = nil; if (saveDelegate == nil)saveDelegate = [[FLsaveDelegate alloc] init]; // not to be ever freed [(NSSavePanel*)_panel setDelegate:saveDelegate]; } if (_preset_file) { preset = [[NSString alloc] initWithUTF8String:_preset_file]; if (strchr(_preset_file, '/') != NULL) { dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]]; } fname = [preset lastPathComponent]; } if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory]; if (_filt_total) { popup = createPopupAccessory((NSSavePanel*)_panel, _filter, "Format:", _filt_value); } retval = [(NSSavePanel*)_panel runModalForDirectory:dir file:fname]; if (_filt_total) { _filt_value = [popup indexOfSelectedItem]; } [dir release]; [preset release]; if ( retval == NSOKButton ) get_saveas_basename(); } [localPool release]; return (retval == NSOKButton ? 0 : 1); } #endif /*!FL_DOXYGEN*/ // // End of "$Id: Fl_Native_File_Chooser_MAC.cxx 7354 2010-03-29 11:07:29Z matt $". //