diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 3b797174726..dc9140d9904 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -133,6 +133,11 @@ public: void setToolbar(QWindow *window, NSToolbar *toolbar); NSToolbar *toolbar(QWindow *window) const; void clearToolbars(); + + void pushPopupWindow(QCocoaWindow *window); + QCocoaWindow *popPopupWindow(); + QCocoaWindow *activePopupWindow() const; + QList *popupWindowStack(); private: static QCocoaIntegration *mInstance; @@ -151,6 +156,7 @@ private: QScopedPointer mKeyboardMapper; QHash mToolbars; + QList m_popupWindowStack; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 881b23cd6ff..aa33cfd8bc6 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -545,4 +545,28 @@ void QCocoaIntegration::clearToolbars() mToolbars.clear(); } +void QCocoaIntegration::pushPopupWindow(QCocoaWindow *window) +{ + m_popupWindowStack.append(window); +} + +QCocoaWindow *QCocoaIntegration::popPopupWindow() +{ + if (m_popupWindowStack.isEmpty()) + return 0; + return m_popupWindowStack.takeLast(); +} + +QCocoaWindow *QCocoaIntegration::activePopupWindow() const +{ + if (m_popupWindowStack.isEmpty()) + return 0; + return m_popupWindowStack.front(); +} + +QList *QCocoaIntegration::popupWindowStack() +{ + return &m_popupWindowStack; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index dd9d7d10ff7..cc14cd75bab 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -262,7 +262,6 @@ public: // for QNSView bool m_effectivelyMaximized; Qt::WindowState m_synchedWindowState; Qt::WindowModality m_windowModality; - QPointer m_activePopupWindow; QPointer m_enterLeaveTargetWindow; bool m_windowUnderMouse; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 64e599ae087..970d7f6075d 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -454,6 +454,10 @@ QCocoaWindow::~QCocoaWindow() [m_qtView clearQWindowPointers]; } + // While it is unlikely that this window will be in the popup stack + // during deletetion we clear any pointers here to make sure. + QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); + foreach (QCocoaWindow *child, m_childWindows) { [m_nsWindow removeChildWindow:child->m_nsWindow]; child->m_parentCocoaWindow = 0; @@ -646,17 +650,17 @@ void QCocoaWindow::setVisible(bool visible) // We need to recreate if the modality has changed as the style mask will need updating if (m_windowModality != window()->modality()) recreateWindow(parent()); + + // Register popup windows. The Cocoa platform plugin will forward mouse events + // to them and close them when needed. + if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) + QCocoaIntegration::instance()->pushPopupWindow(this); + if (parentCocoaWindow) { // The parent window might have moved while this window was hidden, // update the window geometry if there is a parent. setGeometry(window()->geometry()); - // Register popup windows so that the parent window can close them when needed. - if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) { - // qDebug() << "transientParent and popup" << window()->type() << Qt::Popup << (window()->type() & Qt::Popup); - parentCocoaWindow->m_activePopupWindow = window(); - } - if (window()->type() == Qt::Popup) { // QTBUG-30266: a window should not be resizable while a transient popup is open // Since this isn't a native popup, the window manager doesn't close the popup when you click outside @@ -758,8 +762,11 @@ void QCocoaWindow::setVisible(bool visible) [NSEvent removeMonitor:monitor]; monitor = nil; } + + if (window()->type() == Qt::Popup) + QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); + if (parentCocoaWindow && window()->type() == Qt::Popup) { - parentCocoaWindow->m_activePopupWindow = 0; if (m_resizableTransientParent && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) // QTBUG-30266: a window should not be resizable while a transient popup is open @@ -1172,10 +1179,9 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) void QCocoaWindow::windowWillMove() { // Close any open popups on window move - if (m_activePopupWindow) { - QWindowSystemInterface::handleCloseEvent(m_activePopupWindow); + while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { + QWindowSystemInterface::handleCloseEvent(popup->window()); QWindowSystemInterface::flushWindowSystemEvents(); - m_activePopupWindow = 0; } } diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index cfd2eeb837e..a9699814002 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -693,6 +693,10 @@ QT_WARNING_POP m_platformWindow->m_forwardWindow = 0; } + // Popups implicitly grap mouse events; forward to the active popup if there is one + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) + targetView = popup->contentView(); + [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; ulong timestamp = [theEvent timestamp] * 1000; @@ -760,16 +764,39 @@ QT_WARNING_POP if (m_window->flags() & Qt::WindowTransparentForInput) return [super mouseDown:theEvent]; m_sendUpAsRightButton = false; - if (m_platformWindow->m_activePopupWindow) { - Qt::WindowType type = m_platformWindow->m_activePopupWindow->type(); - QWindowSystemInterface::handleCloseEvent(m_platformWindow->m_activePopupWindow); - QWindowSystemInterface::flushWindowSystemEvents(); - m_platformWindow->m_activePopupWindow = 0; - // 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) - return; + + // Handle any active poup windows; clicking outisde them should close them + // all. Don't do anything or clicks inside one of the menus, let Cocoa + // handle that case. Note that in practice many windows of the Qt::Popup type + // will actually close themselves in this case using logic implemented in + // 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 *popups = QCocoaIntegration::instance()->popupWindowStack(); + if (!popups->isEmpty()) { + // Check if the click is outside all popups. + bool inside = false; + QPointF qtScreenPoint = qt_mac_flipPoint([self screenMousePoint:theEvent]); + for (QList::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) { + Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); + while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { + QWindowSystemInterface::handleCloseEvent(popup->window()); + QWindowSystemInterface::flushWindowSystemEvents(); + } + // 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) + return; + } } + if ([self hasMarkedText]) { NSInputManager* inputManager = [NSInputManager currentInputManager]; if ([inputManager wantsToHandleMouseEvents]) { diff --git a/tests/manual/cocoa/popups/main.cpp b/tests/manual/cocoa/popups/main.cpp new file mode 100644 index 00000000000..46249d67725 --- /dev/null +++ b/tests/manual/cocoa/popups/main.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +class Window : public QWidget +{ + Q_OBJECT + +public: + Window(); + +public slots: + void triggered(QAction*); + void clean(); + void showPoppWindow(); + +private: + QLabel *explanation; + QToolButton *toolButton; + QMenu *menu; + QLineEdit *echo; + QComboBox *comboBox; + QPushButton *pushButton; +}; + +Window::Window() +{ + QGroupBox* group = new QGroupBox(tr("test the popup")); + + explanation = new QLabel( + "This test is used to verify that popup windows will be closed " + "as expected. This includes when clicking outside the popup or moving the " + "parent window. Tested popups include context menus, combo box popups, tooltips " + "and QWindow with Qt::Popup set." + ); + explanation->setWordWrap(true); + explanation->setToolTip("I'm a tool tip!"); + + menu = new QMenu(group); + menu->addAction(tr("line one")); + menu->addAction(tr("line two")); + menu->addAction(tr("line three")); + menu->addAction(tr("line four")); + menu->addAction(tr("line five")); + + QMenu *subMenu1 = new QMenu(); + subMenu1->addAction("1"); + subMenu1->addAction("2"); + subMenu1->addAction("3"); + menu->addMenu(subMenu1); + + QMenu *subMenu2 = new QMenu(); + subMenu2->addAction("2 1"); + subMenu2->addAction("2 2"); + subMenu2->addAction("2 3"); + menu->addMenu(subMenu2); + + toolButton = new QToolButton(group); + toolButton->setMenu(menu); + toolButton->setPopupMode( QToolButton::MenuButtonPopup ); + toolButton->setText("select me"); + + echo = new QLineEdit(group); + echo->setPlaceholderText("not triggered"); + + connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(triggered(QAction*))); + connect(menu, SIGNAL(aboutToShow()), this, SLOT(clean())); + + comboBox = new QComboBox(); + comboBox->addItem("Item 1"); + comboBox->addItem("Item 2"); + comboBox->addItem("Item 3"); + + pushButton = new QPushButton("Show popup window"); + connect(pushButton, SIGNAL(clicked()), this, SLOT(showPoppWindow())); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget(explanation); + layout->addWidget(toolButton); + layout->addWidget(echo); + layout->addWidget(comboBox); + layout->addWidget(pushButton); + + group ->setLayout(layout); + setLayout(layout); + setWindowTitle(tr("Popup Window Testing")); +} + +void Window::clean() +{ + echo->setText(""); +} + +void Window::showPoppWindow() +{ + QWindow *window = new QWindow(); + window->setTransientParent(this->windowHandle()); + window->setPosition(this->pos()); + window->setWidth(100); + window->setHeight(100); + window->setFlags(Qt::Window | Qt::Popup); + window->show(); +} + +void Window::triggered(QAction* act) +{ + if (!act) + return; + echo->setText(act->text()); +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + Window window; + window.show(); + return app.exec(); +} + +#include "main.moc" diff --git a/tests/manual/cocoa/popups/popups.pro b/tests/manual/cocoa/popups/popups.pro new file mode 100644 index 00000000000..d0a3d441716 --- /dev/null +++ b/tests/manual/cocoa/popups/popups.pro @@ -0,0 +1,4 @@ +QT += widgets + +SOURCES = main.cpp +CONFIG -= app_bundle