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>
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"
39 #include <objc/objc-runtime.h>
40 #import "Carbon/Carbon.h"
41 #import "apple_remote/RemoteControl.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
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
64 SAL_WNODEPRECATED_DECLARATIONS_PUSH
65 // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12
66 NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined
72 subtype: AquaSalInstance::AppExecuteSVMain
75 SAL_WNODEPRECATED_DECLARATIONS_POP
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 )
94 AquaSalInstance::handleAppDefinedEvent( pEvent );
96 else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
98 NSWindow* pKeyWin = [NSApp keyWindow];
99 if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
101 AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame];
103 // FIXME: the correct solution would be to handle this in framework
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 )
113 if( nModMask == NSCommandKeyMask
114 && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] )
116 [(SalFrameWindow*)pFrame->getNSWindow() windowShouldClose: nil];
122 * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
124 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
126 if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) )
128 [pFrame->getNSWindow() performMiniaturize: nil];
132 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
134 [NSApp miniaturizeAll: nil];
139 // #i90083# handle frame switching
140 // FIXME: lousy workaround
141 if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 )
143 if( [[pEvent characters] isEqualToString: @"<"] ||
144 [[pEvent characters] isEqualToString: @"~"] )
146 [self cycleFrameForward: pFrame];
149 else if( [[pEvent characters] isEqualToString: @">"] ||
150 [[pEvent characters] isEqualToString: @"`"] )
152 [self cycleFrameBackward: pFrame];
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) )
167 [[pKeyWin contentView] keyDown: pEvent];
168 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
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]) )
178 [[pKeyWin contentView] keyDown: pEvent];
179 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
183 bHandled = true; // event handled already or main menu just handled it
185 GetSalData()->maKeyEventAnswer.erase( pEvent );
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
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 )
203 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
205 if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
208 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
210 if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
213 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
215 if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
218 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
220 if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
223 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
225 if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
229 else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) )
231 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
233 if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
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 )
255 if( it != rFrames.end() )
257 // now find the next frame (or end)
261 if( it != rFrames.end() )
263 if( (*it)->mpDockMenuEntry != nullptr &&
266 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
270 } while( it != rFrames.end() );
271 // cycle around, find the next up to pCurFrame
272 it = rFrames.begin();
273 while( *it != pCurFrame )
275 if( (*it)->mpDockMenuEntry != nullptr &&
278 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
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 )
295 if( it != rFrames.rend() )
297 // now find the next frame (or end)
301 if( it != rFrames.rend() )
303 if( (*it)->mpDockMenuEntry != nullptr &&
306 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
310 } while( it != rFrames.rend() );
311 // cycle around, find the next up to pCurFrame
312 it = rFrames.rbegin();
313 while( *it != pCurFrame )
315 if( (*it)->mpDockMenuEntry != nullptr &&
318 [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp];
326 -(NSMenu*)applicationDockMenu:(NSApplication *)sender
329 return AquaSalInstance::GetDynamicDockMenu();
332 -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
335 std::vector<OUString> aFile;
336 aFile.push_back( GetOUString( pFile ) );
337 if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
339 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFile);
340 AquaSalInstance::aAppEventList.push_back( pAppEvent );
345 -(void)application: (NSApplication*) app openFiles: (NSArray*)files
348 std::vector<OUString> aFileList;
350 NSEnumerator* it = [files objectEnumerator];
351 NSString* pFile = nil;
353 while( (pFile = [it nextObject]) != nil )
355 const rtl::OUString aFile( GetOUString( pFile ) );
356 if( ! AquaSalInstance::isOnCommandLine( aFile ) )
358 aFileList.push_back( aFile );
362 if( !aFileList.empty() )
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 );
372 -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
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 );
381 -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
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 )
394 aFileList.push_back( GetOUString( pFile ) );
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
406 sal_detail_deinitialize();
409 -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
412 NSApplicationTerminateReply aReply = NSTerminateNow;
414 SolarMutexGuard aGuard;
416 SalData* pSalData = GetSalData();
417 if( ! pSalData->maFrames.empty() )
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;
424 if( aReply == NSTerminateNow )
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
437 -(void)systemColorsChanged: (NSNotification*) 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
450 SolarMutexGuard aGuard;
452 SalData* pSalData = GetSalData();
453 std::list< AquaSalFrame* >::iterator it;
454 for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it )
456 (*it)->screenParametersChanged();
460 -(void)scrollbarVariantChanged: (NSNotification*) pNotification
463 GetSalData()->mpFirstInstance->delayedSettingsChanged( true );
466 -(void)scrollbarSettingsChanged: (NSNotification*) 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
494 SalData* pSalData = GetSalData();
495 AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
496 if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
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];
506 NSLog(@"Apple Remote will become active - Using remote controls");
509 for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
510 it != pSalData->maPresentationFrames.end(); ++it )
512 NSWindow* pNSWindow = (*it)->getNSWindow();
513 [pNSWindow setLevel: NSPopUpMenuWindowLevel];
514 if( [pNSWindow isVisible] )
515 [pNSWindow orderFront: NSApp];
519 - (void)applicationWillResignActive:(NSNotification *)pNotification
522 SalData* pSalData = GetSalData();
523 AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
524 if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
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];
534 NSLog(@"Apple Remote will resign active - Releasing remote controls");
537 for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
538 it != pSalData->maPresentationFrames.end(); ++it )
540 [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
545 - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
549 NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
550 if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
552 [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
557 -(void)setDockIconClickHandler: (NSObject*)pHandler
559 GetSalData()->mpDockIconClickHandler = pHandler;
565 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */