Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  salframeview.mm   Sprache: unbekannt

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>

#include <memory>

#include <basegfx/numeric/ftools.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <officecfg/Office/Common.hxx>
#include <sal/macros.h>
#include <tools/helpers.hxx>
#include <tools/long.hxx>
#include <vcl/event.hxx>
#include <vcl/inputctx.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/toolkit/edit.hxx>

#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/text/XTextRange.hpp>

#include <osx/a11yfactory.h>
#include <osx/salframe.h>
#include <osx/salframeview.h>
#include <osx/salinst.h>
#include <quartz/salgdi.h>
#include <quartz/utils.h>

#if HAVE_FEATURE_SKIA
#include <vcl/skia/SkiaHelper.hxx>
#include <premac.h>
#include <QuartzCore/QuartzCore.h>
#include <postmac.h>
#endif

#define WHEEL_EVENT_FACTOR 1.5

static sal_uInt16 ImplGetModifierMask( unsigned int nMask )
{
    sal_uInt16 nRet = 0;
    if( (nMask & NSEventModifierFlagShift) != 0 )
        nRet |= KEY_SHIFT;
    if( (nMask & NSEventModifierFlagControl) != 0 )
        nRet |= KEY_MOD3;
    if( (nMask & NSEventModifierFlagOption) != 0 )
        nRet |= KEY_MOD2;
    if( (nMask & NSEventModifierFlagCommand) != 0 )
        nRet |= KEY_MOD1;
    return nRet;
}

static sal_uInt16 ImplMapCharCode( sal_Unicode aCode )
{
    static sal_uInt16 aKeyCodeMap[ 128 ] =
    {
                    0,                0,                0,                0,                0,                0,                0,                0,
        KEY_BACKSPACE,          KEY_TAB,       KEY_RETURN,                0,                0,       KEY_RETURN,                0,                0,
                    0,                0,                0,                0,                0,                0,                0,                0,
                    0,          KEY_TAB,                0,       KEY_ESCAPE,                0,                0,                0,                0,
            KEY_SPACE,                0,                0,                0,                0,                0,                0,                0,
                    0,                0,     KEY_MULTIPLY,          KEY_ADD,        KEY_COMMA,     KEY_SUBTRACT,        KEY_POINT,       KEY_DIVIDE,
                KEY_0,            KEY_1,            KEY_2,            KEY_3,            KEY_4,            KEY_5,            KEY_6,            KEY_7,
                KEY_8,            KEY_9,                0,                0,         KEY_LESS,        KEY_EQUAL,      KEY_GREATER,                0,
                    0,            KEY_A,            KEY_B,            KEY_C,            KEY_D,            KEY_E,            KEY_F,            KEY_G,
                KEY_H,            KEY_I,            KEY_J,            KEY_K,            KEY_L,            KEY_M,            KEY_N,            KEY_O,
                KEY_P,            KEY_Q,            KEY_R,            KEY_S,            KEY_T,            KEY_U,            KEY_V,            KEY_W,
                KEY_X,            KEY_Y,            KEY_Z,                0,                0,                0,                0,                0,
        KEY_QUOTELEFT,            KEY_A,            KEY_B,            KEY_C,            KEY_D,            KEY_E,            KEY_F,            KEY_G,
                KEY_H,            KEY_I,            KEY_J,            KEY_K,            KEY_L,            KEY_M,            KEY_N,            KEY_O,
                KEY_P,            KEY_Q,            KEY_R,            KEY_S,            KEY_T,            KEY_U,            KEY_V,            KEY_W,
                KEY_X,            KEY_Y,            KEY_Z,                0,                0,                0,        KEY_TILDE,    KEY_BACKSPACE
    };

    // Note: the mapping 0x7f should by rights be KEY_DELETE
    // however if you press "backspace" 0x7f is reported
    // whereas for "delete" 0xf728 gets reported

    // Note: the mapping of 0x19 to KEY_TAB is because for unknown reasons
    // tab alone is reported as 0x09 (as expected) but shift-tab is
    // reported as 0x19 (end of medium)

    static sal_uInt16 aFunctionKeyCodeMap[ 128 ] =
    {
            KEY_UP,         KEY_DOWN,         KEY_LEFT,        KEY_RIGHT,           KEY_F1,           KEY_F2,           KEY_F3,           KEY_F4,
            KEY_F5,           KEY_F6,           KEY_F7,           KEY_F8,           KEY_F9,          KEY_F10,          KEY_F11,          KEY_F12,
           KEY_F13,          KEY_F14,          KEY_F15,          KEY_F16,          KEY_F17,          KEY_F18,          KEY_F19,          KEY_F20,
           KEY_F21,          KEY_F22,          KEY_F23,          KEY_F24,          KEY_F25,          KEY_F26,                0,                0,
                 0,                0,                0,                0,                0,                0,                0,       KEY_INSERT,
        KEY_DELETE,         KEY_HOME,                0,          KEY_END,        KEY_PAGEUP,    KEY_PAGEDOWN,                0,                0,
                 0,                0,                0,                0,                 0,        KEY_MENU,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,         KEY_UNDO,        KEY_REPEAT,        KEY_FIND,         KEY_HELP,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0,
                 0,                0,                0,                0,                 0,               0,                0,                0
    };

    sal_uInt16 nKeyCode = 0;
    if( aCode < SAL_N_ELEMENTS( aKeyCodeMap)  )
        nKeyCode = aKeyCodeMap[ aCode ];
    else if( aCode >= 0xf700 && aCode < 0xf780 )
        nKeyCode = aFunctionKeyCodeMap[ aCode - 0xf700 ];
    return nKeyCode;
}

static sal_uInt16 ImplMapKeyCode(sal_uInt16 nKeyCode)
{
    /*
      http://stackoverflow.com/questions/2080312/where-can-i-find-a-list-of-key-codes-for-use-with-cocoas-nsevent-class/2080324#2080324
      /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
     */

    static sal_uInt16 aKeyCodeMap[ 0x80 ] =
    {
            KEY_A,            KEY_S,            KEY_D,            KEY_F,            KEY_H,            KEY_G,            KEY_Z,            KEY_X,
            KEY_C,            KEY_V,                0,            KEY_B,            KEY_Q,            KEY_W,            KEY_E,            KEY_R,
            KEY_Y,            KEY_T,            KEY_1,            KEY_2,            KEY_3,            KEY_4,            KEY_6,            KEY_5,
        KEY_EQUAL,            KEY_9,            KEY_7,     KEY_SUBTRACT,            KEY_8,            KEY_0, KEY_BRACKETRIGHT, KEY_RIGHTCURLYBRACKET,
            KEY_U,  KEY_BRACKETLEFT,            KEY_I,            KEY_P,       KEY_RETURN,            KEY_L,            KEY_J,   KEY_QUOTERIGHT,
            KEY_K,    KEY_SEMICOLON,                0,        KEY_COMMA,       KEY_DIVIDE,            KEY_N,            KEY_M,        KEY_POINT,
          KEY_TAB,        KEY_SPACE,    KEY_QUOTELEFT,       KEY_DELETE,                0,       KEY_ESCAPE,                0,                0,
                0,     KEY_CAPSLOCK,                0,                0,                0,                0,                0,                0,
          KEY_F17,      KEY_DECIMAL,                0,     KEY_MULTIPLY,                0,          KEY_ADD,                0,                0,
                0,                0,                0,       KEY_DIVIDE,       KEY_RETURN,                0,     KEY_SUBTRACT,          KEY_F18,
          KEY_F19,        KEY_EQUAL,                0,                0,                0,                0,                0,                0,
                0,                0,          KEY_F20,                0,                0,                0,                0,                0,
           KEY_F5,           KEY_F6,           KEY_F7,           KEY_F3,           KEY_F8,           KEY_F9,                0,          KEY_F11,
                0,          KEY_F13,          KEY_F16,          KEY_F14,                0,          KEY_F10,                0,          KEY_F12,
                0,          KEY_F15,         KEY_HELP,         KEY_HOME,       KEY_PAGEUP,       KEY_DELETE,           KEY_F4,          KEY_END,
           KEY_F2,     KEY_PAGEDOWN,           KEY_F1,         KEY_LEFT,        KEY_RIGHT,         KEY_DOWN,           KEY_UP,                0
    };

    if (nKeyCode < SAL_N_ELEMENTS(aKeyCodeMap))
        return aKeyCodeMap[nKeyCode];
    return 0;
}

// store the frame the mouse last entered
static AquaSalFrame* s_pMouseFrame = nullptr;
// store the last pressed button for enter/exit events
// which lack that information
static sal_uInt16 s_nLastButton = 0;

static AquaSalFrame* getMouseContainerFrame()
{
    AquaSalFrame* pDispatchFrame = nullptr;
    NSArray* aWindows = [NSWindow windowNumbersWithOptions:0];
    for(NSUInteger i = 0; i < [aWindows count] && ! pDispatchFrame; i++ )
    {
        NSWindow* pWin = [NSApp windowWithWindowNumber:[[aWindows objectAtIndex:i] integerValue]];
        if( pWin && [pWin isMemberOfClass: [SalFrameWindow class]] && [static_cast<SalFrameWindow*>(pWin) containsMouse] )
            pDispatchFrame = [static_cast<SalFrameWindow*>(pWin) getSalFrame];
    }
    return pDispatchFrame;
}

static NSArray *getMergedAccessibilityChildren(NSArray *pDefaultChildren, NSArray *pUnignoredChildrenToAdd)
{
    NSArray *pRet = pDefaultChildren;

    if (pUnignoredChildrenToAdd && [pUnignoredChildrenToAdd count])
    {
        NSMutableArray *pNewChildren = [NSMutableArray arrayWithCapacity:(pRet ? [pRet count] : 0) + 1];
        if (pNewChildren)
        {
            if (pRet)
                [pNewChildren addObjectsFromArray:pRet];

            for (AquaA11yWrapper *pWrapper : pUnignoredChildrenToAdd)
            {
                if (pWrapper && ![pNewChildren containsObject:pWrapper])
                    [pNewChildren addObject:pWrapper];
            }

            pRet = pNewChildren;
        }
        else
        {
            pRet = pUnignoredChildrenToAdd;
        }
    }

    return pRet;
}

// Update ImplGetSVData()->mpWinData->mbIsLiveResize
static void updateWinDataInLiveResize(bool bInLiveResize)
{
    ImplSVData* pSVData = ImplGetSVData();
    assert( pSVData );
    if ( pSVData )
    {
        if ( pSVData->mpWinData->mbIsLiveResize != bInLiveResize )
        {
            pSVData->mpWinData->mbIsLiveResize = bInLiveResize;
            Scheduler::Wakeup();
        }
    }
}

static void freezeWindowSizeAndReschedule( NSWindow *pWindow )
{
    if ( pWindow )
    {
        // Application::Reschedule() can potentially display a modal
        // dialog which will cause a hang so temporarily disable any
        // resizing by clamping the window's minimum and maximum sizes
        // to the current frame size which in Application::Reschedule().
        bool bIsLiveResize = ImplGetSVData()->mpWinData->mbIsLiveResize;
        NSSize aMinSize = [pWindow minSize];
        NSSize aMaxSize = [pWindow maxSize];
        if ( bIsLiveResize )
        {
            NSRect aFrame = [pWindow frame];
            [pWindow setMinSize:aFrame.size];
            [pWindow setMaxSize:aFrame.size];
        }
        Application::Reschedule( true );
        if ( bIsLiveResize )
        {
            [pWindow setMinSize:aMinSize];
            [pWindow setMaxSize:aMaxSize];
        }
    }
}

static bool isMouseScrollWheelEvent( NSEvent *pEvent )
{
    // tdf#151423 allow trackpad or Magic Mouse to behave like a regular mouse
    // Give both trackpad and Magic Mouse users the option to restore
    // the legacy zoom via Command+swipe gesture.
    // The IgnoreKeysWhenScrollingWithTrackpadOrMagicMouse preference is
    // set to true by default and that disables zooming via swiping.
    // The problem is that while trackpad users are able to zoom via a
    // magnify gesture, the Magic Mouse doesn't have a magnify gesture.
    // Since I have not found a reliable way to distinguish a Magic Mouse
    // from a trackpad, Magic Mouse users have no obvious replacement
    // for the zoom via Command+swipe gesture.
    if ( !officecfg::Office::Common::VCL::macOS::IgnoreKeysWhenScrollingWithTrackpadOrMagicMouse::get() )
        return true;

    return ( pEvent && [pEvent type] == NSEventTypeScrollWheel && [pEvent phase] == NSEventPhaseNone && [pEvent momentumPhase] == NSEventPhaseNone );
}

static void updateMenuBarVisibility( const AquaSalFrame *pFrame )
{
    // Show the menubar if application is in native full screen mode
    // since hiding the menubar in that mode will cause the window's
    // titlebar to fail to display or fail to hide when expected.
    if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen )
    {
        [NSMenu setMenuBarVisible: YES];
    }
    // Hide the dock and the menubar if the key window or one of its
    // parent windows are in LibreOffice full screen mode. Otherwise,
    // show the dock and the menubar.
    else if( AquaSalFrame::isAlive( pFrame ) )
    {
        bool bInternalFullScreen = false;
        bool bNativeFullScreen = false;
        const AquaSalFrame *pParentFrame = pFrame;
        while( pParentFrame )
        {
            bInternalFullScreen |= pParentFrame->mbInternalFullScreen;
            bNativeFullScreen |= pParentFrame->mbNativeFullScreen;
            pParentFrame = AquaSalFrame::isAlive( pParentFrame->mpParent ) ? pParentFrame->mpParent : nullptr;
        }

        if( bInternalFullScreen && !bNativeFullScreen )
        {
            const NSWindow *pParentWindow = [NSApp keyWindow];
            while( pParentWindow && pParentWindow != pFrame->getNSWindow() )
                pParentWindow = [pParentWindow parentWindow];

            // Related: tdf#161623 disable menubar visibility if no key window
            // If a window is in LibreOffice's internal full screen mode
            // and not in native full screen mode and then the user switches
            // to a different application and back using the Command-Tab keys.
            // the menubar and Dock would unexpectedly appear.
            // It appears that the key window will still be nil in this
            // case, so only enable menubar visibility if the key window
            // is not nil.
            if( pParentWindow && pParentWindow != pFrame->getNSWindow() )
                [NSMenu setMenuBarVisible: YES];
            else
                [NSMenu setMenuBarVisible: NO];
        }
        else
        {
            [NSMenu setMenuBarVisible: YES];
        }
    }
}

static void updateWindowCollectionBehavior( const SalFrameStyleFlags nStyle, const AquaSalFrame *pParent, NSWindow *pNSWindow )
{
    if( !pNSWindow )
        return;

    // Enable fullscreen options if available and useful
    NSWindowCollectionBehavior eOldCollectionBehavior = [pNSWindow collectionBehavior];
    NSWindowCollectionBehavior eCollectionBehavior = NSWindowCollectionBehaviorFullScreenNone;
    if ( officecfg::Office::Common::VCL::macOS::EnableNativeFullScreenWindows::get() )
    {
        bool bAllowFullScreen = (SalFrameStyleFlags::NONE == (nStyle & (SalFrameStyleFlags::DIALOG | SalFrameStyleFlags::TOOLTIP | SalFrameStyleFlags::SYSTEMCHILD | SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::TOOLWINDOW | SalFrameStyleFlags::INTRO)));
        bAllowFullScreen &= (SalFrameStyleFlags::NONE == (~nStyle & SalFrameStyleFlags::SIZEABLE));
        bAllowFullScreen &= (pParent == nullptr);

        eCollectionBehavior = bAllowFullScreen ? NSWindowCollectionBehaviorFullScreenPrimary : NSWindowCollectionBehaviorFullScreenAuxiliary;
    }
    if ( eCollectionBehavior != eOldCollectionBehavior )
        [pNSWindow setCollectionBehavior: eCollectionBehavior];
}

static NSString* getCurrentSelection()
{
    SolarMutexGuard aGuard;

    // The following is needed for text fields in dialogs, etc.
    vcl::Window *pWin = ImplGetSVData()->mpWinData->mpFocusWin;
    if (pWin)
    {
        Edit *pEditWin = dynamic_cast<Edit*>(pWin);
        if (pEditWin)
            return [CreateNSString(pEditWin->GetSelected()) autorelease];
    }

    css::uno::Reference<css::frame::XDesktop> xDesktop = css::frame::Desktop::create(::comphelper::getProcessComponentContext());
    if (xDesktop.is())
    {
        css::uno::Reference<css::frame::XModel> xModel(xDesktop->getCurrentComponent(), css::uno::UNO_QUERY);
        if (xModel)
        {
            css::uno::Reference<css::uno::XInterface> xSelection(xModel->getCurrentSelection(), css::uno::UNO_QUERY);
            if (xSelection)
            {
                css::uno::Reference<css::container::XIndexAccess> xIndexAccess(xSelection, css::uno::UNO_QUERY);
                if (xIndexAccess.is())
                {
                    if (xIndexAccess->getCount() > 0)
                    {
                        css::uno::Reference<css::text::XTextRange> xTextRange(xIndexAccess->getByIndex(0), css::uno::UNO_QUERY);
                        if (xTextRange.is())
                        {
                            // tdf#168609 catch exceptions from SwXText::getString()
                            // Apparently, implementions of XTextRange::getString()
                            // such as SwXText::getString() can throw an exception.
                            OUString aStr;
                            try
                            {
                                aStr = xTextRange->getString();
                            }
                            catch (css::uno::RuntimeException &)
                            {
                                TOOLS_WARN_EXCEPTION("vcl.osx", "getCurrentSelection: XTextRange::getString() threw RuntimeException");
                            }
                            return [CreateNSString(aStr) autorelease];
                        }
                    }
                }

                // The Basic IDE returns a XEnumeration with a single item
                // Note: the following code was adapted from
                // svx/source/tbxctrls/tbunosearchcontrollers.cxx
                css::uno::Reference<css::container::XEnumeration> xEnum(xSelection, css::uno::UNO_QUERY);
                if (xEnum.is() && xEnum->hasMoreElements())
                {
                    OUString aString;
                    xEnum->nextElement() >>= aString;
                    return [CreateNSString(aString) autorelease];
                }

                // The following is needed for cells and text fields in Calc
                // and Impress
                css::uno::Reference<css::text::XTextRange> xTextRange(xSelection, css::uno::UNO_QUERY);
                if (xTextRange.is())
                {
                    // tdf#168609 catch exceptions from SwXText::getString()
                    // Apparently, implementions of XTextRange::getString()
                    // such as SwXText::getString() can throw an exception.
                    OUString aStr;
                    try
                    {
                        aStr = xTextRange->getString();
                    }
                    catch (css::uno::RuntimeException &)
                    {
                        TOOLS_WARN_EXCEPTION("vcl.osx", "getCurrentSelection: XTextRange::getString() threw RuntimeException");
                    }
                    return [CreateNSString(aStr) autorelease];
                }
            }
        }
    }

    return nil;
}

@interface NSResponder (SalFrameWindow)
-(BOOL)accessibilityIsIgnored;
@end

@implementation SalFrameWindow
-(id)initWithSalFrame: (AquaSalFrame*)pFrame
{
    mDraggingDestinationHandler = nil;
    mbInWindowDidResize = NO;
    mpLiveResizeTimer = nil;
    mpResetParentWindowTimer = nil;
    mbInSetFrame = false;
    mpFrame = pFrame;
    const SalFrameGeometry rFrameGeometry = pFrame->GetUnmirroredGeometry();
    NSRect aRect = { { static_cast<CGFloat>(rFrameGeometry.x()), static_cast<CGFloat>(rFrameGeometry.y()) },
                     { static_cast<CGFloat>(rFrameGeometry.width()), static_cast<CGFloat>(rFrameGeometry.height()) } };
    pFrame->VCLToCocoa( aRect );
    NSWindow* pNSWindow = [super initWithContentRect: aRect
                                 styleMask: mpFrame->getStyleMask()
                                 backing: NSBackingStoreBuffered
                                 defer: Application::IsHeadlessModeEnabled()];

    updateWindowCollectionBehavior( mpFrame->mnStyle, mpFrame->mpParent, pNSWindow );

    [pNSWindow setReleasedWhenClosed: NO];

    // Disable window restoration until we support it directly
    [pNSWindow setRestorable: NO];

    // tdf#137468: Restrict to 24-bit RGB as that is all that we can
    // handle anyway. HDR is far off in the future for LibreOffice.
    [pNSWindow setDynamicDepthLimit: NO];
    [pNSWindow setDepthLimit: NSWindowDepthTwentyfourBitRGB];

    return static_cast<SalFrameWindow *>(pNSWindow);
}

-(void)clearLiveResizeTimer
{
    if ( mpLiveResizeTimer )
    {
        [mpLiveResizeTimer invalidate];
        [mpLiveResizeTimer release];
        mpLiveResizeTimer = nil;
    }
}

-(void)clearResetParentWindowTimer
{
    if ( mpResetParentWindowTimer )
    {
        [mpResetParentWindowTimer invalidate];
        [mpResetParentWindowTimer release];
        mpResetParentWindowTimer = nil;
    }
}

-(void)dealloc
{
    [self clearLiveResizeTimer];
    [self clearResetParentWindowTimer];
    [super dealloc];
}

-(AquaSalFrame*)getSalFrame
{
    return mpFrame;
}

-(void)displayIfNeeded
{
    if( GetSalData() && GetSalData()->mpInstance )
    {
        SolarMutexGuard aGuard;
        [super displayIfNeeded];
    }
}

-(BOOL)containsMouse
{
    // is this event actually inside that NSWindow ?
    NSPoint aPt = [NSEvent mouseLocation];
    NSRect aFrameRect = [self frame];
    bool bInRect = NSPointInRect( aPt, aFrameRect );
    return bInRect;
}

-(BOOL)canBecomeKeyWindow
{
    if( (mpFrame->mnStyle &
            ( SalFrameStyleFlags::FLOAT                 |
              SalFrameStyleFlags::TOOLTIP               |
              SalFrameStyleFlags::INTRO
            )) == SalFrameStyleFlags::NONE )
        return YES;
    if( mpFrame->mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
        return YES;
    if( mpFrame->mbInternalFullScreen )
        return YES;
    return [super canBecomeKeyWindow];
}

-(void)windowDidBecomeKey: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        updateWindowCollectionBehavior( mpFrame->mnStyle, mpFrame->mpParent, mpFrame->mpNSWindow);

        static const SalFrameStyleFlags nGuessDocument = SalFrameStyleFlags::MOVEABLE|
                                            SalFrameStyleFlags::SIZEABLE|
                                            SalFrameStyleFlags::CLOSEABLE;

        // Reset dark mode colors in HITheme controls after printing
        // In dark mode, after an NSPrintOperation has completed, macOS draws
        // HITheme controls with light mode colors so reset all dark mode
        // colors when an NSWindow gains focus.
        mpFrame->UpdateDarkMode();

        if( mpFrame->mpMenu )
            mpFrame->mpMenu->setMainMenu();
        else if( ! mpFrame->mpParent &&
                 ( (mpFrame->mnStyle & nGuessDocument) == nGuessDocument || // set default menu for e.g. help
                    mpFrame->mbInternalFullScreen ) )                               // set default menu for e.g. presentation
        {
            AquaSalMenu::setDefaultMenu();
        }
        mpFrame->CallCallback( SalEvent::GetFocus, nullptr );
        mpFrame->SendPaintEvent(); // repaint controls as active

        updateMenuBarVisibility( mpFrame );
    }

    // Prevent the same native input method popup that was cancelled in a
    // previous call to [self windowDidResignKey:] from reappearing
    [self endExtTextInput];
}

-(void)windowDidResignKey: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    // Commit any uncommitted text and cancel the native input method session
    // whenever a window loses focus like in Safari, Firefox, and Excel
    [self endExtTextInput];

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->CallCallback(SalEvent::LoseFocus, nullptr);
        mpFrame->SendPaintEvent(); // repaint controls as inactive
    }

    // Show the menubar if application is in native full screen mode
    // since hiding the menubar in that mode will cause the window's
    // titlebar to fail to display or fail to hide when expected.
    if( [NSApp presentationOptions] & NSApplicationPresentationFullScreen )
    {
        [NSMenu setMenuBarVisible: YES];
    }
    // Show the dock and the menubar if there is no native modal dialog
    // and if the key window is nil or is not a SalFrameWindow instance.
    // If a SalFrameWindow is the key window, it should have already set
    // the menubar visibility to match its LibreOffice full screen mode
    // state.
    else if ( ![NSApp modalWindow] )
    {
        NSWindow *pKeyWindow = [NSApp keyWindow];
        if( !pKeyWindow || ![pKeyWindow isKindOfClass: [SalFrameWindow class]] )
            [NSMenu setMenuBarVisible: YES];
    }
}

-(void)windowDidChangeScreen: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
        mpFrame->screenParametersChanged();

    // Start timer to handle hiding of native child windows that have been
    // dragged to a different screen.
    if( !mpResetParentWindowTimer )
    {
        mpResetParentWindowTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1f target: self selector: @selector(resetParentWindow) userInfo: nil repeats: YES];
        if( mpResetParentWindowTimer )
        {
            [mpResetParentWindowTimer retain];
            [[NSRunLoop currentRunLoop] addTimer: mpResetParentWindowTimer forMode: NSEventTrackingRunLoopMode];
        }
    }
}

-(void)windowDidMove: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->UpdateFrameGeometry();
        mpFrame->CallCallback( SalEvent::Move, nullptr );

#if HAVE_FEATURE_SKIA
        // tdf#163734 Flush parent frame when Skia is enabled
        // When a dockable window is dragged by its titlebar, a rectangle
        // may be drawn in its parent window. However, the Skia flush
        // timer doesn't run until after the mouse button has been
        // released (probably due to lowering of the Skia flush timer's
        // priority to fix tdf#163734). So run the parent frame's Skia
        // flush timer immediately to display the rectangle.
        if ( SkiaHelper::isVCLSkiaEnabled() &&
             mpFrame->mbShown && mpFrame->mpParent &&
             AquaSalFrame::isAlive( mpFrame->mpParent ) &&
             mpFrame->mpParent->mbShown )
        {
            AquaSalGraphics* pGraphics = mpFrame->mpParent->mpGraphics;
            if ( pGraphics )
                pGraphics->Flush();
        }
#endif
    }
}

-(void)windowDidResize: (NSNotification*)pNotification
{
    SolarMutexGuard aGuard;

    if ( mbInWindowDidResize )
        return;

    mbInWindowDidResize = YES;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->UpdateFrameGeometry();
        mpFrame->CallCallback( SalEvent::Resize, nullptr );

        updateWinDataInLiveResize( [self inLiveResize] );
        if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
        {
            // tdf#152703 Force relayout during live resizing of window
            // During a live resize, macOS floods the application with
            // windowDidResize: notifications so sending a paint event does
            // not trigger redrawing with the new size.
            // Instead, force relayout by dispatching all pending internal
            // events and firing any pending timers.
            freezeWindowSizeAndReschedule( self );

            // Related: tdf128186 Always run timer in full screen mode windows
            // When opening new windows by pressing and holding Command-N
            // in a full screen window, some of the new windows will have
            // content that does not fill the new window. So still run the
            // timer on full screen windows even if live resizing ended
            // during the call to freezeWindowSizeAndReschedule().
            if ( ImplGetSVData()->mpWinData->mbIsLiveResize || [self styleMask] & NSWindowStyleMaskFullScreen )
            {
                // tdf#152703 Force repaint after live resizing ends
                // Repost this notification so that this selector will be called
                // at least once after live resizing ends
                if ( !mpLiveResizeTimer )
                {
                    mpLiveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification repeats:YES];
                    if ( mpLiveResizeTimer )
                    {
                        [mpLiveResizeTimer retain];

                        // The timer won't fire without a call to
                        // Application::Reschedule() unless we copy the fix for
                        // #i84055# from vcl/osx/saltimer.cxx and add the timer
                        // to the NSEventTrackingRunLoopMode run loop mode
                        [[NSRunLoop currentRunLoop] addTimer:mpLiveResizeTimer forMode:NSEventTrackingRunLoopMode];
                    }
                }
            }
        }
        else
        {
            [self clearLiveResizeTimer];
        }

        // tdf#158461 eliminate flicker during live resizing
        // When using Skia/Metal, the window content will flicker while
        // live resizing a window if we don't send a paint event.
        mpFrame->SendPaintEvent();

#if HAVE_FEATURE_SKIA
        // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing
        // The window will clear its background so when Skia/Metal is
        // enabled, explicitly flush the Skia graphics to the window
        // during live resizing or else nothing will be drawn until after
        // live resizing has ended.
        // Also, flushing during [self windowDidResize:] eliminates flicker
        // by forcing this window's SkSurface to recreate its underlying
        // CAMetalLayer with the new size. Flushing in
        // [self displayIfNeeded] does not eliminate flicker so apparently
        // [self windowDidResize:] is called earlier.
        // Lastly, flush after calling AquaSalFrame::SendPaintEvent().
        if ( SkiaHelper::isVCLSkiaEnabled() )
        {
            AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
            if ( pGraphics )
                pGraphics->Flush();
        }
#endif
    }

    mbInWindowDidResize = NO;
}

-(void)windowDidMiniaturize: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->mbShown = false;
        mpFrame->UpdateFrameGeometry();
        mpFrame->CallCallback( SalEvent::Resize, nullptr );
    }
}

-(void)windowDidDeminiaturize: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->mbShown = true;
        mpFrame->UpdateFrameGeometry();
        mpFrame->CallCallback( SalEvent::Resize, nullptr );
    }
}

-(BOOL)windowShouldClose: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    bool bRet = true;
    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        // #i84461# end possible input
        [self endExtTextInput];
        if( AquaSalFrame::isAlive( mpFrame ) )
        {
            mpFrame->CallCallback( SalEvent::Close, nullptr );
            bRet = false; // application will close the window or not, AppKit shouldn't
            AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
            assert( pTimer );
            pTimer->handleWindowShouldClose();
        }
    }

    return bRet;
}

-(void)windowWillEnterFullScreen: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( AquaSalFrame::isAlive( mpFrame) )
    {
        mpFrame->mbNativeFullScreen = true;

        if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) )
        {
            mpFrame->maNativeFullScreenRestoreRect = mpFrame->maInternalFullScreenRestoreRect;
        }
        else
        {
            // Related: tdf#128186 restore rectangles are in VCL coordinates
            NSRect aFrame = [mpFrame->getNSWindow() frame];
            NSRect aContentRect = [mpFrame->getNSWindow() contentRectForFrameRect: aFrame];
            mpFrame->CocoaToVCL( aContentRect );
            mpFrame->maNativeFullScreenRestoreRect = aContentRect;
        }

        updateMenuBarVisibility( mpFrame );

#if HAVE_FEATURE_SKIA
        // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property
        // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1
        // and create a new CAMetalLayer when the window resizes. When using
        // Skia/Metal, flushing to an NSWindow during transitions into or out
        // of native full screen mode causes the Skia/Metal surface to be
        // drawn at the wrong window position which results in a noticeable
        // flicker.
        if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster )
        {
            if( [mpFrame->getNSView() wantsLayer] )
            {
                CALayer *pLayer = [mpFrame->getNSView() layer];
                if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] )
                    [pLayer setHidden: YES];
            }
        }
#endif
    }
}

-(void)windowDidFailToEnterFullScreen: (NSWindow *)pWindow
{
    (void)pWindow;
    SolarMutexGuard aGuard;

    if( AquaSalFrame::isAlive( mpFrame) )
    {
        mpFrame->mbNativeFullScreen = false;

        mpFrame->maNativeFullScreenRestoreRect = NSZeroRect;

        updateMenuBarVisibility( mpFrame );
    }
}

-(void)windowWillExitFullScreen: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

#if HAVE_FEATURE_SKIA
    // Related: tdf#128186 Let vcl use the CAMetalLayer's hidden property
    // to skip the fix for tdf#152703 in external/skia/macosmetal.patch.1
    // and create a new CAMetalLayer when the window resizes. When using
    // Skia/Metal, flushing to an NSWindow during transitions into or out
    // of native full screen mode causes the Skia/Metal surface to be
    // drawn at the wrong window position which results in a noticeable
    // flicker.
    if( AquaSalFrame::isAlive( mpFrame ) && SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster )
    {
        if( [mpFrame->getNSView() wantsLayer] )
        {
            CALayer *pLayer = [mpFrame->getNSView() layer];
            if( pLayer && [pLayer isKindOfClass:[CAMetalLayer class]] )
                [pLayer setHidden: YES];
        }
    }
#endif
}

-(void)windowDidExitFullScreen: (NSNotification*)pNotification
{
    (void)pNotification;
    SolarMutexGuard aGuard;

    if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbNativeFullScreen )
    {
        mpFrame->mbNativeFullScreen = false;

        if( !NSIsEmptyRect( mpFrame->maNativeFullScreenRestoreRect ) )
        {
            // Related: tdf#128186 set window frame before exiting native full screen mode
            // Setting the window frame just before exiting native full
            // screen mode appears to set the desired non-full screen
            // window frame without causing a noticeable flicker during
            // the macOS default "exit full screen" animation.
            NSRect aContentRect;
            if( mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenRestoreRect ) )
                aContentRect = mpFrame->maInternalFullScreenRestoreRect;
            else
                aContentRect = mpFrame->maNativeFullScreenRestoreRect;
            mpFrame->VCLToCocoa( aContentRect );
            NSRect aFrame = [NSWindow frameRectForContentRect: aContentRect styleMask: [mpFrame->getNSWindow() styleMask] & ~NSWindowStyleMaskFullScreen];
            [mpFrame->getNSWindow() setFrame: aFrame display: mpFrame->mbShown ? YES : NO];

            mpFrame->maNativeFullScreenRestoreRect = NSZeroRect;
        }

        updateMenuBarVisibility( mpFrame );
    }
}

-(void)windowDidChangeBackingProperties:(NSNotification *)pNotification
{
    (void)pNotification;
#if HAVE_FEATURE_SKIA
    SolarMutexGuard aGuard;

    sal::aqua::resetWindowScaling();

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        // tdf#147342 Notify Skia that the window's backing properties changed
        if ( SkiaHelper::isVCLSkiaEnabled() )
        {
            AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
            if ( pGraphics )
                pGraphics->WindowBackingPropertiesChanged();
        }
    }
#endif
}

-(void)windowWillStartLiveResize:(NSNotification *)pNotification
{
    SolarMutexGuard aGuard;

    updateWinDataInLiveResize(true);
}

-(void)windowDidEndLiveResize:(NSNotification *)pNotification
{
    SolarMutexGuard aGuard;

    updateWinDataInLiveResize(false);
}

-(void)dockMenuItemTriggered: (id)sender
{
    (void)sender;
    SolarMutexGuard aGuard;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
        mpFrame->ToTop( SalFrameToTop::RestoreWhenMin | SalFrameToTop::GrabFocus );
}

-(css::uno::Reference < css::accessibility::XAccessibleContext >)accessibleContext
{
    return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext();
}

-(BOOL)isIgnoredWindow
{
    SolarMutexGuard aGuard;

    // Treat tooltip windows as ignored
    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
        return (mpFrame->mnStyle & SalFrameStyleFlags::TOOLTIP) != SalFrameStyleFlags::NONE;
    return YES;
}

-(id)accessibilityApplicationFocusedUIElement
{
    return [self accessibilityFocusedUIElement];
}

-(id)accessibilityFocusedUIElement
{
    // Treat tooltip windows as ignored
    if ([self isIgnoredWindow])
        return nil;

    return [super accessibilityFocusedUIElement];
}

-(BOOL)accessibilityIsIgnored
{
    // Treat tooltip windows as ignored
    if ([self isIgnoredWindow])
        return YES;

    return [super accessibilityIsIgnored];
}

-(BOOL)isAccessibilityElement
{
    return ![self accessibilityIsIgnored];
}

-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
  return [mDraggingDestinationHandler draggingEntered: sender];
}

-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
  return [mDraggingDestinationHandler draggingUpdated: sender];
}

-(void)draggingExited:(id <NSDraggingInfo>)sender
{
  [mDraggingDestinationHandler draggingExited: sender];
}

-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
  return [mDraggingDestinationHandler prepareForDragOperation: sender];
}

-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
  return [mDraggingDestinationHandler performDragOperation: sender];
}

-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
  [mDraggingDestinationHandler concludeDragOperation: sender];
}

-(void)registerDraggingDestinationHandler:(id)theHandler
{
  mDraggingDestinationHandler = theHandler;
}

