1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/osx/cocoa/utils.mm
3// Purpose:     various cocoa utility functions
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// Copyright:   (c) Stefan Csomor
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#include "wx/utils.h"
14#include "wx/platinfo.h"
15
16#ifndef WX_PRECOMP
17    #include "wx/intl.h"
18    #include "wx/app.h"
19    #if wxUSE_GUI
20        #include "wx/dialog.h"
21        #include "wx/toplevel.h"
22        #include "wx/font.h"
23    #endif
24#endif
25
26#include "wx/apptrait.h"
27
28#include "wx/osx/private.h"
29#include "wx/osx/private/available.h"
30
31#if wxUSE_GUI
32#if wxOSX_USE_COCOA_OR_CARBON
33    #include <CoreServices/CoreServices.h>
34    #include "wx/osx/dcclient.h"
35    #include "wx/osx/private/timer.h"
36#endif
37#endif // wxUSE_GUI
38
39#if wxUSE_GUI
40
41// Emit a beeeeeep
42void wxBell()
43{
44    NSBeep();
45}
46
47@implementation wxNSAppController
48
49- (void)applicationWillFinishLaunching:(NSNotification *)application
50{
51    wxUnusedVar(application);
52
53    // we must install our handlers later than setting the app delegate, because otherwise our handlers
54    // get overwritten in the meantime
55
56    NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
57
58    [appleEventManager setEventHandler:self andSelector:@selector(handleGetURLEvent:withReplyEvent:)
59                         forEventClass:kInternetEventClass andEventID:kAEGetURL];
60
61    [appleEventManager setEventHandler:self andSelector:@selector(handleOpenAppEvent:withReplyEvent:)
62                         forEventClass:kCoreEventClass andEventID:kAEOpenApplication];
63
64    [appleEventManager setEventHandler:self andSelector:@selector(handleQuitAppEvent:withReplyEvent:)
65                         forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
66
67    wxTheApp->OSXOnWillFinishLaunching();
68}
69
70- (void)applicationDidFinishLaunching:(NSNotification *)notification
71{
72    wxUnusedVar(notification);
73    [NSApp stop:nil];
74    wxTheApp->OSXOnDidFinishLaunching();
75
76    // We need to activate the application manually if it's not part of a
77    // bundle, otherwise not only it won't come to the foreground, but under
78    // recent macOS versions (10.15+), its menus simply won't work at all.
79    //
80    // Note that we have not one but two methods to opt out from this behaviour
81    // for compatibility.
82    if ( !wxApp::sm_isEmbedded && wxTheApp && wxTheApp->OSXIsGUIApplication() )
83    {
84        CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle() ) ;
85        CFStringRef path = CFURLCopyFileSystemPath ( url , kCFURLPOSIXPathStyle ) ;
86        CFRelease( url ) ;
87        wxString app = wxCFStringRef(path).AsString(wxLocale::GetSystemEncoding());
88        if ( !app.EndsWith(".app") )
89        {
90            [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
91            [NSApp activateIgnoringOtherApps: YES];
92        }
93    }
94}
95
96- (void)application:(NSApplication *)sender openFiles:(NSArray *)fileNames
97{
98    wxUnusedVar(sender);
99    wxArrayString fileList;
100    size_t i;
101    const size_t count = [fileNames count];
102    for (i = 0; i < count; i++)
103    {
104        fileList.Add( wxCFStringRef::AsStringWithNormalizationFormC([fileNames objectAtIndex:i]) );
105    }
106
107    if ( wxTheApp->OSXInitWasCalled() )
108        wxTheApp->MacOpenFiles(fileList);
109    else
110        wxTheApp->OSXStoreOpenFiles(fileList);
111}
112
113- (NSApplicationPrintReply)application:(NSApplication *)sender printFiles:(NSArray *)fileNames withSettings:(NSDictionary *)printSettings showPrintPanels:(BOOL)showPrintPanels
114{
115    wxUnusedVar(sender);
116    wxUnusedVar(printSettings);
117    wxUnusedVar(showPrintPanels);
118
119    wxArrayString fileList;
120    size_t i;
121    const size_t count = [fileNames count];
122    for (i = 0; i < count; i++)
123    {
124        fileList.Add( wxCFStringRef::AsStringWithNormalizationFormC([fileNames objectAtIndex:i]) );
125    }
126
127    if ( wxTheApp->OSXInitWasCalled() )
128        wxTheApp->MacPrintFiles(fileList);
129    else
130        wxTheApp->OSXStorePrintFiles(fileList);
131
132    return NSPrintingSuccess;
133}
134
135- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
136{
137    wxUnusedVar(flag);
138    wxUnusedVar(sender);
139    if ( wxTheApp->OSXInitWasCalled() )
140        wxTheApp->MacReopenApp();
141    // else: It's possible that this function was called as the first thing.
142    //       This can happen when OS X restores running apps when starting a new
143    //       user session. Apps that were hidden (dock only) when the previous
144    //       session terminated are only restored in a limited, resources-saving
145    //       way. When the user clicks the icon, applicationShouldHandleReopen:
146    //       is called, but we didn't call OnInit() yet. In this case, we
147    //       shouldn't call MacReopenApp(), but should proceed with normal
148    //       initialization.
149    return NO;
150}
151
152- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
153           withReplyEvent:(NSAppleEventDescriptor *)replyEvent
154{
155    wxUnusedVar(replyEvent);
156    NSString* url = [[event descriptorAtIndex:1] stringValue];
157    wxCFStringRef cf(wxCFRetain(url));
158    if ( wxTheApp->OSXInitWasCalled() )
159        wxTheApp->MacOpenURL(cf.AsString()) ;
160    else
161        wxTheApp->OSXStoreOpenURL(cf.AsString());
162}
163
164- (void)handleQuitAppEvent:(NSAppleEventDescriptor *)event
165            withReplyEvent:(NSAppleEventDescriptor *)replyEvent
166{
167    wxUnusedVar(event);
168    wxUnusedVar(replyEvent);
169    if ( wxTheApp->OSXOnShouldTerminate() )
170    {
171        wxTheApp->OSXOnWillTerminate();
172        wxTheApp->ExitMainLoop();
173    }
174}
175
176- (void)handleOpenAppEvent:(NSAppleEventDescriptor *)event
177           withReplyEvent:(NSAppleEventDescriptor *)replyEvent
178{
179    wxUnusedVar(event);
180    wxUnusedVar(replyEvent);
181}
182
183/*
184    Allowable return values are:
185        NSTerminateNow - it is ok to proceed with termination
186        NSTerminateCancel - the application should not be terminated
187        NSTerminateLater - it may be ok to proceed with termination later.  The application must call -replyToApplicationShouldTerminate: with YES or NO once the answer is known
188            this return value is for delegates who need to provide document modal alerts (sheets) in order to decide whether to quit.
189*/
190- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
191{
192    wxUnusedVar(sender);
193    if ( !wxTheApp->OSXOnShouldTerminate() )
194        return NSTerminateCancel;
195
196    return NSTerminateNow;
197}
198
199- (void)applicationWillTerminate:(NSNotification *)application {
200    wxUnusedVar(application);
201    wxTheApp->OSXOnWillTerminate();
202}
203
204- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
205{
206    wxUnusedVar(sender);
207    // let wx do this, not cocoa
208    return NO;
209}
210
211- (void)applicationDidBecomeActive:(NSNotification *)notification
212{
213    wxUnusedVar(notification);
214
215    for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(),
216         end = wxTopLevelWindows.end();
217         i != end;
218         ++i )
219    {
220        wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i);
221        wxNonOwnedWindowImpl* winimpl = win ? win->GetNonOwnedPeer() : NULL;
222        WXWindow nswindow = win ? win->GetWXWindow() : nil;
223
224        if ( nswindow && [nswindow hidesOnDeactivate] == NO && winimpl)
225            winimpl->RestoreWindowLevel();
226    }
227    if ( wxTheApp )
228        wxTheApp->SetActive( true , NULL ) ;
229}
230
231- (void)applicationWillResignActive:(NSNotification *)notification
232{
233    wxUnusedVar(notification);
234    for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(),
235         end = wxTopLevelWindows.end();
236         i != end;
237         ++i )
238    {
239        wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i);
240        WXWindow nswindow = win ? win->GetWXWindow() : nil;
241
242        if ( nswindow && [nswindow level] == kCGFloatingWindowLevel && [nswindow hidesOnDeactivate] == NO )
243            [nswindow setLevel:kCGNormalWindowLevel];
244    }
245}
246
247- (void)applicationDidResignActive:(NSNotification *)notification
248{
249    wxUnusedVar(notification);
250    if ( wxTheApp )
251        wxTheApp->SetActive( false , NULL ) ;
252}
253
254@end
255
256/*
257    allows ShowModal to work when using sheets.
258    see include/wx/osx/cocoa/private.h for more info
259*/
260@implementation ModalDialogDelegate
261- (id)init
262{
263    if ( self = [super init] )
264    {
265        sheetFinished = NO;
266        resultCode = -1;
267        impl = 0;
268    }
269    return self;
270}
271
272- (void)setImplementation: (wxDialog *)dialog
273{
274    impl = dialog;
275}
276
277- (BOOL)finished
278{
279    return sheetFinished;
280}
281
282- (int)code
283{
284    return resultCode;
285}
286
287- (void)waitForSheetToFinish
288{
289    while (!sheetFinished)
290    {
291        wxSafeYield();
292    }
293}
294
295- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
296{
297    wxUnusedVar(contextInfo);
298    resultCode = returnCode;
299    sheetFinished = YES;
300    // NSAlerts don't need nor respond to orderOut
301    if ([sheet respondsToSelector:@selector(orderOut:)])
302        [sheet orderOut: self];
303
304    if (impl)
305        impl->ModalFinishedCallback(sheet, returnCode);
306}
307@end
308
309// here we subclass NSApplication, for the purpose of being able to override sendEvent.
310@interface wxNSApplication : NSApplication
311{
312}
313
314- (id)init;
315
316- (void)sendEvent:(NSEvent *)anEvent;
317
318@end
319
320@implementation wxNSApplication
321
322- (id)init
323{
324    if ( self = [super init] )
325    {
326        // further init
327    }
328    return self;
329}
330
331/* This is needed because otherwise we don't receive any key-up events for command-key
332 combinations (an AppKit bug, apparently) */
333- (void)sendEvent:(NSEvent *)anEvent
334{
335    if ([anEvent type] == NSKeyUp && ([anEvent modifierFlags] & NSCommandKeyMask))
336        [[self keyWindow] sendEvent:anEvent];
337    else
338        [super sendEvent:anEvent];
339}
340
341@end
342
343WX_NSObject appcontroller = nil;
344
345NSLayoutManager* gNSLayoutManager = nil;
346
347WX_NSObject wxApp::OSXCreateAppController()
348{
349    return [[wxNSAppController alloc] init];
350}
351
352bool wxApp::DoInitGui()
353{
354    wxMacAutoreleasePool pool;
355
356    if (!sm_isEmbedded)
357    {
358        [wxNSApplication sharedApplication];
359
360        appcontroller = OSXCreateAppController();
361        [[NSApplication sharedApplication] setDelegate:(id <NSApplicationDelegate>)appcontroller];
362        [NSColor setIgnoresAlpha:NO];
363    }
364    gNSLayoutManager = [[NSLayoutManager alloc] init];
365
366    // This call makes it so that appplication:openFile: doesn't get bogus calls
367    // from Cocoa doing its own parsing of the argument string. And yes, we need
368    // to use a string with a boolean value in it. That's just how it works.
369    [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
370
371    return true;
372}
373
374bool wxApp::CallOnInit()
375{
376    wxMacAutoreleasePool autoreleasepool;
377    m_onInitResult = false;
378    m_inited = false;
379
380    if ( !sm_isEmbedded )
381    {
382        // Feed the upcoming event loop with a dummy event. Without this,
383        // [NSApp run] below wouldn't return, as we expect it to, if the
384        // application was launched without being activated and would block
385        // until the dock icon was clicked - delaying OnInit() call too.
386        NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
387                                    location:NSMakePoint(0.0, 0.0)
388                               modifierFlags:0
389                                   timestamp:0
390                                windowNumber:0
391                                     context:nil
392                                     subtype:0 data1:0 data2:0];
393        [NSApp postEvent:event atStart:FALSE];
394        [NSApp run];
395    }
396
397    m_onInitResult = OnInit();
398    m_inited = true;
399    if ( !sm_isEmbedded && m_onInitResult )
400    {
401        if ( m_openFiles.GetCount() > 0 )
402            MacOpenFiles(m_openFiles);
403        else if ( m_printFiles.GetCount() > 0 )
404            MacPrintFiles(m_printFiles);
405        else if ( m_getURL.Len() > 0 )
406            MacOpenURL(m_getURL);
407        else
408            MacNewFile();
409    }
410    return m_onInitResult;
411}
412
413void wxApp::DoCleanUp()
414{
415    if ( appcontroller != nil )
416    {
417        [NSApp setDelegate:nil];
418        [appcontroller release];
419        appcontroller = nil;
420    }
421    if ( gNSLayoutManager != nil )
422    {
423        [gNSLayoutManager release];
424        gNSLayoutManager = nil;
425    }
426}
427
428void wxApp::OSXEnableAutomaticTabbing(bool enable)
429{
430    // Automatic tabbing was first introduced in 10.12
431#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
432    if ( WX_IS_MACOS_AVAILABLE(10, 12) )
433    {
434        [NSWindow setAllowsAutomaticWindowTabbing:enable];
435    }
436#endif // macOS 10.12+
437}
438
439extern // used from src/osx/core/display.cpp
440wxRect wxOSXGetMainDisplayClientArea()
441{
442    NSRect displayRect = [wxOSXGetMenuScreen() visibleFrame];
443    return wxFromNSRect( NULL, displayRect );
444}
445
446static NSScreen* wxOSXGetScreenFromDisplay( CGDirectDisplayID ID)
447{
448    for (NSScreen* screen in [NSScreen screens])
449    {
450        CGDirectDisplayID displayID = [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
451        if ( displayID == ID )
452            return screen;
453    }
454    return NULL;
455}
456
457extern // used from src/osx/core/display.cpp
458wxRect wxOSXGetDisplayClientArea(CGDirectDisplayID ID)
459{
460    NSRect displayRect = [wxOSXGetScreenFromDisplay(ID) visibleFrame];
461    return wxFromNSRect( NULL, displayRect );
462}
463
464void wxGetMousePosition( int* x, int* y )
465{
466    wxPoint pt = wxFromNSPoint(NULL, [NSEvent mouseLocation]);
467    if ( x )
468        *x = pt.x;
469    if ( y )
470        *y = pt.y;
471};
472
473wxMouseState wxGetMouseState()
474{
475    wxMouseState ms;
476
477    wxPoint pt = wxGetMousePosition();
478    ms.SetX(pt.x);
479    ms.SetY(pt.y);
480
481    NSUInteger modifiers = [NSEvent modifierFlags];
482    NSUInteger buttons = [NSEvent pressedMouseButtons];
483
484    ms.SetLeftDown( (buttons & 0x01) != 0 );
485    ms.SetMiddleDown( (buttons & 0x04) != 0 );
486    ms.SetRightDown( (buttons & 0x02) != 0 );
487
488    ms.SetRawControlDown(modifiers & NSControlKeyMask);
489    ms.SetShiftDown(modifiers & NSShiftKeyMask);
490    ms.SetAltDown(modifiers & NSAlternateKeyMask);
491    ms.SetControlDown(modifiers & NSCommandKeyMask);
492
493    return ms;
494}
495
496wxTimerImpl* wxGUIAppTraits::CreateTimerImpl(wxTimer *timer)
497{
498    return new wxOSXTimerImpl(timer);
499}
500
501int gs_wxBusyCursorCount = 0;
502extern wxCursor    gMacCurrentCursor;
503wxCursor        gMacStoredActiveCursor;
504
505// Set the cursor to the busy cursor for all windows
506void wxBeginBusyCursor(const wxCursor *cursor)
507{
508    if (gs_wxBusyCursorCount++ == 0)
509    {
510        NSEnumerator *enumerator = [[[NSApplication sharedApplication] windows] objectEnumerator];
511        id object;
512
513        while ((object = [enumerator nextObject])) {
514            [(NSWindow*) object disableCursorRects];
515        }
516
517        gMacStoredActiveCursor = gMacCurrentCursor;
518        cursor->MacInstall();
519
520        wxSetCursor(*cursor);
521    }
522    //else: nothing to do, already set
523}
524
525// Restore cursor to normal
526void wxEndBusyCursor()
527{
528    wxCHECK_RET( gs_wxBusyCursorCount > 0,
529        wxT("no matching wxBeginBusyCursor() for wxEndBusyCursor()") );
530
531    if (--gs_wxBusyCursorCount == 0)
532    {
533        NSEnumerator *enumerator = [[[NSApplication sharedApplication] windows] objectEnumerator];
534        id object;
535
536        while ((object = [enumerator nextObject])) {
537            [(NSWindow*) object enableCursorRects];
538        }
539
540        wxSetCursor(wxNullCursor);
541
542        gMacStoredActiveCursor.MacInstall();
543        gMacStoredActiveCursor = wxNullCursor;
544    }
545}
546
547// true if we're between the above two calls
548bool wxIsBusy()
549{
550    return (gs_wxBusyCursorCount > 0);
551}
552
553wxBitmap wxWindowDCImpl::DoGetAsBitmap(const wxRect *subrect) const
554{
555    // wxScreenDC is derived from wxWindowDC, so a screen dc will
556    // call this method when a Blit is performed with it as a source.
557    if (!m_window)
558        return wxNullBitmap;
559
560    const wxSize bitmapSize(subrect ? subrect->GetSize() : m_window->GetSize());
561    wxBitmap bitmap;
562    bitmap.CreateScaled(bitmapSize.x, bitmapSize.y, -1, m_contentScaleFactor);
563
564    NSView* view = (NSView*) m_window->GetHandle();
565    if ( [view isHiddenOrHasHiddenAncestor] == NO )
566    {
567        // the old implementaiton is not working under 10.15, the new one should work for older systems as well
568        // however the new implementation does not take into account the backgroundViews, and I'm not sure about
569        // until we're
570        // sure the replacement is always better
571
572        bool useOldImplementation = false;
573        NSBitmapImageRep *rep = nil;
574
575        if ( useOldImplementation )
576        {
577            [view lockFocus];
578            // we use this method as other methods force a repaint, and this method can be
579            // called from OnPaint, even with the window's paint dc as source (see wxHTMLWindow)
580            rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: [view bounds]];
581            [view unlockFocus];
582
583        }
584        else
585        {
586            rep = [view bitmapImageRepForCachingDisplayInRect:[view bounds]];
587            [view cacheDisplayInRect:[view bounds] toBitmapImageRep:rep];
588        }
589
590        CGImageRef cgImageRef = (CGImageRef)[rep CGImage];
591
592        CGRect r = CGRectMake( 0 , 0 , CGImageGetWidth(cgImageRef)  , CGImageGetHeight(cgImageRef) );
593
594        // The bitmap created by wxBitmap::CreateScaled() above is scaled,
595        // so we need to adjust the coordinates for it.
596        r.size.width /= m_contentScaleFactor;
597        r.size.height /= m_contentScaleFactor;
598
599        // since our context is upside down we dont use CGContextDrawImage
600        wxMacDrawCGImage( (CGContextRef) bitmap.GetHBITMAP() , &r, cgImageRef ) ;
601
602        if ( useOldImplementation )
603            [rep release];
604    }
605
606    return bitmap;
607}
608
609#endif // wxUSE_GUI
610
611