1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 *   Licensed to the Apache Software Foundation (ASF) under one or more
12 *   contributor license agreements. See the NOTICE file distributed
13 *   with this work for additional information regarding copyright
14 *   ownership. The ASF licenses this file to you under the Apache
15 *   License, Version 2.0 (the "License"); you may not use this file
16 *   except in compliance with the License. You may obtain a copy of
17 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21#include <config_features.h>
22
23#include <vector>
24
25#include <stdlib.h>
26
27#include <sal/main.h>
28#include <vcl/commandevent.hxx>
29#include <vcl/ImageTree.hxx>
30#include <vcl/svapp.hxx>
31#include <vcl/window.hxx>
32
33#include <osx/saldata.hxx>
34#include <osx/salframe.h>
35#include <osx/salframeview.h>
36#include <osx/salinst.h>
37#include <osx/vclnsapp.h>
38#include <quartz/utils.h>
39
40#include <premac.h>
41#include <objc/objc-runtime.h>
42#import "Carbon/Carbon.h"
43#import "apple_remote/RemoteControl.h"
44#include <postmac.h>
45
46
47@implementation CocoaThreadEnabler
48-(void)enableCocoaThreads:(id)param
49{
50    // do nothing, this is just to start an NSThread and therefore put
51    // Cocoa into multithread mode
52    (void)param;
53}
54@end
55
56// If you wonder how this VCL_NSApplication stuff works, one thing you
57// might have missed is that the NSPrincipalClass property in
58// desktop/macosx/Info.plist has the value VCL_NSApplication.
59
60@implementation VCL_NSApplication
61
62-(void)applicationDidFinishLaunching:(NSNotification*)pNotification
63{
64    (void)pNotification;
65
66SAL_WNODEPRECATED_DECLARATIONS_PUSH
67        // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
68    NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
69                               location: NSZeroPoint
70                               modifierFlags: 0
71                               timestamp: [[NSProcessInfo processInfo] systemUptime]
72                               windowNumber: 0
73                               context: nil
74                               subtype: AquaSalInstance::AppExecuteSVMain
75                               data1: 0
76                               data2: 0 ];
77SAL_WNODEPRECATED_DECLARATIONS_POP
78    assert( pEvent );
79    [NSApp postEvent: pEvent atStart: NO];
80
81    if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
82    {
83        [NSWindow setAllowsAutomaticWindowTabbing:NO];
84    }
85}
86
87-(void)sendEvent:(NSEvent*)pEvent
88{
89    NSEventType eType = [pEvent type];
90SAL_WNODEPRECATED_DECLARATIONS_PUSH
91        // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
92        // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
93        // 'NSClosableWindowMask' is deprecated: first deprecated in macOS 10.12
94        // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
95        // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
96        // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12
97        // 'NSMiniaturizableWindowMask' is deprecated: first deprecated in macOS 10.12
98        // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
99    if( eType == NSApplicationDefined )
100    {
101        AquaSalInstance::handleAppDefinedEvent( pEvent );
102    }
103    else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
104    {
105        NSWindow* pKeyWin = [NSApp keyWindow];
106        if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
107        {
108            AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame];
109            unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
110            /*
111             * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
112             */
113            if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
114            {
115                if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
116                {
117                    [pFrame->getNSWindow() performMiniaturize: nil];
118                    return;
119                }
120
121                if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
122                {
123                    [NSApp miniaturizeAll: nil];
124                    return;
125                }
126            }
127
128            // get information whether the event was handled; keyDown returns nothing
129            GetSalData()->maKeyEventAnswer[ pEvent ] = false;
130            bool bHandled = false;
131
132            // dispatch to view directly to avoid the key event being consumed by the menubar
133            // popup windows do not get the focus, so they don't get these either
134            // simplest would be dispatch this to the key window always if it is without parent
135            // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
136            if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) )
137            {
138                [[pKeyWin contentView] keyDown: pEvent];
139                bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
140            }
141
142            // see whether the main menu consumes this event
143            // if not, we want to dispatch it ourselves. Unless we do this "trick"
144            // the main menu just beeps for an unknown or disabled key equivalent
145            // and swallows the event wholesale
146            NSMenu* pMainMenu = [NSApp mainMenu];
147            if( ! bHandled &&
148                (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) )
149            {
150                [[pKeyWin contentView] keyDown: pEvent];
151                bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
152            }
153            else
154            {
155                bHandled = true;  // event handled already or main menu just handled it
156            }
157            GetSalData()->maKeyEventAnswer.erase( pEvent );
158
159            if( bHandled )
160                return;
161        }
162        else if( pKeyWin )
163        {
164            // #i94601# a window not of vcl's making has the focus.
165            // Since our menus do not invoke the usual commands
166            // try to play nice with native windows like the file dialog
167            // and emulate them
168            // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
169            // NOT localized, that is the same in all locales. Should this be
170            // different in any locale, this hack will fail.
171            unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
172            if( nModMask == NSCommandKeyMask )
173            {
174
175                if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
176                {
177                    if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
178                        return;
179                }
180                else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
181                {
182                    if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
183                        return;
184                }
185                else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
186                {
187                    if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
188                        return;
189                }
190                else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
191                {
192                    if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
193                        return;
194                }
195                else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
196                {
197                    if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
198                        return;
199                }
200            }
201            else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) )
202            {
203                if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
204                {
205                    if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
206                        return;
207                }
208            }
209        }
210    }
211SAL_WNODEPRECATED_DECLARATIONS_POP
212    [super sendEvent: pEvent];
213}
214
215-(void)sendSuperEvent:(NSEvent*)pEvent
216{
217    [super sendEvent: pEvent];
218}
219
220-(NSMenu*)applicationDockMenu:(NSApplication *)sender
221{
222    (void)sender;
223    return AquaSalInstance::GetDynamicDockMenu();
224}
225
226-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
227{
228    (void)app;
229    std::vector<OUString> aFile;
230    aFile.push_back( GetOUString( pFile ) );
231    if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
232    {
233        const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFile);
234        AquaSalInstance::aAppEventList.push_back( pAppEvent );
235        AquaSalInstance *pInst = GetSalData()->mpInstance;
236        if( pInst )
237            pInst->TriggerUserEventProcessing();
238    }
239    return YES;
240}
241
242-(void)application: (NSApplication*) app openFiles: (NSArray*)files
243{
244    (void)app;
245    std::vector<OUString> aFileList;
246
247    NSEnumerator* it = [files objectEnumerator];
248    NSString* pFile = nil;
249
250    while( (pFile = [it nextObject]) != nil )
251    {
252        const OUString aFile( GetOUString( pFile ) );
253        if( ! AquaSalInstance::isOnCommandLine( aFile ) )
254        {
255            aFileList.push_back( aFile );
256        }
257    }
258
259    if( !aFileList.empty() )
260    {
261        // we have no back channel here, we have to assume success, in which case
262        // replyToOpenOrPrint does not need to be called according to documentation
263        // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
264        const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFileList);
265        AquaSalInstance::aAppEventList.push_back( pAppEvent );
266        AquaSalInstance *pInst = GetSalData()->mpInstance;
267        if( pInst )
268            pInst->TriggerUserEventProcessing();
269    }
270}
271
272-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
273{
274    (void)app;
275    std::vector<OUString> aFile;
276    aFile.push_back( GetOUString( pFile ) );
277    const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFile);
278    AquaSalInstance::aAppEventList.push_back( pAppEvent );
279    AquaSalInstance *pInst = GetSalData()->mpInstance;
280    if( pInst )
281        pInst->TriggerUserEventProcessing();
282    return YES;
283}
284-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
285{
286    (void)app;
287    (void)printSettings;
288    (void)bShowPrintPanels;
289    // currently ignores print settings a bShowPrintPanels
290    std::vector<OUString> aFileList;
291
292    NSEnumerator* it = [files objectEnumerator];
293    NSString* pFile = nil;
294
295    while( (pFile = [it nextObject]) != nil )
296    {
297        aFileList.push_back( GetOUString( pFile ) );
298    }
299    const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFileList);
300    AquaSalInstance::aAppEventList.push_back( pAppEvent );
301    AquaSalInstance *pInst = GetSalData()->mpInstance;
302    if( pInst )
303        pInst->TriggerUserEventProcessing();
304    // we have no back channel here, we have to assume success
305    // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
306    return NSPrintingSuccess;
307}
308
309-(void)applicationWillTerminate: (NSNotification *) aNotification
310{
311    (void)aNotification;
312    sal_detail_deinitialize();
313    _Exit(0);
314}
315
316-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
317{
318    (void)app;
319    NSApplicationTerminateReply aReply = NSTerminateNow;
320    {
321        SolarMutexGuard aGuard;
322
323        AquaSalInstance *pInst = GetSalData()->mpInstance;
324        SalFrame *pAnyFrame = pInst->anyFrame();
325        if( pAnyFrame )
326        {
327            // the following QueryExit will likely present a message box, activate application
328            [NSApp activateIgnoringOtherApps: YES];
329            aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow;
330        }
331
332        if( aReply == NSTerminateNow )
333        {
334            ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown);
335            GetpApp()->AppEvent( aEv );
336            ImageTree::get().shutdown();
337            // DeInitVCL should be called in ImplSVMain - unless someone exits first which
338            // can occur in Desktop::doShutdown for example
339        }
340    }
341
342    return aReply;
343}
344
345-(void)systemColorsChanged: (NSNotification*) pNotification
346{
347    (void)pNotification;
348    SolarMutexGuard aGuard;
349
350    AquaSalInstance *pInst = GetSalData()->mpInstance;
351    SalFrame *pAnyFrame = pInst->anyFrame();
352    if(  pAnyFrame )
353        pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
354}
355
356-(void)screenParametersChanged: (NSNotification*) pNotification
357{
358    (void)pNotification;
359    SolarMutexGuard aGuard;
360
361    for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
362    {
363        AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame );
364        pFrame->screenParametersChanged();
365    }
366}
367
368-(void)scrollbarVariantChanged: (NSNotification*) pNotification
369{
370    (void)pNotification;
371    GetSalData()->mpInstance->delayedSettingsChanged( true );
372}
373
374-(void)scrollbarSettingsChanged: (NSNotification*) pNotification
375{
376    (void)pNotification;
377    GetSalData()->mpInstance->delayedSettingsChanged( false );
378}
379
380-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
381{
382    AquaSalMenu::addFallbackMenuItem( pNewItem );
383}
384
385-(void)removeFallbackMenuItem: (NSMenuItem*)pItem
386{
387    AquaSalMenu::removeFallbackMenuItem( pItem );
388}
389
390-(void)addDockMenuItem: (NSMenuItem*)pNewItem
391{
392    NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
393    [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
394}
395
396// for Apple Remote implementation
397
398#if !HAVE_FEATURE_MACOSX_SANDBOX
399- (void)applicationWillBecomeActive:(NSNotification *)pNotification
400{
401    (void)pNotification;
402    SalData* pSalData = GetSalData();
403    AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
404    if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
405    {
406        // [remoteControl startListening: self];
407        // does crash because the right thing to do is
408        // [pAppleRemoteCtrl->remoteControl startListening: self];
409        // but the instance variable 'remoteControl' is declared protected
410        // workaround : declare remoteControl instance variable as public in RemoteMainController.m
411
412        [pAppleRemoteCtrl->remoteControl startListening: self];
413#ifdef DEBUG
414        NSLog(@"Apple Remote will become active - Using remote controls");
415#endif
416    }
417    for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
418         it != pSalData->maPresentationFrames.end(); ++it )
419    {
420        NSWindow* pNSWindow = (*it)->getNSWindow();
421        [pNSWindow setLevel: NSPopUpMenuWindowLevel];
422        if( [pNSWindow isVisible] )
423            [pNSWindow orderFront: NSApp];
424    }
425}
426
427- (void)applicationWillResignActive:(NSNotification *)pNotification
428{
429    (void)pNotification;
430    SalData* pSalData = GetSalData();
431    AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
432    if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
433    {
434        // [remoteControl stopListening: self];
435        // does crash because the right thing to do is
436        // [pAppleRemoteCtrl->remoteControl stopListening: self];
437        // but the instance variable 'remoteControl' is declared protected
438        // workaround : declare remoteControl instance variable as public in RemoteMainController.m
439
440        [pAppleRemoteCtrl->remoteControl stopListening: self];
441#ifdef DEBUG
442        NSLog(@"Apple Remote will resign active - Releasing remote controls");
443#endif
444    }
445    for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
446         it != pSalData->maPresentationFrames.end(); ++it )
447    {
448        [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
449    }
450}
451#endif
452
453- (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
454{
455    (void)pApp;
456    (void)bWinVisible;
457    NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
458    if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
459    {
460        [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
461    }
462    return YES;
463}
464
465-(void)setDockIconClickHandler: (NSObject*)pHandler
466{
467    GetSalData()->mpDockIconClickHandler = pHandler;
468}
469
470
471@end
472
473/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
474