github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/internal/frontend/desktop/darwin/WailsContext.m (about)

     1  //go:build darwin
     2  //
     3  //  WailsContext.m
     4  //  test
     5  //
     6  //  Created by Lea Anthony on 10/10/21.
     7  //
     8  
     9  #import <Foundation/Foundation.h>
    10  #import <WebKit/WebKit.h>
    11  #import "WailsContext.h"
    12  #import "WailsAlert.h"
    13  #import "WailsMenu.h"
    14  #import "WindowDelegate.h"
    15  #import "message.h"
    16  #import "Role.h"
    17  
    18  typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
    19  
    20  @implementation WailsWindow
    21  
    22  - (BOOL)canBecomeKeyWindow
    23  {
    24      return YES;
    25  }
    26  
    27  - (void) applyWindowConstraints {
    28      [self setMinSize:self.userMinSize];
    29      [self setMaxSize:self.userMaxSize];
    30  }
    31  
    32  - (void) disableWindowConstraints {
    33      [self setMinSize:NSMakeSize(0, 0)];
    34      [self setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
    35  }
    36  
    37  @end
    38  
    39  @implementation WailsContext
    40  
    41  - (void) SetSize:(int)width :(int)height {
    42      
    43      if (self.shuttingDown) return;
    44      
    45      NSRect frame = [self.mainWindow frame];
    46      frame.origin.y += frame.size.height - height;
    47      frame.size.width = width;
    48      frame.size.height = height;
    49      [self.mainWindow setFrame:frame display:TRUE animate:FALSE];
    50  }
    51  
    52  - (void) SetPosition:(int)x :(int)y {
    53      
    54      if (self.shuttingDown) return;
    55      
    56      NSScreen* screen = [self getCurrentScreen];
    57      NSRect windowFrame = [self.mainWindow frame];
    58      NSRect screenFrame = [screen frame];
    59      windowFrame.origin.x = screenFrame.origin.x + (float)x;
    60      windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y;
    61      
    62      [self.mainWindow setFrame:windowFrame display:TRUE animate:FALSE];
    63  }
    64  
    65  - (void) SetMinSize:(int)minWidth :(int)minHeight {
    66      
    67      if (self.shuttingDown) return;
    68      
    69      NSSize size = { minWidth, minHeight };
    70      self.mainWindow.userMinSize = size;
    71      [self.mainWindow setMinSize:size];
    72      [self adjustWindowSize];
    73  }
    74  
    75  
    76  - (void) SetMaxSize:(int)maxWidth :(int)maxHeight {
    77      
    78      if (self.shuttingDown) return;
    79      
    80      NSSize size = { FLT_MAX, FLT_MAX };
    81      
    82      size.width = maxWidth > 0 ? maxWidth : FLT_MAX;
    83      size.height = maxHeight > 0 ? maxHeight : FLT_MAX;
    84      
    85      self.mainWindow.userMaxSize = size;
    86      [self.mainWindow setMaxSize:size];
    87      [self adjustWindowSize];
    88  }
    89  
    90  
    91  - (void) adjustWindowSize {
    92      
    93      if (self.shuttingDown) return;
    94      
    95      NSRect currentFrame = [self.mainWindow frame];
    96      
    97      if ( currentFrame.size.width > self.mainWindow.userMaxSize.width ) currentFrame.size.width = self.mainWindow.userMaxSize.width;
    98      if ( currentFrame.size.width < self.mainWindow.userMinSize.width ) currentFrame.size.width = self.mainWindow.userMinSize.width;
    99      if ( currentFrame.size.height > self.mainWindow.userMaxSize.height ) currentFrame.size.height = self.mainWindow.userMaxSize.height;
   100      if ( currentFrame.size.height < self.mainWindow.userMinSize.height ) currentFrame.size.height = self.mainWindow.userMinSize.height;
   101  
   102      [self.mainWindow setFrame:currentFrame display:YES animate:FALSE];
   103      
   104  }
   105  
   106  - (void) dealloc {
   107      [self.appdelegate release];
   108      [self.mainWindow release];
   109      [self.mouseEvent release];
   110      [self.userContentController release];
   111      [self.applicationMenu release];
   112      [super dealloc];
   113  }
   114  
   115  - (NSScreen*) getCurrentScreen {
   116      NSScreen* screen = [self.mainWindow screen];
   117      if( screen == NULL ) {
   118          screen = [NSScreen mainScreen];
   119      }
   120      return screen;
   121  }
   122  
   123  - (void) SetTitle:(NSString*)title {
   124      [self.mainWindow setTitle:title];
   125  }
   126  
   127  - (void) Center {
   128       [self.mainWindow center];
   129  }
   130  
   131  - (BOOL) isFullscreen {
   132      NSWindowStyleMask masks = [self.mainWindow styleMask];
   133      if ( masks & NSWindowStyleMaskFullScreen ) {
   134          return YES;
   135      }
   136      return NO;
   137  }
   138  
   139  - (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences {
   140      NSWindowStyleMask styleMask = 0;
   141      
   142      if( !frameless ) {
   143          if (!hideTitleBar) {
   144              styleMask |= NSWindowStyleMaskTitled;
   145          }
   146          styleMask |= NSWindowStyleMaskClosable;
   147      }
   148      
   149      styleMask |= NSWindowStyleMaskMiniaturizable;
   150  
   151      if( fullSizeContent || frameless || titlebarAppearsTransparent ) {
   152          styleMask |= NSWindowStyleMaskFullSizeContentView;
   153      }
   154  
   155      if (resizable) {
   156          styleMask |= NSWindowStyleMaskResizable;
   157      }
   158      
   159      self.mainWindow = [[WailsWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
   160                                                        styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
   161          
   162      if (!frameless && useToolbar) {
   163          id toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"];
   164          [toolbar autorelease];
   165          [toolbar setShowsBaselineSeparator:!hideToolbarSeparator];
   166          [self.mainWindow setToolbar:toolbar];
   167      
   168      }
   169      
   170      [self.mainWindow setTitleVisibility:hideTitle];
   171      [self.mainWindow setTitlebarAppearsTransparent:titlebarAppearsTransparent];
   172      
   173  //    [self.mainWindow canBecomeKeyWindow];
   174      
   175      id contentView = [self.mainWindow contentView];
   176      if (windowIsTranslucent) {
   177          NSVisualEffectView *effectView = [NSVisualEffectView alloc];
   178          NSRect bounds = [contentView bounds];
   179          [effectView initWithFrame:bounds];
   180          [effectView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
   181          [effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
   182          [effectView setState:NSVisualEffectStateActive];
   183          [contentView addSubview:effectView positioned:NSWindowBelow relativeTo:nil];
   184      }
   185      
   186      if (appearance != nil) {
   187          NSAppearance *nsAppearance = [NSAppearance appearanceNamed:appearance];
   188          [self.mainWindow setAppearance:nsAppearance];
   189      }
   190      
   191  
   192      NSSize minSize = { minWidth, minHeight };
   193      NSSize maxSize = { maxWidth, maxHeight };
   194      if (maxSize.width == 0) {
   195          maxSize.width = FLT_MAX;
   196      }
   197      if (maxSize.height == 0) {
   198          maxSize.height = FLT_MAX;
   199      }
   200      self.mainWindow.userMaxSize = maxSize;
   201      self.mainWindow.userMinSize = minSize;
   202      
   203      if( !fullscreen ) {
   204          [self.mainWindow applyWindowConstraints];
   205      }
   206      
   207      WindowDelegate *windowDelegate = [WindowDelegate new];
   208      windowDelegate.hideOnClose = hideWindowOnClose;
   209      windowDelegate.ctx = self;
   210      [self.mainWindow setDelegate:windowDelegate];
   211      
   212      // Webview stuff here!
   213      WKWebViewConfiguration *config = [WKWebViewConfiguration new];
   214      config.suppressesIncrementalRendering = true;
   215      config.applicationNameForUserAgent = @"wails.io";
   216      [config setURLSchemeHandler:self forURLScheme:@"wails"];
   217  
   218      if (preferences.tabFocusesLinks != NULL) {
   219          config.preferences.tabFocusesLinks = *preferences.tabFocusesLinks;
   220      }
   221  
   222  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 110300
   223      if (@available(macOS 11.3, *)) {
   224          if (preferences.textInteractionEnabled != NULL) {
   225              config.preferences.textInteractionEnabled = *preferences.textInteractionEnabled;
   226          }
   227      }
   228  #endif
   229  
   230  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 120300
   231      if (@available(macOS 12.3, *)) {
   232              if (preferences.fullscreenEnabled != NULL) {
   233                  config.preferences.elementFullscreenEnabled = *preferences.fullscreenEnabled;
   234              }
   235      }
   236  #endif
   237  
   238  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
   239      if (@available(macOS 10.15, *)) {
   240          config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled;
   241      }
   242  #endif
   243  
   244      WKUserContentController* userContentController = [WKUserContentController new];
   245      [userContentController addScriptMessageHandler:self name:@"external"];
   246      config.userContentController = userContentController;
   247      self.userContentController = userContentController;
   248  
   249      if (self.devtoolsEnabled) {
   250          [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
   251      }
   252  
   253      if (!self.defaultContextMenuEnabled) {
   254          // Disable default context menus
   255          WKUserScript *initScript = [WKUserScript new];
   256          [initScript initWithSource:@"window.wails.flags.disableDefaultContextMenu = true;"
   257                       injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
   258                    forMainFrameOnly:false];
   259          [userContentController addUserScript:initScript];
   260      }
   261      
   262      self.webview = [WKWebView alloc];
   263      CGRect init = { 0,0,0,0 };
   264      [self.webview initWithFrame:init configuration:config];
   265      [contentView addSubview:self.webview];
   266      [self.webview setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
   267      CGRect contentViewBounds = [contentView bounds];
   268      [self.webview setFrame:contentViewBounds];
   269      
   270      if (webviewIsTransparent) {
   271          [self.webview setValue:[NSNumber numberWithBool:!webviewIsTransparent] forKey:@"drawsBackground"];
   272      }
   273      
   274      [self.webview setNavigationDelegate:self];
   275      self.webview.UIDelegate = self;
   276  
   277      NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   278      [defaults setBool:FALSE forKey:@"NSAutomaticQuoteSubstitutionEnabled"];
   279      
   280      // Mouse monitors
   281      [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
   282          id window = [event window];
   283          if (window == self.mainWindow) {
   284              self.mouseEvent = event;
   285          }
   286          return event;
   287      }];
   288      
   289      [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
   290          id window = [event window];
   291          if (window == self.mainWindow) {
   292              self.mouseEvent = nil;
   293              [self ShowMouse];
   294          }
   295          return event;
   296      }];
   297      
   298      self.applicationMenu = [NSMenu new];
   299      
   300  }
   301  
   302  - (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
   303      NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
   304      if( flags != 0 ) {
   305          [result setKeyEquivalentModifierMask:flags];
   306      }
   307      return result;
   308  }
   309  
   310  - (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key  {
   311      return [self newMenuItem :title :selector :key :0];
   312  }
   313  
   314  - (NSMenu*) newMenu :(NSString*)title {
   315      WailsMenu *result = [[WailsMenu new] initWithTitle:title];
   316      [result setAutoenablesItems:NO];
   317      return result;
   318  }
   319  
   320  - (void) Quit {
   321      processMessage("Q");
   322  }
   323  
   324  - (void) loadRequest :(NSString*)url {
   325      NSURL *wkUrl = [NSURL URLWithString:url];
   326      NSURLRequest *wkRequest = [NSURLRequest requestWithURL:wkUrl];
   327      [self.webview loadRequest:wkRequest];
   328  }
   329  
   330  - (void) SetBackgroundColour:(int)r :(int)g :(int)b :(int)a {
   331      float red = r/255.0;
   332      float green = g/255.0;
   333      float blue = b/255.0;
   334      float alpha = a/255.0;
   335      
   336      id colour = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:alpha ];
   337      
   338      [self.mainWindow setBackgroundColor:colour];
   339  }
   340  
   341  - (void) HideMouse {
   342      [NSCursor hide];
   343  }
   344  
   345  - (void) ShowMouse {
   346      [NSCursor unhide];
   347  }
   348  
   349  - (bool) IsFullScreen {
   350      long mask = [self.mainWindow styleMask];
   351      return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
   352  }
   353  
   354  // Fullscreen sets the main window to be fullscreen
   355  - (void) Fullscreen {
   356      if( ! [self IsFullScreen] ) {
   357          [self.mainWindow disableWindowConstraints];
   358          [self.mainWindow toggleFullScreen:nil];
   359      }
   360  }
   361  
   362  // UnFullscreen resets the main window after a fullscreen
   363  - (void) UnFullscreen {
   364      if( [self IsFullScreen] ) {
   365          [self.mainWindow applyWindowConstraints];
   366          [self.mainWindow toggleFullScreen:nil];
   367      }
   368  }
   369  
   370  - (void) Minimise {
   371      [self.mainWindow miniaturize:nil];
   372  }
   373  
   374  - (void) UnMinimise {
   375      [self.mainWindow deminiaturize:nil];
   376  }
   377  
   378  - (bool) IsMinimised {
   379      return [self.mainWindow isMiniaturized];
   380  }
   381  
   382  - (void) Hide {
   383      [self.mainWindow orderOut:nil];
   384  }
   385  
   386  - (void) Show {
   387      [self.mainWindow makeKeyAndOrderFront:nil];
   388      [NSApp activateIgnoringOtherApps:YES];
   389  }
   390  
   391  - (void) HideApplication {
   392      [[NSApplication sharedApplication] hide:self];
   393  }
   394  
   395  - (void) ShowApplication {
   396      [[NSApplication sharedApplication] unhide:self];
   397      [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
   398  
   399  }
   400  
   401  - (void) Maximise {
   402      if (![self.mainWindow isZoomed]) {
   403          [self.mainWindow zoom:nil];
   404      }
   405  }
   406  
   407  - (void) ToggleMaximise {
   408          [self.mainWindow zoom:nil];
   409  }
   410  
   411  - (void) UnMaximise {
   412      if ([self.mainWindow isZoomed]) {
   413          [self.mainWindow zoom:nil];
   414      }
   415  }
   416  
   417  - (void) SetAlwaysOnTop:(int)onTop {
   418      if (onTop) {
   419          [self.mainWindow setLevel:NSFloatingWindowLevel];
   420      } else {
   421          [self.mainWindow setLevel:NSNormalWindowLevel];
   422      }
   423  }
   424  
   425  - (bool) IsMaximised {
   426      return [self.mainWindow isZoomed];
   427  }
   428  
   429  - (void) ExecJS:(NSString*)script {
   430     [self.webview evaluateJavaScript:script completionHandler:nil];
   431  }
   432  
   433  - (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters 
   434      initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * URLs))completionHandler {
   435      
   436      NSOpenPanel *openPanel = [NSOpenPanel openPanel];
   437      openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
   438  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
   439      if (@available(macOS 10.14, *)) {
   440          openPanel.canChooseDirectories = parameters.allowsDirectories;
   441      }
   442  #endif
   443      [openPanel 
   444          beginSheetModalForWindow:webView.window
   445          completionHandler:^(NSInteger result) {
   446              if (result == NSModalResponseOK)
   447                  completionHandler(openPanel.URLs);
   448              else
   449                  completionHandler(nil);
   450          }];
   451  }
   452  
   453  - (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
   454      // This callback is run with an autorelease pool
   455      processURLRequest(self, urlSchemeTask);
   456  }
   457  
   458  - (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
   459      NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
   460      if (stream) {
   461          NSStreamStatus status = stream.streamStatus;
   462          if (status != NSStreamStatusClosed && status != NSStreamStatusNotOpen) {
   463              [stream close];
   464          }
   465      }
   466  }
   467  
   468  - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
   469      processMessage("DomReady");
   470  }
   471  
   472  - (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
   473      NSString *m = message.body;
   474      
   475      // Check for drag
   476      if ( [m isEqualToString:@"drag"] ) {
   477          if( [self IsFullScreen] ) {
   478              return;
   479          }
   480          if( self.mouseEvent != nil ) {
   481             [self.mainWindow performWindowDragWithEvent:self.mouseEvent];
   482          }
   483          return;
   484      }
   485      
   486      const char *_m = [m UTF8String];
   487      
   488      processMessage(_m);
   489  }
   490  
   491  
   492  /***** Dialogs ******/
   493  -(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength {
   494  
   495      WailsAlert *alert = [WailsAlert new];
   496      
   497      int style = NSAlertStyleInformational;
   498      if (dialogType != nil ) {
   499          if( [dialogType isEqualToString:@"warning"] ) {
   500              style = NSAlertStyleWarning;
   501          }
   502          if( [dialogType isEqualToString:@"error"] ) {
   503              style = NSAlertStyleCritical;
   504          }
   505      }
   506      [alert setAlertStyle:style];
   507      if( title != nil ) {
   508          [alert setMessageText:title];
   509      }
   510      if( message != nil ) {
   511          [alert setInformativeText:message];
   512      }
   513      
   514      [alert addButton:button1 :defaultButton :cancelButton];
   515      [alert addButton:button2 :defaultButton :cancelButton];
   516      [alert addButton:button3 :defaultButton :cancelButton];
   517      [alert addButton:button4 :defaultButton :cancelButton];
   518      
   519      NSImage *icon = nil;
   520      if (iconData != nil) {
   521          NSData *imageData = [NSData dataWithBytes:iconData length:iconDataLength];
   522          icon = [[NSImage alloc] initWithData:imageData];
   523      }
   524      if( icon != nil) {
   525         [alert setIcon:icon];
   526      }
   527      [alert.window setLevel:NSFloatingWindowLevel];
   528  
   529      long response = [alert runModal];
   530      int result;
   531  
   532      if( response == NSAlertFirstButtonReturn ) {
   533          result = 0;
   534      }
   535      else if( response == NSAlertSecondButtonReturn ) {
   536          result = 1;
   537      }
   538      else if( response == NSAlertThirdButtonReturn ) {
   539          result = 2;
   540      } else {
   541          result = 3;
   542      }
   543      processMessageDialogResponse(result);
   544  }
   545  
   546  -(void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters {
   547      
   548      
   549      // Create the dialog
   550      NSOpenPanel *dialog = [NSOpenPanel openPanel];
   551  
   552      // Valid but appears to do nothing.... :/
   553      if( title != nil ) {
   554          [dialog setTitle:title];
   555      }
   556  
   557      // Filters - semicolon delimited list of file extensions
   558      if( allowFiles ) {
   559          if( filters != nil && [filters length] > 0) {
   560              filters = [filters stringByReplacingOccurrencesOfString:@"*." withString:@""];
   561              filters = [filters stringByReplacingOccurrencesOfString:@" " withString:@""];
   562              NSArray *filterList = [filters componentsSeparatedByString:@";"];
   563  #ifdef USE_NEW_FILTERS
   564                  NSMutableArray *contentTypes = [[NSMutableArray new] autorelease];
   565                  for (NSString *filter in filterList) {
   566  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
   567                      if (@available(macOS 11.0, *)) {
   568                          UTType *t = [UTType typeWithFilenameExtension:filter];
   569                          [contentTypes addObject:t];
   570                      }
   571  #endif
   572                  }
   573  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
   574              if (@available(macOS 11.0, *)) {
   575                  [dialog setAllowedContentTypes:contentTypes];
   576              }
   577  #endif
   578  #else
   579                  [dialog setAllowedFileTypes:filterList];
   580  #endif
   581          } else {
   582              [dialog setAllowsOtherFileTypes:true];
   583          }
   584          // Default Filename
   585          if( defaultFilename != nil ) {
   586              [dialog setNameFieldStringValue:defaultFilename];
   587          }
   588          
   589          [dialog setAllowsMultipleSelection: allowMultipleSelection];
   590          [dialog setShowsHiddenFiles: showHiddenFiles];
   591  
   592      }
   593  
   594      // Default Directory
   595      if( defaultDirectory != nil ) {
   596          NSURL *url = [NSURL fileURLWithPath:defaultDirectory];
   597          [dialog setDirectoryURL:url];
   598      }
   599  
   600  
   601      // Setup Options
   602      [dialog setCanChooseFiles: allowFiles];
   603      [dialog setCanChooseDirectories: allowDirectories];
   604      [dialog setCanCreateDirectories: canCreateDirectories];
   605      [dialog setResolvesAliases: resolveAliases];
   606      [dialog setTreatsFilePackagesAsDirectories: treatPackagesAsDirectories];
   607  
   608      // Setup callback handler
   609      [dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
   610          if ( returnCode != NSModalResponseOK) {
   611              processOpenFileDialogResponse("[]");
   612              return;
   613          }
   614          NSMutableArray *arr = [NSMutableArray new];
   615          for (NSURL *url in [dialog URLs]) {
   616              [arr addObject:[url path]];
   617          }
   618          NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arr options:0 error:nil];
   619          NSString *nsjson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
   620          processOpenFileDialogResponse([nsjson UTF8String]);
   621          [nsjson release];
   622          [arr release];
   623      }];
   624      
   625  }
   626  
   627  
   628  -(void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters; {
   629      
   630      
   631      // Create the dialog
   632      NSSavePanel *dialog = [NSSavePanel savePanel];
   633  
   634      // Do not hide extension
   635      [dialog setExtensionHidden:false];
   636      
   637      // Valid but appears to do nothing.... :/
   638      if( title != nil ) {
   639          [dialog setTitle:title];
   640      }
   641  
   642      // Filters - semicolon delimited list of file extensions
   643      if( filters != nil && [filters length] > 0) {
   644          filters = [filters stringByReplacingOccurrencesOfString:@"*." withString:@""];
   645          filters = [filters stringByReplacingOccurrencesOfString:@" " withString:@""];
   646          NSArray *filterList = [filters componentsSeparatedByString:@";"];
   647  #ifdef USE_NEW_FILTERS
   648              NSMutableArray *contentTypes = [[NSMutableArray new] autorelease];
   649              for (NSString *filter in filterList) {
   650  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
   651                  if (@available(macOS 11.0, *)) {
   652                      UTType *t = [UTType typeWithFilenameExtension:filter];
   653                      [contentTypes addObject:t];
   654                  }
   655  #endif
   656              }
   657          if( contentTypes.count == 0) {
   658              [dialog setAllowsOtherFileTypes:true];
   659          } else {
   660  #if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
   661              if (@available(macOS 11.0, *)) {
   662                  [dialog setAllowedContentTypes:contentTypes];
   663              }
   664  #endif
   665          }
   666  
   667  #else
   668              [dialog setAllowedFileTypes:filterList];
   669  #endif
   670      } else {
   671          [dialog setAllowsOtherFileTypes:true];
   672      }
   673      // Default Filename
   674      if( defaultFilename != nil ) {
   675          [dialog setNameFieldStringValue:defaultFilename];
   676      }
   677      
   678      // Default Directory
   679      if( defaultDirectory != nil ) {
   680          NSURL *url = [NSURL fileURLWithPath:defaultDirectory];
   681          [dialog setDirectoryURL:url];
   682      }
   683  
   684      // Setup Options
   685      [dialog setCanSelectHiddenExtension:true];
   686  //    dialog.isExtensionHidden = false;
   687      [dialog setCanCreateDirectories: canCreateDirectories];
   688      [dialog setTreatsFilePackagesAsDirectories: treatPackagesAsDirectories];
   689      [dialog setShowsHiddenFiles: showHiddenFiles];
   690  
   691      // Setup callback handler
   692      [dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
   693          if ( returnCode == NSModalResponseOK ) {
   694              NSURL *url = [dialog URL];
   695              if ( url != nil ) {
   696                  processSaveFileDialogResponse([url.path UTF8String]);
   697                  return;
   698              }
   699          }
   700          processSaveFileDialogResponse("");
   701      }];
   702          
   703  }
   704  
   705  - (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen {
   706      self.aboutTitle = title;
   707      self.aboutDescription = description;
   708     
   709      NSData *imageData = [NSData dataWithBytes:imagedata length:datalen];
   710      self.aboutImage = [[NSImage alloc] initWithData:imageData];
   711  }
   712  
   713  -(void) About {
   714      
   715      WailsAlert *alert = [WailsAlert new];
   716      [alert setAlertStyle:NSAlertStyleInformational];
   717      if( self.aboutTitle != nil ) {
   718          [alert setMessageText:self.aboutTitle];
   719      }
   720      if( self.aboutDescription != nil ) {
   721          [alert setInformativeText:self.aboutDescription];
   722      }
   723      
   724      
   725      [alert.window setLevel:NSFloatingWindowLevel];
   726      if ( self.aboutImage != nil) {
   727          [alert setIcon:self.aboutImage];
   728      }
   729  
   730      [alert runModal];
   731  }
   732  
   733  @end
   734