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
qnsview.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 <QtGui/qtguiglobal.h>
6
7#include <AppKit/AppKit.h>
8#include <MetalKit/MetalKit.h>
9#include <UniformTypeIdentifiers/UTCoreTypes.h>
10
11#include "qnsview.h"
12#include "qcocoawindow.h"
13#include "qcocoahelpers.h"
14#include "qcocoascreen.h"
16#include "qcocoadrag.h"
18#include <qpa/qplatformintegration.h>
19
20#include <qpa/qwindowsysteminterface.h>
21#include <QtGui/QTextFormat>
22#include <QtCore/QDebug>
23#include <QtCore/QPointer>
24#include <QtCore/QSet>
25#include <QtCore/private/qcore_mac_p.h>
26#include <QtGui/QAccessible>
27#include <QtGui/QImage>
28#include <private/qguiapplication_p.h>
29#include <private/qcoregraphics_p.h>
30#include <private/qwindow_p.h>
31#include <private/qpointingdevice_p.h>
32#include <private/qhighdpiscaling_p.h>
34#ifndef QT_NO_OPENGL
36#endif
38#include <QtGui/private/qmacmimeregistry_p.h>
39#include <QtGui/private/qmetallayer_p.h>
40
41#include <QuartzCore/CATransaction.h>
42
43@interface QNSView (Drawing) <CALayerDelegate>
44- (void)initDrawing;
45@end
46
47@interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject
48- (instancetype)initWithView:(QNSView *)theView;
49- (void)mouseMoved:(NSEvent *)theEvent;
50- (void)mouseEntered:(NSEvent *)theEvent;
51- (void)mouseExited:(NSEvent *)theEvent;
52- (void)cursorUpdate:(NSEvent *)theEvent;
53@end
54
55QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
56
57@interface QNSView (Mouse)
58- (void)initMouse;
59- (NSPoint)screenMousePoint:(NSEvent *)theEvent;
60- (void)mouseMovedImpl:(NSEvent *)theEvent;
61- (void)mouseEnteredImpl:(NSEvent *)theEvent;
62- (void)mouseExitedImpl:(NSEvent *)theEvent;
63@end
64
65@interface QNSView (Touch)
66@end
67
68@interface QNSView (Tablet)
69- (bool)handleTabletEvent:(NSEvent *)theEvent;
70@end
71
72@interface QNSView (Gestures)
73@end
74
75@interface QNSView (Dragging)
76-(void)registerDragTypes;
77@end
78
79@interface QNSView (Keys)
80@end
81
82@interface QNSView (ComplexText) <NSTextInputClient>
83@property (readonly) QObject* focusObject;
84@end
85
86@interface QNSView (ServicesMenu) <NSServicesMenuRequestor>
87@end
88
89#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(150000)
90@interface QNSView (ContentSelectionInfo) <NSViewContentSelectionInfo>
91@end
92#endif
93
94@interface QT_MANGLE_NAMESPACE(QNSViewMenuHelper) : NSObject
95- (instancetype)initWithView:(QNSView *)theView;
96@end
97QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMenuHelper);
98
99// Private interface
100@interface QNSView ()
101- (BOOL)isTransparentForUserInput;
102@property (assign) NSView* previousSuperview;
103@property (assign) NSWindow* previousWindow;
104@property (retain) QNSViewMenuHelper* menuHelper;
105@property (nonatomic, retain) NSColorSpace *colorSpace;
106@end
107
108@implementation QNSView {
109 QPointer<QCocoaWindow> m_platformWindow;
110
111 // Mouse
112 QNSViewMouseMoveHelper *m_mouseMoveHelper;
113 Qt::MouseButtons m_buttons;
114 Qt::MouseButtons m_acceptedMouseDowns;
115 Qt::MouseButtons m_frameStrutButtons;
116 Qt::KeyboardModifiers m_currentWheelModifiers;
117 bool m_dontOverrideCtrlLMB;
118 bool m_sendUpAsRightButton;
119 bool m_scrolling;
120 bool m_updatingDrag;
121
122 // Keys
123 bool m_lastKeyDead;
124 bool m_sendKeyEvent;
125 bool m_sendKeyEventWithoutText;
126 NSEvent *m_currentlyInterpretedKeyEvent;
127 QSet<quint32> m_acceptedKeyDowns;
128
129 // Text
130 QString m_composingText;
131 QPointer<QObject> m_composingFocusObject;
132}
133
134@synthesize colorSpace = m_colorSpace;
135
136- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow
137{
138 if ((self = [super initWithFrame:NSZeroRect])) {
139 m_platformWindow = platformWindow;
140
141 // NSViews are by default visible, but QWindows are not.
142 // We should ideally pick up the actual QWindow state here,
143 // but QWindowPrivate::setVisible() expects to control the
144 // order of events tightly, so we need to wait for a call
145 // to QCocoaWindow::setVisible().
146 self.hidden = YES;
147
148 self.focusRingType = NSFocusRingTypeNone;
149
150 self.previousSuperview = nil;
151 self.previousWindow = nil;
152
153 [self initDrawing];
154 [self initMouse];
155 [self registerDragTypes];
156
157 m_updatingDrag = false;
158
159 m_lastKeyDead = false;
160 m_sendKeyEvent = false;
161 m_currentlyInterpretedKeyEvent = nil;
162
163 self.menuHelper = [[[QNSViewMenuHelper alloc] initWithView:self] autorelease];
164 }
165 return self;
166}
167
168- (void)dealloc
169{
170 qCDebug(lcQpaWindow) << "Deallocating" << self;
171
172 if (m_platformWindow && m_platformWindow->window()->handle()) {
173 // If the view is deallocated while the platform window is still alive,
174 // and the corresponding QWindow still knows about the platform window,
175 // it means that someone took ownership of the QNSView and are now
176 // disposing of it, which we want to reflect to the Qt side.
177 qCDebug(lcQpaWindow) << "Platform window is still alive. Assuming"
178 << "external view ownership. Tearing down platform window in response.";
179 // Releasing of the view in ~QCocoaWindow is a noop while being,
180 // deallocated (see objc4/test/bigrc.m), so we avoid clearing the
181 // view here so that destruction still knows about the view
182 m_platformWindow->window()->destroy();
183 }
184
185 self.menuHelper = nil;
186
187 [[NSNotificationCenter defaultCenter] removeObserver:self];
188 [m_mouseMoveHelper release];
189
190 [super dealloc];
191}
192
193- (NSString *)description
194{
195 NSMutableString *description = [NSMutableString stringWithString:[super description]];
196
197#ifndef QT_NO_DEBUG_STREAM
198 QString platformWindowDescription;
199 QDebug debug(&platformWindowDescription);
200 debug.nospace() << "; " << m_platformWindow << ">";
201
202 NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
203 [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()];
204#endif
205
206 return description;
207}
208
209// ----------------------------- Re-parenting ---------------------------------
210
211- (void)removeFromSuperview
212{
213 qCDebug(lcQpaWindow) << "Removing" << self << "from" << self.superview;
214 QMacAutoReleasePool pool;
215 [super removeFromSuperview];
216}
217
218- (void)viewWillMoveToSuperview:(NSView *)newSuperview
219{
220 Q_ASSERT(!self.previousSuperview);
221 self.previousSuperview = self.superview;
222
223 if (newSuperview == self.superview)
224 qCDebug(lcQpaWindow) << "Re-ordering" << self << "inside" << self.superview;
225 else
226 qCDebug(lcQpaWindow) << "Re-parenting" << self << "from" << self.superview << "to" << newSuperview;
227}
228
229- (void)viewDidMoveToSuperview
230{
231 // We reset previousSuperview in didMoveToWindow below, as that's
232 // when we also have a fully formed picture of what the window is.
233
234 if (self.superview == self.previousSuperview) {
235 qCDebug(lcQpaWindow) << "Done re-ordering" << self << "new index:"
236 << [self.superview.subviews indexOfObject:self];
237 return;
238 }
239
240 // Note: at this point the view's window property hasn't been updated to match the window
241 // of the new superview. We have to wait for viewDidMoveToWindow for that to be reflected.
242}
243
244- (void)viewWillMoveToWindow:(NSWindow *)newWindow
245{
246 Q_ASSERT(!self.previousWindow);
247 self.previousWindow = self.window;
248
249 // This callback is documented to be called also when a view is just moved between
250 // subviews in the same NSWindow, so we're not necessarily moving between NSWindows.
251 if (newWindow != self.window)
252 qCDebug(lcQpaWindow) << "Moving" << self << "from" << self.window << "to" << newWindow;
253}
254
255- (void)viewDidMoveToWindow
256{
257 // Reset up front, in case the callbacks below trigger another reparent
258 auto previousSuperview = self.previousSuperview;
259 self.previousSuperview = nil;
260 auto previousWindow = self.previousWindow;
261 self.previousWindow = nil;
262
263 if (!m_platformWindow)
264 return;
265
266 // This callback is documented to be called also when a view is just
267 // moved between subviews in the same NSWindow. And now we also know
268 // what the new window will be, and it's reflected though view.window.
269
270 if (self.superview != previousSuperview)
271 m_platformWindow->viewDidMoveToSuperview(previousSuperview);
272
273 if (self.window != previousWindow)
274 m_platformWindow->viewDidMoveToWindow(previousWindow);
275}
276
277// QWindow::setParent() promises that the child window will be clipped
278// to its parent, which we rely on in e.g. Qt Widgets when a native window
279// is added to a scroll area. We try to be smart and only enable clipping
280// if we have potential child QWindows that rely on this behavior.
281// FIXME: Be even smarter, and only consider QWindow based subviews,
282// in a way that also includes foreign windows.
283
284- (void)didAddSubview:(NSView *)subview
285{
286 self.clipsToBounds = YES;
287}
288
289- (void)willRemoveSubview:(NSView *)subview
290{
291 self.clipsToBounds = self.subviews.count > 1;
292}
293
294// ----------------------------------------------------------------------------
295
296- (QWindow *)topLevelWindow
297{
298 if (!m_platformWindow)
299 return nullptr;
300
301 QWindow *focusWindow = m_platformWindow->window();
302
303 // For widgets we need to do a bit of trickery as the window
304 // to activate is the window of the top-level widget.
305 if (qstrcmp(focusWindow->metaObject()->className(), "QWidgetWindow") == 0) {
306 while (focusWindow->parent()) {
307 focusWindow = focusWindow->parent();
308 }
309 }
310
311 return focusWindow;
312}
313
314/*
315 Invoked when the view is hidden, either directly,
316 or in response to an ancestor being hidden.
317*/
318- (void)viewDidHide
319{
320 qCDebug(lcQpaWindow) << "Did hide" << self;
321
322 if (!m_platformWindow)
323 return;
324
325 if (!m_platformWindow->isExposed())
326 return;
327
328 m_platformWindow->handleExposeEvent(QRegion());
329}
330
331/*
332 Invoked when the view is unhidden, either directly,
333 or in response to an ancestor being unhidden.
334*/
335- (void)viewDidUnhide
336{
337 qCDebug(lcQpaWindow) << "Did unhide" << self;
338
339 [self setNeedsDisplay:YES];
340}
341
342- (BOOL)isTransparentForUserInput
343{
344 return m_platformWindow->window() &&
345 m_platformWindow->window()->flags() & Qt::WindowTransparentForInput;
346}
347
348- (BOOL)becomeFirstResponder
349{
350 if (!m_platformWindow)
351 return NO;
352 if ([self isTransparentForUserInput])
353 return NO;
354
355 if (!m_platformWindow->windowIsPopupType()
356 && (!self.window.canBecomeKeyWindow || self.window.keyWindow)) {
357 // Calling handleWindowActivated for a QWindow has two effects: first, it
358 // will set the QWindow (and all other QWindows in the same hierarchy)
359 // as Active. Being Active means that the window should appear active from
360 // a style perspective (according to QWindow::isActive()). The second
361 // effect is that it will set QQuiApplication::focusWindow() to point to
362 // the QWindow. The latter means that the QWindow should have keyboard
363 // focus. But those two are not necessarily the same; A tool window could e.g be
364 // rendered as Active while the parent window, which is also Active, has
365 // input focus. But we currently don't distinguish between that cleanly in Qt.
366 // Since we don't want a QWindow to be rendered as Active when the NSWindow
367 // it belongs to is not key, we skip calling handleWindowActivated when
368 // that is the case. Instead, we wait for the window to become key, and handle
369 // QWindow activation from QCocoaWindow::windowDidBecomeKey instead. The only
370 // exception is if the window can never become key, in which case we naturally
371 // cannot wait for that to happen.
372 QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
373 [self topLevelWindow], Qt::ActiveWindowFocusReason);
374 }
375
376 return YES;
377}
378
379- (BOOL)acceptsFirstResponder
380{
381 if (!m_platformWindow)
382 return NO;
383 if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder())
384 return NO;
385 if ([self isTransparentForUserInput])
386 return NO;
387 if ((m_platformWindow->window()->flags() & Qt::ToolTip) == Qt::ToolTip)
388 return NO;
389 return YES;
390}
391
392- (NSView *)hitTest:(NSPoint)aPoint
393{
394 NSView *candidate = [super hitTest:aPoint];
395 if (candidate == self) {
396 if ([self isTransparentForUserInput])
397 return nil;
398 }
399 return candidate;
400}
401
402- (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint
403{
404 // Calculate the mouse position in the QWindow and Qt screen coordinate system,
405 // starting from coordinates in the NSWindow coordinate system.
406 //
407 // This involves translating according to the window location on screen,
408 // as well as inverting the y coordinate due to the origin change.
409 //
410 // Coordinate system overview, outer to innermost:
411 //
412 // Name Origin
413 //
414 // OS X screen bottom-left
415 // Qt screen top-left
416 // NSWindow bottom-left
417 // NSView/QWindow top-left
418 //
419 // NSView and QWindow are equal coordinate systems: the QWindow covers the
420 // entire NSView, and we've set the NSView's isFlipped property to true.
421
422 NSWindow *window = [self window];
423 NSPoint nsWindowPoint;
424 NSRect windowRect = [window convertRectFromScreen:NSMakeRect(mouseLocation.x, mouseLocation.y, 1, 1)];
425 nsWindowPoint = windowRect.origin; // NSWindow coordinates
426 NSPoint nsViewPoint = [self convertPoint: nsWindowPoint fromView: nil]; // NSView/QWindow coordinates
427 *qtWindowPoint = QPointF(nsViewPoint.x, nsViewPoint.y); // NSView/QWindow coordinates
428 *qtScreenPoint = QCocoaScreen::mapFromNative(mouseLocation);
429}
430
431@end
432
434#include "qnsview_mouse.mm"
435#include "qnsview_touch.mm"
437#include "qnsview_tablet.mm"
439#include "qnsview_keys.mm"
441#include "qnsview_menus.mm"
442#if QT_CONFIG(accessibility)
443#include "qnsview_accessibility.mm"
444#endif
445
446// -----------------------------------------------------
447
448@implementation QNSView (QtExtras)
449
450- (QCocoaWindow*)platformWindow
451{
452 return m_platformWindow.data();;
453}
454
455@end
\inmodule QtGui
Definition qwindow.h:64
Q_FORWARD_DECLARE_OBJC_CLASS(NSView)
Q_FORWARD_DECLARE_OBJC_CLASS(NSString)