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