macOS: close popups on mousedown within the window frame

On macOS, we close active popups when handling mouse-down events in the
NSView, but not for such events in the window frame. This allows users
to close a window that has a context menu open via the window's close
button, which then leaves open popups behind.

Factor the popup-closing code out into a dedicated method that we can
call from within the NSWindow::sendEvent implementation for mouse down
events.

Fixes: QTBUG-30522
Change-Id: I9c354efc449cfefff3ed84fa34b1cd8a0da3b4a7
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
(cherry picked from commit 70b94eea10d7af83cced09296755a8af28e167b5)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2021-08-19 17:47:38 +02:00 committed by Qt Cherry-pick Bot
parent e0e64df0de
commit 91d53ba22e
3 changed files with 53 additions and 34 deletions

View File

@ -57,6 +57,7 @@ QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QNSView, NSView
#if defined(__OBJC__)
@interface QNSView (MouseAPI)
- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent;
- (bool)closePopups:(NSEvent *)theEvent;
- (void)resetMouseButtons;
@end

View File

@ -176,6 +176,38 @@
QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(),
timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType);
}
- (bool)closePopups:(NSEvent *)theEvent
{
QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack();
if (!popups->isEmpty()) {
// Check if the click is outside all popups.
bool inside = false;
QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]);
for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) {
if ((*it)->geometry().contains(qtScreenPoint.toPoint())) {
inside = true;
break;
}
}
// Close the popups if the click was outside.
if (!inside) {
bool selfClosed = false;
Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type();
while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) {
selfClosed = self == popup->view();
QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(popup->window());
if (!m_platformWindow)
return true; // Bail out if window was destroyed
}
// Consume the mouse event when closing the popup, except for tool tips
// were it's expected that the event is processed normally.
if (type != Qt::ToolTip || selfClosed)
return true;
}
}
return false;
}
@end
@implementation QNSView (Mouse)
@ -379,33 +411,8 @@
// that particular poup type (for example context menus). However, Qt expects
// that plain popup QWindows will also be closed, so we implement the logic
// here as well.
QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack();
if (!popups->isEmpty()) {
// Check if the click is outside all popups.
bool inside = false;
QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]);
for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) {
if ((*it)->geometry().contains(qtScreenPoint.toPoint())) {
inside = true;
break;
}
}
// Close the popups if the click was outside.
if (!inside) {
bool selfClosed = false;
Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type();
while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) {
selfClosed = self == popup->view();
QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(popup->window());
if (!m_platformWindow)
return; // Bail out if window was destroyed
}
// Consume the mouse event when closing the popup, except for tool tips
// were it's expected that the event is processed normally.
if (type != Qt::ToolTip || selfClosed)
return;
}
}
if ([self closePopups:theEvent])
return;
QPointF qtWindowPoint;
QPointF qtScreenPoint;

View File

@ -349,18 +349,29 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
return;
}
const bool mouseEventInFrameStrut = [theEvent, self]{
if (isMouseEvent(theEvent)) {
const NSPoint loc = theEvent.locationInWindow;
const NSRect windowFrame = [self convertRectFromScreen:self.frame];
const NSRect contentFrame = self.contentView.frame;
if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
return true;
}
return false;
}();
// Any mouse-press in the frame of the window, including the title bar buttons, should
// close open popups. Presses within the window's content are handled to do that in the
// NSView::mouseDown implementation.
if (theEvent.type == NSEventTypeLeftMouseDown && mouseEventInFrameStrut)
[qnsview_cast(m_platformWindow->view()) closePopups:theEvent];
[super sendEvent:theEvent];
if (!m_platformWindow)
return; // Platform window went away while processing event
if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self convertRectFromScreen:self.frame];
NSRect contentFrame = self.contentView.frame;
if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
[qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
}
if (m_platformWindow->frameStrutEventsEnabled() && mouseEventInFrameStrut)
[qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
}
- (void)closeAndRelease