xref: /core/vcl/osx/salframeview.mm (revision e1a369bbfa2a56e902771aa66b15aa1832d40ec5)
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <sal/config.h>
21
22#include <memory>
23
24#include <basegfx/numeric/ftools.hxx>
25#include <officecfg/Office/Common.hxx>
26#include <sal/macros.h>
27#include <tools/helpers.hxx>
28#include <tools/long.hxx>
29#include <vcl/event.hxx>
30#include <vcl/inputctx.hxx>
31#include <vcl/settings.hxx>
32#include <vcl/svapp.hxx>
33#include <vcl/window.hxx>
34#include <vcl/commandevent.hxx>
35#include <vcl/toolkit/edit.hxx>
36
37#include <com/sun/star/frame/Desktop.hpp>
38#include <com/sun/star/text/XTextRange.hpp>
39
40#include <osx/a11yfactory.h>
41#include <osx/salframe.h>
42#include <osx/salframeview.h>
43#include <osx/salinst.h>
44#include <quartz/salgdi.h>
45#include <quartz/utils.h>
46
47#if HAVE_FEATURE_SKIA
48#include <vcl/skia/SkiaHelper.hxx>
49#include <premac.h>
50#include <QuartzCore/QuartzCore.h>
51#include <postmac.h>
52#endif
53
54#define WHEEL_EVENT_FACTOR 1.5
55
56static sal_uInt16 ImplGetModifierMask( unsigned int nMask )
57{
58    sal_uInt16 nRet = 0;
59    if( (nMask & NSEventModifierFlagShift) != 0 )
60        nRet |= KEY_SHIFT;
61    if( (nMask & NSEventModifierFlagControl) != 0 )
62        nRet |= KEY_MOD3;
63    if( (nMask & NSEventModifierFlagOption) != 0 )
64        nRet |= KEY_MOD2;
65    if( (nMask & NSEventModifierFlagCommand) != 0 )
66        nRet |= KEY_MOD1;
67    return nRet;
68}
69
70static sal_uInt16 ImplMapCharCode( sal_Unicode aCode )
71{
72    static sal_uInt16 aKeyCodeMap[ 128 ] =
73    {
74                    0,                0,                0,                0,                0,                0,                0,                0,
75        KEY_BACKSPACE,          KEY_TAB,       KEY_RETURN,                0,                0,       KEY_RETURN,                0,                0,
76                    0,                0,                0,                0,                0,                0,                0,                0,
77                    0,          KEY_TAB,                0,       KEY_ESCAPE,                0,                0,                0,                0,
78            KEY_SPACE,                0,                0,                0,                0,                0,                0,                0,
79                    0,                0,     KEY_MULTIPLY,          KEY_ADD,        KEY_COMMA,     KEY_SUBTRACT,        KEY_POINT,       KEY_DIVIDE,
80                KEY_0,            KEY_1,            KEY_2,            KEY_3,            KEY_4,            KEY_5,            KEY_6,            KEY_7,
81                KEY_8,            KEY_9,                0,                0,         KEY_LESS,        KEY_EQUAL,      KEY_GREATER,                0,
82                    0,            KEY_A,            KEY_B,            KEY_C,            KEY_D,            KEY_E,            KEY_F,            KEY_G,
83                KEY_H,            KEY_I,            KEY_J,            KEY_K,            KEY_L,            KEY_M,            KEY_N,            KEY_O,
84                KEY_P,            KEY_Q,            KEY_R,            KEY_S,            KEY_T,            KEY_U,            KEY_V,            KEY_W,
85                KEY_X,            KEY_Y,            KEY_Z,                0,                0,                0,                0,                0,
86        KEY_QUOTELEFT,            KEY_A,            KEY_B,            KEY_C,            KEY_D,            KEY_E,            KEY_F,            KEY_G,
87                KEY_H,            KEY_I,            KEY_J,            KEY_K,            KEY_L,            KEY_M,            KEY_N,            KEY_O,
88                KEY_P,            KEY_Q,            KEY_R,            KEY_S,            KEY_T,            KEY_U,            KEY_V,            KEY_W,
89                KEY_X,            KEY_Y,            KEY_Z,                0,                0,                0,        KEY_TILDE,    KEY_BACKSPACE
90    };
91
92    // Note: the mapping 0x7f should by rights be KEY_DELETE
93    // however if you press "backspace" 0x7f is reported
94    // whereas for "delete" 0xf728 gets reported
95
96    // Note: the mapping of 0x19 to KEY_TAB is because for unknown reasons
97    // tab alone is reported as 0x09 (as expected) but shift-tab is
98    // reported as 0x19 (end of medium)
99
100    static sal_uInt16 aFunctionKeyCodeMap[ 128 ] =
101    {
102            KEY_UP,         KEY_DOWN,         KEY_LEFT,        KEY_RIGHT,           KEY_F1,           KEY_F2,           KEY_F3,           KEY_F4,
103            KEY_F5,           KEY_F6,           KEY_F7,           KEY_F8,           KEY_F9,          KEY_F10,          KEY_F11,          KEY_F12,
104           KEY_F13,          KEY_F14,          KEY_F15,          KEY_F16,          KEY_F17,          KEY_F18,          KEY_F19,          KEY_F20,
105           KEY_F21,          KEY_F22,          KEY_F23,          KEY_F24,          KEY_F25,          KEY_F26,                0,                0,
106                 0,                0,                0,                0,                0,                0,                0,       KEY_INSERT,
107        KEY_DELETE,         KEY_HOME,                0,          KEY_END,        KEY_PAGEUP,    KEY_PAGEDOWN,                0,                0,
108                 0,                0,                0,                0,                 0,        KEY_MENU,                0,                0,
109                 0,                0,                0,                0,                 0,               0,                0,                0,
110                 0,                0,                0,         KEY_UNDO,        KEY_REPEAT,        KEY_FIND,         KEY_HELP,                0,
111                 0,                0,                0,                0,                 0,               0,                0,                0,
112                 0,                0,                0,                0,                 0,               0,                0,                0,
113                 0,                0,                0,                0,                 0,               0,                0,                0,
114                 0,                0,                0,                0,                 0,               0,                0,                0,
115                 0,                0,                0,                0,                 0,               0,                0,                0,
116                 0,                0,                0,                0,                 0,               0,                0,                0,
117                 0,                0,                0,                0,                 0,               0,                0,                0
118    };
119
120    sal_uInt16 nKeyCode = 0;
121    if( aCode < SAL_N_ELEMENTS( aKeyCodeMap)  )
122        nKeyCode = aKeyCodeMap[ aCode ];
123    else if( aCode >= 0xf700 && aCode < 0xf780 )
124        nKeyCode = aFunctionKeyCodeMap[ aCode - 0xf700 ];
125    return nKeyCode;
126}
127
128static sal_uInt16 ImplMapKeyCode(sal_uInt16 nKeyCode)
129{
130    /*
131      http://stackoverflow.com/questions/2080312/where-can-i-find-a-list-of-key-codes-for-use-with-cocoas-nsevent-class/2080324#2080324
132      /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
133     */
134
135    static sal_uInt16 aKeyCodeMap[ 0x80 ] =
136    {
137            KEY_A,            KEY_S,            KEY_D,            KEY_F,            KEY_H,            KEY_G,            KEY_Z,            KEY_X,
138            KEY_C,            KEY_V,                0,            KEY_B,            KEY_Q,            KEY_W,            KEY_E,            KEY_R,
139            KEY_Y,            KEY_T,            KEY_1,            KEY_2,            KEY_3,            KEY_4,            KEY_6,            KEY_5,
140        KEY_EQUAL,            KEY_9,            KEY_7,     KEY_SUBTRACT,            KEY_8,            KEY_0, KEY_BRACKETRIGHT, KEY_RIGHTCURLYBRACKET,
141            KEY_U,  KEY_BRACKETLEFT,            KEY_I,            KEY_P,       KEY_RETURN,            KEY_L,            KEY_J,   KEY_QUOTERIGHT,
142            KEY_K,    KEY_SEMICOLON,                0,        KEY_COMMA,       KEY_DIVIDE,            KEY_N,            KEY_M,        KEY_POINT,
143          KEY_TAB,        KEY_SPACE,    KEY_QUOTELEFT,       KEY_DELETE,                0,       KEY_ESCAPE,                0,                0,
144                0,     KEY_CAPSLOCK,                0,                0,                0,                0,                0,                0,
145          KEY_F17,      KEY_DECIMAL,                0,     KEY_MULTIPLY,                0,          KEY_ADD,                0,                0,
146                0,                0,                0,       KEY_DIVIDE,       KEY_RETURN,                0,     KEY_SUBTRACT,          KEY_F18,
147          KEY_F19,        KEY_EQUAL,                0,                0,                0,                0,                0,                0,
148                0,                0,          KEY_F20,                0,                0,                0,                0,                0,
149           KEY_F5,           KEY_F6,           KEY_F7,           KEY_F3,           KEY_F8,           KEY_F9,                0,          KEY_F11,
150                0,          KEY_F13,          KEY_F16,          KEY_F14,                0,          KEY_F10,                0,          KEY_F12,
151                0,          KEY_F15,         KEY_HELP,         KEY_HOME,       KEY_PAGEUP,       KEY_DELETE,           KEY_F4,          KEY_END,
152           KEY_F2,     KEY_PAGEDOWN,           KEY_F1,         KEY_LEFT,        KEY_RIGHT,         KEY_DOWN,           KEY_UP,                0
153    };
154
155    if (nKeyCode < SAL_N_ELEMENTS(aKeyCodeMap))
156        return aKeyCodeMap[nKeyCode];
157    return 0;
158}
159
160// store the frame the mouse last entered
161static AquaSalFrame* s_pMouseFrame = nullptr;
162// store the last pressed button for enter/exit events
163// which lack that information
164static sal_uInt16 s_nLastButton = 0;
165
166static AquaSalFrame* getMouseContainerFrame()
167{
168    AquaSalFrame* pDispatchFrame = nullptr;
169    NSArray* aWindows = [NSWindow windowNumbersWithOptions:0];
170    for(NSUInteger i = 0; i < [aWindows count] && ! pDispatchFrame; i++ )
171    {
172        NSWindow* pWin = [NSApp windowWithWindowNumber:[[aWindows objectAtIndex:i] integerValue]];
173        if( pWin && [pWin isMemberOfClass: [SalFrameWindow class]] && [static_cast<SalFrameWindow*>(pWin) containsMouse] )
174            pDispatchFrame = [static_cast<SalFrameWindow*>(pWin) getSalFrame];
175    }
176    return pDispatchFrame;
177}
178
179static NSArray *getMergedAccessibilityChildren(NSArray *pDefaultChildren, NSArray *pUnignoredChildrenToAdd)
180{
181    NSArray *pRet = pDefaultChildren;
182
183    if (pUnignoredChildrenToAdd && [pUnignoredChildrenToAdd count])
184    {
185        NSMutableArray *pNewChildren = [NSMutableArray arrayWithCapacity:(pRet ? [pRet count] : 0) + 1];
186        if (pNewChildren)
187        {
188            if (pRet)
189                [pNewChildren addObjectsFromArray:pRet];
190
191            for (AquaA11yWrapper *pWrapper : pUnignoredChildrenToAdd)
192            {
193                if (pWrapper && ![pNewChildren containsObject:pWrapper])
194                    [pNewChildren addObject:pWrapper];
195            }
196
197            pRet = pNewChildren;
198        }
199        else
200        {
201            pRet = pUnignoredChildrenToAdd;
202        }
203    }
204
205    return pRet;
206}
207
208// Update ImplGetSVData()->mpWinData->mbIsLiveResize
209static void updateWinDataInLiveResize(bool bInLiveResize)
210{
211    ImplSVData* pSVData = ImplGetSVData();
212    assert( pSVData );
213    if ( pSVData )
214    {
215        if ( pSVData->mpWinData->mbIsLiveResize != bInLiveResize )
216        {
217            pSVData->mpWinData->mbIsLiveResize = bInLiveResize;
218            Scheduler::Wakeup();
219        }
220    }
221}
222
223static void freezeWindowSizeAndReschedule( NSWindow *pWindow )
224{
225    if ( pWindow )
226    {
227        // Application::Reschedule() can potentially display a modal
228        // dialog which will cause a hang so temporarily disable any
229        // resizing by clamping the window's minimum and maximum sizes
230        // to the current frame size which in Application::Reschedule().
231        bool bIsLiveResize = ImplGetSVData()->mpWinData->mbIsLiveResize;
232        NSSize aMinSize = [pWindow minSize];
233        NSSize aMaxSize = [pWindow maxSize];
234        if ( bIsLiveResize )
235        {
236            NSRect aFrame = [pWindow frame];
237            [pWindow setMinSize:aFrame.size];
238            [pWindow setMaxSize:aFrame.size];
239        }
240        Application::Reschedule( true );
241        if ( bIsLiveResize )
242        {
243            [pWindow setMinSize:aMinSize];
244            [pWindow setMaxSize:aMaxSize];
245        }
246    }
247}
248
249static bool isMouseScrollWheelEvent( NSEvent *pEvent )
250{
251    // tdf#151423 allow trackpad or Magic Mouse to behave like a regular mouse
252    // Give both trackpad and Magic Mouse users the option to restore
253    // the legacy zoom via Command+swipe gesture.
254    // The IgnoreKeysWhenScrollingWithTrackpadOrMagicMouse preference is
255    // set to true by default and that disables zooming via swiping.
256    // The problem is that while trackpad users are able to zoom via a
257    // magnify gesture, the Magic Mouse doesn't have a magnify gesture.
258    // Since I have not found a reliable way to distinguish a Magic Mouse
259    // from a trackpad, Magic Mouse users have no obvious replacement
260    // for the zoom via Command+swipe gesture.
261    if ( !officecfg::Office::Common::VCL::macOS::IgnoreKeysWhenScrollingWithTrackpadOrMagicMouse::get() )
262        return true;
263
264    return ( pEvent && [pEvent type] == NSEventTypeScrollWheel && [pEvent phase] == NSEventPhaseNone && [pEvent momentumPhase] == NSEventPhaseNone );
265}
266
267static void updateMenuBarVisibility( const AquaSalFrame *pFrame )
268{
269    // Show the menubar if application is in native full screen mode
270    // since hiding the menubar in that mode will cause the window's
271    // titlebar to fail to display or fail to hide when expected.
272    if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen )
273    {
274        [NSMenu setMenuBarVisible: YES];
275    }
276    // Hide the dock and the menubar if the key window or one of its
277    // parent windows are in LibreOffice full screen mode. Otherwise,
278    // show the dock and the menubar.
279    else if( AquaSalFrame::isAlive( pFrame ) )
280    {
281        bool bInternalFullScreen = false;
282        bool bNativeFullScreen = false;
283        const AquaSalFrame *pParentFrame = pFrame;
284        while( pParentFrame )
285        {
286            bInternalFullScreen |= pParentFrame->mbInternalFullScreen;
287            bNativeFullScreen |= pParentFrame->mbNativeFullScreen;
288            pParentFrame = AquaSalFrame::isAlive( pParentFrame->mpParent ) ? pParentFrame->mpParent : nullptr;
289        }
290
291        if( bInternalFullScreen && !bNativeFullScreen )
292        {
293            const NSWindow *pParentWindow = [NSApp keyWindow];
294            while( pParentWindow && pParentWindow != pFrame->getNSWindow() )
295                pParentWindow = [pParentWindow parentWindow];
296
297            // Related: tdf#161623 disable menubar visibility if no key window
298            // If a window is in LibreOffice's internal full screen mode
299            // and not in native full screen mode and then the user switches
300            // to a different application and back using the Command-Tab keys.
301            // the menubar and Dock would unexpectedly appear.
302            // It appears that the key window will still be nil in this
303            // case, so only enable menubar visibility if the key window
304            // is not nil.
305            if( pParentWindow && pParentWindow != pFrame->getNSWindow() )
306                [NSMenu setMenuBarVisible: YES];
307            else
308                [NSMenu setMenuBarVisible: NO];
309        }
310        else
311        {
312            [NSMenu setMenuBarVisible: YES];
313        }
314    }
315}
316
317static void updateWindowCollectionBehavior( const SalFrameStyleFlags nStyle, const AquaSalFrame *pParent, NSWindow *pNSWindow )
318{
319    if( !pNSWindow )
320        return;
321
322    // Enable fullscreen options if available and useful
323    NSWindowCollectionBehavior eOldCollectionBehavior = [pNSWindow collectionBehavior];
324    NSWindowCollectionBehavior eCollectionBehavior = NSWindowCollectionBehaviorFullScreenNone;
325    if ( officecfg::Office::Common::VCL::macOS::EnableNativeFullScreenWindows::get() )
326    {
327        bool bAllowFullScreen = (SalFrameStyleFlags::NONE == (nStyle & (SalFrameStyleFlags::DIALOG | SalFrameStyleFlags::TOOLTIP | SalFrameStyleFlags::SYSTEMCHILD | SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::TOOLWINDOW | SalFrameStyleFlags::INTRO)));
328        bAllowFullScreen &= (SalFrameStyleFlags::NONE == (~nStyle & SalFrameStyleFlags::SIZEABLE));
329        bAllowFullScreen &= (pParent == nullptr);
330
331        eCollectionBehavior = bAllowFullScreen ? NSWindowCollectionBehaviorFullScreenPrimary : NSWindowCollectionBehaviorFullScreenAuxiliary;
332    }
333    if ( eCollectionBehavior != eOldCollectionBehavior )
334        [pNSWindow setCollectionBehavior: eCollectionBehavior];
335}
336
337static NSString* getCurrentSelection()
338{
339    SolarMutexGuard aGuard;
340
341    // The following is needed for text fields in dialogs, etc.
342    vcl::Window *pWin = ImplGetSVData()->mpWinData->mpFocusWin;
343    if (pWin)
344    {
345        Edit *pEditWin = dynamic_cast<Edit*>(pWin);
346        if (pEditWin)
347            return [CreateNSString(pEditWin->GetSelected()) autorelease];
348    }
349
350    css::uno::Reference<css::frame::XDesktop> xDesktop = css::frame::Desktop::create(::comphelper::getProcessComponentContext());
351    if (xDesktop.is())
352    {
353        css::uno::Reference<css::frame::XModel> xModel(xDesktop->getCurrentComponent(), css::uno::UNO_QUERY);
354        if (xModel)
355        {
356            css::uno::Reference<css::uno::XInterface> xSelection(xModel->getCurrentSelection(), css::uno::UNO_QUERY);
357            if (xSelection)
358            {
359                css::uno::Reference<css::container::XIndexAccess> xIndexAccess(xSelection, css::uno::UNO_QUERY);
360                if (xIndexAccess.is())
361                {
362                    if (xIndexAccess->getCount() > 0)
363                    {
364                        css::uno::Reference<css::text::XTextRange> xTextRange(xIndexAccess->getByIndex(0), css::uno::UNO_QUERY);
365                        if (xTextRange.is())
366                            return [CreateNSString(xTextRange->getString()) autorelease];
367                    }
368                }
369
370                // The Basic IDE returns a XEnumeration with a single item
371                // Note: the following code was adapted from
372                // svx/source/tbxctrls/tbunosearchcontrollers.cxx
373                css::uno::Reference<css::container::XEnumeration> xEnum(xSelection, css::uno::UNO_QUERY);
374                if (xEnum.is() && xEnum->hasMoreElements())
375                {
376                    OUString aString;
377                    xEnum->nextElement() >>= aString;
378                    return [CreateNSString(aString) autorelease];
379                }
380
381                // The following is needed for cells and text fields in Calc
382                // and Impress
383                css::uno::Reference<css::text::XTextRange> xTextRange(xSelection, css::uno::UNO_QUERY);
384                if (xTextRange.is())
385                    return [CreateNSString(xTextRange->getString()) autorelease];
386            }
387        }
388    }
389
390    return nil;
391}
392
393@interface NSResponder (SalFrameWindow)
394-(BOOL)accessibilityIsIgnored;
395@end
396
397@implementation SalFrameWindow
398-(id)initWithSalFrame: (AquaSalFrame*)pFrame
399{
400    mDraggingDestinationHandler = nil;
401    mbInWindowDidResize = NO;
402    mpLiveResizeTimer = nil;
403    mpResetParentWindowTimer = nil;
404    mbInSetFrame = false;
405    mpFrame = pFrame;
406    const SalFrameGeometry rFrameGeometry = pFrame->GetUnmirroredGeometry();
407    NSRect aRect = { { static_cast<CGFloat>(rFrameGeometry.x()), static_cast<CGFloat>(rFrameGeometry.y()) },
408                     { static_cast<CGFloat>(rFrameGeometry.width()), static_cast<CGFloat>(rFrameGeometry.height()) } };
409    pFrame->VCLToCocoa( aRect );
410    NSWindow* pNSWindow = [super initWithContentRect: aRect
411                                 styleMask: mpFrame->getStyleMask()
412                                 backing: NSBackingStoreBuffered
413                                 defer: Application::IsHeadlessModeEnabled()];
414
415    updateWindowCollectionBehavior( mpFrame->mnStyle, mpFrame->mpParent, pNSWindow );
416
417    [pNSWindow setReleasedWhenClosed: NO];
418
419    // Disable window restoration until we support it directly
420    [pNSWindow setRestorable: NO];
421
422    // tdf#137468: Restrict to 24-bit RGB as that is all that we can
423    // handle anyway. HDR is far off in the future for LibreOffice.
424    [pNSWindow setDynamicDepthLimit: NO];
425    [pNSWindow setDepthLimit: NSWindowDepthTwentyfourBitRGB];
426
427    return static_cast<SalFrameWindow *>(pNSWindow);
428}
429
430-(void)clearLiveResizeTimer
431{
432    if ( mpLiveResizeTimer )
433    {
434        [mpLiveResizeTimer invalidate];
435        [mpLiveResizeTimer release];
436        mpLiveResizeTimer = nil;
437    }
438}
439
440-(void)clearResetParentWindowTimer
441{
442    if ( mpResetParentWindowTimer )
443    {
444        [mpResetParentWindowTimer invalidate];
445        [mpResetParentWindowTimer release];
446        mpResetParentWindowTimer = nil;
447    }
448}
449
450-(void)dealloc
451{
452    [self clearLiveResizeTimer];
453    [self clearResetParentWindowTimer];
454    [super dealloc];
455}
456
457-(AquaSalFrame*)getSalFrame
458{
459    return mpFrame;
460}
461
462-(void)displayIfNeeded
463{
464    if( GetSalData() && GetSalData()->mpInstance )
465    {
466        SolarMutexGuard aGuard;
467        [super displayIfNeeded];
468    }
469}
470
471-(BOOL)containsMouse
472{
473    // is this event actually inside that NSWindow ?
474    NSPoint aPt = [NSEvent mouseLocation];
475    NSRect aFrameRect = [self frame];
476    bool bInRect = NSPointInRect( aPt, aFrameRect );
477    return bInRect;
478}
479
480-(BOOL)canBecomeKeyWindow
481{
482    if( (mpFrame->mnStyle &
483            ( SalFrameStyleFlags::FLOAT                 |
484              SalFrameStyleFlags::TOOLTIP               |
485              SalFrameStyleFlags::INTRO
486            )) == SalFrameStyleFlags::NONE )
487        return YES;
488    if( mpFrame->mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
489        return YES;
490    if( mpFrame->mbInternalFullScreen )
491        return YES;
492    return [super canBecomeKeyWindow];
493}
494
495-(void)windowDidBecomeKey: (NSNotification*)pNotification
496{
497    (void)pNotification;
498    SolarMutexGuard aGuard;
499
500    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
501    {
502        updateWindowCollectionBehavior( mpFrame->mnStyle, mpFrame->mpParent, mpFrame->mpNSWindow);
503
504        static const SalFrameStyleFlags nGuessDocument = SalFrameStyleFlags::MOVEABLE|
505                                            SalFrameStyleFlags::SIZEABLE|
506                                            SalFrameStyleFlags::CLOSEABLE;
507
508        // Reset dark mode colors in HITheme controls after printing
509        // In dark mode, after an NSPrintOperation has completed, macOS draws
510        // HITheme controls with light mode colors so reset all dark mode
511        // colors when an NSWindow gains focus.
512        mpFrame->UpdateDarkMode();
513
514        if( mpFrame->mpMenu )
515            mpFrame->mpMenu->setMainMenu();
516        else if( ! mpFrame->mpParent &&
517                 ( (mpFrame->mnStyle & nGuessDocument) == nGuessDocument || // set default menu for e.g. help
518                    mpFrame->mbInternalFullScreen ) )                               // set default menu for e.g. presentation
519        {
520            AquaSalMenu::setDefaultMenu();
521        }
522        mpFrame->CallCallback( SalEvent::GetFocus, nullptr );
523        mpFrame->SendPaintEvent(); // repaint controls as active
524
525        updateMenuBarVisibility( mpFrame );
526    }
527
528    // Prevent the same native input method popup that was cancelled in a
529    // previous call to [self windowDidResignKey:] from reappearing
530    [self endExtTextInput];
531}
532
533-(void)windowDidResignKey: (NSNotification*)pNotification
534{
535    (void)pNotification;
536    SolarMutexGuard aGuard;
537
538    // Commit any uncommitted text and cancel the native input method session
539    // whenever a window loses focus like in Safari, Firefox, and Excel
540    [self endExtTextInput];
541
542    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
543    {
544        mpFrame->CallCallback(SalEvent::LoseFocus, nullptr);
545        mpFrame->SendPaintEvent(); // repaint controls as inactive
546    }
547
548    // Show the menubar if application is in native full screen mode
549    // since hiding the menubar in that mode will cause the window's
550    // titlebar to fail to display or fail to hide when expected.
551    if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen )
552    {
553        [NSMenu setMenuBarVisible: YES];
554    }
555    // Show the dock and the menubar if there is no native modal dialog
556    // and if the key window is nil or is not a SalFrameWindow instance.
557    // If a SalFrameWindow is the key window, it should have already set
558    // the menubar visibility to match its LibreOffice full screen mode
559    // state.
560    else if ( ![NSApp modalWindow] )
561    {
562        NSWindow *pKeyWindow = [NSApp keyWindow];
563        if( !pKeyWindow || ![pKeyWindow isKindOfClass: [SalFrameWindow class]] )
564            [NSMenu setMenuBarVisible: YES];
565    }
566}
567
568-(void)windowDidChangeScreen: (NSNotification*)pNotification
569{
570    (void)pNotification;
571    SolarMutexGuard aGuard;
572
573    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
574        mpFrame->screenParametersChanged();
575
576    // Start timer to handle hiding of native child windows that have been
577    // dragged to a different screen.
578    if( !mpResetParentWindowTimer )
579    {
580        mpResetParentWindowTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1f target: self selector: @selector(resetParentWindow) userInfo: nil repeats: YES];
581        if( mpResetParentWindowTimer )
582        {
583            [mpResetParentWindowTimer retain];
584            [[NSRunLoop currentRunLoop] addTimer: mpResetParentWindowTimer forMode: NSEventTrackingRunLoopMode];
585        }
586    }
587}
588
589-(void)windowDidMove: (NSNotification*)pNotification
590{
591    (void)pNotification;
592    SolarMutexGuard aGuard;
593
594    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
595    {
596        mpFrame->UpdateFrameGeometry();
597        mpFrame->CallCallback( SalEvent::Move, nullptr );
598
599#if HAVE_FEATURE_SKIA
600        // tdf#163734 Flush parent frame when Skia is enabled
601        // When a dockable window is dragged by its titlebar, a rectangle
602        // may be drawn in its parent window. However, the Skia flush
603        // timer doesn't run until after the mouse button has been
604        // released (probably due to lowering of the Skia flush timer's
605        // priority to fix tdf#163734). So run the parent frame's Skia
606        // flush timer immediately to display the rectangle.
607        if ( SkiaHelper::isVCLSkiaEnabled() &&
608             mpFrame->mbShown && mpFrame->mpParent &&
609             AquaSalFrame::isAlive( mpFrame->mpParent ) &&
610             mpFrame->mpParent->mbShown )
611        {
612            AquaSalGraphics* pGraphics = mpFrame->mpParent->mpGraphics;
613            if ( pGraphics )
614                pGraphics->Flush();
615        }
616#endif
617    }
618}
619
620-(void)windowDidResize: (NSNotification*)pNotification
621{
622    SolarMutexGuard aGuard;
623
624    if ( mbInWindowDidResize )
625        return;
626
627    mbInWindowDidResize = YES;
628
629    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
630    {
631        mpFrame->UpdateFrameGeometry();
632        mpFrame->CallCallback( SalEvent::Resize, nullptr );
633
634        updateWinDataInLiveResize( [self inLiveResize] );
635        if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
636        {
637            // tdf#152703 Force relayout during live resizing of window
638            // During a live resize, macOS floods the application with
639            // windowDidResize: notifications so sending a paint event does
640            // not trigger redrawing with the new size.
641            // Instead, force relayout by dispatching all pending internal
642            // events and firing any pending timers.
643            freezeWindowSizeAndReschedule( self );
644
645            // Related: tdf128186 Always run timer in full screen mode windows
646            // When opening new windows by pressing and holding Command-N
647            // in a full screen window, some of the new windows will have
648            // content that does not fill the new window. So still run the
649            // timer on full screen windows even if live resizing ended
650            // during the call to freezeWindowSizeAndReschedule().
651            if ( ImplGetSVData()->mpWinData->mbIsLiveResize || [self styleMask] & NSWindowStyleMaskFullScreen )
652            {
653                // tdf#152703 Force repaint after live resizing ends
654                // Repost this notification so that this selector will be called
655                // at least once after live resizing ends
656                if ( !mpLiveResizeTimer )
657                {
658                    mpLiveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification repeats:YES];
659                    if ( mpLiveResizeTimer )
660                    {
661                        [mpLiveResizeTimer retain];
662
663                        // The timer won't fire without a call to
664                        // Application::Reschedule() unless we copy the fix for
665                        // #i84055# from vcl/osx/saltimer.cxx and add the timer
666                        // to the NSEventTrackingRunLoopMode run loop mode
667                        [[NSRunLoop currentRunLoop] addTimer:mpLiveResizeTimer forMode:NSEventTrackingRunLoopMode];
668                    }
669                }
670            }
671        }
672        else
673        {
674            [self clearLiveResizeTimer];
675        }
676
677        // tdf#158461 eliminate flicker during live resizing
678        // When using Skia/Metal, the window content will flicker while
679        // live resizing a window if we don't send a paint event.
680        mpFrame->SendPaintEvent();
681
682#if HAVE_FEATURE_SKIA
683        // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing
684        // The window will clear its background so when Skia/Metal is
685        // enabled, explicitly flush the Skia graphics to the window
686        // during live resizing or else nothing will be drawn until after
687        // live resizing has ended.
688        // Also, flushing during [self windowDidResize:] eliminates flicker
689        // by forcing this window's SkSurface to recreate its underlying
690        // CAMetalLayer with the new size. Flushing in
691        // [self displayIfNeeded] does not eliminate flicker so apparently
692        // [self windowDidResize:] is called earlier.
693        // Lastly, flush after calling AquaSalFrame::SendPaintEvent().
694        if ( SkiaHelper::isVCLSkiaEnabled() )
695        {
696            AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
697            if ( pGraphics )
698                pGraphics->Flush();
699        }
700#endif
701    }
702
703    mbInWindowDidResize = NO;
704}
705
706-(void)windowDidMiniaturize: (NSNotification*)pNotification
707{
708    (void)pNotification;
709    SolarMutexGuard aGuard;
710
711    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
712    {
713        mpFrame->mbShown = false;
714        mpFrame->UpdateFrameGeometry();
715        mpFrame->CallCallback( SalEvent::Resize, nullptr );
716    }
717}
718
719-(void)windowDidDeminiaturize: (NSNotification*)pNotification
720{
721    (void)pNotification;
722    SolarMutexGuard aGuard;
723
724    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
725    {
726        mpFrame->mbShown = true;
727        mpFrame->UpdateFrameGeometry();
728        mpFrame->CallCallback( SalEvent::Resize, nullptr );
729    }
730}
731
732-(BOOL)windowShouldClose: (NSNotification*)pNotification
733{
734    (void)pNotification;
735    SolarMutexGuard aGuard;
736
737    bool bRet = true;
738    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
739    {
740        // #i84461# end possible input
741        [self endExtTextInput];
742        if( AquaSalFrame::isAlive( mpFrame ) )
743        {
744            mpFrame->CallCallback( SalEvent::Close, nullptr );
745            bRet = false; // application will close the window or not, AppKit shouldn't
746            AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
747            assert( pTimer );
748            pTimer->handleWindowShouldClose();
749        }
750    }
751
752    return bRet;
753}
754
755-(void)windowWillEnterFullScreen: (NSNotification*)pNotification
756{
757    (void)pNotification;
758    SolarMutexGuard aGuard;
759
760    if( AquaSalFrame::isAlive( mpFrame) )
761    {
762        mpFrame->mbNativeFullScreen = true;
763
764        if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) )
765        {
766            mpFrame->maNativeFullScreenRestoreRect = mpFrame->maInternalFullScreenRestoreRect;
767        }
768        else
769        {
770            // Related: tdf#128186 restore rectangles are in VCL coordinates
771            NSRect aFrame = [mpFrame->getNSWindow() frame];
772            NSRect aContentRect = [mpFrame->getNSWindow() contentRectForFrameRect: aFrame];
773            mpFrame->CocoaToVCL( aContentRect );
774            mpFrame->maNativeFullScreenRestoreRect = aContentRect;
775        }
776
777        updateMenuBarVisibility( mpFrame );
778
779        // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property
780        // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1
781        // and create a new CAMetalLayer when the window resizes. When using
782        // Skia/Metal, flushing to an NSWindow during transitions into or out
783        // of native full screen mode causes the Skia/Metal surface to be
784        // drawn at the wrong window position which results in a noticeable
785        // flicker.
786        assert( SkiaHelper::isVCLSkiaEnabled() && "macos requires skia" );
787        if( SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster )
788        {
789            if( [mpFrame->getNSView() wantsLayer] )
790            {
791                CALayer *pLayer = [mpFrame->getNSView() layer];
792                if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] )
793                    [pLayer setHidden: YES];
794            }
795        }
796    }
797}
798
799-(void)windowDidFailToEnterFullScreen: (NSWindow *)pWindow
800{
801    (void)pWindow;
802    SolarMutexGuard aGuard;
803
804    if( AquaSalFrame::isAlive( mpFrame) )
805    {
806        mpFrame->mbNativeFullScreen = false;
807
808        mpFrame->maNativeFullScreenRestoreRect = NSZeroRect;
809
810        updateMenuBarVisibility( mpFrame );
811    }
812}
813
814-(void)windowWillExitFullScreen: (NSNotification*)pNotification
815{
816    (void)pNotification;
817    SolarMutexGuard aGuard;
818
819    // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property
820    // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1
821    // and create a new CAMetalLayer when the window resizes. When using
822    // Skia/Metal, flushing to an NSWindow during transitions into or out
823    // of native full screen mode causes the Skia/Metal surface to be
824    // drawn at the wrong window position which results in a noticeable
825    // flicker.
826    assert( SkiaHelper::isVCLSkiaEnabled() && "macos requires skia" );
827    if( AquaSalFrame::isAlive( mpFrame ) && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster )
828    {
829        if( [mpFrame->getNSView() wantsLayer] )
830        {
831            CALayer *pLayer = [mpFrame->getNSView() layer];
832            if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] )
833                [pLayer setHidden: YES];
834        }
835    }
836}
837
838-(void)windowDidExitFullScreen: (NSNotification*)pNotification
839{
840    (void)pNotification;
841    SolarMutexGuard aGuard;
842
843    if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbNativeFullScreen )
844    {
845        mpFrame->mbNativeFullScreen = false;
846
847        if( !NSIsEmptyRect( mpFrame->maNativeFullScreenRestoreRect ) )
848        {
849            // Related: tdf#128186 set window frame before exiting native full screen mode
850            // Setting the window frame just before exiting native full
851            // screen mode appears to set the desired non-full screen
852            // window frame without causing a noticeable flicker during
853            // the macOS default "exit full screen" animation.
854            NSRect aContentRect;
855            if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) )
856                aContentRect = mpFrame->maInternalFullScreenRestoreRect;
857            else
858                aContentRect = mpFrame->maNativeFullScreenRestoreRect;
859            mpFrame->VCLToCocoa( aContentRect );
860            NSRect aFrame = [NSWindow frameRectForContentRect: aContentRect styleMask: [mpFrame->getNSWindow() styleMask] & ~NSWindowStyleMaskFullScreen];
861            [mpFrame->getNSWindow() setFrame: aFrame display: mpFrame->mbShown ? YES : NO];
862
863            mpFrame->maNativeFullScreenRestoreRect = NSZeroRect;
864        }
865
866        updateMenuBarVisibility( mpFrame );
867    }
868}
869
870-(void)windowDidChangeBackingProperties:(NSNotification *)pNotification
871{
872    (void)pNotification;
873#if HAVE_FEATURE_SKIA
874    SolarMutexGuard aGuard;
875
876    sal::aqua::resetWindowScaling();
877
878    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
879    {
880        // tdf#147342 Notify Skia that the window's backing properties changed
881        if ( SkiaHelper::isVCLSkiaEnabled() )
882        {
883            AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
884            if ( pGraphics )
885                pGraphics->WindowBackingPropertiesChanged();
886        }
887    }
888#endif
889}
890
891-(void)windowWillStartLiveResize:(NSNotification *)pNotification
892{
893    SolarMutexGuard aGuard;
894
895    updateWinDataInLiveResize(true);
896}
897
898-(void)windowDidEndLiveResize:(NSNotification *)pNotification
899{
900    SolarMutexGuard aGuard;
901
902    updateWinDataInLiveResize(false);
903}
904
905-(void)dockMenuItemTriggered: (id)sender
906{
907    (void)sender;
908    SolarMutexGuard aGuard;
909
910    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
911        mpFrame->ToTop( SalFrameToTop::RestoreWhenMin | SalFrameToTop::GrabFocus );
912}
913
914-(css::uno::Reference < css::accessibility::XAccessibleContext >)accessibleContext
915{
916    return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext();
917}
918
919-(BOOL)isIgnoredWindow
920{
921    SolarMutexGuard aGuard;
922
923    // Treat tooltip windows as ignored
924    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
925        return (mpFrame->mnStyle & SalFrameStyleFlags::TOOLTIP) != SalFrameStyleFlags::NONE;
926    return YES;
927}
928
929-(id)accessibilityApplicationFocusedUIElement
930{
931    return [self accessibilityFocusedUIElement];
932}
933
934-(id)accessibilityFocusedUIElement
935{
936    // Treat tooltip windows as ignored
937    if ([self isIgnoredWindow])
938        return nil;
939
940    return [super accessibilityFocusedUIElement];
941}
942
943-(BOOL)accessibilityIsIgnored
944{
945    // Treat tooltip windows as ignored
946    if ([self isIgnoredWindow])
947        return YES;
948
949    return [super accessibilityIsIgnored];
950}
951
952-(BOOL)isAccessibilityElement
953{
954    return ![self accessibilityIsIgnored];
955}
956
957-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
958{
959  return [mDraggingDestinationHandler draggingEntered: sender];
960}
961
962-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
963{
964  return [mDraggingDestinationHandler draggingUpdated: sender];
965}
966
967-(void)draggingExited:(id <NSDraggingInfo>)sender
968{
969  [mDraggingDestinationHandler draggingExited: sender];
970}
971
972-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
973{
974  return [mDraggingDestinationHandler prepareForDragOperation: sender];
975}
976
977-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
978{
979  return [mDraggingDestinationHandler performDragOperation: sender];
980}
981
982-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
983{
984  [mDraggingDestinationHandler concludeDragOperation: sender];
985}
986
987-(void)registerDraggingDestinationHandler:(id)theHandler
988{
989  mDraggingDestinationHandler = theHandler;
990}
991
992-(void)unregisterDraggingDestinationHandler:(id)theHandler
993{
994    (void)theHandler;
995    mDraggingDestinationHandler = nil;
996}
997
998-(void)endExtTextInput
999{
1000    [self endExtTextInput:EndExtTextInputFlags::Complete];
1001}
1002
1003-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
1004{
1005    SalFrameView *pView = static_cast<SalFrameView*>([self firstResponder]);
1006    if (pView && [pView isKindOfClass:[SalFrameView class]])
1007        [pView endExtTextInput:nFlags];
1008}
1009
1010-(void)windowDidResizeWithTimer:(NSTimer *)pTimer
1011{
1012    if ( pTimer )
1013        [self windowDidResize:[pTimer userInfo]];
1014}
1015
1016-(void)resetParentWindow
1017{
1018    // Wait until the left mouse button has been released. Otherwise
1019    // the code below will cause native child windows to flicker while
1020    // dragging the window in a different screen than its parent window.
1021    if( [NSEvent pressedMouseButtons] & 0x1 )
1022        return;
1023
1024    // Stop hiding of child windows when dragged to a different screen
1025    // LibreOffice sets all dialog windows as a native child window of
1026    // its related document window in order to force the dialog windows
1027    // to always remain in front of their related document window.
1028    // However, for some unknown reason, if a native child window is
1029    // dragged to a different screen than its native parent window,
1030    // macOS will hide the native child window when the drag has ended.
1031    // So, once the current drag has finished, unattach and reattach
1032    // the native child window to its native parent window. This should
1033    // cause macOS to force the native child window to jump back to the
1034    // same screen as its native parent window.
1035    NSWindow *pParentWindow = [self parentWindow];
1036    if( pParentWindow && [pParentWindow screen] != [self screen] )
1037    {
1038        [pParentWindow removeChildWindow: self];
1039        [pParentWindow addChildWindow: self ordered: NSWindowAbove];
1040    }
1041
1042    [self clearResetParentWindowTimer];
1043}
1044
1045-(NSRect)constrainFrameRect: (NSRect)aFrameRect toScreen: (NSScreen *)pScreen
1046{
1047    SolarMutexGuard aGuard;
1048
1049    NSRect aRet = [super constrainFrameRect: aFrameRect toScreen: pScreen];
1050
1051    // Related: tdf#161623 the menubar and Dock are both hidden when a
1052    // window enters LibreOffice full screen mode. However, the call to
1053    // -[super constrainFrameRect:toScreen:] shrinks the window frame to
1054    // allow room for the menubar if the window is on the main screen. So,
1055    // force the return value to match the frame that LibreOffice expects.
1056    // Related: tdf#165448 skip fix for menu items inserted by macOS
1057    // If the window is in LibreOffice's internal full screen mode and
1058    // any of the menu items that macOS inserts into the windows menu
1059    // is selected, the frame size will be changed without calling
1060    // -[SalFrameWindow setFrame:display:]. So only use the fix for
1061    // tdf#161623 when the LibreOffice code explicitly resizes the frame.
1062    // Otherwise, selecting any of the menu items inserted by macOS will
1063    // cause the window to snap back to full screen size.
1064    if( mbInSetFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) )
1065        aRet = mpFrame->maInternalFullScreenExpectedRect;
1066
1067    return aRet;
1068}
1069
1070- (NSArray<NSWindow *> *)customWindowsToExitFullScreenForWindow: (NSWindow *)pWindow
1071{
1072    SolarMutexGuard aGuard;
1073
1074    // Related: tdf#161623 suppress animation when in internal full screen mode
1075    // LibreOffice's internal full screen mode fills the screen with a
1076    // regular window so suppress animation when exiting native full
1077    // screen mode.
1078    if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) )
1079        return [NSArray arrayWithObject: self];
1080
1081    return nil;
1082}
1083
1084-(void)setFrame: (NSRect)aFrameRect display: (BOOL)bFlag
1085{
1086    mbInSetFrame = true;
1087    [super setFrame: aFrameRect display: bFlag];
1088    mbInSetFrame = false;
1089}
1090
1091@end
1092
1093@implementation SalFrameView
1094+(void)unsetMouseFrame: (AquaSalFrame*)pFrame
1095{
1096    if( pFrame == s_pMouseFrame )
1097        s_pMouseFrame = nullptr;
1098}
1099
1100-(id)initWithSalFrame: (AquaSalFrame*)pFrame
1101{
1102    if ((self = [super initWithFrame: [NSWindow contentRectForFrameRect: [pFrame->getNSWindow() frame] styleMask: pFrame->mnStyleMask]]) != nil)
1103    {
1104        mDraggingDestinationHandler = nil;
1105        mpFrame = pFrame;
1106        mpChildWrapper = nil;
1107        mbNeedChildWrapper = NO;
1108        mpLastEvent = nil;
1109        mMarkedRange = NSMakeRange(NSNotFound, 0);
1110        mSelectedRange = NSMakeRange(NSNotFound, 0);
1111        mpMouseEventListener = nil;
1112        mpLastSuperEvent = nil;
1113        mfLastMagnifyTime = 0.0;
1114
1115        mbInEndExtTextInput = NO;
1116        mbInCommitMarkedText = NO;
1117        mpLastMarkedText = nil;
1118        mbTextInputWantsNonRepeatKeyDown = NO;
1119        mpLastTrackingArea = nil;
1120
1121        mbInViewDidChangeEffectiveAppearance = NO;
1122
1123        mpMouseDraggedTimer = nil;
1124        mpPendingMouseDraggedEvent = nil;
1125    }
1126
1127    return self;
1128}
1129
1130-(void)clearMouseDraggedTimer
1131{
1132    if ( mpMouseDraggedTimer )
1133    {
1134        [mpMouseDraggedTimer invalidate];
1135        [mpMouseDraggedTimer release];
1136        mpMouseDraggedTimer = nil;
1137    }
1138
1139    // Clear the pending mouse dragged event as well
1140    [self clearPendingMouseDraggedEvent];
1141}
1142
1143-(void)clearPendingMouseDraggedEvent
1144{
1145    if ( mpPendingMouseDraggedEvent )
1146    {
1147        [mpPendingMouseDraggedEvent release];
1148        mpPendingMouseDraggedEvent = nil;
1149    }
1150}
1151
1152-(void)dealloc
1153{
1154    [self clearMouseDraggedTimer];
1155    [self clearLastEvent];
1156    [self clearLastMarkedText];
1157    [self clearLastTrackingArea];
1158    [self revokeWrapper];
1159
1160    [super dealloc];
1161}
1162
1163-(AquaSalFrame*)getSalFrame
1164{
1165    return mpFrame;
1166}
1167
1168-(void)resetCursorRects
1169{
1170    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
1171    {
1172        // FIXME: does this leak the returned NSCursor of getCurrentCursor ?
1173        const NSRect aRect = { NSZeroPoint, NSMakeSize(mpFrame->GetUnmirroredGeometry().width(), mpFrame->GetUnmirroredGeometry().height()) };
1174        [self addCursorRect: aRect cursor: mpFrame->getCurrentCursor()];
1175    }
1176}
1177
1178-(BOOL)acceptsFirstResponder
1179{
1180    return YES;
1181}
1182
1183-(BOOL)acceptsFirstMouse: (NSEvent*)pEvent
1184{
1185    (void)pEvent;
1186    return YES;
1187}
1188
1189-(BOOL)isOpaque
1190{
1191    if( !mpFrame)
1192        return YES;
1193    if( !AquaSalFrame::isAlive( mpFrame))
1194        return YES;
1195    if( !mpFrame->getClipPath())
1196        return YES;
1197    return NO;
1198}
1199
1200-(void)drawRect: (NSRect)aRect
1201{
1202    ImplSVData* pSVData = ImplGetSVData();
1203    assert( pSVData );
1204    if ( !pSVData )
1205        return;
1206
1207    SolarMutexGuard aGuard;
1208    if (!mpFrame || !AquaSalFrame::isAlive(mpFrame))
1209        return;
1210
1211    updateWinDataInLiveResize([self inLiveResize]);
1212
1213    AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
1214    if (pGraphics)
1215    {
1216        pGraphics->UpdateWindow(aRect);
1217        if (mpFrame->getClipPath())
1218            [mpFrame->getNSWindow() invalidateShadow];
1219    }
1220}
1221
1222-(void)sendMouseEventToFrame: (NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(SalEvent)nEvent
1223{
1224    SolarMutexGuard aGuard;
1225
1226    AquaSalFrame* pDispatchFrame = AquaSalFrame::GetCaptureFrame();
1227    bool bIsCaptured = false;
1228    if( pDispatchFrame )
1229    {
1230        bIsCaptured = true;
1231        if( nEvent == SalEvent::MouseLeave ) // no leave events if mouse is captured
1232            nEvent = SalEvent::MouseMove;
1233    }
1234    else if( s_pMouseFrame )
1235        pDispatchFrame = s_pMouseFrame;
1236    else
1237        pDispatchFrame = mpFrame;
1238
1239    /* #i81645# Cocoa reports mouse events while a button is pressed
1240       to the window in which it was first pressed. This is reasonable and fine and
1241       gets one around most cases where on other platforms one uses CaptureMouse or XGrabPointer,
1242       however vcl expects mouse events to occur in the window the mouse is over, unless the
1243       mouse is explicitly captured. So we need to find the window the mouse is actually
1244       over for conformance with other platforms.
1245    */
1246    if( ! bIsCaptured && nButton && pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
1247    {
1248        // is this event actually inside that NSWindow ?
1249        NSPoint aPt = [NSEvent mouseLocation];
1250        NSRect aFrameRect = [pDispatchFrame->getNSWindow() frame];
1251
1252        if ( ! NSPointInRect( aPt, aFrameRect ) )
1253        {
1254            // no, it is not
1255            // now we need to find the one it may be in
1256            /* #i93756# we ant to get enumerate the application windows in z-order
1257               to check if any contains the mouse. This could be elegantly done with this
1258               code:
1259
1260               // use NSApp to check windows in ZOrder whether they contain the mouse pointer
1261               NSWindow* pWindow = [NSApp makeWindowsPerform: @selector(containsMouse) inOrder: YES];
1262               if( pWindow && [pWindow isMemberOfClass: [SalFrameWindow class]] )
1263                   pDispatchFrame = [(SalFrameWindow*)pWindow getSalFrame];
1264
1265               However if a non SalFrameWindow is on screen (like e.g. the file dialog)
1266               it can be hit with the containsMouse selector, which it doesn't support.
1267               Sadly NSApplication:makeWindowsPerform does not check (for performance reasons
1268               I assume) whether a window supports a selector before sending it.
1269            */
1270            AquaSalFrame* pMouseFrame = getMouseContainerFrame();
1271            if( pMouseFrame )
1272                pDispatchFrame = pMouseFrame;
1273        }
1274    }
1275
1276    if( pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
1277    {
1278        pDispatchFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
1279        pDispatchFrame->mnLastModifierFlags = [pEvent modifierFlags];
1280
1281        NSPoint aPt = [NSEvent mouseLocation];
1282        pDispatchFrame->CocoaToVCL( aPt );
1283
1284        sal_uInt16 nModMask = ImplGetModifierMask( [pEvent modifierFlags] );
1285        // #i82284# emulate ctrl left
1286        if( nModMask == KEY_MOD3 && nButton == MOUSE_LEFT )
1287        {
1288            nModMask    = 0;
1289            nButton     = MOUSE_RIGHT;
1290        }
1291
1292        SalMouseEvent aEvent;
1293        aEvent.mnTime   = pDispatchFrame->mnLastEventTime;
1294        aEvent.mnX = static_cast<tools::Long>(aPt.x) - pDispatchFrame->GetUnmirroredGeometry().x();
1295        aEvent.mnY = static_cast<tools::Long>(aPt.y) - pDispatchFrame->GetUnmirroredGeometry().y();
1296        aEvent.mnButton = nButton;
1297        aEvent.mnCode   =  aEvent.mnButton | nModMask;
1298
1299        if( AllSettings::GetLayoutRTL() )
1300            aEvent.mnX = pDispatchFrame->GetWidth() - 1 - aEvent.mnX;
1301
1302        pDispatchFrame->CallCallback( nEvent, &aEvent );
1303
1304        // tdf#155266 force flush after scrolling
1305        if (nButton == MOUSE_LEFT && nEvent == SalEvent::MouseMove)
1306            mpFrame->mbForceFlushScrolling = true;
1307    }
1308}
1309
1310-(void)mouseDown: (NSEvent*)pEvent
1311{
1312    if ( mpMouseEventListener != nil &&
1313        [mpMouseEventListener respondsToSelector: @selector(mouseDown:)])
1314    {
1315        [mpMouseEventListener mouseDown: pEvent];
1316    }
1317
1318    s_nLastButton = MOUSE_LEFT;
1319    [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonDown];
1320}
1321
1322-(void)mouseDragged: (NSEvent*)pEvent
1323{
1324    // tdf#163945 Coalesce mouse dragged events
1325    // When dragging a selection box on an empty background in Impress
1326    // while using Skia/Metal, the selection box would not keep up with
1327    // the pointer. The selection box would repaint sporadically or not
1328    // at all if the pointer was dragged rapidly and the status bar was
1329    // visible.
1330    // Apparently, flushing a graphics doesn't actually do much of
1331    // anything with Skia/Raster and Skia disabled so the selection box
1332    // repaints without any noticeable delay.
1333    // However, with Skia/Metal every flush of a graphics creates and
1334    // queues a new CAMetalLayer drawable. During rapid dragging, this
1335    // can lead to creating and queueing up to 200 drawables per second
1336    // leaving no spare time for the Impress selection box painting
1337    // timer to fire. So coalesce mouse dragged events so that only
1338    // a maximum of 50 mouse dragged events are dispatched per second.
1339    [self clearPendingMouseDraggedEvent];
1340    mpPendingMouseDraggedEvent = [pEvent retain];
1341    if ( !mpMouseDraggedTimer )
1342    {
1343        mpMouseDraggedTimer = [NSTimer scheduledTimerWithTimeInterval:0.025f target:self selector:@selector(mouseDraggedWithTimer:) userInfo:nil repeats:YES];
1344        if ( mpMouseDraggedTimer )
1345        {
1346            [mpMouseDraggedTimer retain];
1347
1348            // The timer won't fire without a call to
1349            // Application::Reschedule() unless we copy the fix for
1350            // #i84055# from vcl/osx/saltimer.cxx and add the timer
1351            // to the NSEventTrackingRunLoopMode run loop mode
1352            [[NSRunLoop currentRunLoop] addTimer:mpMouseDraggedTimer forMode:NSEventTrackingRunLoopMode];
1353        }
1354    }
1355}
1356
1357-(void)mouseUp: (NSEvent*)pEvent
1358{
1359    // Dispatch any pending mouse dragged event before dispatching the
1360    // mouse up event
1361    if ( mpPendingMouseDraggedEvent )
1362        [self mouseDraggedWithTimer: nil];
1363    [self clearMouseDraggedTimer];
1364
1365    s_nLastButton = 0;
1366    [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonUp];
1367}
1368
1369-(void)mouseMoved: (NSEvent*)pEvent
1370{
1371    s_nLastButton = 0;
1372    [self sendMouseEventToFrame:pEvent button:0 eventtype:SalEvent::MouseMove];
1373}
1374
1375-(void)mouseEntered: (NSEvent*)pEvent
1376{
1377    s_pMouseFrame = mpFrame;
1378
1379    // #i107215# the only mouse events we get when inactive are enter/exit
1380    // actually we would like to have all of them, but better none than some
1381    if( [NSApp isActive] )
1382        [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseMove];
1383}
1384
1385-(void)mouseExited: (NSEvent*)pEvent
1386{
1387    if( s_pMouseFrame == mpFrame )
1388        s_pMouseFrame = nullptr;
1389
1390    // #i107215# the only mouse events we get when inactive are enter/exit
1391    // actually we would like to have all of them, but better none than some
1392    if( [NSApp isActive] )
1393        [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseLeave];
1394}
1395
1396-(void)rightMouseDown: (NSEvent*)pEvent
1397{
1398    s_nLastButton = MOUSE_RIGHT;
1399    [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonDown];
1400}
1401
1402-(void)rightMouseDragged: (NSEvent*)pEvent
1403{
1404    s_nLastButton = MOUSE_RIGHT;
1405    [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseMove];
1406}
1407
1408-(void)rightMouseUp: (NSEvent*)pEvent
1409{
1410    s_nLastButton = 0;
1411    [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonUp];
1412}
1413
1414-(void)otherMouseDown: (NSEvent*)pEvent
1415{
1416    if( [pEvent buttonNumber] == 2 )
1417    {
1418        s_nLastButton = MOUSE_MIDDLE;
1419        [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonDown];
1420    }
1421    else
1422        s_nLastButton = 0;
1423}
1424
1425-(void)otherMouseDragged: (NSEvent*)pEvent
1426{
1427    if( [pEvent buttonNumber] == 2 )
1428    {
1429        s_nLastButton = MOUSE_MIDDLE;
1430        [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseMove];
1431    }
1432    else
1433        s_nLastButton = 0;
1434}
1435
1436-(void)otherMouseUp: (NSEvent*)pEvent
1437{
1438    s_nLastButton = 0;
1439    if( [pEvent buttonNumber] == 2 )
1440        [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonUp];
1441}
1442
1443- (void)magnifyWithEvent: (NSEvent*)pEvent
1444{
1445    SolarMutexGuard aGuard;
1446
1447    // TODO: ??  -(float)magnification;
1448    if( AquaSalFrame::isAlive( mpFrame ) )
1449    {
1450        const NSTimeInterval fMagnifyTime = [pEvent timestamp];
1451        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( fMagnifyTime * 1000.0 );
1452        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
1453
1454        // check if this is a new series of magnify events
1455        static const NSTimeInterval fMaxDiffTime = 0.3;
1456        const bool bNewSeries = (fMagnifyTime - mfLastMagnifyTime > fMaxDiffTime);
1457
1458        if( bNewSeries )
1459            mfMagnifyDeltaSum = 0.0;
1460        mfMagnifyDeltaSum += [pEvent magnification];
1461
1462        mfLastMagnifyTime = [pEvent timestamp];
1463// TODO: change to 0.1 when CommandWheelMode::ZOOM handlers allow finer zooming control
1464        static const float fMagnifyFactor = 0.25*500; // steps are 500 times smaller for -magnification
1465        static const float fMinMagnifyStep = 15.0 / fMagnifyFactor;
1466        if( fabs(mfMagnifyDeltaSum) <= fMinMagnifyStep )
1467            return;
1468
1469        // adapt NSEvent-sensitivity to application expectations
1470        // TODO: rather make CommandWheelMode::ZOOM handlers smarter
1471        const float fDeltaZ = mfMagnifyDeltaSum * fMagnifyFactor;
1472        int nDeltaZ = basegfx::fround<int>( fDeltaZ );
1473        if( !nDeltaZ )
1474        {
1475            // handle new series immediately
1476            if( !bNewSeries )
1477                return;
1478            nDeltaZ = (fDeltaZ >= 0.0) ? +1 : -1;
1479        }
1480        // eventually give credit for delta sum
1481        mfMagnifyDeltaSum -= nDeltaZ / fMagnifyFactor;
1482
1483        NSPoint aPt = [NSEvent mouseLocation];
1484        mpFrame->CocoaToVCL( aPt );
1485
1486        SalWheelMouseEvent aEvent;
1487        aEvent.mnTime           = mpFrame->mnLastEventTime;
1488        aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x();
1489        aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y();
1490        aEvent.mnCode           = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
1491        aEvent.mnCode           |= KEY_MOD1; // we want zooming, no scrolling
1492        aEvent.mbDeltaIsPixel   = true;
1493
1494        if( AllSettings::GetLayoutRTL() )
1495            aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX;
1496
1497        aEvent.mnDelta = nDeltaZ;
1498        aEvent.mnNotchDelta = (nDeltaZ >= 0) ? +1 : -1;
1499        if( aEvent.mnDelta == 0 )
1500            aEvent.mnDelta = aEvent.mnNotchDelta;
1501        aEvent.mbHorz = false;
1502        sal_uInt32 nScrollLines = nDeltaZ;
1503        if (nScrollLines == 0)
1504            nScrollLines = 1;
1505        aEvent.mnScrollLines = nScrollLines;
1506        mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
1507    }
1508}
1509
1510- (void)rotateWithEvent: (NSEvent*)pEvent
1511{
1512    //Rotation : -(float)rotation;
1513    // TODO: create new CommandType so rotation is available to the applications
1514    (void)pEvent;
1515}
1516
1517- (void)swipeWithEvent: (NSEvent*)pEvent
1518{
1519    SolarMutexGuard aGuard;
1520
1521    if( AquaSalFrame::isAlive( mpFrame ) )
1522    {
1523        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
1524        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
1525
1526        // merge pending scroll wheel events
1527        CGFloat dX = 0.0;
1528        CGFloat dY = 0.0;
1529        for(;;)
1530        {
1531            dX += [pEvent deltaX];
1532            dY += [pEvent deltaY];
1533            NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskSwipe
1534            untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
1535            if( !pNextEvent )
1536                break;
1537            pEvent = pNextEvent;
1538        }
1539
1540        NSPoint aPt = [NSEvent mouseLocation];
1541        mpFrame->CocoaToVCL( aPt );
1542
1543        SalWheelMouseEvent aEvent;
1544        aEvent.mnTime           = mpFrame->mnLastEventTime;
1545        aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x();
1546        aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y();
1547        // tdf#151423 Ignore all modifiers for swipe events
1548        // It appears that devices that generate swipe events can generate
1549        // both vertical and horizontal swipe events. So, behave like most
1550        // macOS applications and ignore all modifiers if this a swipe event.
1551        aEvent.mnCode           = 0;
1552        aEvent.mbDeltaIsPixel   = true;
1553
1554        if( AllSettings::GetLayoutRTL() )
1555            aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX;
1556
1557        if( dX != 0.0 )
1558        {
1559            aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
1560            aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
1561            if( aEvent.mnDelta == 0 )
1562                aEvent.mnDelta = aEvent.mnNotchDelta;
1563            aEvent.mbHorz = true;
1564            aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
1565            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
1566        }
1567        if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ))
1568        {
1569            aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
1570            aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
1571            if( aEvent.mnDelta == 0 )
1572                aEvent.mnDelta = aEvent.mnNotchDelta;
1573            aEvent.mbHorz = false;
1574            aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
1575            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
1576        }
1577
1578        // tdf#155266 force flush after scrolling
1579        mpFrame->mbForceFlushScrolling = true;
1580    }
1581}
1582
1583-(void)scrollWheel: (NSEvent*)pEvent
1584{
1585    SolarMutexGuard aGuard;
1586
1587    if( AquaSalFrame::isAlive( mpFrame ) )
1588    {
1589        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
1590        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
1591
1592        // merge pending scroll wheel events
1593        CGFloat dX = 0.0;
1594        CGFloat dY = 0.0;
1595        bool bAllowModifiers = isMouseScrollWheelEvent( pEvent );
1596        for(;;)
1597        {
1598            dX += [pEvent deltaX];
1599            dY += [pEvent deltaY];
1600            NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel
1601                untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
1602            if( !pNextEvent || ( isMouseScrollWheelEvent( pNextEvent ) != bAllowModifiers ) )
1603                break;
1604            pEvent = pNextEvent;
1605        }
1606
1607        NSPoint aPt = [NSEvent mouseLocation];
1608        mpFrame->CocoaToVCL( aPt );
1609
1610        SalWheelMouseEvent aEvent;
1611        aEvent.mnTime         = mpFrame->mnLastEventTime;
1612        aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x();
1613        aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y();
1614        // tdf#151423 Only allow modifiers for mouse scrollwheel events
1615        // The Command modifier converts scrollwheel events into
1616        // magnification events and the Shift modifier converts vertical
1617        // scrollwheel events into horizontal scrollwheel events. This
1618        // behavior is reasonable for mouse scrollwheel events since many
1619        // mice only have a single, vertical scrollwheel but trackpads
1620        // already have specific gestures for magnification and horizontal
1621        // scrolling. So, behave like most macOS applications and ignore
1622        // all modifiers if this a trackpad scrollwheel event.
1623        aEvent.mnCode         = bAllowModifiers ? ImplGetModifierMask( mpFrame->mnLastModifierFlags ) : 0;
1624        aEvent.mbDeltaIsPixel = false;
1625
1626        if( AllSettings::GetLayoutRTL() )
1627            aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX;
1628
1629        if( dX != 0.0 )
1630        {
1631            aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
1632            aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
1633            if( aEvent.mnDelta == 0 )
1634                aEvent.mnDelta = aEvent.mnNotchDelta;
1635            aEvent.mbHorz = true;
1636            sal_uInt32 nScrollLines = fabs(dX) / WHEEL_EVENT_FACTOR;
1637            if (nScrollLines == 0)
1638                nScrollLines = 1;
1639            aEvent.mnScrollLines = nScrollLines;
1640
1641            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
1642        }
1643        if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ) )
1644        {
1645            aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
1646            aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
1647            if( aEvent.mnDelta == 0 )
1648                aEvent.mnDelta = aEvent.mnNotchDelta;
1649            aEvent.mbHorz = false;
1650            sal_uInt32 nScrollLines = fabs(dY) / WHEEL_EVENT_FACTOR;
1651            if (nScrollLines == 0)
1652                nScrollLines = 1;
1653            aEvent.mnScrollLines = nScrollLines;
1654
1655            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
1656        }
1657
1658        // tdf#155266 force flush after scrolling
1659        mpFrame->mbForceFlushScrolling = true;
1660    }
1661}
1662
1663
1664-(void)keyDown: (NSEvent*)pEvent
1665{
1666    SolarMutexGuard aGuard;
1667
1668    if( AquaSalFrame::isAlive( mpFrame ) )
1669    {
1670        // Retain the event as it will be released sometime before a key up
1671        // event is dispatched
1672        [self clearLastEvent];
1673        mpLastEvent = [pEvent retain];
1674
1675        mbInKeyInput = true;
1676        mbNeedSpecialKeyHandle = false;
1677        mbKeyHandled = false;
1678
1679        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
1680        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
1681
1682        if( ! [self handleKeyDownException: pEvent] )
1683        {
1684            sal_uInt16 nKeyCode = ImplMapKeyCode( [pEvent keyCode] );
1685            if ( nKeyCode == KEY_DELETE && mbTextInputWantsNonRepeatKeyDown )
1686            {
1687                // tdf#42437 Enable press-and-hold special character input method
1688                // Emulate the press-and-hold behavior of the TextEdit
1689                // application by deleting the marked text when only the
1690                // Delete key is pressed and keep the marked text when the
1691                // Backspace key or Fn-Delete keys are pressed.
1692                if ( [pEvent keyCode] == 51 )
1693                {
1694                    [self deleteTextInputWantsNonRepeatKeyDown];
1695                }
1696                else
1697                {
1698                    [self unmarkText];
1699                    mbKeyHandled = true;
1700                    mbInKeyInput = false;
1701                }
1702
1703                [self endExtTextInput];
1704                return;
1705            }
1706
1707            NSArray* pArray = [NSArray arrayWithObject: pEvent];
1708            [self interpretKeyEvents: pArray];
1709
1710            // Handle repeat key events by explicitly inserting the text if
1711            // -[NSResponder interpretKeyEvents:] does not insert or mark any
1712            // text. Note: do not do this step if there is uncommitted text.
1713            // Related: tdf#42437 Skip special press-and-hold handling for action keys
1714            // Pressing and holding action keys such as arrow keys must not be
1715            // handled like pressing and holding a character key as it will
1716            // insert unexpected text.
1717            if ( !mbKeyHandled && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] )
1718            {
1719                NSString *pChars = [mpLastEvent characters];
1720                if ( pChars )
1721                    [self insertText:pChars replacementRange:NSMakeRange( 0, [pChars length] )];
1722            }
1723            // tdf#42437 Enable press-and-hold special character input method
1724            // Emulate the press-and-hold behavior of the TextEdit application
1725            // by committing an empty string for key down events dispatched
1726            // while the special character input method popup is displayed.
1727            else if ( mpLastMarkedText && mbTextInputWantsNonRepeatKeyDown && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && ![mpLastEvent isARepeat] )
1728            {
1729                // If the escape or return key is pressed, unmark the text to
1730                // skip deletion of marked text
1731                if ( nKeyCode == KEY_ESCAPE || nKeyCode == KEY_RETURN )
1732                    [self unmarkText];
1733                [self insertText:[NSString string] replacementRange:NSMakeRange( NSNotFound, 0 )];
1734            }
1735        }
1736
1737        mbInKeyInput = false;
1738    }
1739}
1740
1741-(BOOL)handleKeyDownException:(NSEvent*)pEvent
1742{
1743    // check for a very special set of modified characters
1744    NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers];
1745
1746    if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
1747    {
1748        /* #i103102# key events with command and alternate don't make it through
1749           interpretKeyEvents (why?). Try to dispatch them here first,
1750           if not successful continue normally
1751        */
1752        if( (mpFrame->mnLastModifierFlags & (NSEventModifierFlagOption | NSEventModifierFlagCommand))
1753                    == (NSEventModifierFlagOption | NSEventModifierFlagCommand) )
1754        {
1755            if( [self sendSingleCharacter: mpLastEvent] )
1756                return YES;
1757        }
1758    }
1759    return NO;
1760}
1761
1762-(void)flagsChanged: (NSEvent*)pEvent
1763{
1764    SolarMutexGuard aGuard;
1765
1766    if( AquaSalFrame::isAlive( mpFrame ) )
1767    {
1768        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
1769        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
1770    }
1771}
1772
1773-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange
1774{
1775    (void) replacementRange; // FIXME: surely it must be used
1776
1777    SolarMutexGuard aGuard;
1778
1779    [self deleteTextInputWantsNonRepeatKeyDown];
1780
1781    // Ignore duplicate events that are sometimes posted during cancellation
1782    // of the native input method session. This usually happens when
1783    // [self endExtTextInput] is called from [self windowDidBecomeKey:] and,
1784    // if the native input method popup, that was cancelled in a
1785    // previous call to [self windowDidResignKey:], has reappeared. In such
1786    // cases, the native input context posts the reappearing popup's
1787    // uncommitted text.
1788    if (mbInEndExtTextInput && !mbInCommitMarkedText)
1789        return;
1790
1791    if( AquaSalFrame::isAlive( mpFrame ) )
1792    {
1793        NSString* pInsert = nil;
1794        if( [aString isKindOfClass: [NSAttributedString class]] )
1795            pInsert = [aString string];
1796        else
1797            pInsert = aString;
1798
1799        int nLen = 0;
1800        if( pInsert && ( nLen = [pInsert length] ) > 0 )
1801        {
1802            OUString aInsertString( GetOUString( pInsert ) );
1803             // aCharCode initializer is safe since aInsertString will at least contain '\0'
1804            sal_Unicode aCharCode = *aInsertString.getStr();
1805
1806            if( nLen == 1 &&
1807                aCharCode < 0x80 &&
1808                aCharCode > 0x1f &&
1809                ! [self hasMarkedText ]
1810                )
1811            {
1812                sal_uInt16 nKeyCode = ImplMapCharCode( aCharCode );
1813                unsigned int nLastModifiers = mpFrame->mnLastModifierFlags;
1814
1815                // #i99567#
1816                // find out the unmodified key code
1817
1818                // sanity check
1819                if( mpLastEvent && ( [mpLastEvent type] == NSEventTypeKeyDown || [mpLastEvent type] == NSEventTypeKeyUp ) )
1820                {
1821                    // get unmodified string
1822                    NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers];
1823                    if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
1824                    {
1825                        // map the unmodified key code
1826                        unichar keyChar = [pUnmodifiedString characterAtIndex: 0];
1827                        nKeyCode = ImplMapCharCode( keyChar );
1828                    }
1829                    nLastModifiers = [mpLastEvent modifierFlags];
1830
1831                }
1832                // #i99567#
1833                // applications and vcl's edit fields ignore key events with ALT
1834                // however we're at a place where we know text should be inserted
1835                // so it seems we need to strip the Alt modifier here
1836                if( (nLastModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption | NSEventModifierFlagCommand))
1837                    == NSEventModifierFlagOption )
1838                {
1839                    nLastModifiers = 0;
1840                }
1841                [self sendKeyInputAndReleaseToFrame: nKeyCode character: aCharCode modifiers: nLastModifiers];
1842            }
1843            else
1844            {
1845                SalExtTextInputEvent aEvent;
1846                aEvent.maText           = aInsertString;
1847                aEvent.mpTextAttr       = nullptr;
1848                aEvent.mnCursorPos      = aInsertString.getLength();
1849                aEvent.mnCursorFlags    = 0;
1850                mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent );
1851                if( AquaSalFrame::isAlive( mpFrame ) )
1852                    mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
1853            }
1854        }
1855        else
1856        {
1857            SalExtTextInputEvent aEvent;
1858            aEvent.maText.clear();
1859            aEvent.mpTextAttr       = nullptr;
1860            aEvent.mnCursorPos      = 0;
1861            aEvent.mnCursorFlags    = 0;
1862            mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent );
1863            if( AquaSalFrame::isAlive( mpFrame ) )
1864                mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
1865
1866        }
1867        [self unmarkText];
1868    }
1869
1870    // Mark event as handled even if the frame isn't valid like is done in
1871    // [self setMarkedText:selectedRange:replacementRange:] and
1872    // [self doCommandBySelector:]
1873    mbKeyHandled = true;
1874}
1875
1876-(void)insertTab: (id)aSender
1877{
1878    (void)aSender;
1879    [self sendKeyInputAndReleaseToFrame: KEY_TAB character: '\t' modifiers: 0];
1880}
1881
1882-(void)insertBacktab: (id)aSender
1883{
1884    (void)aSender;
1885    [self sendKeyInputAndReleaseToFrame: (KEY_TAB | KEY_SHIFT) character: '\t' modifiers: 0];
1886}
1887
1888-(void)moveLeft: (id)aSender
1889{
1890    (void)aSender;
1891    [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: 0];
1892}
1893
1894-(void)moveLeftAndModifySelection: (id)aSender
1895{
1896    (void)aSender;
1897    [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: NSEventModifierFlagShift];
1898}
1899
1900-(void)moveBackwardAndModifySelection: (id)aSender
1901{
1902    (void)aSender;
1903    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_BACKWARD character: 0  modifiers: 0];
1904}
1905
1906-(void)moveRight: (id)aSender
1907{
1908    (void)aSender;
1909    [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: 0];
1910}
1911
1912-(void)moveRightAndModifySelection: (id)aSender
1913{
1914    (void)aSender;
1915    [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: NSEventModifierFlagShift];
1916}
1917
1918-(void)moveForwardAndModifySelection: (id)aSender
1919{
1920    (void)aSender;
1921    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_FORWARD character: 0  modifiers: 0];
1922}
1923
1924-(void)moveWordLeft: (id)aSender
1925{
1926    (void)aSender;
1927    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0  modifiers: 0];
1928}
1929
1930-(void)moveWordBackward: (id)aSender
1931{
1932    (void)aSender;
1933    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0  modifiers: 0];
1934}
1935
1936-(void)moveWordBackwardAndModifySelection: (id)aSender
1937{
1938    (void)aSender;
1939    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0  modifiers: 0];
1940}
1941
1942-(void)moveWordLeftAndModifySelection: (id)aSender
1943{
1944    (void)aSender;
1945    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0  modifiers: 0];
1946}
1947
1948-(void)moveWordRight: (id)aSender
1949{
1950    (void)aSender;
1951    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0  modifiers: 0];
1952}
1953
1954-(void)moveWordForward: (id)aSender
1955{
1956    (void)aSender;
1957    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0  modifiers: 0];
1958}
1959
1960-(void)moveWordForwardAndModifySelection: (id)aSender
1961{
1962    (void)aSender;
1963    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0  modifiers: 0];
1964}
1965
1966-(void)moveWordRightAndModifySelection: (id)aSender
1967{
1968    (void)aSender;
1969    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0  modifiers: 0];
1970}
1971
1972-(void)moveToEndOfLine: (id)aSender
1973{
1974    (void)aSender;
1975    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0  modifiers: 0];
1976}
1977
1978-(void)moveToRightEndOfLine: (id)aSender
1979{
1980    (void)aSender;
1981    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0  modifiers: 0];
1982}
1983
1984-(void)moveToEndOfLineAndModifySelection: (id)aSender
1985{
1986    (void)aSender;
1987    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0  modifiers: 0];
1988}
1989
1990-(void)moveToRightEndOfLineAndModifySelection: (id)aSender
1991{
1992    (void)aSender;
1993    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0  modifiers: 0];
1994}
1995
1996-(void)moveToBeginningOfLine: (id)aSender
1997{
1998    (void)aSender;
1999    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0  modifiers: 0];
2000}
2001
2002-(void)moveToLeftEndOfLine: (id)aSender
2003{
2004    (void)aSender;
2005    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0  modifiers: 0];
2006}
2007
2008-(void)moveToBeginningOfLineAndModifySelection: (id)aSender
2009{
2010    (void)aSender;
2011    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0  modifiers: 0];
2012}
2013
2014-(void)moveToLeftEndOfLineAndModifySelection: (id)aSender
2015{
2016    (void)aSender;
2017    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0  modifiers: 0];
2018}
2019
2020-(void)moveToEndOfParagraph: (id)aSender
2021{
2022    (void)aSender;
2023    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0  modifiers: 0];
2024}
2025
2026-(void)moveToEndOfParagraphAndModifySelection: (id)aSender
2027{
2028    (void)aSender;
2029    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0  modifiers: 0];
2030}
2031
2032-(void)moveParagraphForward: (id)aSender
2033{
2034    (void)aSender;
2035    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0  modifiers: 0];
2036}
2037
2038-(void)moveParagraphForwardAndModifySelection: (id)aSender
2039{
2040    (void)aSender;
2041    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0  modifiers: 0];
2042}
2043
2044-(void)moveToBeginningOfParagraph: (id)aSender
2045{
2046    (void)aSender;
2047    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0  modifiers: 0];
2048}
2049
2050-(void)moveParagraphBackward: (id)aSender
2051{
2052    (void)aSender;
2053    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0  modifiers: 0];
2054}
2055
2056-(void)moveToBeginningOfParagraphAndModifySelection: (id)aSender
2057{
2058    (void)aSender;
2059    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0  modifiers: 0];
2060}
2061
2062-(void)moveParagraphBackwardAndModifySelection: (id)aSender
2063{
2064    (void)aSender;
2065    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0  modifiers: 0];
2066}
2067
2068-(void)moveToEndOfDocument: (id)aSender
2069{
2070    (void)aSender;
2071    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0  modifiers: 0];
2072}
2073
2074-(void)scrollToEndOfDocument: (id)aSender
2075{
2076    (void)aSender;
2077    // this is not exactly what we should do, but it makes "End" and "Shift-End" behave consistent
2078    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0  modifiers: 0];
2079}
2080
2081-(void)moveToEndOfDocumentAndModifySelection: (id)aSender
2082{
2083    (void)aSender;
2084    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_DOCUMENT character: 0  modifiers: 0];
2085}
2086
2087-(void)moveToBeginningOfDocument: (id)aSender
2088{
2089    (void)aSender;
2090    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0  modifiers: 0];
2091}
2092
2093-(void)scrollToBeginningOfDocument: (id)aSender
2094{
2095    (void)aSender;
2096    // this is not exactly what we should do, but it makes "Home" and "Shift-Home" behave consistent
2097    [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0  modifiers: 0];
2098}
2099
2100-(void)moveToBeginningOfDocumentAndModifySelection: (id)aSender
2101{
2102    (void)aSender;
2103    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT character: 0  modifiers: 0];
2104}
2105
2106-(void)moveUp: (id)aSender
2107{
2108    (void)aSender;
2109    [self sendKeyInputAndReleaseToFrame: KEY_UP character: 0 modifiers: 0];
2110}
2111
2112-(void)moveDown: (id)aSender
2113{
2114    (void)aSender;
2115    [self sendKeyInputAndReleaseToFrame: KEY_DOWN character: 0 modifiers: 0];
2116}
2117
2118-(void)insertNewline: (id)aSender
2119{
2120    (void)aSender;
2121    // #i91267# make enter and shift-enter work by evaluating the modifiers
2122    [self sendKeyInputAndReleaseToFrame: KEY_RETURN character: '\n' modifiers: mpFrame->mnLastModifierFlags];
2123}
2124
2125-(void)deleteBackward: (id)aSender
2126{
2127    (void)aSender;
2128    [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0];
2129}
2130
2131-(void)deleteForward: (id)aSender
2132{
2133    (void)aSender;
2134    [self sendKeyInputAndReleaseToFrame: KEY_DELETE character: 0x7f modifiers: 0];
2135}
2136
2137-(void)deleteBackwardByDecomposingPreviousCharacter: (id)aSender
2138{
2139    (void)aSender;
2140    [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0];
2141}
2142
2143-(void)deleteWordBackward: (id)aSender
2144{
2145    (void)aSender;
2146    [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_BACKWARD character: 0  modifiers: 0];
2147}
2148
2149-(void)deleteWordForward: (id)aSender
2150{
2151    (void)aSender;
2152    [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_FORWARD character: 0  modifiers: 0];
2153}
2154
2155-(void)deleteToBeginningOfLine: (id)aSender
2156{
2157    (void)aSender;
2158    [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_LINE character: 0  modifiers: 0];
2159}
2160
2161-(void)deleteToEndOfLine: (id)aSender
2162{
2163    (void)aSender;
2164    [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_LINE character: 0  modifiers: 0];
2165}
2166
2167-(void)deleteToBeginningOfParagraph: (id)aSender
2168{
2169    (void)aSender;
2170    [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH character: 0  modifiers: 0];
2171}
2172
2173-(void)deleteToEndOfParagraph: (id)aSender
2174{
2175    (void)aSender;
2176    [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_PARAGRAPH character: 0  modifiers: 0];
2177}
2178
2179-(void)insertLineBreak: (id)aSender
2180{
2181    (void)aSender;
2182    [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_LINEBREAK character: 0  modifiers: 0];
2183}
2184
2185-(void)insertParagraphSeparator: (id)aSender
2186{
2187    (void)aSender;
2188    [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_PARAGRAPH character: 0  modifiers: 0];
2189}
2190
2191-(void)selectWord: (id)aSender
2192{
2193    (void)aSender;
2194    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD character: 0  modifiers: 0];
2195}
2196
2197-(void)selectLine: (id)aSender
2198{
2199    (void)aSender;
2200    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_LINE character: 0  modifiers: 0];
2201}
2202
2203-(void)selectParagraph: (id)aSender
2204{
2205    (void)aSender;
2206    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_PARAGRAPH character: 0  modifiers: 0];
2207}
2208
2209-(void)selectAll: (id)aSender
2210{
2211    (void)aSender;
2212    [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_ALL character: 0  modifiers: 0];
2213}
2214
2215-(void)cancelOperation: (id)aSender
2216{
2217    (void)aSender;
2218    [self sendKeyInputAndReleaseToFrame: KEY_ESCAPE character: 0x1b modifiers: 0];
2219}
2220
2221-(void)noop: (id)aSender
2222{
2223    (void)aSender;
2224    if( ! mbKeyHandled && mpLastEvent )
2225    {
2226        // Related tdf#162843: replace the event's string parameter
2227        // When using the Dvorak - QWERTY keyboard and the Command key
2228        // is pressed, any key events that match a disabled menu item
2229        // are handled here. However, the Dvorak - QWERTY event's
2230        // charactersIgnoringModifiers string can cause cutting and
2231        // copying to fail in the Find toolbar and the Find and Replace
2232        // dialog so replace the event's charactersIgnoringModifiers
2233        // string with the event's character string.
2234        NSEvent* pEvent = mpLastEvent;
2235        NSEventModifierFlags nModMask = [mpLastEvent modifierFlags];
2236        if( nModMask & NSEventModifierFlagCommand )
2237        {
2238            switch( [mpLastEvent type] )
2239            {
2240                case NSEventTypeKeyDown:
2241                case NSEventTypeKeyUp:
2242                case NSEventTypeFlagsChanged:
2243                {
2244                    NSString* pCharacters = [mpLastEvent characters];
2245                    NSString* pCharactersIgnoringModifiers = ( nModMask & NSEventModifierFlagShift ) ? [pCharacters uppercaseString] : pCharacters;
2246                    pEvent = [NSEvent keyEventWithType: [pEvent type] location: [pEvent locationInWindow] modifierFlags: nModMask timestamp: [pEvent timestamp] windowNumber: [pEvent windowNumber] context: nil characters: pCharacters charactersIgnoringModifiers: pCharactersIgnoringModifiers isARepeat: [pEvent isARepeat] keyCode: [pEvent keyCode]];
2247                    break;
2248                }
2249                default:
2250                    break;
2251            }
2252        }
2253
2254        if( ! [self sendSingleCharacter:pEvent] )
2255        {
2256            /* prevent recursion */
2257            if( mpLastEvent != mpLastSuperEvent && [NSApp respondsToSelector: @selector(sendSuperEvent:)] )
2258            {
2259                id pLastSuperEvent = mpLastSuperEvent;
2260                mpLastSuperEvent = mpLastEvent;
2261                [NSApp performSelector:@selector(sendSuperEvent:) withObject: mpLastEvent];
2262                mpLastSuperEvent = pLastSuperEvent;
2263
2264                std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent );
2265                if( it != GetSalData()->maKeyEventAnswer.end() )
2266                    it->second = true;
2267            }
2268        }
2269    }
2270}
2271
2272-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar
2273{
2274    return [self sendKeyInputAndReleaseToFrame: nKeyCode character: aChar modifiers: mpFrame->mnLastModifierFlags];
2275}
2276
2277-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod
2278{
2279    return [self sendKeyToFrameDirect: nKeyCode character: aChar modifiers: nMod] ||
2280           [self sendSingleCharacter: mpLastEvent];
2281}
2282
2283-(BOOL)sendKeyToFrameDirect: (sal_uInt16)nKeyCode  character: (sal_Unicode)aChar modifiers: (unsigned int)nMod
2284{
2285    SolarMutexGuard aGuard;
2286
2287    bool nRet = false;
2288    if( AquaSalFrame::isAlive( mpFrame ) )
2289    {
2290        SalKeyEvent aEvent;
2291        aEvent.mnCode           = nKeyCode | ImplGetModifierMask( nMod );
2292        aEvent.mnCharCode       = aChar;
2293        aEvent.mnRepeat         = FALSE;
2294        nRet = mpFrame->CallCallback( SalEvent::KeyInput, &aEvent );
2295        std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent );
2296        if( it != GetSalData()->maKeyEventAnswer.end() )
2297            it->second = nRet;
2298        if( AquaSalFrame::isAlive( mpFrame ) )
2299            mpFrame->CallCallback( SalEvent::KeyUp, &aEvent );
2300    }
2301    return nRet;
2302}
2303
2304
2305-(BOOL)sendSingleCharacter: (NSEvent *)pEvent
2306{
2307    NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers];
2308
2309    if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
2310    {
2311        unichar keyChar = [pUnmodifiedString characterAtIndex: 0];
2312        sal_uInt16 nKeyCode = ImplMapCharCode( keyChar );
2313        if (nKeyCode == 0)
2314        {
2315            sal_uInt16 nOtherKeyCode = [pEvent keyCode];
2316            nKeyCode = ImplMapKeyCode(nOtherKeyCode);
2317        }
2318        if( nKeyCode != 0 )
2319        {
2320            // don't send code points in the private use area
2321            if( keyChar >= 0xf700 && keyChar < 0xf780 )
2322                keyChar = 0;
2323            bool bRet = [self sendKeyToFrameDirect: nKeyCode character: keyChar modifiers: mpFrame->mnLastModifierFlags];
2324            mbInKeyInput = false;
2325
2326            return bRet;
2327        }
2328    }
2329    return NO;
2330}
2331
2332
2333// NSTextInput/NSTextInputClient protocol
2334- (NSArray *)validAttributesForMarkedText
2335{
2336    return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, nil];
2337}
2338
2339- (BOOL)hasMarkedText
2340{
2341    bool bHasMarkedText;
2342
2343    bHasMarkedText = ( mMarkedRange.location != NSNotFound ) &&
2344                     ( mMarkedRange.length != 0 );
2345    // hack to check keys like "Control-j"
2346    if( mbInKeyInput )
2347    {
2348        mbNeedSpecialKeyHandle = true;
2349    }
2350
2351    return bHasMarkedText;
2352}
2353
2354- (NSRange)markedRange
2355{
2356    return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 );
2357}
2358
2359- (NSRange)selectedRange
2360{
2361    // tdf#42437 Enable press-and-hold special character input method
2362    // Always return a valid range location. If the range location is
2363    // NSNotFound, -[NSResponder interpretKeyEvents:] will not call
2364    // [self firstRectForCharacterRange:actualRange:] and will not display the
2365    // special character input method popup.
2366    // tdf#128600 Implement handling of macOS "Reverse Conversion" menu item
2367    // When a Japanese keyboard is selected, the keyboard's "Reverse Conversion"
2368    // menu item would silently fail when an empty range was returned by
2369    // -[SalFrameView selectedRange].
2370    // So return a valid range in that call using the following steps:
2371    // 1. If there is marked text, return the marked text range
2372    // 2. If LibreOffice is selected text, return the selected text length
2373    // Similar steps in the same order are in
2374    // -[SalFrameView attributedSubstringForProposedRange:actualRange:].
2375    if ( [self hasMarkedText] )
2376        return ( mMarkedRange.location == NSNotFound ? NSMakeRange( 0, 0 ) : mMarkedRange );
2377    NSString *pSelectedText = getCurrentSelection();
2378    if ( pSelectedText )
2379        return NSMakeRange( 0, [pSelectedText length] );
2380    return ( mSelectedRange.location == NSNotFound ? NSMakeRange( 0, 0 ) : mSelectedRange );
2381}
2382
2383- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
2384{
2385    (void) replacementRange; // FIXME - use it!
2386
2387    SolarMutexGuard aGuard;
2388
2389    [self deleteTextInputWantsNonRepeatKeyDown];
2390
2391    if( ![aString isKindOfClass:[NSAttributedString class]] )
2392        aString = [[[NSAttributedString alloc] initWithString:aString] autorelease];
2393
2394    // Reset cached state
2395    bool bOldHasMarkedText = [self hasMarkedText];
2396    [self unmarkText];
2397
2398    // tdf#163876 ignore marked text generated from Command-` events
2399    // For some unknown reason, when using the standard macOS French
2400    // layout, pressing Command-` causes -[NSView interpretKeyEvents:]
2401    // to temporarily set and unset the marked text.
2402    // Command-` should only cycle through the application's windows
2403    // so ignore marked text changes from such key down events.
2404    if( !bOldHasMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent keyCode] == 42 && ( [mpLastEvent modifierFlags] & ( NSEventModifierFlagCommand | NSEventModifierFlagOption | NSEventModifierFlagControl | NSEventModifierFlagShift ) ) == NSEventModifierFlagCommand )
2405    {
2406        NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers];
2407        if( pUnmodifiedString && ![pUnmodifiedString length] )
2408            return;
2409    }
2410
2411    int len = [aString length];
2412    bool bReschedule = false;
2413    SalExtTextInputEvent aInputEvent;
2414    if( len > 0 ) {
2415        // Set the marked and selected ranges to the marked text and selected
2416        // range parameters
2417        mMarkedRange = NSMakeRange( 0, [aString length] );
2418        if (selRange.location == NSNotFound || selRange.location >= mMarkedRange.length)
2419             mSelectedRange = NSMakeRange( NSNotFound, 0 );
2420        else
2421             mSelectedRange = NSMakeRange( selRange.location, selRange.location + selRange.length > mMarkedRange.length ? mMarkedRange.length - selRange.location : selRange.length );
2422
2423        // If we are going to post uncommitted text, cache the string parameter
2424        // as is needed in both [self endExtTextInput] and
2425        // [self attributedSubstringForProposedRange:actualRange:]
2426        mpLastMarkedText = [aString retain];
2427
2428        NSString *pString = [aString string];
2429        OUString aInsertString( GetOUString( pString ) );
2430        std::vector<ExtTextInputAttr> aInputFlags( std::max( 1, len ), ExtTextInputAttr::NONE );
2431        int nSelectionStart = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location);
2432        int nSelectionEnd = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location + selRange.length);
2433        for ( int i = 0; i < len; i++ )
2434        {
2435            // Highlight all characters in the selected range. Normally
2436            // uncommitted text is underlined but when an item is selected in
2437            // the native input method popup or selecting a subblock of
2438            // uncommitted text using the left or right arrow keys, the
2439            // selection range is set and the selected range is either
2440            // highlighted like in Excel or is bold underlined like in
2441            // Safari. Highlighting the selected range was chosen because
2442            // using bold and double underlines can get clipped making the
2443            // selection range indistinguishable from the rest of the
2444            // uncommitted text.
2445            if (i >= nSelectionStart && i < nSelectionEnd)
2446            {
2447                aInputFlags[i] = ExtTextInputAttr::Highlight;
2448                continue;
2449            }
2450
2451            unsigned int nUnderlineValue;
2452            NSRange effectiveRange;
2453
2454            effectiveRange = NSMakeRange(i, 1);
2455            nUnderlineValue = [[aString attribute:NSUnderlineStyleAttributeName atIndex:i effectiveRange:&effectiveRange] unsignedIntValue];
2456
2457            switch (nUnderlineValue & 0xff) {
2458            case NSUnderlineStyleSingle:
2459                aInputFlags[i] = ExtTextInputAttr::Underline;
2460                break;
2461            case NSUnderlineStyleThick:
2462                aInputFlags[i] = ExtTextInputAttr::BoldUnderline;
2463                break;
2464            case NSUnderlineStyleDouble:
2465                aInputFlags[i] = ExtTextInputAttr::DoubleUnderline;
2466                break;
2467            default:
2468                aInputFlags[i] = ExtTextInputAttr::Highlight;
2469                break;
2470            }
2471        }
2472
2473        aInputEvent.maText = aInsertString;
2474        aInputEvent.mnCursorPos = nSelectionStart;
2475        aInputEvent.mnCursorFlags = 0;
2476        aInputEvent.mpTextAttr = aInputFlags.data();
2477        if( AquaSalFrame::isAlive( mpFrame ) )
2478        {
2479            bReschedule = true;
2480            mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
2481        }
2482    } else {
2483        aInputEvent.maText.clear();
2484        aInputEvent.mnCursorPos = 0;
2485        aInputEvent.mnCursorFlags = 0;
2486        aInputEvent.mpTextAttr = nullptr;
2487        if( AquaSalFrame::isAlive( mpFrame ) )
2488        {
2489            bReschedule = true;
2490            mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
2491            if( AquaSalFrame::isAlive( mpFrame ) )
2492                mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
2493        }
2494    }
2495
2496    // tdf#163764 force pending timers to run after marked text changes
2497    // During native dictation, waiting for the next native event is
2498    // blocked while dictation runs in a loop within a native callback.
2499    // Because of this, LibreOffice's painting timers won't fire until
2500    // dictation is cancelled or the user pauses speaking. So, force
2501    // any pending timers to fire after the marked text changes.
2502    if( bReschedule && ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent )
2503        freezeWindowSizeAndReschedule( [self window] );
2504
2505    mbKeyHandled = true;
2506}
2507
2508- (void)unmarkText
2509{
2510    [self clearLastMarkedText];
2511
2512    mSelectedRange = mMarkedRange = NSMakeRange(NSNotFound, 0);
2513}
2514
2515- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
2516{
2517    (void)aRange;
2518
2519    // tdf#128600 Implement handling of macOS "Reverse Conversion" menu item
2520    // When a Japanese keyboard is selected, the keyboard's "Reverse Conversion"
2521    // menu item would silently fail when nil was returned by the unimplemented
2522    // -[SalFrameView attributedSubstringForProposedRange:actualRange:].
2523    // So return a valid string in that call using the following steps:
2524    // 1. If there is marked text, return the last marked text
2525    // 2. If LibreOffice is selected text, return the selected text
2526    // Similar steps in the same order are in -[SalFrameView selectedRange].
2527    if ( [self hasMarkedText] )
2528    {
2529        if ( actualRange )
2530            *actualRange = mMarkedRange;
2531        return mpLastMarkedText;
2532    }
2533    NSString *pSelectedText = getCurrentSelection();
2534    if ( pSelectedText )
2535    {
2536        // Related: tdf#128600 "Reverse Conversion" with Japanese keyboards
2537        // only edits the first non-whitespace chunk of selected text but
2538        // I don't know how to adjust LibreOffice's selected range to match
2539        // the substring that the input method will edit. This causes the
2540        // entire LibreOffice selection to be overwritten by the substring
2541        // so return nil if there is any whitespace in the selection.
2542        NSRange aWhitespaceRange = [pSelectedText rangeOfCharacterFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
2543        if ( aWhitespaceRange.location == NSNotFound )
2544        {
2545            if ( actualRange )
2546                *actualRange = NSMakeRange( 0, [pSelectedText length] );
2547            return [[[NSAttributedString alloc] initWithString: pSelectedText] autorelease];
2548        }
2549    }
2550    return nil;
2551}
2552
2553- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
2554{
2555    (void)thePoint;
2556    // FIXME
2557    return 0;
2558}
2559
2560- (NSInteger)conversationIdentifier
2561{
2562    return reinterpret_cast<long>(self);
2563}
2564
2565- (void)doCommandBySelector:(SEL)aSelector
2566{
2567    if( AquaSalFrame::isAlive( mpFrame ) )
2568    {
2569        if( (mpFrame->mnICOptions & InputContextFlags::Text) &&
2570            aSelector != nullptr && [self respondsToSelector: aSelector] )
2571        {
2572            [self performSelector: aSelector];
2573        }
2574        else
2575        {
2576            [self sendSingleCharacter:mpLastEvent];
2577        }
2578    }
2579
2580    mbKeyHandled = true;
2581}
2582
2583-(void)clearLastEvent
2584{
2585    if (mpLastEvent)
2586    {
2587        [mpLastEvent release];
2588        mpLastEvent = nil;
2589    }
2590}
2591
2592-(void)clearLastMarkedText
2593{
2594    if (mpLastMarkedText)
2595    {
2596        [mpLastMarkedText release];
2597        mpLastMarkedText = nil;
2598    }
2599
2600    mbTextInputWantsNonRepeatKeyDown = NO;
2601}
2602
2603-(void)clearLastTrackingArea
2604{
2605    if (mpLastTrackingArea)
2606    {
2607        [self removeTrackingArea: mpLastTrackingArea];
2608        [mpLastTrackingArea release];
2609        mpLastTrackingArea = nil;
2610    }
2611}
2612
2613-(void)updateTrackingAreas
2614{
2615    [super updateTrackingAreas];
2616
2617    // tdf#155092 use tracking areas instead of tracking rectangles
2618    // Apparently, the older, tracking rectangles selectors cause
2619    // unexpected window resizing upon the first mouse down after the
2620    // window has been manually resized so switch to the newer,
2621    // tracking areas selectors. Also, the NSTrackingInVisibleRect
2622    // option allows us to create one single tracking area that
2623    // resizes itself automatically over the lifetime of the view.
2624    // Note: for some unknown reason, both NSTrackingMouseMoved and
2625    // NSTrackingAssumeInside are necessary options for this fix
2626    // to work.
2627    // Note: for some unknown reason, [mpLastTrackingArea rect]
2628    // returns an empty NSRect (at least on macOS Sequoia) so always
2629    // remove the old tracking area and add a new one.
2630    [self clearLastTrackingArea];
2631    mpLastTrackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds] options: ( NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingAssumeInside | NSTrackingInVisibleRect ) owner: self userInfo: nil];
2632    [self addTrackingArea: mpLastTrackingArea];
2633}
2634
2635- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
2636{
2637     // FIXME - These should probably be used?
2638    (void) aRange;
2639    (void) actualRange;
2640
2641    SolarMutexGuard aGuard;
2642
2643    // tdf#42437 Enable press-and-hold special character input method
2644    // Some text entry controls, such as Writer comments or the cell editor in
2645    // Calc's Formula Bar, need to have an input method session open or else
2646    // the returned position won't be anywhere near the text cursor. So,
2647    // dispatch an empty SalEvent::ExtTextInput event, fetch the position,
2648    // and then dispatch a SalEvent::EndExtTextInput event.
2649    NSString *pNewMarkedText = nullptr;
2650    NSString *pChars = [mpLastEvent characters];
2651
2652    // tdf#158124 KEY_DELETE events do not need an ExtTextInput event
2653    // When using various Japanese input methods, the last event will be a
2654    // repeating key down event with a single delete character while the
2655    // Backspace key, Delete key, or Fn-Delete keys are pressed. These key
2656    // events are now ignored since setting mbTextInputWantsNonRepeatKeyDown
2657    // to YES for these events will trigger an assert or crash when saving a
2658    // .docx document.
2659    bool bNeedsExtTextInput = ( pChars && mbInKeyInput && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] && ImplMapKeyCode( [mpLastEvent keyCode] ) != KEY_DELETE );
2660    if ( bNeedsExtTextInput )
2661    {
2662        // tdf#154708 Preserve selection for repeating Shift-arrow on Japanese keyboard
2663        // Skip the posting of SalEvent::ExtTextInput and
2664        // SalEvent::EndExtTextInput events for private use area characters.
2665        NSUInteger nLen = [pChars length];
2666        auto const pBuf = std::make_unique<unichar[]>( nLen + 1 );
2667        NSUInteger nBufLen = 0;
2668        for ( NSUInteger i = 0; i < nLen; i++ )
2669        {
2670            unichar aChar = [pChars characterAtIndex:i];
2671            if ( aChar >= 0xf700 && aChar < 0xf780 )
2672                continue;
2673
2674            pBuf[nBufLen++] = aChar;
2675        }
2676        pBuf[nBufLen] = 0;
2677
2678        pNewMarkedText = [NSString stringWithCharacters:pBuf.get() length:nBufLen];
2679        if (!pNewMarkedText || ![pNewMarkedText length])
2680            bNeedsExtTextInput = false;
2681    }
2682
2683    if ( bNeedsExtTextInput )
2684    {
2685        SalExtTextInputEvent aInputEvent;
2686        aInputEvent.maText.clear();
2687        aInputEvent.mnCursorPos = 0;
2688        aInputEvent.mnCursorFlags = 0;
2689        aInputEvent.mpTextAttr = nullptr;
2690        if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
2691            mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
2692    }
2693
2694    SalExtTextInputPosEvent aPosEvent;
2695    if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
2696        mpFrame->CallCallback( SalEvent::ExtTextInputPos, static_cast<void *>(&aPosEvent) );
2697
2698    if ( bNeedsExtTextInput )
2699    {
2700        if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
2701            mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
2702
2703        // tdf#42437 Enable press-and-hold special character input method
2704        // Emulate the press-and-hold behavior of the TextEdit application by
2705        // setting the marked text to the last key down event's characters. The
2706        // characters will already have been committed by the special character
2707        // input method so set the mbTextInputWantsNonRepeatKeyDown flag to
2708        // indicate that the characters need to be deleted if the input method
2709        // replaces the committed characters.
2710        if ( pNewMarkedText )
2711        {
2712            [self unmarkText];
2713            mpLastMarkedText = [[NSAttributedString alloc] initWithString:pNewMarkedText];
2714            mSelectedRange = mMarkedRange = NSMakeRange( 0, [mpLastMarkedText length] );
2715            mbTextInputWantsNonRepeatKeyDown = YES;
2716        }
2717    }
2718
2719    NSRect rect;
2720
2721    if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
2722    {
2723        rect.origin.x = aPosEvent.mnX + mpFrame->GetUnmirroredGeometry().x();
2724        rect.origin.y = aPosEvent.mnY + mpFrame->GetUnmirroredGeometry().y() + 4; // add some space for underlines
2725        rect.size.width = aPosEvent.mnWidth;
2726        rect.size.height = aPosEvent.mnHeight;
2727
2728        mpFrame->VCLToCocoa( rect );
2729    }
2730    else
2731    {
2732        rect = NSMakeRect( aPosEvent.mnX, aPosEvent.mnY, aPosEvent.mnWidth, aPosEvent.mnHeight );
2733    }
2734
2735    return rect;
2736}
2737
2738-(id)parentAttribute {
2739    return reinterpret_cast<NSView*>(mpFrame->getNSWindow());
2740        //TODO: odd cast really needed for fdo#74121?
2741}
2742
2743-(css::accessibility::XAccessibleContext *)accessibleContext
2744{
2745    SolarMutexGuard aGuard;
2746
2747    [self insertRegisteredWrapperIntoWrapperRepository];
2748    if (mpChildWrapper)
2749        return [mpChildWrapper accessibleContext];
2750
2751    return nil;
2752}
2753
2754-(NSWindow*)windowForParent
2755{
2756    return mpFrame->getNSWindow();
2757}
2758
2759-(void)registerMouseEventListener: (id)theListener
2760{
2761  mpMouseEventListener = theListener;
2762}
2763
2764-(void)unregisterMouseEventListener: (id)theListener
2765{
2766    (void)theListener;
2767    mpMouseEventListener = nil;
2768}
2769
2770-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
2771{
2772  return [mDraggingDestinationHandler draggingEntered: sender];
2773}
2774
2775-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
2776{
2777  return [mDraggingDestinationHandler draggingUpdated: sender];
2778}
2779
2780-(void)draggingExited:(id <NSDraggingInfo>)sender
2781{
2782  [mDraggingDestinationHandler draggingExited: sender];
2783}
2784
2785-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
2786{
2787  return [mDraggingDestinationHandler prepareForDragOperation: sender];
2788}
2789
2790-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
2791{
2792  return [mDraggingDestinationHandler performDragOperation: sender];
2793}
2794
2795-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
2796{
2797  [mDraggingDestinationHandler concludeDragOperation: sender];
2798}
2799
2800-(void)registerDraggingDestinationHandler:(id)theHandler
2801{
2802  mDraggingDestinationHandler = theHandler;
2803}
2804
2805-(void)unregisterDraggingDestinationHandler:(id)theHandler
2806{
2807    (void)theHandler;
2808    mDraggingDestinationHandler = nil;
2809}
2810
2811-(void)endExtTextInput
2812{
2813    [self endExtTextInput:EndExtTextInputFlags::Complete];
2814}
2815
2816-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
2817{
2818    // Prevent recursion from any additional [self insertText:] calls that
2819    // may be called when cancelling the native input method session
2820    if (mbInEndExtTextInput)
2821        return;
2822
2823    mbInEndExtTextInput = YES;
2824
2825    SolarMutexGuard aGuard;
2826
2827    NSTextInputContext *pInputContext = [NSTextInputContext currentInputContext];
2828    if (pInputContext)
2829    {
2830        // Cancel the native input method session
2831        [pInputContext discardMarkedText];
2832
2833        // Commit any uncommitted text. Note: when the delete key is used to
2834        // remove all uncommitted characters, the marked range will be zero
2835        // length but a SalEvent::EndExtTextInput must still be dispatched.
2836        if (mpLastMarkedText && [mpLastMarkedText length] && mMarkedRange.location != NSNotFound && mpFrame && AquaSalFrame::isAlive(mpFrame))
2837        {
2838            // If there is any marked text, SalEvent::EndExtTextInput may leave
2839            // the cursor hidden so commit the marked text to force the cursor
2840            // to be visible.
2841            mbInCommitMarkedText = YES;
2842            if (nFlags & EndExtTextInputFlags::Complete)
2843            {
2844                // Retain the last marked text as it will be released in
2845                // [self insertText:replacementText:]
2846                NSAttributedString *pText = [mpLastMarkedText retain];
2847                [self insertText:pText replacementRange:NSMakeRange(0, [mpLastMarkedText length])];
2848                [pText release];
2849            }
2850            else
2851            {
2852                [self insertText:[NSString string] replacementRange:NSMakeRange(0, 0)];
2853            }
2854            mbInCommitMarkedText = NO;
2855        }
2856
2857        [self unmarkText];
2858
2859        // If a different view is the input context's client, commit that
2860        // view's uncommitted text as well
2861        id<NSTextInputClient> pClient = [pInputContext client];
2862        if (pClient != self)
2863        {
2864            SalFrameView *pView = static_cast<SalFrameView*>(pClient);
2865            if ([pView isKindOfClass:[SalFrameView class]])
2866                [pView endExtTextInput];
2867            else
2868                [pClient unmarkText];
2869        }
2870    }
2871
2872    mbInEndExtTextInput = NO;
2873}
2874
2875-(void)deleteTextInputWantsNonRepeatKeyDown
2876{
2877    SolarMutexGuard aGuard;
2878
2879    // tdf#42437 Enable press-and-hold special character input method
2880    // Emulate the press-and-hold behavior of the TextEdit application by
2881    // dispatching backspace events to delete any marked characters. The
2882    // special character input method commits the marked characters so we must
2883    // delete the marked characters before the input method calls
2884    // [self insertText:replacementRange:].
2885    if (mbTextInputWantsNonRepeatKeyDown)
2886    {
2887        if ( mpLastMarkedText )
2888        {
2889            NSString *pChars = [mpLastMarkedText string];
2890            if ( pChars )
2891            {
2892                NSUInteger nLength = [pChars length];
2893                for ( NSUInteger i = 0; i < nLength; i++ )
2894                    [self deleteBackward:self];
2895            }
2896        }
2897
2898        [self unmarkText];
2899    }
2900}
2901
2902-(void)insertRegisteredWrapperIntoWrapperRepository
2903{
2904    SolarMutexGuard aGuard;
2905
2906    if (!mbNeedChildWrapper)
2907        return;
2908
2909    vcl::Window *pWindow = mpFrame->GetWindow();
2910    if (!pWindow)
2911        return;
2912
2913    mbNeedChildWrapper = NO;
2914
2915    ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext > xAccessibleContext( pWindow->GetAccessible()->getAccessibleContext() );
2916    assert(!mpChildWrapper);
2917    mpChildWrapper = [[SalFrameViewA11yWrapper alloc] initWithParent:self accessibleContext:xAccessibleContext];
2918    [AquaA11yFactory insertIntoWrapperRepository:mpChildWrapper forAccessibleContext:xAccessibleContext];
2919}
2920
2921-(void)registerWrapper
2922{
2923    [self revokeWrapper];
2924
2925    mbNeedChildWrapper = YES;
2926}
2927
2928-(void)revokeWrapper
2929{
2930    mbNeedChildWrapper = NO;
2931
2932    if (mpChildWrapper)
2933    {
2934        [AquaA11yFactory revokeWrapper:mpChildWrapper];
2935        [mpChildWrapper setAccessibilityParent:nil];
2936        [mpChildWrapper release];
2937        mpChildWrapper = nil;
2938    }
2939}
2940
2941-(id)accessibilityAttributeValue:(NSString *)pAttribute
2942{
2943    SolarMutexGuard aGuard;
2944
2945    [self insertRegisteredWrapperIntoWrapperRepository];
2946    if (mpChildWrapper)
2947        return [mpChildWrapper accessibilityAttributeValue:pAttribute];
2948    else
2949        return nil;
2950}
2951
2952-(BOOL)accessibilityIsIgnored
2953{
2954    SolarMutexGuard aGuard;
2955
2956    [self insertRegisteredWrapperIntoWrapperRepository];
2957    if (mpChildWrapper)
2958        return [mpChildWrapper accessibilityIsIgnored];
2959    else
2960        return YES;
2961}
2962
2963-(NSArray *)accessibilityAttributeNames
2964{
2965    SolarMutexGuard aGuard;
2966
2967    [self insertRegisteredWrapperIntoWrapperRepository];
2968    if (mpChildWrapper)
2969        return [mpChildWrapper accessibilityAttributeNames];
2970    else
2971        return [NSArray array];
2972}
2973
2974-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute
2975{
2976    SolarMutexGuard aGuard;
2977
2978    [self insertRegisteredWrapperIntoWrapperRepository];
2979    if (mpChildWrapper)
2980        return [mpChildWrapper accessibilityIsAttributeSettable:pAttribute];
2981    else
2982        return NO;
2983}
2984
2985-(NSArray *)accessibilityParameterizedAttributeNames
2986{
2987    SolarMutexGuard aGuard;
2988
2989    [self insertRegisteredWrapperIntoWrapperRepository];
2990    if (mpChildWrapper)
2991        return [mpChildWrapper accessibilityParameterizedAttributeNames];
2992    else
2993        return [NSArray array];
2994}
2995
2996-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute
2997{
2998    SolarMutexGuard aGuard;
2999
3000    [self insertRegisteredWrapperIntoWrapperRepository];
3001    if (mpChildWrapper)
3002        return [mpChildWrapper accessibilitySetOverrideValue:pValue forAttribute:pAttribute];
3003    else
3004        return NO;
3005}
3006
3007-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute
3008{
3009    SolarMutexGuard aGuard;
3010
3011    [self insertRegisteredWrapperIntoWrapperRepository];
3012    if (mpChildWrapper)
3013        [mpChildWrapper accessibilitySetValue:pValue forAttribute:pAttribute];
3014}
3015
3016-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter
3017{
3018    SolarMutexGuard aGuard;
3019
3020    [self insertRegisteredWrapperIntoWrapperRepository];
3021    if (mpChildWrapper)
3022        return [mpChildWrapper accessibilityAttributeValue:pAttribute forParameter:pParameter];
3023    else
3024        return nil;
3025}
3026
3027-(id)accessibilityFocusedUIElement
3028{
3029    SolarMutexGuard aGuard;
3030
3031    [self insertRegisteredWrapperIntoWrapperRepository];
3032    if (mpChildWrapper)
3033        return [mpChildWrapper accessibilityFocusedUIElement];
3034    else
3035        return nil;
3036}
3037
3038-(NSString *)accessibilityActionDescription:(NSString *)pAction
3039{
3040    SolarMutexGuard aGuard;
3041
3042    [self insertRegisteredWrapperIntoWrapperRepository];
3043    if (mpChildWrapper)
3044        return [mpChildWrapper accessibilityActionDescription:pAction];
3045    else
3046        return nil;
3047}
3048
3049-(void)accessibilityPerformAction:(NSString *)pAction
3050{
3051    SolarMutexGuard aGuard;
3052
3053    [self insertRegisteredWrapperIntoWrapperRepository];
3054    if (mpChildWrapper)
3055        [mpChildWrapper accessibilityPerformAction:pAction];
3056}
3057
3058-(NSArray *)accessibilityActionNames
3059{
3060    SolarMutexGuard aGuard;
3061
3062    [self insertRegisteredWrapperIntoWrapperRepository];
3063    if (mpChildWrapper)
3064        return [mpChildWrapper accessibilityActionNames];
3065    else
3066        return [NSArray array];
3067}
3068
3069-(id)accessibilityHitTest:(NSPoint)aPoint
3070{
3071    SolarMutexGuard aGuard;
3072
3073    [self insertRegisteredWrapperIntoWrapperRepository];
3074    if (mpChildWrapper)
3075        return [mpChildWrapper accessibilityHitTest:aPoint];
3076    else
3077        return nil;
3078}
3079
3080-(id)accessibilityParent
3081{
3082    return [self window];
3083}
3084
3085-(NSArray *)accessibilityVisibleChildren
3086{
3087    return [self accessibilityChildren];
3088}
3089
3090-(NSArray *)accessibilitySelectedChildren
3091{
3092    SolarMutexGuard aGuard;
3093
3094    NSArray *pRet = [super accessibilityChildren];
3095
3096    [self insertRegisteredWrapperIntoWrapperRepository];
3097    if (mpChildWrapper)
3098        pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilitySelectedChildren]);
3099
3100    return pRet;
3101}
3102
3103-(NSArray *)accessibilityChildren
3104{
3105    SolarMutexGuard aGuard;
3106
3107    NSArray *pRet = [super accessibilityChildren];
3108
3109    [self insertRegisteredWrapperIntoWrapperRepository];
3110    if (mpChildWrapper)
3111        pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilityChildren]);
3112
3113    return pRet;
3114}
3115
3116-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder
3117{
3118    return [self accessibilityChildren];
3119}
3120
3121-(void)viewDidChangeEffectiveAppearance
3122{
3123    if (mbInViewDidChangeEffectiveAppearance)
3124        return;
3125
3126    mbInViewDidChangeEffectiveAppearance = YES;
3127
3128    // Related: tdf#156855 force the current theme to reload its colors
3129    // This call is called when the macOS light/dark mode changes while
3130    // LibreOffice is running. Send a SalEvent::SettingsChanged event
3131    // but do it in an idle timer. Otherwise, an infinite recursion
3132    // can occur.
3133    NSWindow *pWindow = [self window];
3134    if (pWindow && ([pWindow isVisible] || [pWindow isMiniaturized]))
3135    {
3136        SolarMutexGuard aGuard;
3137
3138        GetSalData()->mpInstance->delayedSettingsChanged(true);
3139    }
3140
3141    mbInViewDidChangeEffectiveAppearance = NO;
3142}
3143
3144-(void)mouseDraggedWithTimer: (NSTimer *)pTimer
3145{
3146    (void)pTimer;
3147
3148    if ( mpPendingMouseDraggedEvent )
3149    {
3150        if ( mpMouseEventListener != nil &&
3151             [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)])
3152        {
3153            [mpMouseEventListener mouseDragged: mpPendingMouseDraggedEvent];
3154        }
3155
3156        s_nLastButton = MOUSE_LEFT;
3157        [self sendMouseEventToFrame:mpPendingMouseDraggedEvent button:MOUSE_LEFT eventtype:SalEvent::MouseMove];
3158
3159        [self clearPendingMouseDraggedEvent];
3160    }
3161    else
3162    {
3163        [self clearMouseDraggedTimer];
3164    }
3165}
3166
3167@end
3168
3169@implementation SalFrameViewA11yWrapper
3170
3171-(id)initWithParent:(SalFrameView *)pParentView accessibleContext:(::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >&)rxAccessibleContext
3172{
3173    [super init];
3174
3175    maReferenceWrapper.rAccessibleContext = rxAccessibleContext;
3176
3177    mpParentView = pParentView;
3178    if (mpParentView)
3179    {
3180        [mpParentView retain];
3181        [self setAccessibilityParent:mpParentView];
3182    }
3183
3184    return self;
3185}
3186
3187-(void)dealloc
3188{
3189    if (mpParentView)
3190        [mpParentView release];
3191
3192    [super dealloc];
3193}
3194
3195-(id)parentAttribute
3196{
3197    if (mpParentView)
3198        return NSAccessibilityUnignoredAncestor(mpParentView);
3199    else
3200        return nil;
3201}
3202
3203-(void)setAccessibilityParent:(id)pObject
3204{
3205    if (mpParentView)
3206    {
3207        [mpParentView release];
3208        mpParentView = nil;
3209    }
3210
3211    if (pObject && [pObject isKindOfClass:[SalFrameView class]])
3212    {
3213        mpParentView = static_cast<SalFrameView *>(pObject);
3214        [mpParentView retain];
3215    }
3216
3217    [super setAccessibilityParent:mpParentView];
3218}
3219
3220-(id)windowAttribute
3221{
3222    if (mpParentView)
3223        return [mpParentView window];
3224    else
3225        return nil;
3226}
3227
3228-(NSWindow *)windowForParent
3229{
3230    return [self windowAttribute];
3231}
3232
3233@end
3234
3235/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3236