-(void)unregisterDraggingDestinationHandler:(id)theHandler
{
    (void)theHandler;
    mDraggingDestinationHandler = nil;
}

-(void)endExtTextInput
{
    [self endExtTextInput:EndExtTextInputFlags::Complete];
}

-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
{
    SalFrameView *pView = static_cast<SalFrameView*>([self firstResponder]);
    if (pView && [pView isKindOfClass:[SalFrameView class]])
        [pView endExtTextInput:nFlags];
}

-(void)windowDidResizeWithTimer:(NSTimer *)pTimer
{
    if ( pTimer )
        [self windowDidResize:[pTimer userInfo]];
}

-(void)resetParentWindow
{
    // Wait until the left mouse button has been released. Otherwise
    // the code below will cause native child windows to flicker while
    // dragging the window in a different screen than its parent window.
    if( [NSEvent pressedMouseButtons] & 0x1 )
        return;

    // Stop hiding of child windows when dragged to a different screen
    // LibreOffice sets all dialog windows as a native child window of
    // its related document window in order to force the dialog windows
    // to always remain in front of their related document window.
    // However, for some unknown reason, if a native child window is
    // dragged to a different screen than its native parent window,
    // macOS will hide the native child window when the drag has ended.
    // So, once the current drag has finished, unattach and reattach
    // the native child window to its native parent window. This should
    // cause macOS to force the native child window to jump back to the
    // same screen as its native parent window.
    NSWindow *pParentWindow = [self parentWindow];
    if( pParentWindow && [pParentWindow screen] != [self screen] )
    {
        [pParentWindow removeChildWindow: self];
        [pParentWindow addChildWindow: self ordered: NSWindowAbove];
    }

    [self clearResetParentWindowTimer];
}

-(NSRect)constrainFrameRect: (NSRect)aFrameRect toScreen: (NSScreen *)pScreen
{
    SolarMutexGuard aGuard;

    NSRect aRet = [super constrainFrameRect: aFrameRect toScreen: pScreen];

    // Related: tdf#161623 the menubar and Dock are both hidden when a
    // window enters LibreOffice full screen mode. However, the call to
    // -[super constrainFrameRect:toScreen:] shrinks the window frame to
    // allow room for the menubar if the window is on the main screen. So,
    // force the return value to match the frame that LibreOffice expects.
    // Related: tdf#165448 skip fix for menu items inserted by macOS
    // If the window is in LibreOffice's internal full screen mode and
    // any of the menu items that macOS inserts into the windows menu
    // is selected, the frame size will be changed without calling
    // -[SalFrameWindow setFrame:display:]. So only use the fix for
    // tdf#161623 when the LibreOffice code explicitly resizes the frame.
    // Otherwise, selecting any of the menu items inserted by macOS will
    // cause the window to snap back to full screen size.
    if( mbInSetFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) )
        aRet = mpFrame->maInternalFullScreenExpectedRect;

    return aRet;
}

- (NSArray<NSWindow *> *)customWindowsToExitFullScreenForWindow: (NSWindow *)pWindow
{
    SolarMutexGuard aGuard;

    // Related: tdf#161623 suppress animation when in internal full screen mode
    // LibreOffice's internal full screen mode fills the screen with a
    // regular window so suppress animation when exiting native full
    // screen mode.
    if( AquaSalFrame::isAlive( mpFrame) && mpFrame->mbInternalFullScreen && !NSIsEmptyRect( mpFrame->maInternalFullScreenExpectedRect ) )
        return [NSArray arrayWithObject: self];

    return nil;
}

-(void)setFrame: (NSRect)aFrameRect display: (BOOL)bFlag
{
    mbInSetFrame = true;
    [super setFrame: aFrameRect display: bFlag];
    mbInSetFrame = false;
}

@end

@implementation SalFrameView
+(void)unsetMouseFrame: (AquaSalFrame*)pFrame
{
    if( pFrame == s_pMouseFrame )
        s_pMouseFrame = nullptr;
}

-(id)initWithSalFrame: (AquaSalFrame*)pFrame
{
    if ((self = [super initWithFrame: [NSWindow contentRectForFrameRect: [pFrame->getNSWindow() frame] styleMask: pFrame->mnStyleMask]]) != nil)
    {
        mDraggingDestinationHandler = nil;
        mpFrame = pFrame;
        mpChildWrapper = nil;
        mbNeedChildWrapper = NO;
        mpLastEvent = nil;
        mMarkedRange = NSMakeRange(NSNotFound, 0);
        mSelectedRange = NSMakeRange(NSNotFound, 0);
        mpMouseEventListener = nil;
        mpLastSuperEvent = nil;
        mfLastMagnifyTime = 0.0;

        mbInEndExtTextInput = NO;
        mbInCommitMarkedText = NO;
        mpLastMarkedText = nil;
        mbTextInputWantsNonRepeatKeyDown = NO;
        mpLastTrackingArea = nil;

        mbInViewDidChangeEffectiveAppearance = NO;

        mpMouseDraggedTimer = nil;
        mpPendingMouseDraggedEvent = nil;
    }

    return self;
}

-(void)clearMouseDraggedTimer
{
    if ( mpMouseDraggedTimer )
    {
        [mpMouseDraggedTimer invalidate];
        [mpMouseDraggedTimer release];
        mpMouseDraggedTimer = nil;
    }

    // Clear the pending mouse dragged event as well
    [self clearPendingMouseDraggedEvent];
}

-(void)clearPendingMouseDraggedEvent
{
    if ( mpPendingMouseDraggedEvent )
    {
        [mpPendingMouseDraggedEvent release];
        mpPendingMouseDraggedEvent = nil;
    }
}

-(void)dealloc
{
    [self clearMouseDraggedTimer];
    [self clearLastEvent];
    [self clearLastMarkedText];
    [self clearLastTrackingArea];
    [self revokeWrapper];

    [super dealloc];
}

-(AquaSalFrame*)getSalFrame
{
    return mpFrame;
}

-(void)resetCursorRects
{
    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        // FIXME: does this leak the returned NSCursor of getCurrentCursor ?
        const NSRect aRect = { NSZeroPoint, NSMakeSize(mpFrame->GetUnmirroredGeometry().width(), mpFrame->GetUnmirroredGeometry().height()) };
        [self addCursorRect: aRect cursor: mpFrame->getCurrentCursor()];
    }
}

-(BOOL)acceptsFirstResponder
{
    return YES;
}

-(BOOL)acceptsFirstMouse: (NSEvent*)pEvent
{
    (void)pEvent;
    return YES;
}

-(BOOL)isOpaque
{
    if( !mpFrame)
        return YES;
    if( !AquaSalFrame::isAlive( mpFrame))
        return YES;
    if( !mpFrame->getClipPath())
        return YES;
    return NO;
}

-(void)drawRect: (NSRect)aRect
{
    ImplSVData* pSVData = ImplGetSVData();
    assert( pSVData );
    if ( !pSVData )
        return;

    SolarMutexGuard aGuard;
    if (!mpFrame || !AquaSalFrame::isAlive(mpFrame))
        return;

    updateWinDataInLiveResize([self inLiveResize]);

    AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
    if (pGraphics)
    {
        pGraphics->UpdateWindow(aRect);
        if (mpFrame->getClipPath())
            [mpFrame->getNSWindow() invalidateShadow];
    }
}

