//
// FLTK native OS file chooser widget for macOS
//
// Copyright 2004 Greg Ercolano.
// Copyright 1998-2024 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
//     https://www.fltk.org/bugs.php
//

// TODO:
//      o When doing 'open file', only dir is preset, not filename.
//        Possibly 'preset_file' could be used to select the filename.
//

#include <FL/Fl.H>
#include <FL/platform.H> // for fl_mac_os_version
#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;               // kind-of browser to show()
  int             _options;             // general options
  NSSavePanel   *_panel;
  char          **_pathnames;           // array of pathnames
  int             _tpathnames;          // total pathnames
  char           *_directory;           // default pathname to use
  char           *_title;               // title for window
  char           *_preset_file;         // the 'save as' filename

  char           *_filter;              // user-side search filter, eg:
  // C Files\t*.[ch]\nText Files\t*.txt"

  char           *_filt_names;          // filter names (tab delimited)
  // eg. "C Files\tText Files"

  char           *_filt_patt[MAXFILTERS];
  // array of filter patterns, eg:
  //     _filt_patt[0]="*.{cxx,h}"
  //     _filt_patt[1]="*.txt"

  int             _filt_total;          // parse_filter() # of filters loaded
  int             _filt_value;          // index of the selected filter
  char           *_errmsg;              // error message

  // Private methods
  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);
}

// FREE PATHNAMES ARRAY, IF IT HAS ANY CONTENTS
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;
}

// SET A SINGLE PATHNAME
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;
}

// CONSTRUCTOR
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;
}

// DESTRUCTOR
Fl_Quartz_Native_File_Chooser_Driver::~Fl_Quartz_Native_File_Chooser_Driver() {
  // _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_Quartz_Native_File_Chooser_Driver::type() const {
  return(_btype);
}

// SET OPTIONS
void Fl_Quartz_Native_File_Chooser_Driver::options(int val) {
  _options = val;
}

// GET OPTIONS
int Fl_Quartz_Native_File_Chooser_Driver::options() const {
  return(_options);
}

// SHOW THE BROWSER WINDOW
//     Returns:
//         0 - user picked a file
//         1 - user cancelled
//        -1 - failed; errmsg() has reason
//
int Fl_Quartz_Native_File_Chooser_Driver::show() {

  // Make sure fltk interface updates before posting our dialog
  Fl::flush();

  // POST BROWSER
  int err = post();

  return(err);
}

// SET ERROR MESSAGE
//     Internal use only.
//
void Fl_Quartz_Native_File_Chooser_Driver::errmsg(const char *msg) {
  _errmsg = strfree(_errmsg);
  _errmsg = strnew(msg);
}

// RETURN ERROR MESSAGE
const char *Fl_Quartz_Native_File_Chooser_Driver::errmsg() const {
  return(_errmsg ? _errmsg : "No error");
}

// GET FILENAME
const char* Fl_Quartz_Native_File_Chooser_Driver::filename() const {
  if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]);
  return("");
}

// GET FILENAME FROM LIST OF FILENAMES
const char* Fl_Quartz_Native_File_Chooser_Driver::filename(int i) const {
  if ( _pathnames && i < _tpathnames ) return(_pathnames[i]);
  return("");
}

// GET TOTAL FILENAMES CHOSEN
int Fl_Quartz_Native_File_Chooser_Driver::count() const {
  return(_tpathnames);
}

// PRESET PATHNAME
//     Value can be NULL for none.
//
void Fl_Quartz_Native_File_Chooser_Driver::directory(const char *val) {
  _directory = strfree(_directory);
  _directory = strnew(val);
}

// GET PRESET PATHNAME
//     Returned value can be NULL if none set.
//
const char* Fl_Quartz_Native_File_Chooser_Driver::directory() const {
  return(_directory);
}

// SET TITLE
//     Value can be NULL if no title desired.
//
void Fl_Quartz_Native_File_Chooser_Driver::title(const char *val) {
  _title = strfree(_title);
  _title = strnew(val);
}

// GET TITLE
//     Returned value can be NULL if none set.
//
const char *Fl_Quartz_Native_File_Chooser_Driver::title() const {
  return(_title);
}

// SET FILTER
//     Can be NULL if no filter needed
//
void Fl_Quartz_Native_File_Chooser_Driver::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_Quartz_Native_File_Chooser_Driver::filter() const {
  return(_filter);
}

// CLEAR ALL FILTERS
//    Internal use only.
//
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;
}

// 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_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';     // 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' ) {
          snprintf(name, sizeof(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_Quartz_Native_File_Chooser_Driver::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_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) {
      // Remove UNLIKELYPREFIX between directory and filename parts
      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;
}

// SET THE TYPE OF BROWSER
void Fl_Quartz_Native_File_Chooser_Driver::type(int val) {
  _btype = val;
}

/* Input
 filter=  "C files\t*.{c,h}\nText files\t*.txt\n"
 patterns[0] = "*.{c,h}"
 patterns[1] = "*.txt"
 count = 2
 Return:
 "C files (*.{c,h})\nText files (*.txt)\n"
 */
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 {  // 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);
    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;
  // 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];
}
- (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]; // 11.0 + framework UniformTypeIdentifiers
      [dialog setAllowedContentTypes:[NSArray arrayWithObject:type]]; // 11.0
  }
  else
#endif
  if (fl_mac_os_version >= 100900) {
    [dialog performSelector:@selector(setAllowedFileTypes:)
                 withObject:[NSArray arrayWithObject:ext]];
  }
}
- (void)changedPopup:(id)sender
// runs when the save panel popup menu changes output file type
// correspondingly changes the extension of the output file name
{
  if (fl_mac_os_version < 100600) return; // because of setNameFieldStringValue and nameFieldStringValue
  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]]; // 10.6
  }
#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 );
  // the "Show hidden files" button
  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]; // never released
  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];
    // horizontally move box to fit the locale-dependent width of its title
    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]]) {
      // STR #3406: If both dir + fname specified, combine and pass to setDirectoryURL
      path = [[NSString alloc] initWithFormat:@"%@/%@", dir, fname];  // dir+fname -> path
      // See if full path to file exists
      //    If dir exists but fname doesn't, avoid using setDirectoryURL,
      //    otherwise NSSavePanel falls back to showing user's Documents dir.
      //
      if ( [[NSFileManager defaultManager] fileExistsAtPath:path] ) usepath = true;
    }
    if (usepath) {
      // Set only if full path exists
      [_panel setDirectoryURL:[NSURL fileURLWithPath:path]]; // 10.6
    } else { // didn't setDirectoryURL to full path? Set dir + fname separately..
      if (dir) [_panel setDirectoryURL:[NSURL fileURLWithPath:dir]];  // 10.6
      if (fname) [_panel setNameFieldStringValue:fname]; // 10.6
    }
    [path release];
    if ([NSApp mainWindow]) {
      __block NSInteger complete = -1;
      [_panel beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger returnCode) {
        complete = returnCode; // this block runs after OK or Cancel was triggered in file dialog
      }]; // 10.6 this message returns immediately and begins the file dialog as a sheet
      while (complete == -1) Fl::wait(100); // loop until end of file dialog
      retval = complete;
    } else {
      retval = [_panel runModal];
    }
  }
  else
#endif
  { // the deprecation warning can be ignored because runs only for macOS < 10.6
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    retval = [_panel runModalForDirectory:dir file:fname]; // deprecated in 10.6
#pragma clang diagnostic pop
  }
  [dir release];
  [preset release];
  return (retval == NSModalResponseOK ? 1 : 0);
}

// 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_Quartz_Native_File_Chooser_Driver::post() {
  // INITIALIZE BROWSER
  if ( _filt_total == 0 ) {     // Make sure they match
    _filt_value = 0;            // TBD: move to someplace more logical?
  }
  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];
      /* FALLTHROUGH */
    case Fl_Native_File_Chooser::BROWSE_DIRECTORY:
      [(NSOpenPanel*)_panel setCanChooseDirectories:YES];
      break;
    case Fl_Native_File_Chooser::BROWSE_SAVE_DIRECTORY:
      [_panel setCanCreateDirectories:YES];
      break;
  }

  // SHOW THE DIALOG
  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);
}
