/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <config_features.h>
#include <tools/long.hxx>
#include <vcl/salnativewidgets.hxx>
#include <vcl/decoview.hxx>
#include <vcl/svapp.hxx>
#include <vcl/threadex.hxx>
#include <vcl/timer.hxx>
#include <vcl/settings.hxx>
#include <vcl/themecolors.hxx>

#include <quartz/salgdi.h>
#include <osx/salnativewidgets.h>
#include <osx/saldata.hxx>
#include <osx/salframe.h>
#include <osx/salframeview.h>

#include <premac.h>
#include <Carbon/Carbon.h>
#include <postmac.h>

#include <scrollbarvalue.hxx>

#if HAVE_FEATURE_SKIA
#include <vcl/skia/SkiaHelper.hxx>
#endif

#include "cuidraw.hxx"

// presentation of native widgets consists of two important methods:

// AquaSalGraphics::getNativeControlRegion to determine native rectangle in pixels to draw the widget
// AquaSalGraphics::drawNativeControl to do the drawing operation itself

// getNativeControlRegion has to calculate a content rectangle within it is safe to draw the widget. Furthermore a bounding rectangle
// has to be calculated by getNativeControlRegion to consider adornments like a focus rectangle. As drawNativeControl uses Carbon
// API calls, all widgets are drawn without text. Drawing of text is done separately by VCL on top of graphical Carbon widget
// representation. drawNativeControl is called by VCL using content rectangle determined by getNativeControlRegion.

// FIXME: when calculation bounding rectangle larger then content rectangle, text displayed by VCL will become misaligned. To avoid
// misalignment bounding rectangle and content rectangle are calculated equally including adornments. Reduction of size for content
// is done by drawNativeControl subsequently. Only exception is editbox: As other widgets have distinct ControlPart::SubEdit control
// parts, editbox bounding rectangle and content rectangle are both calculated to reflect content area. Extending size for
// adornments is done by drawNativeControl subsequently.

#if !HAVE_FEATURE_MACOSX_SANDBOX

@interface NSWindow(CoreUIRendererPrivate)
+ (CUIRendererRef)coreUIRenderer;
@end

#endif

static HIRect ImplGetHIRectFromRectangle(tools::Rectangle aRect)
{
    HIRect aHIRect;
    aHIRect.origin.x = static_cast<float>(aRect.Left());
    aHIRect.origin.y = static_cast<float>(aRect.Top());
    aHIRect.size.width = static_cast<float>(aRect.GetWidth());
    aHIRect.size.height = static_cast<float>(aRect.GetHeight());
    return aHIRect;
}

static NSControlStateValue ImplGetButtonValue(ButtonValue aButtonValue)
{
    switch (aButtonValue)
    {
        case ButtonValue::On:
            return NSControlStateValueOn;
        case ButtonValue::Off:
        case ButtonValue::DontKnow:
            return NSControlStateValueOff;
        case ButtonValue::Mixed:
        default:
            return NSControlStateValueMixed;
    }
}

static bool AquaGetScrollRect(/* TODO: int nScreen, */
                              ControlPart nPart, const tools::Rectangle &rControlRect, tools::Rectangle &rResultRect)
{
    bool bRetVal = true;
    rResultRect = rControlRect;
    switch (nPart)
    {
        case ControlPart::ButtonUp:
            rResultRect.SetBottom(rResultRect.Top());
            break;
        case ControlPart::ButtonDown:
            rResultRect.SetTop(rResultRect.Bottom());
            break;
        case ControlPart::ButtonLeft:
            rResultRect.SetRight(rResultRect.Left());
            break;
        case ControlPart::ButtonRight:
            rResultRect.SetLeft(rResultRect.Right());
            break;
        case ControlPart::TrackHorzArea:
        case ControlPart::TrackVertArea:
        case ControlPart::ThumbHorz:
        case ControlPart::ThumbVert:
        case ControlPart::TrackHorzLeft:
        case ControlPart::TrackHorzRight:
        case ControlPart::TrackVertUpper:
        case ControlPart::TrackVertLower:
            break;
        default:
            bRetVal = false;
    }
    return bRetVal;
}

bool AquaSalGraphics::isNativeControlSupported(ControlType nType, ControlPart nPart)
{
    // native controls are now defaults. If you want to disable native controls, set the environment variable SAL_NO_NWF to
    // something and VCL controls will be used as default again.

    switch (nType)
    {
        case ControlType::Pushbutton:
        case ControlType::Radiobutton:
        case ControlType::Checkbox:
        case ControlType::ListNode:
            if (nPart == ControlPart::Entire)
                return true;
            break;
        case ControlType::Scrollbar:
            if (nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert
                || nPart == ControlPart::Entire || nPart == ControlPart::HasThreeButtons)
                return true;
            break;
        case ControlType::Slider:
            if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
                return true;
            break;
        case ControlType::Editbox:
            if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
                return true;
            break;
        case ControlType::MultilineEditbox:
            if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
                return true;
            break;
        case ControlType::Spinbox:
            if (nPart == ControlPart::Entire || nPart == ControlPart::AllButtons || nPart == ControlPart::HasBackgroundTexture)
                return true;
            break;
        case ControlType::SpinButtons:
            return false;
        case ControlType::Combobox:
            if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
                return true;
            break;
        case ControlType::Listbox:
            if (nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow || nPart == ControlPart::HasBackgroundTexture
                || nPart == ControlPart::SubEdit)
                return true;
            break;
        case ControlType::TabPane:
            if (nPart == ControlPart::TabPaneWithHeader)
                return true;
            [[fallthrough]];
        case ControlType::TabItem:
        case ControlType::TabBody:
            if (nPart == ControlPart::Entire || nPart == ControlPart::TabsDrawRtl || nPart == ControlPart::HasBackgroundTexture)
                return true;
            break;
        case ControlType::Toolbar:
            if (nPart == ControlPart::Entire || nPart == ControlPart::DrawBackgroundHorz
                || nPart == ControlPart::DrawBackgroundVert)
                return true;
            break;
        case  ControlType::WindowBackground:
            if (nPart == ControlPart::BackgroundWindow || nPart == ControlPart::BackgroundDialog)
                 return true;
            break;
        case ControlType::Menubar:
            if (nPart == ControlPart::Entire)
                return true;
            break;
        case ControlType::Tooltip:
            if (nPart == ControlPart::Entire)
                return true;
            break;
        case ControlType::MenuPopup:
            if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::MenuItemCheckMark
                || nPart == ControlPart::MenuItemRadioMark)
                return true;
            break;
        case ControlType::LevelBar:
        case ControlType::Progress:
        case ControlType::IntroProgress:
            if (nPart == ControlPart::Entire)
                return true;
            break;
        case ControlType::Frame:
            if (nPart == ControlPart::Border)
                return true;
            break;
        case ControlType::ListNet:
            if (nPart == ControlPart::Entire)
                return true;
            break;
        default:
            break;
    }
    return false;
}

bool AquaSalGraphics::hitTestNativeControl(ControlType nType, ControlPart nPart, const tools::Rectangle &rControlRegion,
                                           const Point &rPos, bool& rIsInside)
{
    if (nType == ControlType::Scrollbar)
    {
        tools::Rectangle aRect;
        bool bValid = AquaGetScrollRect(/* TODO: int nScreen, */
                                        nPart, rControlRegion, aRect);
        rIsInside = bValid && aRect.Contains(rPos);
        return bValid;
    }
    return false;
}

static bool getEnabled(ControlState nState, AquaSalFrame* mpFrame)
{

    // there are non key windows which are children of key windows, e.g. autofilter configuration dialog or sidebar dropdown dialogs.
    // To handle these windows correctly, parent frame's key window state is considered here additionally.

    const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow]
                             || mpFrame->mpParent == nullptr || [mpFrame->mpParent->getNSWindow() isKeyWindow];
    if (!(nState & ControlState::ENABLED) || !bDrawActive)
    {
        return false;
    }
    return true;
}

bool AquaSalGraphics::drawNativeControl(ControlType nType,
                                        ControlPart nPart,
                                        const tools::Rectangle &rControlRegion,
                                        ControlState nState,
                                        const ImplControlValue &aValue,
                                        const OUString &,
                                        const Color&)
{
    // tdf#165266 Force native controls to use current effective appearance
    // +[NSAppearance setCurrentAppearance:] is deprecated and calling
    // that appears to do less and less with each new version of macos
    // and/or Xcode so run all drawing of native controls in a block passed
    // to -[NSAppearance performAsCurrentDrawingAppearance:].
    __block bool bRet = false;
    if (@available(macOS 11, *))
    {
        [[NSApp effectiveAppearance] performAsCurrentDrawingAppearance:^() {
            bRet = mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
        }];
    }
    else
    {
        bRet = mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
    }

    return bRet;
}

static NSColor* colorFromRGB(const Color& rColor)
{
    return [NSColor colorWithSRGBRed:(rColor.GetRed() / 255.0f)
                               green:(rColor.GetGreen() / 255.0f)
                                blue:(rColor.GetBlue() / 255.0f)
                               alpha:(rColor.GetAlpha() / 255.0f)];
}

static void paintCell(NSCell* pBtn, const NSRect& bounds, bool bShowsFirstResponder, CGContextRef context, NSView* pView)
{
    //translate and scale because up side down otherwise
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y + bounds.size.height);
    CGContextScaleCTM(context, 1, -1);

    NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];

    NSRect rect = { NSZeroPoint, bounds.size };

    if ([pBtn isKindOfClass: [NSSliderCell class]])
    {
        // NSSliderCell doesn't seem to work with drawWithFrame(?), so draw the elements directly
        [static_cast<NSSliderCell*>(pBtn)
            drawBarInside: [static_cast<NSSliderCell*>(pBtn) barRectFlipped: NO] flipped: NO];
        rect = [static_cast<NSSliderCell*>(pBtn) knobRectFlipped: NO];
        [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
    }
    else
        [pBtn drawWithFrame: rect inView: pView];

    // setShowsFirstResponder apparently causes a hang when set on NSComboBoxCell
    const bool bIsComboBox = [pBtn isMemberOfClass: [NSComboBoxCell class]];
    if (!bIsComboBox)
        [pBtn setShowsFirstResponder: bShowsFirstResponder];

    if (bShowsFirstResponder)
    {
        NSSetFocusRingStyle(NSFocusRingOnly);

        CGContextBeginTransparencyLayerWithRect(context, rect, nullptr);
        if ([pBtn isMemberOfClass: [NSTextFieldCell class]])
        {
            // I wonder why NSTextFieldCell doesn't work for me in the default else branch.
            // NSComboBoxCell works, and that derives from NSTextFieldCell, on the other
            // hand setShowsFirstResponder causes a hangs when set on NSComboBoxCell
            NSRect out = [pBtn focusRingMaskBoundsForFrame: rect inView: pView];
            CGContextFillRect(context, out);
        }
        else if ([pBtn isKindOfClass: [NSSliderCell class]])
        {
            // Not getting anything useful for a NSSliderCell, so use the knob
            [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
        }
        else
            [pBtn drawFocusRingMaskWithFrame:rect inView: pView];

        CGContextEndTransparencyLayer(context);
    }

    [NSGraphicsContext setCurrentContext:savedContext];
    CGContextRestoreGState(context);
}

static void paintFocusRect(double radius, const NSRect& rect, CGContextRef context)
{
    NSRect bounds = rect;

    CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, nullptr);
    CGContextSetStrokeColorWithColor(context, [NSColor keyboardFocusIndicatorColor].CGColor);
    CGContextSetLineWidth(context, FOCUS_RING_WIDTH);
    CGContextBeginPath(context);
    CGContextAddPath(context, path);
    CGContextStrokePath(context);
    CFRelease(path);
}

@interface FixedWidthTabViewItem : NSTabViewItem {
    int m_nWidth;
}
- (NSSize)sizeOfLabel: (BOOL)computeMin;
- (void)setTabWidth: (int)nWidth;
@end

@implementation FixedWidthTabViewItem
- (NSSize)sizeOfLabel: (BOOL)computeMin
{
    NSSize size = [super sizeOfLabel: computeMin];
    size.width = m_nWidth;
    return size;
}
- (void)setTabWidth: (int)nWidth
{
    m_nWidth = nWidth;
}
@end

bool AquaGraphicsBackend::drawNativeControl(ControlType nType,
                                            ControlPart nPart,
                                            const tools::Rectangle &rControlRegion,
                                            ControlState nState,
                                            const ImplControlValue &aValue)
{
    if (!mrShared.checkContext())
        return false;
    mrShared.maContextHolder.saveState();
    bool bOK = performDrawNativeControl(nType, nPart, rControlRegion, nState, aValue,
                                        mrShared.maContextHolder.get(), mrShared.mpFrame);
    mrShared.maContextHolder.restoreState();

    tools::Rectangle buttonRect = rControlRegion;

    // in most cases invalidating the whole control region instead of just the unclipped part of it is sufficient (and probably
    // faster). However for the window background we should not unnecessarily enlarge the really changed rectangle since the
    // difference is usually quite high. Background is always drawn as a whole since we don't know anything about its possible
    // contents (see issue i90291).

    if (nType == ControlType::WindowBackground)
    {
        CGRect aRect = {{0, 0}, {0, 0}};
        if (mrShared.mxClipPath)
            aRect = CGPathGetBoundingBox(mrShared.mxClipPath);
        if (aRect.size.width != 0 && aRect.size.height != 0)
            buttonRect.Intersection(tools::Rectangle(Point(static_cast<tools::Long>(aRect.origin.x),
                                                           static_cast<tools::Long>(aRect.origin.y)),
                                                     Size(static_cast<tools::Long>(aRect.size.width),
                                                          static_cast<tools::Long>(aRect.size.height))));
    }
    mrShared.refreshRect(buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight());
    return bOK;
}

static void drawBox(CGContextRef context, const NSRect& rc, NSColor* pColor)
{
    assert(pColor);

    CGContextSaveGState(context);
    CGContextTranslateCTM(context, rc.origin.x, rc.origin.y + rc.size.height);
    CGContextScaleCTM(context, 1, -1);

    NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];

    NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
    NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
    [pBox setBoxType: NSBoxCustom];
    [pBox setFillColor: pColor];

    // -[NSBox setBorderType: NSNoBorder] is deprecated so hide the border
    // by setting the border color to transparent
    [pBox setBorderColor: [NSColor clearColor]];
    [pBox setTitlePosition: NSNoTitle];

    [pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];

    [pBox release];

    CGContextRestoreGState(context);
}

// if I don't crystallize this bg then the InvertCursor using kCGBlendModeDifference doesn't
// work correctly and the cursor doesn't appear correctly
static void drawEditableBackground(CGContextRef context, const NSRect& rc)
{
    CGContextSaveGState(context);
    if (ThemeColors::VclPluginCanUseThemeColors())
        CGContextSetFillColorWithColor(context, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()).CGColor);
    else
        CGContextSetFillColorWithColor(context, [NSColor controlBackgroundColor].CGColor);
    CGContextFillRect(context, rc);
    CGContextRestoreGState(context);
}

static bool useTahoeNativeBounds()
{
    if (@available(macOS 26.2, *))
        return true;
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
    else if (@available(macOS 26, *))
        return true;
#endif

    return false;
}

static bool adjustPreTahoeBuildForTahoeNativeBounds()
{
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 260000
    if (@available(macOS 26.2, *))
        return true;
#endif

    return false;
}

static int spinButtonWidth()
{
    // Builds on pre-Tahoe macOS versions still render the pre-Tahoe style
    // so further adjustments are needed when running on macOS Tahoe.
    if (useTahoeNativeBounds() && !adjustPreTahoeBuildForTahoeNativeBounds())
        return 23;
    else
        return 16;
}

static NSWindow* createControlDrawingWindow()
{
    return [[[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, 1, 1) styleMask: NSWindowStyleMaskBorderless backing: NSBackingStoreBuffered defer: NO] autorelease];
}

// As seen in macOS 12.3.1. All a bit odd really.
const int RoundedMargin[4] = { 6, 4, 0, 3 };

bool AquaGraphicsBackendBase::performDrawNativeControl(ControlType nType,
                                ControlPart nPart,
                                const tools::Rectangle &rControlRegion,
                                ControlState nState,
                                const ImplControlValue &aValue,
                                CGContextRef context,
                                AquaSalFrame* mpFrame)
{
    bool bOK = false;
    bool bCanUseThemeColors(ThemeColors::VclPluginCanUseThemeColors());
    AquaSalInstance* pInst = GetSalData()->mpInstance;
    HIRect rc = ImplGetHIRectFromRectangle(rControlRegion);
    switch (nType)
    {
        case ControlType::Toolbar:
            {
                if (bCanUseThemeColors)
                    drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetWindowColor()));
                else
                    drawBox(context, rc, NSColor.windowBackgroundColor);
                bOK = true;
            }
            break;
        case ControlType::WindowBackground:
            {
                if (bCanUseThemeColors)
                    drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetWindowColor()));
                else
                    drawBox(context, rc, NSColor.windowBackgroundColor);
                bOK = true;
            }
            break;
        case ControlType::Tooltip:
            {
                rc.size.width += 2;
                rc.size.height += 2;
                if (bCanUseThemeColors)
                    drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()));
                else
                    drawBox(context, rc, NSColor.controlBackgroundColor);
                bOK = true;
            }
            break;
        case ControlType::Menubar:
        case ControlType::MenuPopup:
            if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::HasBackgroundTexture)
            {
                // FIXME: without this magical offset there is a 2 pixel black border on the right

                rc.size.width += 2;
                HIThemeMenuDrawInfo aMenuInfo;
                aMenuInfo.version = 0;
                aMenuInfo.menuType = kThemeMenuTypePullDown;
                HIThemeMenuItemDrawInfo aMenuItemDrawInfo;

                // grey theme when the item is selected is drawn here.

                aMenuItemDrawInfo.itemType = kThemeMenuItemPlain;
                if ((nPart == ControlPart::MenuItem) && (nState & ControlState::SELECTED))

                    // blue theme when the item is selected is drawn here.

                    aMenuItemDrawInfo.state = kThemeMenuSelected;
                else

                    // normal color for non selected item

                    aMenuItemDrawInfo.state = kThemeMenuActive;

                // repaints the background of the pull down menu

                HIThemeDrawMenuBackground(&rc, &aMenuInfo, context, kHIThemeOrientationNormal);

                // repaints the item either blue (selected) and/or grey (active only)

                HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, context, kHIThemeOrientationNormal, &rc);
                bOK = true;
            }
            else if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
            {
                // checked, else it is not displayed (see vcl/source/window/menu.cxx)

                if (nState & ControlState::PRESSED)
                {
                    HIThemeTextInfo aTextInfo;
                    aTextInfo.version = 0;
                    aTextInfo.state = (nState & ControlState::ENABLED) ? kThemeStateInactive: kThemeStateActive;
                    aTextInfo.fontID = kThemeMenuItemMarkFont;
                    aTextInfo.horizontalFlushness = kHIThemeTextHorizontalFlushCenter;
                    aTextInfo.verticalFlushness = kHIThemeTextVerticalFlushTop;
                    aTextInfo.options = kHIThemeTextBoxOptionNone;
                    aTextInfo.truncationPosition = kHIThemeTextTruncationNone;

                    // aTextInfo.truncationMaxLines unused because of kHIThemeTextTruncationNone item highlighted

                    if (nState & ControlState::SELECTED) aTextInfo.state = kThemeStatePressed;
                    UniChar mark=(nPart == ControlPart::MenuItemCheckMark) ? kCheckUnicode: kBulletUnicode;
                    CFStringRef cfString = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &mark, 1, kCFAllocatorNull);
                    HIThemeDrawTextBox(cfString, &rc, &aTextInfo, context, kHIThemeOrientationNormal);
                    if (cfString)
                        CFRelease(cfString);
                    bOK = true;
                }
            }
            break;
        case ControlType::Pushbutton:
            {
                NSControlSize eSizeKind = NSControlSizeRegular;
                NSBezelStyle eBezelStyle = NSBezelStylePush;

                PushButtonValue const *pPBVal = aValue.getType() == ControlType::Pushbutton ?
                                                static_cast<PushButtonValue const *>(&aValue) : nullptr;

                SInt32 nPaintHeight = rc.size.height;
                if (rc.size.height <= PUSH_BUTTON_NORMAL_HEIGHT)
                {
                    eSizeKind = NSControlSizeMini;
                    GetThemeMetric(kThemeMetricSmallPushButtonHeight, &nPaintHeight);
                }
                else if ((pPBVal && pPBVal->mbSingleLine) || rc.size.height < PUSH_BUTTON_NORMAL_HEIGHT * 3 / 2)
                {
                    GetThemeMetric(kThemeMetricPushButtonHeight, &nPaintHeight);
                }
                else if (pPBVal && !pPBVal->mbSingleLine)
                {
                    // If not a single line button, allow the button to expand
                    // its height
                    eBezelStyle = NSBezelStyleFlexiblePush;
                }
                else
                {
                    // A simple square bezel style that can scale to any size
                    eBezelStyle = NSBezelStyleSmallSquare;
                }

                // translate the origin for controls with fixed paint height so content ends up somewhere sensible
                rc.origin.y += (rc.size.height - nPaintHeight + 1) / 2;
                rc.size.height = nPaintHeight;

                NSButtonCell* pBtn = pInst->mpButtonCell;
                pBtn.allowsMixedState = YES;

                [pBtn setTitle: @""];
                [pBtn setButtonType: NSButtonTypeMomentaryPushIn];
                [pBtn setBezelStyle: eBezelStyle];
                [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
                [pBtn setEnabled: getEnabled(nState, mpFrame)];
                [pBtn setFocusRingType: NSFocusRingTypeExterior];
                [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
                [pBtn setControlSize: eSizeKind];
                if (nState & ControlState::DEFAULT)
                    [pBtn setKeyEquivalent: @"\r"];
                else
                    [pBtn setKeyEquivalent: @""];

                if (eBezelStyle == NSBezelStylePush || eBezelStyle == NSBezelStyleFlexiblePush)
                {
                    int nMargin = RoundedMargin[eSizeKind];
                    rc.origin.x -= nMargin;
                    rc.size.width += nMargin * 2;

                    if (useTahoeNativeBounds())
                    {
                        rc.origin.x += FOCUS_RING_WIDTH * 2;
                        rc.size.width -= FOCUS_RING_WIDTH * 4;
                        if (eBezelStyle == NSBezelStyleFlexiblePush)
                        {
                            rc.origin.y += 1;
                            rc.size.height -= FOCUS_RING_WIDTH;
                        }

                        // Builds on pre-Tahoe macOS versions still render
                        // the pre-Tahoe style so further adjustments are
                        // needed when running on macOS Tahoe.
                        if (adjustPreTahoeBuildForTahoeNativeBounds())
                        {
                            if (eBezelStyle == NSBezelStyleFlexiblePush)
                            {
                                rc.origin.x -= FOCUS_RING_WIDTH;
                                rc.size.width += FOCUS_RING_WIDTH * 2;
                                rc.origin.y -= (FOCUS_RING_WIDTH / 2) - 1;
                                rc.size.height += FOCUS_RING_WIDTH;
                            }
                            else
                            {
                                rc.origin.x -= FOCUS_RING_WIDTH * 2;
                                rc.size.width += FOCUS_RING_WIDTH * 4;
                                rc.origin.y -= FOCUS_RING_WIDTH * 2;
                                rc.size.height += FOCUS_RING_WIDTH * 4;
                            }
                        }
                    }
                    else
                    {
                        if (eBezelStyle == NSBezelStyleFlexiblePush)
                        {
                            rc.origin.x += FOCUS_RING_WIDTH;
                            rc.size.width -= FOCUS_RING_WIDTH * 2;
                        }
                        else
                        {
                            rc.origin.x += FOCUS_RING_WIDTH / 2;
                            rc.size.width -= FOCUS_RING_WIDTH;
                        }
                    }
                }

                const bool bFocused(nState & ControlState::FOCUSED);
                paintCell(pBtn, rc, bFocused, context, nullptr);

                bOK = true;
            }
            break;
        case ControlType::Radiobutton:
        case ControlType::Checkbox:
            {
                rc.size.width -= 2 * FOCUS_RING_WIDTH;
                rc.size.height = RADIO_BUTTON_SMALL_SIZE;
                rc.origin.x += FOCUS_RING_WIDTH;
                rc.origin.y += FOCUS_RING_WIDTH;

                NSButtonCell* pBtn = nType == ControlType::Checkbox ? pInst->mpCheckCell : pInst->mpRadioCell;
                pBtn.allowsMixedState = YES;

                [pBtn setTitle: @""];
                [pBtn setButtonType: nType == ControlType::Checkbox ? NSButtonTypeSwitch : NSButtonTypeRadio];
                [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
                [pBtn setEnabled: getEnabled(nState, mpFrame)];
                [pBtn setFocusRingType: NSFocusRingTypeExterior];
                [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];

                const bool bFocused(nState & ControlState::FOCUSED);
                paintCell(pBtn, rc, bFocused, context, nullptr);

                bOK = true;
            }
            break;
        case ControlType::ListNode:
            {
                NSButtonCell* pBtn = pInst->mpListNodeCell;
                pBtn.allowsMixedState = YES;

                [pBtn setTitle: @""];
                [pBtn setButtonType: NSButtonTypeOnOff];
                [pBtn setBezelStyle: NSBezelStyleDisclosure];
                [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
                [pBtn setEnabled: getEnabled(nState, mpFrame)];
                [pBtn setFocusRingType: NSFocusRingTypeExterior];

                const bool bFocused(nState & ControlState::FOCUSED);
                paintCell(pBtn, rc, bFocused, context, nullptr);

                bOK = true;
            }
            break;
        case ControlType::LevelBar:
            {
                NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
                NSLevelIndicator* pBox = [[NSLevelIndicator alloc] initWithFrame:rect];
                [pBox setLevelIndicatorStyle: NSLevelIndicatorStyleContinuousCapacity];
                [pBox setMinValue: 0];
                [pBox setMaxValue: rc.size.width];
                [pBox setCriticalValue: rc.size.width * 35.0 / 100.0];
                [pBox setWarningValue: rc.size.width * 70.0 / 100.0];
                [pBox setDoubleValue: aValue.getNumericVal()];

                CGContextSaveGState(context);
                CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);

                NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
                NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
                [NSGraphicsContext setCurrentContext: graphicsContext];

                [pBox drawRect: rect];

                [NSGraphicsContext setCurrentContext: savedContext];

                CGContextRestoreGState(context);

                [pBox release];

                bOK = true;
            }
            break;
        case ControlType::Progress:
        case ControlType::IntroProgress:
            {
                NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
                NSProgressIndicator* pBox = [[NSProgressIndicator alloc] initWithFrame: rect];
                [pBox setControlSize: (rc.size.height > MEDIUM_PROGRESS_INDICATOR_HEIGHT) ?
                                      NSControlSizeRegular : NSControlSizeSmall];
                [pBox setMinValue: 0];
                [pBox setMaxValue: rc.size.width];
                [pBox setDoubleValue: aValue.getNumericVal()];
                pBox.usesThreadedAnimation = NO;
                [pBox setIndeterminate: NO];

                CGContextSaveGState(context);
                CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);

                NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
                NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
                [NSGraphicsContext setCurrentContext: graphicsContext];

                [pBox drawRect: rect];

                [NSGraphicsContext setCurrentContext: savedContext];

                CGContextRestoreGState(context);

                [pBox release];

                // tdf#164428 Skia/Metal needs flush after drawing progress bar
                assert(SkiaHelper::isVCLSkiaEnabled() && "macos requires skia");
                if (SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
                    mpFrame->mbForceFlushProgressBar = true;

                bOK = true;
            }
            break;
        case ControlType::Slider:
            {
                const SliderValue *pSliderVal = static_cast<SliderValue const *>(&aValue);
                if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
                {
                    NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
                    NSSlider* pBox = [[NSSlider alloc] initWithFrame: rect];

                    [pBox setEnabled: getEnabled(nState, mpFrame)];
                    [pBox setVertical: nPart == ControlPart::TrackVertArea];
                    [pBox setMinValue: pSliderVal->mnMin];
                    [pBox setMaxValue: pSliderVal->mnMax];
                    [pBox setIntegerValue: pSliderVal->mnCur];
                    [pBox setSliderType: NSSliderTypeLinear];
                    [pBox setFocusRingType: NSFocusRingTypeExterior];

                    const bool bFocused(nState & ControlState::FOCUSED);
                    paintCell(pBox.cell, rc, bFocused, context, mpFrame->getNSView());

                    [pBox release];

                    bOK = true;
                }
            }
            break;
        case ControlType::Scrollbar:
            {
                const ScrollbarValue *pScrollbarVal = (aValue.getType() == ControlType::Scrollbar)
                                                    ? static_cast<const ScrollbarValue *>(&aValue) : nullptr;
                if (nPart == ControlPart::DrawBackgroundVert || nPart == ControlPart::DrawBackgroundHorz)
                {
                    if (bCanUseThemeColors)
                        drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()));
                    else
                        drawBox(context, rc, NSColor.controlBackgroundColor);

                    NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
                    NSScroller* pBar = [[NSScroller alloc] initWithFrame: rect];

                    double range = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize - pScrollbarVal->mnMin;
                    double value = range ? (pScrollbarVal->mnCur - pScrollbarVal->mnMin) / range : 0;

                    double length = pScrollbarVal->mnMax - pScrollbarVal->mnMin;
                    double proportion = pScrollbarVal->mnVisibleSize / length;

                    [pBar setEnabled: getEnabled(nState, mpFrame)];
                    [pBar setScrollerStyle: NSScrollerStyleLegacy];
                    [pBar setFloatValue: value];
                    [pBar setKnobProportion: proportion];
                    bool bPressed = (pScrollbarVal->mnThumbState & ControlState::ENABLED) &&
                                    (pScrollbarVal->mnThumbState & ControlState::PRESSED);

                    CGContextSaveGState(context);
                    CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);

                    NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];

                    NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
                    [NSGraphicsContext setCurrentContext: graphicsContext];

                    // For not-pressed first draw without the knob and then
                    // draw just the knob but with 50% opaque which looks sort of
                    // right

                    [pBar drawKnobSlotInRect: rect highlight: NO];

                    NSBitmapImageRep* pImageRep = [pBar bitmapImageRepForCachingDisplayInRect: rect];

                    NSGraphicsContext* imageContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:pImageRep];
                    [NSGraphicsContext setCurrentContext: imageContext];

                    [pBar drawKnob];

                    [NSGraphicsContext setCurrentContext: graphicsContext];

                    NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
                    [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep

                    [pImage drawInRect: rect fromRect: rect
                                        operation: NSCompositingOperationSourceOver
                                        fraction: bPressed ? 1.0 : 0.5];

                    [pImage release];

                    [NSGraphicsContext setCurrentContext:savedContext];

                    CGContextRestoreGState(context);

                    bOK = true;

                    [pBar release];
                }
            }
            break;
        case ControlType::TabPane:
            {
                NSTabView* pBox = [[NSTabView alloc] initWithFrame: rc];

                SInt32 nOverlap;
                GetThemeMetric(kThemeMetricTabFrameOverlap, &nOverlap);

                // this calculation is probably more than a little dubious
                rc.origin.x -= pBox.contentRect.origin.x - FOCUS_RING_WIDTH;
                rc.size.width += rc.size.width - pBox.contentRect.size.width - 2 * FOCUS_RING_WIDTH;
                double nTopBorder = pBox.contentRect.origin.y;
                double nBottomBorder = rc.size.height - pBox.contentRect.size.height - nTopBorder;
                double nExtraTop = (nTopBorder - nBottomBorder) / 2;
                rc.origin.y -= (nTopBorder - nExtraTop + nOverlap);
                rc.size.height += (nTopBorder - nExtraTop + nBottomBorder);

                CGContextSaveGState(context);
                CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);

                rc.origin.x = 0;
                rc.origin.y = 0;

                [pBox setBoundsOrigin: rc.origin];
                [pBox setBoundsSize: rc.size];

                // jam this in to force the tab contents area to be left undrawn, the ControlType::TabItem
                // will be drawn in this space.
                const TabPaneValue& rValue = static_cast<const TabPaneValue&>(aValue);
                SInt32 nEndCapWidth;
                GetThemeMetric(kThemeMetricLargeTabCapsWidth, &nEndCapWidth);
                FixedWidthTabViewItem* pItem = [[[FixedWidthTabViewItem alloc] initWithIdentifier: @"tab"] autorelease];
                [pItem setTabWidth: rValue.m_aTabHeaderRect.GetWidth() - 2 * nEndCapWidth];
                [pBox addTabViewItem: pItem];

                NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];

                NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
                [NSGraphicsContext setCurrentContext: graphicsContext];

                [pBox drawRect: rc];

                [NSGraphicsContext setCurrentContext: savedContext];

                [pBox release];

                CGContextRestoreGState(context);

                bOK = true;
            }
            break;
        case ControlType::TabItem:
            {
                // first, last or middle tab

                TabitemValue const * pTabValue = static_cast<TabitemValue const *>(&aValue);
                TabitemFlags nAlignment = pTabValue->mnAlignment;

                // TabitemFlags::LeftAligned (and TabitemFlags::RightAligned) for the leftmost (or rightmost) tab
                // when there are several lines of tabs because there is only one first tab and one
                // last tab and TabitemFlags::FirstInGroup (and TabitemFlags::LastInGroup) because when the
                // line width is different from window width, there may not be TabitemFlags::RightAligned
                int nPaintIndex = 1;
                bool bSolo = false;
                if (((nAlignment & TabitemFlags::LeftAligned) && (nAlignment & TabitemFlags::RightAligned))
                    || ((nAlignment & TabitemFlags::FirstInGroup) && (nAlignment & TabitemFlags::LastInGroup)))
                {
                    nPaintIndex = 0;
                    bSolo = true;
                }
                else if ((nAlignment & TabitemFlags::LeftAligned) || (nAlignment & TabitemFlags::FirstInGroup))
                    nPaintIndex = !AllSettings::GetLayoutRTL() ? 0 : 2;
                else if ((nAlignment & TabitemFlags::RightAligned) || (nAlignment & TabitemFlags::LastInGroup))
                    nPaintIndex = !AllSettings::GetLayoutRTL() ? 2 : 0;

                int nCells = !bSolo ? 3 : 1;
                NSRect ctrlrect = { NSZeroPoint, NSMakeSize(rc.size.width * nCells + FOCUS_RING_WIDTH, rc.size.height) };
                NSSegmentedControl* pCtrl = [[NSSegmentedControl alloc] initWithFrame: ctrlrect];
                [pCtrl setSegmentCount: nCells];
                if (bSolo)
                {
                    if (useTahoeNativeBounds())
                        [pCtrl setWidth: rc.size.width forSegment: 0];
                    else
                        [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH forSegment: 0];
                }
                else
                {
                    if (useTahoeNativeBounds())
                    {
                        [pCtrl setWidth: rc.size.width - FOCUS_RING_WIDTH/2 forSegment: 0];
                        [pCtrl setWidth: rc.size.width - FOCUS_RING_WIDTH/4 forSegment: 1];
                        [pCtrl setWidth: rc.size.width - FOCUS_RING_WIDTH/2 forSegment: 2];
                    }
                    else
                    {

                        [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 0];
                        [pCtrl setWidth: rc.size.width forSegment: 1];
                        [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 2];
                    }
                }
                [pCtrl setSelected: (nState & ControlState::SELECTED) ? YES : NO forSegment: nPaintIndex];
                [pCtrl setFocusRingType: NSFocusRingTypeExterior];

                // Fix tab item drawing failures on macOS Tahoe
                // Starting in macOS Tahoe, there are two issues that affect
                // drawing of tab items:
                // 1. When compiled and run on macOS Tahoe, the tab item will
                //    fail to draw if the NSSegmentedControl is not a subview
                //    of a window whether the window is visible or not.
                // 2. The selected tab item has the same bright background
                //    color as the default push button when the tab item is
                //    in the key window. In previous versions of macOS, the
                //    same background color is used for the selected tab
                //    item whether the tab item is enabled or not.
                // Both issues are fixed by adding the NSSegmentedControl
                // as a subview of a window. Note that the window must be
                // a key window to draw the selected tab item with the new
                // bright background color.
                if (@available(macOS 26, *))
                {
                    NSWindow *pKeyWindow = nil;
                    if (mpFrame)
                    {
                        // Note: use -[NSWindow isKeyWindow] as it may return
                        // YES even when +[NSApp keyWindow] is nil.
                        NSWindow *pFrameWindow = mpFrame->getNSWindow();
                        if (pFrameWindow && [pFrameWindow isKindOfClass: [SalFrameWindow class]] && [pFrameWindow isKeyWindow])
                            pKeyWindow = pFrameWindow;
                    }

                    // Default to adding the NSSegmentedControl to a window
                    // that is not visible. However, in order to draw selected
                    // tab items with the new bright background color, the
                    // window must be visible since only visible windows can
                    // be the key window. In that case, add the control to
                    // the key window.
                    NSWindow *pControlDrawingWindow = pKeyWindow;
                    if (!pControlDrawingWindow)
                        pControlDrawingWindow = createControlDrawingWindow();
                    if (pControlDrawingWindow)
                        [[pControlDrawingWindow contentView] addSubview: pCtrl positioned: NSWindowBelow relativeTo: nil];
                }

                NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
                [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];

                NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
                NSRect tabrect = { NSMakePoint(rc.size.width * nPaintIndex + FOCUS_RING_WIDTH / 2, 0),
                                   NSMakeSize(rc.size.width, rc.size.height) };
                NSBitmapImageRep* pImageRep = [pCtrl bitmapImageRepForCachingDisplayInRect: tabrect];
                [pCtrl cacheDisplayInRect: tabrect toBitmapImageRep: pImageRep];

                NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
                [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep

                [pImage drawInRect: rc fromRect: rect
                        operation: NSCompositingOperationSourceOver
                        fraction: 1.0];

                [pImage release];

                [NSGraphicsContext setCurrentContext:savedContext];

                if (@available(macOS 26, *))
                    [pCtrl removeFromSuperviewWithoutNeedingDisplay];
                [pCtrl release];

                if (nState & ControlState::FOCUSED)
                {
                    if (bSolo)
                    {
                        if (useTahoeNativeBounds())
                        {
                            rc.origin.y += FOCUS_RING_WIDTH / 4;
                            rc.size.height -= FOCUS_RING_WIDTH / 2;

                            // Builds on pre-Tahoe macOS versions still render
                            // the pre-Tahoe style so further adjustments are
                            // needed when running on macOS Tahoe.
                            if (adjustPreTahoeBuildForTahoeNativeBounds())
                            {
                                rc.size.width -= 1;
                                rc.origin.y += 1;
                                rc.size.height -= FOCUS_RING_WIDTH / 2;
                            }
                        }
                        else
                        {
                            rc.size.width -= 1;
                        }
                    }
                    else
                    {
                        if (useTahoeNativeBounds())
                        {
                            if (nPaintIndex == 0)
                                rc.origin.x += FOCUS_RING_WIDTH / 2;
                            else
                                rc.origin.x += FOCUS_RING_WIDTH / 4;
                            rc.origin.y += FOCUS_RING_WIDTH / 2;
                            if (nPaintIndex == 1)
                                rc.size.width -= FOCUS_RING_WIDTH / 2;
                            else
                                rc.size.width -= FOCUS_RING_WIDTH * 3 / 4;
                            rc.size.height -= FOCUS_RING_WIDTH;

                            // Builds on pre-Tahoe macOS versions still render
                            // the pre-Tahoe style so further adjustments are
                            // needed when running on macOS Tahoe.
                            if (adjustPreTahoeBuildForTahoeNativeBounds())
                            {
                                if (nPaintIndex == 0)
                                {
                                    rc.origin.x -= FOCUS_RING_WIDTH / 2;
                                    rc.size.width += FOCUS_RING_WIDTH / 2;
                                }
                                else if (nPaintIndex == 1)
                                {
                                    rc.origin.x -= FOCUS_RING_WIDTH / 2 + 1;
                                    rc.size.width += FOCUS_RING_WIDTH;
                                }
                                else if (nPaintIndex == 2)
                                {
                                    rc.origin.x -= FOCUS_RING_WIDTH / 2;
                                    rc.size.width += FOCUS_RING_WIDTH;
                                }
                            }
                        }
                        else
                        {
                            if (nPaintIndex == 1)
                            {
                                rc.origin.x -= 1;
                                rc.size.width += 1;
                            }
                            else if (nPaintIndex == 2)
                            {
                                rc.origin.x -= 1;
                                rc.size.width += 1;
                            }
                        }
                    }

                    paintFocusRect(4.0, rc, context);
                }

                // The Skia flushing timer appears to stop when modal windows
                // (e.g. the native Open or Print dialogs) are displayed
                if (@available(macOS 26, *))
                {
                    if (mpFrame && [NSApp modalWindow])
                        mpFrame->mbForceFlushTabItems = [NSApp modalWindow];
                }

                bOK=true;
            }
            break;
        case ControlType::Editbox:
        case ControlType::MultilineEditbox:
            {
                rc.size.width += 2 * EDITBOX_INSET_MARGIN;
                if (nType == ControlType::Editbox)
                  rc.size.height = EDITBOX_HEIGHT;
                else
                  rc.size.height += 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
                rc.origin.x -= EDITBOX_INSET_MARGIN;
                rc.origin.y -= EDITBOX_INSET_MARGIN;

                NSTextFieldCell* pBtn = pInst->mpTextFieldCell;

                [pBtn setEnabled: getEnabled(nState, mpFrame)];
                [pBtn setBezeled: YES];
                [pBtn setEditable: YES];
                [pBtn setFocusRingType: NSFocusRingTypeExterior];

                drawEditableBackground(context, rc);
                const bool bFocused(nState & ControlState::FOCUSED);
                paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());

                bOK = true;
            }
            break;
        case ControlType::Combobox:
            if (nPart == ControlPart::HasBackgroundTexture || nPart == ControlPart::Entire)
            {
                rc.origin.y += (rc.size.height - COMBOBOX_HEIGHT + 1) / 2;
                rc.size.height = COMBOBOX_HEIGHT;

                NSComboBoxCell* pBtn = pInst->mpComboBoxCell;

                [pBtn setEnabled: getEnabled(nState, mpFrame)];
                [pBtn setEditable: YES];
                [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
                [pBtn setFocusRingType: NSFocusRingTypeExterior];

                {
                    rc.origin.x += 2;
                    if (useTahoeNativeBounds())
                        rc.size.width -= 4;
                    else
                        rc.size.width -= 1;
                }

                drawEditableBackground(context, rc);
                const bool bFocused(nState & ControlState::FOCUSED);
                paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());

                bOK = true;
            }
            break;
        case ControlType::Listbox:

            switch (nPart)
            {
                case ControlPart::Entire:
                case ControlPart::ButtonDown:
                {
                    rc.origin.y += (rc.size.height - LISTBOX_HEIGHT + 1) / 2;
                    rc.size.height = LISTBOX_HEIGHT;

                    NSPopUpButtonCell* pBtn = pInst->mpPopUpButtonCell;

                    [pBtn setTitle: @""];
                    [pBtn setEnabled: getEnabled(nState, mpFrame)];
                    [pBtn setFocusRingType: NSFocusRingTypeExterior];
                    [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
                    if (nState & ControlState::DEFAULT)
                        [pBtn setKeyEquivalent: @"\r"];
                    else
                        [pBtn setKeyEquivalent: @""];

                    if (useTahoeNativeBounds())
                    {
                        rc.origin.x += 2;
                        rc.size.width -= 4;
                    }
                    else
                    {
                        rc.size.width += 1;
                    }

                    const bool bFocused(nState & ControlState::FOCUSED);
                    paintCell(pBtn, rc, bFocused, context, nullptr);

                    bOK = true;
                    break;
                }
                case ControlPart::ListboxWindow:
                {
                    NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
                    NSScrollView* pBox = [[NSScrollView alloc] initWithFrame: rect];
                    [pBox setBorderType: NSLineBorder];

                    CGContextSaveGState(context);
                    CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);

                    NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
                    NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
                    [NSGraphicsContext setCurrentContext: graphicsContext];

                    [pBox drawRect: rect];

                    [NSGraphicsContext setCurrentContext: savedContext];

                    CGContextRestoreGState(context);

                    [pBox release];

                    bOK = true;
                    break;
                }
                default:
                    break;
            }
            break;
        case ControlType::Spinbox:
            if (nPart == ControlPart::Entire)
            {
                // text field

                rc.size.width -= SPIN_BUTTON_WIDTH + 4 * FOCUS_RING_WIDTH;
                rc.size.height = EDITBOX_HEIGHT;
                rc.origin.x += FOCUS_RING_WIDTH;
                rc.origin.y += FOCUS_RING_WIDTH;

                NSTextFieldCell* pEdit = pInst->mpTextFieldCell;

                [pEdit setEnabled: YES];
                [pEdit setBezeled: YES];
                [pEdit setEditable: YES];
                [pEdit setFocusRingType: NSFocusRingTypeExterior];

                drawEditableBackground(context, rc);
                const bool bFocused(nState & ControlState::FOCUSED);
                paintCell(pEdit, rc, bFocused, context, mpFrame->getNSView());

                // buttons

                const SpinbuttonValue *pSpinButtonVal = (aValue.getType() == ControlType::SpinButtons)
                                                      ? static_cast <const SpinbuttonValue *>(&aValue) : nullptr;
                if (pSpinButtonVal)
                {
                    ControlState nUpperState = pSpinButtonVal->mnUpperState;
                    ControlState nLowerState = pSpinButtonVal->mnLowerState;

                    rc.origin.x += rc.size.width + FOCUS_RING_WIDTH + 1;
                    if (!useTahoeNativeBounds())
                        rc.origin.y -= 1;
                    rc.size.width = SPIN_BUTTON_WIDTH;
                    rc.size.height = SPIN_UPPER_BUTTON_HEIGHT + SPIN_LOWER_BUTTON_HEIGHT;

                    NSStepperCell* pBtn = pInst->mpStepperCell;

                    [pBtn setTitle: @""];
                    [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
                    [pBtn setEnabled: (nUpperState & ControlState::ENABLED || nLowerState & ControlState::ENABLED) ?
                                    YES : NO];
                    [pBtn setFocusRingType: NSFocusRingTypeExterior];
                    [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];

                    const bool bSpinFocused(nUpperState & ControlState::FOCUSED || nLowerState & ControlState::FOCUSED);
                    paintCell(pBtn, rc, bSpinFocused, context, nullptr);
                }
                bOK = true;
            }
            break;
        case ControlType::Frame:
            {
                DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(aValue.getNumericVal());
                if (nPart == ControlPart::Border)
                {
                    if (!(nStyle & DrawFrameFlags::Menu) && !(nStyle & DrawFrameFlags::WindowBorder))
                    {

                        // strange effects start to happen when HIThemeDrawFrame meets the border of the window.
                        // These can be avoided by clipping to the boundary of the frame (see issue 84756)

                        if (rc.origin.y + rc.size.height >= mpFrame->GetHeight() - 3)
                        {
                            CGMutablePathRef rPath = CGPathCreateMutable();
                            CGPathAddRect(rPath, nullptr,
                                          CGRectMake(0, 0, mpFrame->GetWidth() - 1, mpFrame->GetHeight() - 1));
                            CGContextBeginPath(context);
                            CGContextAddPath(context, rPath);
                            CGContextClip(context);
                            CGPathRelease(rPath);
                        }
                        HIThemeFrameDrawInfo aTextDrawInfo;
                        aTextDrawInfo.version = 0;
                        aTextDrawInfo.kind = kHIThemeFrameListBox;
                        aTextDrawInfo.state = kThemeStateActive;
                        aTextDrawInfo.isFocused = false;
                        HIThemeDrawFrame(&rc, &aTextDrawInfo, context, kHIThemeOrientationNormal);
                        bOK = true;
                    }
                }
            }
            break;
        case ControlType::ListNet:

            // do nothing as there isn't net for listviews on macOS

            bOK = true;
            break;
        default:
            break;
    }

    return bOK;
}

bool AquaSalGraphics::getNativeControlRegion(ControlType nType,
                                             ControlPart nPart,
                                             const tools::Rectangle &rControlRegion,
                                             ControlState,
                                             const ImplControlValue &aValue,
                                             const OUString &,
                                             tools::Rectangle &rNativeBoundingRegion,
                                             tools::Rectangle &rNativeContentRegion)
{
    bool toReturn = false;
    tools::Rectangle aCtrlBoundRect(rControlRegion);
    short x = aCtrlBoundRect.Left();
    short y = aCtrlBoundRect.Top();
    short w, h;
    switch (nType)
    {
        case ControlType::Pushbutton:
        case ControlType::Radiobutton:
        case ControlType::Checkbox:
            {
                if (nType == ControlType::Pushbutton)
                {
                    w = aCtrlBoundRect.GetWidth();
                    h = aCtrlBoundRect.GetHeight();
                }
                else
                {
                    w = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH + RADIO_BUTTON_TEXT_SEPARATOR;
                    h = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH;
                }
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::LevelBar:
        case ControlType::Progress:
            {
                tools::Rectangle aRect(aCtrlBoundRect);
                if (aRect.GetHeight() < LARGE_PROGRESS_INDICATOR_HEIGHT)
                    aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
                else
                    aRect.SetBottom(aRect.Top() + LARGE_PROGRESS_INDICATOR_HEIGHT - 1);
                rNativeBoundingRegion = aRect;
                rNativeContentRegion = aRect;
                toReturn = true;
            }
            break;
        case ControlType::IntroProgress:
            {
                tools::Rectangle aRect(aCtrlBoundRect);
                aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
                rNativeBoundingRegion = aRect;
                rNativeContentRegion = aRect;
                toReturn = true;
            }
            break;
        case ControlType::Slider:
            if (nPart == ControlPart::ThumbHorz)
            {
                w = SLIDER_WIDTH;
                h = aCtrlBoundRect.GetHeight();
                rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::ThumbVert)
            {
                w = aCtrlBoundRect.GetWidth();
                h = SLIDER_HEIGHT;
                rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::Scrollbar:
            {
                tools::Rectangle aRect;
                if (AquaGetScrollRect(nPart, aCtrlBoundRect, aRect))
                {
                    toReturn = true;
                    rNativeBoundingRegion = aRect;
                    rNativeContentRegion = aRect;
                }
            }
            break;
        case ControlType::TabItem:
            {
                w = aCtrlBoundRect.GetWidth() + 2 * TAB_TEXT_MARGIN;
                if (useTahoeNativeBounds())
                    h = TAB_HEIGHT + 2 + FOCUS_RING_WIDTH;
                else
                    h = TAB_HEIGHT + 2;
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::Editbox:
            {
                const tools::Long nBorderThickness = FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
                // tdf#144241 don't return a negative width, expand the region to the min osx width
                w = std::max(nBorderThickness * 2, aCtrlBoundRect.GetWidth());
                h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                w -= 2 * nBorderThickness;
                h -= 2 * nBorderThickness;
                x += nBorderThickness;
                y += nBorderThickness;
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::Combobox:
            if (nPart == ControlPart::Entire)
            {
                w = aCtrlBoundRect.GetWidth();
                h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::ButtonDown)
            {
                w = COMBOBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
                h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
                x += aCtrlBoundRect.GetWidth() - w;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::SubEdit)
            {
                w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - COMBOBOX_BUTTON_WIDTH - COMBOBOX_BORDER_WIDTH
                    - 2 * COMBOBOX_TEXT_MARGIN;
                h = COMBOBOX_HEIGHT - 2 * COMBOBOX_BORDER_WIDTH;
                x += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH + COMBOBOX_TEXT_MARGIN;
                y += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::Listbox:
            if (nPart == ControlPart::Entire)
            {
                w = aCtrlBoundRect.GetWidth();
                h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::ButtonDown)
            {
                w = LISTBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
                h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
                x += aCtrlBoundRect.GetWidth() - w;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::SubEdit)
            {
                w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - LISTBOX_BUTTON_WIDTH - LISTBOX_BORDER_WIDTH
                    - 2 * LISTBOX_TEXT_MARGIN;
                h = LISTBOX_HEIGHT - 2 * LISTBOX_BORDER_WIDTH;
                x += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH + LISTBOX_TEXT_MARGIN;
                y += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::ListboxWindow)
            {
                w = aCtrlBoundRect.GetWidth() - 2;
                h = aCtrlBoundRect.GetHeight() - 2;
                x += 1;
                y += 1;
                rNativeBoundingRegion = aCtrlBoundRect;
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::Spinbox:
            if (nPart == ControlPart::Entire)
            {
                w = aCtrlBoundRect.GetWidth();
                if (useTahoeNativeBounds())
                    h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH + 1;
                else
                    h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
                x += SPINBOX_OFFSET;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::SubEdit)
            {
                w = aCtrlBoundRect.GetWidth() - 4 * FOCUS_RING_WIDTH - SPIN_BUTTON_WIDTH - 2 * EDITBOX_BORDER_WIDTH
                    - 2 * EDITBOX_INSET_MARGIN;
                h = EDITBOX_HEIGHT - 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
                x += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN + SPINBOX_OFFSET;
                y += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::ButtonUp)
            {
                w = SPIN_BUTTON_WIDTH +  2 * FOCUS_RING_WIDTH;
                h = SPIN_UPPER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
                x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            else if (nPart == ControlPart::ButtonDown)
            {
                w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
                h = SPIN_LOWER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
                x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
                y += FOCUS_RING_WIDTH + SPIN_UPPER_BUTTON_HEIGHT;
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        case ControlType::Frame:
            {
                DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(aValue.getNumericVal() & 0x000f);
                DrawFrameFlags nFlags = static_cast<DrawFrameFlags>(aValue.getNumericVal() & 0xfff0);
                if (nPart == ControlPart::Border
                    && !(nFlags & (DrawFrameFlags::Menu | DrawFrameFlags::WindowBorder | DrawFrameFlags::BorderWindowBorder)))
                {
                    tools::Rectangle aRect(aCtrlBoundRect);
                    if (nStyle == DrawFrameStyle::DoubleIn)
                    {
                        aRect.AdjustLeft(1);
                        aRect.AdjustTop(1);
                        // rRect.Right() -= 1;
                        // rRect.Bottom() -= 1;
                    }
                    else
                    {
                        aRect.AdjustLeft(1);
                        aRect.AdjustTop(1);
                        aRect.AdjustRight(-1);
                        aRect.AdjustBottom(-1);
                    }
                    rNativeContentRegion = aRect;
                    rNativeBoundingRegion = aRect;
                    toReturn = true;
                }
            }
            break;
        case ControlType::Menubar:
        case ControlType::MenuPopup:
            if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
            {
                w=10;
                h=10;
                rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
                rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
                toReturn = true;
            }
            break;
        default:
            break;
    }
    return toReturn;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