-(void)sendMouseEventToFrame: (NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(SalEvent)nEvent
{
    SolarMutexGuard aGuard;

    AquaSalFrame* pDispatchFrame = AquaSalFrame::GetCaptureFrame();
    bool bIsCaptured = false;
    if( pDispatchFrame )
    {
        bIsCaptured = true;
        if( nEvent == SalEvent::MouseLeave ) // no leave events if mouse is captured
            nEvent = SalEvent::MouseMove;
    }
    else if( s_pMouseFrame )
        pDispatchFrame = s_pMouseFrame;
    else
        pDispatchFrame = mpFrame;

    /* #i81645# Cocoa reports mouse events while a button is pressed
       to the window in which it was first pressed. This is reasonable and fine and
       gets one around most cases where on other platforms one uses CaptureMouse or XGrabPointer,
       however vcl expects mouse events to occur in the window the mouse is over, unless the
       mouse is explicitly captured. So we need to find the window the mouse is actually
       over for conformance with other platforms.
    */
    if( ! bIsCaptured && nButton && pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
    {
        // is this event actually inside that NSWindow ?
        NSPoint aPt = [NSEvent mouseLocation];
        NSRect aFrameRect = [pDispatchFrame->getNSWindow() frame];

        if ( ! NSPointInRect( aPt, aFrameRect ) )
        {
            // no, it is not
            // now we need to find the one it may be in
            /* #i93756# we ant to get enumerate the application windows in z-order
               to check if any contains the mouse. This could be elegantly done with this
               code:

               // use NSApp to check windows in ZOrder whether they contain the mouse pointer
               NSWindow* pWindow = [NSApp makeWindowsPerform: @selector(containsMouse) inOrder: YES];
               if( pWindow && [pWindow isMemberOfClass: [SalFrameWindow class]] )
                   pDispatchFrame = [(SalFrameWindow*)pWindow getSalFrame];

               However if a non SalFrameWindow is on screen (like e.g. the file dialog)
               it can be hit with the containsMouse selector, which it doesn't support.
               Sadly NSApplication:makeWindowsPerform does not check (for performance reasons
               I assume) whether a window supports a selector before sending it.
            */
            AquaSalFrame* pMouseFrame = getMouseContainerFrame();
            if( pMouseFrame )
                pDispatchFrame = pMouseFrame;
        }
    }

    if( pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
    {
        pDispatchFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
        pDispatchFrame->mnLastModifierFlags = [pEvent modifierFlags];

        NSPoint aPt = [NSEvent mouseLocation];
        pDispatchFrame->CocoaToVCL( aPt );

        sal_uInt16 nModMask = ImplGetModifierMask( [pEvent modifierFlags] );
        // #i82284# emulate ctrl left
        if( nModMask == KEY_MOD3 && nButton == MOUSE_LEFT )
        {
            nModMask    = 0;
            nButton     = MOUSE_RIGHT;
        }

        SalMouseEvent aEvent;
        aEvent.mnTime   = pDispatchFrame->mnLastEventTime;
        aEvent.mnX = static_cast<tools::Long>(aPt.x) - pDispatchFrame->GetUnmirroredGeometry().x();
        aEvent.mnY = static_cast<tools::Long>(aPt.y) - pDispatchFrame->GetUnmirroredGeometry().y();
        aEvent.mnButton = nButton;
        aEvent.mnCode   =  aEvent.mnButton | nModMask;

        if( AllSettings::GetLayoutRTL() )
            aEvent.mnX = pDispatchFrame->GetWidth() - 1 - aEvent.mnX;

        pDispatchFrame->CallCallback( nEvent, &aEvent );

        // tdf#155266 force flush after scrolling
        if (nButton == MOUSE_LEFT && nEvent == SalEvent::MouseMove)
            mpFrame->mbForceFlushScrolling = true;
    }
}

-(void)mouseDown: (NSEvent*)pEvent
{
    if ( mpMouseEventListener != nil &&
        [mpMouseEventListener respondsToSelector: @selector(mouseDown:)])
    {
        [mpMouseEventListener mouseDown: pEvent];
    }

    s_nLastButton = MOUSE_LEFT;
    [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonDown];
}

-(void)mouseDragged: (NSEvent*)pEvent
{
    // tdf#163945 Coalesce mouse dragged events
    // When dragging a selection box on an empty background in Impress
    // while using Skia/Metal, the selection box would not keep up with
    // the pointer. The selection box would repaint sporadically or not
    // at all if the pointer was dragged rapidly and the status bar was
    // visible.
    // Apparently, flushing a graphics doesn't actually do much of
    // anything with Skia/Raster and Skia disabled so the selection box
    // repaints without any noticeable delay.
    // However, with Skia/Metal every flush of a graphics creates and
    // queues a new CAMetalLayer drawable. During rapid dragging, this
    // can lead to creating and queueing up to 200 drawables per second
    // leaving no spare time for the Impress selection box painting
    // timer to fire. So coalesce mouse dragged events so that only
    // a maximum of 50 mouse dragged events are dispatched per second.
    [self clearPendingMouseDraggedEvent];
    mpPendingMouseDraggedEvent = [pEvent retain];
    if ( !mpMouseDraggedTimer )
    {
        mpMouseDraggedTimer = [NSTimer scheduledTimerWithTimeInterval:0.025f target:self selector:@selector(mouseDraggedWithTimer:) userInfo:nil repeats:YES];
        if ( mpMouseDraggedTimer )
        {
            [mpMouseDraggedTimer retain];

            // The timer won't fire without a call to
            // Application::Reschedule() unless we copy the fix for
            // #i84055# from vcl/osx/saltimer.cxx and add the timer
            // to the NSEventTrackingRunLoopMode run loop mode
            [[NSRunLoop currentRunLoop] addTimer:mpMouseDraggedTimer forMode:NSEventTrackingRunLoopMode];
        }
    }
}

-(void)mouseUp: (NSEvent*)pEvent
{
    // Dispatch any pending mouse dragged event before dispatching the
    // mouse up event
    if ( mpPendingMouseDraggedEvent )
        [self mouseDraggedWithTimer: nil];
    [self clearMouseDraggedTimer];

    s_nLastButton = 0;
    [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonUp];
}

-(void)mouseMoved: (NSEvent*)pEvent
{
    s_nLastButton = 0;
    [self sendMouseEventToFrame:pEvent button:0 eventtype:SalEvent::MouseMove];
}

-(void)mouseEntered: (NSEvent*)pEvent
{
    s_pMouseFrame = mpFrame;

    // #i107215# the only mouse events we get when inactive are enter/exit
    // actually we would like to have all of them, but better none than some
    if( [NSApp isActive] )
        [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseMove];
}

-(void)mouseExited: (NSEvent*)pEvent
{
    if( s_pMouseFrame == mpFrame )
        s_pMouseFrame = nullptr;

    // #i107215# the only mouse events we get when inactive are enter/exit
    // actually we would like to have all of them, but better none than some
    if( [NSApp isActive] )
        [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseLeave];
}

-(void)rightMouseDown: (NSEvent*)pEvent
{
    s_nLastButton = MOUSE_RIGHT;
    [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonDown];
}

-(void)rightMouseDragged: (NSEvent*)pEvent
{
    s_nLastButton = MOUSE_RIGHT;
    [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseMove];
}

-(void)rightMouseUp: (NSEvent*)pEvent
{
    s_nLastButton = 0;
    [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonUp];
}

-(void)otherMouseDown: (NSEvent*)pEvent
{
    if( [pEvent buttonNumber] == 2 )
    {
        s_nLastButton = MOUSE_MIDDLE;
        [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonDown];
    }
    else
        s_nLastButton = 0;
}

-(void)otherMouseDragged: (NSEvent*)pEvent
{
    if( [pEvent buttonNumber] == 2 )
    {
        s_nLastButton = MOUSE_MIDDLE;
        [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseMove];
    }
    else
        s_nLastButton = 0;
}

-(void)otherMouseUp: (NSEvent*)pEvent
{
    s_nLastButton = 0;
    if( [pEvent buttonNumber] == 2 )
        [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonUp];
}

- (void)magnifyWithEvent: (NSEvent*)pEvent
{
    SolarMutexGuard aGuard;

    // TODO: ??  -(float)magnification;
    if( AquaSalFrame::isAlive( mpFrame ) )
    {
        const NSTimeInterval fMagnifyTime = [pEvent timestamp];
        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( fMagnifyTime * 1000.0 );
        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];

        // check if this is a new series of magnify events
        static const NSTimeInterval fMaxDiffTime = 0.3;
        const bool bNewSeries = (fMagnifyTime - mfLastMagnifyTime > fMaxDiffTime);

        if( bNewSeries )
            mfMagnifyDeltaSum = 0.0;
        mfMagnifyDeltaSum += [pEvent magnification];

        mfLastMagnifyTime = [pEvent timestamp];
// TODO: change to 0.1 when CommandWheelMode::ZOOM handlers allow finer zooming control
        static const float fMagnifyFactor = 0.25*500; // steps are 500 times smaller for -magnification
        static const float fMinMagnifyStep = 15.0 / fMagnifyFactor;
        if( fabs(mfMagnifyDeltaSum) <= fMinMagnifyStep )
            return;

        // adapt NSEvent-sensitivity to application expectations
        // TODO: rather make CommandWheelMode::ZOOM handlers smarter
        const float fDeltaZ = mfMagnifyDeltaSum * fMagnifyFactor;
        int nDeltaZ = basegfx::fround<int>( fDeltaZ );
        if( !nDeltaZ )
        {
            // handle new series immediately
            if( !bNewSeries )
                return;
            nDeltaZ = (fDeltaZ >= 0.0) ? +1 : -1;
        }
        // eventually give credit for delta sum
        mfMagnifyDeltaSum -= nDeltaZ / fMagnifyFactor;

        NSPoint aPt = [NSEvent mouseLocation];
        mpFrame->CocoaToVCL( aPt );

        SalWheelMouseEvent aEvent;
        aEvent.mnTime           = mpFrame->mnLastEventTime;
        aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x();
        aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y();
        aEvent.mnCode           = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
        aEvent.mnCode           |= KEY_MOD1; // we want zooming, no scrolling
        aEvent.mbDeltaIsPixel   = true;

        if( AllSettings::GetLayoutRTL() )
            aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX;

        aEvent.mnDelta = nDeltaZ;
        aEvent.mnNotchDelta = (nDeltaZ >= 0) ? +1 : -1;
        if( aEvent.mnDelta == 0 )
            aEvent.mnDelta = aEvent.mnNotchDelta;
        aEvent.mbHorz = false;
        sal_uInt32 nScrollLines = nDeltaZ;
        if (nScrollLines == 0)
            nScrollLines = 1;
        aEvent.mnScrollLines = nScrollLines;
        mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
    }
}

- (void)rotateWithEvent: (NSEvent*)pEvent
{
    //Rotation : -(float)rotation;
    // TODO: create new CommandType so rotation is available to the applications
    (void)pEvent;
}

- (void)swipeWithEvent: (NSEvent*)pEvent
{
    SolarMutexGuard aGuard;

    if( AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];

        // merge pending scroll wheel events
        CGFloat dX = 0.0;
        CGFloat dY = 0.0;
        for(;;)
        {
            dX += [pEvent deltaX];
            dY += [pEvent deltaY];
            NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskSwipe
            untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
            if( !pNextEvent )
                break;
            pEvent = pNextEvent;
        }

        NSPoint aPt = [NSEvent mouseLocation];
        mpFrame->CocoaToVCL( aPt );

        SalWheelMouseEvent aEvent;
        aEvent.mnTime           = mpFrame->mnLastEventTime;
        aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x();
        aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y();
        // tdf#151423 Ignore all modifiers for swipe events
        // It appears that devices that generate swipe events can generate
        // both vertical and horizontal swipe events. So, behave like most
        // macOS applications and ignore all modifiers if this a swipe event.
        aEvent.mnCode           = 0;
        aEvent.mbDeltaIsPixel   = true;

        if( AllSettings::GetLayoutRTL() )
            aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX;

        if( dX != 0.0 )
        {
            aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
            aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
            if( aEvent.mnDelta == 0 )
                aEvent.mnDelta = aEvent.mnNotchDelta;
            aEvent.mbHorz = true;
            aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
        }
        if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ))
        {
            aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
            aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
            if( aEvent.mnDelta == 0 )
                aEvent.mnDelta = aEvent.mnNotchDelta;
            aEvent.mbHorz = false;
            aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
        }

        // tdf#155266 force flush after scrolling
        mpFrame->mbForceFlushScrolling = true;
    }
}

