xref: /aoo41x/main/vcl/aqua/source/app/vclnsapp.mm (revision c7ada659)
1/**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements.  See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership.  The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License.  You may obtain a copy of the License at
10 *
11 *   http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied.  See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25#include "precompiled_vcl.hxx"
26
27#include "rtl/ustrbuf.hxx"
28
29#include "vcl/window.hxx"
30#include "vcl/svapp.hxx"
31#include "vcl/cmdevt.hxx"
32
33#include "aqua/vclnsapp.h"
34#include "aqua/salinst.h"
35#include "aqua/saldata.hxx"
36#include "aqua/salframe.h"
37#include "aqua/salframeview.h"
38
39#include "impimagetree.hxx"
40
41#include "premac.h"
42#import "Carbon/Carbon.h"
43#import "apple_remote/RemoteControl.h"
44#include "postmac.h"
45
46
47@implementation CocoaThreadEnabler
48-(void)enableCocoaThreads:(id)param
49{
50    // do nothing, this is just to start an NSThread and therefore put
51    // Cocoa into multithread mode
52    (void)param;
53}
54@end
55
56@implementation VCL_NSApplication
57-(void)sendEvent:(NSEvent*)pEvent
58{
59    NSEventType eType = [pEvent type];
60    if( eType == NSApplicationDefined )
61        GetSalData()->mpFirstInstance->handleAppDefinedEvent( pEvent );
62    else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 )
63    {
64        NSWindow* pKeyWin = [NSApp keyWindow];
65        if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
66        {
67            AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame];
68            // handle Cmd-W
69            // FIXME: the correct solution would be to handle this in framework
70            // in the menu code
71            // however that is currently being revised, so let's use a preliminary solution here
72            // this hack is based on assumption
73            // a) Cmd-W is the same in all languages in OOo's menu conig
74            // b) Cmd-W is the same in all languages in on MacOS
75            // for now this seems to be true
76            unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
77            if( (pFrame->mnStyleMask & NSClosableWindowMask) != 0 )
78            {
79                if( nModMask == NSCommandKeyMask
80                    && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] )
81                {
82                    [pFrame->getWindow() windowShouldClose: nil];
83                    return;
84                }
85            }
86
87            /*
88             * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
89             */
90            if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
91            {
92                if ( nModMask == NSCommandKeyMask && ([pFrame->getWindow() styleMask] & NSMiniaturizableWindowMask) )
93                {
94                    [pFrame->getWindow() performMiniaturize: nil];
95                    return;
96                }
97
98                if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) )
99                {
100                    [NSApp miniaturizeAll: nil];
101                    return;
102                }
103            }
104
105            // #i90083# handle frame switching
106            // FIXME: lousy workaround
107            if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 )
108            {
109                if( [[pEvent characters] isEqualToString: @"<"] ||
110                    [[pEvent characters] isEqualToString: @"~"] )
111                {
112                    [self cycleFrameForward: pFrame];
113                    return;
114                }
115                else if( [[pEvent characters] isEqualToString: @">"] ||
116                         [[pEvent characters] isEqualToString: @"`"] )
117                {
118                    [self cycleFrameBackward: pFrame];
119                    return;
120                }
121            }
122
123            // get information whether the event was handled; keyDown returns nothing
124            GetSalData()->maKeyEventAnswer[ pEvent ] = false;
125            bool bHandled = false;
126
127            // dispatch to view directly to avoid the key event being consumed by the menubar
128            // popup windows do not get the focus, so they don't get these either
129            // simplest would be dispatch this to the key window always if it is without parent
130            // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
131            if( pFrame->mpParent && (pFrame->mnStyle & SAL_FRAME_STYLE_FLOAT) == 0 )
132            {
133                [[pKeyWin contentView] keyDown: pEvent];
134                bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
135            }
136
137            // see whether the main menu consumes this event
138            // if not, we want to dispatch it ourselves. Unless we do this "trick"
139            // the main menu just beeps for an unknown or disabled key equivalent
140            // and swallows the event wholesale
141            NSMenu* pMainMenu = [NSApp mainMenu];
142            if( ! bHandled && (pMainMenu == 0 || ! [pMainMenu performKeyEquivalent: pEvent]) )
143            {
144                [[pKeyWin contentView] keyDown: pEvent];
145                bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
146            }
147            else
148                bHandled = true;  // event handled already or main menu just handled it
149
150            GetSalData()->maKeyEventAnswer.erase( pEvent );
151            if( bHandled )
152                return;
153        }
154        else if( pKeyWin )
155        {
156            // #i94601# a window not of vcl's making has the focus.
157            // Since our menus do not invoke the usual commands
158            // try to play nice with native windows like the file dialog
159            // and emulate them
160            // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
161            // NOT localized, that is the same in all locales. Should this be
162            // different in any locale, this hack will fail.
163            unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask));
164            if( nModMask == NSCommandKeyMask )
165            {
166
167                if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
168                {
169                    if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
170                        return;
171                }
172                else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
173                {
174                    if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
175                        return;
176                }
177                else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
178                {
179                    if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
180                        return;
181                }
182            }
183        }
184    }
185    else if( eType == NSScrollWheel && ( GetSalData()->mnSystemVersion < VER_LEOPARD /* fixed in Leopard and above */ ) )
186    {
187
188        NSWindow* pWin = [pEvent window];
189        // on Tiger wheel events do not reach non key windows
190        // which probably should be considered a bug
191        if( [pWin isKindOfClass: [SalFrameWindow class]] && [pWin canBecomeKeyWindow] == NO )
192        {
193            [[pWin contentView] scrollWheel: pEvent];
194            return;
195        }
196    }
197    [super sendEvent: pEvent];
198}
199
200-(void)sendSuperEvent:(NSEvent*)pEvent
201{
202    [super sendEvent: pEvent];
203}
204
205-(void)cycleFrameForward: (AquaSalFrame*)pCurFrame
206{
207    // find current frame in list
208    std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
209    std::list< AquaSalFrame* >::iterator it = rFrames.begin();
210    for( ; it != rFrames.end() && *it != pCurFrame; ++it )
211        ;
212    if( it != rFrames.end() )
213    {
214        // now find the next frame (or end)
215        do
216        {
217            ++it;
218            if( it != rFrames.end() )
219            {
220                if( (*it)->mpDockMenuEntry != NULL &&
221                    (*it)->mbShown )
222                {
223                    [(*it)->getWindow() makeKeyAndOrderFront: NSApp];
224                    return;
225                }
226            }
227        } while( it != rFrames.end() );
228        // cycle around, find the next up to pCurFrame
229        it = rFrames.begin();
230        while( *it != pCurFrame )
231        {
232            if( (*it)->mpDockMenuEntry != NULL &&
233                (*it)->mbShown )
234            {
235                [(*it)->getWindow() makeKeyAndOrderFront: NSApp];
236                return;
237            }
238            ++it;
239        }
240    }
241}
242
243-(void)cycleFrameBackward: (AquaSalFrame*)pCurFrame
244{
245    // do the same as cycleFrameForward only with a reverse iterator
246
247    // find current frame in list
248    std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames );
249    std::list< AquaSalFrame* >::reverse_iterator it = rFrames.rbegin();
250    for( ; it != rFrames.rend() && *it != pCurFrame; ++it )
251        ;
252    if( it != rFrames.rend() )
253    {
254        // now find the next frame (or end)
255        do
256        {
257            ++it;
258            if( it != rFrames.rend() )
259            {
260                if( (*it)->mpDockMenuEntry != NULL &&
261                    (*it)->mbShown )
262                {
263                    [(*it)->getWindow() makeKeyAndOrderFront: NSApp];
264                    return;
265                }
266            }
267        } while( it != rFrames.rend() );
268        // cycle around, find the next up to pCurFrame
269        it = rFrames.rbegin();
270        while( *it != pCurFrame )
271        {
272            if( (*it)->mpDockMenuEntry != NULL &&
273                (*it)->mbShown )
274            {
275                [(*it)->getWindow() makeKeyAndOrderFront: NSApp];
276                return;
277            }
278            ++it;
279        }
280    }
281}
282
283-(NSMenu*)applicationDockMenu:(NSApplication *)sender
284{
285    (void)sender;
286    return AquaSalInstance::GetDynamicDockMenu();
287}
288
289-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
290{
291    (void)app;
292    const rtl::OUString aFile( GetOUString( pFile ) );
293    if( ! AquaSalInstance::isOnCommandLine( aFile ) )
294    {
295        const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(),
296                                                    APPEVENT_OPEN_STRING, aFile );
297        AquaSalInstance::aAppEventList.push_back( pAppEvent );
298    }
299    return YES;
300}
301
302-(void)application: (NSApplication*) app openFiles: (NSArray*)files
303{
304    (void)app;
305    rtl::OUStringBuffer aFileList( 256 );
306
307    NSEnumerator* it = [files objectEnumerator];
308    NSString* pFile = nil;
309
310    while( (pFile = [it nextObject]) != nil )
311    {
312        const rtl::OUString aFile( GetOUString( pFile ) );
313        if( ! AquaSalInstance::isOnCommandLine( aFile ) )
314        {
315            if( aFileList.getLength() > 0 )
316                aFileList.append( sal_Unicode( APPEVENT_PARAM_DELIMITER ) );
317            aFileList.append( aFile );
318        }
319    }
320
321    if( aFileList.getLength() )
322    {
323        // we have no back channel here, we have to assume success, in which case
324        // replyToOpenOrPrint does not need to be called according to documentation
325        // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
326        const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(),
327                                                    APPEVENT_OPEN_STRING, aFileList.makeStringAndClear() );
328        AquaSalInstance::aAppEventList.push_back( pAppEvent );
329    }
330}
331
332-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
333{
334    (void)app;
335    const rtl::OUString aFile( GetOUString( pFile ) );
336	const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(),
337                                                APPEVENT_PRINT_STRING, aFile );
338	AquaSalInstance::aAppEventList.push_back( pAppEvent );
339    return YES;
340}
341-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
342{
343    (void)app;
344    (void)printSettings;
345    (void)bShowPrintPanels;
346    // currently ignores print settings an bShowPrintPanels
347    rtl::OUStringBuffer aFileList( 256 );
348
349    NSEnumerator* it = [files objectEnumerator];
350    NSString* pFile = nil;
351
352    while( (pFile = [it nextObject]) != nil )
353    {
354        if( aFileList.getLength() > 0 )
355            aFileList.append( sal_Unicode( APPEVENT_PARAM_DELIMITER ) );
356        aFileList.append( GetOUString( pFile ) );
357    }
358	const ApplicationEvent* pAppEvent = new ApplicationEvent( String(), ApplicationAddress(),
359                                                APPEVENT_PRINT_STRING, aFileList.makeStringAndClear() );
360	AquaSalInstance::aAppEventList.push_back( pAppEvent );
361    // we have no back channel here, we have to assume success
362    // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
363    return NSPrintingSuccess;
364}
365
366-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
367{
368    (void)app;
369    NSApplicationTerminateReply aReply = NSTerminateNow;
370    {
371        YIELD_GUARD;
372
373        SalData* pSalData = GetSalData();
374        if( ! pSalData->maFrames.empty() )
375        {
376            // the following QueryExit will likely present a message box, activate application
377            [NSApp activateIgnoringOtherApps: YES];
378            aReply = pSalData->maFrames.front()->CallCallback( SALEVENT_SHUTDOWN, NULL ) ? NSTerminateCancel : NSTerminateNow;
379        }
380
381        if( aReply == NSTerminateNow )
382        {
383            ApplicationEvent aEv( String(), ApplicationAddress(), ByteString( "PRIVATE:DOSHUTDOWN" ), String() );
384            GetpApp()->AppEvent( aEv );
385            ImplImageTreeSingletonRef()->shutDown();
386            // DeInitVCL should be called in ImplSVMain - unless someon _exits first which
387            // can occur in Desktop::doShutdown for example
388        }
389    }
390
391    return aReply;
392}
393
394-(void)systemColorsChanged: (NSNotification*) pNotification
395{
396    (void)pNotification;
397    YIELD_GUARD;
398
399    const SalData* pSalData = GetSalData();
400	if( !pSalData->maFrames.empty() )
401		pSalData->maFrames.front()->CallCallback( SALEVENT_SETTINGSCHANGED, NULL );
402}
403
404-(void)screenParametersChanged: (NSNotification*) pNotification
405{
406    (void)pNotification;
407    YIELD_GUARD;
408
409    SalData* pSalData = GetSalData();
410    std::list< AquaSalFrame* >::iterator it;
411    for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it )
412    {
413        (*it)->screenParametersChanged();
414    }
415}
416
417-(void)scrollbarVariantChanged: (NSNotification*) pNotification
418{
419    (void)pNotification;
420    GetSalData()->mpFirstInstance->delayedSettingsChanged( true );
421}
422
423-(void)scrollbarSettingsChanged: (NSNotification*) pNotification
424{
425    (void)pNotification;
426    GetSalData()->mpFirstInstance->delayedSettingsChanged( false );
427}
428
429-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
430{
431    AquaSalMenu::addFallbackMenuItem( pNewItem );
432}
433
434-(void)removeFallbackMenuItem: (NSMenuItem*)pItem
435{
436    AquaSalMenu::removeFallbackMenuItem( pItem );
437}
438
439-(void)addDockMenuItem: (NSMenuItem*)pNewItem
440{
441    NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
442    [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
443}
444
445// for Apple Remote implementation
446
447#pragma mark -
448#pragma mark NSApplication Delegates
449- (void)applicationWillBecomeActive:(NSNotification *)pNotification
450{
451    (void)pNotification;
452    SalData* pSalData = GetSalData();
453    if( pSalData->mpMainController && pSalData->mpMainController->remoteControl)
454    {
455        // [remoteControl startListening: self];
456        // does crash because the right thing to do is
457        // [GetSalData()->mpMainController->remoteControl startListening: self];
458        // but the instance variable 'remoteControl' is declared protected
459        // workaround : declare remoteControl instance variable as public in RemoteMainController.m
460
461        [pSalData->mpMainController->remoteControl startListening: self];
462#ifdef DEBUG
463        NSLog(@"Apple Remote will become active - Using remote controls");
464#endif
465    }
466    for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
467         it != pSalData->maPresentationFrames.end(); ++it )
468    {
469        [(*it)->mpWindow setLevel: NSPopUpMenuWindowLevel];
470        if( [(*it)->mpWindow isVisible] )
471            [(*it)->mpWindow orderFront: NSApp];
472    }
473}
474
475- (void)applicationWillResignActive:(NSNotification *)pNotification
476{
477    (void)pNotification;
478    SalData* pSalData = GetSalData();
479    if( pSalData->mpMainController && pSalData->mpMainController->remoteControl)
480    {
481        // [remoteControl stopListening: self];
482        // does crash because the right thing to do is
483        // [GetSalData()->mpMainController->remoteControl stopListening: self];
484        // but the instance variable 'remoteControl' is declared protected
485        // workaround : declare remoteControl instance variable as public in RemoteMainController.m
486
487        [pSalData->mpMainController->remoteControl stopListening: self];
488#ifdef DEBUG
489        NSLog(@"Apple Remote will resign active - Releasing remote controls");
490#endif
491    }
492    for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
493         it != pSalData->maPresentationFrames.end(); ++it )
494    {
495        [(*it)->mpWindow setLevel: NSNormalWindowLevel];
496    }
497}
498
499- (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
500{
501    (void)pApp;
502    (void)bWinVisible;
503    NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
504    if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
505    {
506        [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
507    }
508    return YES;
509}
510
511-(void)setDockIconClickHandler: (NSObject*)pHandler
512{
513    GetSalData()->mpDockIconClickHandler = pHandler;
514}
515
516
517@end
518
519