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