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