1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
21 #include <config_features.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>
41 #include <objc/objc-runtime.h>
42 #import "Carbon/Carbon.h"
43 #import "apple_remote/RemoteControl.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
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
66 SAL_WNODEPRECATED_DECLARATIONS_PUSH
67 // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
68 NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
71 timestamp: [[NSProcessInfo processInfo] systemUptime]
74 subtype: AquaSalInstance::AppExecuteSVMain
77 SAL_WNODEPRECATED_DECLARATIONS_POP
79 [NSApp postEvent: pEvent atStart: NO];
81 if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
83 [NSWindow setAllowsAutomaticWindowTabbing:NO];
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 )
101 AquaSalInstance::handleAppDefinedEvent( pEvent );
103 else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
105 NSWindow* pKeyWin = [NSApp keyWindow];
106 if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
108 AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame];
109 unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
111 * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
113 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
115 if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
117 [pFrame->getNSWindow() performMiniaturize: nil];
121 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
123 [NSApp miniaturizeAll: nil];
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) )
138 [[pKeyWin contentView] keyDown: pEvent];
139 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
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];
148 (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) )
150 [[pKeyWin contentView] keyDown: pEvent];
151 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
155 bHandled = true; // event handled already or main menu just handled it
157 GetSalData()->maKeyEventAnswer.erase( pEvent );
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
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 )
175 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
177 if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
180 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
182 if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
185 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
187 if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
190 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
192 if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
195 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
197 if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
201 else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) )
203 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
205 if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
211 SAL_WNODEPRECATED_DECLARATIONS_POP
212 [super sendEvent: pEvent];
215 -(void)sendSuperEvent:(NSEvent*)pEvent
217 [super sendEvent: pEvent];
220 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
223 return AquaSalInstance::GetDynamicDockMenu();
226 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
229 std::vector<OUString> aFile;
230 aFile.push_back( GetOUString( pFile ) );
231 if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
233 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFile);
234 AquaSalInstance::aAppEventList.push_back( pAppEvent );
235 AquaSalInstance *pInst = GetSalData()->mpInstance;
237 pInst->TriggerUserEventProcessing();
242 -(void)application: (NSApplication*) app openFiles: (NSArray*)files
245 std::vector<OUString> aFileList;
247 NSEnumerator* it = [files objectEnumerator];
248 NSString* pFile = nil;
250 while( (pFile = [it nextObject]) != nil )
252 const OUString aFile( GetOUString( pFile ) );
253 if( ! AquaSalInstance::isOnCommandLine( aFile ) )
255 aFileList.push_back( aFile );
259 if( !aFileList.empty() )
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;
268 pInst->TriggerUserEventProcessing();
272 -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
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;
281 pInst->TriggerUserEventProcessing();
284 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
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 )
297 aFileList.push_back( GetOUString( pFile ) );
299 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFileList);
300 AquaSalInstance::aAppEventList.push_back( pAppEvent );
301 AquaSalInstance *pInst = GetSalData()->mpInstance;
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
312 sal_detail_deinitialize();
316 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
319 NSApplicationTerminateReply aReply = NSTerminateNow;
321 SolarMutexGuard aGuard;
323 AquaSalInstance *pInst = GetSalData()->mpInstance;
324 SalFrame *pAnyFrame = pInst->anyFrame();
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;
332 if( aReply == NSTerminateNow )
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
345 -(void)systemColorsChanged: (NSNotification*) pNotification
348 SolarMutexGuard aGuard;
350 AquaSalInstance *pInst = GetSalData()->mpInstance;
351 SalFrame *pAnyFrame = pInst->anyFrame();
353 pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
356 -(void)screenParametersChanged: (NSNotification*) pNotification
359 SolarMutexGuard aGuard;
361 for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
363 AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame );
364 pFrame->screenParametersChanged();
368 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
371 GetSalData()->mpInstance->delayedSettingsChanged( true );
374 -(void)scrollbarSettingsChanged: (NSNotification*) 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
402 SalData* pSalData = GetSalData();
403 AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
404 if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
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];
414 NSLog(@"Apple Remote will become active - Using remote controls");
417 for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
418 it != pSalData->maPresentationFrames.end(); ++it )
420 NSWindow* pNSWindow = (*it)->getNSWindow();
421 [pNSWindow setLevel: NSPopUpMenuWindowLevel];
422 if( [pNSWindow isVisible] )
423 [pNSWindow orderFront: NSApp];
427 - (void)applicationWillResignActive:(NSNotification *)pNotification
430 SalData* pSalData = GetSalData();
431 AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
432 if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
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];
442 NSLog(@"Apple Remote will resign active - Releasing remote controls");
445 for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
446 it != pSalData->maPresentationFrames.end(); ++it )
448 [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
453 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
457 NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
458 if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
460 [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
465 -(void)setDockIconClickHandler: (NSObject*)pHandler
467 GetSalData()->mpDockIconClickHandler = pHandler;
473 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */