Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / vcl / osx / vclnsapp.mm
blob14ce7ec3417f3ef1966339b304fcfdf6a59beb80
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/implimagetree.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     NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
65                                location: NSZeroPoint
66                                modifierFlags: 0
67                                timestamp: 0
68                                windowNumber: 0
69                                context: nil
70                                subtype: AquaSalInstance::AppExecuteSVMain
71                                data1: 0
72                                data2: 0 ];
73     if( pEvent )
74         [NSApp postEvent: pEvent atStart: NO];
77 -(void)sendEvent:(NSEvent*)pEvent
79     NSEventType eType = [pEvent type];
80     if( eType == NSApplicationDefined )
81     {
82         AquaSalInstance::handleAppDefinedEvent( pEvent );
83     }
84     else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
85     {
86         NSWindow* pKeyWin = [NSApp keyWindow];
87         if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
88         {
89             AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame];
90             // handle Cmd-W
91             // FIXME: the correct solution would be to handle this in framework
92             // in the menu code
93             // however that is currently being revised, so let's use a preliminary solution here
94             // this hack is based on assumption
95             // a) Cmd-W is the same in all languages in OOo's menu conig
96             // b) Cmd-W is the same in all languages in on MacOS
97             // for now this seems to be true
98             unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
99             if( (pFrame->mnStyleMask & NSClosableWindowMask) != 0 )
100             {
101                 if( nModMask == NSCommandKeyMask
102                     && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] )
103                 {
104                     [(SalFrameWindow*)pFrame->getNSWindow() windowShouldClose: nil];
105                     return;
106                 }
107             }
109             /*
110              * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
111              */
112             if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
113             {
114                 if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
115                 {
116                     [pFrame->getNSWindow() performMiniaturize: nil];
117                     return;
118                 }
120                 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
121                 {
122                     [NSApp miniaturizeAll: nil];
123                     return;
124                 }
125             }
127             // #i90083# handle frame switching
128             // FIXME: lousy workaround
129             if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 )
130             {
131                 if( [[pEvent characters] isEqualToString: @"<"] ||
132                     [[pEvent characters] isEqualToString: @"~"] )
133                 {
134                     [self cycleFrameForward: pFrame];
135                     return;
136                 }
137                 else if( [[pEvent characters] isEqualToString: @">"] ||
138                          [[pEvent characters] isEqualToString: @"`"] )
139                 {
140                     [self cycleFrameBackward: pFrame];
141                     return;
142                 }
143             }
145             // get information whether the event was handled; keyDown returns nothing
146             GetSalData()->maKeyEventAnswer[ pEvent ] = false;
147             bool bHandled = false;
149             // dispatch to view directly to avoid the key event being consumed by the menubar
150             // popup windows do not get the focus, so they don't get these either
151             // simplest would be dispatch this to the key window always if it is without parent
152             // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
153             if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) )
154             {
155                 [[pKeyWin contentView] keyDown: pEvent];
156                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
157             }
159             // see whether the main menu consumes this event
160             // if not, we want to dispatch it ourselves. Unless we do this "trick"
161             // the main menu just beeps for an unknown or disabled key equivalent
162             // and swallows the event wholesale
163             NSMenu* pMainMenu = [NSApp mainMenu];
164             if( ! bHandled && (pMainMenu == nullptr || ! [pMainMenu performKeyEquivalent: pEvent]) )
165             {
166                 [[pKeyWin contentView] keyDown: pEvent];
167                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
168             }
169             else
170             {
171                 bHandled = true;  // event handled already or main menu just handled it
172             }
173             GetSalData()->maKeyEventAnswer.erase( pEvent );
175             if( bHandled )
176                 return;
177         }
178         else if( pKeyWin )
179         {
180             // #i94601# a window not of vcl's making has the focus.
181             // Since our menus do not invoke the usual commands
182             // try to play nice with native windows like the file dialog
183             // and emulate them
184             // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
185             // NOT localized, that is the same in all locales. Should this be
186             // different in any locale, this hack will fail.
187             unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
188             if( nModMask == NSCommandKeyMask )
189             {
191                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
192                 {
193                     if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
194                         return;
195                 }
196                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
197                 {
198                     if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
199                         return;
200                 }
201                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
202                 {
203                     if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
204                         return;
205                 }
206                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
207                 {
208                     if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
209                         return;
210                 }
211                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
212                 {
213                     if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
214                         return;
215                 }
216             }
217             else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) )
218             {
219                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
220                 {
221                     if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
222                         return;
223                 }
224             }
225         }
226     }
227     [super sendEvent: pEvent];
230 -(void)sendSuperEvent:(NSEvent*)pEvent
232     [super sendEvent: pEvent];
235 -(void)cycleFrameForward: (AquaSalFrame*)pCurFrame
237     // find current frame in list
238     std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
239     std::list< AquaSalFrame* >::iterator it = rFrames.begin();
240     for( ; it != rFrames.end() && *it != pCurFrame; ++it )
241         ;
242     if( it != rFrames.end() )
243     {
244         // now find the next frame (or end)
245         do
246         {
247             ++it;
248             if( it != rFrames.end() )
249             {
250                 if( (*it)->mpDockMenuEntry != nullptr &&
251                     (*it)->mbShown )
252                 {
253                     [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
254                     return;
255                 }
256             }
257         } while( it != rFrames.end() );
258         // cycle around, find the next up to pCurFrame
259         it = rFrames.begin();
260         while( *it != pCurFrame )
261         {
262             if( (*it)->mpDockMenuEntry != nullptr &&
263                 (*it)->mbShown )
264             {
265                 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
266                 return;
267             }
268             ++it;
269         }
270     }
273 -(void)cycleFrameBackward: (AquaSalFrame*)pCurFrame
275     // do the same as cycleFrameForward only with a reverse iterator
277     // find current frame in list
278     std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
279     std::list< AquaSalFrame* >::reverse_iterator it = rFrames.rbegin();
280     for( ; it != rFrames.rend() && *it != pCurFrame; ++it )
281         ;
282     if( it != rFrames.rend() )
283     {
284         // now find the next frame (or end)
285         do
286         {
287             ++it;
288             if( it != rFrames.rend() )
289             {
290                 if( (*it)->mpDockMenuEntry != nullptr &&
291                     (*it)->mbShown )
292                 {
293                     [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
294                     return;
295                 }
296             }
297         } while( it != rFrames.rend() );
298         // cycle around, find the next up to pCurFrame
299         it = rFrames.rbegin();
300         while( *it != pCurFrame )
301         {
302             if( (*it)->mpDockMenuEntry != nullptr &&
303                 (*it)->mbShown )
304             {
305                 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
306                 return;
307             }
308             ++it;
309         }
310     }
313 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
315     (void)sender;
316     return AquaSalInstance::GetDynamicDockMenu();
319 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
321     (void)app;
322     std::vector<OUString> aFile;
323     aFile.push_back( GetOUString( pFile ) );
324     if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
325     {
326         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_OPEN, aFile);
327         AquaSalInstance::aAppEventList.push_back( pAppEvent );
328     }
329     return YES;
332 -(void)application: (NSApplication*) app openFiles: (NSArray*)files
334     (void)app;
335     std::vector<OUString> aFileList;
337     NSEnumerator* it = [files objectEnumerator];
338     NSString* pFile = nil;
340     while( (pFile = [it nextObject]) != nil )
341     {
342         const rtl::OUString aFile( GetOUString( pFile ) );
343         if( ! AquaSalInstance::isOnCommandLine( aFile ) )
344         {
345             aFileList.push_back( aFile );
346         }
347     }
349     if( !aFileList.empty() )
350     {
351         // we have no back channel here, we have to assume success, in which case
352         // replyToOpenOrPrint does not need to be called according to documentation
353         // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
354         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_OPEN, aFileList);
355         AquaSalInstance::aAppEventList.push_back( pAppEvent );
356     }
359 -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
361     (void)app;
362     std::vector<OUString> aFile;
363     aFile.push_back( GetOUString( pFile ) );
364         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_PRINT, aFile);
365         AquaSalInstance::aAppEventList.push_back( pAppEvent );
366     return YES;
368 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
370     (void)app;
371     (void)printSettings;
372     (void)bShowPrintPanels;
373     // currently ignores print settings an bShowPrintPanels
374     std::vector<OUString> aFileList;
376     NSEnumerator* it = [files objectEnumerator];
377     NSString* pFile = nil;
379     while( (pFile = [it nextObject]) != nil )
380     {
381         aFileList.push_back( GetOUString( pFile ) );
382     }
383         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_PRINT, aFileList);
384         AquaSalInstance::aAppEventList.push_back( pAppEvent );
385     // we have no back channel here, we have to assume success
386     // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
387     return NSPrintingSuccess;
390 -(void)applicationWillTerminate: (NSNotification *) aNotification
392     (void)aNotification;
393     sal_detail_deinitialize();
396 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
398     (void)app;
399     NSApplicationTerminateReply aReply = NSTerminateNow;
400     {
401         SolarMutexGuard aGuard;
403         SalData* pSalData = GetSalData();
404         if( ! pSalData->maFrames.empty() )
405         {
406             // the following QueryExit will likely present a message box, activate application
407             [NSApp activateIgnoringOtherApps: YES];
408             aReply = pSalData->maFrames.front()->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow;
409         }
411         if( aReply == NSTerminateNow )
412         {
413             ApplicationEvent aEv(ApplicationEvent::TYPE_PRIVATE_DOSHUTDOWN);
414             GetpApp()->AppEvent( aEv );
415             ImplImageTree::get().shutDown();
416             // DeInitVCL should be called in ImplSVMain - unless someon _exits first which
417             // can occur in Desktop::doShutdown for example
418         }
419     }
421     return aReply;
424 -(void)systemColorsChanged: (NSNotification*) pNotification
426     (void)pNotification;
427     SolarMutexGuard aGuard;
429     const SalData* pSalData = GetSalData();
430         if( !pSalData->maFrames.empty() )
431                 pSalData->maFrames.front()->CallCallback( SalEvent::SettingsChanged, nullptr );
434 -(void)screenParametersChanged: (NSNotification*) pNotification
436     (void)pNotification;
437     SolarMutexGuard aGuard;
439     SalData* pSalData = GetSalData();
440     std::list< AquaSalFrame* >::iterator it;
441     for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it )
442     {
443         (*it)->screenParametersChanged();
444     }
447 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
449     (void)pNotification;
450     GetSalData()->mpFirstInstance->delayedSettingsChanged( true );
453 -(void)scrollbarSettingsChanged: (NSNotification*) pNotification
455     (void)pNotification;
456     GetSalData()->mpFirstInstance->delayedSettingsChanged( false );
459 -(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
461     AquaSalMenu::addFallbackMenuItem( pNewItem );
464 -(void)removeFallbackMenuItem: (NSMenuItem*)pItem
466     AquaSalMenu::removeFallbackMenuItem( pItem );
469 -(void)addDockMenuItem: (NSMenuItem*)pNewItem
471     NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
472     [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
475 // for Apple Remote implementation
477 #if !HAVE_FEATURE_MACOSX_SANDBOX
478 - (void)applicationWillBecomeActive:(NSNotification *)pNotification
480     (void)pNotification;
481     SalData* pSalData = GetSalData();
482     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
483     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
484     {
485         // [remoteControl startListening: self];
486         // does crash because the right thing to do is
487         // [pAppleRemoteCtrl->remoteControl startListening: self];
488         // but the instance variable 'remoteControl' is declared protected
489         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
491         [pAppleRemoteCtrl->remoteControl startListening: self];
492 #ifdef DEBUG
493         NSLog(@"Apple Remote will become active - Using remote controls");
494 #endif
495     }
496     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
497          it != pSalData->maPresentationFrames.end(); ++it )
498     {
499         NSWindow* pNSWindow = (*it)->getNSWindow();
500         [pNSWindow setLevel: NSPopUpMenuWindowLevel];
501         if( [pNSWindow isVisible] )
502             [pNSWindow orderFront: NSApp];
503     }
506 - (void)applicationWillResignActive:(NSNotification *)pNotification
508     (void)pNotification;
509     SalData* pSalData = GetSalData();
510     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
511     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
512     {
513         // [remoteControl stopListening: self];
514         // does crash because the right thing to do is
515         // [pAppleRemoteCtrl->remoteControl stopListening: self];
516         // but the instance variable 'remoteControl' is declared protected
517         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
519         [pAppleRemoteCtrl->remoteControl stopListening: self];
520 #ifdef DEBUG
521         NSLog(@"Apple Remote will resign active - Releasing remote controls");
522 #endif
523     }
524     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
525          it != pSalData->maPresentationFrames.end(); ++it )
526     {
527         [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
528     }
530 #endif
532 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
534     (void)pApp;
535     (void)bWinVisible;
536     NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
537     if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
538     {
539         [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
540     }
541     return YES;
544 -(void)setDockIconClickHandler: (NSObject*)pHandler
546     GetSalData()->mpDockIconClickHandler = pHandler;
550 @end
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */