blob: b34e62403d545d73c2f85bec1d43856f4e754952 [file] [log] [blame]
/**************************************************************
*
* 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