-(void)scrollWheel: (NSEvent*)pEvent
{
    SolarMutexGuard aGuard;

    if( AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
        mpFrame->mnLastModifierFlags = [pEvent modifierFlags];

        // merge pending scroll wheel events
        CGFloat dX = 0.0;
        CGFloat dY = 0.0;
        bool bAllowModifiers = isMouseScrollWheelEvent( pEvent );
        for(;;)
        {
            dX += [pEvent deltaX];
            dY += [pEvent deltaY];
            NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel
                untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
            if( !pNextEvent || ( isMouseScrollWheelEvent( pNextEvent ) != bAllowModifiers ) )
                break;
            pEvent = pNextEvent;
        }

        NSPoint aPt = [NSEvent mouseLocation];
        mpFrame->CocoaToVCL( aPt );

        SalWheelMouseEvent aEvent;
        aEvent.mnTime         = mpFrame->mnLastEventTime;
        aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->GetUnmirroredGeometry().x();
        aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->GetUnmirroredGeometry().y();
        // tdf#151423 Only allow modifiers for mouse scrollwheel events
        // The Command modifier converts scrollwheel events into
        // magnification events and the Shift modifier converts vertical
        // scrollwheel events into horizontal scrollwheel events. This
        // behavior is reasonable for mouse scrollwheel events since many
        // mice only have a single, vertical scrollwheel but trackpads
        // already have specific gestures for magnification and horizontal
        // scrolling. So, behave like most macOS applications and ignore
        // all modifiers if this a trackpad scrollwheel event.
        aEvent.mnCode         = bAllowModifiers ? ImplGetModifierMask( mpFrame->mnLastModifierFlags ) : 0;
        aEvent.mbDeltaIsPixel = false;

        if( AllSettings::GetLayoutRTL() )
            aEvent.mnX = mpFrame->GetWidth() - 1 - aEvent.mnX;

        if( dX != 0.0 )
        {
            aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
            aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
            if( aEvent.mnDelta == 0 )
                aEvent.mnDelta = aEvent.mnNotchDelta;
            aEvent.mbHorz = true;
            sal_uInt32 nScrollLines = fabs(dX) / WHEEL_EVENT_FACTOR;
            if (nScrollLines == 0)
                nScrollLines = 1;
            aEvent.mnScrollLines = nScrollLines;

            mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
        }
        if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ) )
        {
            aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
--> --------------------

--> maximum size reached

--> --------------------

[ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge