1 // "$Id: Fl_Native_File_Chooser_MAC.mm 8784 2011-06-06 12:11:04Z manolo $"
3 // FLTK native OS file chooser widget
5 // Copyright 1998-2010 by Bill Spitzak and others.
6 // Copyright 2004 Greg Ercolano.
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
23 // Please report all bugs and problems to:
25 // http://www.fltk.org/str.php
29 // o When doing 'open file', only dir is preset, not filename.
30 // Possibly 'preset_file' could be used to select the filename.
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)
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() {
49 while ( --_tpathnames >= 0 ) {
50 _pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]);
58 // SET A SINGLE PATHNAME
59 void Fl_Native_File_Chooser::set_single_pathname(const char *s) {
61 _pathnames = new char*[1];
62 _pathnames[0] = strnew(s);
67 Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) {
70 _options = NO_OPTIONS;
76 memset(_filt_patt, 0, sizeof(char*) * MAXFILTERS);
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
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()
99 //_filt_value // nothing to manage
100 _errmsg = strfree(_errmsg);
103 // GET TYPE OF BROWSER
104 int Fl_Native_File_Chooser::type() const {
109 void Fl_Native_File_Chooser::options(int val) {
114 int Fl_Native_File_Chooser::options() const {
118 // SHOW THE BROWSER WINDOW
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
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");
151 const char* Fl_Native_File_Chooser::filename() const {
152 if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]);
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]);
162 // GET TOTAL FILENAMES CHOSEN
163 int Fl_Native_File_Chooser::count() const {
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 {
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);
191 // Returned value can be NULL if none set.
193 const char *Fl_Native_File_Chooser::title() const {
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"
211 parse_filter(_filter);
215 // Returned value can be NULL if none set.
217 const char *Fl_Native_File_Chooser::filter() const {
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]);
232 // PARSE USER'S FILTER SPEC
233 // Parses user specified filter ('in'),
234 // breaks out into _filt_patt[], _filt_names, and _filt_total.
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]"
246 // IN:"C Files\t*.{cxx,h}"
248 // mode: nnnnnnn wwwwwwwww
252 void Fl_Native_File_Chooser::parse_filter(const char *in) {
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
265 //// printf("WORKING ON '%c': mode=<%c> name=<%s> wildcard=<%s>\n",
266 //// *in, mode, name, wildcard);
269 // FINISHED PARSING NAME?
271 if ( mode != 'n' ) goto regchar;
280 // FINISHED PARSING ONE OF POSSIBLY SEVERAL FILTERS?
285 // If user didn't specify a name, make one
287 if ( name[0] == '\0' ) {
288 sprintf(name, "%.*s Files", (int)sizeof(name)-10, wildcard);
290 // APPEND NEW FILTER TO LIST
292 // Add to filtername list
293 // Tab delimit if more than one. We later break
294 // tab delimited string into CFArray with
295 // CFStringCreateArrayBySeparatingStrings()
298 _filt_names = strapp(_filt_names, "\t");
300 _filt_names = strapp(_filt_names, name);
302 // Add filter to the pattern array
303 _filt_patt[_filt_total++] = strnew(wildcard);
306 wildcard[0] = name[0] = '\0';
307 mode = strchr(in, '\t') ? 'n' : 'w';
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
316 case 'n': chrcat(name, *in); continue;
317 case 'w': chrcat(wildcard, *in); continue;
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);
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) {
344 int Fl_Native_File_Chooser::filter_value() const {
348 int Fl_Native_File_Chooser::filters() const {
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
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);
368 set_single_pathname( q );
373 // SET THE TYPE OF BROWSER
374 void Fl_Native_File_Chooser::type(int val) {
379 filter= "C files\t*.{c,h}\nText files\t*.txt\n"
380 patterns[0] = "*.{c,h}"
381 patterns[1] = "*.txt"
384 "C files (*.{c,h})\nText files (*.txt)\n"
386 static char *prepareMacFilter(int count, const char *filter, char **patterns) {
388 for (int i = 0; i < count; i++) {
389 l += strlen(patterns[i]) + 3;
391 const char *p = filter;
392 char *q; q = new char[strlen(p) + l + 1];
396 do { // copy to t what is in filter removing what is between \t and \n, if any
398 if (!r) r = p + strlen(p);
403 if (rank < count) { sprintf(q, " (%s)", patterns[rank]); q += strlen(q); }
417 @interface FLopenDelegate : NSObject
418 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
419 <NSOpenSavePanelDelegate>
422 NSPopUpButton *nspopup;
423 char **filter_pattern;
425 - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern;
426 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
428 @implementation FLopenDelegate
429 - (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern
432 filter_pattern = pattern;
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;
445 @interface FLsaveDelegate : NSObject
446 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
447 <NSOpenSavePanelDelegate>
451 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag;
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];
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];
478 NSFont *font = [NSFont controlContentFontOfSize:NSRegularControlSize];
479 [box setTitleFont:font];
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");
488 tmp_cfs = CFStringCreateWithCString(NULL, filter, kCFStringEncodingUTF8);
489 CFArrayRef array = CFStringCreateArrayBySeparatingStrings(NULL, tmp_cfs, tab);
492 [popup addItemsWithTitles:(NSArray*)array];
493 NSMenuItem *item = [popup itemWithTitle:@""];
494 if (item) [popup removeItemWithTitle:@""];
496 [popup selectItemAtIndex:rank];
497 [panel setAccessoryView:view];
502 // Internal use only.
503 // Assumes '_opts' has been initialized.
506 // 0 - user picked a file
507 // 1 - user cancelled
508 // -1 - failed; errmsg() has reason
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?
515 NSAutoreleasePool *localPool;
516 localPool = [[NSAutoreleasePool alloc] init];
519 case BROWSE_MULTI_FILE:
520 case BROWSE_DIRECTORY:
521 case BROWSE_MULTI_DIRECTORY:
522 _panel = [NSOpenPanel openPanel];
524 case BROWSE_SAVE_DIRECTORY:
525 case BROWSE_SAVE_FILE:
526 _panel = [NSSavePanel savePanel];
530 NSString *nstitle = [NSString stringWithUTF8String: (_title ? _title : "No Title")];
531 [(NSSavePanel*)_panel setTitle:nstitle];
533 case BROWSE_MULTI_FILE:
534 [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
536 case BROWSE_MULTI_DIRECTORY:
537 [(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
539 case BROWSE_DIRECTORY:
540 [(NSOpenPanel*)_panel setCanChooseDirectories:YES];
542 case BROWSE_SAVE_DIRECTORY:
543 [(NSSavePanel*)_panel setCanCreateDirectories:YES];
548 if ( [(NSSavePanel*)_panel isKindOfClass:[NSOpenPanel class]] ) {
549 NSPopUpButton *popup = nil;
551 char *t = prepareMacFilter(_filt_total, _filter, _filt_patt);
552 popup = createPopupAccessory((NSSavePanel*)_panel, t, Fl_File_Chooser::show_label, 0);
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];
563 [openDelegate setPopup:popup filter_pattern:_filt_patt];
564 [(NSOpenPanel*)_panel setDelegate:openDelegate];
567 NSString *fname = nil;
568 NSString *preset = nil;
570 preset = [[NSString alloc] initWithUTF8String:_preset_file];
571 if (strchr(_preset_file, '/') != NULL)
572 dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]];
573 fname = [preset lastPathComponent];
575 if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory];
576 retval = [(NSOpenPanel*)_panel runModalForDirectory:dir file:fname types:nil];
580 _filt_value = [popup indexOfSelectedItem];
582 if ( retval == NSOKButton ) {
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]);
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];
604 preset = [[NSString alloc] initWithUTF8String:_preset_file];
605 if (strchr(_preset_file, '/') != NULL) {
606 dir = [[NSString alloc] initWithString:[preset stringByDeletingLastPathComponent]];
608 fname = [preset lastPathComponent];
610 if (_directory && !dir) dir = [[NSString alloc] initWithUTF8String:_directory];
612 char *t = prepareMacFilter(_filt_total, _filter, _filt_patt);
613 popup = createPopupAccessory((NSSavePanel*)_panel, t, [[(NSSavePanel*)_panel nameFieldLabel] UTF8String], _filt_value);
616 retval = [(NSSavePanel*)_panel runModalForDirectory:dir file:fname];
618 _filt_value = [popup indexOfSelectedItem];
622 if ( retval == NSOKButton ) get_saveas_basename();
625 return (retval == NSOKButton ? 0 : 1);
631 // End of "$Id: Fl_Native_File_Chooser_MAC.mm 8784 2011-06-06 12:11:04Z manolo $".