Branch libreoffice-5-0-4
[LibreOffice.git] / vcl / osx / vclnsapp.mm
blob6eda13bfdc68a0649736ef9e6b044f43efa68365
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 <config_features.h>
22 #include "sal/config.h"
23 #include "sal/main.h"
24 #include <vector>
26 #include <vcl/implimagetree.hxx>
27 #include "vcl/window.hxx"
28 #include "vcl/svapp.hxx"
29 #include "vcl/cmdevt.hxx"
31 #include "osx/vclnsapp.h"
32 #include "osx/salinst.h"
33 #include "osx/saldata.hxx"
34 #include "osx/salframe.h"
35 #include "osx/salframeview.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         AquaSalInstance::handleAppDefinedEvent( pEvent );
82     else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
83     {
84         NSWindow* pKeyWin = [NSApp keyWindow];
85         if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
86         {
87             AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame];
88             // handle Cmd-W
89             // FIXME: the correct solution would be to handle this in framework
90             // in the menu code
91             // however that is currently being revised, so let's use a preliminary solution here
92             // this hack is based on assumption
93             // a) Cmd-W is the same in all languages in OOo's menu conig
94             // b) Cmd-W is the same in all languages in on MacOS
95             // for now this seems to be true
96             unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
97             if( (pFrame->mnStyleMask & NSClosableWindowMask) != 0 )
98             {
99                 if( nModMask == NSCommandKeyMask
100                     && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] )
101                 {
102                     [(SalFrameWindow*)pFrame->getNSWindow() windowShouldClose: nil];
103                     return;
104                 }
105             }
107             /*
108              * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
109              */
110             if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
111             {
112                 if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
113                 {
114                     [pFrame->getNSWindow() performMiniaturize: nil];
115                     return;
116                 }
118                 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
119                 {
120                     [NSApp miniaturizeAll: nil];
121                     return;
122                 }
123             }
125             // #i90083# handle frame switching
126             // FIXME: lousy workaround
127             if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 )
128             {
129                 if( [[pEvent characters] isEqualToString: @"<"] ||
130                     [[pEvent characters] isEqualToString: @"~"] )
131                 {
132                     [self cycleFrameForward: pFrame];
133                     return;
134                 }
135                 else if( [[pEvent characters] isEqualToString: @">"] ||
136                          [[pEvent characters] isEqualToString: @"`"] )
137                 {
138                     [self cycleFrameBackward: pFrame];
139                     return;
140                 }
141             }
143             // get information whether the event was handled; keyDown returns nothing
144             GetSalData()->maKeyEventAnswer[ pEvent ] = false;
145             bool bHandled = false;
147             // dispatch to view directly to avoid the key event being consumed by the menubar
148             // popup windows do not get the focus, so they don't get these either
149             // simplest would be dispatch this to the key window always if it is without parent
150             // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
151             if( pFrame->mpParent && (pFrame->mnStyle & SAL_FRAME_STYLE_FLOAT) == 0 )
152             {
153                 [[pKeyWin contentView] keyDown: pEvent];
154                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
155             }
157             // see whether the main menu consumes this event
158             // if not, we want to dispatch it ourselves. Unless we do this "trick"
159             // the main menu just beeps for an unknown or disabled key equivalent
160             // and swallows the event wholesale
161             NSMenu* pMainMenu = [NSApp mainMenu];
162             if( ! bHandled && (pMainMenu == 0 || ! [pMainMenu performKeyEquivalent: pEvent]) )
163             {
164                 [[pKeyWin contentView] keyDown: pEvent];
165                 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
166             }
167             else
168                 bHandled = true;  // event handled already or main menu just handled it
170             GetSalData()->maKeyEventAnswer.erase( pEvent );
171             if( bHandled )
172                 return;
173         }
174         else if( pKeyWin )
175         {
176             // #i94601# a window not of vcl's making has the focus.
177             // Since our menus do not invoke the usual commands
178             // try to play nice with native windows like the file dialog
179             // and emulate them
180             // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
181             // NOT localized, that is the same in all locales. Should this be
182             // different in any locale, this hack will fail.
183             unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
184             if( nModMask == NSCommandKeyMask )
185             {
187                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
188                 {
189                     if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
190                         return;
191                 }
192                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
193                 {
194                     if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
195                         return;
196                 }
197                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
198                 {
199                     if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
200                         return;
201                 }
202                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
203                 {
204                     if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
205                         return;
206                 }
207                 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
208                 {
209                     if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
210                         return;
211                 }
212             }
213             else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) )
214             {
215                 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
216                 {
217                     if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
218                         return;
219                 }
220             }
221         }
222     }
223     [super sendEvent: pEvent];
226 -(void)sendSuperEvent:(NSEvent*)pEvent
228     [super sendEvent: pEvent];
231 -(void)cycleFrameForward: (AquaSalFrame*)pCurFrame
233     // find current frame in list
234     std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
235     std::list< AquaSalFrame* >::iterator it = rFrames.begin();
236     for( ; it != rFrames.end() && *it != pCurFrame; ++it )
237         ;
238     if( it != rFrames.end() )
239     {
240         // now find the next frame (or end)
241         do
242         {
243             ++it;
244             if( it != rFrames.end() )
245             {
246                 if( (*it)->mpDockMenuEntry != NULL &&
247                     (*it)->mbShown )
248                 {
249                     [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
250                     return;
251                 }
252             }
253         } while( it != rFrames.end() );
254         // cycle around, find the next up to pCurFrame
255         it = rFrames.begin();
256         while( *it != pCurFrame )
257         {
258             if( (*it)->mpDockMenuEntry != NULL &&
259                 (*it)->mbShown )
260             {
261                 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
262                 return;
263             }
264             ++it;
265         }
266     }
269 -(void)cycleFrameBackward: (AquaSalFrame*)pCurFrame
271     // do the same as cycleFrameForward only with a reverse iterator
273     // find current frame in list
274     std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
275     std::list< AquaSalFrame* >::reverse_iterator it = rFrames.rbegin();
276     for( ; it != rFrames.rend() && *it != pCurFrame; ++it )
277         ;
278     if( it != rFrames.rend() )
279     {
280         // now find the next frame (or end)
281         do
282         {
283             ++it;
284             if( it != rFrames.rend() )
285             {
286                 if( (*it)->mpDockMenuEntry != NULL &&
287                     (*it)->mbShown )
288                 {
289                     [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
290                     return;
291                 }
292             }
293         } while( it != rFrames.rend() );
294         // cycle around, find the next up to pCurFrame
295         it = rFrames.rbegin();
296         while( *it != pCurFrame )
297         {
298             if( (*it)->mpDockMenuEntry != NULL &&
299                 (*it)->mbShown )
300             {
301                 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
302                 return;
303             }
304             ++it;
305         }
306     }
309 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
311     (void)sender;
312     return AquaSalInstance::GetDynamicDockMenu();
315 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
317     (void)app;
318     std::vector<OUString> aFile;
319     aFile.push_back( GetOUString( pFile ) );
320     if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
321     {
322         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_OPEN, aFile);
323         AquaSalInstance::aAppEventList.push_back( pAppEvent );
324     }
325     return YES;
328 -(void)application: (NSApplication*) app openFiles: (NSArray*)files
330     (void)app;
331     std::vector<OUString> aFileList;
333     NSEnumerator* it = [files objectEnumerator];
334     NSString* pFile = nil;
336     while( (pFile = [it nextObject]) != nil )
337     {
338         const rtl::OUString aFile( GetOUString( pFile ) );
339         if( ! AquaSalInstance::isOnCommandLine( aFile ) )
340         {
341             aFileList.push_back( aFile );
342         }
343     }
345     if( !aFileList.empty() )
346     {
347         // we have no back channel here, we have to assume success, in which case
348         // replyToOpenOrPrint does not need to be called according to documentation
349         // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
350         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_OPEN, aFileList);
351         AquaSalInstance::aAppEventList.push_back( pAppEvent );
352     }
355 -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
357     (void)app;
358     std::vector<OUString> aFile;
359     aFile.push_back( GetOUString( pFile ) );
360         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_PRINT, aFile);
361         AquaSalInstance::aAppEventList.push_back( pAppEvent );
362     return YES;
364 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
366     (void)app;
367     (void)printSettings;
368     (void)bShowPrintPanels;
369     // currently ignores print settings an bShowPrintPanels
370     std::vector<OUString> aFileList;
372     NSEnumerator* it = [files objectEnumerator];
373     NSString* pFile = nil;
375     while( (pFile = [it nextObject]) != nil )
376     {
377         aFileList.push_back( GetOUString( pFile ) );
378     }
379         const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_PRINT, aFileList);
380         AquaSalInstance::aAppEventList.push_back( pAppEvent );
381     // we have no back channel here, we have to assume success
382     // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
383     return NSPrintingSuccess;
386 -(void)applicationWillTerminate: (NSNotification *) aNotification
388     (void)aNotification;
389     sal_detail_deinitialize();
392 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
394     (void)app;
395     NSApplicationTerminateReply aReply = NSTerminateNow;
396     {
397         YIELD_GUARD;
399         SalData* pSalData = GetSalData();
400         if( ! pSalData->maFrames.empty() )
401         {
402             // the following QueryExit will likely present a message box, activate application
403             [NSApp activateIgnoringOtherApps: YES];
404             aReply = pSalData->maFrames.front()->CallCallback( SALEVENT_SHUTDOWN, NULL ) ? NSTerminateCancel : NSTerminateNow;
405         }
407         if( aReply == NSTerminateNow )
408         {
409             ApplicationEvent aEv(ApplicationEvent::TYPE_PRIVATE_DOSHUTDOWN);
410             GetpApp()->AppEvent( aEv );
411             ImplImageTree::get().shutDown();
412             // DeInitVCL should be called in ImplSVMain - unless someon _exits first which
413             // can occur in Desktop::doShutdown for example
414         }
415     }
417     return aReply;
420 -(void)systemColorsChanged: (NSNotification*) pNotification
422     (void)pNotification;
423     YIELD_GUARD;
425     const SalData* pSalData = GetSalData();
426         if( !pSalData->maFrames.empty() )
427                 pSalData->maFrames.front()->CallCallback( SALEVENT_SETTINGSCHANGED, NULL );
430 -(void)screenParametersChanged: (NSNotification*) pNotification
432     (void)pNotification;
433     YIELD_GUARD;
435     SalData* pSalData = GetSalData();
436     std::list< AquaSalFrame* >::iterator it;
437     for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it )
438     {
439         (*it)->screenParametersChanged();
440     }
443 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
445     (void)pNotification;
446     GetSalData()->mpFirstInstance->delayedSettingsChanged( true );
449 -(void)scrollbarSettingsChanged: (NSNotification*) pNotification
451     (void)pNotification;
452     GetSalData()->mpFirstInstance->delayedSettingsChanged( false );
455 -(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
457     AquaSalMenu::addFallbackMenuItem( pNewItem );
460 -(void)removeFallbackMenuItem: (NSMenuItem*)pItem
462     AquaSalMenu::removeFallbackMenuItem( pItem );
465 -(void)addDockMenuItem: (NSMenuItem*)pNewItem
467     NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
468     [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
471 // for Apple Remote implementation
473 #if !HAVE_FEATURE_MACOSX_SANDBOX
474 - (void)applicationWillBecomeActive:(NSNotification *)pNotification
476     (void)pNotification;
477     SalData* pSalData = GetSalData();
478     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
479     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
480     {
481         // [remoteControl startListening: self];
482         // does crash because the right thing to do is
483         // [pAppleRemoteCtrl->remoteControl startListening: self];
484         // but the instance variable 'remoteControl' is declared protected
485         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
487         [pAppleRemoteCtrl->remoteControl startListening: self];
488 #ifdef DEBUG
489         NSLog(@"Apple Remote will become active - Using remote controls");
490 #endif
491     }
492     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
493          it != pSalData->maPresentationFrames.end(); ++it )
494     {
495         NSWindow* pNSWindow = (*it)->getNSWindow();
496         [pNSWindow setLevel: NSPopUpMenuWindowLevel];
497         if( [pNSWindow isVisible] )
498             [pNSWindow orderFront: NSApp];
499     }
502 - (void)applicationWillResignActive:(NSNotification *)pNotification
504     (void)pNotification;
505     SalData* pSalData = GetSalData();
506     AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
507     if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
508     {
509         // [remoteControl stopListening: self];
510         // does crash because the right thing to do is
511         // [pAppleRemoteCtrl->remoteControl stopListening: self];
512         // but the instance variable 'remoteControl' is declared protected
513         // workaround : declare remoteControl instance variable as public in RemoteMainController.m
515         [pAppleRemoteCtrl->remoteControl stopListening: self];
516 #ifdef DEBUG
517         NSLog(@"Apple Remote will resign active - Releasing remote controls");
518 #endif
519     }
520     for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
521          it != pSalData->maPresentationFrames.end(); ++it )
522     {
523         [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
524     }
526 #endif
528 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
530     (void)pApp;
531     (void)bWinVisible;
532     NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
533     if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
534     {
535         [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
536     }
537     return YES;
540 -(void)setDockIconClickHandler: (NSObject*)pHandler
542     GetSalData()->mpDockIconClickHandler = pHandler;
546 @end
548 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */