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 <maurice.kalinowski@digia.com>
This commit is contained in:
Andrew Knight 2014-07-04 15:24:41 +03:00
parent fff0844735
commit 53ed4de022
5 changed files with 259 additions and 221 deletions

View File

@ -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 <QtCore/qfunctions_winrt.h>
#include <windows.ui.popups.h>
#include <windows.foundation.h>
#include <windows.foundation.collections.h>
#include <wrl.h>
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<IUICommand *> DialogCompletedHandler;
QT_BEGIN_NAMESPACE
class CommandId : public RuntimeClass<IInspectable>
{
public:
CommandId(QPlatformDialogHelper::StandardButton button)
: button(button) { }
QPlatformDialogHelper::StandardButton button;
};
class QWinRTMessageDialogHelperPrivate
{
public:
const QWinRTTheme *theme;
bool shown;
ComPtr<IAsyncInfo> 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<QMessageDialogOptions> 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<IMessageDialogFactory> 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<IUICommandFactory> 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<IMessageDialog> dialog;
HStringReference nativeText(reinterpret_cast<LPCWSTR>(text.utf16()), text.size());
if (!title.isEmpty()) {
HStringReference nativeTitle(reinterpret_cast<LPCWSTR>(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<IVector<IUICommand *>> 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<LPCWSTR>(label.utf16()), label.size());
ComPtr<IUICommand> command;
hr = commandFactory->Create(nativeLabel.Get(), &command);
RETURN_FALSE_IF_FAILED("Failed to create message box command");
ComPtr<IInspectable> id = Make<CommandId>(static_cast<StandardButton>(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<IAsyncOperation<IUICommand *>> op;
hr = dialog->ShowAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to show dialog");
hr = op->put_Completed(Callback<DialogCompletedHandler>(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<IUICommand *> *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<IUICommand> command;
hr = asyncInfo->GetResults(&command);
RETURN_OK_IF_FAILED("Failed to get command");
ComPtr<CommandId> 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

View File

@ -39,11 +39,10 @@
** **
****************************************************************************/ ****************************************************************************/
#ifndef QWINRTPLATFORMMESSAGEDIALOGHELPER_H #ifndef QWINRTMESSAGEDIALOGHELPER_H
#define QWINRTPLATFORMMESSAGEDIALOGHELPER_H #define QWINRTMESSAGEDIALOGHELPER_H
#include <qpa/qplatformdialoghelper.h> #include <qpa/qplatformdialoghelper.h>
#include <QtCore/QEventLoop>
#include <QtCore/qt_windows.h> #include <QtCore/qt_windows.h>
namespace ABI { namespace ABI {
@ -53,19 +52,24 @@ namespace ABI {
struct IUICommand; struct IUICommand;
} }
} }
namespace Foundation {
enum class AsyncStatus;
template <typename T> struct IAsyncOperation;
}
} }
} }
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
struct QWinRTPlatformMessageDialogInfo; class QWinRTTheme;
class QWinRTPlatformMessageDialogHelper : public QPlatformMessageDialogHelper class QWinRTMessageDialogHelperPrivate;
class QWinRTMessageDialogHelper : public QPlatformMessageDialogHelper
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit QWinRTPlatformMessageDialogHelper(); explicit QWinRTMessageDialogHelper(const QWinRTTheme *theme);
~QWinRTPlatformMessageDialogHelper(); ~QWinRTMessageDialogHelper();
void exec(); void exec();
bool show(Qt::WindowFlags windowFlags, bool show(Qt::WindowFlags windowFlags,
@ -73,13 +77,14 @@ public:
QWindow *parent); QWindow *parent);
void hide(); void hide();
HRESULT onInvoked(ABI::Windows::UI::Popups::IUICommand *command);
private: private:
QWinRTPlatformMessageDialogInfo *m_info; HRESULT onCompleted(ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::UI::Popups::IUICommand *> *asyncInfo,
QEventLoop m_loop; ABI::Windows::Foundation::AsyncStatus status);
bool m_shown;
QScopedPointer<QWinRTMessageDialogHelperPrivate> d_ptr;
Q_DECLARE_PRIVATE(QWinRTMessageDialogHelper)
}; };
QT_END_NAMESPACE QT_END_NAMESPACE
#endif // QWINRTPLATFORMMESSAGEDIALOGHELPER_H #endif // QWINRTMESSAGEDIALOGHELPER_H

View File

@ -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 <QtGui/QGuiApplication>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformtheme.h>
#include <asyncinfo.h>
#include <windows.ui.popups.h>
#include <windows.foundation.h>
#include <windows.foundation.collections.h>
#include <wrl.h>
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<IAsyncInfo> 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<QMessageDialogOptions> 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<IMessageDialogFactory> dialogFactory;
if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Popups_MessageDialog).Get(), &dialogFactory)))
return false;
ComPtr<IUICommandFactory> commandFactory;
if (FAILED(GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Popups_UICommand).Get(), &commandFactory)))
return false;
HString nativeText;
nativeText.Set(reinterpret_cast<LPCWSTR>(text.utf16()), text.size());
ComPtr<IMessageDialog> dialog;
if (!title.isEmpty()) {
HString nativeTitle;
nativeTitle.Set(reinterpret_cast<LPCWSTR>(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<IVector<IUICommand*> > 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<LPCWSTR>(label.utf16()), label.size());
ABI::Windows::UI::Popups::IUICommand *command;
if (FAILED(commandFactory->CreateWithHandler(hLabel.Get(),
Callback<IUICommandInvokedHandler>(this, &QWinRTPlatformMessageDialogHelper::onInvoked).Get(),
&command)))
return false;
dialogCommands->Append(command);
}
}
ComPtr<IAsyncOperation<IUICommand*> > 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<QPlatformDialogHelper::StandardButton>(buttonId);
QPlatformDialogHelper::ButtonRole role = QPlatformDialogHelper::buttonRole(standardButton);
emit clicked(standardButton, role);
return S_OK;
}
QT_END_NAMESPACE

View File

@ -40,7 +40,7 @@
****************************************************************************/ ****************************************************************************/
#include "qwinrttheme.h" #include "qwinrttheme.h"
#include "qwinrtplatformmessagedialoghelper.h" #include "qwinrtmessagedialoghelper.h"
#include <QtCore/qfunctions_winrt.h> #include <QtCore/qfunctions_winrt.h>
#include <QtGui/QPalette> #include <QtGui/QPalette>
@ -131,8 +131,11 @@ QWinRTTheme::QWinRTTheme()
bool QWinRTTheme::usePlatformNativeDialog(DialogType type) const 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) if (type == MessageDialog)
return true; return useNativeDialogs;
return false; return false;
} }
@ -140,7 +143,7 @@ QPlatformDialogHelper *QWinRTTheme::createPlatformDialogHelper(DialogType type)
{ {
switch (type) { switch (type) {
case MessageDialog: case MessageDialog:
return new QWinRTPlatformMessageDialogHelper; return new QWinRTMessageDialogHelper(this);
default: default:
break; break;
} }

View File

@ -35,12 +35,13 @@ SOURCES = \
qwinrtfontdatabase.cpp \ qwinrtfontdatabase.cpp \
qwinrtinputcontext.cpp \ qwinrtinputcontext.cpp \
qwinrtintegration.cpp \ qwinrtintegration.cpp \
qwinrtplatformmessagedialoghelper.cpp \ qwinrtmessagedialoghelper.cpp \
qwinrtscreen.cpp \ qwinrtscreen.cpp \
qwinrtservices.cpp \ qwinrtservices.cpp \
qwinrttheme.cpp \ qwinrttheme.cpp \
qwinrtwindow.cpp qwinrtwindow.cpp
HEADERS = \ HEADERS = \
qwinrtbackingstore.h \ qwinrtbackingstore.h \
qwinrtcursor.h \ qwinrtcursor.h \
@ -49,12 +50,13 @@ HEADERS = \
qwinrtfontdatabase.h \ qwinrtfontdatabase.h \
qwinrtinputcontext.h \ qwinrtinputcontext.h \
qwinrtintegration.h \ qwinrtintegration.h \
qwinrtplatformmessagedialoghelper.h \ qwinrtmessagedialoghelper.h \
qwinrtscreen.h \ qwinrtscreen.h \
qwinrtservices.h \ qwinrtservices.h \
qwinrttheme.h \ qwinrttheme.h \
qwinrtwindow.h qwinrtwindow.h
BLIT_INPUT = $$PWD/blit.hlsl 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.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 fxc_blitps.output = $$OUT_PWD/blitps.h