Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / osx / vclnsapp.mm
blob53d002a8aa466d60d8cb6b6d7740c513c38d2789
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  */
20 #include <sal/config.h>
21 #include <config_features.h>
23 #include <vector>
25 #include <stdlib.h>
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>
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>
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>
47 @implementation CocoaThreadEnabler
48 -(void)enableCocoaThreads:(id)param
50     // do nothing, this is just to start an NSThread and therefore put
51     // Cocoa into multithread mode
52     (void)param;
54 @end
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.
60 @implementation VCL_NSApplication
62 -(void)applicationDidFinishLaunching:(NSNotification*)pNotification
64     (void)pNotification;
66 SAL_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 ];
77 SAL_WNODEPRECATED_DECLARATIONS_POP
78     assert( pEvent );
79     [NSApp postEvent: pEvent atStart: NO];
81     if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
82     {
83         [NSWindow setAllowsAutomaticWindowTabbing:NO];
84     }
87 -(void)sendEvent:(NSEvent*)pEvent
89     NSEventType eType = [pEvent type];
90 SAL_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                 }
121                 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
122                 {
123                     [NSApp miniaturizeAll: nil];
124                     return;
125                 }
126             }
128             // get information whether the event was handled; keyDown returns nothing
129             GetSalData()->maKeyEventAnswer[ pEvent ] = false;
130             bool bHandled = false;
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             }
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 );
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             {
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     }
211 SAL_WNODEPRECATED_DECLARATIONS_POP
212     [super sendEvent: pEvent];
215 -(void)sendSuperEvent:(NSEvent*)pEvent
217     [super sendEvent: pEvent];
220 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
222     (void)sender;
223     return AquaSalInstance::GetDynamicDockMenu();
226 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
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;
242 -(void)application: (NSApplication*) app openFiles: (NSArray*)files
244     (void)app;
245     std::vector<OUString> aFileList;
247     NSEnumerator* it = [files objectEnumerator];
248     NSString* pFile = nil;
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     }
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     }
272 -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
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;
284 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
286     (void)app;
287     (void)printSettings;
288     (void)bShowPrintPanels;
289     // currently ignores print settings a bShowPrintPanels
290     std::vector<OUString> aFileList;
292     NSEnumerator* it = [files objectEnumerator];
293     NSString* pFile = nil;
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;
309 -(void)applicationWillTerminate: (NSNotification *) aNotification
311     (void)aNotification;
312     sal_detail_deinitialize();
313     _Exit(0);
316 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
318     (void)app;
319     NSApplicationTerminateReply aReply = NSTerminateNow;
320     {
321         SolarMutexGuard aGuard;
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         }
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     }
342     return aReply;
345 -(void)systemColorsChanged: (NSNotification*) pNotification
347     (void)pNotification;
348     SolarMutexGuard aGuard;
350     AquaSalInstance *pInst = GetSalData()->mpInstance;
351     SalFrame *pAnyFrame = pInst->anyFrame();
352     if(  pAnyFrame )
353         pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
356 -(void)screenParametersChanged: (NSNotification*) pNotification
358     (void)pNotification;
359     SolarMutexGuard aGuard;
361     for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
362     {
363         AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame );
364         pFrame->screenParametersChanged();
365     }
368 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
370     (void)pNotification;
371     GetSalData()->mpInstance->delayedSettingsChanged( true );
374 -(void)scrollbarSettingsChanged: (NSNotification*) pNotification
376     (void)pNotification;
377     GetSalData()->mpInstance->delayedSettingsChanged( false );
380 -(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
382     AquaSalMenu::addFallbackMenuItem( pNewItem );
385 -(void)removeFallbackMenuItem: (NSMenuItem*)pItem
387     AquaSalMenu::removeFallbackMenuItem( pItem );
390 -(void)addDockMenuItem: (NSMenuItem*)pNewItem
392     NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
393     [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
396 // for Apple Remote implementation
398 #if !HAVE_FEATURE_MACOSX_SANDBOX
399 - (void)applicationWillBecomeActive:(NSNotification *)pNotification
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
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     }
427 - (void)applicationWillResignActive:(NSNotification *)pNotification
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
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     }
451 #endif
453 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
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;
465 -(void)setDockIconClickHandler: (NSObject*)pHandler
467     GetSalData()->mpDockIconClickHandler = pHandler;
471 @end
473 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */