diff options
Diffstat (limited to 'src/plugins/platforms/windows/qwindowsdialoghelpers.cpp')
-rw-r--r-- | src/plugins/platforms/windows/qwindowsdialoghelpers.cpp | 166 |
1 files changed, 63 insertions, 103 deletions
diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index bfd2608648..0ce0b0e2a7 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -3,12 +3,7 @@ #define QT_NO_URL_CAST_FROM_STRING 1 -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0A00 -#endif - #include <QtCore/qt_windows.h> -#include "qwindowscombase.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" @@ -35,6 +30,9 @@ #include <QtCore/qmutex.h> #include <QtCore/quuid.h> #include <QtCore/qtemporaryfile.h> +#include <QtCore/private/qfunctions_win_p.h> +#include <QtCore/private/qsystemerror_p.h> +#include <QtCore/private/qcomobject_p.h> #include <algorithm> #include <vector> @@ -45,34 +43,6 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -#ifndef QT_NO_DEBUG_STREAM -/* Output UID (IID, CLSID) as C++ constants. - * The constants are contained in the Windows SDK libs, but not for MinGW. */ -static inline QString guidToString(const GUID &g) -{ - QString rc; - QTextStream str(&rc); - str.setIntegerBase(16); - str.setNumberFlags(str.numberFlags() | QTextStream::ShowBase); - str << '{' << g.Data1 << ", " << g.Data2 << ", " << g.Data3; - str.setFieldWidth(2); - str.setFieldAlignment(QTextStream::AlignRight); - str.setPadChar(u'0'); - str << ",{" << g.Data4[0] << ", " << g.Data4[1] << ", " << g.Data4[2] << ", " << g.Data4[3] - << ", " << g.Data4[4] << ", " << g.Data4[5] << ", " << g.Data4[6] << ", " << g.Data4[7] - << "}};"; - return rc; -} - -inline QDebug operator<<(QDebug d, const GUID &g) -{ - QDebugStateSaver saver(d); - d.nospace(); - d << guidToString(g); - return d; -} -#endif // !QT_NO_DEBUG_STREAM - // Return an allocated wchar_t array from a QString, reserve more memory if desired. static wchar_t *qStringToWCharArray(const QString &s, size_t reserveSize = 0) { @@ -106,6 +76,22 @@ void eatMouseMove() qCDebug(lcQpaDialogs) << __FUNCTION__ << "triggered=" << (msg.message == WM_MOUSEMOVE); } +HWND getHWND(IFileDialog *fileDialog) +{ + IOleWindow *oleWindow = nullptr; + if (FAILED(fileDialog->QueryInterface(IID_IOleWindow, reinterpret_cast<void **>(&oleWindow)))) { + qCWarning(lcQpaDialogs, "Native file dialog: unable to query IID_IOleWindow interface."); + return HWND(0); + } + + HWND result(0); + if (FAILED(oleWindow->GetWindow(&result))) + qCWarning(lcQpaDialogs, "Native file dialog: unable to get dialog's window."); + + oleWindow->Release(); + return result; +} + } // namespace QWindowsDialogs /*! @@ -178,20 +164,25 @@ private: */ template <class BaseClass> +QWindowsDialogHelperBase<BaseClass>::~QWindowsDialogHelperBase() +{ + hide(); + cleanupThread(); +} + +template <class BaseClass> void QWindowsDialogHelperBase<BaseClass>::cleanupThread() { - if (m_thread) { // Thread may be running if the dialog failed to close. - if (m_thread->isRunning()) - m_thread->wait(500); - if (m_thread->isRunning()) { - m_thread->terminate(); - m_thread->wait(300); - if (m_thread->isRunning()) - qCCritical(lcQpaDialogs) <<__FUNCTION__ << "Failed to terminate thread."; - else - qCWarning(lcQpaDialogs) << __FUNCTION__ << "Thread terminated."; - } - delete m_thread; + if (m_thread) { + // Thread may be running if the dialog failed to close. Give it a bit + // to exit, but let it be a memory leak if that fails. We must not + // terminate the thread, it might be stuck in Comdlg32 or an IModalWindow + // implementation, and we might end up dead-locking the application if the thread + // holds a mutex or critical section. + if (m_thread->wait(500)) + delete m_thread; + else + qCCritical(lcQpaDialogs) <<__FUNCTION__ << "Thread failed to finish."; m_thread = nullptr; } } @@ -218,7 +209,7 @@ QWindowsNativeDialogBase *QWindowsDialogHelperBase<BaseClass>::ensureNativeDialo // Create dialog and apply common settings. Check "executed" flag as well // since for example IFileDialog::Show() works only once. if (m_nativeDialog.isNull() || m_nativeDialog->executed()) - m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog()); + m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog(), &QObject::deleteLater); return m_nativeDialog.data(); } @@ -247,6 +238,7 @@ private: void QWindowsDialogThread::run() { qCDebug(lcQpaDialogs) << '>' << __FUNCTION__; + QComHelper comInit(COINIT_APARTMENTTHREADED); m_dialog->exec(m_owner); qCDebug(lcQpaDialogs) << '<' << __FUNCTION__; } @@ -303,47 +295,13 @@ void QWindowsDialogHelperBase<BaseClass>::stopTimer() } } -// Find a file dialog window created by IFileDialog by process id, window -// title and class, which starts with a hash '#'. - -struct FindDialogContext -{ - explicit FindDialogContext(const QString &titleIn) - : title(qStringToWCharArray(titleIn)), processId(GetCurrentProcessId()), hwnd(nullptr) {} - - const QScopedArrayPointer<wchar_t> title; - const DWORD processId; - HWND hwnd; // contains the HWND of the window found. -}; - -static BOOL QT_WIN_CALLBACK findDialogEnumWindowsProc(HWND hwnd, LPARAM lParam) -{ - auto *context = reinterpret_cast<FindDialogContext *>(lParam); - DWORD winPid = 0; - GetWindowThreadProcessId(hwnd, &winPid); - if (winPid != context->processId) - return TRUE; - wchar_t buf[256]; - if (!RealGetWindowClass(hwnd, buf, sizeof(buf)/sizeof(wchar_t)) || buf[0] != L'#') - return TRUE; - if (!GetWindowTextW(hwnd, buf, sizeof(buf)/sizeof(wchar_t)) || wcscmp(buf, context->title.data()) != 0) - return TRUE; - context->hwnd = hwnd; - return FALSE; -} - -static inline HWND findDialogWindow(const QString &title) -{ - FindDialogContext context(title); - EnumWindows(findDialogEnumWindowsProc, reinterpret_cast<LPARAM>(&context)); - return context.hwnd; -} - template <class BaseClass> void QWindowsDialogHelperBase<BaseClass>::hide() { - if (m_nativeDialog) + if (m_nativeDialog) { m_nativeDialog->close(); + m_nativeDialog.clear(); + } m_ownerWindow = nullptr; } @@ -465,7 +423,7 @@ inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer<QFile class QWindowsNativeFileDialogBase; -class QWindowsNativeFileDialogEventHandler : public QWindowsComBase<IFileDialogEvents> +class QWindowsNativeFileDialogEventHandler : public QComObject<IFileDialogEvents> { Q_DISABLE_COPY_MOVE(QWindowsNativeFileDialogEventHandler) public: @@ -558,8 +516,18 @@ QWindowsShellItem::QWindowsShellItem(IShellItem *item) : m_item(item) , m_attributes(0) { - if (FAILED(item->GetAttributes(SFGAO_CAPABILITYMASK | SFGAO_DISPLAYATTRMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK, &m_attributes))) + SFGAOF mask = (SFGAO_CAPABILITYMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK); + + // Check for attributes which might be expensive to enumerate for subfolders + if (FAILED(item->GetAttributes((SFGAO_STREAM | SFGAO_COMPRESSED), &m_attributes))) { m_attributes = 0; + } else { + // If the item is compressed or stream, skip expensive subfolder test + if (m_attributes & (SFGAO_STREAM | SFGAO_COMPRESSED)) + mask &= ~SFGAO_HASSUBFOLDER; + if (FAILED(item->GetAttributes(mask, &m_attributes))) + m_attributes = 0; + } } QString QWindowsShellItem::path() const @@ -598,7 +566,7 @@ QUrl QWindowsShellItem::url() const if (urlV.isValid()) return urlV; // Last resort: encode the absolute desktop parsing id as data URL - const QString data = QStringLiteral("data:text/plain;base64,") + const QString data = "data:text/plain;base64,"_L1 + QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64()); return QUrl(data); } @@ -639,7 +607,7 @@ bool QWindowsShellItem::copyData(QIODevice *out, QString *errorMessage) HRESULT hr = m_item->BindToHandler(nullptr, BHID_Stream, IID_PPV_ARGS(&istream)); if (FAILED(hr)) { *errorMessage = "BindToHandler() failed: "_L1 - + QLatin1StringView(QWindowsContext::comErrorString(hr)); + + QSystemError::windowsComString(hr); return false; } enum : ULONG { bufSize = 102400 }; @@ -656,7 +624,7 @@ bool QWindowsShellItem::copyData(QIODevice *out, QString *errorMessage) istream->Release(); if (hr != S_OK && hr != S_FALSE) { *errorMessage = "Read() failed: "_L1 - + QLatin1StringView(QWindowsContext::comErrorString(hr)); + + QSystemError::windowsComString(hr); return false; } return true; @@ -1233,7 +1201,7 @@ void QWindowsNativeFileDialogBase::close() m_fileDialog->Close(S_OK); // IFileDialog::Close() does not work unless invoked from a callback. // Try to find the window and send it a WM_CLOSE in addition. - const HWND hwnd = findDialogWindow(m_title); + const HWND hwnd = QWindowsDialogs::getHWND(m_fileDialog); qCDebug(lcQpaDialogs) << __FUNCTION__ << "closing" << hwnd; if (hwnd && IsWindowVisible(hwnd)) PostMessageW(hwnd, WM_CLOSE, 0, 0); @@ -1369,7 +1337,7 @@ Q_GLOBAL_STATIC(QStringList, temporaryItemCopies) static void cleanupTemporaryItemCopies() { - for (const QString &file : qAsConst(*temporaryItemCopies())) + for (const QString &file : std::as_const(*temporaryItemCopies())) QFile::remove(file); } @@ -1391,7 +1359,7 @@ QString tempFilePattern(QString name) int lastDot = name.lastIndexOf(u'.'); if (lastDot < 0) lastDot = name.size(); - name.insert(lastDot, QStringLiteral("_XXXXXX")); + name.insert(lastDot, "_XXXXXX"_L1); for (int i = lastDot - 1; i >= 0; --i) { if (!validFileNameCharacter(name.at(i))) @@ -1583,7 +1551,7 @@ QWindowsNativeDialogBase *QWindowsFileDialogHelper::createNativeDialog() if (!info.isDir()) result->selectFile(info.fileName()); } else { - result->selectFile(url.path()); // TODO url.fileName() once it exists + result->selectFile(url.fileName()); } } // No need to select initialNameFilter if mode is Dir @@ -1617,7 +1585,7 @@ void QWindowsFileDialogHelper::selectFile(const QUrl &fileName) qCDebug(lcQpaDialogs) << __FUNCTION__ << fileName.toString(); if (hasNativeDialog()) // Might be invoked from the QFileDialog constructor. - nativeFileDialog()->selectFile(fileName.toLocalFile()); // ## should use QUrl::fileName() once it exists + nativeFileDialog()->selectFile(fileName.fileName()); } QList<QUrl> QWindowsFileDialogHelper::selectedFiles() const @@ -1725,14 +1693,6 @@ static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UIN return dialog->existingDirCallback(hwnd, uMsg, lParam); } -/* The correct declaration of the SHGetPathFromIDList symbol is - * being used in mingw-w64 as of r6215, which is a v3 snapshot. */ -#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3) -typedef ITEMIDLIST *qt_LpItemIdList; -#else -using qt_LpItemIdList = PIDLIST_ABSOLUTE; -#endif - int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam) { switch (uMsg) { @@ -1746,7 +1706,7 @@ int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM break; case BFFM_SELCHANGED: { wchar_t path[MAX_PATH]; - const bool ok = SHGetPathFromIDList(reinterpret_cast<qt_LpItemIdList>(lParam), path) + const bool ok = SHGetPathFromIDList(reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path) && path[0]; SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1); } @@ -1768,7 +1728,7 @@ QList<QUrl> QWindowsXpNativeFileDialog::execExistingDir(HWND owner) bi.lpfn = xpFileDialogGetExistingDirCallbackProc; bi.lParam = LPARAM(this); QList<QUrl> selectedFiles; - if (qt_LpItemIdList pItemIDList = SHBrowseForFolder(&bi)) { + if (const auto pItemIDList = SHBrowseForFolder(&bi)) { wchar_t path[MAX_PATH]; path[0] = 0; if (SHGetPathFromIDList(pItemIDList, path) && path[0]) |