/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #include "rtl/ustrbuf.hxx" #include "vcl/window.hxx" #include "vcl/svapp.hxx" #include "vcl/cmdevt.hxx" #include "aqua/vclnsapp.h" #include "aqua/salinst.h" #include "aqua/saldata.hxx" #include "aqua/salframe.h" #include "aqua/salframeview.h" #include "impimagetree.hxx" #include "premac.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 @implementation VCL_NSApplication -(void)applicationDidFinishLaunching:(NSNotification*)aNotification { NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined location: NSZeroPoint modifierFlags: 0 timestamp: 0 windowNumber: 0 context: nil subtype: AquaSalInstance::AppExecuteSVMain data1: 0 data2: 0 ]; if( pEvent ) [NSApp postEvent: pEvent atStart: NO]; } -(void)sendEvent:(NSEvent*)pEvent { NSEventType eType = [pEvent type]; if( eType == NSApplicationDefined ) GetSalData()->mpFirstInstance->handleAppDefinedEvent( pEvent ); else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 ) { NSWindow* pKeyWin = [NSApp keyWindow]; if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] ) { AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame]; // handle Cmd-W // FIXME: the correct solution would be to handle this in framework // in the menu code // however that is currently being revised, so let's use a preliminary solution here // this hack is based on assumption // a) Cmd-W is the same in all languages in OOo's menu conig // b) Cmd-W is the same in all languages in on MacOS // for now this seems to be true unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)); if( (pFrame->mnStyleMask & NSClosableWindowMask) != 0 ) { if( nModMask == NSCommandKeyMask && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] ) { [(SalFrameWindow*)pFrame->getNSWindow() windowShouldClose: nil]; return; } } /* * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows */ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] ) { if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) ) { [pFrame->getNSWindow() performMiniaturize: nil]; return; } if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) ) { [NSApp miniaturizeAll: nil]; return; } } // #i90083# handle frame switching // FIXME: lousy workaround if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 ) { if( [[pEvent characters] isEqualToString: @"<"] || [[pEvent characters] isEqualToString: @"~"] ) { [self cycleFrameForward: pFrame]; return; } else if( [[pEvent characters] isEqualToString: @">"] || [[pEvent characters] isEqualToString: @"`"] ) { [self cycleFrameBackward: pFrame]; 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 & SAL_FRAME_STYLE_FLOAT) == 0 ) { [[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 == 0 || ! [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] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)); if( nModMask == NSCommandKeyMask ) { 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( eType == NSScrollWheel && ( GetSalData()->mnSystemVersion < OSX_VER_LEOPARD /* fixed in Leopard and above */ ) ) { NSWindow* pWin = [pEvent window]; // on Tiger wheel events do not reach non key windows // which probably should be considered a bug if( [pWin isKindOfClass: [SalFrameWindow class]] && [pWin canBecomeKeyWindow] == NO ) { [[pWin contentView] scrollWheel: pEvent]; return; } } [super sendEvent: pEvent]; } -(void)sendSuperEvent:(NSEvent*)pEvent { [super sendEvent: pEvent]; } -(void)cycleFrameForward: (AquaSalFrame*)pCurFrame { // find current frame in list std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames ); std::list< AquaSalFrame* >::iterator it = rFrames.begin(); for( ; it != rFrames.end() && *it != pCurFrame; ++it ) ; if( it != rFrames.end() ) { // now find the next frame (or end) do { ++it; if( it != rFrames.end() ) { if( (*it)->mpDockMenuEntry != NULL && (*it)->mbShown ) { [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; return; } } } while( it != rFrames.end() ); // cycle around, find the next up to pCurFrame it = rFrames.begin(); while( *it != pCurFrame ) { if( (*it)->mpDockMenuEntry != NULL && (*it)->mbShown ) { [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; return; } ++it; } } } -(void)cycleFrameBackward: (AquaSalFrame*)pCurFrame { // do the same as cycleFrameForward only with a reverse iterator // find current frame in list std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames ); std::list< AquaSalFrame* >::reverse_iterator it = rFrames.rbegin(); for( ; it != rFrames.rend() && *it != pCurFrame; ++it ) ; if( it != rFrames.rend() ) { // now find the next frame (or end) do { ++it; if( it != rFrames.rend() ) { if( (*it)->mpDockMenuEntry != NULL && (*it)->mbShown ) { [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; return; } } } while( it != rFrames.rend() ); // cycle around, find the next up to pCurFrame it = rFrames.rbegin(); while( *it != pCurFrame ) { if( (*it)->mpDockMenuEntry != NULL && (*it)->mbShown ) { [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; return; } ++it; } } } -(NSMenu*)applicationDockMenu:(NSApplication *)sender { (void)sender; return AquaSalInstance::GetDynamicDockMenu(); } -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile { (void)app; const rtl::OUString aFile( GetOUString( pFile ) ); if( ! AquaSalInstance::isOnCommandLine( aFile ) ) { const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(), APPEVENT_OPEN_STRING, aFile ); AquaSalInstance::aAppEventList.push_back( pAppEvent ); } return YES; } -(void)application: (NSApplication*) app openFiles: (NSArray*)files { (void)app; rtl::OUStringBuffer aFileList( 256 ); NSEnumerator* it = [files objectEnumerator]; NSString* pFile = nil; while( (pFile = [it nextObject]) != nil ) { const rtl::OUString aFile( GetOUString( pFile ) ); if( ! AquaSalInstance::isOnCommandLine( aFile ) ) { if( aFileList.getLength() > 0 ) aFileList.append( sal_Unicode( APPEVENT_PARAM_DELIMITER ) ); aFileList.append( aFile ); } } if( aFileList.getLength() ) { // 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( String(), ApplicationAddress(), APPEVENT_OPEN_STRING, aFileList.makeStringAndClear() ); AquaSalInstance::aAppEventList.push_back( pAppEvent ); } } -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile { (void)app; const rtl::OUString aFile( GetOUString( pFile ) ); const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(), APPEVENT_PRINT_STRING, aFile ); AquaSalInstance::aAppEventList.push_back( pAppEvent ); 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 an bShowPrintPanels rtl::OUStringBuffer aFileList( 256 ); NSEnumerator* it = [files objectEnumerator]; NSString* pFile = nil; while( (pFile = [it nextObject]) != nil ) { if( aFileList.getLength() > 0 ) aFileList.append( sal_Unicode( APPEVENT_PARAM_DELIMITER ) ); aFileList.append( GetOUString( pFile ) ); } const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(), APPEVENT_PRINT_STRING, aFileList.makeStringAndClear() ); AquaSalInstance::aAppEventList.push_back( pAppEvent ); // we have no back channel here, we have to assume success // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint] return NSPrintingSuccess; } -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app { (void)app; NSApplicationTerminateReply aReply = NSTerminateNow; { YIELD_GUARD; SalData* pSalData = GetSalData(); if( ! pSalData->maFrames.empty() ) { // the following QueryExit will likely present a message box, activate application [NSApp activateIgnoringOtherApps: YES]; aReply = pSalData->maFrames.front()->CallCallback( SALEVENT_SHUTDOWN, NULL ) ? NSTerminateCancel : NSTerminateNow; } if( aReply == NSTerminateNow ) { ApplicationEvent aEv( String(), ApplicationAddress(), ByteString( "PRIVATE:DOSHUTDOWN" ), String() ); GetpApp()->AppEvent( aEv ); ImplImageTreeSingletonRef()->shutDown(); // DeInitVCL should be called in ImplSVMain - unless someon _exits first which // can occur in Desktop::doShutdown for example } } return aReply; } -(void)systemColorsChanged: (NSNotification*) pNotification { (void)pNotification; YIELD_GUARD; const SalData* pSalData = GetSalData(); if( !pSalData->maFrames.empty() ) pSalData->maFrames.front()->CallCallback( SALEVENT_SETTINGSCHANGED, NULL ); } -(void)screenParametersChanged: (NSNotification*) pNotification { (void)pNotification; YIELD_GUARD; SalData* pSalData = GetSalData(); std::list< AquaSalFrame* >::iterator it; for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it ) { (*it)->screenParametersChanged(); } } -(void)scrollbarVariantChanged: (NSNotification*) pNotification { (void)pNotification; GetSalData()->mpFirstInstance->delayedSettingsChanged( true ); } -(void)scrollbarSettingsChanged: (NSNotification*) pNotification { (void)pNotification; GetSalData()->mpFirstInstance->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 #pragma mark - #pragma mark NSApplication Delegates - (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]; } } - (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