winrt: Add support for native pickers for Windows Phone.

Support picking file(s), folder and save file via native component on
Windows Phone.

Task-Id: QTBUG-44833
Change-Id: Ibffc1cf3c133c9fbf9c92e0a4f874c4e197c50ee
Reviewed-by: Oliver Wolff <oliver.wolff@theqtcompany.com>
bb10
Samuel Nevala 2015-08-28 13:32:55 +03:00
parent 5953109a04
commit 296422fc63
3 changed files with 280 additions and 50 deletions

View File

@ -37,25 +37,32 @@
#include "qwinrtfiledialoghelper.h"
#include "qwinrtfileengine.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/QEventLoop>
#include <QtCore/QMap>
#include <QtCore/QVector>
#include <QtCore/qfunctions_winrt.h>
#include <private/qeventdispatcher_winrt_p.h>
#include <functional>
#include <wrl.h>
#include <windows.foundation.h>
#include <windows.storage.pickers.h>
#include <Windows.ApplicationModel.activation.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::ApplicationModel::Activation;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Storage::Pickers;
#ifndef Q_OS_WINPHONE
typedef IAsyncOperationCompletedHandler<StorageFile *> SingleFileHandler;
typedef IAsyncOperationCompletedHandler<IVectorView<StorageFile *> *> MultipleFileHandler;
typedef IAsyncOperationCompletedHandler<StorageFolder *> SingleFolderHandler;
#endif
QT_BEGIN_NAMESPACE
@ -142,6 +149,16 @@ private:
QVector<HSTRING> impl;
};
#ifdef Q_OS_WINPHONE
class QActivationEvent : public QEvent
{
public:
IInspectable *args() const {
return reinterpret_cast<IInspectable *>(d);
}
};
#endif
template<typename T>
static bool initializePicker(HSTRING runtimeId, T **picker, const QSharedPointer<QFileDialogOptions> &options)
{
@ -200,6 +217,99 @@ static bool initializeOpenPickerOptions(T *picker, const QSharedPointer<QFileDia
return true;
}
static bool pickFiles(IFileOpenPicker *picker, QWinRTFileDialogHelper *helper, bool singleFile)
{
Q_ASSERT(picker);
Q_ASSERT(helper);
HRESULT hr;
#ifdef Q_OS_WINPHONE
hr = QEventDispatcherWinRT::runOnXamlThread([picker, singleFile]() {
HRESULT hr;
ComPtr<IFileOpenPicker2> picker2;
hr = picker->QueryInterface(IID_PPV_ARGS(picker2.GetAddressOf()));
RETURN_HR_IF_FAILED("Failed to cast file picker");
if (singleFile)
return picker2->PickSingleFileAndContinue();
else
return picker2->PickMultipleFilesAndContinue();
});
RETURN_FALSE_IF_FAILED("Failed to open file picker");
QAbstractEventDispatcher *eventDispatcher = QCoreApplication::eventDispatcher();
Q_ASSERT(eventDispatcher);
eventDispatcher->installEventFilter(helper);
return true;
#else
if (singleFile) {
ComPtr<IAsyncOperation<StorageFile *>> op;
hr = picker->PickSingleFileAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open single file picker");
hr = op->put_Completed(Callback<SingleFileHandler>(helper, &QWinRTFileDialogHelper::onSingleFilePicked).Get());
RETURN_FALSE_IF_FAILED("Failed to attach file picker callback");
} else {
ComPtr<IAsyncOperation<IVectorView<StorageFile *> *>> op;
hr = picker->PickMultipleFilesAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open multi file picker");
hr = op->put_Completed(Callback<MultipleFileHandler>(helper, &QWinRTFileDialogHelper::onMultipleFilesPicked).Get());
RETURN_FALSE_IF_FAILED("Failed to attach multi file callback");
}
return true;
#endif
}
static bool pickFolder(IFolderPicker *picker, QWinRTFileDialogHelper *helper)
{
Q_ASSERT(picker);
Q_ASSERT(helper);
HRESULT hr;
#ifdef Q_OS_WINPHONE
hr = QEventDispatcherWinRT::runOnXamlThread([picker]() {
HRESULT hr;
ComPtr<IFolderPicker2> picker2;
hr = picker->QueryInterface(IID_PPV_ARGS(picker2.GetAddressOf()));
RETURN_HR_IF_FAILED("Failed to cast folder picker");
return picker2->PickFolderAndContinue();
});
RETURN_FALSE_IF_FAILED("Failed to open folder picker");
QAbstractEventDispatcher *eventDispatcher = QCoreApplication::eventDispatcher();
Q_ASSERT(eventDispatcher);
eventDispatcher->installEventFilter(helper);
#else
ComPtr<IAsyncOperation<StorageFolder *>> op;
hr = picker->PickSingleFolderAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open folder picker");
hr = op->put_Completed(Callback<SingleFolderHandler>(helper, &QWinRTFileDialogHelper::onSingleFolderPicked).Get());
RETURN_FALSE_IF_FAILED("Failed to attach folder picker callback");
#endif
return true;
}
static bool pickSaveFile(IFileSavePicker *picker, QWinRTFileDialogHelper *helper)
{
Q_ASSERT(picker);
Q_ASSERT(helper);
HRESULT hr;
#ifdef Q_OS_WINPHONE
hr = QEventDispatcherWinRT::runOnXamlThread([picker]() {
HRESULT hr;
ComPtr<IFileSavePicker2> picker2;
hr = picker->QueryInterface(IID_PPV_ARGS(picker2.GetAddressOf()));
RETURN_HR_IF_FAILED("Failed to cast save file picker");
return picker2->PickSaveFileAndContinue();
});
RETURN_FALSE_IF_FAILED("Failed to open single file picker");
QAbstractEventDispatcher *eventDispatcher = QCoreApplication::eventDispatcher();
Q_ASSERT(eventDispatcher);
eventDispatcher->installEventFilter(helper);
#else
ComPtr<IAsyncOperation<StorageFile *>> op;
hr = picker->PickSaveFileAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open save file picker");
hr = op->put_Completed(Callback<SingleFileHandler>(helper, &QWinRTFileDialogHelper::onSingleFilePicked).Get());
RETURN_FALSE_IF_FAILED("Failed to attach save file picker callback");
#endif
return true;
}
class QWinRTFileDialogHelperPrivate
{
public:
@ -260,18 +370,9 @@ bool QWinRTFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModalit
if (!initializeOpenPickerOptions(picker.Get(), dialogOptions))
return false;
if (dialogOptions->fileMode() == QFileDialogOptions::ExistingFiles) {
ComPtr<IAsyncOperation<IVectorView<StorageFile *> *>> op;
hr = picker->PickMultipleFilesAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open multi file picker");
hr = op->put_Completed(Callback<MultipleFileHandler>(this, &QWinRTFileDialogHelper::onMultipleFilesPicked).Get());
} else {
ComPtr<IAsyncOperation<StorageFile *>> op;
hr = picker->PickSingleFileAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open single file picker");
hr = op->put_Completed(Callback<SingleFileHandler>(this, &QWinRTFileDialogHelper::onSingleFilePicked).Get());
}
RETURN_FALSE_IF_FAILED("Failed to attach file picker callback");
if (!pickFiles(picker.Get(), this, dialogOptions->fileMode() == QFileDialogOptions::ExistingFile))
return false;
break;
}
case QFileDialogOptions::Directory:
@ -284,11 +385,9 @@ bool QWinRTFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModalit
if (!initializeOpenPickerOptions(picker.Get(), dialogOptions))
return false;
ComPtr<IAsyncOperation<StorageFolder *>> op;
hr = picker->PickSingleFolderAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open folder picker");
hr = op->put_Completed(Callback<SingleFolderHandler>(this, &QWinRTFileDialogHelper::onSingleFolderPicked).Get());
RETURN_FALSE_IF_FAILED("Failed to attach folder picker callback");
if (!pickFolder(picker.Get(), this))
return false;
break;
}
}
@ -344,11 +443,9 @@ bool QWinRTFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModalit
RETURN_FALSE_IF_FAILED("Failed to set suggested file name");
}
ComPtr<IAsyncOperation<StorageFile *>> op;
hr = picker->PickSaveFileAsync(&op);
RETURN_FALSE_IF_FAILED("Failed to open save file picker");
hr = op->put_Completed(Callback<SingleFileHandler>(this, &QWinRTFileDialogHelper::onSingleFilePicked).Get());
RETURN_FALSE_IF_FAILED("Failed to attach file picker callback");
if (!pickSaveFile(picker.Get(), this))
return false;
break;
}
}
@ -367,6 +464,68 @@ void QWinRTFileDialogHelper::hide()
d->shown = false;
}
#ifdef Q_OS_WINPHONE
bool QWinRTFileDialogHelper::eventFilter(QObject *, QEvent *e)
{
if (e->type() != QEvent::WinEventAct)
return false;
HRESULT hr;
QActivationEvent *event = static_cast<QActivationEvent *>(e);
ComPtr<IInspectable> inspectable = event->args();
ComPtr<IActivatedEventArgs> arguments;
hr = inspectable.As(&arguments);
Q_ASSERT_SUCCEEDED(hr);
ActivationKind activationKind;
hr = arguments->get_Kind(&activationKind);
Q_ASSERT_SUCCEEDED(hr);
// Handle only File, Folder and Save file pick continuation here.
if (activationKind != ActivationKind_PickFileContinuation
&& activationKind != ActivationKind_PickFolderContinuation
&& activationKind != ActivationKind_PickSaveFileContinuation) {
return false;
}
QAbstractEventDispatcher *eventDispatcher = QCoreApplication::eventDispatcher();
Q_ASSERT(eventDispatcher);
eventDispatcher->removeEventFilter(this);
e->accept();
if (activationKind == ActivationKind_PickFileContinuation) {
ComPtr<IFileOpenPickerContinuationEventArgs> fileContinuationArgs;
hr = arguments.As(&fileContinuationArgs);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IVectorView<StorageFile *>> files;
hr = fileContinuationArgs->get_Files(&files);
Q_ASSERT_SUCCEEDED(hr);
hr = onFilesPicked(files.Get());
Q_ASSERT_SUCCEEDED(hr);
} else if (activationKind == ActivationKind_PickFolderContinuation) {
ComPtr<IFolderPickerContinuationEventArgs> folderContinuationArgs;
hr = arguments.As(&folderContinuationArgs);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IStorageFolder> folder;
hr = folderContinuationArgs->get_Folder(&folder);
Q_ASSERT_SUCCEEDED(hr);
hr = onFolderPicked(folder.Get());
Q_ASSERT_SUCCEEDED(hr);
} else {
ComPtr<IFileSavePickerContinuationEventArgs> saveFileContinuationArgs;
hr = arguments.As(&saveFileContinuationArgs);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IStorageFile> file;
hr = saveFileContinuationArgs->get_File(&file);
Q_ASSERT_SUCCEEDED(hr);
hr = onFilePicked(file.Get());
Q_ASSERT_SUCCEEDED(hr);
}
return true;
}
#endif
void QWinRTFileDialogHelper::setDirectory(const QUrl &directory)
{
Q_D(QWinRTFileDialogHelper);
@ -403,6 +562,7 @@ QString QWinRTFileDialogHelper::selectedNameFilter() const
return d->selectedNameFilter;
}
#ifndef Q_OS_WINPHONE
HRESULT QWinRTFileDialogHelper::onSingleFilePicked(IAsyncOperation<StorageFile *> *args, AsyncStatus status)
{
Q_D(QWinRTFileDialogHelper);
@ -419,14 +579,7 @@ HRESULT QWinRTFileDialogHelper::onSingleFilePicked(IAsyncOperation<StorageFile *
ComPtr<IStorageFile> file;
hr = args->GetResults(&file);
Q_ASSERT_SUCCEEDED(hr);
if (!file) {
emit reject();
return S_OK;
}
appendFile(file.Get());
emit accept();
return S_OK;
return onFilePicked(file.Get());
}
HRESULT QWinRTFileDialogHelper::onMultipleFilesPicked(IAsyncOperation<IVectorView<StorageFile *> *> *args, AsyncStatus status)
@ -445,23 +598,7 @@ HRESULT QWinRTFileDialogHelper::onMultipleFilesPicked(IAsyncOperation<IVectorVie
ComPtr<IVectorView<StorageFile *>> fileList;
hr = args->GetResults(&fileList);
RETURN_HR_IF_FAILED("Failed to get file list");
quint32 size;
hr = fileList->get_Size(&size);
Q_ASSERT_SUCCEEDED(hr);
if (!size) {
emit reject();
return S_OK;
}
for (quint32 i = 0; i < size; ++i) {
ComPtr<IStorageFile> file;
hr = fileList->GetAt(i, &file);
Q_ASSERT_SUCCEEDED(hr);
appendFile(file.Get());
}
emit accept();
return S_OK;
return onFilesPicked(fileList.Get());
}
HRESULT QWinRTFileDialogHelper::onSingleFolderPicked(IAsyncOperation<StorageFolder *> *args, AsyncStatus status)
@ -480,12 +617,73 @@ HRESULT QWinRTFileDialogHelper::onSingleFolderPicked(IAsyncOperation<StorageFold
ComPtr<IStorageFolder> folder;
hr = args->GetResults(&folder);
Q_ASSERT_SUCCEEDED(hr);
return onFolderPicked(folder.Get());
}
#endif //Q_OS_WINPHONE
HRESULT QWinRTFileDialogHelper::onFilesPicked(IVectorView<StorageFile *> *files)
{
#ifdef Q_OS_WINPHONE
Q_D(QWinRTFileDialogHelper);
QEventLoopLocker locker(&d->loop);
d->shown = false;
d->selectedFiles.clear();
#endif
HRESULT hr;
quint32 size;
hr = files->get_Size(&size);
Q_ASSERT_SUCCEEDED(hr);
if (!size) {
emit reject();
return S_OK;
}
for (quint32 i = 0; i < size; ++i) {
ComPtr<IStorageFile> file;
hr = files->GetAt(i, &file);
Q_ASSERT_SUCCEEDED(hr);
appendFile(file.Get());
}
emit accept();
return S_OK;
}
HRESULT QWinRTFileDialogHelper::onFolderPicked(IStorageFolder *folder)
{
#ifdef Q_OS_WINPHONE
Q_D(QWinRTFileDialogHelper);
QEventLoopLocker locker(&d->loop);
d->shown = false;
d->selectedFiles.clear();
#endif
if (!folder) {
emit reject();
return S_OK;
}
appendFile(folder.Get());
appendFile(folder);
emit accept();
return S_OK;
}
HRESULT QWinRTFileDialogHelper::onFilePicked(IStorageFile *file)
{
#ifdef Q_OS_WINPHONE
Q_D(QWinRTFileDialogHelper);
QEventLoopLocker locker(&d->loop);
d->shown = false;
d->selectedFiles.clear();
#endif
if (!file) {
emit reject();
return S_OK;
}
appendFile(file);
emit accept();
return S_OK;
}

View File

@ -47,6 +47,7 @@ namespace ABI {
class StorageFile;
class StorageFolder;
struct IStorageFile;
struct IStorageFolder;
}
namespace Foundation {
enum class AsyncStatus;
@ -71,6 +72,9 @@ public:
void exec() Q_DECL_OVERRIDE;
bool show(Qt::WindowFlags, Qt::WindowModality, QWindow *) Q_DECL_OVERRIDE;
void hide() Q_DECL_OVERRIDE;
#ifdef Q_OS_WINPHONE
bool eventFilter(QObject *o, QEvent *e) Q_DECL_OVERRIDE;
#endif
bool defaultNameFilterDisables() const Q_DECL_OVERRIDE { return false; }
void setDirectory(const QUrl &directory) Q_DECL_OVERRIDE;
@ -81,13 +85,19 @@ public:
void selectNameFilter(const QString &selectedNameFilter) Q_DECL_OVERRIDE;
QString selectedNameFilter() const;
private:
#ifndef Q_OS_WINPHONE
HRESULT onSingleFilePicked(ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile *> *,
ABI::Windows::Foundation::AsyncStatus);
HRESULT onMultipleFilesPicked(ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Foundation::Collections::IVectorView<ABI::Windows::Storage::StorageFile *> *> *,
ABI::Windows::Foundation::AsyncStatus);
HRESULT onSingleFolderPicked(ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFolder *> *,
ABI::Windows::Foundation::AsyncStatus);
#endif
private:
HRESULT onFilesPicked(ABI::Windows::Foundation::Collections::IVectorView<ABI::Windows::Storage::StorageFile *> *files);
HRESULT onFolderPicked(ABI::Windows::Storage::IStorageFolder *folder);
HRESULT onFilePicked(ABI::Windows::Storage::IStorageFile *file);
void appendFile(IInspectable *);
QScopedPointer<QWinRTFileDialogHelperPrivate> d_ptr;

View File

@ -70,6 +70,7 @@ extern "C" {
#include <qdir.h>
#include <qstandardpaths.h>
#include <qfunctions_winrt.h>
#include <qcoreapplication.h>
#include <wrl.h>
#include <Windows.ApplicationModel.core.h>
@ -109,6 +110,24 @@ static void devMessageHandler(QtMsgType type, const QMessageLogContext &context,
defaultMessageHandler(type, context, message);
}
class QActivationEvent : public QEvent
{
public:
explicit QActivationEvent(IInspectable *args)
: QEvent(QEvent::WinEventAct)
{
setAccepted(false);
args->AddRef();
d = reinterpret_cast<QEventPrivate *>(args);
}
~QActivationEvent() {
IUnknown *args = reinterpret_cast<IUnknown *>(d);
args->Release();
d = nullptr;
}
};
class AppContainer : public RuntimeClass<Xaml::IApplicationOverrides>
{
public:
@ -158,7 +177,10 @@ public:
private:
HRESULT __stdcall OnActivated(IActivatedEventArgs *args) Q_DECL_OVERRIDE
{
return base->OnActivated(args);
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
if (dispatcher)
QCoreApplication::postEvent(dispatcher, new QActivationEvent(args));
return S_OK;
}
HRESULT __stdcall OnLaunched(ILaunchActivatedEventArgs *launchArgs) Q_DECL_OVERRIDE