diff options
Diffstat (limited to 'src/plugins/platforms/windows/qwindowsdialoghelpers.cpp')
-rw-r--r-- | src/plugins/platforms/windows/qwindowsdialoghelpers.cpp | 198 |
1 files changed, 129 insertions, 69 deletions
diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 681b35eb7c..9de3268fc8 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -133,8 +133,8 @@ namespace QWindowsDialogs void eatMouseMove() { - MSG msg = {0, 0, 0, 0, 0, {0, 0} }; - while (PeekMessage(&msg, 0, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) + MSG msg = {nullptr, 0, 0, 0, 0, {0, 0} }; + while (PeekMessage(&msg, nullptr, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) ; if (msg.message == WM_MOUSEMOVE) PostMessage(msg.hwnd, msg.message, 0, msg.lParam); @@ -180,7 +180,7 @@ class QWindowsNativeDialogBase : public QObject public: virtual void setWindowTitle(const QString &title) = 0; bool executed() const { return m_executed; } - void exec(HWND owner = 0) { doExec(owner); m_executed = true; } + void exec(HWND owner = nullptr) { doExec(owner); m_executed = true; } signals: void accepted(); @@ -193,7 +193,7 @@ protected: QWindowsNativeDialogBase() : m_executed(false) {} private: - virtual void doExec(HWND owner = 0) = 0; + virtual void doExec(HWND owner = nullptr) = 0; bool m_executed; }; @@ -229,7 +229,7 @@ void QWindowsDialogHelperBase<BaseClass>::cleanupThread() qCWarning(lcQpaDialogs) << __FUNCTION__ << "Thread terminated."; } delete m_thread; - m_thread = 0; + m_thread = nullptr; } } @@ -238,7 +238,7 @@ QWindowsNativeDialogBase *QWindowsDialogHelperBase<BaseClass>::nativeDialog() co { if (m_nativeDialog.isNull()) { qWarning("%s invoked with no native dialog present.", __FUNCTION__); - return 0; + return nullptr; } return m_nativeDialog.data(); } @@ -300,7 +300,7 @@ bool QWindowsDialogHelperBase<BaseClass>::show(Qt::WindowFlags, if (parent) { m_ownerWindow = QWindowsWindow::handleOf(parent); } else { - m_ownerWindow = 0; + m_ownerWindow = nullptr; } qCDebug(lcQpaDialogs) << __FUNCTION__ << "modal=" << modal << " modal supported? " << supportsNonModalDialog(parent) @@ -347,7 +347,7 @@ void QWindowsDialogHelperBase<BaseClass>::stopTimer() struct FindDialogContext { explicit FindDialogContext(const QString &titleIn) - : title(qStringToWCharArray(titleIn)), processId(GetCurrentProcessId()), hwnd(0) {} + : title(qStringToWCharArray(titleIn)), processId(GetCurrentProcessId()), hwnd(nullptr) {} const QScopedArrayPointer<wchar_t> title; const DWORD processId; @@ -382,7 +382,7 @@ void QWindowsDialogHelperBase<BaseClass>::hide() { if (m_nativeDialog) m_nativeDialog->close(); - m_ownerWindow = 0; + m_ownerWindow = nullptr; } template <class BaseClass> @@ -534,7 +534,7 @@ IFileDialogEvents *QWindowsNativeFileDialogEventHandler::create(QWindowsNativeFi QWindowsNativeFileDialogEventHandler *eventHandler = new QWindowsNativeFileDialogEventHandler(nativeFileDialog); if (FAILED(eventHandler->QueryInterface(IID_IFileDialogEvents, reinterpret_cast<void **>(&result)))) { qErrnoWarning("Unable to obtain IFileDialogEvents"); - return 0; + return nullptr; } eventHandler->Release(); return result; @@ -558,6 +558,10 @@ public: SFGAOF attributes() const { return m_attributes; } QString normalDisplay() const // base name, usually { return displayName(m_item, SIGDN_NORMALDISPLAY); } + QString urlString() const + { return displayName(m_item, SIGDN_URL); } + QString fileSysPath() const + { return displayName(m_item, SIGDN_FILESYSPATH); } QString desktopAbsoluteParsing() const { return displayName(m_item, SIGDN_DESKTOPABSOLUTEPARSING); } QString path() const; // Only set for 'FileSystem' (SFGAO_FILESYSTEM) items @@ -565,12 +569,10 @@ public: bool isFileSystem() const { return (m_attributes & SFGAO_FILESYSTEM) != 0; } bool isDir() const { return (m_attributes & SFGAO_FOLDER) != 0; } - // Copy using IFileOperation - bool canCopy() const { return (m_attributes & SFGAO_CANCOPY) != 0; } // Supports IStream bool canStream() const { return (m_attributes & SFGAO_STREAM) != 0; } - bool copyData(QIODevice *out); + bool copyData(QIODevice *out, QString *errorMessage); static IShellItems itemsFromItemArray(IShellItemArray *items); @@ -662,14 +664,19 @@ QWindowsShellItem::IShellItems QWindowsShellItem::itemsFromItemArray(IShellItemA return result; } -bool QWindowsShellItem::copyData(QIODevice *out) +bool QWindowsShellItem::copyData(QIODevice *out, QString *errorMessage) { - if (!canCopy() || !canStream()) + if (!canStream()) { + *errorMessage = QLatin1String("Item not streamable"); return false; + } IStream *istream = nullptr; - HRESULT hr = m_item->BindToHandler(NULL, BHID_Stream, IID_PPV_ARGS(&istream)); - if (FAILED(hr)) + HRESULT hr = m_item->BindToHandler(nullptr, BHID_Stream, IID_PPV_ARGS(&istream)); + if (FAILED(hr)) { + *errorMessage = QLatin1String("BindToHandler() failed: ") + + QLatin1String(QWindowsContext::comErrorString(hr)); return false; + } enum : ULONG { bufSize = 102400 }; char buffer[bufSize]; ULONG bytesRead; @@ -682,7 +689,12 @@ bool QWindowsShellItem::copyData(QIODevice *out) break; } istream->Release(); - return hr == S_OK || hr == S_FALSE; + if (hr != S_OK && hr != S_FALSE) { + *errorMessage = QLatin1String("Read() failed: ") + + QLatin1String(QWindowsContext::comErrorString(hr)); + return false; + } + return true; } // Helper for "Libraries": collections of folders appearing from Windows 7 @@ -698,7 +710,7 @@ static IShellLibrary *sHLoadLibraryFromItem(IShellItem *libraryItem, DWORD mode) IShellLibrary *helper = nullptr; IShellLibrary *result = nullptr; - if (SUCCEEDED(CoCreateInstance(classId_ShellLibrary, NULL, CLSCTX_INPROC_SERVER, iId_IShellLibrary, reinterpret_cast<void **>(&helper)))) + if (SUCCEEDED(CoCreateInstance(classId_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER, iId_IShellLibrary, reinterpret_cast<void **>(&helper)))) if (SUCCEEDED(helper->LoadLibraryFromItem(libraryItem, mode))) helper->QueryInterface(iId_IShellLibrary, reinterpret_cast<void **>(&result)); if (helper) @@ -731,10 +743,9 @@ void QWindowsShellItem::format(QDebug &d) const d << " [dir]"; if (canStream()) d << " [stream]"; - if (canCopy()) - d << " [copyable]"; d << ", normalDisplay=\"" << normalDisplay() - << "\", desktopAbsoluteParsing=\"" << desktopAbsoluteParsing() << '"'; + << "\", desktopAbsoluteParsing=\"" << desktopAbsoluteParsing() + << "\", urlString=\"" << urlString() << "\", fileSysPath=\"" << fileSysPath() << '"'; const QString pathS = path(); if (!pathS.isEmpty()) d << ", path=\"" << pathS << '"'; @@ -795,7 +806,7 @@ public: inline void setDirectory(const QUrl &directory); inline void updateDirectory() { setDirectory(m_data.directory()); } inline QString directory() const; - void doExec(HWND owner = 0) override; + void doExec(HWND owner = nullptr) override; virtual void setNameFilters(const QStringList &f); inline void selectNameFilter(const QString &filter); inline void updateSelectedNameFilter() { selectNameFilter(m_data.selectedNameFilter()); } @@ -864,7 +875,7 @@ QWindowsNativeFileDialogBase::~QWindowsNativeFileDialogBase() bool QWindowsNativeFileDialogBase::init(const CLSID &clsId, const IID &iid) { - HRESULT hr = CoCreateInstance(clsId, NULL, CLSCTX_INPROC_SERVER, + HRESULT hr = CoCreateInstance(clsId, nullptr, CLSCTX_INPROC_SERVER, iid, reinterpret_cast<void **>(&m_fileDialog)); if (FAILED(hr)) { qErrnoWarning("CoCreateInstance failed"); @@ -897,7 +908,7 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) const QString native = QDir::toNativeSeparators(url.toLocalFile()); const HRESULT hr = SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()), - NULL, IID_IShellItem, + nullptr, IID_IShellItem, reinterpret_cast<void **>(&result)); if (FAILED(hr)) { qErrnoWarning("%s: SHCreateItemFromParsingName(%s)) failed", __FUNCTION__, qPrintable(url.toString())); @@ -915,7 +926,7 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) return nullptr; } PIDLIST_ABSOLUTE idList; - HRESULT hr = SHGetKnownFolderIDList(uuid, 0, 0, &idList); + HRESULT hr = SHGetKnownFolderIDList(uuid, 0, nullptr, &idList); if (FAILED(hr)) { qErrnoWarning("%s: SHGetKnownFolderIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString())); return nullptr; @@ -930,7 +941,7 @@ IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url) } else { qWarning() << __FUNCTION__ << ": Unhandled scheme: " << url.scheme(); } - return 0; + return nullptr; } void QWindowsNativeFileDialogBase::setDirectory(const QUrl &directory) @@ -946,7 +957,7 @@ void QWindowsNativeFileDialogBase::setDirectory(const QUrl &directory) QString QWindowsNativeFileDialogBase::directory() const { QString result; - IShellItem *item = 0; + IShellItem *item = nullptr; if (m_fileDialog && SUCCEEDED(m_fileDialog->GetFolder(&item)) && item) { result = QWindowsShellItem(item).path(); item->Release(); @@ -962,7 +973,9 @@ void QWindowsNativeFileDialogBase::doExec(HWND owner) const HRESULT hr = m_fileDialog->Show(owner); QWindowsDialogs::eatMouseMove(); qCDebug(lcQpaDialogs) << '<' << __FUNCTION__ << " returns " << hex << hr; - if (hr == S_OK) { + // Emit accepted() only if there is a result as otherwise UI hangs occur. + // For example, typing in invalid URLs results in empty result lists. + if (hr == S_OK && !m_data.selectedFiles().isEmpty()) { emit accepted(); } else { emit rejected(); @@ -1334,7 +1347,7 @@ void QWindowsNativeSaveFileDialog::setNameFilters(const QStringList &f) QList<QUrl> QWindowsNativeSaveFileDialog::dialogResult() const { QList<QUrl> result; - IShellItem *item = 0; + IShellItem *item = nullptr; if (SUCCEEDED(fileDialog()->GetResult(&item)) && item) result.append(QWindowsShellItem(item).url()); return result; @@ -1343,7 +1356,7 @@ QList<QUrl> QWindowsNativeSaveFileDialog::dialogResult() const QList<QUrl> QWindowsNativeSaveFileDialog::selectedFiles() const { QList<QUrl> result; - IShellItem *item = 0; + IShellItem *item = nullptr; const HRESULT hr = fileDialog()->GetCurrentSelection(&item); if (SUCCEEDED(hr) && item) { result.append(QWindowsShellItem(item).url()); @@ -1388,21 +1401,50 @@ static void cleanupTemporaryItemCopies() QFile::remove(file); } -static QString createTemporaryItemCopy(QWindowsShellItem &qItem) +// Determine temporary file pattern from a shell item's display +// name. This can be a URL. + +static bool validFileNameCharacter(QChar c) +{ + return c.isLetterOrNumber() || c == QLatin1Char('_') || c == QLatin1Char('-'); +} + +QString tempFilePattern(QString name) { - if (!qItem.canCopy() || !qItem.canStream()) + const int lastSlash = qMax(name.lastIndexOf(QLatin1Char('/')), + name.lastIndexOf(QLatin1Char('\\'))); + if (lastSlash != -1) + name.remove(0, lastSlash + 1); + + int lastDot = name.lastIndexOf(QLatin1Char('.')); + if (lastDot < 0) + lastDot = name.size(); + name.insert(lastDot, QStringLiteral("_XXXXXX")); + + for (int i = lastDot - 1; i >= 0; --i) { + if (!validFileNameCharacter(name.at(i))) + name[i] = QLatin1Char('_'); + } + + name.prepend(QDir::tempPath() + QLatin1Char('/')); + return name; +} + +static QString createTemporaryItemCopy(QWindowsShellItem &qItem, QString *errorMessage) +{ + if (!qItem.canStream()) { + *errorMessage = QLatin1String("Item not streamable"); return QString(); - QString pattern = qItem.normalDisplay(); - const int lastDot = pattern.lastIndexOf(QLatin1Char('.')); - const QString placeHolder = QStringLiteral("_XXXXXX"); - if (lastDot >= 0) - pattern.insert(lastDot, placeHolder); - else - pattern.append(placeHolder); - - QTemporaryFile targetFile(QDir::tempPath() + QLatin1Char('/') + pattern); + } + + QTemporaryFile targetFile(tempFilePattern(qItem.normalDisplay())); targetFile.setAutoRemove(false); - if (!targetFile.open() || !qItem.copyData(&targetFile)) + if (!targetFile.open()) { + *errorMessage = QLatin1String("Cannot create temporary file: ") + + targetFile.errorString(); + return QString(); + } + if (!qItem.copyData(&targetFile, errorMessage)) return QString(); const QString result = targetFile.fileName(); if (temporaryItemCopies()->isEmpty()) @@ -1411,23 +1453,41 @@ static QString createTemporaryItemCopy(QWindowsShellItem &qItem) return result; } +static QUrl itemToDialogUrl(QWindowsShellItem &qItem, QString *errorMessage) +{ + QUrl url = qItem.url(); + if (url.isLocalFile() || url.scheme().startsWith(QLatin1String("http"))) + return url; + const QString path = qItem.path(); + if (path.isEmpty() && !qItem.isDir() && qItem.canStream()) { + const QString temporaryCopy = createTemporaryItemCopy(qItem, errorMessage); + if (temporaryCopy.isEmpty()) { + QDebug(errorMessage).noquote() << "Unable to create a local copy of" + << qItem << ": " << errorMessage; + return QUrl(); + } + return QUrl::fromLocalFile(temporaryCopy); + } + if (!url.isValid()) + QDebug(errorMessage).noquote() << "Invalid URL obtained from" << qItem; + return url; +} + QList<QUrl> QWindowsNativeOpenFileDialog::dialogResult() const { QList<QUrl> result; - IShellItemArray *items = 0; + IShellItemArray *items = nullptr; if (SUCCEEDED(openFileDialog()->GetResults(&items)) && items) { + QString errorMessage; for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) { QWindowsShellItem qItem(item); - const QString path = qItem.path(); - if (path.isEmpty() && !qItem.isDir()) { - const QString temporaryCopy = createTemporaryItemCopy(qItem); - if (temporaryCopy.isEmpty()) - qWarning() << "Unable to create a local copy of" << qItem; - else - result.append(QUrl::fromLocalFile(temporaryCopy)); - } else { - result.append(qItem.url()); + const QUrl url = itemToDialogUrl(qItem, &errorMessage); + if (!url.isValid()) { + qWarning("%s", qPrintable(errorMessage)); + result.clear(); + break; } + result.append(url); } } return result; @@ -1436,7 +1496,7 @@ QList<QUrl> QWindowsNativeOpenFileDialog::dialogResult() const QList<QUrl> QWindowsNativeOpenFileDialog::selectedFiles() const { QList<QUrl> result; - IShellItemArray *items = 0; + IShellItemArray *items = nullptr; const HRESULT hr = openFileDialog()->GetSelectedItems(&items); if (SUCCEEDED(hr) && items) { for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) { @@ -1460,18 +1520,18 @@ QList<QUrl> QWindowsNativeOpenFileDialog::selectedFiles() const QWindowsNativeFileDialogBase *QWindowsNativeFileDialogBase::create(QFileDialogOptions::AcceptMode am, const QWindowsFileDialogSharedData &data) { - QWindowsNativeFileDialogBase *result = 0; + QWindowsNativeFileDialogBase *result = nullptr; if (am == QFileDialogOptions::AcceptOpen) { result = new QWindowsNativeOpenFileDialog(data); if (!result->init(CLSID_FileOpenDialog, IID_IFileOpenDialog)) { delete result; - return 0; + return nullptr; } } else { result = new QWindowsNativeSaveFileDialog(data); if (!result->init(CLSID_FileSaveDialog, IID_IFileSaveDialog)) { delete result; - return 0; + return nullptr; } } return result; @@ -1492,7 +1552,7 @@ class QWindowsFileDialogHelper : public QWindowsDialogHelperBase<QPlatformFileDi { public: QWindowsFileDialogHelper() {} - bool supportsNonModalDialog(const QWindow * /* parent */ = 0) const override { return false; } + bool supportsNonModalDialog(const QWindow * /* parent */ = nullptr) const override { return false; } bool defaultNameFilterDisables() const override { return false; } void setDirectory(const QUrl &directory) override; @@ -1516,7 +1576,7 @@ QWindowsNativeDialogBase *QWindowsFileDialogHelper::createNativeDialog() { QWindowsNativeFileDialogBase *result = QWindowsNativeFileDialogBase::create(options()->acceptMode(), m_data); if (!result) - return 0; + return nullptr; QObject::connect(result, &QWindowsNativeDialogBase::accepted, this, &QPlatformDialogHelper::accept); QObject::connect(result, &QWindowsNativeDialogBase::rejected, this, &QPlatformDialogHelper::reject); QObject::connect(result, &QWindowsNativeFileDialogBase::directoryEntered, @@ -1633,7 +1693,7 @@ public: static QWindowsXpNativeFileDialog *create(const OptionsPtr &options, const QWindowsFileDialogSharedData &data); void setWindowTitle(const QString &t) override { m_title = t; } - void doExec(HWND owner = 0) override; + void doExec(HWND owner = nullptr) override; int existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam); @@ -1658,8 +1718,8 @@ private: static PtrGetSaveFileNameW m_getSaveFileNameW; }; -QWindowsXpNativeFileDialog::PtrGetOpenFileNameW QWindowsXpNativeFileDialog::m_getOpenFileNameW = 0; -QWindowsXpNativeFileDialog::PtrGetSaveFileNameW QWindowsXpNativeFileDialog::m_getSaveFileNameW = 0; +QWindowsXpNativeFileDialog::PtrGetOpenFileNameW QWindowsXpNativeFileDialog::m_getOpenFileNameW = nullptr; +QWindowsXpNativeFileDialog::PtrGetSaveFileNameW QWindowsXpNativeFileDialog::m_getSaveFileNameW = nullptr; QWindowsXpNativeFileDialog *QWindowsXpNativeFileDialog::create(const OptionsPtr &options, const QWindowsFileDialogSharedData &data) { @@ -1673,7 +1733,7 @@ QWindowsXpNativeFileDialog *QWindowsXpNativeFileDialog::create(const OptionsPtr } if (m_getOpenFileNameW && m_getSaveFileNameW) return new QWindowsXpNativeFileDialog(options, data); - return 0; + return nullptr; } QWindowsXpNativeFileDialog::QWindowsXpNativeFileDialog(const OptionsPtr &options, @@ -1750,8 +1810,8 @@ QList<QUrl> QWindowsXpNativeFileDialog::execExistingDir(HWND owner) wchar_t initPath[MAX_PATH]; initPath[0] = 0; bi.hwndOwner = owner; - bi.pidlRoot = NULL; - bi.lpszTitle = 0; + bi.pidlRoot = nullptr; + bi.lpszTitle = nullptr; bi.pszDisplayName = initPath; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE; bi.lpfn = xpFileDialogGetExistingDirCallbackProc; @@ -1874,7 +1934,7 @@ class QWindowsXpFileDialogHelper : public QWindowsDialogHelperBase<QPlatformFile { public: QWindowsXpFileDialogHelper() = default; - bool supportsNonModalDialog(const QWindow * /* parent */ = 0) const override { return false; } + bool supportsNonModalDialog(const QWindow * /* parent */ = nullptr) const override { return false; } bool defaultNameFilterDisables() const override { return true; } void setDirectory(const QUrl &directory) override; @@ -1901,7 +1961,7 @@ QWindowsNativeDialogBase *QWindowsXpFileDialogHelper::createNativeDialog() QObject::connect(result, &QWindowsNativeDialogBase::rejected, this, &QPlatformDialogHelper::reject); return result; } - return 0; + return nullptr; } void QWindowsXpFileDialogHelper::setDirectory(const QUrl &directory) @@ -2084,7 +2144,7 @@ bool useHelper(QPlatformTheme::DialogType type) QPlatformDialogHelper *createHelper(QPlatformTheme::DialogType type) { if (QWindowsIntegration::instance()->options() & QWindowsIntegration::NoNativeDialogs) - return 0; + return nullptr; switch (type) { case QPlatformTheme::FileDialog: if (QWindowsIntegration::instance()->options() & QWindowsIntegration::XpNativeDialogs) @@ -2102,7 +2162,7 @@ QPlatformDialogHelper *createHelper(QPlatformTheme::DialogType type) default: break; } - return 0; + return nullptr; } } // namespace QWindowsDialogs |