themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_Native_File_Chooser_MAC.mm
blobc17b4a076f2c9025612e34533f4565acf842c54c
1 // "$Id: Fl_Native_File_Chooser_MAC.mm 8784 2011-06-06 12:11:04Z manolo $"
2 //
3 // FLTK native OS file chooser widget
4 //
5 // Copyright 1998-2010 by Bill Spitzak and others.
6 // Copyright 2004 Greg Ercolano.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 // USA.
23 // Please report all bugs and problems to:
25 //     http://www.fltk.org/str.php
28 // TODO:
29 //      o When doing 'open file', only dir is preset, not filename.
30 //        Possibly 'preset_file' could be used to select the filename.
33 #ifdef __APPLE__
35 #include "Fl_Native_File_Chooser_common.cxx"            // strnew/strfree/strapp/chrcat
36 #include <libgen.h>             // dirname(3)
37 #include <sys/types.h>          // stat(2)
38 #include <sys/stat.h>           // stat(2)
41 #include <FL/Fl.H>
42 #include <FL/Fl_Native_File_Chooser.H>
43 #include <FL/Fl_File_Chooser.H>
44 #include <FL/filename.H>
46 // FREE PATHNAMES ARRAY, IF IT HAS ANY CONTENTS
47 void Fl_Native_File_Chooser::clear_pathnames() {
48   if ( _pathnames ) {
49     while ( --_tpathnames >= 0 ) {
50       _pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]);
51     }
52     delete [] _pathnames;
53     _pathnames = NULL;
54   }
55   _tpathnames = 0;
58 // SET A SINGLE PATHNAME
59 void Fl_Native_File_Chooser::set_single_pathname(const char *s) {
60   clear_pathnames();
61   _pathnames = new char*[1];
62   _pathnames[0] = strnew(s);
63   _tpathnames = 1;
66 // CONSTRUCTOR
67 Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) {
68   _btype          = val;
69   _panel = NULL;
70   _options        = NO_OPTIONS;
71   _pathnames      = NULL;
72   _tpathnames     = 0;
73   _title          = NULL;
74   _filter         = NULL;
75   _filt_names     = NULL;
76   memset(_filt_patt, 0, sizeof(char*) * MAXFILTERS);
77   _filt_total     = 0;
78   _filt_value     = 0;
79   _directory      = NULL;
80   _preset_file    = NULL;
81   _errmsg         = NULL;
84 // DESTRUCTOR
85 Fl_Native_File_Chooser::~Fl_Native_File_Chooser() {
86   // _opts              // nothing to manage
87   // _options           // nothing to manage
88   // _keepstate         // nothing to manage
89   // _tempitem          // nothing to manage
90   clear_pathnames();
91   _directory   = strfree(_directory);
92   _title       = strfree(_title);
93   _preset_file = strfree(_preset_file);
94   _filter      = strfree(_filter);
95   //_filt_names         // managed by clear_filters()
96   //_filt_patt[i]       // managed by clear_filters()
97   //_filt_total         // managed by clear_filters()
98   clear_filters();
99   //_filt_value         // nothing to manage
100   _errmsg = strfree(_errmsg);
103 // GET TYPE OF BROWSER
104 int Fl_Native_File_Chooser::type() const {
105   return(_btype);
108 // SET OPTIONS
109 void Fl_Native_File_Chooser::options(int val) {
110   _options = val;
113 // GET OPTIONS
114 int Fl_Native_File_Chooser::options() const {
115   return(_options);
118 // SHOW THE BROWSER WINDOW
119 //     Returns:
120 //         0 - user picked a file
121 //         1 - user cancelled
122 //        -1 - failed; errmsg() has reason
124 int Fl_Native_File_Chooser::show() {
126   // Make sure fltk interface updates before posting our dialog
127   Fl::flush();
128   
129   // POST BROWSER
130   int err = post();
132   _filt_total = 0;
134   return(err);
137 // SET ERROR MESSAGE
138 //     Internal use only.
140 void Fl_Native_File_Chooser::errmsg(const char *msg) {
141   _errmsg = strfree(_errmsg);
142   _errmsg = strnew(msg);
145 // RETURN ERROR MESSAGE
146 const char *Fl_Native_File_Chooser::errmsg() const {
147   return(_errmsg ? _errmsg : "No error");
150 // GET FILENAME
151 const char* Fl_Native_File_Chooser::filename() const {
152   if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]);
153   return("");
156 // GET FILENAME FROM LIST OF FILENAMES
157 const char* Fl_Native_File_Chooser::filename(int i) const {
158   if ( _pathnames && i < _tpathnames ) return(_pathnames[i]);
159   return("");
162 // GET TOTAL FILENAMES CHOSEN
163 int Fl_Native_File_Chooser::count() const {
164   return(_tpathnames);
167 // PRESET PATHNAME
168 //     Value can be NULL for none.
170 void Fl_Native_File_Chooser::directory(const char *val) {
171   _directory = strfree(_directory);
172   _directory = strnew(val);
175 // GET PRESET PATHNAME
176 //     Returned value can be NULL if none set.
178 const char* Fl_Native_File_Chooser::directory() const {
179   return(_directory);
182 // SET TITLE
183 //     Value can be NULL if no title desired.
185 void Fl_Native_File_Chooser::title(const char *val) {
186   _title = strfree(_title);
187   _title = strnew(val);
190 // GET TITLE
191 //     Returned value can be NULL if none set.
193 const char *Fl_Native_File_Chooser::title() const {
194   return(_title);
197 // SET FILTER
198 //     Can be NULL if no filter needed
200 void Fl_Native_File_Chooser::filter(const char *val) {
201   _filter = strfree(_filter);
202   _filter = strnew(val);
204   // Parse filter user specified
205   //     IN: _filter = "C Files\t*.{cxx,h}\nText Files\t*.txt"
206   //    OUT: _filt_names   = "C Files\tText Files"
207   //         _filt_patt[0] = "*.{cxx,h}"
208   //         _filt_patt[1] = "*.txt"
209   //         _filt_total   = 2
210   //
211   parse_filter(_filter);
214 // GET FILTER
215 //     Returned value can be NULL if none set.
217 const char *Fl_Native_File_Chooser::filter() const {
218   return(_filter);
221 // CLEAR ALL FILTERS
222 //    Internal use only.
224 void Fl_Native_File_Chooser::clear_filters() {
225   _filt_names = strfree(_filt_names);
226   for (int i=0; i<_filt_total; i++) {
227     _filt_patt[i] = strfree(_filt_patt[i]);
228   }
229   _filt_total = 0;
232 // PARSE USER'S FILTER SPEC
233 //    Parses user specified filter ('in'),
234 //    breaks out into _filt_patt[], _filt_names, and _filt_total.
236 //    Handles:
237 //    IN:                                   OUT:_filt_names    OUT: _filt_patt
238 //    ------------------------------------  ------------------ ---------------
239 //    "*.{ma,mb}"                           "*.{ma,mb} Files"  "*.{ma,mb}"
240 //    "*.[abc]"                             "*.[abc] Files"    "*.[abc]"
241 //    "*.txt"                               "*.txt Files"      "*.c"
242 //    "C Files\t*.[ch]"                     "C Files"          "*.[ch]"
243 //    "C Files\t*.[ch]\nText Files\t*.cxx"  "C Files"          "*.[ch]"
245 //    Parsing Mode:
246 //         IN:"C Files\t*.{cxx,h}"
247 //             |||||||  |||||||||
248 //       mode: nnnnnnn  wwwwwwwww
249 //             \_____/  \_______/
250 //              Name     Wildcard
252 void Fl_Native_File_Chooser::parse_filter(const char *in) {
253   clear_filters();
254   if ( ! in ) return;
255   int has_name = strchr(in, '\t') ? 1 : 0;
257   char mode = has_name ? 'n' : 'w';     // parse mode: n=title, w=wildcard
258   char wildcard[1024] = "";             // parsed wildcard
259   char name[1024] = "";
261   // Parse filter user specified
262   for ( ; 1; in++ ) {
264     //// DEBUG
265     //// printf("WORKING ON '%c': mode=<%c> name=<%s> wildcard=<%s>\n",
266     ////                    *in,  mode,     name,     wildcard);
267     
268     switch (*in) {
269       // FINISHED PARSING NAME?
270       case '\t':
271         if ( mode != 'n' ) goto regchar;
272         mode = 'w';
273         break;
275       // ESCAPE NEXT CHAR
276       case '\\':
277         ++in;
278         goto regchar;
280       // FINISHED PARSING ONE OF POSSIBLY SEVERAL FILTERS?
281       case '\r':
282       case '\n':
283       case '\0':
284         // TITLE
285         //     If user didn't specify a name, make one
286         //
287         if ( name[0] == '\0' ) {
288           sprintf(name, "%.*s Files", (int)sizeof(name)-10, wildcard);
289         }
290         // APPEND NEW FILTER TO LIST
291         if ( wildcard[0] ) {
292           // Add to filtername list
293           //     Tab delimit if more than one. We later break
294           //     tab delimited string into CFArray with 
295           //     CFStringCreateArrayBySeparatingStrings()
296           //
297           if ( _filt_total ) {
298               _filt_names = strapp(_filt_names, "\t");
299           }
300           _filt_names = strapp(_filt_names, name);
302           // Add filter to the pattern array
303           _filt_patt[_filt_total++] = strnew(wildcard);
304         }
305         // RESET
306         wildcard[0] = name[0] = '\0';
307         mode = strchr(in, '\t') ? 'n' : 'w';
308         // DONE?
309         if ( *in == '\0' ) return;      // done
310         else continue;                  // not done yet, more filters
312       // Parse all other chars
313       default:                          // handle all non-special chars
314       regchar:                          // handle regular char
315         switch ( mode ) {
316           case 'n': chrcat(name, *in);     continue;
317           case 'w': chrcat(wildcard, *in); continue;
318         }
319         break;
320     }
321   }
322   //NOTREACHED
325 // SET PRESET FILE
326 //     Value can be NULL for none.
328 void Fl_Native_File_Chooser::preset_file(const char* val) {
329   _preset_file = strfree(_preset_file);
330   _preset_file = strnew(val);
333 // PRESET FILE
334 //     Returned value can be NULL if none set.
336 const char* Fl_Native_File_Chooser::preset_file() const {
337   return(_preset_file);
340 void Fl_Native_File_Chooser::filter_value(int val) {
341   _filt_value = val;
344 int Fl_Native_File_Chooser::filter_value() const {
345   return(_filt_value);
348 int Fl_Native_File_Chooser::filters() const {
349   return(_filt_total);
352 #import <Cocoa/Cocoa.h>
353 #define UNLIKELYPREFIX "___fl_very_unlikely_prefix_"
354 #ifndef MAC_OS_X_VERSION_10_6
355 #define MAC_OS_X_VERSION_10_6 1060
356 #endif
358 int Fl_Native_File_Chooser::get_saveas_basename(void) {
359   char *q = strdup( [[(NSSavePanel*)_panel filename] fileSystemRepresentation] );
360   id delegate = [(NSSavePanel*)_panel delegate];
361   if (delegate != nil) {
362     const char *d = [[(NSSavePanel*)_panel directory] fileSystemRepresentation];
363     int l = strlen(d) + 1;
364     int lu = strlen(UNLIKELYPREFIX);
365     // Remove UNLIKELYPREFIX between directory and filename parts
366     memmove(q + l, q + l + lu, strlen(q + l + lu) + 1);
367   }
368   set_single_pathname( q );
369   free(q);
370   return 0;
373 // SET THE TYPE OF BROWSER
374 void Fl_Native_File_Chooser::type(int val) {
375   _btype = val;
378 /* Input
379  filter=  "C files\t*.{c,h}\nText files\t*.txt\n"
380  patterns[0] = "*.{c,h}"
381  patterns[1] = "*.txt"
382  count = 2
383  Return:
384  "C files (*.{c,h})\nText files (*.txt)\n"
385  */
386 static char *prepareMacFilter(int count, const char *filter, char **patterns) {
387   int rank = 0, l = 0;
388   for (int i = 0; i < count; i++) {
389     l += strlen(patterns[i]) + 3;
390     }
391   const char *p = filter;
392   char *q; q = new char[strlen(p) + l + 1];
393   const char *r, *s;
394   char *t;
395   t = q;
396   do {  // copy to t what is in filter removing what is between \t and \n, if any
397     r = strchr(p, '\n');
398     if (!r) r = p + strlen(p);
399     s = strchr(p, '\t');
400     if (s && s < r) { 
401       memcpy(q, p, s - p); 
402       q += s - p; 
403       if (rank < count) { sprintf(q, " (%s)", patterns[rank]); q += strlen(q); }
404     }
405     else { 
406       memcpy(q, p, r - p); 
407       q += r - p; 
408     }
409     rank++;
410     *(q++) = '\n'; 
411     if (*p) p = r + 1;
412   } while(*p);
413   *q = 0;
414   return t;
416   
417 @interface FLopenDelegate : NSObject 
418 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
419 <NSOpenSavePanelDelegate>
420 #endif
422   NSPopUpButton *nspopup;
423   char **filter_pattern;
425 - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern;
426 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
427 @end
428 @implementation FLopenDelegate
429 - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern
431   nspopup = popup;
432   filter_pattern = pattern;
433   return self;
435 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
437   if ( [nspopup indexOfSelectedItem] == [nspopup numberOfItems] - 1) return YES;
438   const char *pathname = [filename fileSystemRepresentation];
439   if ( fl_filename_isdir(pathname) ) return YES;
440   if ( fl_filename_match(pathname, filter_pattern[ [nspopup indexOfSelectedItem] ]) ) return YES;
441   return NO;
443 @end
445 @interface FLsaveDelegate : NSObject 
446 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
447 <NSOpenSavePanelDelegate>
448 #endif
451 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag;
452 @end
453 @implementation FLsaveDelegate
454 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
456   if (! okFlag) return filename;
457   // User has clicked save, and no overwrite confirmation should occur.
458   // To get the latter, we need to change the name we return (hence the prefix):
459   return [@ UNLIKELYPREFIX stringByAppendingString:filename];
461 @end
462   
463 static NSPopUpButton *createPopupAccessory(NSSavePanel *panel, const char *filter, const char *title, int rank)
465   NSPopUpButton *popup;
466   NSRect rectview = NSMakeRect(5, 5, 350, 30 );
467   NSView *view = [[[NSView alloc] initWithFrame:rectview] autorelease];
468   NSRect rectbox = NSMakeRect(0, 3, 140, 20 );
469   NSBox *box = [[[NSBox alloc] initWithFrame:rectbox] autorelease];
470   NSRect rectpop = NSMakeRect(105, 0, 246, 30 );
471   popup = [[[NSPopUpButton alloc ] initWithFrame:rectpop pullsDown:NO] autorelease];
472   [view addSubview:box];
473   [view addSubview:popup];
474   [box setBorderType:NSNoBorder];
475   NSString *nstitle = [[NSString alloc] initWithUTF8String:title];
476   [box setTitle:nstitle];
477   [nstitle release];
478   NSFont *font = [NSFont controlContentFontOfSize:NSRegularControlSize];
479   [box setTitleFont:font];
480   [box sizeToFit];
481   // horizontally move box to fit the locale-dependent width of its title
482   NSRect r=[box frame];
483   NSPoint o = r.origin;
484   o.x = rectpop.origin.x - r.size.width + 15;
485   [box setFrameOrigin:o];
486   CFStringRef tab = CFSTR("\n");
487   CFStringRef tmp_cfs;
488   tmp_cfs = CFStringCreateWithCString(NULL, filter, kCFStringEncodingUTF8);
489   CFArrayRef array = CFStringCreateArrayBySeparatingStrings(NULL, tmp_cfs, tab);
490   CFRelease(tmp_cfs);
491   CFRelease(tab);
492   [popup addItemsWithTitles:(NSArray*)array];
493   NSMenuItem *item = [popup itemWithTitle:@""];
494   if (item) [popup removeItemWithTitle:@""];
495   CFRelease(array);
496   [popup selectItemAtIndex:rank];
497   [panel setAccessoryView:view];
498   return popup;
500   
501 // POST BROWSER
502 //     Internal use only.
503 //     Assumes '_opts' has been initialized.
505 //     Returns:
506 //         0 - user picked a file
507 //         1 - user cancelled
508 //        -1 - failed; errmsg() has reason
509 //     
510 int Fl_Native_File_Chooser::post() {
511   // INITIALIZE BROWSER
512   if ( _filt_total == 0 ) {     // Make sure they match
513     _filt_value = 0;            // TBD: move to someplace more logical?
514   }
515   NSAutoreleasePool *localPool;
516   localPool = [[NSAutoreleasePool alloc] init];
517   switch (_btype) {
518     case BROWSE_FILE:
519     case BROWSE_MULTI_FILE:
520     case BROWSE_DIRECTORY:
521     case BROWSE_MULTI_DIRECTORY:
522       _panel =  [NSOpenPanel openPanel];
523       break;      
524     case BROWSE_SAVE_DIRECTORY:
525     case BROWSE_SAVE_FILE:
526       _panel =  [NSSavePanel savePanel];
527       break;
528   }
529   int retval;
530   NSString *nstitle = [NSString stringWithUTF8String: (_title ? _title : "No Title")];
531   [(NSSavePanel*)_panel setTitle:nstitle];
532   switch (_btype) {
533     case BROWSE_MULTI_FILE:
534       [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
535       break;
536     case BROWSE_MULTI_DIRECTORY:
537       [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
538       /* FALLTHROUGH */
539     case BROWSE_DIRECTORY:
540       [(NSOpenPanel*)_panel setCanChooseDirectories:YES];
541       break;
542     case BROWSE_SAVE_DIRECTORY:
543       [(NSSavePanel*)_panel setCanCreateDirectories:YES];
544       break;
545   }
546   
547   // SHOW THE DIALOG
548   if ( [(NSSavePanel*)_panel isKindOfClass:[NSOpenPanel class]] ) {
549     NSPopUpButton *popup = nil;
550     if (_filt_total) {
551       char *t = prepareMacFilter(_filt_total, _filter, _filt_patt);
552       popup = createPopupAccessory((NSSavePanel*)_panel, t, Fl_File_Chooser::show_label, 0);
553       delete[] t;
554       [[popup menu] addItem:[NSMenuItem separatorItem]];
555       [popup addItemWithTitle:[[NSString alloc] initWithUTF8String:Fl_File_Chooser::all_files_label]];
556       [popup setAction:@selector(validateVisibleColumns)];
557       [popup setTarget:(NSObject*)_panel];
558       static FLopenDelegate *openDelegate = nil;
559       if (openDelegate == nil) {
560         // not to be ever freed
561         openDelegate = [[FLopenDelegate alloc] init];
562       }
563       [openDelegate setPopup:popup filter_pattern:_filt_patt];
564       [(NSOpenPanel*)_panel setDelegate:openDelegate];
565     }
566     NSString *dir = nil;
567     NSString *fname = nil;
568     NSString *preset = nil;
569     if (_preset_file) {
570       preset = [[NSString alloc] initWithUTF8String:_preset_file];
571       if (strchr(_preset_file, '/') != NULL) 
572         dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]];
573       fname = [preset lastPathComponent];
574     }
575     if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory];
576     retval = [(NSOpenPanel*)_panel runModalForDirectory:dir file:fname types:nil];      
577     [dir release];
578     [preset release];
579     if (_filt_total) {
580       _filt_value = [popup indexOfSelectedItem];
581     }
582     if ( retval == NSOKButton ) {
583       clear_pathnames();
584       NSArray *array = [(NSOpenPanel*)_panel filenames];
585       _tpathnames = [array count];
586       _pathnames = new char*[_tpathnames];
587       for(int i = 0; i < _tpathnames; i++) {
588         _pathnames[i] = strnew([(NSString*)[array objectAtIndex:i] fileSystemRepresentation]);
589       }
590     }
591   }
592   else {
593     NSString *dir = nil;
594     NSString *fname = nil;
595     NSString *preset = nil;
596     NSPopUpButton *popup = nil;
597     [(NSSavePanel*)_panel setAllowsOtherFileTypes:YES];
598     if ( !(_options & SAVEAS_CONFIRM) ) {
599       static FLsaveDelegate *saveDelegate = nil;
600       if (saveDelegate == nil)saveDelegate = [[FLsaveDelegate alloc] init]; // not to be ever freed
601       [(NSSavePanel*)_panel setDelegate:saveDelegate];
602     }
603     if (_preset_file) {
604       preset = [[NSString alloc] initWithUTF8String:_preset_file];
605       if (strchr(_preset_file, '/') != NULL) {
606         dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]];
607       }
608       fname = [preset lastPathComponent];
609     }
610     if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory];
611     if (_filt_total) {
612       char *t = prepareMacFilter(_filt_total, _filter, _filt_patt);
613       popup = createPopupAccessory((NSSavePanel*)_panel, t, [[(NSSavePanel*)_panel nameFieldLabel] UTF8String], _filt_value);
614       delete[] t;
615     }
616     retval = [(NSSavePanel*)_panel runModalForDirectory:dir file:fname];
617     if (_filt_total) {
618       _filt_value = [popup indexOfSelectedItem];
619     }
620     [dir release];
621     [preset release];
622     if ( retval == NSOKButton ) get_saveas_basename();
623   }
624   [localPool release];
625   return (retval == NSOKButton ? 0 : 1);
628 #endif // __APPLE__
631 // End of "$Id: Fl_Native_File_Chooser_MAC.mm 8784 2011-06-06 12:11:04Z manolo $".