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
qnswindow.mm
Go to the documentation of this file.
1// Copyright (C) 2017 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#if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION)
6
7#include <AppKit/AppKit.h>
8
9#include "qnswindow.h"
10#include "qcocoawindow.h"
11#include "qcocoahelpers.h"
14
15#include <qpa/qwindowsysteminterface.h>
16
17#include <QtGui/private/qhighdpiscaling_p.h>
18
19Q_STATIC_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events");
20
21@implementation NSWindow (FullScreenProperty)
22
23+ (void)load
24{
25 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
26 [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil
27 usingBlock:^(NSNotification *notification) {
28 objc_setAssociatedObject(notification.object, @selector(qt_fullScreen),
29 @(YES), OBJC_ASSOCIATION_RETAIN);
30 }
31 ];
32 [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil
33 usingBlock:^(NSNotification *notification) {
34 objc_setAssociatedObject(notification.object, @selector(qt_fullScreen),
35 nil, OBJC_ASSOCIATION_RETAIN);
36 }
37 ];
38}
39
40- (BOOL)qt_fullScreen
41{
42 NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen));
43 return [number boolValue];
44}
45@end
46
47
48QT_BEGIN_NAMESPACE
49NSWindow<QNSWindowProtocol> *qnswindow_cast(NSWindow *window)
50{
51 if ([window conformsToProtocol:@protocol(QNSWindowProtocol)])
52 return static_cast<QCocoaNSWindow *>(window);
53 else
54 return nil;
55}
56QT_END_NAMESPACE
57
58@implementation QNSWindow
59#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
60#include "qnswindow.mm"
61#undef QNSWINDOW_PROTOCOL_IMPLMENTATION
62
63+ (void)applicationActivationChanged:(NSNotification*)notification
64{
65 const id sender = self;
66 NSEnumerator<NSWindow*> *windowEnumerator = nullptr;
67 NSApplication *application = [NSApplication sharedApplication];
68
69 // Unfortunately there's no NSWindowListOrderedBackToFront,
70 // so we have to manually reverse the order using an array.
71 NSMutableArray<NSWindow *> *windows = [[NSMutableArray<NSWindow *> new] autorelease];
72 [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
73 usingBlock:^(NSWindow *window, BOOL *) {
74 // For some reason AppKit will give us nil-windows, skip those
75 if (!window)
76 return;
77
78 [windows addObject:window];
79 }
80 ];
81
82 windowEnumerator = windows.reverseObjectEnumerator;
83
84 for (NSWindow *window in windowEnumerator) {
85 // We're meddling with normal and floating windows, so leave others alone
86 if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel))
87 continue;
88
89 // Windows that hide automatically will keep their NSFloatingWindowLevel,
90 // and hence be on top of the window stack. We don't want to affect these
91 // windows, as otherwise we might end up with key windows being ordered
92 // behind these auto-hidden windows when activating the application by
93 // clicking on a new tool window.
94 if (window.hidesOnDeactivate)
95 continue;
96
97 if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
98 if (QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow) {
99 window.level = notification.name == NSApplicationWillResignActiveNotification ?
100 NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
101 }
102 }
103
104 // The documentation says that "when a window enters a new level, it’s ordered
105 // in front of all its peers in that level", but that doesn't seem to be the
106 // case in practice. To keep the order correct after meddling with the window
107 // levels, we explicitly order each window to the front. Since we are iterating
108 // the windows in back-to-front order, this is okey. The call also triggers AppKit
109 // to re-evaluate the level in relation to windows from other applications,
110 // working around an issue where our tool windows would stay on top of other
111 // application windows if activation was transferred to another application by
112 // clicking on it instead of via the application switcher or Dock. Finally, we
113 // do this re-ordering for all windows (except auto-hiding ones), otherwise we would
114 // end up triggering a bug in AppKit where the tool windows would disappear behind
115 // the application window.
116 [window orderFront:sender];
117 }
118}
119
120@end
121
122@implementation QNSPanel
123#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
124#include "qnswindow.mm"
125#undef QNSWINDOW_PROTOCOL_IMPLMENTATION
126
127- (BOOL)worksWhenModal
128{
129 if (!m_platformWindow)
130 return NO;
131
132 // Conceptually there are two sets of windows we need consider:
133 //
134 // - windows 'lower' in the modal session stack
135 // - windows 'within' the current modal session
136 //
137 // The first set of windows should always be blocked by the current
138 // modal session, regardless of window type. The latter set may contain
139 // windows with a transient parent, which from Qt's point of view makes
140 // them 'child' windows, so we treat them as operable within the current
141 // modal session.
142
143 if (!NSApp.modalWindow)
144 return NO;
145
146 // Special case popup windows (menus, completions, etc), as these usually
147 // don't have a transient parent set, and we don't want to block them. The
148 // assumption is that these windows are only opened intermittently, from
149 // within windows that can already be interacted with in this modal session.
150 Qt::WindowType type = m_platformWindow->window()->type();
151 if (type == Qt::Popup)
152 return YES;
153
154 // If the current modal window (top level modal session) is not a Qt window we
155 // have no way of knowing if this window is transient child of the modal window.
156 if (![NSApp.modalWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
157 return NO;
158
159 if (auto *modalWindow = static_cast<QCocoaNSWindow *>(NSApp.modalWindow).platformWindow) {
160 if (modalWindow->window()->isAncestorOf(m_platformWindow->window(), QWindow::IncludeTransients))
161 return YES;
162 }
163
164 return NO;
165}
166@end
167
168#else // QNSWINDOW_PROTOCOL_IMPLMENTATION
169
170// The following content is mixed in to the QNSWindow and QNSPanel classes via includes
171
172{
173 // Member variables
174 QPointer<QCocoaWindow> m_platformWindow;
175 bool m_isMinimizing;
176}
177
178- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style
179 backing:(NSBackingStoreType)backingStoreType defer:(BOOL)defer screen:(NSScreen *)screen
180 platformWindow:(QCocoaWindow*)window
181{
182 // Initializing the window will end up in [NSWindow _commonAwake], which calls many
183 // of the getters below. We need to set up the platform window reference first, so
184 // we can properly reflect the window's state during initialization.
185 m_platformWindow = window;
186
187 m_isMinimizing = false;
188
189 return [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:defer screen:screen];
190}
191
192- (QCocoaWindow *)platformWindow
193{
194 return m_platformWindow;
195}
196
197- (void)setContentView:(NSView*)view
198{
199 [super setContentView:view];
200
201 if (!qnsview_cast(self.contentView))
202 return;
203
204 // Now that we're the content view, we can apply the properties of
205 // the QWindow. We do this here, instead of in init, so that we can
206 // use the same code paths for setting these properties during
207 // NSWindow initialization as we do when setting them later on.
208 const QWindow *window = m_platformWindow->window();
209 qCDebug(lcQpaWindow) << "Reflecting" << window << "state to" << self;
210
211 m_platformWindow->propagateSizeHints();
212 m_platformWindow->setWindowFlags(window->flags());
213 m_platformWindow->setWindowTitle(window->title());
214 m_platformWindow->setWindowFilePath(window->filePath()); // Also sets window icon
215 m_platformWindow->setWindowState(window->windowState());
216 m_platformWindow->setOpacity(window->opacity());
217
218 // At the time of creation the QNSWindow is given a geometry based
219 // on the client geometry of the QWindow. But at that point we don't
220 // know anything about the size of the NSWindow frame, which means
221 // that the logic in QCocoaWindow::setGeometry for adjusting the
222 // client geometry based on the QWindow's positionPolicy is a noop.
223 // Now that we have a NSWindow to read the frame from we re-apply
224 // the QWindow geometry, which will move the NSWindow if needed.
225 m_platformWindow->setGeometry(QHighDpi::toNativeWindowGeometry(window->geometry(), window));
226
227
228 m_platformWindow->setVisible(window->isVisible());
229}
230
231- (void)setStyleMask:(NSWindowStyleMask)styleMask
232{
233 // Setting the style mask might move the content view between
234 // a NSThemeFrame and a NSNextStepFrame, which results in also
235 // losing the view.window temporarily. We don't want that to
236 // result in dropping our window controller.
237 [[self.windowController retain] autorelease];
238
239 [super setStyleMask:styleMask];
240}
241
242- (NSString *)description
243{
244 NSMutableString *description = [NSMutableString stringWithString:[super description]];
245
246#ifndef QT_NO_DEBUG_STREAM
247 QString contentViewDescription;
248 QDebug debug(&contentViewDescription);
249 debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">";
250
251 NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
252 [description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()];
253#endif
254
255 return description;
256}
257
258- (BOOL)canBecomeKeyWindow
259{
260 if (!m_platformWindow)
261 return NO;
262
263 if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder())
264 return NO;
265
266 if ([self isKindOfClass:[QNSPanel class]]) {
267 // Only tool or dialog windows should become key:
268 Qt::WindowType type = m_platformWindow->window()->type();
269 if (type == Qt::Tool || type == Qt::Dialog)
270 return YES;
271
272 return NO;
273 } else {
274 // The default implementation returns NO for title-bar less windows,
275 // override and return yes here to make sure popup windows such as
276 // the combobox popup can become the key window.
277 return YES;
278 }
279}
280
281- (BOOL)canBecomeMainWindow
282{
283 // Windows with a transient parent (such as combobox popup windows)
284 // cannot become the main window:
285 if (!m_platformWindow || m_platformWindow->window()->transientParent())
286 return NO;
287
288 return [super canBecomeMainWindow];
289}
290
291- (BOOL)isOpaque
292{
293 return m_platformWindow ? m_platformWindow->isOpaque() : [super isOpaque];
294}
295
296- (NSColor *)backgroundColor
297{
298 // FIXME: Plumb to a WA_NoSystemBackground-like window flag,
299 // or a QWindow::backgroundColor() property. In the meantime
300 // we assume that if you have translucent content, without a
301 // frame then you intend to do all background drawing yourself.
302 const QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr;
303 if (!self.opaque && window) {
304 // Qt::Popup also requires clearColor - in qmacstyle
305 // we fill background using a special path with rounded corners.
306 if (window->flags().testFlag(Qt::FramelessWindowHint)
307 || (window->flags() & Qt::WindowType_Mask) == Qt::Popup)
308 return [NSColor clearColor];
309 }
310
311 // This still allows you to have translucent content with a frame,
312 // where the system background (or color set via NSWindow) will
313 // shine through.
314 return [super backgroundColor];
315}
316
317- (void)sendEvent:(NSEvent*)theEvent
318{
319 qCDebug(lcQpaEvents) << "Sending" << theEvent << "to" << self;
320
321 // We might get events for a NSWindow after the corresponding platform
322 // window has been deleted, as the NSWindow can outlive the QCocoaWindow
323 // e.g. if being retained by other parts of AppKit, or in an auto-release
324 // pool. We guard against this in QNSView as well, as not all callbacks
325 // come via events, but if they do there's no point in propagating them.
326 if (!m_platformWindow)
327 return;
328
329 // Prevent deallocation of this NSWindow during event delivery, as we
330 // have logic further below that depends on the window being alive.
331 [[self retain] autorelease];
332
333 const char *eventType = object_getClassName(theEvent);
334 if (QWindowSystemInterface::handleNativeEvent(m_platformWindow->window(),
335 QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) {
336 return;
337 }
338
339 const bool mouseEventInFrameStrut = [theEvent, self]{
340 if (qt_mac_isMouseEvent(theEvent)) {
341 const NSPoint loc = theEvent.locationInWindow;
342 const NSRect windowFrame = [self convertRectFromScreen:self.frame];
343 const NSRect contentFrame = self.contentView.frame;
344 if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
345 return true;
346 }
347 return false;
348 }();
349 // Any mouse-press in the frame of the window, including the title bar buttons, should
350 // close open popups. Presses within the window's content are handled to do that in the
351 // NSView::mouseDown implementation.
352 if (theEvent.type == NSEventTypeLeftMouseDown && mouseEventInFrameStrut)
353 QGuiApplicationPrivate::instance()->closeAllPopups();
354
355 [super sendEvent:theEvent];
356
357 if (!m_platformWindow)
358 return; // Platform window went away while processing event
359
360 // Cocoa will not deliver mouse events to a window that is modally blocked (by Cocoa,
361 // not Qt). However, an active popup is expected to grab any mouse event within the
362 // application, so we need to handle those explicitly and trust Qt's isWindowBlocked
363 // implementation to eat events that shouldn't be delivered anyway.
364 if (qt_mac_isMouseEvent(theEvent) && QGuiApplicationPrivate::instance()->activePopupWindow()
365 && QGuiApplicationPrivate::instance()->isWindowBlocked(m_platformWindow->window(), nullptr)) {
366 qCDebug(lcQpaWindow) << "Mouse event over modally blocked window" << m_platformWindow->window()
367 << "while popup is open - redirecting";
368 [qnsview_cast(m_platformWindow->view()) handleMouseEvent:theEvent];
369 }
370 if (m_platformWindow->frameStrutEventsEnabled() && mouseEventInFrameStrut)
371 [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
372}
373
374- (void)miniaturize:(id)sender
375{
376 QScopedValueRollback miniaturizeTracker(m_isMinimizing, true);
377 [super miniaturize:sender];
378}
379
380- (NSButton *)standardWindowButton:(NSWindowButton)buttonType
381{
382 NSButton *button = [super standardWindowButton:buttonType];
383
384 // When an NSWindow is asked to minimize it will check the
385 // NSWindowMiniaturizeButton for enablement before continuing,
386 // even if the style mask includes NSWindowStyleMaskMiniaturizable.
387 // To ensure that a window can be minimized, even when the
388 // minimize button has been disabled in response to the user
389 // setting CustomizeWindowHint, we temporarily return a default
390 // minimize-button that we haven't modified in updateTitleBarButtons.
391 // This ensures the window can be minimized, without visually
392 // toggling the actual minimize-button on and off.
393 if (buttonType == NSWindowMiniaturizeButton && m_isMinimizing && !button.enabled)
394 return [NSWindow standardWindowButton:buttonType forStyleMask:self.styleMask];
395
396 return button;
397}
398
399- (void)closeAndRelease
400{
401 qCDebug(lcQpaWindow) << "Closing and releasing" << self;
402 [self close];
403 [self release];
404}
405
406- (void)dealloc
407{
408 qCDebug(lcQpaWindow) << "Deallocating" << self;
409 self.delegate = nil;
410
411 [super dealloc];
412}
413
414#endif