From 4347e811611ab15e544765a3a34761d2f5c68cb3 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 25 Mar 2015 10:45:58 +0100 Subject: Windows: Support virtual folders as initial directory for file dialog. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the scheme "clsid" to be able to pass them as "clsid:" (with '{', '}' stripped). Task-number: QTBUG-33962 Change-Id: Ib045fe81518bca6e91588007ce8a245a48479b1f Reviewed-by: Leena Miettinen Reviewed-by: Patrick Spendrin Reviewed-by: Oliver Wolff Reviewed-by: Björn Breitmeyer Reviewed-by: Joerg Bornemann --- src/plugins/platforms/windows/qwindowscontext.cpp | 4 ++ src/plugins/platforms/windows/qwindowscontext.h | 8 +++ .../platforms/windows/qwindowsdialoghelpers.cpp | 80 +++++++++++++++++----- src/widgets/dialogs/qfiledialog.cpp | 10 +++ 4 files changed, 83 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index fa355f6201..7264fdcbb6 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -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() diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 81f4a36433..d2a3481b28 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -41,6 +41,10 @@ #include #include +#define STRICT_TYPED_ITEMIDS +#include +#include + 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) diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 3b47829402..3284795fc1 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -58,16 +58,14 @@ #include #include #include +#include +#include #include #include #include "qtwindows_additional.h" -#define STRICT_TYPED_ITEMIDS -#include -#include - // #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 libraryItemFolders(IShellItem *item); static QString libraryItemDefaultSaveFolder(IShellItem *item); static int itemPaths(IShellItemArray *items, QList *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(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(native.utf16()), - NULL, IID_IShellItem, - reinterpret_cast(&result)); - if (SUCCEEDED(hr)) - return result; + QWindowsContext::shell32dll.sHCreateItemFromParsingName(reinterpret_cast(native.utf16()), + NULL, IID_IShellItem, + reinterpret_cast(&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:" (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(&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 diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index 455111225e..6a1374e3ee 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -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) -- cgit v1.2.3