#include <FL/Fl.H>
#include <FL/platform.H>
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/filename.H>
#include <FL/fl_string_functions.h>
#define MAXFILTERS 80
#import <Cocoa/Cocoa.h>
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_0
# import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#endif
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9
const NSInteger NSModalResponseOK = NSFileHandlingPanelOKButton;
#endif
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
const NSUInteger NSControlSizeRegular = NSRegularControlSize;
#endif
class Fl_Quartz_Native_File_Chooser_Driver : public Fl_Native_File_Chooser_Driver {
private:
int _btype; int _options; NSSavePanel *_panel;
char **_pathnames; int _tpathnames; char *_directory; char *_title; char *_preset_file;
char *_filter;
char *_filt_names;
char *_filt_patt[MAXFILTERS];
int _filt_total; int _filt_value; char *_errmsg;
void errmsg(const char *msg);
void clear_pathnames();
void set_single_pathname(const char *s);
int get_saveas_basename(void);
void clear_filters();
void parse_filter(const char *from);
int post();
int runmodal();
public:
Fl_Quartz_Native_File_Chooser_Driver(int val);
~Fl_Quartz_Native_File_Chooser_Driver();
void type(int t) FL_OVERRIDE;
int type() const FL_OVERRIDE;
void options(int o) FL_OVERRIDE;
int options() const FL_OVERRIDE;
int count() const FL_OVERRIDE;
const char *filename() const FL_OVERRIDE;
const char *filename(int i) const FL_OVERRIDE;
void directory(const char *val) FL_OVERRIDE;
const char *directory() const FL_OVERRIDE;
void title(const char *t) FL_OVERRIDE;
const char* title() const FL_OVERRIDE;
const char *filter() const FL_OVERRIDE;
void filter(const char *f) FL_OVERRIDE;
int filters() const FL_OVERRIDE;
void filter_value(int i) FL_OVERRIDE;
int filter_value() const FL_OVERRIDE;
void preset_file(const char*f) FL_OVERRIDE;
const char* preset_file() const FL_OVERRIDE;
const char *errmsg() const FL_OVERRIDE;
int show() FL_OVERRIDE;
};
Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) {
platform_fnfc = new Fl_Quartz_Native_File_Chooser_Driver(val);
}
void Fl_Quartz_Native_File_Chooser_Driver::clear_pathnames() {
if ( _pathnames ) {
while ( --_tpathnames >= 0 ) {
_pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]);
}
delete [] _pathnames;
_pathnames = NULL;
}
_tpathnames = 0;
}
void Fl_Quartz_Native_File_Chooser_Driver::set_single_pathname(const char *s) {
clear_pathnames();
_pathnames = new char*[1];
_pathnames[0] = strnew(s);
_tpathnames = 1;
}
Fl_Quartz_Native_File_Chooser_Driver::Fl_Quartz_Native_File_Chooser_Driver(int val) :
Fl_Native_File_Chooser_Driver(val) {
_btype = val;
_panel = NULL;
_options = Fl_Native_File_Chooser::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;
}
Fl_Quartz_Native_File_Chooser_Driver::~Fl_Quartz_Native_File_Chooser_Driver() {
clear_pathnames();
_directory = strfree(_directory);
_title = strfree(_title);
_preset_file = strfree(_preset_file);
_filter = strfree(_filter);
clear_filters();
_errmsg = strfree(_errmsg);
}
int Fl_Quartz_Native_File_Chooser_Driver::type() const {
return(_btype);
}
void Fl_Quartz_Native_File_Chooser_Driver::options(int val) {
_options = val;
}
int Fl_Quartz_Native_File_Chooser_Driver::options() const {
return(_options);
}
int Fl_Quartz_Native_File_Chooser_Driver::show() {
Fl::flush();
int err = post();
return(err);
}
void Fl_Quartz_Native_File_Chooser_Driver::errmsg(const char *msg) {
_errmsg = strfree(_errmsg);
_errmsg = strnew(msg);
}
const char *Fl_Quartz_Native_File_Chooser_Driver::errmsg() const {
return(_errmsg ? _errmsg : "No error");
}
const char* Fl_Quartz_Native_File_Chooser_Driver::filename() const {
if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]);
return("");
}
const char* Fl_Quartz_Native_File_Chooser_Driver::filename(int i) const {
if ( _pathnames && i < _tpathnames ) return(_pathnames[i]);
return("");
}
int Fl_Quartz_Native_File_Chooser_Driver::count() const {
return(_tpathnames);
}
void Fl_Quartz_Native_File_Chooser_Driver::directory(const char *val) {
_directory = strfree(_directory);
_directory = strnew(val);
}
const char* Fl_Quartz_Native_File_Chooser_Driver::directory() const {
return(_directory);
}
void Fl_Quartz_Native_File_Chooser_Driver::title(const char *val) {
_title = strfree(_title);
_title = strnew(val);
}
const char *Fl_Quartz_Native_File_Chooser_Driver::title() const {
return(_title);
}
void Fl_Quartz_Native_File_Chooser_Driver::filter(const char *val) {
_filter = strfree(_filter);
_filter = strnew(val);
parse_filter(_filter);
}
const char *Fl_Quartz_Native_File_Chooser_Driver::filter() const {
return(_filter);
}
void Fl_Quartz_Native_File_Chooser_Driver::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;
}
void Fl_Quartz_Native_File_Chooser_Driver::parse_filter(const char *in) {
clear_filters();
if ( ! in ) return;
int has_name = strchr(in, '\t') ? 1 : 0;
char mode = has_name ? 'n' : 'w'; char wildcard[1024] = ""; char name[1024] = "";
for ( ; 1; in++ ) {
switch (*in) {
case '\t':
if ( mode != 'n' ) goto regchar;
mode = 'w';
break;
case '\\':
++in;
goto regchar;
case '\r':
case '\n':
case '\0':
if ( name[0] == '\0' ) {
snprintf(name, sizeof(name), "%.*s Files", (int)sizeof(name)-10, wildcard);
}
if ( wildcard[0] ) {
if ( _filt_total ) {
_filt_names = strapp(_filt_names, "\t");
}
_filt_names = strapp(_filt_names, name);
_filt_patt[_filt_total++] = strnew(wildcard);
}
wildcard[0] = name[0] = '\0';
mode = strchr(in, '\t') ? 'n' : 'w';
if ( *in == '\0' ) return; else continue;
default: regchar: switch ( mode ) {
case 'n': chrcat(name, *in); continue;
case 'w': chrcat(wildcard, *in); continue;
}
break;
}
}
}
void Fl_Quartz_Native_File_Chooser_Driver::preset_file(const char* val) {
_preset_file = strfree(_preset_file);
_preset_file = strnew(val);
}
const char* Fl_Quartz_Native_File_Chooser_Driver::preset_file() const {
return(_preset_file);
}
void Fl_Quartz_Native_File_Chooser_Driver::filter_value(int val) {
_filt_value = val;
}
int Fl_Quartz_Native_File_Chooser_Driver::filter_value() const {
return(_filt_value);
}
int Fl_Quartz_Native_File_Chooser_Driver::filters() const {
return(_filt_total);
}
#define UNLIKELYPREFIX "___fl_very_unlikely_prefix_"
int Fl_Quartz_Native_File_Chooser_Driver::get_saveas_basename(void) {
char *q = fl_strdup( [[[_panel URL] path] UTF8String] );
if ( !(_options & Fl_Native_File_Chooser::SAVEAS_CONFIRM) ) {
const char *d = [[[[_panel URL] path] stringByDeletingLastPathComponent] UTF8String];
int l = (int)strlen(d) + 1;
if (strcmp(d, "/") == 0) l = 1;
int lu = (int)strlen(UNLIKELYPREFIX);
int ln = (int)strlen(q+l);
if (ln >= lu) {
if (memcmp(q+l, UNLIKELYPREFIX, lu) == 0) memmove(q + l, q + l + lu, strlen(q + l + lu) + 1);
}
}
set_single_pathname( q );
free(q);
return 0;
}
void Fl_Quartz_Native_File_Chooser_Driver::type(int val) {
_btype = val;
}
static char *prepareMacFilter(int count, const char *filter, char **patterns) {
int rank = 0, l = 0;
for (int i = 0; i < count; i++) {
l += strlen(patterns[i]) + 3;
}
const char *p = filter;
const int t_size = (int)strlen(p) + l + 1;
char *q; q = new char[t_size];
const char *r, *s;
char *t;
t = q;
do { r = strchr(p, '\n');
if (!r) r = p + strlen(p);
s = strchr(p, '\t');
if (s && s < r) {
memcpy(q, p, s - p);
q += s - p;
if (rank < count) {
snprintf(q, t_size-(q-t), " (%s)", patterns[rank]); q += strlen(q);
}
}
else {
memcpy(q, p, r - p);
q += r - p;
}
rank++;
*(q++) = '\n';
if (*r) p = r + 1; else p = r;
} while(*p);
*q = 0;
return t;
}
@interface FLopenDelegate : NSObject
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
<NSOpenSavePanelDelegate>
#endif
{
NSPopUpButton *nspopup;
char **filter_pattern;
}
- (FLopenDelegate*)setPopup:(NSPopUpButton*)popup filter_pattern:(char**)pattern;
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url;
@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;
BOOL isdir = NO;
[[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isdir];
if (isdir) return YES;
if ( fl_filename_match([filename fileSystemRepresentation], filter_pattern[([nspopup indexOfSelectedItem])]) ) return YES;
return NO;
}
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url
{
return [self panel:sender shouldShowFilename:[url path]];
}
@end
@interface FLsaveDelegate : NSObject
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
<NSOpenSavePanelDelegate>
#endif
{
NSSavePanel *dialog;
BOOL saveas_confirm;
}
- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag;
- (void)control_allowed_types:(const char *)p;
- (void)changedPopup:(id)sender;
- (void)panel:(NSSavePanel*)p;
- (void)option:(BOOL)o;
@end
@implementation FLsaveDelegate
- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
{
if ( !okFlag || saveas_confirm ) return filename;
return [@ UNLIKELYPREFIX stringByAppendingString:filename];
}
- (void)control_allowed_types:(const char *)p
{
NSString *ext = [NSString stringWithUTF8String:p];
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_0
if (@available(macOS 11.0, *)) {
UTType *type = [UTType typeWithFilenameExtension:ext]; [dialog setAllowedContentTypes:[NSArray arrayWithObject:type]]; }
else
#endif
if (fl_mac_os_version >= 100900) {
[dialog performSelector:@selector(setAllowedFileTypes:)
withObject:[NSArray arrayWithObject:ext]];
}
}
- (void)changedPopup:(id)sender
{
if (fl_mac_os_version < 100600) return; char *s = fl_strdup([[(NSPopUpButton*)sender titleOfSelectedItem] UTF8String]);
if (!s) return;
char *p = strchr(s, '(');
if (!p) p = s;
p = strchr(p, '.');
if (!p) {free(s); return;}
p++;
while (*p == ' ') p++;
if (!p || *p == '{') {free(s); return;}
char *q = p+1;
while (*q != ' ' && *q != ')' && *q != 0) q++;
*q = 0;
NSString *ns = [NSString stringWithFormat:@"%@.%@",
[[dialog performSelector:@selector(nameFieldStringValue)] stringByDeletingPathExtension],
[NSString stringWithUTF8String:p]];
[self control_allowed_types:p];
free(s);
[dialog performSelector:@selector(setNameFieldStringValue:) withObject:ns];
}
- (void)panel:(NSSavePanel*)p
{
dialog = p;
}
- (void) option:(BOOL)o
{
saveas_confirm = o;
}
@end
@interface FLHiddenFilesAction : NSObject
{
@public
NSSavePanel *panel;
NSButton *button;
}
- (void)action;
@end
@implementation FLHiddenFilesAction
- (void)action {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
if (fl_mac_os_version >= 100600) {
[panel setShowsHiddenFiles:[button intValue]]; }
#endif
}
@end
static NSPopUpButton *createPopupAccessory(NSSavePanel *panel, const char *filter, const char *title, int rank)
{
NSPopUpButton *popup = nil;
NSRect rectview = NSMakeRect(5, 5, 350, filter ? 60 : 30);
NSView *view = [[[NSView alloc] initWithFrame:rectview] autorelease];
NSRect rectbox = NSMakeRect(0, 3, 140, 20 );
NSRect hidden_files_rect = {{150, 0}, {80, 30}};
if (filter) hidden_files_rect.origin.y = 35;
NSButton *hidden_files = [[[NSButton alloc] initWithFrame:hidden_files_rect] autorelease];
[hidden_files setButtonType:
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
NSButtonTypeSwitch
#else
NSSwitchButton
#endif
];
[hidden_files setTitle:[NSString stringWithUTF8String:Fl_File_Chooser::hidden_label]];
[hidden_files sizeToFit];
[hidden_files setIntValue:0];
[view addSubview:hidden_files];
static FLHiddenFilesAction *target = [[FLHiddenFilesAction alloc] init]; target->panel = panel;
target->button = hidden_files;
[hidden_files setAction:@selector(action)];
[hidden_files setTarget:target];
if (filter) {
NSBox *box = [[[NSBox alloc] initWithFrame:rectbox] autorelease];
NSRect rectpop = NSMakeRect(105, 0, 246, 30 );
popup = [[[NSPopUpButton alloc ] initWithFrame:rectpop pullsDown:NO] autorelease];
[view addSubview:box];
[view addSubview:popup];
NSString *nstitle = [[NSString alloc] initWithUTF8String:title];
[box setTitle:nstitle];
[box setTitlePosition:NSBelowTop];
[nstitle release];
NSFont *font = [NSFont systemFontOfSize:[NSFont systemFontSize]];
[box setTitleFont:font];
[box sizeToFit];
NSRect r=[box frame];
r.origin.x = rectpop.origin.x - r.size.width;
r.origin.y = rectpop.origin.y + (rectpop.size.height - r.size.height) / 2;
[box setFrame:r];
CFStringRef tab = CFSTR("\n");
CFStringRef tmp_cfs;
tmp_cfs = CFStringCreateWithCString(NULL, filter, kCFStringEncodingUTF8);
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;
}
int Fl_Quartz_Native_File_Chooser_Driver::runmodal()
{
NSString *dir = nil;
NSString *fname = nil;
NSString *preset = nil;
NSInteger retval;
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 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 && defined(__BLOCKS__)
if (fl_mac_os_version >= 100600) {
bool usepath = false;
NSString *path = nil;
if (dir && fname && [_panel isKindOfClass:[NSOpenPanel class]]) {
path = [[NSString alloc] initWithFormat:@"%@/%@", dir, fname]; if ( [[NSFileManager defaultManager] fileExistsAtPath:path] ) usepath = true;
}
if (usepath) {
[_panel setDirectoryURL:[NSURL fileURLWithPath:path]]; } else { if (dir) [_panel setDirectoryURL:[NSURL fileURLWithPath:dir]]; if (fname) [_panel setNameFieldStringValue:fname]; }
[path release];
if ([NSApp mainWindow]) {
__block NSInteger complete = -1;
[_panel beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger returnCode) {
complete = returnCode; }]; while (complete == -1) Fl::wait(100); retval = complete;
} else {
retval = [_panel runModal];
}
}
else
#endif
{ #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
retval = [_panel runModalForDirectory:dir file:fname]; #pragma clang diagnostic pop
}
[dir release];
[preset release];
return (retval == NSModalResponseOK ? 1 : 0);
}
int Fl_Quartz_Native_File_Chooser_Driver::post() {
if ( _filt_total == 0 ) { _filt_value = 0; }
fl_open_display();
NSAutoreleasePool *localPool;
localPool = [[NSAutoreleasePool alloc] init];
switch (_btype) {
case Fl_Native_File_Chooser::BROWSE_FILE:
case Fl_Native_File_Chooser::BROWSE_MULTI_FILE:
case Fl_Native_File_Chooser::BROWSE_DIRECTORY:
case Fl_Native_File_Chooser::BROWSE_MULTI_DIRECTORY:
_panel = [NSOpenPanel openPanel];
break;
case Fl_Native_File_Chooser::BROWSE_SAVE_DIRECTORY:
case Fl_Native_File_Chooser::BROWSE_SAVE_FILE:
_panel = [NSSavePanel savePanel];
break;
}
BOOL is_open_panel = [_panel isKindOfClass:[NSOpenPanel class]];
if (_title) {
SEL title_or_message = (is_open_panel && fl_mac_os_version >= 101200) ?
@selector(setMessage:) : @selector(setTitle:);
[_panel performSelector:title_or_message withObject:[NSString stringWithUTF8String:_title]];
}
switch (_btype) {
case Fl_Native_File_Chooser::BROWSE_MULTI_FILE:
[(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
break;
case Fl_Native_File_Chooser::BROWSE_MULTI_DIRECTORY:
[(NSOpenPanel*)_panel setAllowsMultipleSelection:YES];
case Fl_Native_File_Chooser::BROWSE_DIRECTORY:
[(NSOpenPanel*)_panel setCanChooseDirectories:YES];
break;
case Fl_Native_File_Chooser::BROWSE_SAVE_DIRECTORY:
[_panel setCanCreateDirectories:YES];
break;
}
NSWindow *key = [NSApp keyWindow];
NSPopUpButton *popup = nil;
if ( is_open_panel ) {
if (_filt_total) {
char *t = prepareMacFilter(_filt_total, _filter, _filt_patt);
popup = createPopupAccessory(_panel, t, Fl_File_Chooser::show_label, 0);
delete[] t;
[[popup menu] addItem:[NSMenuItem separatorItem]];
[popup addItemWithTitle:[NSString stringWithUTF8String:Fl_File_Chooser::all_files_label]];
[popup setAction:@selector(validateVisibleColumns)];
[popup setTarget:(NSObject*)_panel];
FLopenDelegate *openDelegate = [[[FLopenDelegate alloc] init] autorelease];
[openDelegate setPopup:popup filter_pattern:_filt_patt];
[_panel setDelegate:openDelegate];
} else createPopupAccessory(_panel, NULL, Fl_File_Chooser::show_label, 0);
}
else {
FLsaveDelegate *saveDelegate = [[[FLsaveDelegate alloc] init] autorelease];
[_panel setAllowsOtherFileTypes:YES];
[_panel setDelegate:saveDelegate];
[saveDelegate option:(_options & Fl_Native_File_Chooser::SAVEAS_CONFIRM)];
if (_filt_total) {
if (_filt_value >= _filt_total) _filt_value = _filt_total - 1;
char *t = prepareMacFilter(_filt_total, _filter, _filt_patt);
popup = createPopupAccessory(_panel, t, [[_panel nameFieldLabel] UTF8String], _filt_value);
delete[] t;
if (_options & Fl_Native_File_Chooser::USE_FILTER_EXT) {
[popup setAction:@selector(changedPopup:)];
[popup setTarget:saveDelegate];
[saveDelegate panel:(NSSavePanel*)_panel];
if (fl_mac_os_version >= 100900) {
char *p = _filt_patt[_filt_value];
char *q = strchr(p, '.'); if(!q) q = p-1;
do q++; while (*q==' ' || *q=='{');
p = fl_strdup(q);
q = strchr(p, ','); if (q) *q = 0;
[saveDelegate control_allowed_types:p];
free(p);
}
}
[_panel setCanSelectHiddenExtension:YES];
[_panel setExtensionHidden:NO];
} else createPopupAccessory(_panel, NULL, Fl_File_Chooser::show_label, 0);
}
int retval = runmodal();
if (_filt_total) {
_filt_value = (int)[popup indexOfSelectedItem];
}
if ( retval == 1 ) {
if (is_open_panel) {
clear_pathnames();
NSArray *array = [(NSOpenPanel*)_panel URLs];
_tpathnames = (int)[array count];
_pathnames = new char*[_tpathnames];
for(int i = 0; i < _tpathnames; i++) {
_pathnames[i] = strnew([[(NSURL*)[array objectAtIndex:i] path] UTF8String]);
}
}
else get_saveas_basename();
}
[key makeKeyWindow];
[localPool release];
return (retval == 1 ? 0 : 1);
}