xref: /core/vcl/osx/salnativewidgets.cxx (revision 764799befcb927f68f78e904213e3db7195554cb)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_features.h>
21 #include <tools/long.hxx>
22 #include <vcl/salnativewidgets.hxx>
23 #include <vcl/decoview.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/threadex.hxx>
26 #include <vcl/timer.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/themecolors.hxx>
29 
30 #include <quartz/salgdi.h>
31 #include <osx/salnativewidgets.h>
32 #include <osx/saldata.hxx>
33 #include <osx/salframe.h>
34 
35 #include <premac.h>
36 #include <Carbon/Carbon.h>
37 #include <postmac.h>
38 
39 #include <scrollbarvalue.hxx>
40 
41 #if HAVE_FEATURE_SKIA
42 #include <vcl/skia/SkiaHelper.hxx>
43 #endif
44 
45 #include "cuidraw.hxx"
46 
47 // presentation of native widgets consists of two important methods:
48 
49 // AquaSalGraphics::getNativeControlRegion to determine native rectangle in pixels to draw the widget
50 // AquaSalGraphics::drawNativeControl to do the drawing operation itself
51 
52 // getNativeControlRegion has to calculate a content rectangle within it is safe to draw the widget. Furthermore a bounding rectangle
53 // has to be calculated by getNativeControlRegion to consider adornments like a focus rectangle. As drawNativeControl uses Carbon
54 // API calls, all widgets are drawn without text. Drawing of text is done separately by VCL on top of graphical Carbon widget
55 // representation. drawNativeControl is called by VCL using content rectangle determined by getNativeControlRegion.
56 
57 // FIXME: when calculation bounding rectangle larger then content rectangle, text displayed by VCL will become misaligned. To avoid
58 // misalignment bounding rectangle and content rectangle are calculated equally including adornments. Reduction of size for content
59 // is done by drawNativeControl subsequently. Only exception is editbox: As other widgets have distinct ControlPart::SubEdit control
60 // parts, editbox bounding rectangle and content rectangle are both calculated to reflect content area. Extending size for
61 // adornments is done by drawNativeControl subsequently.
62 
63 #if !HAVE_FEATURE_MACOSX_SANDBOX
64 
65 @interface NSWindow(CoreUIRendererPrivate)
66 + (CUIRendererRef)coreUIRenderer;
67 @end
68 
69 #endif
70 
ImplGetHIRectFromRectangle(tools::Rectangle aRect)71 static HIRect ImplGetHIRectFromRectangle(tools::Rectangle aRect)
72 {
73     HIRect aHIRect;
74     aHIRect.origin.x = static_cast<float>(aRect.Left());
75     aHIRect.origin.y = static_cast<float>(aRect.Top());
76     aHIRect.size.width = static_cast<float>(aRect.GetWidth());
77     aHIRect.size.height = static_cast<float>(aRect.GetHeight());
78     return aHIRect;
79 }
80 
ImplGetButtonValue(ButtonValue aButtonValue)81 static NSControlStateValue ImplGetButtonValue(ButtonValue aButtonValue)
82 {
83     switch (aButtonValue)
84     {
85         case ButtonValue::On:
86             return NSControlStateValueOn;
87         case ButtonValue::Off:
88         case ButtonValue::DontKnow:
89             return NSControlStateValueOff;
90         case ButtonValue::Mixed:
91         default:
92             return NSControlStateValueMixed;
93     }
94 }
95 
AquaGetScrollRect(ControlPart nPart,const tools::Rectangle & rControlRect,tools::Rectangle & rResultRect)96 static bool AquaGetScrollRect(/* TODO: int nScreen, */
97                               ControlPart nPart, const tools::Rectangle &rControlRect, tools::Rectangle &rResultRect)
98 {
99     bool bRetVal = true;
100     rResultRect = rControlRect;
101     switch (nPart)
102     {
103         case ControlPart::ButtonUp:
104             rResultRect.SetBottom(rResultRect.Top());
105             break;
106         case ControlPart::ButtonDown:
107             rResultRect.SetTop(rResultRect.Bottom());
108             break;
109         case ControlPart::ButtonLeft:
110             rResultRect.SetRight(rResultRect.Left());
111             break;
112         case ControlPart::ButtonRight:
113             rResultRect.SetLeft(rResultRect.Right());
114             break;
115         case ControlPart::TrackHorzArea:
116         case ControlPart::TrackVertArea:
117         case ControlPart::ThumbHorz:
118         case ControlPart::ThumbVert:
119         case ControlPart::TrackHorzLeft:
120         case ControlPart::TrackHorzRight:
121         case ControlPart::TrackVertUpper:
122         case ControlPart::TrackVertLower:
123             break;
124         default:
125             bRetVal = false;
126     }
127     return bRetVal;
128 }
129 
isNativeControlSupported(ControlType nType,ControlPart nPart)130 bool AquaSalGraphics::isNativeControlSupported(ControlType nType, ControlPart nPart)
131 {
132     // native controls are now defaults. If you want to disable native controls, set the environment variable SAL_NO_NWF to
133     // something and VCL controls will be used as default again.
134 
135     switch (nType)
136     {
137         case ControlType::Pushbutton:
138         case ControlType::Radiobutton:
139         case ControlType::Checkbox:
140         case ControlType::ListNode:
141             if (nPart == ControlPart::Entire)
142                 return true;
143             break;
144         case ControlType::Scrollbar:
145             if (nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert
146                 || nPart == ControlPart::Entire || nPart == ControlPart::HasThreeButtons)
147                 return true;
148             break;
149         case ControlType::Slider:
150             if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
151                 return true;
152             break;
153         case ControlType::Editbox:
154             if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
155                 return true;
156             break;
157         case ControlType::MultilineEditbox:
158             if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
159                 return true;
160             break;
161         case ControlType::Spinbox:
162             if (nPart == ControlPart::Entire || nPart == ControlPart::AllButtons || nPart == ControlPart::HasBackgroundTexture)
163                 return true;
164             break;
165         case ControlType::SpinButtons:
166             return false;
167         case ControlType::Combobox:
168             if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
169                 return true;
170             break;
171         case ControlType::Listbox:
172             if (nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow || nPart == ControlPart::HasBackgroundTexture
173                 || nPart == ControlPart::SubEdit)
174                 return true;
175             break;
176         case ControlType::TabItem:
177         case ControlType::TabPane:
178         case ControlType::TabBody:
179             if (nPart == ControlPart::Entire || nPart == ControlPart::TabsDrawRtl || nPart == ControlPart::HasBackgroundTexture)
180                 return true;
181             break;
182         case ControlType::Toolbar:
183             if (nPart == ControlPart::Entire || nPart == ControlPart::DrawBackgroundHorz
184                 || nPart == ControlPart::DrawBackgroundVert)
185                 return true;
186             break;
187         case  ControlType::WindowBackground:
188             if (nPart == ControlPart::BackgroundWindow || nPart == ControlPart::BackgroundDialog)
189                  return true;
190             break;
191         case ControlType::Menubar:
192             if (nPart == ControlPart::Entire)
193                 return true;
194             break;
195         case ControlType::Tooltip:
196             if (nPart == ControlPart::Entire)
197                 return true;
198             break;
199         case ControlType::MenuPopup:
200             if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::MenuItemCheckMark
201                 || nPart == ControlPart::MenuItemRadioMark)
202                 return true;
203             break;
204         case ControlType::LevelBar:
205         case ControlType::Progress:
206         case ControlType::IntroProgress:
207             if (nPart == ControlPart::Entire)
208                 return true;
209             break;
210         case ControlType::Frame:
211             if (nPart == ControlPart::Border)
212                 return true;
213             break;
214         case ControlType::ListNet:
215             if (nPart == ControlPart::Entire)
216                 return true;
217             break;
218         default:
219             break;
220     }
221     return false;
222 }
223 
hitTestNativeControl(ControlType nType,ControlPart nPart,const tools::Rectangle & rControlRegion,const Point & rPos,bool & rIsInside)224 bool AquaSalGraphics::hitTestNativeControl(ControlType nType, ControlPart nPart, const tools::Rectangle &rControlRegion,
225                                            const Point &rPos, bool& rIsInside)
226 {
227     if (nType == ControlType::Scrollbar)
228     {
229         tools::Rectangle aRect;
230         bool bValid = AquaGetScrollRect(/* TODO: int nScreen, */
231                                         nPart, rControlRegion, aRect);
232         rIsInside = bValid && aRect.Contains(rPos);
233         return bValid;
234     }
235     return false;
236 }
237 
getEnabled(ControlState nState,AquaSalFrame * mpFrame)238 static bool getEnabled(ControlState nState, AquaSalFrame* mpFrame)
239 {
240 
241     // there are non key windows which are children of key windows, e.g. autofilter configuration dialog or sidebar dropdown dialogs.
242     // To handle these windows correctly, parent frame's key window state is considered here additionally.
243 
244     const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow]
245                              || mpFrame->mpParent == nullptr || [mpFrame->mpParent->getNSWindow() isKeyWindow];
246     if (!(nState & ControlState::ENABLED) || !bDrawActive)
247     {
248         return false;
249     }
250     return true;
251 }
252 
drawNativeControl(ControlType nType,ControlPart nPart,const tools::Rectangle & rControlRegion,ControlState nState,const ImplControlValue & aValue,const OUString &,const Color &)253 bool AquaSalGraphics::drawNativeControl(ControlType nType,
254                                         ControlPart nPart,
255                                         const tools::Rectangle &rControlRegion,
256                                         ControlState nState,
257                                         const ImplControlValue &aValue,
258                                         const OUString &,
259                                         const Color&)
260 {
261     // tdf#165266 Force native controls to use current effective appearance
262     // +[NSAppearance setCurrentAppearance:] is deprecated and calling
263     // that appears to do less and less with each new version of macos
264     // and/or Xcode so run all drawing of native controls in a block passed
265     // to -[NSAppearance performAsCurrentDrawingAppearance:].
266     __block bool bRet = false;
267     if (@available(macOS 11, *))
268     {
269         [[NSApp effectiveAppearance] performAsCurrentDrawingAppearance:^() {
270             bRet = mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
271         }];
272     }
273     else
274     {
275         bRet = mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
276     }
277 
278     return bRet;
279 }
280 
colorFromRGB(const Color & rColor)281 static NSColor* colorFromRGB(const Color& rColor)
282 {
283     return [NSColor colorWithSRGBRed:(rColor.GetRed() / 255.0f)
284                                green:(rColor.GetGreen() / 255.0f)
285                                 blue:(rColor.GetBlue() / 255.0f)
286                                alpha:(rColor.GetAlpha() / 255.0f)];
287 }
288 
paintCell(NSCell * pBtn,const NSRect & bounds,bool bShowsFirstResponder,CGContextRef context,NSView * pView)289 static void paintCell(NSCell* pBtn, const NSRect& bounds, bool bShowsFirstResponder, CGContextRef context, NSView* pView)
290 {
291     //translate and scale because up side down otherwise
292     CGContextSaveGState(context);
293     CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y + bounds.size.height);
294     CGContextScaleCTM(context, 1, -1);
295 
296     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
297     [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
298 
299     NSRect rect = { NSZeroPoint, bounds.size };
300 
301     if ([pBtn isKindOfClass: [NSSliderCell class]])
302     {
303         // NSSliderCell doesn't seem to work with drawWithFrame(?), so draw the elements directly
304         [static_cast<NSSliderCell*>(pBtn)
305             drawBarInside: [static_cast<NSSliderCell*>(pBtn) barRectFlipped: NO] flipped: NO];
306         rect = [static_cast<NSSliderCell*>(pBtn) knobRectFlipped: NO];
307         [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
308     }
309     else
310         [pBtn drawWithFrame: rect inView: pView];
311 
312     // setShowsFirstResponder apparently causes a hang when set on NSComboBoxCell
313     const bool bIsComboBox = [pBtn isMemberOfClass: [NSComboBoxCell class]];
314     if (!bIsComboBox)
315         [pBtn setShowsFirstResponder: bShowsFirstResponder];
316 
317     if (bShowsFirstResponder)
318     {
319         NSSetFocusRingStyle(NSFocusRingOnly);
320 
321         CGContextBeginTransparencyLayerWithRect(context, rect, nullptr);
322         if ([pBtn isMemberOfClass: [NSTextFieldCell class]])
323         {
324             // I wonder why NSTextFieldCell doesn't work for me in the default else branch.
325             // NSComboBoxCell works, and that derives from NSTextFieldCell, on the other
326             // hand setShowsFirstResponder causes a hangs when set on NSComboBoxCell
327             NSRect out = [pBtn focusRingMaskBoundsForFrame: rect inView: pView];
328             CGContextFillRect(context, out);
329         }
330         else if ([pBtn isKindOfClass: [NSSliderCell class]])
331         {
332             // Not getting anything useful for a NSSliderCell, so use the knob
333             [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
334         }
335         else
336             [pBtn drawFocusRingMaskWithFrame:rect inView: pView];
337 
338         CGContextEndTransparencyLayer(context);
339     }
340 
341     [NSGraphicsContext setCurrentContext:savedContext];
342     CGContextRestoreGState(context);
343 }
344 
paintFocusRect(double radius,const NSRect & rect,CGContextRef context)345 static void paintFocusRect(double radius, const NSRect& rect, CGContextRef context)
346 {
347     NSRect bounds = rect;
348 
349     CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, nullptr);
350     CGContextSetStrokeColorWithColor(context, [NSColor keyboardFocusIndicatorColor].CGColor);
351     CGContextSetLineWidth(context, FOCUS_RING_WIDTH);
352     CGContextBeginPath(context);
353     CGContextAddPath(context, path);
354     CGContextStrokePath(context);
355     CFRelease(path);
356 }
357 
358 @interface FixedWidthTabViewItem : NSTabViewItem {
359     int m_nWidth;
360 }
361 - (NSSize)sizeOfLabel: (BOOL)computeMin;
362 - (void)setTabWidth: (int)nWidth;
363 @end
364 
365 @implementation FixedWidthTabViewItem
366 - (NSSize)sizeOfLabel: (BOOL)computeMin
367 {
368     NSSize size = [super sizeOfLabel: computeMin];
369     size.width = m_nWidth;
370     return size;
371 }
372 - (void)setTabWidth: (int)nWidth
373 {
374     m_nWidth = nWidth;
375 }
376 @end
377 
drawNativeControl(ControlType nType,ControlPart nPart,const tools::Rectangle & rControlRegion,ControlState nState,const ImplControlValue & aValue)378 bool AquaGraphicsBackend::drawNativeControl(ControlType nType,
379                                             ControlPart nPart,
380                                             const tools::Rectangle &rControlRegion,
381                                             ControlState nState,
382                                             const ImplControlValue &aValue)
383 {
384     if (!mrShared.checkContext())
385         return false;
386     mrShared.maContextHolder.saveState();
387     bool bOK = performDrawNativeControl(nType, nPart, rControlRegion, nState, aValue,
388                                         mrShared.maContextHolder.get(), mrShared.mpFrame);
389     mrShared.maContextHolder.restoreState();
390 
391     tools::Rectangle buttonRect = rControlRegion;
392 
393     // in most cases invalidating the whole control region instead of just the unclipped part of it is sufficient (and probably
394     // faster). However for the window background we should not unnecessarily enlarge the really changed rectangle since the
395     // difference is usually quite high. Background is always drawn as a whole since we don't know anything about its possible
396     // contents (see issue i90291).
397 
398     if (nType == ControlType::WindowBackground)
399     {
400         CGRect aRect = {{0, 0}, {0, 0}};
401         if (mrShared.mxClipPath)
402             aRect = CGPathGetBoundingBox(mrShared.mxClipPath);
403         if (aRect.size.width != 0 && aRect.size.height != 0)
404             buttonRect.Intersection(tools::Rectangle(Point(static_cast<tools::Long>(aRect.origin.x),
405                                                            static_cast<tools::Long>(aRect.origin.y)),
406                                                      Size(static_cast<tools::Long>(aRect.size.width),
407                                                           static_cast<tools::Long>(aRect.size.height))));
408     }
409     mrShared.refreshRect(buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight());
410     return bOK;
411 }
412 
drawBox(CGContextRef context,const NSRect & rc,NSColor * pColor)413 static void drawBox(CGContextRef context, const NSRect& rc, NSColor* pColor)
414 {
415     assert(pColor);
416 
417     CGContextSaveGState(context);
418     CGContextTranslateCTM(context, rc.origin.x, rc.origin.y + rc.size.height);
419     CGContextScaleCTM(context, 1, -1);
420 
421     NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
422 
423     NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
424     NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
425     [pBox setBoxType: NSBoxCustom];
426     [pBox setFillColor: pColor];
427 
428     // -[NSBox setBorderType: NSNoBorder] is deprecated so hide the border
429     // by setting the border color to transparent
430     [pBox setBorderColor: [NSColor clearColor]];
431     [pBox setTitlePosition: NSNoTitle];
432 
433     [pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];
434 
435     [pBox release];
436 
437     CGContextRestoreGState(context);
438 }
439 
440 // if I don't crystallize this bg then the InvertCursor using kCGBlendModeDifference doesn't
441 // work correctly and the cursor doesn't appear correctly
drawEditableBackground(CGContextRef context,const NSRect & rc)442 static void drawEditableBackground(CGContextRef context, const NSRect& rc)
443 {
444     CGContextSaveGState(context);
445     if (ThemeColors::VclPluginCanUseThemeColors())
446         CGContextSetFillColorWithColor(context, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()).CGColor);
447     else
448         CGContextSetFillColorWithColor(context, [NSColor controlBackgroundColor].CGColor);
449     CGContextFillRect(context, rc);
450     CGContextRestoreGState(context);
451 }
452 
spinButtonWidth()453 static constexpr int spinButtonWidth()
454 {
455 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
456     if (@available(macOS 26, *))
457         return 23;
458     else
459 #endif
460         return 16;
461 }
462 
463 // As seen in macOS 12.3.1. All a bit odd really.
464 const int RoundedMargin[4] = { 6, 4, 0, 3 };
465 
performDrawNativeControl(ControlType nType,ControlPart nPart,const tools::Rectangle & rControlRegion,ControlState nState,const ImplControlValue & aValue,CGContextRef context,AquaSalFrame * mpFrame)466 bool AquaGraphicsBackendBase::performDrawNativeControl(ControlType nType,
467                                 ControlPart nPart,
468                                 const tools::Rectangle &rControlRegion,
469                                 ControlState nState,
470                                 const ImplControlValue &aValue,
471                                 CGContextRef context,
472                                 AquaSalFrame* mpFrame)
473 {
474     bool bOK = false;
475     bool bCanUseThemeColors(ThemeColors::VclPluginCanUseThemeColors());
476     AquaSalInstance* pInst = GetSalData()->mpInstance;
477     HIRect rc = ImplGetHIRectFromRectangle(rControlRegion);
478     switch (nType)
479     {
480         case ControlType::Toolbar:
481             {
482                 if (bCanUseThemeColors)
483                     drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetWindowColor()));
484                 else
485                     drawBox(context, rc, NSColor.windowBackgroundColor);
486                 bOK = true;
487             }
488             break;
489         case ControlType::WindowBackground:
490             {
491                 if (bCanUseThemeColors)
492                     drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetWindowColor()));
493                 else
494                     drawBox(context, rc, NSColor.windowBackgroundColor);
495                 bOK = true;
496             }
497             break;
498         case ControlType::Tooltip:
499             {
500                 rc.size.width += 2;
501                 rc.size.height += 2;
502                 if (bCanUseThemeColors)
503                     drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()));
504                 else
505                     drawBox(context, rc, NSColor.controlBackgroundColor);
506                 bOK = true;
507             }
508             break;
509         case ControlType::Menubar:
510         case ControlType::MenuPopup:
511             if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::HasBackgroundTexture)
512             {
513                 // FIXME: without this magical offset there is a 2 pixel black border on the right
514 
515                 rc.size.width += 2;
516                 HIThemeMenuDrawInfo aMenuInfo;
517                 aMenuInfo.version = 0;
518                 aMenuInfo.menuType = kThemeMenuTypePullDown;
519                 HIThemeMenuItemDrawInfo aMenuItemDrawInfo;
520 
521                 // grey theme when the item is selected is drawn here.
522 
523                 aMenuItemDrawInfo.itemType = kThemeMenuItemPlain;
524                 if ((nPart == ControlPart::MenuItem) && (nState & ControlState::SELECTED))
525 
526                     // blue theme when the item is selected is drawn here.
527 
528                     aMenuItemDrawInfo.state = kThemeMenuSelected;
529                 else
530 
531                     // normal color for non selected item
532 
533                     aMenuItemDrawInfo.state = kThemeMenuActive;
534 
535                 // repaints the background of the pull down menu
536 
537                 HIThemeDrawMenuBackground(&rc, &aMenuInfo, context, kHIThemeOrientationNormal);
538 
539                 // repaints the item either blue (selected) and/or grey (active only)
540 
541                 HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, context, kHIThemeOrientationNormal, &rc);
542                 bOK = true;
543             }
544             else if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
545             {
546                 // checked, else it is not displayed (see vcl/source/window/menu.cxx)
547 
548                 if (nState & ControlState::PRESSED)
549                 {
550                     HIThemeTextInfo aTextInfo;
551                     aTextInfo.version = 0;
552                     aTextInfo.state = (nState & ControlState::ENABLED) ? kThemeStateInactive: kThemeStateActive;
553                     aTextInfo.fontID = kThemeMenuItemMarkFont;
554                     aTextInfo.horizontalFlushness = kHIThemeTextHorizontalFlushCenter;
555                     aTextInfo.verticalFlushness = kHIThemeTextVerticalFlushTop;
556                     aTextInfo.options = kHIThemeTextBoxOptionNone;
557                     aTextInfo.truncationPosition = kHIThemeTextTruncationNone;
558 
559                     // aTextInfo.truncationMaxLines unused because of kHIThemeTextTruncationNone item highlighted
560 
561                     if (nState & ControlState::SELECTED) aTextInfo.state = kThemeStatePressed;
562                     UniChar mark=(nPart == ControlPart::MenuItemCheckMark) ? kCheckUnicode: kBulletUnicode;
563                     CFStringRef cfString = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &mark, 1, kCFAllocatorNull);
564                     HIThemeDrawTextBox(cfString, &rc, &aTextInfo, context, kHIThemeOrientationNormal);
565                     if (cfString)
566                         CFRelease(cfString);
567                     bOK = true;
568                 }
569             }
570             break;
571         case ControlType::Pushbutton:
572             {
573                 NSControlSize eSizeKind = NSControlSizeRegular;
574                 NSBezelStyle eBezelStyle = NSBezelStylePush;
575 
576                 PushButtonValue const *pPBVal = aValue.getType() == ControlType::Pushbutton ?
577                                                 static_cast<PushButtonValue const *>(&aValue) : nullptr;
578 
579                 SInt32 nPaintHeight = rc.size.height;
580                 if (rc.size.height <= PUSH_BUTTON_NORMAL_HEIGHT)
581                 {
582                     eSizeKind = NSControlSizeMini;
583                     GetThemeMetric(kThemeMetricSmallPushButtonHeight, &nPaintHeight);
584                 }
585                 else if ((pPBVal && pPBVal->mbSingleLine) || rc.size.height < PUSH_BUTTON_NORMAL_HEIGHT * 3 / 2)
586                 {
587                     GetThemeMetric(kThemeMetricPushButtonHeight, &nPaintHeight);
588                 }
589                 else if (pPBVal && !pPBVal->mbSingleLine)
590                 {
591                     // If not a single line button, allow the button to expand
592                     // its height
593                     eBezelStyle = NSBezelStyleFlexiblePush;
594                 }
595                 else
596                 {
597                     // A simple square bezel style that can scale to any size
598                     eBezelStyle = NSBezelStyleSmallSquare;
599                 }
600 
601                 // translate the origin for controls with fixed paint height so content ends up somewhere sensible
602                 rc.origin.y += (rc.size.height - nPaintHeight + 1) / 2;
603                 rc.size.height = nPaintHeight;
604 
605                 NSButtonCell* pBtn = pInst->mpButtonCell;
606                 pBtn.allowsMixedState = YES;
607 
608                 [pBtn setTitle: @""];
609                 [pBtn setButtonType: NSButtonTypeMomentaryPushIn];
610                 [pBtn setBezelStyle: eBezelStyle];
611                 [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
612                 [pBtn setEnabled: getEnabled(nState, mpFrame)];
613                 [pBtn setFocusRingType: NSFocusRingTypeExterior];
614                 [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
615                 [pBtn setControlSize: eSizeKind];
616                 if (nState & ControlState::DEFAULT)
617                     [pBtn setKeyEquivalent: @"\r"];
618                 else
619                     [pBtn setKeyEquivalent: @""];
620 
621                 if (eBezelStyle == NSBezelStylePush || eBezelStyle == NSBezelStyleFlexiblePush)
622                 {
623                     int nMargin = RoundedMargin[eSizeKind];
624                     rc.origin.x -= nMargin;
625                     rc.size.width += nMargin * 2;
626 
627 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
628                     if (@available(macOS 26, *))
629                     {
630                         rc.origin.x += FOCUS_RING_WIDTH * 2;
631                         rc.size.width -= FOCUS_RING_WIDTH * 4;
632                         if (eBezelStyle == NSBezelStyleFlexiblePush)
633                         {
634                             rc.origin.y += 1;
635                             rc.size.height -= FOCUS_RING_WIDTH;
636                         }
637                     }
638                     else
639 #endif
640                     {
641                         if (eBezelStyle == NSBezelStyleFlexiblePush)
642                         {
643                             rc.origin.x += FOCUS_RING_WIDTH;
644                             rc.size.width -= FOCUS_RING_WIDTH * 2;
645                         }
646                         else
647                         {
648                             rc.origin.x += FOCUS_RING_WIDTH / 2;
649                             rc.size.width -= FOCUS_RING_WIDTH;
650                         }
651                     }
652                 }
653 
654                 const bool bFocused(nState & ControlState::FOCUSED);
655                 paintCell(pBtn, rc, bFocused, context, nullptr);
656 
657                 bOK = true;
658             }
659             break;
660         case ControlType::Radiobutton:
661         case ControlType::Checkbox:
662             {
663                 rc.size.width -= 2 * FOCUS_RING_WIDTH;
664                 rc.size.height = RADIO_BUTTON_SMALL_SIZE;
665                 rc.origin.x += FOCUS_RING_WIDTH;
666                 rc.origin.y += FOCUS_RING_WIDTH;
667 
668                 NSButtonCell* pBtn = nType == ControlType::Checkbox ? pInst->mpCheckCell : pInst->mpRadioCell;
669                 pBtn.allowsMixedState = YES;
670 
671                 [pBtn setTitle: @""];
672                 [pBtn setButtonType: nType == ControlType::Checkbox ? NSButtonTypeSwitch : NSButtonTypeRadio];
673                 [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
674                 [pBtn setEnabled: getEnabled(nState, mpFrame)];
675                 [pBtn setFocusRingType: NSFocusRingTypeExterior];
676                 [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
677 
678                 const bool bFocused(nState & ControlState::FOCUSED);
679                 paintCell(pBtn, rc, bFocused, context, nullptr);
680 
681                 bOK = true;
682             }
683             break;
684         case ControlType::ListNode:
685             {
686                 NSButtonCell* pBtn = pInst->mpListNodeCell;
687                 pBtn.allowsMixedState = YES;
688 
689                 [pBtn setTitle: @""];
690                 [pBtn setButtonType: NSButtonTypeOnOff];
691                 [pBtn setBezelStyle: NSBezelStyleDisclosure];
692                 [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
693                 [pBtn setEnabled: getEnabled(nState, mpFrame)];
694                 [pBtn setFocusRingType: NSFocusRingTypeExterior];
695 
696                 const bool bFocused(nState & ControlState::FOCUSED);
697                 paintCell(pBtn, rc, bFocused, context, nullptr);
698 
699                 bOK = true;
700             }
701             break;
702         case ControlType::LevelBar:
703             {
704                 NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
705                 NSLevelIndicator* pBox = [[NSLevelIndicator alloc] initWithFrame:rect];
706                 [pBox setLevelIndicatorStyle: NSLevelIndicatorStyleContinuousCapacity];
707                 [pBox setMinValue: 0];
708                 [pBox setMaxValue: rc.size.width];
709                 [pBox setCriticalValue: rc.size.width * 35.0 / 100.0];
710                 [pBox setWarningValue: rc.size.width * 70.0 / 100.0];
711                 [pBox setDoubleValue: aValue.getNumericVal()];
712 
713                 CGContextSaveGState(context);
714                 CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
715 
716                 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
717                 NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
718                 [NSGraphicsContext setCurrentContext: graphicsContext];
719 
720                 [pBox drawRect: rect];
721 
722                 [NSGraphicsContext setCurrentContext: savedContext];
723 
724                 CGContextRestoreGState(context);
725 
726                 [pBox release];
727 
728                 bOK = true;
729             }
730             break;
731         case ControlType::Progress:
732         case ControlType::IntroProgress:
733             {
734                 NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
735                 NSProgressIndicator* pBox = [[NSProgressIndicator alloc] initWithFrame: rect];
736                 [pBox setControlSize: (rc.size.height > MEDIUM_PROGRESS_INDICATOR_HEIGHT) ?
737                                       NSControlSizeRegular : NSControlSizeSmall];
738                 [pBox setMinValue: 0];
739                 [pBox setMaxValue: rc.size.width];
740                 [pBox setDoubleValue: aValue.getNumericVal()];
741                 pBox.usesThreadedAnimation = NO;
742                 [pBox setIndeterminate: NO];
743 
744                 CGContextSaveGState(context);
745                 CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
746 
747                 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
748                 NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
749                 [NSGraphicsContext setCurrentContext: graphicsContext];
750 
751                 [pBox drawRect: rect];
752 
753                 [NSGraphicsContext setCurrentContext: savedContext];
754 
755                 CGContextRestoreGState(context);
756 
757                 [pBox release];
758 
759                 // tdf#164428 Skia/Metal needs flush after drawing progress bar
760                 assert(SkiaHelper::isVCLSkiaEnabled() && "macos requires skia");
761                 if (SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
762                     mpFrame->mbForceFlushProgressBar = true;
763 
764                 bOK = true;
765             }
766             break;
767         case ControlType::Slider:
768             {
769                 const SliderValue *pSliderVal = static_cast<SliderValue const *>(&aValue);
770                 if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
771                 {
772                     NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
773                     NSSlider* pBox = [[NSSlider alloc] initWithFrame: rect];
774 
775                     [pBox setEnabled: getEnabled(nState, mpFrame)];
776                     [pBox setVertical: nPart == ControlPart::TrackVertArea];
777                     [pBox setMinValue: pSliderVal->mnMin];
778                     [pBox setMaxValue: pSliderVal->mnMax];
779                     [pBox setIntegerValue: pSliderVal->mnCur];
780                     [pBox setSliderType: NSSliderTypeLinear];
781                     [pBox setFocusRingType: NSFocusRingTypeExterior];
782 
783                     const bool bFocused(nState & ControlState::FOCUSED);
784                     paintCell(pBox.cell, rc, bFocused, context, mpFrame->getNSView());
785 
786                     [pBox release];
787 
788                     bOK = true;
789                 }
790             }
791             break;
792         case ControlType::Scrollbar:
793             {
794                 const ScrollbarValue *pScrollbarVal = (aValue.getType() == ControlType::Scrollbar)
795                                                     ? static_cast<const ScrollbarValue *>(&aValue) : nullptr;
796                 if (nPart == ControlPart::DrawBackgroundVert || nPart == ControlPart::DrawBackgroundHorz)
797                 {
798                     if (bCanUseThemeColors)
799                         drawBox(context, rc, colorFromRGB(ThemeColors::GetThemeColors().GetBaseColor()));
800                     else
801                         drawBox(context, rc, NSColor.controlBackgroundColor);
802 
803                     NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
804                     NSScroller* pBar = [[NSScroller alloc] initWithFrame: rect];
805 
806                     double range = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize - pScrollbarVal->mnMin;
807                     double value = range ? (pScrollbarVal->mnCur - pScrollbarVal->mnMin) / range : 0;
808 
809                     double length = pScrollbarVal->mnMax - pScrollbarVal->mnMin;
810                     double proportion = pScrollbarVal->mnVisibleSize / length;
811 
812                     [pBar setEnabled: getEnabled(nState, mpFrame)];
813                     [pBar setScrollerStyle: NSScrollerStyleLegacy];
814                     [pBar setFloatValue: value];
815                     [pBar setKnobProportion: proportion];
816                     bool bPressed = (pScrollbarVal->mnThumbState & ControlState::ENABLED) &&
817                                     (pScrollbarVal->mnThumbState & ControlState::PRESSED);
818 
819                     CGContextSaveGState(context);
820                     CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
821 
822                     NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
823 
824                     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
825                     [NSGraphicsContext setCurrentContext: graphicsContext];
826 
827                     // For not-pressed first draw without the knob and then
828                     // draw just the knob but with 50% opaque which looks sort of
829                     // right
830 
831                     [pBar drawKnobSlotInRect: rect highlight: NO];
832 
833                     NSBitmapImageRep* pImageRep = [pBar bitmapImageRepForCachingDisplayInRect: rect];
834 
835                     NSGraphicsContext* imageContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:pImageRep];
836                     [NSGraphicsContext setCurrentContext: imageContext];
837 
838                     [pBar drawKnob];
839 
840                     [NSGraphicsContext setCurrentContext: graphicsContext];
841 
842                     NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
843                     [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
844 
845                     [pImage drawInRect: rect fromRect: rect
846                                         operation: NSCompositingOperationSourceOver
847                                         fraction: bPressed ? 1.0 : 0.5];
848 
849                     [pImage release];
850 
851                     [NSGraphicsContext setCurrentContext:savedContext];
852 
853                     CGContextRestoreGState(context);
854 
855                     bOK = true;
856 
857                     [pBar release];
858                 }
859             }
860             break;
861         case ControlType::TabPane:
862             {
863                 NSTabView* pBox = [[NSTabView alloc] initWithFrame: rc];
864 
865                 SInt32 nOverlap;
866                 GetThemeMetric(kThemeMetricTabFrameOverlap, &nOverlap);
867 
868                 // this calculation is probably more than a little dubious
869                 rc.origin.x -= pBox.contentRect.origin.x - FOCUS_RING_WIDTH;
870                 rc.size.width += rc.size.width - pBox.contentRect.size.width - 2 * FOCUS_RING_WIDTH;
871                 double nTopBorder = pBox.contentRect.origin.y;
872                 double nBottomBorder = rc.size.height - pBox.contentRect.size.height - nTopBorder;
873                 double nExtraTop = (nTopBorder - nBottomBorder) / 2;
874                 rc.origin.y -= (nTopBorder - nExtraTop + nOverlap);
875                 rc.size.height += (nTopBorder - nExtraTop + nBottomBorder);
876 
877                 CGContextSaveGState(context);
878                 CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
879 
880                 rc.origin.x = 0;
881                 rc.origin.y = 0;
882 
883                 [pBox setBoundsOrigin: rc.origin];
884                 [pBox setBoundsSize: rc.size];
885 
886                 // jam this in to force the tab contents area to be left undrawn, the ControlType::TabItem
887                 // will be drawn in this space.
888                 const TabPaneValue& rValue = static_cast<const TabPaneValue&>(aValue);
889                 SInt32 nEndCapWidth;
890                 GetThemeMetric(kThemeMetricLargeTabCapsWidth, &nEndCapWidth);
891                 FixedWidthTabViewItem* pItem = [[[FixedWidthTabViewItem alloc] initWithIdentifier: @"tab"] autorelease];
892                 [pItem setTabWidth: rValue.m_aTabHeaderRect.GetWidth() - 2 * nEndCapWidth];
893                 [pBox addTabViewItem: pItem];
894 
895                 NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
896 
897                 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
898                 [NSGraphicsContext setCurrentContext: graphicsContext];
899 
900                 [pBox drawRect: rc];
901 
902                 [NSGraphicsContext setCurrentContext: savedContext];
903 
904                 [pBox release];
905 
906                 CGContextRestoreGState(context);
907 
908                 bOK = true;
909             }
910             break;
911         case ControlType::TabItem:
912             {
913                 // first, last or middle tab
914 
915                 TabitemValue const * pTabValue = static_cast<TabitemValue const *>(&aValue);
916                 TabitemFlags nAlignment = pTabValue->mnAlignment;
917 
918                 // TabitemFlags::LeftAligned (and TabitemFlags::RightAligned) for the leftmost (or rightmost) tab
919                 // when there are several lines of tabs because there is only one first tab and one
920                 // last tab and TabitemFlags::FirstInGroup (and TabitemFlags::LastInGroup) because when the
921                 // line width is different from window width, there may not be TabitemFlags::RightAligned
922                 int nPaintIndex = 1;
923                 bool bSolo = false;
924                 if (((nAlignment & TabitemFlags::LeftAligned) && (nAlignment & TabitemFlags::RightAligned))
925                     || ((nAlignment & TabitemFlags::FirstInGroup) && (nAlignment & TabitemFlags::LastInGroup)))
926                 {
927                     nPaintIndex = 0;
928                     bSolo = true;
929                 }
930                 else if ((nAlignment & TabitemFlags::LeftAligned) || (nAlignment & TabitemFlags::FirstInGroup))
931                     nPaintIndex = !AllSettings::GetLayoutRTL() ? 0 : 2;
932                 else if ((nAlignment & TabitemFlags::RightAligned) || (nAlignment & TabitemFlags::LastInGroup))
933                     nPaintIndex = !AllSettings::GetLayoutRTL() ? 2 : 0;
934 
935                 int nCells = !bSolo ? 3 : 1;
936                 NSRect ctrlrect = { NSZeroPoint, NSMakeSize(rc.size.width * nCells + FOCUS_RING_WIDTH, rc.size.height) };
937                 NSSegmentedControl* pCtrl = [[NSSegmentedControl alloc] initWithFrame: ctrlrect];
938                 [pCtrl setSegmentCount: nCells];
939                 if (bSolo)
940                     [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH forSegment: 0];
941                 else
942                 {
943                     [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 0];
944                     [pCtrl setWidth: rc.size.width forSegment: 1];
945                     [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 2];
946                 }
947                 [pCtrl setSelected: (nState & ControlState::SELECTED) ? YES : NO forSegment: nPaintIndex];
948                 [pCtrl setFocusRingType: NSFocusRingTypeExterior];
949 
950                 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
951                 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
952 
953                 NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
954                 NSRect tabrect = { NSMakePoint(rc.size.width * nPaintIndex + FOCUS_RING_WIDTH / 2, 0),
955                                    NSMakeSize(rc.size.width, rc.size.height) };
956                 NSBitmapImageRep* pImageRep = [pCtrl bitmapImageRepForCachingDisplayInRect: tabrect];
957                 [pCtrl cacheDisplayInRect: tabrect toBitmapImageRep: pImageRep];
958 
959                 NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
960                 [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
961 
962                 [pImage drawInRect: rc fromRect: rect
963                         operation: NSCompositingOperationSourceOver
964                         fraction: 1.0];
965 
966                 [pImage release];
967 
968                 [NSGraphicsContext setCurrentContext:savedContext];
969 
970                 [pCtrl release];
971 
972                 if (nState & ControlState::FOCUSED)
973                 {
974                     if (!bSolo)
975                     {
976                         if (nPaintIndex == 0)
977                         {
978                             rc.origin.x += FOCUS_RING_WIDTH / 2;
979                             rc.size.width -= FOCUS_RING_WIDTH / 2;
980                         }
981                         else if (nPaintIndex == 2)
982                         {
983                             rc.size.width -= FOCUS_RING_WIDTH / 2;
984                             rc.size.width -= FOCUS_RING_WIDTH / 2;
985                         }
986                     }
987 
988                     paintFocusRect(4.0, rc, context);
989                 }
990                 bOK=true;
991             }
992             break;
993         case ControlType::Editbox:
994         case ControlType::MultilineEditbox:
995             {
996                 rc.size.width += 2 * EDITBOX_INSET_MARGIN;
997                 if (nType == ControlType::Editbox)
998                   rc.size.height = EDITBOX_HEIGHT;
999                 else
1000                   rc.size.height += 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
1001                 rc.origin.x -= EDITBOX_INSET_MARGIN;
1002                 rc.origin.y -= EDITBOX_INSET_MARGIN;
1003 
1004                 NSTextFieldCell* pBtn = pInst->mpTextFieldCell;
1005 
1006                 [pBtn setEnabled: getEnabled(nState, mpFrame)];
1007                 [pBtn setBezeled: YES];
1008                 [pBtn setEditable: YES];
1009                 [pBtn setFocusRingType: NSFocusRingTypeExterior];
1010 
1011                 drawEditableBackground(context, rc);
1012                 const bool bFocused(nState & ControlState::FOCUSED);
1013                 paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
1014 
1015                 bOK = true;
1016             }
1017             break;
1018         case ControlType::Combobox:
1019             if (nPart == ControlPart::HasBackgroundTexture || nPart == ControlPart::Entire)
1020             {
1021                 rc.origin.y += (rc.size.height - COMBOBOX_HEIGHT + 1) / 2;
1022                 rc.size.height = COMBOBOX_HEIGHT;
1023 
1024                 NSComboBoxCell* pBtn = pInst->mpComboBoxCell;
1025 
1026                 [pBtn setEnabled: getEnabled(nState, mpFrame)];
1027                 [pBtn setEditable: YES];
1028                 [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
1029                 [pBtn setFocusRingType: NSFocusRingTypeExterior];
1030 
1031                 {
1032                     rc.origin.x += 2;
1033 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
1034                     if (@available(macOS 26, *))
1035                         rc.size.width -= 4;
1036                     else
1037 #endif
1038                         rc.size.width -= 1;
1039                 }
1040 
1041                 drawEditableBackground(context, rc);
1042                 const bool bFocused(nState & ControlState::FOCUSED);
1043                 paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
1044 
1045                 bOK = true;
1046             }
1047             break;
1048         case ControlType::Listbox:
1049 
1050             switch (nPart)
1051             {
1052                 case ControlPart::Entire:
1053                 case ControlPart::ButtonDown:
1054                 {
1055                     rc.origin.y += (rc.size.height - LISTBOX_HEIGHT + 1) / 2;
1056                     rc.size.height = LISTBOX_HEIGHT;
1057 
1058                     NSPopUpButtonCell* pBtn = pInst->mpPopUpButtonCell;
1059 
1060                     [pBtn setTitle: @""];
1061                     [pBtn setEnabled: getEnabled(nState, mpFrame)];
1062                     [pBtn setFocusRingType: NSFocusRingTypeExterior];
1063                     [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
1064                     if (nState & ControlState::DEFAULT)
1065                         [pBtn setKeyEquivalent: @"\r"];
1066                     else
1067                         [pBtn setKeyEquivalent: @""];
1068 
1069 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
1070                     if (@available(macOS 26, *))
1071                     {
1072                         rc.origin.x += 2;
1073                         rc.size.width -= 4;
1074                     }
1075                     else
1076 #endif
1077                     {
1078                         rc.size.width += 1;
1079                     }
1080 
1081                     const bool bFocused(nState & ControlState::FOCUSED);
1082                     paintCell(pBtn, rc, bFocused, context, nullptr);
1083 
1084                     bOK = true;
1085                     break;
1086                 }
1087                 case ControlPart::ListboxWindow:
1088                 {
1089                     NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
1090                     NSScrollView* pBox = [[NSScrollView alloc] initWithFrame: rect];
1091                     [pBox setBorderType: NSLineBorder];
1092 
1093                     CGContextSaveGState(context);
1094                     CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
1095 
1096                     NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
1097                     NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
1098                     [NSGraphicsContext setCurrentContext: graphicsContext];
1099 
1100                     [pBox drawRect: rect];
1101 
1102                     [NSGraphicsContext setCurrentContext: savedContext];
1103 
1104                     CGContextRestoreGState(context);
1105 
1106                     [pBox release];
1107 
1108                     bOK = true;
1109                     break;
1110                 }
1111                 default:
1112                     break;
1113             }
1114             break;
1115         case ControlType::Spinbox:
1116             if (nPart == ControlPart::Entire)
1117             {
1118                 // text field
1119 
1120                 rc.size.width -= SPIN_BUTTON_WIDTH + 4 * FOCUS_RING_WIDTH;
1121                 rc.size.height = EDITBOX_HEIGHT;
1122                 rc.origin.x += FOCUS_RING_WIDTH;
1123                 rc.origin.y += FOCUS_RING_WIDTH;
1124 
1125                 NSTextFieldCell* pEdit = pInst->mpTextFieldCell;
1126 
1127                 [pEdit setEnabled: YES];
1128                 [pEdit setBezeled: YES];
1129                 [pEdit setEditable: YES];
1130                 [pEdit setFocusRingType: NSFocusRingTypeExterior];
1131 
1132                 drawEditableBackground(context, rc);
1133                 const bool bFocused(nState & ControlState::FOCUSED);
1134                 paintCell(pEdit, rc, bFocused, context, mpFrame->getNSView());
1135 
1136                 // buttons
1137 
1138                 const SpinbuttonValue *pSpinButtonVal = (aValue.getType() == ControlType::SpinButtons)
1139                                                       ? static_cast <const SpinbuttonValue *>(&aValue) : nullptr;
1140                 if (pSpinButtonVal)
1141                 {
1142                     ControlState nUpperState = pSpinButtonVal->mnUpperState;
1143                     ControlState nLowerState = pSpinButtonVal->mnLowerState;
1144 
1145                     rc.origin.x += rc.size.width + FOCUS_RING_WIDTH + 1;
1146 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
1147                     if (@available(macOS 26, *))
1148                         ;
1149                     else
1150 #endif
1151                         rc.origin.y -= 1;
1152                     rc.size.width = SPIN_BUTTON_WIDTH;
1153                     rc.size.height = SPIN_UPPER_BUTTON_HEIGHT + SPIN_LOWER_BUTTON_HEIGHT;
1154 
1155                     NSStepperCell* pBtn = pInst->mpStepperCell;
1156 
1157                     [pBtn setTitle: @""];
1158                     [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
1159                     [pBtn setEnabled: (nUpperState & ControlState::ENABLED || nLowerState & ControlState::ENABLED) ?
1160                                     YES : NO];
1161                     [pBtn setFocusRingType: NSFocusRingTypeExterior];
1162                     [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
1163 
1164                     const bool bSpinFocused(nUpperState & ControlState::FOCUSED || nLowerState & ControlState::FOCUSED);
1165                     paintCell(pBtn, rc, bSpinFocused, context, nullptr);
1166                 }
1167                 bOK = true;
1168             }
1169             break;
1170         case ControlType::Frame:
1171             {
1172                 DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(aValue.getNumericVal());
1173                 if (nPart == ControlPart::Border)
1174                 {
1175                     if (!(nStyle & DrawFrameFlags::Menu) && !(nStyle & DrawFrameFlags::WindowBorder))
1176                     {
1177 
1178                         // strange effects start to happen when HIThemeDrawFrame meets the border of the window.
1179                         // These can be avoided by clipping to the boundary of the frame (see issue 84756)
1180 
1181                         if (rc.origin.y + rc.size.height >= mpFrame->GetHeight() - 3)
1182                         {
1183                             CGMutablePathRef rPath = CGPathCreateMutable();
1184                             CGPathAddRect(rPath, nullptr,
1185                                           CGRectMake(0, 0, mpFrame->GetWidth() - 1, mpFrame->GetHeight() - 1));
1186                             CGContextBeginPath(context);
1187                             CGContextAddPath(context, rPath);
1188                             CGContextClip(context);
1189                             CGPathRelease(rPath);
1190                         }
1191                         HIThemeFrameDrawInfo aTextDrawInfo;
1192                         aTextDrawInfo.version = 0;
1193                         aTextDrawInfo.kind = kHIThemeFrameListBox;
1194                         aTextDrawInfo.state = kThemeStateActive;
1195                         aTextDrawInfo.isFocused = false;
1196                         HIThemeDrawFrame(&rc, &aTextDrawInfo, context, kHIThemeOrientationNormal);
1197                         bOK = true;
1198                     }
1199                 }
1200             }
1201             break;
1202         case ControlType::ListNet:
1203 
1204             // do nothing as there isn't net for listviews on macOS
1205 
1206             bOK = true;
1207             break;
1208         default:
1209             break;
1210     }
1211 
1212     return bOK;
1213 }
1214 
getNativeControlRegion(ControlType nType,ControlPart nPart,const tools::Rectangle & rControlRegion,ControlState,const ImplControlValue & aValue,const OUString &,tools::Rectangle & rNativeBoundingRegion,tools::Rectangle & rNativeContentRegion)1215 bool AquaSalGraphics::getNativeControlRegion(ControlType nType,
1216                                              ControlPart nPart,
1217                                              const tools::Rectangle &rControlRegion,
1218                                              ControlState,
1219                                              const ImplControlValue &aValue,
1220                                              const OUString &,
1221                                              tools::Rectangle &rNativeBoundingRegion,
1222                                              tools::Rectangle &rNativeContentRegion)
1223 {
1224     bool toReturn = false;
1225     tools::Rectangle aCtrlBoundRect(rControlRegion);
1226     short x = aCtrlBoundRect.Left();
1227     short y = aCtrlBoundRect.Top();
1228     short w, h;
1229     switch (nType)
1230     {
1231         case ControlType::Pushbutton:
1232         case ControlType::Radiobutton:
1233         case ControlType::Checkbox:
1234             {
1235                 if (nType == ControlType::Pushbutton)
1236                 {
1237                     w = aCtrlBoundRect.GetWidth();
1238                     h = aCtrlBoundRect.GetHeight();
1239                 }
1240                 else
1241                 {
1242                     w = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH + RADIO_BUTTON_TEXT_SEPARATOR;
1243                     h = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH;
1244                 }
1245                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1246                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1247                 toReturn = true;
1248             }
1249             break;
1250         case ControlType::LevelBar:
1251         case ControlType::Progress:
1252             {
1253                 tools::Rectangle aRect(aCtrlBoundRect);
1254                 if (aRect.GetHeight() < LARGE_PROGRESS_INDICATOR_HEIGHT)
1255                     aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
1256                 else
1257                     aRect.SetBottom(aRect.Top() + LARGE_PROGRESS_INDICATOR_HEIGHT - 1);
1258                 rNativeBoundingRegion = aRect;
1259                 rNativeContentRegion = aRect;
1260                 toReturn = true;
1261             }
1262             break;
1263         case ControlType::IntroProgress:
1264             {
1265                 tools::Rectangle aRect(aCtrlBoundRect);
1266                 aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
1267                 rNativeBoundingRegion = aRect;
1268                 rNativeContentRegion = aRect;
1269                 toReturn = true;
1270             }
1271             break;
1272         case ControlType::Slider:
1273             if (nPart == ControlPart::ThumbHorz)
1274             {
1275                 w = SLIDER_WIDTH;
1276                 h = aCtrlBoundRect.GetHeight();
1277                 rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1278                 toReturn = true;
1279             }
1280             else if (nPart == ControlPart::ThumbVert)
1281             {
1282                 w = aCtrlBoundRect.GetWidth();
1283                 h = SLIDER_HEIGHT;
1284                 rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1285                 toReturn = true;
1286             }
1287             break;
1288         case ControlType::Scrollbar:
1289             {
1290                 tools::Rectangle aRect;
1291                 if (AquaGetScrollRect(nPart, aCtrlBoundRect, aRect))
1292                 {
1293                     toReturn = true;
1294                     rNativeBoundingRegion = aRect;
1295                     rNativeContentRegion = aRect;
1296                 }
1297             }
1298             break;
1299         case ControlType::TabItem:
1300             {
1301                 w = aCtrlBoundRect.GetWidth() + 2 * TAB_TEXT_MARGIN;
1302                 h = TAB_HEIGHT + 2;
1303                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1304                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1305                 toReturn = true;
1306             }
1307             break;
1308         case ControlType::Editbox:
1309             {
1310                 const tools::Long nBorderThickness = FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
1311                 // tdf#144241 don't return a negative width, expand the region to the min osx width
1312                 w = std::max(nBorderThickness * 2, aCtrlBoundRect.GetWidth());
1313                 h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
1314                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1315                 w -= 2 * nBorderThickness;
1316                 h -= 2 * nBorderThickness;
1317                 x += nBorderThickness;
1318                 y += nBorderThickness;
1319                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1320                 toReturn = true;
1321             }
1322             break;
1323         case ControlType::Combobox:
1324             if (nPart == ControlPart::Entire)
1325             {
1326                 w = aCtrlBoundRect.GetWidth();
1327                 h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
1328                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1329                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1330                 toReturn = true;
1331             }
1332             else if (nPart == ControlPart::ButtonDown)
1333             {
1334                 w = COMBOBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
1335                 h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
1336                 x += aCtrlBoundRect.GetWidth() - w;
1337                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1338                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1339                 toReturn = true;
1340             }
1341             else if (nPart == ControlPart::SubEdit)
1342             {
1343                 w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - COMBOBOX_BUTTON_WIDTH - COMBOBOX_BORDER_WIDTH
1344                     - 2 * COMBOBOX_TEXT_MARGIN;
1345                 h = COMBOBOX_HEIGHT - 2 * COMBOBOX_BORDER_WIDTH;
1346                 x += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH + COMBOBOX_TEXT_MARGIN;
1347                 y += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH;
1348                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1349                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1350                 toReturn = true;
1351             }
1352             break;
1353         case ControlType::Listbox:
1354             if (nPart == ControlPart::Entire)
1355             {
1356                 w = aCtrlBoundRect.GetWidth();
1357                 h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
1358                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1359                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1360                 toReturn = true;
1361             }
1362             else if (nPart == ControlPart::ButtonDown)
1363             {
1364                 w = LISTBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
1365                 h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
1366                 x += aCtrlBoundRect.GetWidth() - w;
1367                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1368                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1369                 toReturn = true;
1370             }
1371             else if (nPart == ControlPart::SubEdit)
1372             {
1373                 w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - LISTBOX_BUTTON_WIDTH - LISTBOX_BORDER_WIDTH
1374                     - 2 * LISTBOX_TEXT_MARGIN;
1375                 h = LISTBOX_HEIGHT - 2 * LISTBOX_BORDER_WIDTH;
1376                 x += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH + LISTBOX_TEXT_MARGIN;
1377                 y += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH;
1378                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1379                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1380                 toReturn = true;
1381             }
1382             else if (nPart == ControlPart::ListboxWindow)
1383             {
1384                 w = aCtrlBoundRect.GetWidth() - 2;
1385                 h = aCtrlBoundRect.GetHeight() - 2;
1386                 x += 1;
1387                 y += 1;
1388                 rNativeBoundingRegion = aCtrlBoundRect;
1389                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1390                 toReturn = true;
1391             }
1392             break;
1393         case ControlType::Spinbox:
1394             if (nPart == ControlPart::Entire)
1395             {
1396                 w = aCtrlBoundRect.GetWidth();
1397 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
1398                 if (@available(macOS 26, *))
1399                     h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH + 1;
1400                 else
1401 #endif
1402                     h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
1403                 x += SPINBOX_OFFSET;
1404                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1405                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1406                 toReturn = true;
1407             }
1408             else if (nPart == ControlPart::SubEdit)
1409             {
1410                 w = aCtrlBoundRect.GetWidth() - 4 * FOCUS_RING_WIDTH - SPIN_BUTTON_WIDTH - 2 * EDITBOX_BORDER_WIDTH
1411                     - 2 * EDITBOX_INSET_MARGIN;
1412                 h = EDITBOX_HEIGHT - 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
1413                 x += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN + SPINBOX_OFFSET;
1414                 y += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
1415                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1416                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1417                 toReturn = true;
1418             }
1419             else if (nPart == ControlPart::ButtonUp)
1420             {
1421                 w = SPIN_BUTTON_WIDTH +  2 * FOCUS_RING_WIDTH;
1422                 h = SPIN_UPPER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
1423                 x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
1424                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1425                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1426                 toReturn = true;
1427             }
1428             else if (nPart == ControlPart::ButtonDown)
1429             {
1430                 w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
1431                 h = SPIN_LOWER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
1432                 x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
1433                 y += FOCUS_RING_WIDTH + SPIN_UPPER_BUTTON_HEIGHT;
1434                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1435                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1436                 toReturn = true;
1437             }
1438             break;
1439         case ControlType::Frame:
1440             {
1441                 DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(aValue.getNumericVal() & 0x000f);
1442                 DrawFrameFlags nFlags = static_cast<DrawFrameFlags>(aValue.getNumericVal() & 0xfff0);
1443                 if (nPart == ControlPart::Border
1444                     && !(nFlags & (DrawFrameFlags::Menu | DrawFrameFlags::WindowBorder | DrawFrameFlags::BorderWindowBorder)))
1445                 {
1446                     tools::Rectangle aRect(aCtrlBoundRect);
1447                     if (nStyle == DrawFrameStyle::DoubleIn)
1448                     {
1449                         aRect.AdjustLeft(1);
1450                         aRect.AdjustTop(1);
1451                         // rRect.Right() -= 1;
1452                         // rRect.Bottom() -= 1;
1453                     }
1454                     else
1455                     {
1456                         aRect.AdjustLeft(1);
1457                         aRect.AdjustTop(1);
1458                         aRect.AdjustRight(-1);
1459                         aRect.AdjustBottom(-1);
1460                     }
1461                     rNativeContentRegion = aRect;
1462                     rNativeBoundingRegion = aRect;
1463                     toReturn = true;
1464                 }
1465             }
1466             break;
1467         case ControlType::Menubar:
1468         case ControlType::MenuPopup:
1469             if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
1470             {
1471                 w=10;
1472                 h=10;
1473                 rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
1474                 rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
1475                 toReturn = true;
1476             }
1477             break;
1478         default:
1479             break;
1480     }
1481     return toReturn;
1482 }
1483 
1484 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1485