Windows: Support virtual folders as initial directory for file dialog.

Use the scheme "clsid" to be able to pass them as "clsid:<GUID>"
(with '{', '}' stripped).

Task-number: QTBUG-33962
Change-Id: Ib045fe81518bca6e91588007ce8a245a48479b1f
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@theqtcompany.com>
Reviewed-by: Patrick Spendrin <patrick.spendrin@kdab.com>
Reviewed-by: Oliver Wolff <oliver.wolff@theqtcompany.com>
Reviewed-by: Björn Breitmeyer <bjoern.breitmeyer@kdab.com>
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Friedemann Kleint 2015-03-25 10:45:58 +01:00
parent 696a18b063
commit 4347e81161
4 changed files with 83 additions and 19 deletions

View File

@ -225,8 +225,10 @@ bool QWindowsUser32DLL::initTouch()
QWindowsShell32DLL::QWindowsShell32DLL()
: sHCreateItemFromParsingName(0)
, sHGetKnownFolderIDList(0)
, sHGetStockIconInfo(0)
, sHGetImageList(0)
, sHCreateItemFromIDList(0)
{
}
@ -234,8 +236,10 @@ void QWindowsShell32DLL::init()
{
QSystemLibrary library(QStringLiteral("shell32"));
sHCreateItemFromParsingName = (SHCreateItemFromParsingName)(library.resolve("SHCreateItemFromParsingName"));
sHGetKnownFolderIDList = (SHGetKnownFolderIDList)(library.resolve("SHGetKnownFolderIDList"));
sHGetStockIconInfo = (SHGetStockIconInfo)library.resolve("SHGetStockIconInfo");
sHGetImageList = (SHGetImageList)library.resolve("SHGetImageList");
sHCreateItemFromIDList = (SHCreateItemFromIDList)library.resolve("SHCreateItemFromIDList");
}
QWindowsShcoreDLL::QWindowsShcoreDLL()

View File

@ -41,6 +41,10 @@
#include <QtCore/QSharedPointer>
#include <QtCore/QLoggingCategory>
#define STRICT_TYPED_ITEMIDS
#include <shlobj.h>
#include <shlwapi.h>
struct IBindCtx;
struct _SHSTOCKICONINFO;
@ -120,12 +124,16 @@ struct QWindowsShell32DLL
inline void init();
typedef HRESULT (WINAPI *SHCreateItemFromParsingName)(PCWSTR, IBindCtx *, const GUID&, void **);
typedef HRESULT (WINAPI *SHGetKnownFolderIDList)(const GUID &, DWORD, HANDLE, PIDLIST_ABSOLUTE *);
typedef HRESULT (WINAPI *SHGetStockIconInfo)(int , int , _SHSTOCKICONINFO *);
typedef HRESULT (WINAPI *SHGetImageList)(int, REFIID , void **);
typedef HRESULT (WINAPI *SHCreateItemFromIDList)(PCIDLIST_ABSOLUTE, REFIID, void **);
SHCreateItemFromParsingName sHCreateItemFromParsingName;
SHGetKnownFolderIDList sHGetKnownFolderIDList;
SHGetStockIconInfo sHGetStockIconInfo;
SHGetImageList sHGetImageList;
SHCreateItemFromIDList sHCreateItemFromIDList;
};
// Shell scaling library (Windows 8.1 onwards)

View File

@ -58,16 +58,14 @@
#include <QtCore/QExplicitlySharedDataPointer>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <QtCore/QUuid>
#include <QtCore/QRegularExpression>
#include <QtCore/private/qsystemlibrary_p.h>
#include <algorithm>
#include "qtwindows_additional.h"
#define STRICT_TYPED_ITEMIDS
#include <shlobj.h>
#include <shlwapi.h>
// #define USE_NATIVE_COLOR_DIALOG /* Testing purposes only */
#ifdef Q_CC_MINGW /* Add missing declarations for MinGW */
@ -873,8 +871,8 @@ public:
virtual void setWindowTitle(const QString &title);
inline void setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options);
inline void setDirectory(const QString &directory);
inline void updateDirectory() { setDirectory(m_data.directory().toLocalFile()); }
inline void setDirectory(const QUrl &directory);
inline void updateDirectory() { setDirectory(m_data.directory()); }
inline QString directory() const;
virtual void doExec(HWND owner = 0);
virtual void setNameFilters(const QStringList &f);
@ -916,7 +914,7 @@ protected:
static QList<QUrl> libraryItemFolders(IShellItem *item);
static QString libraryItemDefaultSaveFolder(IShellItem *item);
static int itemPaths(IShellItemArray *items, QList<QUrl> *fileResult = 0);
static IShellItem *shellItem(const QString &path);
static IShellItem *shellItem(const QUrl &url);
const QWindowsFileDialogSharedData &data() const { return m_data; }
QWindowsFileDialogSharedData &data() { return m_data; }
@ -976,25 +974,58 @@ void QWindowsNativeFileDialogBase::setWindowTitle(const QString &title)
m_fileDialog->SetTitle(reinterpret_cast<const wchar_t *>(title.utf16()));
}
IShellItem *QWindowsNativeFileDialogBase::shellItem(const QString &path)
IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url)
{
#ifndef Q_OS_WINCE
if (QWindowsContext::shell32dll.sHCreateItemFromParsingName) {
IShellItem *result = 0;
const QString native = QDir::toNativeSeparators(path);
if (url.isLocalFile()) {
if (!QWindowsContext::shell32dll.sHCreateItemFromParsingName)
return Q_NULLPTR;
IShellItem *result = Q_NULLPTR;
const QString native = QDir::toNativeSeparators(url.toLocalFile());
const HRESULT hr =
QWindowsContext::shell32dll.sHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
NULL, IID_IShellItem,
reinterpret_cast<void **>(&result));
if (SUCCEEDED(hr))
return result;
QWindowsContext::shell32dll.sHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
NULL, IID_IShellItem,
reinterpret_cast<void **>(&result));
if (FAILED(hr)) {
qErrnoWarning("%s: SHCreateItemFromParsingName(%s)) failed", __FUNCTION__, qPrintable(url.toString()));
return Q_NULLPTR;
}
return result;
} else if (url.scheme() == QLatin1String("clsid")) {
if (!QWindowsContext::shell32dll.sHGetKnownFolderIDList || !QWindowsContext::shell32dll.sHCreateItemFromIDList)
return Q_NULLPTR;
// Support for virtual folders via GUID
// (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx)
// specified as "clsid:<GUID>" (without '{', '}').
IShellItem *result = Q_NULLPTR;
const QUuid uuid(url.path());
if (uuid.isNull()) {
qWarning() << __FUNCTION__ << ": Invalid CLSID: " << url.path();
return Q_NULLPTR;
}
PIDLIST_ABSOLUTE idList;
HRESULT hr = QWindowsContext::shell32dll.sHGetKnownFolderIDList(uuid, 0, 0, &idList);
if (FAILED(hr)) {
qErrnoWarning("%s: SHGetKnownFolderIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString()));
return Q_NULLPTR;
}
hr = QWindowsContext::shell32dll.sHCreateItemFromIDList(idList, IID_IShellItem, reinterpret_cast<void **>(&result));
CoTaskMemFree(idList);
if (FAILED(hr)) {
qErrnoWarning("%s: SHCreateItemFromIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString()));
return Q_NULLPTR;
}
return result;
} else {
qWarning() << __FUNCTION__ << ": Unhandled scheme: " << url.scheme();
}
#else // !Q_OS_WINCE
Q_UNUSED(url)
#endif
qErrnoWarning("%s: SHCreateItemFromParsingName(%s)) failed", __FUNCTION__, qPrintable(path));
return 0;
}
void QWindowsNativeFileDialogBase::setDirectory(const QString &directory)
void QWindowsNativeFileDialogBase::setDirectory(const QUrl &directory)
{
if (!directory.isEmpty()) {
if (IShellItem *psi = QWindowsNativeFileDialogBase::shellItem(directory)) {
@ -1284,9 +1315,20 @@ void QWindowsNativeFileDialogBase::setLabelText(QFileDialogOptions::DialogLabel
}
}
static inline bool isClsid(const QString &s)
{
// detect "374DE290-123F-4565-9164-39C4925E467B".
static const QRegularExpression pattern(QLatin1String("[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{8}"));
Q_ASSERT(pattern.isValid());
return pattern.match(s).hasMatch();
}
void QWindowsNativeFileDialogBase::selectFile(const QString &fileName) const
{
m_fileDialog->SetFileName((wchar_t*)fileName.utf16());
// Hack to prevent CLSIDs from being set as file name due to
// QFileDialogPrivate::initialSelection() being QString-based.
if (!isClsid(fileName))
m_fileDialog->SetFileName((wchar_t*)fileName.utf16());
}
// Return the index of the selected filter, accounting for QFileDialog

View File

@ -976,6 +976,16 @@ QDir QFileDialog::directory() const
\note The non-native QFileDialog supports only local files.
\note On Windows, it is possible to pass URLs representing
one of the \e {virtual folders}, such as "Computer" or "Network".
This is done by passing a QUrl using the scheme \c clsid followed
by the CLSID value with the curly braces removed. For example the URL
\c clsid:374DE290-123F-4565-9164-39C4925E467B denotes the download
location. For a complete list of possible values, see the MSDN documentation on
\l{https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx}{KNOWNFOLDERID}.
This feature was added in Qt 5.5.
\sa QUuid
\since 5.2
*/
void QFileDialog::setDirectoryUrl(const QUrl &directory)