Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / osx / vclnsapp.mm
blob4c38466961b07fc8ac5a1695f7250e249cfd5dc9
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 <sal/main.h>
26 #include <vcl/commandevent.hxx>
27 #include <vcl/ImageTree.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/window.hxx>
31 #include "osx/saldata.hxx"
32 #include "osx/salframe.h"
33 #include "osx/salframeview.h"
34 #include "osx/salinst.h"
35 #include "osx/vclnsapp.h"
36 #include "quartz/utils.h"
38 #include "premac.h"
39 #include <objc/objc-runtime.h>
40 #import "Carbon/Carbon.h"
41 #import "apple_remote/RemoteControl.h"
42 #include "postmac.h"
45 @implementation CocoaThreadEnabler
46 -(void)enableCocoaThreads:(id)param
48     // do nothing, this is just to start an NSThread and therefore put
49     // Cocoa into multithread mode
50     (void)param;
52 @end
54 // If you wonder how this VCL_NSApplication stuff works, one thing you
55 // might have missed is that the NSPrincipalClass property in
56 // desktop/macosx/Info.plist has the value VCL_NSApplication.
58 @implementation VCL_NSApplication
60 -(void)applicationDidFinishLaunching:(NSNotification*)pNotification
62     (void)pNotification;
64 SAL_WNODEPRECATED_DECLARATIONS_PUSH
65         // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
66     NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
67                                location: NSZeroPoint
68                                modifierFlags: 0
69                                timestamp: 0
70                                windowNumber: 0
71                                context: nil
72                                subtype: AquaSalInstance::AppExecuteSVMain
73                                data1: 0
74                                data2: 0 ];
75 SAL_WNODEPRECATED_DECLARATIONS_POP
76     if( pEvent )
77         [NSApp postEvent: pEvent atStart: NO];
80 -(void)sendEvent:(NSEvent*)pEvent
82     NSEventType eType = [pEvent type];
83 SAL_WNODEPRECATED_DECLARATIONS_PUSH
84         // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12
85         // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
86         // 'NSClosableWindowMask' is deprecated: first deprecated in macOS 10.12
87         // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12
88         // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12
89         // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12
90         // 'NSMiniaturizableWindowMask' is deprecated: first deprecated in macOS 10.12
91         // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12
92     if( eType == NSApplicationDefined )
93     {
94         AquaSalInstance::handleAppDefinedEvent( pEvent );
95     }
96     else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
97     {
98         NSWindow* pKeyWin = [NSApp keyWindow];
99         if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
100         {
101             AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame];
102             // handle Cmd-W
103             // FIXME: the correct solution would be to handle this in framework
104             // in the menu code
105             // however that is currently being revised, so let's use a preliminary solution here
106             // this hack is based on assumption
107             // a) Cmd-W is the same in all languages in OOo's menu conig
108             // b) Cmd-W is the same in all languages in on MacOS
109             // for now this seems to be true
110             unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
111             if( (pFrame->mnStyleMask & NSClosableWindowMask) != 0 )
112             {
113                 if( nModMask == NSCommandKeyMask
114                     && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] )
115                 {
116                     [(SalFrameWindow*)pFrame->getNSWindow() windowShouldClose: nil];
117                     return;
118                 }
119             }
121             /*
122              * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
123              */
124             if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
125             {
126                 if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
127                 {
128                     [pFrame->getNSWindow() performMiniaturize: nil];
129                     return;
130                 }
132                 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
133                 {
134                     [NSApp miniaturizeAll: nil];
135                     return;
136                 }
137             }
139             // #i90083# handle frame switching
140             // FIXME: lousy workaround
141             if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 )
142             {
143                 if( [[pEvent characters] isEqualToString: @"<"] ||
144                     [[pEvent characters] isEqualToString: @"~"] )
145                 {
146                     [self cycleFrameForward: pFrame];
147                     return;
148                 }
149                 else if( [[pEvent characters] isEqualToString: @">"] ||
150                          [[pEvent characters] isEqualToString: @"`"] )
151                 {
152                     [self cycleFrameBackward: pFrame];
153                     return;
154                 }
155             }
157             // get information whether the event was handled; keyDown returns nothing
158             GetSalData()->maKeyEventAnswer[ pEvent ] = false;
159             bool bHandled = false;
161             // dispatch to view directly to avoid the key event being consumed by the menubar
162             // popup windows do not get the focus, so they don't get these either
163             // simplest would be dispatch this to the key window always if it is without parent
164             // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
165             if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) )
166             {
167                 [[pKeyWin contentView] keyDown: pEvent];
168                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
169             }
171             // see whether the main menu consumes this event
172             // if not, we want to dispatch it ourselves. Unless we do this "trick"
173             // the main menu just beeps for an unknown or disabled key equivalent
174             // and swallows the event wholesale
175             NSMenu* pMainMenu = [NSApp mainMenu];
176             if( ! bHandled && (pMainMenu == nullptr || ! [pMainMenu performKeyEquivalent: pEvent]) )
177             {
178                 [[pKeyWin contentView] keyDown: pEvent];
179                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
180             }
181             else
182             {
183                 bHandled = true;  // event handled already or main menu just handled it
184             }
185             GetSalData()->maKeyEventAnswer.erase( pEvent );
187             if( bHandled )
188                 return;
189         }
190         else if( pKeyWin )
191         {
192             // #i94601# a window not of vcl's making has the focus.
193             // Since our menus do not invoke the usual commands
194             // try to play nice with native windows like the file dialog
195             // and emulate them
196             // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
197             // NOT localized, that is the same in all locales. Should this be
198             // different in any locale, this hack will fail.
199             unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
200             if( nModMask == NSCommandKeyMask )
201             {
203                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
204                 {
205                     if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
206                         return;
207                 }
208                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
209                 {
210                     if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
211                         return;
212                 }
213                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
214                 {
215                     if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
216                         return;
217                 }
218                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
219                 {
220                     if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
221                         return;
222                 }
223                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
224                 {
225                     if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
226                         return;
227                 }
228             }
229             else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) )
230             {
231                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
232                 {
233                     if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
234                         return;
235                 }
236             }
237         }
238     }
239 SAL_WNODEPRECATED_DECLARATIONS_POP
240     [super sendEvent: pEvent];
243 -(void)sendSuperEvent:(NSEvent*)pEvent
245     [super sendEvent: pEvent];
248 -(void)cycleFrameForward: (AquaSalFrame*)pCurFrame
250     // find current frame in list
251     std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
252     std::list< AquaSalFrame* >::iterator it = rFrames.begin();
253     for( ; it != rFrames.end() && *it != pCurFrame; ++it )
254         ;
255     if( it != rFrames.end() )
256     {
257         // now find the next frame (or end)
258         do
259         {
260             ++it;
261             if( it != rFrames.end() )
262             {
263                 if( (*it)->mpDockMenuEntry != nullptr &&
264                     (*it)->mbShown )
265                 {
266                     [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
267                     return;
268                 }
269             }
270         } while( it != rFrames.end() );
271         // cycle around, find the next up to pCurFrame
272         it = rFrames.begin();
273         while( *it != pCurFrame )
274         {
275             if( (*it)->mpDockMenuEntry != nullptr &&
276                 (*it)->mbShown )
277             {
278                 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
279                 return;
280             }
281             ++it;
282         }
283     }
286 -(void)cycleFrameBackward: (AquaSalFrame*)pCurFrame
288     // do the same as cycleFrameForward only with a reverse iterator
290     // find current frame in list
291     std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
292     std::list< AquaSalFrame* >::reverse_iterator it = rFrames.rbegin();
293     for( ; it != rFrames.rend() && *it != pCurFrame; ++it )
294         ;
295     if( it != rFrames.rend() )
296     {
297         // now find the next frame (or end)
298         do
299         {
300             ++it;
301             if( it != rFrames.rend() )
302             {
303                 if( (*it)->mpDockMenuEntry != nullptr &&
304                     (*it)->mbShown )
305                 {
306                     [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
307                     return;
308                 }
309             }
310         } while( it != rFrames.rend() );
311         // cycle around, find the next up to pCurFrame
312         it = rFrames.rbegin();
313         while( *it != pCurFrame )
314         {
315             if( (*it)->mpDockMenuEntry != nullptr &&
316                 (*it)->mbShown )
317             {
318                 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
319                 return;
320             }
321             ++it;
322         }
323     }
326 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
328     (void)sender;
329     return AquaSalInstance::GetDynamicDockMenu();
332 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
334     (void)app;
335     std::vector<OUString> aFile;
336     aFile.push_back( GetOUString( pFile ) );
337     if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
338     {
339         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFile);
340         AquaSalInstance::aAppEventList.push_back( pAppEvent );
341     }
342     return YES;
345 -(void)application: (NSApplication*) app openFiles: (NSArray*)files
347     (void)app;
348     std::vector<OUString> aFileList;
350     NSEnumerator* it = [files objectEnumerator];
351     NSString* pFile = nil;
353     while( (pFile = [it nextObject]) != nil )
354     {
355         const rtl::OUString aFile( GetOUString( pFile ) );
356         if( ! AquaSalInstance::isOnCommandLine( aFile ) )
357         {
358             aFileList.push_back( aFile );
359         }
360     }
362     if( !aFileList.empty() )
363     {
364         // we have no back channel here, we have to assume success, in which case
365         // replyToOpenOrPrint does not need to be called according to documentation
366         // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
367         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFileList);
368         AquaSalInstance::aAppEventList.push_back( pAppEvent );
369     }
372 -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
374     (void)app;
375     std::vector<OUString> aFile;
376     aFile.push_back( GetOUString( pFile ) );
377     const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFile);
378     AquaSalInstance::aAppEventList.push_back( pAppEvent );
379     return YES;
381 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
383     (void)app;
384     (void)printSettings;
385     (void)bShowPrintPanels;
386     // currently ignores print settings an bShowPrintPanels
387     std::vector<OUString> aFileList;
389     NSEnumerator* it = [files objectEnumerator];
390     NSString* pFile = nil;
392     while( (pFile = [it nextObject]) != nil )
393     {
394         aFileList.push_back( GetOUString( pFile ) );
395     }
396     const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFileList);
397     AquaSalInstance::aAppEventList.push_back( pAppEvent );
398     // we have no back channel here, we have to assume success
399     // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
400     return NSPrintingSuccess;
403 -(void)applicationWillTerminate: (NSNotification *) aNotification
405     (void)aNotification;
406     sal_detail_deinitialize();
409 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
411     (void)app;
412     NSApplicationTerminateReply aReply = NSTerminateNow;
413     {
414         SolarMutexGuard aGuard;
416         SalData* pSalData = GetSalData();
417         if( ! pSalData->maFrames.empty() )
418         {
419             // the following QueryExit will likely present a message box, activate application
420             [NSApp activateIgnoringOtherApps: YES];
421             aReply = pSalData->maFrames.front()->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow;
422         }
424         if( aReply == NSTerminateNow )
425         {
426             ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown);
427             GetpApp()->AppEvent( aEv );
428             ImageTree::get().shutdown();
429             // DeInitVCL should be called in ImplSVMain - unless someon _exits first which
430             // can occur in Desktop::doShutdown for example
431         }
432     }
434     return aReply;
437 -(void)systemColorsChanged: (NSNotification*) pNotification
439     (void)pNotification;
440     SolarMutexGuard aGuard;
442     const SalData* pSalData = GetSalData();
443     if( !pSalData->maFrames.empty() )
444         pSalData->maFrames.front()->CallCallback( SalEvent::SettingsChanged, nullptr );
447 -(void)screenParametersChanged: (NSNotification*) pNotification
449     (void)pNotification;
450     SolarMutexGuard aGuard;
452     SalData* pSalData = GetSalData();
453     std::list< AquaSalFrame* >::iterator it;
454     for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it )
455     {
456         (*it)->screenParametersChanged();
457     }
460 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
462     (void)pNotification;
463     GetSalData()->mpFirstInstance->delayedSettingsChanged( true );
466 -(void)scrollbarSettingsChanged: (NSNotification*) pNotification
468     (void)pNotification;
469     GetSalData()->mpFirstInstance->delayedSettingsChanged( false );
472 -(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
474     AquaSalMenu::addFallbackMenuItem( pNewItem );
477 -(void)removeFallbackMenuItem: (NSMenuItem*)pItem
479     AquaSalMenu::removeFallbackMenuItem( pItem );
482 -(void)addDockMenuItem: (NSMenuItem*)pNewItem
484     NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
485     [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
488 // for Apple Remote implementation
490 #if !HAVE_FEATURE_MACOSX_SANDBOX
491 - (void)applicationWillBecomeActive:(NSNotification *)pNotification
493     (void)pNotification;
494     SalData* pSalData = GetSalData();
495     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
496     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
497     {
498         // [remoteControl startListening: self];
499         // does crash because the right thing to do is
500         // [pAppleRemoteCtrl->remoteControl startListening: self];
501         // but the instance variable 'remoteControl' is declared protected
502         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
504         [pAppleRemoteCtrl->remoteControl startListening: self];
505 #ifdef DEBUG
506         NSLog(@"Apple Remote will become active - Using remote controls");
507 #endif
508     }
509     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
510          it != pSalData->maPresentationFrames.end(); ++it )
511     {
512         NSWindow* pNSWindow = (*it)->getNSWindow();
513         [pNSWindow setLevel: NSPopUpMenuWindowLevel];
514         if( [pNSWindow isVisible] )
515             [pNSWindow orderFront: NSApp];
516     }
519 - (void)applicationWillResignActive:(NSNotification *)pNotification
521     (void)pNotification;
522     SalData* pSalData = GetSalData();
523     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
524     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
525     {
526         // [remoteControl stopListening: self];
527         // does crash because the right thing to do is
528         // [pAppleRemoteCtrl->remoteControl stopListening: self];
529         // but the instance variable 'remoteControl' is declared protected
530         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
532         [pAppleRemoteCtrl->remoteControl stopListening: self];
533 #ifdef DEBUG
534         NSLog(@"Apple Remote will resign active - Releasing remote controls");
535 #endif
536     }
537     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
538          it != pSalData->maPresentationFrames.end(); ++it )
539     {
540         [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
541     }
543 #endif
545 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
547     (void)pApp;
548     (void)bWinVisible;
549     NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
550     if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
551     {
552         [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
553     }
554     return YES;
557 -(void)setDockIconClickHandler: (NSObject*)pHandler
559     GetSalData()->mpDockIconClickHandler = pHandler;
563 @end
565 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */