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
312/*!
313 \fn Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons)
314
315 Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
316*/
317Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons)
318{
319 return static_cast<Qt::MouseButton>(pressedMouseButtons & Qt::MouseButtonMask);
320}
321
322/*!
323 \fn Qt::MouseButtons currentlyPressedMouseButtons()
324
325 Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
326*/
328{
329 return cocoaMouseButtons2QtMouseButtons(NSEvent.pressedMouseButtons);
330}
331
333{
334 return QPlatformTheme::removeMnemonics(s).trimmed();
335}
336
337NSString *qt_mac_AppKitString(NSString *table, NSString *key)
338{
339 static const NSBundle *appKit = [NSBundle bundleForClass:NSApplication.class];
340 if (!appKit)
341 return key;
342
343 return [appKit localizedStringForKey:key value:nil table:table];
344}
345
346QT_END_NAMESPACE
347
348/*! \internal
349
350 This NSView derived class is used to add OK/Cancel
351 buttons to NSColorPanel and NSFontPanel. It replaces
352 the panel's content view, while reparenting the former
353 content view into itself. It also takes care of setting
354 the target-action for the OK/Cancel buttons and making
355 sure the layout is consistent.
356 */
357@implementation QNSPanelContentsWrapper {
358 NSButton *_okButton;
359 NSButton *_cancelButton;
360 NSView *_panelContents;
361 NSEdgeInsets _panelContentsMargins;
362}
363
364@synthesize okButton = _okButton;
365@synthesize cancelButton = _cancelButton;
366@synthesize panelContents = _panelContents;
367@synthesize panelContentsMargins = _panelContentsMargins;
368
369- (instancetype)initWithPanelDelegate:(id<QNSPanelDelegate>)panelDelegate
370{
371 if ((self = [super initWithFrame:NSZeroRect])) {
372 // create OK and Cancel buttons and add these as subviews
373 _okButton = [self createButtonWithTitle:QPlatformDialogHelper::Ok];
374 _okButton.action = @selector(onOkClicked);
375 _okButton.target = panelDelegate;
376 _cancelButton = [self createButtonWithTitle:QPlatformDialogHelper::Cancel];
377 _cancelButton.action = @selector(onCancelClicked);
378 _cancelButton.target = panelDelegate;
379
380 _panelContents = nil;
381
382 _panelContentsMargins = NSEdgeInsetsMake(0, 0, 0, 0);
383 }
384
385 return self;
386}
387
388- (void)dealloc
389{
390 [_okButton release];
391 _okButton = nil;
392 [_cancelButton release];
393 _cancelButton = nil;
394
395 _panelContents = nil;
396
397 [super dealloc];
398}
399
400- (NSButton *)createButtonWithTitle:(QPlatformDialogHelper::StandardButton)type
401{
402 NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
403 button.buttonType = NSButtonTypeMomentaryLight;
404 button.bezelStyle = NSBezelStyleRounded;
405 const QString &cleanTitle =
406 QPlatformTheme::removeMnemonics(QGuiApplicationPrivate::platformTheme()->standardButtonText(type));
407 // FIXME: Not obvious, from Cocoa's documentation, that QString::toNSString() makes a deep copy
408 button.title = (NSString *)cleanTitle.toCFString();
409 ((NSButtonCell *)button.cell).font =
410 [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSControlSizeRegular]];
411 [self addSubview:button];
412 return button;
413}
414
415- (void)layout
416{
417 static const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon
418 static const CGFloat ButtonMinHeight = 32.0;
419 static const CGFloat ButtonSpacing = 0.0;
420 static const CGFloat ButtonTopMargin = 0.0;
421 static const CGFloat ButtonBottomMargin = 7.0;
422 static const CGFloat ButtonSideMargin = 9.0;
423
424 NSSize frameSize = self.frame.size;
425
426 [self.okButton sizeToFit];
427 NSSize okSizeHint = self.okButton.frame.size;
428
429 [self.cancelButton sizeToFit];
430 NSSize cancelSizeHint = self.cancelButton.frame.size;
431
432 const CGFloat buttonWidth = qMin(qMax(ButtonMinWidth,
433 qMax(okSizeHint.width, cancelSizeHint.width)),
434 CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
435 const CGFloat buttonHeight = qMax(ButtonMinHeight,
436 qMax(okSizeHint.height, cancelSizeHint.height));
437
438 NSRect okRect = { { frameSize.width - ButtonSideMargin - buttonWidth,
439 ButtonBottomMargin },
440 { buttonWidth, buttonHeight } };
441 self.okButton.frame = okRect;
442 self.okButton.needsDisplay = YES;
443
444 NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - buttonWidth,
445 ButtonBottomMargin },
446 { buttonWidth, buttonHeight } };
447 self.cancelButton.frame = cancelRect;
448 self.cancelButton.needsDisplay = YES;
449
450 // The third view should be the original panel contents. Cache it.
451 if (!self.panelContents)
452 for (NSView *view in self.subviews)
453 if (view != self.okButton && view != self.cancelButton) {
454 _panelContents = view;
455 break;
456 }
457
458 const CGFloat buttonBoxHeight = ButtonBottomMargin + buttonHeight + ButtonTopMargin;
459 const NSRect panelContentsFrame = NSMakeRect(
460 self.panelContentsMargins.left,
461 buttonBoxHeight + self.panelContentsMargins.bottom,
462 frameSize.width - (self.panelContentsMargins.left + self.panelContentsMargins.right),
463 frameSize.height - buttonBoxHeight - (self.panelContentsMargins.top + self.panelContentsMargins.bottom));
464 self.panelContents.frame = panelContentsFrame;
465 self.panelContents.needsDisplay = YES;
466
467 self.needsDisplay = YES;
468 [super layout];
469}
470
471@end // QNSPanelContentsWrapper
472
473QT_BEGIN_NAMESPACE
474
475// -------------------------------------------------------------------------
476
477InputMethodQueryResult queryInputMethod(QObject *object, Qt::InputMethodQueries queries)
478{
479 if (object) {
480 QInputMethodQueryEvent queryEvent(queries | Qt::ImEnabled);
481 if (QCoreApplication::sendEvent(object, &queryEvent)) {
482 if (queryEvent.value(Qt::ImEnabled).toBool()) {
483 InputMethodQueryResult result;
484 static QMetaEnum queryEnum = QMetaEnum::fromType<Qt::InputMethodQuery>();
485 for (int i = 0; i < queryEnum.keyCount(); ++i) {
486 auto query = Qt::InputMethodQuery(queryEnum.value(i));
487 if (queries & query)
488 result.insert(query, queryEvent.value(query));
489 }
490 return result;
491 }
492 }
493 }
494 return {};
495}
496
497// -------------------------------------------------------------------------
498
499QDebug operator<<(QDebug debug, const NSRange &range)
500{
501 if (range.location == NSNotFound) {
502 QDebugStateSaver saver(debug);
503 debug.nospace() << "{NSNotFound, " << range.length << "}";
504 } else {
505 debug << NSStringFromRange(range);
506 }
507 return debug;
508}
509
510QDebug operator<<(QDebug debug, SEL selector)
511{
512 debug << NSStringFromSelector(selector);
513 return debug;
514}
515
516QT_END_NAMESPACE
\inmodule QtCore\reentrant
Definition qpoint.h:231
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.
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.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
QDebug operator<<(QDebug dbg, const QFileInfo &fi)
QDebug operator<<(QDebug debug, QIODevice::OpenMode modes)
NSDragOperation mac_code
Qt::DropAction qt_code