From 53ed4de02278b17708199891f9b56c6e6e04103b Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Fri, 4 Jul 2014 15:24:41 +0300 Subject: [PATCH] winrt: Clean up platform message dialog helper - Remove "Platform" from class name for consistency - Use ComPtr and HRESULT checking where missing - Use UICommand ID class to reduce callback complexity - Use dialog completed callback to fix failed repeated dialog opening These changes have been tested with the QtQuick.Dialogs message dialog example, and all features appear to be working. Note that the WinRT dialog supports a maximum of three buttons, though, and a warning is printed if this number is exceeded. Similarly to Android, the native hooks can be now be disabled by using qputenv("QT_USE_WINRT_NATIVE_DIALOGS", "0"). Task-number: QTBUG-38115 Task-number: QTBUG-39868 Change-Id: I9943c7c11bd640790db68219cefdca1a680e96ec Reviewed-by: Maurice Kalinowski --- .../winrt/qwinrtmessagedialoghelper.cpp | 232 ++++++++++++++++++ ...oghelper.h => qwinrtmessagedialoghelper.h} | 29 ++- .../qwinrtplatformmessagedialoghelper.cpp | 204 --------------- src/plugins/platforms/winrt/qwinrttheme.cpp | 9 +- src/plugins/platforms/winrt/winrt.pro | 6 +- 5 files changed, 259 insertions(+), 221 deletions(-) create mode 100644 src/plugins/platforms/winrt/qwinrtmessagedialoghelper.cpp rename src/plugins/platforms/winrt/{qwinrtplatformmessagedialoghelper.h => qwinrtmessagedialoghelper.h} (75%) delete mode 100644 src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.cpp diff --git a/src/plugins/platforms/winrt/qwinrtmessagedialoghelper.cpp b/src/plugins/platforms/winrt/qwinrtmessagedialoghelper.cpp new file mode 100644 index 0000000000..6de90ba1ec --- /dev/null +++ b/src/plugins/platforms/winrt/qwinrtmessagedialoghelper.cpp @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtmessagedialoghelper.h" +#include "qwinrttheme.h" + +#include + +#include +#include +#include +#include + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::UI::Popups; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +typedef IAsyncOperationCompletedHandler DialogCompletedHandler; + +QT_BEGIN_NAMESPACE + +class CommandId : public RuntimeClass +{ +public: + CommandId(QPlatformDialogHelper::StandardButton button) + : button(button) { } + QPlatformDialogHelper::StandardButton button; +}; + +class QWinRTMessageDialogHelperPrivate +{ +public: + const QWinRTTheme *theme; + bool shown; + ComPtr info; + QEventLoop loop; +}; + +QWinRTMessageDialogHelper::QWinRTMessageDialogHelper(const QWinRTTheme *theme) + : QPlatformMessageDialogHelper(), d_ptr(new QWinRTMessageDialogHelperPrivate) +{ + Q_D(QWinRTMessageDialogHelper); + + d->theme = theme; + d->shown = false; +} + +QWinRTMessageDialogHelper::~QWinRTMessageDialogHelper() +{ + Q_D(QWinRTMessageDialogHelper); + + if (d->shown) + hide(); +} + +void QWinRTMessageDialogHelper::exec() +{ + Q_D(QWinRTMessageDialogHelper); + + if (!d->shown) + show(Qt::Dialog, Qt::ApplicationModal, 0); + d->loop.exec(); +} + +bool QWinRTMessageDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) +{ + Q_UNUSED(windowFlags) + Q_UNUSED(windowModality) + Q_UNUSED(parent) + Q_D(QWinRTMessageDialogHelper); + + QSharedPointer options = this->options(); + const QString informativeText = options->informativeText(); + const QString title = options->windowTitle(); + const QString text = informativeText.isEmpty() ? options->text() : (options->text() + QLatin1Char('\n') + informativeText); + + HRESULT hr; + ComPtr dialogFactory; + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Popups_MessageDialog).Get(), + IID_PPV_ARGS(&dialogFactory)); + RETURN_FALSE_IF_FAILED("Failed to create dialog factory"); + + ComPtr commandFactory; + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Popups_UICommand).Get(), + IID_PPV_ARGS(&commandFactory)); + RETURN_FALSE_IF_FAILED("Failed to create command factory"); + + ComPtr dialog; + HStringReference nativeText(reinterpret_cast(text.utf16()), text.size()); + if (!title.isEmpty()) { + HStringReference nativeTitle(reinterpret_cast(title.utf16()), title.size()); + hr = dialogFactory->CreateWithTitle(nativeText.Get(), nativeTitle.Get(), &dialog); + RETURN_FALSE_IF_FAILED("Failed to create dialog with title"); + } else { + hr = dialogFactory->Create(nativeText.Get(), &dialog); + RETURN_FALSE_IF_FAILED("Failed to create dialog"); + } + + // Add Buttons + ComPtr> dialogCommands; + hr = dialog->get_Commands(&dialogCommands); + RETURN_FALSE_IF_FAILED("Failed to get dialog commands"); + + // If no button is specified we need to create one to get close notification + int buttons = options->standardButtons(); + if (buttons == 0) + buttons = Ok; + + for (int i = FirstButton; i < LastButton; i<<=1) { + if (!(buttons & i)) + continue; + // Add native command + const QString label = d->theme->standardButtonText(i); + HStringReference nativeLabel(reinterpret_cast(label.utf16()), label.size()); + ComPtr command; + hr = commandFactory->Create(nativeLabel.Get(), &command); + RETURN_FALSE_IF_FAILED("Failed to create message box command"); + ComPtr id = Make(static_cast(i)); + hr = command->put_Id(id.Get()); + RETURN_FALSE_IF_FAILED("Failed to set command ID"); + hr = dialogCommands->Append(command.Get()); + if (hr == E_BOUNDS) { + qErrnoWarning(hr, "The WinRT message dialog supports a maximum of three buttons"); + continue; + } + RETURN_FALSE_IF_FAILED("Failed to append message box command"); + if (i == Abort || i == Cancel || i == Close) { + quint32 size; + hr = dialogCommands->get_Size(&size); + RETURN_FALSE_IF_FAILED("Failed to get command list size"); + hr = dialog->put_CancelCommandIndex(size - 1); + RETURN_FALSE_IF_FAILED("Failed to set cancel index"); + } + } + + ComPtr> op; + hr = dialog->ShowAsync(&op); + RETURN_FALSE_IF_FAILED("Failed to show dialog"); + hr = op->put_Completed(Callback(this, &QWinRTMessageDialogHelper::onCompleted).Get()); + RETURN_FALSE_IF_FAILED("Failed to set dialog callback"); + + d->shown = true; + hr = op.As(&d->info); + RETURN_FALSE_IF_FAILED("Failed to acquire AsyncInfo for MessageDialog"); + + return true; +} + +void QWinRTMessageDialogHelper::hide() +{ + Q_D(QWinRTMessageDialogHelper); + + if (!d->shown) + return; + + HRESULT hr = d->info->Cancel(); + if (FAILED(hr)) + qErrnoWarning(hr, "Failed to cancel dialog operation"); + + d->shown = false; +} + +HRESULT QWinRTMessageDialogHelper::onCompleted(IAsyncOperation *asyncInfo, AsyncStatus status) +{ + Q_UNUSED(status); + Q_D(QWinRTMessageDialogHelper); + + if (d->loop.isRunning()) + d->loop.exit(); + + d->shown = false; + + if (status == Canceled) { + emit reject(); + return S_OK; + } + + HRESULT hr; + ComPtr command; + hr = asyncInfo->GetResults(&command); + RETURN_OK_IF_FAILED("Failed to get command"); + + ComPtr id; + hr = command->get_Id(&id); + RETURN_OK_IF_FAILED("Failed to get command ID"); + + ButtonRole role = buttonRole(id->button); + emit clicked(id->button, role); + return S_OK; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.h b/src/plugins/platforms/winrt/qwinrtmessagedialoghelper.h similarity index 75% rename from src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.h rename to src/plugins/platforms/winrt/qwinrtmessagedialoghelper.h index fbb21ed69c..25199e569c 100644 --- a/src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.h +++ b/src/plugins/platforms/winrt/qwinrtmessagedialoghelper.h @@ -39,11 +39,10 @@ ** ****************************************************************************/ -#ifndef QWINRTPLATFORMMESSAGEDIALOGHELPER_H -#define QWINRTPLATFORMMESSAGEDIALOGHELPER_H +#ifndef QWINRTMESSAGEDIALOGHELPER_H +#define QWINRTMESSAGEDIALOGHELPER_H #include -#include #include namespace ABI { @@ -53,19 +52,24 @@ namespace ABI { struct IUICommand; } } + namespace Foundation { + enum class AsyncStatus; + template struct IAsyncOperation; + } } } QT_BEGIN_NAMESPACE -struct QWinRTPlatformMessageDialogInfo; +class QWinRTTheme; -class QWinRTPlatformMessageDialogHelper : public QPlatformMessageDialogHelper +class QWinRTMessageDialogHelperPrivate; +class QWinRTMessageDialogHelper : public QPlatformMessageDialogHelper { Q_OBJECT public: - explicit QWinRTPlatformMessageDialogHelper(); - ~QWinRTPlatformMessageDialogHelper(); + explicit QWinRTMessageDialogHelper(const QWinRTTheme *theme); + ~QWinRTMessageDialogHelper(); void exec(); bool show(Qt::WindowFlags windowFlags, @@ -73,13 +77,14 @@ public: QWindow *parent); void hide(); - HRESULT onInvoked(ABI::Windows::UI::Popups::IUICommand *command); private: - QWinRTPlatformMessageDialogInfo *m_info; - QEventLoop m_loop; - bool m_shown; + HRESULT onCompleted(ABI::Windows::Foundation::IAsyncOperation *asyncInfo, + ABI::Windows::Foundation::AsyncStatus status); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTMessageDialogHelper) }; QT_END_NAMESPACE -#endif // QWINRTPLATFORMMESSAGEDIALOGHELPER_H +#endif // QWINRTMESSAGEDIALOGHELPER_H diff --git a/src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.cpp b/src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.cpp deleted file mode 100644 index c2f884055d..0000000000 --- a/src/plugins/platforms/winrt/qwinrtplatformmessagedialoghelper.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/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 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qwinrtplatformmessagedialoghelper.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace ABI::Windows::Foundation; -using namespace ABI::Windows::Foundation::Collections; -using namespace ABI::Windows::UI::Popups; -using namespace Microsoft::WRL; -using namespace Microsoft::WRL::Wrappers; - -QT_BEGIN_NAMESPACE - -struct QWinRTPlatformMessageDialogInfo -{ - ComPtr info; -}; - -QWinRTPlatformMessageDialogHelper::QWinRTPlatformMessageDialogHelper() : - QPlatformMessageDialogHelper(), - m_info(new QWinRTPlatformMessageDialogInfo), - m_shown(false) -{ -} - -QWinRTPlatformMessageDialogHelper::~QWinRTPlatformMessageDialogHelper() -{ - if (m_shown) - hide(); - delete m_info; -} - -void QWinRTPlatformMessageDialogHelper::exec() -{ - if (!m_shown) - show(Qt::Dialog, Qt::ApplicationModal, 0); - m_loop.exec(); -} - -bool QWinRTPlatformMessageDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) -{ - Q_UNUSED(windowFlags) - Q_UNUSED(windowModality) - Q_UNUSED(parent) - - QSharedPointer options = this->options(); - - const QString informativeText = options->informativeText(); - const QString title = options->windowTitle(); - const QString text = informativeText.isEmpty() ? options->text() : (options->text() + QLatin1Char('\n') + informativeText); - - - ComPtr dialogFactory; - if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Popups_MessageDialog).Get(), &dialogFactory))) - return false; - - ComPtr commandFactory; - if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Popups_UICommand).Get(), &commandFactory))) - return false; - - HString nativeText; - nativeText.Set(reinterpret_cast(text.utf16()), text.size()); - ComPtr dialog; - - if (!title.isEmpty()) { - HString nativeTitle; - nativeTitle.Set(reinterpret_cast(title.utf16()), title.size()); - if (FAILED(dialogFactory->CreateWithTitle(nativeText.Get(), nativeTitle.Get(), &dialog))) - return false; - } else { - if (FAILED(dialogFactory->Create(nativeText.Get(), &dialog))) - return false; - } - - // Add Buttons - ComPtr > dialogCommands; - if (FAILED(dialog->get_Commands(&dialogCommands))) - return false; - - // If no button is specified we need to create one to get close notification - int buttons = options->standardButtons(); - if (buttons == 0) - buttons = QPlatformDialogHelper::Ok; - - for (int i = QPlatformDialogHelper::FirstButton; i < QPlatformDialogHelper::LastButton; i<<=1) { - if (buttons & i) { - // Add native command - const QString label = QGuiApplicationPrivate::platformTheme()->standardButtonText(i); - - HString hLabel; - hLabel.Set(reinterpret_cast(label.utf16()), label.size()); - - ABI::Windows::UI::Popups::IUICommand *command; - if (FAILED(commandFactory->CreateWithHandler(hLabel.Get(), - Callback(this, &QWinRTPlatformMessageDialogHelper::onInvoked).Get(), - &command))) - return false; - dialogCommands->Append(command); - } - } - - ComPtr > op; - if (FAILED(dialog->ShowAsync(&op))) - return false; - - m_shown = true; - if (FAILED(op.As(&m_info->info))) { - m_shown = false; - // The dialog is shown already, so we cannot return false - qWarning("Failed to acquire AsyncInfo for MessageDialog"); - } - return true; -} - -void QWinRTPlatformMessageDialogHelper::hide() -{ - if (!m_shown) - return; - - m_info->info->Cancel(); - m_shown = false; -} - -HRESULT QWinRTPlatformMessageDialogHelper::onInvoked(ABI::Windows::UI::Popups::IUICommand *command) -{ - HString hLabel; - UINT32 labelLength; - command->get_Label(hLabel.GetAddressOf()); - PCWSTR rawString = hLabel.GetRawBuffer(&labelLength); - QString label = QString::fromWCharArray(rawString, labelLength); - int buttonId = -1; - for (int i = QPlatformDialogHelper::FirstButton; i < QPlatformDialogHelper::LastButton; i<<=1) { - if ( options()->standardButtons() & i ) { - if (QGuiApplicationPrivate::platformTheme()->standardButtonText(i) == label) { - buttonId = i; - break; - } - } - } - if (m_loop.isRunning()) - m_loop.exit(); - - m_shown = false; - - if (buttonId < 0) { - emit reject(); - return S_OK; - } - - QPlatformDialogHelper::StandardButton standardButton = static_cast(buttonId); - QPlatformDialogHelper::ButtonRole role = QPlatformDialogHelper::buttonRole(standardButton); - emit clicked(standardButton, role); - return S_OK; -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/winrt/qwinrttheme.cpp b/src/plugins/platforms/winrt/qwinrttheme.cpp index 65ed9440b2..f9c2e21676 100644 --- a/src/plugins/platforms/winrt/qwinrttheme.cpp +++ b/src/plugins/platforms/winrt/qwinrttheme.cpp @@ -40,7 +40,7 @@ ****************************************************************************/ #include "qwinrttheme.h" -#include "qwinrtplatformmessagedialoghelper.h" +#include "qwinrtmessagedialoghelper.h" #include #include @@ -131,8 +131,11 @@ QWinRTTheme::QWinRTTheme() bool QWinRTTheme::usePlatformNativeDialog(DialogType type) const { + static bool useNativeDialogs = qEnvironmentVariableIsSet("QT_USE_WINRT_NATIVE_DIALOGS") + ? qgetenv("QT_USE_WINRT_NATIVE_DIALOGS").toInt() : true; + if (type == MessageDialog) - return true; + return useNativeDialogs; return false; } @@ -140,7 +143,7 @@ QPlatformDialogHelper *QWinRTTheme::createPlatformDialogHelper(DialogType type) { switch (type) { case MessageDialog: - return new QWinRTPlatformMessageDialogHelper; + return new QWinRTMessageDialogHelper(this); default: break; } diff --git a/src/plugins/platforms/winrt/winrt.pro b/src/plugins/platforms/winrt/winrt.pro index 5460a7b18c..427920a670 100644 --- a/src/plugins/platforms/winrt/winrt.pro +++ b/src/plugins/platforms/winrt/winrt.pro @@ -35,12 +35,13 @@ SOURCES = \ qwinrtfontdatabase.cpp \ qwinrtinputcontext.cpp \ qwinrtintegration.cpp \ - qwinrtplatformmessagedialoghelper.cpp \ + qwinrtmessagedialoghelper.cpp \ qwinrtscreen.cpp \ qwinrtservices.cpp \ qwinrttheme.cpp \ qwinrtwindow.cpp + HEADERS = \ qwinrtbackingstore.h \ qwinrtcursor.h \ @@ -49,12 +50,13 @@ HEADERS = \ qwinrtfontdatabase.h \ qwinrtinputcontext.h \ qwinrtintegration.h \ - qwinrtplatformmessagedialoghelper.h \ + qwinrtmessagedialoghelper.h \ qwinrtscreen.h \ qwinrtservices.h \ qwinrttheme.h \ qwinrtwindow.h + BLIT_INPUT = $$PWD/blit.hlsl fxc_blitps.commands = fxc.exe /nologo /T ps_4_0_level_9_1 /E blitps /Vn q_blitps /Fh ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME} fxc_blitps.output = $$OUT_PWD/blitps.h