fltk 1.3.0rc3
About: FLTK (Fast Light Tool Kit) is a cross-platform C++ GUI toolkit for UNIX/Linux (X11), Microsoft Windows, and MacOS X. Release candidate.
  SfR Fresh Dox: fltk-1.3.0rc3-source.tar.gz ("inofficial" and yet experimental doxygen-generated source code documentation)  

Fl_Native_File_Chooser_MAC.mm

Go to the documentation of this file.
00001 // "$Id: Fl_Native_File_Chooser_MAC.mm 8062 2010-12-19 17:57:19Z manolo $"
00002 //
00003 // FLTK native OS file chooser widget
00004 //
00005 // Copyright 1998-2010 by Bill Spitzak and others.
00006 // Copyright 2004 Greg Ercolano.
00007 //
00008 // This library is free software; you can redistribute it and/or
00009 // modify it under the terms of the GNU Library General Public
00010 // License as published by the Free Software Foundation; either
00011 // version 2 of the License, or (at your option) any later version.
00012 //
00013 // This library is distributed in the hope that it will be useful,
00014 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016 // Library General Public License for more details.
00017 //
00018 // You should have received a copy of the GNU Library General Public
00019 // License along with this library; if not, write to the Free Software
00020 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00021 // USA.
00022 //
00023 // Please report all bugs and problems to:
00024 //
00025 //     http://www.fltk.org/str.php
00026 //
00027 
00028 // TODO:
00029 //      o When doing 'open file', only dir is preset, not filename.
00030 //        Possibly 'preset_file' could be used to select the filename.
00031 //
00032 
00033 #ifdef __APPLE__
00034 
00035 #include "Fl_Native_File_Chooser_common.cxx"            // strnew/strfree/strapp/chrcat
00036 #include <libgen.h>             // dirname(3)
00037 #include <sys/types.h>          // stat(2)
00038 #include <sys/stat.h>           // stat(2)
00039 
00040 
00041 #include <FL/Fl.H>
00042 #include <FL/Fl_Native_File_Chooser.H>
00043 #include <FL/filename.H>
00044 
00045 // FREE PATHNAMES ARRAY, IF IT HAS ANY CONTENTS
00046 void Fl_Native_File_Chooser::clear_pathnames() {
00047   if ( _pathnames ) {
00048     while ( --_tpathnames >= 0 ) {
00049       _pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]);
00050     }
00051     delete [] _pathnames;
00052     _pathnames = NULL;
00053   }
00054   _tpathnames = 0;
00055 }
00056 
00057 // SET A SINGLE PATHNAME
00058 void Fl_Native_File_Chooser::set_single_pathname(const char *s) {
00059   clear_pathnames();
00060   _pathnames = new char*[1];
00061   _pathnames[0] = strnew(s);
00062   _tpathnames = 1;
00063 }
00064 
00065 // CONSTRUCTOR
00066 Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) {
00067   _btype          = val;
00068   _panel = NULL;
00069   _options        = NO_OPTIONS;
00070   _pathnames      = NULL;
00071   _tpathnames     = 0;
00072   _title          = NULL;
00073   _filter         = NULL;
00074   _filt_names     = NULL;
00075   memset(_filt_patt, 0, sizeof(char*) * MAXFILTERS);
00076   _filt_total     = 0;
00077   _filt_value     = 0;
00078   _directory      = NULL;
00079   _preset_file    = NULL;
00080   _errmsg         = NULL;
00081 }
00082 
00083 // DESTRUCTOR
00084 Fl_Native_File_Chooser::~Fl_Native_File_Chooser() {
00085   // _opts              // nothing to manage
00086   // _options           // nothing to manage
00087   // _keepstate         // nothing to manage
00088   // _tempitem          // nothing to manage
00089   clear_pathnames();
00090   _directory   = strfree(_directory);
00091   _title       = strfree(_title);
00092   _preset_file = strfree(_preset_file);
00093   _filter      = strfree(_filter);
00094   //_filt_names         // managed by clear_filters()
00095   //_filt_patt[i]       // managed by clear_filters()
00096   //_filt_total         // managed by clear_filters()
00097   clear_filters();
00098   //_filt_value         // nothing to manage
00099   _errmsg = strfree(_errmsg);
00100 }
00101 
00102 // GET TYPE OF BROWSER
00103 int Fl_Native_File_Chooser::type() const {
00104   return(_btype);
00105 }
00106 
00107 // SET OPTIONS
00108 void Fl_Native_File_Chooser::options(int val) {
00109   _options = val;
00110 }
00111 
00112 // GET OPTIONS
00113 int Fl_Native_File_Chooser::options() const {
00114   return(_options);
00115 }
00116 
00117 // SHOW THE BROWSER WINDOW
00118 //     Returns:
00119 //         0 - user picked a file
00120 //         1 - user cancelled
00121 //        -1 - failed; errmsg() has reason
00122 //
00123 int Fl_Native_File_Chooser::show() {
00124 
00125   // Make sure fltk interface updates before posting our dialog
00126   Fl::flush();
00127   
00128   // POST BROWSER
00129   int err = post();
00130 
00131   _filt_total = 0;
00132 
00133   return(err);
00134 }
00135 
00136 // SET ERROR MESSAGE
00137 //     Internal use only.
00138 //
00139 void Fl_Native_File_Chooser::errmsg(const char *msg) {
00140   _errmsg = strfree(_errmsg);
00141   _errmsg = strnew(msg);
00142 }
00143 
00144 // RETURN ERROR MESSAGE
00145 const char *Fl_Native_File_Chooser::errmsg() const {
00146   return(_errmsg ? _errmsg : "No error");
00147 }
00148 
00149 // GET FILENAME
00150 const char* Fl_Native_File_Chooser::filename() const {
00151   if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]);
00152   return("");
00153 }
00154 
00155 // GET FILENAME FROM LIST OF FILENAMES
00156 const char* Fl_Native_File_Chooser::filename(int i) const {
00157   if ( _pathnames && i < _tpathnames ) return(_pathnames[i]);
00158   return("");
00159 }
00160 
00161 // GET TOTAL FILENAMES CHOSEN
00162 int Fl_Native_File_Chooser::count() const {
00163   return(_tpathnames);
00164 }
00165 
00166 // PRESET PATHNAME
00167 //     Value can be NULL for none.
00168 //
00169 void Fl_Native_File_Chooser::directory(const char *val) {
00170   _directory = strfree(_directory);
00171   _directory = strnew(val);
00172 }
00173 
00174 // GET PRESET PATHNAME
00175 //     Returned value can be NULL if none set.
00176 //
00177 const char* Fl_Native_File_Chooser::directory() const {
00178   return(_directory);
00179 }
00180 
00181 // SET TITLE
00182 //     Value can be NULL if no title desired.
00183 //
00184 void Fl_Native_File_Chooser::title(const char *val) {
00185   _title = strfree(_title);
00186   _title = strnew(val);
00187 }
00188 
00189 // GET TITLE
00190 //     Returned value can be NULL if none set.
00191 //
00192 const char *Fl_Native_File_Chooser::title() const {
00193   return(_title);
00194 }
00195 
00196 // SET FILTER
00197 //     Can be NULL if no filter needed
00198 //
00199 void Fl_Native_File_Chooser::filter(const char *val) {
00200   _filter = strfree(_filter);
00201   _filter = strnew(val);
00202 
00203   // Parse filter user specified
00204   //     IN: _filter = "C Files\t*.{cxx,h}\nText Files\t*.txt"
00205   //    OUT: _filt_names   = "C Files\tText Files"
00206   //         _filt_patt[0] = "*.{cxx,h}"
00207   //         _filt_patt[1] = "*.txt"
00208   //         _filt_total   = 2
00209   //
00210   parse_filter(_filter);
00211 }
00212 
00213 // GET FILTER
00214 //     Returned value can be NULL if none set.
00215 //
00216 const char *Fl_Native_File_Chooser::filter() const {
00217   return(_filter);
00218 }
00219 
00220 // CLEAR ALL FILTERS
00221 //    Internal use only.
00222 //
00223 void Fl_Native_File_Chooser::clear_filters() {
00224   _filt_names = strfree(_filt_names);
00225   for (int i=0; i<_filt_total; i++) {
00226     _filt_patt[i] = strfree(_filt_patt[i]);
00227   }
00228   _filt_total = 0;
00229 }
00230 
00231 // PARSE USER'S FILTER SPEC
00232 //    Parses user specified filter ('in'),
00233 //    breaks out into _filt_patt[], _filt_names, and _filt_total.
00234 //
00235 //    Handles:
00236 //    IN:                                   OUT:_filt_names    OUT: _filt_patt
00237 //    ------------------------------------  ------------------ ---------------
00238 //    "*.{ma,mb}"                           "*.{ma,mb} Files"  "*.{ma,mb}"
00239 //    "*.[abc]"                             "*.[abc] Files"    "*.[abc]"
00240 //    "*.txt"                               "*.txt Files"      "*.c"
00241 //    "C Files\t*.[ch]"                     "C Files"          "*.[ch]"
00242 //    "C Files\t*.[ch]\nText Files\t*.cxx"  "C Files"          "*.[ch]"
00243 //
00244 //    Parsing Mode:
00245 //         IN:"C Files\t*.{cxx,h}"
00246 //             |||||||  |||||||||
00247 //       mode: nnnnnnn  wwwwwwwww
00248 //             \_____/  \_______/
00249 //              Name     Wildcard
00250 //
00251 void Fl_Native_File_Chooser::parse_filter(const char *in) {
00252   clear_filters();
00253   if ( ! in ) return;
00254   int has_name = strchr(in, '\t') ? 1 : 0;
00255 
00256   char mode = has_name ? 'n' : 'w';     // parse mode: n=title, w=wildcard
00257   char wildcard[1024] = "";             // parsed wildcard
00258   char name[1024] = "";
00259 
00260   // Parse filter user specified
00261   for ( ; 1; in++ ) {
00262 
00266     
00267     switch (*in) {
00268       // FINISHED PARSING NAME?
00269       case '\t':
00270         if ( mode != 'n' ) goto regchar;
00271         mode = 'w';
00272         break;
00273 
00274       // ESCAPE NEXT CHAR
00275       case '\\':
00276         ++in;
00277         goto regchar;
00278 
00279       // FINISHED PARSING ONE OF POSSIBLY SEVERAL FILTERS?
00280       case '\r':
00281       case '\n':
00282       case '\0':
00283         // TITLE
00284         //     If user didn't specify a name, make one
00285         //
00286         if ( name[0] == '\0' ) {
00287           sprintf(name, "%.*s Files", (int)sizeof(name)-10, wildcard);
00288         }
00289         // APPEND NEW FILTER TO LIST
00290         if ( wildcard[0] ) {
00291           // Add to filtername list
00292           //     Tab delimit if more than one. We later break
00293           //     tab delimited string into CFArray with 
00294           //     CFStringCreateArrayBySeparatingStrings()
00295           //
00296           if ( _filt_total ) {
00297               _filt_names = strapp(_filt_names, "\t");
00298           }
00299           _filt_names = strapp(_filt_names, name);
00300 
00301           // Add filter to the pattern array
00302           _filt_patt[_filt_total++] = strnew(wildcard);
00303         }
00304         // RESET
00305         wildcard[0] = name[0] = '\0';
00306         mode = strchr(in, '\t') ? 'n' : 'w';
00307         // DONE?
00308         if ( *in == '\0' ) return;      // done
00309         else continue;                  // not done yet, more filters
00310 
00311       // Parse all other chars
00312       default:                          // handle all non-special chars
00313       regchar:                          // handle regular char
00314         switch ( mode ) {
00315           case 'n': chrcat(name, *in);     continue;
00316           case 'w': chrcat(wildcard, *in); continue;
00317         }
00318         break;
00319     }
00320   }
00321   //NOTREACHED
00322 }
00323 
00324 // SET PRESET FILE
00325 //     Value can be NULL for none.
00326 //
00327 void Fl_Native_File_Chooser::preset_file(const char* val) {
00328   _preset_file = strfree(_preset_file);
00329   _preset_file = strnew(val);
00330 }
00331 
00332 // PRESET FILE
00333 //     Returned value can be NULL if none set.
00334 //
00335 const char* Fl_Native_File_Chooser::preset_file() {
00336   return(_preset_file);
00337 }
00338 
00339 #import <Cocoa/Cocoa.h>
00340 #define UNLIKELYPREFIX "___fl_very_unlikely_prefix_"
00341 #ifndef MAC_OS_X_VERSION_10_6
00342 #define MAC_OS_X_VERSION_10_6 1060
00343 #endif
00344 
00345 int Fl_Native_File_Chooser::get_saveas_basename(void) {
00346   char *q = strdup( [[(NSSavePanel*)_panel filename] fileSystemRepresentation] );
00347   id delegate = [(NSSavePanel*)_panel delegate];
00348   if (delegate != nil) {
00349     const char *d = [[(NSSavePanel*)_panel directory] fileSystemRepresentation];
00350     int l = strlen(d) + 1;
00351     int lu = strlen(UNLIKELYPREFIX);
00352     // Remove UNLIKELYPREFIX between directory and filename parts
00353     memmove(q + l, q + l + lu, strlen(q + l + lu) + 1);
00354   }
00355   set_single_pathname( q );
00356   free(q);
00357   return 0;
00358 }
00359 
00360 // SET THE TYPE OF BROWSER
00361 void Fl_Native_File_Chooser::type(int val) {
00362   _btype = val;
00363   switch (_btype) {
00364     case BROWSE_FILE:
00365     case BROWSE_MULTI_FILE:
00366     case BROWSE_DIRECTORY:
00367     case BROWSE_MULTI_DIRECTORY:
00368       _panel =  [NSOpenPanel openPanel];
00369       break;      
00370     case BROWSE_SAVE_DIRECTORY:
00371     case BROWSE_SAVE_FILE:
00372       _panel =  [NSSavePanel savePanel];
00373       break;
00374   }
00375 }
00376   
00377 @interface FLopenDelegate : NSObject 
00378 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
00379 <NSOpenSavePanelDelegate>
00380 #endif
00381 {
00382   NSPopUpButton *nspopup;
00383   char **filter_pattern;
00384 }
00385 - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern;
00386 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
00387 @end
00388 @implementation FLopenDelegate
00389 - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern
00390 {
00391   nspopup = popup;
00392   filter_pattern = pattern;
00393   return self;
00394 }
00395 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
00396 {
00397   if ( [nspopup indexOfSelectedItem] == [nspopup numberOfItems] - 1) return YES;
00398   const char *pathname = [filename fileSystemRepresentation];
00399   if ( fl_filename_isdir(pathname) ) return YES;
00400   if ( fl_filename_match(pathname, filter_pattern[ [nspopup indexOfSelectedItem] ]) ) return YES;
00401   return NO;
00402 }
00403 @end
00404 
00405 @interface FLsaveDelegate : NSObject 
00406 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
00407 <NSOpenSavePanelDelegate>
00408 #endif
00409 {
00410 }
00411 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag;
00412 @end
00413 @implementation FLsaveDelegate
00414 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
00415 {
00416   if (! okFlag) return filename;
00417   // User has clicked save, and no overwrite confirmation should occur.
00418   // To get the latter, we need to change the name we return (hence the prefix):
00419   return [@ UNLIKELYPREFIX stringByAppendingString:filename];
00420 }
00421 @end
00422   
00423 static NSPopUpButton *createPopupAccessory(NSSavePanel *panel, const char *filter, const char *title, int rank)
00424 {
00425   NSPopUpButton *popup;
00426   NSRect rectview = NSMakeRect(5, 5, 350, 30 );
00427   NSView *view = [[[NSView alloc] initWithFrame:rectview] autorelease];
00428   NSRect rectbox = NSMakeRect(0, 3, 50, 1 );
00429   NSBox *box = [[[NSBox alloc] initWithFrame:rectbox] autorelease];
00430   NSRect rectpop = NSMakeRect(60, 0, 250, 30 );
00431   popup = [[[NSPopUpButton alloc ] initWithFrame:rectpop pullsDown:NO] autorelease];
00432   [view addSubview:box];
00433   [view addSubview:popup];
00434   [box setBorderType:NSNoBorder];
00435   NSString *nstitle = [[NSString alloc] initWithUTF8String:title];
00436   [box setTitle:nstitle];
00437   [nstitle release];
00438   NSFont *font = [NSFont controlContentFontOfSize:NSRegularControlSize];
00439   [box setTitleFont:font];
00440   [box sizeToFit];
00441   CFStringRef tab = CFSTR("\n");
00442   CFStringRef tmp_cfs;
00443   tmp_cfs = CFStringCreateWithCString(NULL, filter, kCFStringEncodingASCII);
00444   CFArrayRef array = CFStringCreateArrayBySeparatingStrings(NULL, tmp_cfs, tab);
00445   CFRelease(tmp_cfs);
00446   CFRelease(tab);
00447   [popup addItemsWithTitles:(NSArray*)array];
00448   NSMenuItem *item = [popup itemWithTitle:@""];
00449   if (item) [popup removeItemWithTitle:@""];
00450   CFRelease(array);
00451   [popup selectItemAtIndex:rank];
00452   [panel setAccessoryView:view];
00453   return popup;
00454 }
00455   
00456 // POST BROWSER
00457 //     Internal use only.
00458 //     Assumes '_opts' has been initialized.
00459 //
00460 //     Returns:
00461 //         0 - user picked a file
00462 //         1 - user cancelled
00463 //        -1 - failed; errmsg() has reason
00464 //     
00465 int Fl_Native_File_Chooser::post() {
00466   // INITIALIZE BROWSER
00467   if ( _filt_total == 0 ) {     // Make sure they match
00468     _filt_value = 0;            // TBD: move to someplace more logical?
00469   }
00470   NSAutoreleasePool *localPool;
00471   localPool = [[NSAutoreleasePool alloc] init];
00472   int retval;
00473   NSString *nstitle = [NSString stringWithUTF8String: (_title ? _title : "No Title")];
00474   [(NSSavePanel*)_panel setTitle:nstitle];
00475   switch (_btype) {
00476     case BROWSE_MULTI_FILE:
00477       [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
00478       break;
00479     case BROWSE_MULTI_DIRECTORY:
00480       [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
00481     case BROWSE_DIRECTORY:
00482       [(NSOpenPanel*)_panel setCanChooseDirectories:YES];
00483       break;
00484     case BROWSE_SAVE_DIRECTORY:
00485       [(NSSavePanel*)_panel setCanCreateDirectories:YES];
00486       break;
00487   }
00488   
00489   // SHOW THE DIALOG
00490   if ( [(NSSavePanel*)_panel isKindOfClass:[NSOpenPanel class]] ) {
00491     NSPopUpButton *popup = nil;
00492     if (_filt_total) {
00493       char *p; p = _filter;
00494       char *q; q = new char[strlen(p) + 1];
00495       char *r, *s, *t;
00496       t = q;
00497       do {      // copy to t what is in _filter removing what is between \t and \n, if any
00498         r = strchr(p, '\n');
00499         if (!r) r = p + strlen(p) - 1;
00500         s = strchr(p, '\t');
00501         if (s && s < r) { memcpy(q, p, s - p); q += s - p; *(q++) = '\n'; }
00502         else { memcpy(q, p, r - p + 1); q += r - p + 1; }
00503         *q = 0;
00504         p = r + 1;
00505       } while(*p);
00506       popup = createPopupAccessory((NSSavePanel*)_panel, t, "Enable:", 0);
00507       delete t;
00508       [[popup menu] addItem:[NSMenuItem separatorItem]];
00509       [popup addItemWithTitle:@"All Documents"];
00510       [popup setAction:@selector(validateVisibleColumns)];
00511       [popup setTarget:(NSObject*)_panel];
00512       static FLopenDelegate *openDelegate = nil;
00513       if (openDelegate == nil) {
00514         // not to be ever freed
00515         openDelegate = [[FLopenDelegate alloc] init];
00516       }
00517       [openDelegate setPopup:popup filter_pattern:_filt_patt];
00518       [(NSOpenPanel*)_panel setDelegate:openDelegate];
00519     }
00520     NSString *dir = nil;
00521     NSString *fname = nil;
00522     NSString *preset = nil;
00523     if (_preset_file) {
00524       preset = [[NSString alloc] initWithUTF8String:_preset_file];
00525       if (strchr(_preset_file, '/') != NULL) 
00526         dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]];
00527       fname = [preset lastPathComponent];
00528     }
00529     if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory];
00530     retval = [(NSOpenPanel*)_panel runModalForDirectory:dir file:fname types:nil];      
00531     [dir release];
00532     [preset release];
00533     if (_filt_total) {
00534       _filt_value = [popup indexOfSelectedItem];
00535     }
00536     if ( retval == NSOKButton ) {
00537       clear_pathnames();
00538       NSArray *array = [(NSOpenPanel*)_panel filenames];
00539       _tpathnames = [array count];
00540       _pathnames = new char*[_tpathnames];
00541       for(int i = 0; i < _tpathnames; i++) {
00542         _pathnames[i] = strnew([(NSString*)[array objectAtIndex:i] fileSystemRepresentation]);
00543       }
00544     }
00545   }
00546   else {
00547     NSString *dir = nil;
00548     NSString *fname = nil;
00549     NSString *preset = nil;
00550     NSPopUpButton *popup = nil;
00551     [(NSSavePanel*)_panel setAllowsOtherFileTypes:YES];
00552     if ( !(_options & SAVEAS_CONFIRM) ) {
00553       static FLsaveDelegate *saveDelegate = nil;
00554       if (saveDelegate == nil)saveDelegate = [[FLsaveDelegate alloc] init]; // not to be ever freed
00555       [(NSSavePanel*)_panel setDelegate:saveDelegate];
00556     }
00557     if (_preset_file) {
00558       preset = [[NSString alloc] initWithUTF8String:_preset_file];
00559       if (strchr(_preset_file, '/') != NULL) {
00560         dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]];
00561       }
00562       fname = [preset lastPathComponent];
00563     }
00564     if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory];
00565     if (_filt_total) {
00566       popup = createPopupAccessory((NSSavePanel*)_panel, _filter, "Format:", _filt_value);
00567     }
00568     retval = [(NSSavePanel*)_panel runModalForDirectory:dir file:fname];
00569     if (_filt_total) {
00570       _filt_value = [popup indexOfSelectedItem];
00571     }
00572     [dir release];
00573     [preset release];
00574     if ( retval == NSOKButton ) get_saveas_basename();
00575   }
00576   [localPool release];
00577   return (retval == NSOKButton ? 0 : 1);
00578 }
00579 
00580 #endif // __APPLE__
00581 
00582 //
00583 // End of "$Id: Fl_Native_File_Chooser_MAC.mm 8062 2010-12-19 17:57:19Z manolo $".
00584 //