diff options
Diffstat (limited to 'vcl/osx/vclnsapp.mm')
-rw-r--r-- | vcl/osx/vclnsapp.mm | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/vcl/osx/vclnsapp.mm b/vcl/osx/vclnsapp.mm new file mode 100644 index 0000000000..5daf923ce1 --- /dev/null +++ b/vcl/osx/vclnsapp.mm @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <config_features.h> + +#include <vector> + +#include <stdlib.h> + +#include <sal/main.h> +#include <vcl/commandevent.hxx> +#include <vcl/ImageTree.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +#include <osx/saldata.hxx> +#include <osx/salframe.h> +#include <osx/salframeview.h> +#include <osx/salinst.h> +#include <osx/vclnsapp.h> +#include <quartz/utils.h> + +#include <premac.h> +#include <objc/objc-runtime.h> +#import "Carbon/Carbon.h" +#import "apple_remote/RemoteControl.h" +#include <postmac.h> + + +@implementation CocoaThreadEnabler +-(void)enableCocoaThreads:(id)param +{ + // do nothing, this is just to start an NSThread and therefore put + // Cocoa into multithread mode + (void)param; +} +@end + +// If you wonder how this VCL_NSApplication stuff works, one thing you +// might have missed is that the NSPrincipalClass property in +// desktop/macosx/Info.plist has the value VCL_NSApplication. + +@implementation VCL_NSApplication + +-(void)applicationDidFinishLaunching:(NSNotification*)pNotification +{ + (void)pNotification; + + NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: [[NSProcessInfo processInfo] systemUptime] + windowNumber: 0 + context: nil + subtype: AquaSalInstance::AppExecuteSVMain + data1: 0 + data2: 0 ]; + assert( pEvent ); + [NSApp postEvent: pEvent atStart: NO]; + + if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] ) + { + [NSWindow setAllowsAutomaticWindowTabbing:NO]; + } + + // listen to dark mode change + [NSApp addObserver:self forKeyPath:@"effectiveAppearance" options: 0 context: nil]; +} + +-(void)sendEvent:(NSEvent*)pEvent +{ + NSEventType eType = [pEvent type]; + if( eType == NSEventTypeApplicationDefined ) + { + AquaSalInstance::handleAppDefinedEvent( pEvent ); + } + else if( eType == NSEventTypeKeyDown && ([pEvent modifierFlags] & NSEventModifierFlagCommand) != 0 ) + { + NSWindow* pKeyWin = [NSApp keyWindow]; + if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] ) + { + // Commit uncommitted text before dispatching key shortcuts. In + // certain cases such as pressing Command-Option-C in a Writer + // document while there is uncommitted text will call + // AquaSalFrame::EndExtTextInput() which will dispatch a + // SalEvent::EndExtTextInput event. Writer's handler for that event + // will delete the uncommitted text and then insert the committed + // text but LibreOffice will crash when deleting the uncommitted + // text because deletion of the text also removes and deletes the + // newly inserted comment. + [static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput]; + + AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame]; + unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand)); + /* + * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows + */ + if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] ) + { + if ( nModMask == NSEventModifierFlagCommand && ([pFrame->getNSWindow() styleMask] & NSWindowStyleMaskMiniaturizable) ) + { + [pFrame->getNSWindow() performMiniaturize: nil]; + return; + } + + if ( nModMask == ( NSEventModifierFlagCommand | NSEventModifierFlagOption ) ) + { + [NSApp miniaturizeAll: nil]; + return; + } + } + + // get information whether the event was handled; keyDown returns nothing + GetSalData()->maKeyEventAnswer[ pEvent ] = false; + bool bHandled = false; + + // dispatch to view directly to avoid the key event being consumed by the menubar + // popup windows do not get the focus, so they don't get these either + // simplest would be dispatch this to the key window always if it is without parent + // however e.g. in document we want the menu shortcut if e.g. the stylist has focus + if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) ) + { + [[pKeyWin contentView] keyDown: pEvent]; + bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; + } + + // see whether the main menu consumes this event + // if not, we want to dispatch it ourselves. Unless we do this "trick" + // the main menu just beeps for an unknown or disabled key equivalent + // and swallows the event wholesale + NSMenu* pMainMenu = [NSApp mainMenu]; + if( ! bHandled && + (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) ) + { + [[pKeyWin contentView] keyDown: pEvent]; + bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; + } + else + { + bHandled = true; // event handled already or main menu just handled it + } + GetSalData()->maKeyEventAnswer.erase( pEvent ); + + if( bHandled ) + return; + } + else if( pKeyWin ) + { + // #i94601# a window not of vcl's making has the focus. + // Since our menus do not invoke the usual commands + // try to play nice with native windows like the file dialog + // and emulate them + // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are + // NOT localized, that is the same in all locales. Should this be + // different in any locale, this hack will fail. + unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand)); + if( nModMask == NSEventModifierFlagCommand ) + { + + if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] ) + { + if( [NSApp sendAction: @selector(paste:) to: nil from: nil] ) + return; + } + else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] ) + { + if( [NSApp sendAction: @selector(copy:) to: nil from: nil] ) + return; + } + else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] ) + { + if( [NSApp sendAction: @selector(cut:) to: nil from: nil] ) + return; + } + else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] ) + { + if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] ) + return; + } + else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] ) + { + if( [NSApp sendAction: @selector(undo:) to: nil from: nil] ) + return; + } + } + else if( nModMask == (NSEventModifierFlagCommand|NSEventModifierFlagShift) ) + { + if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] ) + { + if( [NSApp sendAction: @selector(redo:) to: nil from: nil] ) + return; + } + } + } + } + [super sendEvent: pEvent]; +} + +-(void)sendSuperEvent:(NSEvent*)pEvent +{ + [super sendEvent: pEvent]; +} + +-(NSMenu*)applicationDockMenu:(NSApplication *)sender +{ + (void)sender; + return AquaSalInstance::GetDynamicDockMenu(); +} + +-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile +{ + (void)app; + std::vector<OUString> aFile { GetOUString( pFile ) }; + if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) ) + { + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFile)); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + AquaSalInstance *pInst = GetSalData()->mpInstance; + if( pInst ) + pInst->TriggerUserEventProcessing(); + } + return YES; +} + +-(void)application: (NSApplication*) app openFiles: (NSArray*)files +{ + (void)app; + std::vector<OUString> aFileList; + + NSEnumerator* it = [files objectEnumerator]; + NSString* pFile = nil; + + while( (pFile = [it nextObject]) != nil ) + { + const OUString aFile( GetOUString( pFile ) ); + if( ! AquaSalInstance::isOnCommandLine( aFile ) ) + { + aFileList.push_back( aFile ); + } + } + + if( !aFileList.empty() ) + { + // we have no back channel here, we have to assume success, in which case + // replyToOpenOrPrint does not need to be called according to documentation + // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess]; + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFileList)); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + AquaSalInstance *pInst = GetSalData()->mpInstance; + if( pInst ) + pInst->TriggerUserEventProcessing(); + } +} + +-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile +{ + (void)app; + std::vector<OUString> aFile { GetOUString(pFile) }; + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFile)); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + AquaSalInstance *pInst = GetSalData()->mpInstance; + if( pInst ) + pInst->TriggerUserEventProcessing(); + return YES; +} +-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels +{ + (void)app; + (void)printSettings; + (void)bShowPrintPanels; + // currently ignores print settings a bShowPrintPanels + std::vector<OUString> aFileList; + + NSEnumerator* it = [files objectEnumerator]; + NSString* pFile = nil; + + while( (pFile = [it nextObject]) != nil ) + { + aFileList.push_back( GetOUString( pFile ) ); + } + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFileList)); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + AquaSalInstance *pInst = GetSalData()->mpInstance; + if( pInst ) + pInst->TriggerUserEventProcessing(); + // we have no back channel here, we have to assume success + // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint] + return NSPrintingSuccess; +} + +-(void)applicationWillTerminate: (NSNotification *) aNotification +{ + (void)aNotification; + sal_detail_deinitialize(); + _Exit(0); +} + +-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app +{ + (void)app; + NSApplicationTerminateReply aReply = NSTerminateNow; + { + SolarMutexGuard aGuard; + + AquaSalInstance *pInst = GetSalData()->mpInstance; + SalFrame *pAnyFrame = pInst->anyFrame(); + if( pAnyFrame ) + { + // the following QueryExit will likely present a message box, activate application + [NSApp activateIgnoringOtherApps: YES]; + aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow; + } + + if( aReply == NSTerminateNow ) + { + ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown); + GetpApp()->AppEvent( aEv ); + ImageTree::get().shutdown(); + // DeInitVCL should be called in ImplSVMain - unless someone exits first which + // can occur in Desktop::doShutdown for example + } + } + + return aReply; +} + +-(void)observeValueForKeyPath: (NSString*) keyPath ofObject:(id)object + change: (NSDictionary<NSKeyValueChangeKey, id>*)change + context: (void*)context +{ + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + if ([keyPath isEqualToString:@"effectiveAppearance"]) + [self systemColorsChanged: nil]; +} + +-(void)systemColorsChanged: (NSNotification*) pNotification +{ + (void)pNotification; + SolarMutexGuard aGuard; + + AquaSalInstance *pInst = GetSalData()->mpInstance; + SalFrame *pAnyFrame = pInst->anyFrame(); + if( pAnyFrame ) + pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr ); +} + +-(void)screenParametersChanged: (NSNotification*) pNotification +{ + (void)pNotification; + SolarMutexGuard aGuard; + + for( auto pSalFrame : GetSalData()->mpInstance->getFrames() ) + { + AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame ); + pFrame->screenParametersChanged(); + } +} + +-(void)scrollbarVariantChanged: (NSNotification*) pNotification +{ + (void)pNotification; + GetSalData()->mpInstance->delayedSettingsChanged( true ); +} + +-(void)scrollbarSettingsChanged: (NSNotification*) pNotification +{ + (void)pNotification; + GetSalData()->mpInstance->delayedSettingsChanged( false ); +} + +-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem +{ + AquaSalMenu::addFallbackMenuItem( pNewItem ); +} + +-(void)removeFallbackMenuItem: (NSMenuItem*)pItem +{ + AquaSalMenu::removeFallbackMenuItem( pItem ); +} + +-(void)addDockMenuItem: (NSMenuItem*)pNewItem +{ + NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu(); + [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]]; +} + +// for Apple Remote implementation + +#if !HAVE_FEATURE_MACOSX_SANDBOX +- (void)applicationWillBecomeActive:(NSNotification *)pNotification +{ + (void)pNotification; + SalData* pSalData = GetSalData(); + AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController; + if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl) + { + // [remoteControl startListening: self]; + // does crash because the right thing to do is + // [pAppleRemoteCtrl->remoteControl startListening: self]; + // but the instance variable 'remoteControl' is declared protected + // workaround : declare remoteControl instance variable as public in RemoteMainController.m + + [pAppleRemoteCtrl->remoteControl startListening: self]; +#ifdef DEBUG + NSLog(@"Apple Remote will become active - Using remote controls"); +#endif + } + for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin(); + it != pSalData->maPresentationFrames.end(); ++it ) + { + NSWindow* pNSWindow = (*it)->getNSWindow(); + [pNSWindow setLevel: NSPopUpMenuWindowLevel]; + if( [pNSWindow isVisible] ) + [pNSWindow orderFront: NSApp]; + } +} + +- (void)applicationWillResignActive:(NSNotification *)pNotification +{ + (void)pNotification; + SalData* pSalData = GetSalData(); + AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController; + if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl) + { + // [remoteControl stopListening: self]; + // does crash because the right thing to do is + // [pAppleRemoteCtrl->remoteControl stopListening: self]; + // but the instance variable 'remoteControl' is declared protected + // workaround : declare remoteControl instance variable as public in RemoteMainController.m + + [pAppleRemoteCtrl->remoteControl stopListening: self]; +#ifdef DEBUG + NSLog(@"Apple Remote will resign active - Releasing remote controls"); +#endif + } + for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin(); + it != pSalData->maPresentationFrames.end(); ++it ) + { + [(*it)->getNSWindow() setLevel: NSNormalWindowLevel]; + } +} +#endif + +- (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible +{ + (void)pApp; + (void)bWinVisible; + NSObject* pHdl = GetSalData()->mpDockIconClickHandler; + if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] ) + { + [pHdl performSelector:@selector(dockIconClicked:) withObject: self]; + } + return YES; +} + +-(void)setDockIconClickHandler: (NSObject*)pHandler +{ + GetSalData()->mpDockIconClickHandler = pHandler; +} + + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |