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