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