Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoahelpers.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
7#include <qpa/qplatformtheme.h>
8
10#include "qnsview.h"
11
12#include <qpa/qplatformscreen.h>
13#include <private/qguiapplication_p.h>
14#include <private/qwindow_p.h>
15#include <QtGui/private/qcoregraphics_p.h>
16
17#include <algorithm>
18
20
21Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
22Q_LOGGING_CATEGORY(lcQpaDrawing, "qt.qpa.drawing");
23Q_LOGGING_CATEGORY(lcQpaMouse, "qt.qpa.input.mouse", QtCriticalMsg);
24Q_LOGGING_CATEGORY(lcQpaKeys, "qt.qpa.input.keys", QtCriticalMsg);
25Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods")
26Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen", QtCriticalMsg);
27Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application");
28Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard")
29Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs")
30Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
31Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
32
33//
34// Conversion Functions
35//
36
37QStringList qt_mac_NSArrayToQStringList(NSArray<NSString *> *array)
38{
39 QStringList result;
40 for (NSString *string in array)
41 result << QString::fromNSString(string);
42 return result;
43}
44
46{
47 NSMutableArray<NSString *> *result = [NSMutableArray<NSString *> arrayWithCapacity:list.size()];
48 for (const QString &string : list)
49 [result addObject:string.toNSString()];
50 return result;
51}
52
59
61 { NSDragOperationLink, Qt::LinkAction, true },
62 { NSDragOperationMove, Qt::MoveAction, true },
63 { NSDragOperationDelete, Qt::MoveAction, true },
64 { NSDragOperationCopy, Qt::CopyAction, true },
65 { NSDragOperationGeneric, Qt::CopyAction, false },
66 { NSDragOperationEvery, Qt::ActionMask, false },
67 { NSDragOperationNone, Qt::IgnoreAction, false }
68};
69
71{
72 for (int i=0; dnd_enums[i].qt_code; i++) {
73 if (dnd_enums[i].Qt2Mac && (action & dnd_enums[i].qt_code)) {
74 return dnd_enums[i].mac_code;
75 }
76 }
77 return NSDragOperationNone;
78}
79
80NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions)
81{
82 NSDragOperation nsActions = NSDragOperationNone;
83 for (int i=0; dnd_enums[i].qt_code; i++) {
84 if (dnd_enums[i].Qt2Mac && (actions & dnd_enums[i].qt_code))
85 nsActions |= dnd_enums[i].mac_code;
86 }
87 return nsActions;
88}
89
90Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions)
91{
92 Qt::DropAction action = Qt::IgnoreAction;
93 for (int i=0; dnd_enums[i].mac_code; i++) {
94 if (nsActions & dnd_enums[i].mac_code)
95 return dnd_enums[i].qt_code;
96 }
97 return action;
98}
99
100Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions)
101{
102 Qt::DropActions actions = Qt::IgnoreAction;
103
104 for (int i=0; dnd_enums[i].mac_code; i++) {
105 if (dnd_enums[i].mac_code == NSDragOperationEvery)
106 continue;
107
108 if (nsActions & dnd_enums[i].mac_code)
109 actions |= dnd_enums[i].qt_code;
110 }
111 return actions;
112}
113
114/*!
115 Returns the view cast to a QNSview if possible.
116
117 If the view is not a QNSView, nil is returned, which is safe to
118 send messages to, effectivly making [qnsview_cast(view) message]
119 a no-op.
120
121 For extra verbosity and clearer code, please consider checking
122 that the platform window is not a foreign window before using
123 this cast, via QPlatformWindow::isForeignWindow().
124
125 Do not use this method solely to check for foreign windows, as
126 that will make the code harder to read for people not working
127 primarily on macOS, who do not know the difference between the
128 NSView and QNSView cases.
129*/
130QNSView *qnsview_cast(NSView *view)
131{
132 return qt_objc_cast<QNSView *>(view);
133}
134
135//
136// Misc
137//
138
139// Sets the activation policy for this process to NSApplicationActivationPolicyRegular,
140// unless either LSUIElement or LSBackgroundOnly is set in the Info.plist.
142{
143 bool forceTransform = true;
144 CFTypeRef value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(),
145 CFSTR("LSUIElement"));
146 if (value) {
147 CFTypeID valueType = CFGetTypeID(value);
148 // Officially it's supposed to be a string, a boolean makes sense, so we'll check.
149 // A number less so, but OK.
150 if (valueType == CFStringGetTypeID())
151 forceTransform = !(QString::fromCFString(static_cast<CFStringRef>(value)).toInt());
152 else if (valueType == CFBooleanGetTypeID())
153 forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value));
154 else if (valueType == CFNumberGetTypeID()) {
155 int valueAsInt;
156 CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberIntType, &valueAsInt);
157 forceTransform = !valueAsInt;
158 }
159 }
160
161 if (forceTransform) {
162 value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(),
163 CFSTR("LSBackgroundOnly"));
164 if (value) {
165 CFTypeID valueType = CFGetTypeID(value);
166 if (valueType == CFBooleanGetTypeID())
167 forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value));
168 else if (valueType == CFStringGetTypeID())
169 forceTransform = !(QString::fromCFString(static_cast<CFStringRef>(value)).toInt());
170 else if (valueType == CFNumberGetTypeID()) {
171 int valueAsInt;
172 CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberIntType, &valueAsInt);
173 forceTransform = !valueAsInt;
174 }
175 }
176 }
177
178 if (forceTransform) {
179 [[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
180 }
181}
182
184{
185 QString appName;
186 CFTypeRef string = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), CFSTR("CFBundleName"));
187 if (string)
188 appName = QString::fromCFString(static_cast<CFStringRef>(string));
189
190 if (appName.isEmpty()) {
191 QString arg0 = QGuiApplicationPrivate::instance()->appName();
192 if (arg0.contains("/")) {
193 QStringList parts = arg0.split(u'/');
194 appName = parts.at(parts.count() - 1);
195 } else {
196 appName = arg0;
197 }
198 }
199 return appName;
200}
201
202// -------------------------------------------------------------------------
203
204/*!
205 \fn QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference)
206 \fn QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference)
207
208 Flips the Y coordinate of the point/rect between quadrant I and IV.
209
210 The native coordinate system on macOS uses quadrant I, with origin
211 in bottom left, and Qt uses quadrant IV, with origin in top left.
212
213 By flipping the Y coordinate, we can map the point/rect between
214 the two coordinate systems.
215
216 The flip is always in relation to a reference rectangle, e.g.
217 the frame of the parent view, or the screen geometry. In the
218 latter case the specialized QCocoaScreen::mapFrom/To functions
219 should be used instead.
220*/
221QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference)
222{
223 return QPointF(pos.x(), reference.height() - pos.y());
224}
225
226QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference)
227{
228 return QRectF(qt_mac_flip(rect.bottomLeft(), reference), rect.size());
229}
230
231// -------------------------------------------------------------------------
232
233/*!
234 \fn Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum)
235
236 Returns the Qt::Button that corresponds to an NSEvent.buttonNumber.
237
238 \note AppKit will use buttonNumber 0 to indicate both "left button"
239 and "no button". Only NSEvents that describes mouse press/release
240 events (e.g NSEventTypeOtherMouseDown) will contain a valid
241 button number.
242*/
244{
245 if (buttonNum >= 0 && buttonNum <= 31)
246 return Qt::MouseButton(1 << buttonNum);
247 return Qt::NoButton;
248}
249
250/*!
251 \fn Qt::MouseButton cocoaButton2QtButton(NSEvent *event)
252
253 Returns the Qt::Button that corresponds to an NSEvent.buttonNumber.
254
255 \note AppKit will use buttonNumber 0 to indicate both "left button"
256 and "no button". Only NSEvents that describes mouse press/release/dragging
257 events (e.g NSEventTypeOtherMouseDown) will contain a valid
258 button number.
259
260 \note Wacom tablet might not return the correct button number for NSEvent buttonNumber
261 on right clicks. Decide here that the button is the "right" button.
262*/
264{
265 if (cocoaEvent2QtMouseEvent(event) == QEvent::MouseMove)
266 return Qt::NoButton;
267
268 switch (event.type) {
269 case NSEventTypeRightMouseUp:
270 case NSEventTypeRightMouseDown:
271 return Qt::RightButton;
272
273 default:
274 break;
275 }
276
277 return cocoaButton2QtButton(event.buttonNumber);
278}
279
280/*!
281 \fn QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event)
282
283 Returns the QEvent::Type that corresponds to an NSEvent.type.
284*/
286{
287 switch (event.type) {
288 case NSEventTypeLeftMouseDown:
289 case NSEventTypeRightMouseDown:
290 case NSEventTypeOtherMouseDown:
291 return QEvent::MouseButtonPress;
292
293 case NSEventTypeLeftMouseUp:
294 case NSEventTypeRightMouseUp:
295 case NSEventTypeOtherMouseUp:
296 return QEvent::MouseButtonRelease;
297
298 case NSEventTypeLeftMouseDragged:
299 case NSEventTypeRightMouseDragged:
300 case NSEventTypeOtherMouseDragged:
301 return QEvent::MouseMove;
302
303 case NSEventTypeMouseMoved:
304 return QEvent::MouseMove;
305
306 default:
307 break;
308 }
309
310 return QEvent::None;
311}
312
313bool qt_mac_isMouseEvent(NSEvent *event)
314{
315 return cocoaEvent2QtMouseEvent(event) != QEvent::None;
316}
317
318/*!
319 \fn Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons)
320
321 Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
322*/
323Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons)
324{
325 return static_cast<Qt::MouseButton>(pressedMouseButtons & Qt::MouseButtonMask);
326}
327
328/*!
329 \fn Qt::MouseButtons currentlyPressedMouseButtons()
330
331 Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
332*/
334{
335 return cocoaMouseButtons2QtMouseButtons(NSEvent.pressedMouseButtons);
336}
337
339{
340 return QPlatformTheme::removeMnemonics(s).trimmed();
341}
342
343NSString *qt_mac_AppKitString(NSString *table, NSString *key)
344{
345 static const NSBundle *appKit = [NSBundle bundleForClass:NSApplication.class];
346 if (!appKit)
347 return key;
348
349 return [appKit localizedStringForKey:key value:nil table:table];
350}
351
352QT_END_NAMESPACE
353
354/*! \internal
355
356 This NSView derived class is used to add OK/Cancel
357 buttons to NSColorPanel and NSFontPanel. It replaces
358 the panel's content view, while reparenting the former
359 content view into itself. It also takes care of setting
360 the target-action for the OK/Cancel buttons and making
361 sure the layout is consistent.
362 */
363@implementation QNSPanelContentsWrapper {
364 NSButton *_okButton;
365 NSButton *_cancelButton;
366 NSView *_panelContents;
367 NSEdgeInsets _panelContentsMargins;
368}
369
370@synthesize okButton = _okButton;
371@synthesize cancelButton = _cancelButton;
372@synthesize panelContents = _panelContents;
373@synthesize panelContentsMargins = _panelContentsMargins;
374
375- (instancetype)initWithPanelDelegate:(id<QNSPanelDelegate>)panelDelegate
376{
377 if ((self = [super initWithFrame:NSZeroRect])) {
378 // create OK and Cancel buttons and add these as subviews
379 _okButton = [self createButtonWithTitle:QPlatformDialogHelper::Ok];
380 _okButton.action = @selector(onOkClicked);
381 _okButton.target = panelDelegate;
382 _cancelButton = [self createButtonWithTitle:QPlatformDialogHelper::Cancel];
383 _cancelButton.action = @selector(onCancelClicked);
384 _cancelButton.target = panelDelegate;
385
386 _panelContents = nil;
387
388 _panelContentsMargins = NSEdgeInsetsMake(0, 0, 0, 0);
389 }
390
391 return self;
392}
393
394- (void)dealloc
395{
396 [_okButton release];
397 _okButton = nil;
398 [_cancelButton release];
399 _cancelButton = nil;
400
401 _panelContents = nil;
402
403 [super dealloc];
404}
405
406- (NSButton *)createButtonWithTitle:(QPlatformDialogHelper::StandardButton)type
407{
408 NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
409 button.buttonType = NSButtonTypeMomentaryLight;
410 button.bezelStyle = NSBezelStyleRounded;
411 const QString &cleanTitle =
412 QPlatformTheme::removeMnemonics(QGuiApplicationPrivate::platformTheme()->standardButtonText(type));
413 // FIXME: Not obvious, from Cocoa's documentation, that QString::toNSString() makes a deep copy
414 button.title = (NSString *)cleanTitle.toCFString();
415 ((NSButtonCell *)button.cell).font =
416 [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSControlSizeRegular]];
417 [self addSubview:button];
418 return button;
419}
420
421- (void)layout
422{
423 static const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon
424 static const CGFloat ButtonMinHeight = 32.0;
425 static const CGFloat ButtonSpacing = 0.0;
426 static const CGFloat ButtonTopMargin = 0.0;
427 static const CGFloat ButtonBottomMargin = 7.0;
428 static const CGFloat ButtonSideMargin = 9.0;
429
430 NSSize frameSize = self.frame.size;
431
432 [self.okButton sizeToFit];
433 NSSize okSizeHint = self.okButton.frame.size;
434
435 [self.cancelButton sizeToFit];
436 NSSize cancelSizeHint = self.cancelButton.frame.size;
437
438 const CGFloat buttonWidth = qMin(qMax(ButtonMinWidth,
439 qMax(okSizeHint.width, cancelSizeHint.width)),
440 CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
441 const CGFloat buttonHeight = qMax(ButtonMinHeight,
442 qMax(okSizeHint.height, cancelSizeHint.height));
443
444 NSRect okRect = { { frameSize.width - ButtonSideMargin - buttonWidth,
445 ButtonBottomMargin },
446 { buttonWidth, buttonHeight } };
447 self.okButton.frame = okRect;
448 self.okButton.needsDisplay = YES;
449
450 NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - buttonWidth,
451 ButtonBottomMargin },
452 { buttonWidth, buttonHeight } };
453 self.cancelButton.frame = cancelRect;
454 self.cancelButton.needsDisplay = YES;
455
456 // The third view should be the original panel contents. Cache it.
457 if (!self.panelContents)
458 for (NSView *view in self.subviews)
459 if (view != self.okButton && view != self.cancelButton) {
460 _panelContents = view;
461 break;
462 }
463
464 const CGFloat buttonBoxHeight = ButtonBottomMargin + buttonHeight + ButtonTopMargin;
465 const NSRect panelContentsFrame = NSMakeRect(
466 self.panelContentsMargins.left,
467 buttonBoxHeight + self.panelContentsMargins.bottom,
468 frameSize.width - (self.panelContentsMargins.left + self.panelContentsMargins.right),
469 frameSize.height - buttonBoxHeight - (self.panelContentsMargins.top + self.panelContentsMargins.bottom));
470 self.panelContents.frame = panelContentsFrame;
471 self.panelContents.needsDisplay = YES;
472
473 self.needsDisplay = YES;
474 [super layout];
475}
476
477@end // QNSPanelContentsWrapper
478
479QT_BEGIN_NAMESPACE
480
481// -------------------------------------------------------------------------
482
483InputMethodQueryResult queryInputMethod(QObject *object, Qt::InputMethodQueries queries)
484{
485 if (object) {
486 QInputMethodQueryEvent queryEvent(queries | Qt::ImEnabled);
487 if (QCoreApplication::sendEvent(object, &queryEvent)) {
488 if (queryEvent.value(Qt::ImEnabled).toBool()) {
489 InputMethodQueryResult result;
490 static QMetaEnum queryEnum = QMetaEnum::fromType<Qt::InputMethodQuery>();
491 for (int i = 0; i < queryEnum.keyCount(); ++i) {
492 auto query = Qt::InputMethodQuery(queryEnum.value(i));
493 if (queries & query)
494 result.insert(query, queryEvent.value(query));
495 }
496 return result;
497 }
498 }
499 }
500 return {};
501}
502
503// -------------------------------------------------------------------------
504
505QDebug operator<<(QDebug debug, const NSRange &range)
506{
507 if (range.location == NSNotFound) {
508 QDebugStateSaver saver(debug);
509 debug.nospace() << "{NSNotFound, " << range.length << "}";
510 } else {
511 debug << NSStringFromRange(range);
512 }
513 return debug;
514}
515
516QDebug operator<<(QDebug debug, SEL selector)
517{
518 debug << NSStringFromSelector(selector);
519 return debug;
520}
521
522QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:232
Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons)
Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
NSString * qt_mac_AppKitString(NSString *table, NSString *key)
Qt::MouseButton cocoaButton2QtButton(NSEvent *event)
Returns the Qt::Button that corresponds to an NSEvent.buttonNumber.
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event)
Returns the QEvent::Type that corresponds to an NSEvent.type.
Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions)
QString qt_mac_applicationName()
QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference)
Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum)
Returns the Qt::Button that corresponds to an NSEvent.buttonNumber.
void qt_mac_transformProccessToForegroundApplication()
static dndenum_mapper dnd_enums[]
NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions)
Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions)
QString qt_mac_removeAmpersandEscapes(QString s)
QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference)
Flips the Y coordinate of the point/rect between quadrant I and IV.
bool qt_mac_isMouseEvent(NSEvent *event)
NSDragOperation qt_mac_mapDropAction(Qt::DropAction action)
NSMutableArray< NSString * > * qt_mac_QStringListToNSMutableArray(const QStringList &list)
Qt::MouseButtons currentlyPressedMouseButtons()
Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
QDebug operator<<(QDebug debug, QDir::Filters filters)
Definition qdir.cpp:2620
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
QDebug operator<<(QDebug dbg, const QFileInfo &fi)
NSDragOperation mac_code
Qt::DropAction qt_code