bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / osx / vclnsapp.mm
blob5daf923ce105a0e77104be46e9a70a171c906f65
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     NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
67                                location: NSZeroPoint
68                                modifierFlags: 0
69                                timestamp: [[NSProcessInfo processInfo] systemUptime]
70                                windowNumber: 0
71                                context: nil
72                                subtype: AquaSalInstance::AppExecuteSVMain
73                                data1: 0
74                                data2: 0 ];
75     assert( pEvent );
76     [NSApp postEvent: pEvent atStart: NO];
78     if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
79     {
80         [NSWindow setAllowsAutomaticWindowTabbing:NO];
81     }
83     // listen to dark mode change
84     [NSApp addObserver:self forKeyPath:@"effectiveAppearance" options: 0 context: nil];
87 -(void)sendEvent:(NSEvent*)pEvent
89     NSEventType eType = [pEvent type];
90     if( eType == NSEventTypeApplicationDefined )
91     {
92         AquaSalInstance::handleAppDefinedEvent( pEvent );
93     }
94     else if( eType == NSEventTypeKeyDown && ([pEvent modifierFlags] & NSEventModifierFlagCommand) != 0 )
95     {
96         NSWindow* pKeyWin = [NSApp keyWindow];
97         if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
98         {
99             // Commit uncommitted text before dispatching key shortcuts. In
100             // certain cases such as pressing Command-Option-C in a Writer
101             // document while there is uncommitted text will call
102             // AquaSalFrame::EndExtTextInput() which will dispatch a
103             // SalEvent::EndExtTextInput event. Writer's handler for that event
104             // will delete the uncommitted text and then insert the committed
105             // text but LibreOffice will crash when deleting the uncommitted
106             // text because deletion of the text also removes and deletes the
107             // newly inserted comment.
108             [static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput];
110             AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame];
111             unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
112             /*
113              * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
114              */
115             if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
116             {
117                 if ( nModMask == NSEventModifierFlagCommand && ([pFrame->getNSWindow() styleMask] & NSWindowStyleMaskMiniaturizable) )
118                 {
119                     [pFrame->getNSWindow() performMiniaturize: nil];
120                     return;
121                 }
123                 if ( nModMask == ( NSEventModifierFlagCommand | NSEventModifierFlagOption ) )
124                 {
125                     [NSApp miniaturizeAll: nil];
126                     return;
127                 }
128             }
130             // get information whether the event was handled; keyDown returns nothing
131             GetSalData()->maKeyEventAnswer[ pEvent ] = false;
132             bool bHandled = false;
134             // dispatch to view directly to avoid the key event being consumed by the menubar
135             // popup windows do not get the focus, so they don't get these either
136             // simplest would be dispatch this to the key window always if it is without parent
137             // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
138             if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) )
139             {
140                 [[pKeyWin contentView] keyDown: pEvent];
141                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
142             }
144             // see whether the main menu consumes this event
145             // if not, we want to dispatch it ourselves. Unless we do this "trick"
146             // the main menu just beeps for an unknown or disabled key equivalent
147             // and swallows the event wholesale
148             NSMenu* pMainMenu = [NSApp mainMenu];
149             if( ! bHandled &&
150                 (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) )
151             {
152                 [[pKeyWin contentView] keyDown: pEvent];
153                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
154             }
155             else
156             {
157                 bHandled = true;  // event handled already or main menu just handled it
158             }
159             GetSalData()->maKeyEventAnswer.erase( pEvent );
161             if( bHandled )
162                 return;
163         }
164         else if( pKeyWin )
165         {
166             // #i94601# a window not of vcl's making has the focus.
167             // Since our menus do not invoke the usual commands
168             // try to play nice with native windows like the file dialog
169             // and emulate them
170             // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
171             // NOT localized, that is the same in all locales. Should this be
172             // different in any locale, this hack will fail.
173             unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
174             if( nModMask == NSEventModifierFlagCommand )
175             {
177                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
178                 {
179                     if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
180                         return;
181                 }
182                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
183                 {
184                     if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
185                         return;
186                 }
187                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
188                 {
189                     if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
190                         return;
191                 }
192                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
193                 {
194                     if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
195                         return;
196                 }
197                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
198                 {
199                     if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
200                         return;
201                 }
202             }
203             else if( nModMask == (NSEventModifierFlagCommand|NSEventModifierFlagShift) )
204             {
205                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
206                 {
207                     if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
208                         return;
209                 }
210             }
211         }
212     }
213     [super sendEvent: pEvent];
216 -(void)sendSuperEvent:(NSEvent*)pEvent
218     [super sendEvent: pEvent];
221 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
223     (void)sender;
224     return AquaSalInstance::GetDynamicDockMenu();
227 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
229     (void)app;
230     std::vector<OUString> aFile { GetOUString( pFile ) };
231     if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
232     {
233         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(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, std::move(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 { GetOUString(pFile) };
276     const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFile));
277     AquaSalInstance::aAppEventList.push_back( pAppEvent );
278     AquaSalInstance *pInst = GetSalData()->mpInstance;
279     if( pInst )
280         pInst->TriggerUserEventProcessing();
281     return YES;
283 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
285     (void)app;
286     (void)printSettings;
287     (void)bShowPrintPanels;
288     // currently ignores print settings a bShowPrintPanels
289     std::vector<OUString> aFileList;
291     NSEnumerator* it = [files objectEnumerator];
292     NSString* pFile = nil;
294     while( (pFile = [it nextObject]) != nil )
295     {
296         aFileList.push_back( GetOUString( pFile ) );
297     }
298     const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFileList));
299     AquaSalInstance::aAppEventList.push_back( pAppEvent );
300     AquaSalInstance *pInst = GetSalData()->mpInstance;
301     if( pInst )
302         pInst->TriggerUserEventProcessing();
303     // we have no back channel here, we have to assume success
304     // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
305     return NSPrintingSuccess;
308 -(void)applicationWillTerminate: (NSNotification *) aNotification
310     (void)aNotification;
311     sal_detail_deinitialize();
312     _Exit(0);
315 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
317     (void)app;
318     NSApplicationTerminateReply aReply = NSTerminateNow;
319     {
320         SolarMutexGuard aGuard;
322         AquaSalInstance *pInst = GetSalData()->mpInstance;
323         SalFrame *pAnyFrame = pInst->anyFrame();
324         if( pAnyFrame )
325         {
326             // the following QueryExit will likely present a message box, activate application
327             [NSApp activateIgnoringOtherApps: YES];
328             aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow;
329         }
331         if( aReply == NSTerminateNow )
332         {
333             ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown);
334             GetpApp()->AppEvent( aEv );
335             ImageTree::get().shutdown();
336             // DeInitVCL should be called in ImplSVMain - unless someone exits first which
337             // can occur in Desktop::doShutdown for example
338         }
339     }
341     return aReply;
344 -(void)observeValueForKeyPath: (NSString*) keyPath ofObject:(id)object
345                                change: (NSDictionary<NSKeyValueChangeKey, id>*)change
346                                context: (void*)context
348     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
349     if ([keyPath isEqualToString:@"effectiveAppearance"])
350         [self systemColorsChanged: nil];
353 -(void)systemColorsChanged: (NSNotification*) pNotification
355     (void)pNotification;
356     SolarMutexGuard aGuard;
358     AquaSalInstance *pInst = GetSalData()->mpInstance;
359     SalFrame *pAnyFrame = pInst->anyFrame();
360     if(  pAnyFrame )
361         pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
364 -(void)screenParametersChanged: (NSNotification*) pNotification
366     (void)pNotification;
367     SolarMutexGuard aGuard;
369     for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
370     {
371         AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame );
372         pFrame->screenParametersChanged();
373     }
376 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
378     (void)pNotification;
379     GetSalData()->mpInstance->delayedSettingsChanged( true );
382 -(void)scrollbarSettingsChanged: (NSNotification*) pNotification
384     (void)pNotification;
385     GetSalData()->mpInstance->delayedSettingsChanged( false );
388 -(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
390     AquaSalMenu::addFallbackMenuItem( pNewItem );
393 -(void)removeFallbackMenuItem: (NSMenuItem*)pItem
395     AquaSalMenu::removeFallbackMenuItem( pItem );
398 -(void)addDockMenuItem: (NSMenuItem*)pNewItem
400     NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
401     [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
404 // for Apple Remote implementation
406 #if !HAVE_FEATURE_MACOSX_SANDBOX
407 - (void)applicationWillBecomeActive:(NSNotification *)pNotification
409     (void)pNotification;
410     SalData* pSalData = GetSalData();
411     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
412     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
413     {
414         // [remoteControl startListening: self];
415         // does crash because the right thing to do is
416         // [pAppleRemoteCtrl->remoteControl startListening: self];
417         // but the instance variable 'remoteControl' is declared protected
418         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
420         [pAppleRemoteCtrl->remoteControl startListening: self];
421 #ifdef DEBUG
422         NSLog(@"Apple Remote will become active - Using remote controls");
423 #endif
424     }
425     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
426          it != pSalData->maPresentationFrames.end(); ++it )
427     {
428         NSWindow* pNSWindow = (*it)->getNSWindow();
429         [pNSWindow setLevel: NSPopUpMenuWindowLevel];
430         if( [pNSWindow isVisible] )
431             [pNSWindow orderFront: NSApp];
432     }
435 - (void)applicationWillResignActive:(NSNotification *)pNotification
437     (void)pNotification;
438     SalData* pSalData = GetSalData();
439     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
440     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
441     {
442         // [remoteControl stopListening: self];
443         // does crash because the right thing to do is
444         // [pAppleRemoteCtrl->remoteControl stopListening: self];
445         // but the instance variable 'remoteControl' is declared protected
446         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
448         [pAppleRemoteCtrl->remoteControl stopListening: self];
449 #ifdef DEBUG
450         NSLog(@"Apple Remote will resign active - Releasing remote controls");
451 #endif
452     }
453     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
454          it != pSalData->maPresentationFrames.end(); ++it )
455     {
456         [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
457     }
459 #endif
461 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
463     (void)pApp;
464     (void)bWinVisible;
465     NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
466     if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
467     {
468         [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
469     }
470     return YES;
473 -(void)setDockIconClickHandler: (NSObject*)pHandler
475     GetSalData()->mpDockIconClickHandler = pHandler;
479 @end
481 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */