Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsdialoghelpers.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#define QT_NO_URL_CAST_FROM_STRING 1
6
7#include <QtCore/qt_windows.h>
9
11#include "qwindowswindow.h"
13#include "qwindowstheme.h" // Color conversion helpers
14
15#include <QtGui/qguiapplication.h>
16#include <QtGui/qcolor.h>
17
18#include <QtCore/qdebug.h>
19#if QT_CONFIG(regularexpression)
20# include <QtCore/qregularexpression.h>
21#endif
22#include <QtCore/qtimer.h>
23#include <QtCore/qdir.h>
24#include <QtCore/qscopedpointer.h>
25#include <QtCore/qsharedpointer.h>
26#include <QtCore/qobject.h>
27#include <QtCore/qthread.h>
28#include <QtCore/qsysinfo.h>
29#include <QtCore/qshareddata.h>
30#include <QtCore/qshareddata.h>
31#include <QtCore/qmutex.h>
32#include <QtCore/quuid.h>
33#include <QtCore/qtemporaryfile.h>
34#include <QtCore/private/qfunctions_win_p.h>
35#include <QtCore/private/qsystemerror_p.h>
36#include <QtCore/private/qcomobject_p.h>
37
38#include <algorithm>
39#include <QtCore/q20memory.h>
40#include <vector>
41
42using namespace std::chrono_literals;
43
44// #define USE_NATIVE_COLOR_DIALOG /* Testing purposes only */
45
46QT_BEGIN_NAMESPACE
47
48using namespace Qt::StringLiterals;
49
50// Return an allocated wchar_t array from a QString, reserve more memory if desired.
51static wchar_t *qStringToWCharArray(const QString &s, size_t reserveSize = 0)
52{
53 const size_t stringSize = s.size();
54 wchar_t *result = new wchar_t[qMax(stringSize + 1, reserveSize)];
55 s.toWCharArray(result);
56 result[stringSize] = 0;
57 return result;
58}
59
61{
62/*!
63 \fn eatMouseMove()
64
65 After closing a windows dialog with a double click (i.e. open a file)
66 the message queue still contains a dubious WM_MOUSEMOVE message where
67 the left button is reported to be down (wParam != 0).
68 remove all those messages (usually 1) and post the last one with a
69 reset button state.
70
71*/
72
74{
75 MSG msg = {nullptr, 0, 0, 0, 0, {0, 0} };
76 while (PeekMessage(&msg, nullptr, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE))
77 ;
78 if (msg.message == WM_MOUSEMOVE)
79 PostMessage(msg.hwnd, msg.message, 0, msg.lParam);
80 qCDebug(lcQpaDialogs) << __FUNCTION__ << "triggered=" << (msg.message == WM_MOUSEMOVE);
81}
82
83HWND getHWND(IFileDialog *fileDialog)
84{
85 IOleWindow *oleWindow = nullptr;
86 if (FAILED(fileDialog->QueryInterface(IID_IOleWindow, reinterpret_cast<void **>(&oleWindow)))) {
87 qCWarning(lcQpaDialogs, "Native file dialog: unable to query IID_IOleWindow interface.");
88 return HWND(0);
89 }
90
91 HWND result(0);
92 if (FAILED(oleWindow->GetWindow(&result)))
93 qCWarning(lcQpaDialogs, "Native file dialog: unable to get dialog's window.");
94
95 oleWindow->Release();
96 return result;
97}
98
99} // namespace QWindowsDialogs
100
101/*!
102 \class QWindowsNativeDialogBase
103 \brief Base class for Windows native dialogs.
104
105 Base classes for native dialogs (using the CLSID-based
106 dialog interfaces "IFileDialog", etc. available from Windows
107 Vista on) that mimic the behavior of their QDialog
108 counterparts as close as possible.
109
110 Instances of derived classes are controlled by
111 QWindowsDialogHelperBase-derived classes.
112
113 A major difference is that there is only an exec(), which
114 is a modal, blocking call; there is no non-blocking show().
115 There 2 types of native dialogs:
116
117 \list
118 \li Dialogs provided by the Comdlg32 library (ChooseColor,
119 ChooseFont). They only provide a modal, blocking
120 function call (with idle processing).
121 \li File dialogs are classes derived from IFileDialog. They
122 inherit IModalWindow and their exec() method (calling
123 IModalWindow::Show()) is similarly blocking, but methods
124 like close() can be called on them from event handlers.
125 \endlist
126
127 \sa QWindowsDialogHelperBase
128 \internal
129*/
130
132{
134public:
135 virtual void setWindowTitle(const QString &title) = 0;
136 bool executed() const { return m_executed; }
137 void exec(HWND owner = nullptr) { doExec(owner); m_executed = true; }
138
139signals:
140 void accepted();
141 void rejected();
142
143public slots:
144 virtual void close() = 0;
145
146protected:
147 QWindowsNativeDialogBase() : m_executed(false) {}
148
149private:
150 virtual void doExec(HWND owner = nullptr) = 0;
151
152 bool m_executed;
153};
154
155/*!
156 \class QWindowsDialogHelperBase
157 \brief Helper for native Windows dialogs.
158
159 Provides basic functionality and introduces new virtuals.
160 The native dialog is created in setVisible_sys() since
161 then modality and the state of DontUseNativeDialog is known.
162
163 Modal dialogs are then run by exec(). Non-modal dialogs are shown using a
164 separate thread started in show() should they support it.
165
166 \sa QWindowsDialogThread, QWindowsNativeDialogBase
167 \internal
168*/
169
170template <class BaseClass>
172{
173 hide();
174 cleanupThread();
175}
176
177template <class BaseClass>
178void QWindowsDialogHelperBase<BaseClass>::cleanupThread()
179{
180 if (m_thread) {
181 // Thread may be running if the dialog failed to close. Give it a bit
182 // to exit, but let it be a memory leak if that fails. We must not
183 // terminate the thread, it might be stuck in Comdlg32 or an IModalWindow
184 // implementation, and we might end up dead-locking the application if the thread
185 // holds a mutex or critical section.
186 if (m_thread->wait(500))
187 delete m_thread;
188 else
189 qCCritical(lcQpaDialogs) <<__FUNCTION__ << "Thread failed to finish.";
190 m_thread = nullptr;
191 }
192}
193
194template <class BaseClass>
196{
197 if (m_nativeDialog.isNull()) {
198 qWarning("%s invoked with no native dialog present.", __FUNCTION__);
199 return nullptr;
200 }
201 return m_nativeDialog.data();
202}
203
204template <class BaseClass>
205void QWindowsDialogHelperBase<BaseClass>::timerEvent(QTimerEvent *)
206{
207 startDialogThread();
208}
209
210template <class BaseClass>
211QWindowsNativeDialogBase *QWindowsDialogHelperBase<BaseClass>::ensureNativeDialog()
212{
213 // Create dialog and apply common settings. Check "executed" flag as well
214 // since for example IFileDialog::Show() works only once.
215 if (m_nativeDialog.isNull() || m_nativeDialog->executed())
216 m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog(), &QObject::deleteLater);
217 return m_nativeDialog.data();
218}
219
220/*!
221 \class QWindowsDialogThread
222 \brief Run a non-modal native dialog in a separate thread.
223
224 \sa QWindowsDialogHelperBase
225 \internal
226*/
227
229{
230public:
232
233 explicit QWindowsDialogThread(const QWindowsNativeDialogBasePtr &d, HWND owner)
234 : m_dialog(d), m_owner(owner) {}
235 void run() override;
236
237private:
238 const QWindowsNativeDialogBasePtr m_dialog;
239 const HWND m_owner;
240};
241
243{
244 qCDebug(lcQpaDialogs) << '>' << __FUNCTION__;
245 QComHelper comInit(COINIT_APARTMENTTHREADED);
246 m_dialog->exec(m_owner);
247 qCDebug(lcQpaDialogs) << '<' << __FUNCTION__;
248}
249
250template <class BaseClass>
251bool QWindowsDialogHelperBase<BaseClass>::show(Qt::WindowFlags,
252 Qt::WindowModality windowModality,
253 QWindow *parent)
254{
255 const bool modal = (windowModality != Qt::NonModal);
256 if (!parent)
257 parent = QGuiApplication::focusWindow(); // Need a parent window, else the application loses activation when closed.
258 if (parent) {
259 m_ownerWindow = QWindowsWindow::handleOf(parent);
260 } else {
261 m_ownerWindow = nullptr;
262 }
263 qCDebug(lcQpaDialogs) << __FUNCTION__ << "modal=" << modal
264 << " modal supported? " << supportsNonModalDialog(parent)
265 << "native=" << m_nativeDialog.data() << "owner" << m_ownerWindow;
266 if (!modal && !supportsNonModalDialog(parent))
267 return false; // Was it changed in-between?
268 if (!ensureNativeDialog())
269 return false;
270 // Start a background thread to show the dialog. For modal dialogs,
271 // a subsequent call to exec() may follow. So, start an idle timer
272 // which will start the dialog thread. If exec() is then called, the
273 // timer is stopped and dialog->exec() is called directly.
274 cleanupThread();
275 if (modal) {
276 m_timer.start(0ns, this);
277 } else {
278 startDialogThread();
279 }
280 return true;
281}
282
283template <class BaseClass>
284void QWindowsDialogHelperBase<BaseClass>::startDialogThread()
285{
286 Q_ASSERT(!m_nativeDialog.isNull());
287 Q_ASSERT(!m_thread);
288 m_thread = new QWindowsDialogThread(m_nativeDialog, m_ownerWindow);
289 m_thread->start();
290 stopTimer();
291}
292
293template <class BaseClass>
294void QWindowsDialogHelperBase<BaseClass>::stopTimer()
295{
296 m_timer.stop();
297}
298
299template <class BaseClass>
301{
302 if (m_nativeDialog) {
303 m_nativeDialog->close();
304 m_nativeDialog.clear();
305 }
306 m_ownerWindow = nullptr;
307}
308
309template <class BaseClass>
311{
312 qCDebug(lcQpaDialogs) << __FUNCTION__;
313 stopTimer();
315 nd->exec(m_ownerWindow);
316 m_nativeDialog.clear();
317 }
318}
319
320/*!
321 \class QWindowsFileDialogSharedData
322 \brief Explicitly shared file dialog parameters that are not in QFileDialogOptions.
323
324 Contain Parameters that need to be cached while the native dialog does not
325 exist yet. In addition, the data are updated by the change notifications of the
326 IFileDialogEvent, as querying them after the dialog has closed
327 does not reliably work. Provides thread-safe setters (for the non-modal case).
328
329 \internal
330 \sa QFileDialogOptions
331*/
332
334{
335public:
337 void fromOptions(const QSharedPointer<QFileDialogOptions> &o);
338
340 void setDirectory(const QUrl &);
342 void setSelectedNameFilter(const QString &);
344 void setSelectedFiles(const QList<QUrl> &);
346
347private:
348 class Data : public QSharedData {
349 public:
350 QUrl directory;
351 QString selectedNameFilter;
352 QList<QUrl> selectedFiles;
353 QMutex mutex;
354 };
355 QExplicitlySharedDataPointer<Data> m_data;
356};
357
359{
360 m_data->mutex.lock();
361 const QUrl result = m_data->directory;
362 m_data->mutex.unlock();
363 return result;
364}
365
367{
368 QMutexLocker locker(&m_data->mutex);
369 m_data->directory = d;
370}
371
373{
374 m_data->mutex.lock();
375 const QString result = m_data->selectedNameFilter;
376 m_data->mutex.unlock();
377 return result;
378}
379
381{
382 QMutexLocker locker(&m_data->mutex);
383 m_data->selectedNameFilter = f;
384}
385
387{
388 m_data->mutex.lock();
389 const auto result = m_data->selectedFiles;
390 m_data->mutex.unlock();
391 return result;
392}
393
395{
396 const auto files = selectedFiles();
397 return files.isEmpty() ? QString() : files.front().toLocalFile();
398}
399
400inline void QWindowsFileDialogSharedData::setSelectedFiles(const QList<QUrl> &urls)
401{
402 QMutexLocker locker(&m_data->mutex);
403 m_data->selectedFiles = urls;
404}
405
406inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer<QFileDialogOptions> &o)
407{
408 QMutexLocker locker(&m_data->mutex);
409 m_data->directory = o->initialDirectory();
410 m_data->selectedFiles = o->initiallySelectedFiles();
411 m_data->selectedNameFilter = o->initiallySelectedNameFilter();
412}
413
414/*!
415 \class QWindowsNativeFileDialogEventHandler
416 \brief Listens to IFileDialog events and forwards them to QWindowsNativeFileDialogBase
417
418 Events like 'folder change' that have an equivalent signal
419 in QFileDialog are forwarded.
420
421 \sa QWindowsNativeFileDialogBase, QWindowsFileDialogHelper
422 \internal
423*/
424
426
428{
430public:
432
433 // IFileDialogEvents methods
434 IFACEMETHODIMP OnFileOk(IFileDialog *) override;
435 IFACEMETHODIMP OnFolderChange(IFileDialog *) override { return S_OK; }
436 IFACEMETHODIMP OnFolderChanging(IFileDialog *, IShellItem *) override;
437 IFACEMETHODIMP OnSelectionChange(IFileDialog *) override;
438 IFACEMETHODIMP OnShareViolation(IFileDialog *, IShellItem *,
439 FDE_SHAREVIOLATION_RESPONSE *) override
440 {
441 return S_OK;
442 }
443 IFACEMETHODIMP OnTypeChange(IFileDialog *) override;
444 IFACEMETHODIMP OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) override
445 {
446 return S_OK;
447 }
448
450 m_nativeFileDialog(nativeFileDialog) {}
451
452private:
453 QWindowsNativeFileDialogBase *m_nativeFileDialog;
454};
455
456IFileDialogEvents *QWindowsNativeFileDialogEventHandler::create(QWindowsNativeFileDialogBase *nativeFileDialog)
457{
458 IFileDialogEvents *result;
459 auto *eventHandler = new QWindowsNativeFileDialogEventHandler(nativeFileDialog);
460 if (FAILED(eventHandler->QueryInterface(IID_IFileDialogEvents, reinterpret_cast<void **>(&result)))) {
461 qErrnoWarning("Unable to obtain IFileDialogEvents");
462 return nullptr;
463 }
464 eventHandler->Release();
465 return result;
466}
467
468/*!
469 \class QWindowsShellItem
470 \brief Wrapper for IShellItem
471
472 \sa QWindowsNativeFileDialogBase
473 \internal
474*/
476{
477public:
479
480 explicit QWindowsShellItem(IShellItem *item);
481
482 SFGAOF attributes() const { return m_attributes; }
483 QString normalDisplay() const // base name, usually
484 { return displayName(m_item, SIGDN_NORMALDISPLAY); }
486 { return displayName(m_item, SIGDN_URL); }
488 { return displayName(m_item, SIGDN_FILESYSPATH); }
490 { return displayName(m_item, SIGDN_DESKTOPABSOLUTEPARSING); }
491 QString path() const; // Only set for 'FileSystem' (SFGAO_FILESYSTEM) items
492 QUrl url() const;
493
494 bool isFileSystem() const { return (m_attributes & SFGAO_FILESYSTEM) != 0; }
495 bool isDir() const { return (m_attributes & SFGAO_FOLDER) != 0; }
496 // Supports IStream
497 bool canStream() const { return (m_attributes & SFGAO_STREAM) != 0; }
498
499 bool copyData(QIODevice *out, QString *errorMessage);
500
501 static IShellItems itemsFromItemArray(IShellItemArray *items);
502
503#ifndef QT_NO_DEBUG_STREAM
504 void format(QDebug &d) const;
505#endif
506
507private:
508 static QString displayName(IShellItem *item, SIGDN mode);
509 static QString libraryItemDefaultSaveFolder(IShellItem *item);
510 QUrl urlValue() const;
511
512 IShellItem *m_item;
513 SFGAOF m_attributes;
514};
515
517 : m_item(item)
518 , m_attributes(0)
519{
520 SFGAOF mask = (SFGAO_CAPABILITYMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK);
521
522 // Check for attributes which might be expensive to enumerate for subfolders
523 if (FAILED(item->GetAttributes((SFGAO_STREAM | SFGAO_COMPRESSED), &m_attributes))) {
524 m_attributes = 0;
525 } else {
526 // If the item is compressed or stream, skip expensive subfolder test
527 if (m_attributes & (SFGAO_STREAM | SFGAO_COMPRESSED))
528 mask &= ~SFGAO_HASSUBFOLDER;
529 if (FAILED(item->GetAttributes(mask, &m_attributes)))
530 m_attributes = 0;
531 }
532}
533
535{
536 if (isFileSystem())
537 return QDir::cleanPath(QWindowsShellItem::displayName(m_item, SIGDN_FILESYSPATH));
538 // Check for a "Library" item
539 if (isDir())
540 return QWindowsShellItem::libraryItemDefaultSaveFolder(m_item);
541 return QString();
542}
543
544QUrl QWindowsShellItem::urlValue() const // plain URL as returned by SIGDN_URL, not set for all items
545{
546 QUrl result;
547 const QString urlString = displayName(m_item, SIGDN_URL);
548 if (!urlString.isEmpty()) {
549 const QUrl parsed = QUrl(urlString);
550 if (parsed.isValid()) {
551 result = parsed;
552 } else {
553 qWarning("%s: Unable to decode URL \"%s\": %s", __FUNCTION__,
554 qPrintable(urlString), qPrintable(parsed.errorString()));
555 }
556 }
557 return result;
558}
559
561{
562 // Prefer file if existent to avoid any misunderstandings about UNC shares
563 const QString fsPath = path();
564 if (!fsPath.isEmpty())
565 return QUrl::fromLocalFile(fsPath);
566 const QUrl urlV = urlValue();
567 if (urlV.isValid())
568 return urlV;
569 // Last resort: encode the absolute desktop parsing id as data URL
570 const QString data = "data:text/plain;base64,"_L1
571 + QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64());
572 return QUrl(data);
573}
574
575QString QWindowsShellItem::displayName(IShellItem *item, SIGDN mode)
576{
577 LPWSTR name = nullptr;
578 QString result;
579 if (SUCCEEDED(item->GetDisplayName(mode, &name))) {
580 result = QString::fromWCharArray(name);
581 CoTaskMemFree(name);
582 }
583 return result;
584}
585
586QWindowsShellItem::IShellItems QWindowsShellItem::itemsFromItemArray(IShellItemArray *items)
587{
588 IShellItems result;
589 DWORD itemCount = 0;
590 if (FAILED(items->GetCount(&itemCount)) || itemCount == 0)
591 return result;
592 result.reserve(itemCount);
593 for (DWORD i = 0; i < itemCount; ++i) {
594 IShellItem *item = nullptr;
595 if (SUCCEEDED(items->GetItemAt(i, &item)))
596 result.push_back(item);
597 }
598 return result;
599}
600
601bool QWindowsShellItem::copyData(QIODevice *out, QString *errorMessage)
602{
603 if (!canStream()) {
604 *errorMessage = "Item not streamable"_L1;
605 return false;
606 }
607 IStream *istream = nullptr;
608 HRESULT hr = m_item->BindToHandler(nullptr, BHID_Stream, IID_PPV_ARGS(&istream));
609 if (FAILED(hr)) {
610 *errorMessage = "BindToHandler() failed: "_L1
611 + QSystemError::windowsComString(hr);
612 return false;
613 }
614 enum : ULONG { bufSize = 102400 };
615 const auto memory = q20::make_unique_for_overwrite<char[]>(bufSize);
616 char * const buffer = memory.get();
617 ULONG bytesRead;
618 forever {
619 bytesRead = 0;
620 hr = istream->Read(buffer, bufSize, &bytesRead); // S_FALSE: EOF reached
621 if ((hr == S_OK || hr == S_FALSE) && bytesRead)
622 out->write(buffer, bytesRead);
623 else
624 break;
625 }
626 istream->Release();
627 if (hr != S_OK && hr != S_FALSE) {
628 *errorMessage = "Read() failed: "_L1
629 + QSystemError::windowsComString(hr);
630 return false;
631 }
632 return true;
633}
634
635// Helper for "Libraries": collections of folders appearing from Windows 7
636// on, visible in the file dialogs.
637
638// Load a library from a IShellItem (sanitized copy of the inline function
639// SHLoadLibraryFromItem from ShObjIdl.h, which does not exist for MinGW).
640static IShellLibrary *sHLoadLibraryFromItem(IShellItem *libraryItem, DWORD mode)
641{
642 // ID symbols present from Windows 7 on:
643 static const CLSID classId_ShellLibrary = {0xd9b3211d, 0xe57f, 0x4426, {0xaa, 0xef, 0x30, 0xa8, 0x6, 0xad, 0xd3, 0x97}};
644 static const IID iId_IShellLibrary = {0x11a66efa, 0x382e, 0x451a, {0x92, 0x34, 0x1e, 0xe, 0x12, 0xef, 0x30, 0x85}};
645
646 IShellLibrary *helper = nullptr;
647 IShellLibrary *result = nullptr;
648 if (SUCCEEDED(CoCreateInstance(classId_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER, iId_IShellLibrary, reinterpret_cast<void **>(&helper))))
649 if (SUCCEEDED(helper->LoadLibraryFromItem(libraryItem, mode)))
650 helper->QueryInterface(iId_IShellLibrary, reinterpret_cast<void **>(&result));
651 if (helper)
652 helper->Release();
653 return result;
654}
655
656// Return default save folders of a library-type item.
657QString QWindowsShellItem::libraryItemDefaultSaveFolder(IShellItem *item)
658{
659 QString result;
660 if (IShellLibrary *library = sHLoadLibraryFromItem(item, STGM_READ | STGM_SHARE_DENY_WRITE)) {
661 IShellItem *item = nullptr;
662 if (SUCCEEDED(library->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, reinterpret_cast<void **>(&item)))) {
663 result = QDir::cleanPath(QWindowsShellItem::displayName(item, SIGDN_FILESYSPATH));
664 item->Release();
665 }
666 library->Release();
667 }
668 return result;
669}
670
671#ifndef QT_NO_DEBUG_STREAM
672void QWindowsShellItem::format(QDebug &d) const
673{
674 d << "attributes=0x" << Qt::hex << attributes() << Qt::dec;
675 if (isFileSystem())
676 d << " [filesys]";
677 if (isDir())
678 d << " [dir]";
679 if (canStream())
680 d << " [stream]";
681 d << ", normalDisplay=\"" << normalDisplay()
682 << "\", desktopAbsoluteParsing=\"" << desktopAbsoluteParsing()
683 << "\", urlString=\"" << urlString() << "\", fileSysPath=\"" << fileSysPath() << '"';
684 const QString pathS = path();
685 if (!pathS.isEmpty())
686 d << ", path=\"" << pathS << '"';
687 const QUrl urlV = urlValue();
688 if (urlV.isValid())
689 d << "\", url=" << urlV;
690}
691
692QDebug operator<<(QDebug d, const QWindowsShellItem &i)
693{
694 QDebugStateSaver saver(d);
695 d.nospace();
696 d.noquote();
697 d << "QShellItem(";
698 i.format(d);
699 d << ')';
700 return d;
701}
702
703QDebug operator<<(QDebug d, IShellItem *i)
704{
705 QDebugStateSaver saver(d);
706 d.nospace();
707 d.noquote();
708 d << "IShellItem(" << static_cast<const void *>(i);
709 if (i) {
710 d << ", ";
712 }
713 d << ')';
714 return d;
715}
716#endif // !QT_NO_DEBUG_STREAM
717
718/*!
719 \class QWindowsNativeFileDialogBase
720 \brief Windows native file dialog wrapper around IFileOpenDialog, IFileSaveDialog.
721
722 Provides convenience methods.
723 Note that only IFileOpenDialog has multi-file functionality.
724
725 \sa QWindowsNativeFileDialogEventHandler, QWindowsFileDialogHelper
726 \internal
727*/
728
730{
731 Q_OBJECT
732 Q_PROPERTY(bool hideFiltersDetails READ hideFiltersDetails WRITE setHideFiltersDetails)
733public:
735
736 inline static QWindowsNativeFileDialogBase *create(QFileDialogOptions::AcceptMode am, const QWindowsFileDialogSharedData &data);
737
738 void setWindowTitle(const QString &title) override;
739 inline void setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options);
740 inline void setDirectory(const QUrl &directory);
741 inline void updateDirectory() { setDirectory(m_data.directory()); }
742 inline QString directory() const;
743 void doExec(HWND owner = nullptr) override;
744 virtual void setNameFilters(const QStringList &f);
745 inline void selectNameFilter(const QString &filter);
746 inline void updateSelectedNameFilter() { selectNameFilter(m_data.selectedNameFilter()); }
748 void selectFile(const QString &fileName) const;
749 bool hideFiltersDetails() const { return m_hideFiltersDetails; }
750 void setHideFiltersDetails(bool h) { m_hideFiltersDetails = h; }
751 void setDefaultSuffix(const QString &s);
752 inline bool hasDefaultSuffix() const { return m_hasDefaultSuffix; }
753 inline void setLabelText(QFileDialogOptions::DialogLabel l, const QString &text);
754
755 // Return the selected files for tracking in OnSelectionChanged().
756 virtual QList<QUrl> selectedFiles() const = 0;
757 // Return the result for tracking in OnFileOk(). Differs from selection for
758 // example by appended default suffixes, etc.
759 virtual QList<QUrl> dialogResult() const = 0;
760
761 inline void onFolderChange(IShellItem *);
762 inline void onSelectionChange();
763 inline void onTypeChange();
764 inline bool onFileOk();
765
766signals:
768 void currentChanged(const QUrl &file);
769 void filterSelected(const QString & filter);
770
771public slots:
773
774protected:
776 bool init(const CLSID &clsId, const IID &iid);
777 void setDefaultSuffixSys(const QString &s);
778 inline IFileDialog * fileDialog() const { return m_fileDialog; }
779 static IShellItem *shellItem(const QUrl &url);
780
781 const QWindowsFileDialogSharedData &data() const { return m_data; }
782 QWindowsFileDialogSharedData &data() { return m_data; }
783
784private:
785 IFileDialog *m_fileDialog = nullptr;
786 IFileDialogEvents *m_dialogEvents = nullptr;
787 DWORD m_cookie = 0;
788 QStringList m_nameFilters;
789 bool m_hideFiltersDetails = false;
790 bool m_hasDefaultSuffix = false;
792 QString m_title;
793};
794
799
800QWindowsNativeFileDialogBase::~QWindowsNativeFileDialogBase()
801{
802 if (m_dialogEvents && m_fileDialog)
803 m_fileDialog->Unadvise(m_cookie);
804 if (m_dialogEvents)
805 m_dialogEvents->Release();
806 if (m_fileDialog)
807 m_fileDialog->Release();
808}
809
810bool QWindowsNativeFileDialogBase::init(const CLSID &clsId, const IID &iid)
811{
812 HRESULT hr = CoCreateInstance(clsId, nullptr, CLSCTX_INPROC_SERVER,
813 iid, reinterpret_cast<void **>(&m_fileDialog));
814 if (FAILED(hr)) {
815 qErrnoWarning("CoCreateInstance failed");
816 return false;
817 }
818 m_dialogEvents = QWindowsNativeFileDialogEventHandler::create(this);
819 if (!m_dialogEvents)
820 return false;
821 // Register event handler
822 hr = m_fileDialog->Advise(m_dialogEvents, &m_cookie);
823 if (FAILED(hr)) {
824 qErrnoWarning("IFileDialog::Advise failed");
825 return false;
826 }
827 qCDebug(lcQpaDialogs) << __FUNCTION__ << m_fileDialog << m_dialogEvents << m_cookie;
828
829 return true;
830}
831
833{
834 m_title = title;
835 m_fileDialog->SetTitle(reinterpret_cast<const wchar_t *>(title.utf16()));
836}
837
838IShellItem *QWindowsNativeFileDialogBase::shellItem(const QUrl &url)
839{
840 if (url.isLocalFile()) {
841 IShellItem *result = nullptr;
842 const QString native = QDir::toNativeSeparators(url.toLocalFile());
843 const HRESULT hr =
844 SHCreateItemFromParsingName(reinterpret_cast<const wchar_t *>(native.utf16()),
845 nullptr, IID_IShellItem,
846 reinterpret_cast<void **>(&result));
847 if (FAILED(hr)) {
848 qErrnoWarning("%s: SHCreateItemFromParsingName(%s)) failed", __FUNCTION__, qPrintable(url.toString()));
849 return nullptr;
850 }
851 return result;
852 } else if (url.scheme() == u"clsid") {
853 // Support for virtual folders via GUID
854 // (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx)
855 // specified as "clsid:<GUID>" (without '{', '}').
856 IShellItem *result = nullptr;
857 const auto uuid = QUuid::fromString(url.path());
858 if (uuid.isNull()) {
859 qWarning() << __FUNCTION__ << ": Invalid CLSID: " << url.path();
860 return nullptr;
861 }
862 PIDLIST_ABSOLUTE idList;
863 HRESULT hr = SHGetKnownFolderIDList(uuid, 0, nullptr, &idList);
864 if (FAILED(hr)) {
865 qErrnoWarning("%s: SHGetKnownFolderIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString()));
866 return nullptr;
867 }
868 hr = SHCreateItemFromIDList(idList, IID_IShellItem, reinterpret_cast<void **>(&result));
869 CoTaskMemFree(idList);
870 if (FAILED(hr)) {
871 qErrnoWarning("%s: SHCreateItemFromIDList(%s)) failed", __FUNCTION__, qPrintable(url.toString()));
872 return nullptr;
873 }
874 return result;
875 } else {
876 qWarning() << __FUNCTION__ << ": Unhandled scheme: " << url.scheme();
877 }
878 return nullptr;
879}
880
881void QWindowsNativeFileDialogBase::setDirectory(const QUrl &directory)
882{
883 if (!directory.isEmpty()) {
884 if (IShellItem *psi = QWindowsNativeFileDialogBase::shellItem(directory)) {
885 m_fileDialog->SetFolder(psi);
886 psi->Release();
887 }
888 }
889}
890
892{
893 QString result;
894 IShellItem *item = nullptr;
895 if (m_fileDialog && SUCCEEDED(m_fileDialog->GetFolder(&item)) && item) {
896 result = QWindowsShellItem(item).path();
897 item->Release();
898 }
899 return result;
900}
901
903{
904 qCDebug(lcQpaDialogs) << '>' << __FUNCTION__;
905 // Show() blocks until the user closes the dialog, the dialog window
906 // gets a WM_CLOSE or the parent window is destroyed.
907 const HRESULT hr = m_fileDialog->Show(owner);
909 qCDebug(lcQpaDialogs) << '<' << __FUNCTION__ << " returns " << Qt::hex << hr;
910 // Emit accepted() only if there is a result as otherwise UI hangs occur.
911 // For example, typing in invalid URLs results in empty result lists.
912 if (hr == S_OK && !m_data.selectedFiles().isEmpty()) {
913 emit accepted();
914 } else {
915 emit rejected();
916 }
917}
918
919void QWindowsNativeFileDialogBase::setMode(QFileDialogOptions::FileMode mode,
920 QFileDialogOptions::AcceptMode acceptMode,
921 QFileDialogOptions::FileDialogOptions options)
922{
923 DWORD flags = FOS_PATHMUSTEXIST;
924 if (QWindowsContext::readAdvancedExplorerSettings(L"Hidden", 1) == 1) // 1:show, 2:hidden
925 flags |= FOS_FORCESHOWHIDDEN;
926 if (options & QFileDialogOptions::DontResolveSymlinks)
927 flags |= FOS_NODEREFERENCELINKS;
928 switch (mode) {
929 case QFileDialogOptions::AnyFile:
930 if (acceptMode == QFileDialogOptions::AcceptSave)
931 flags |= FOS_NOREADONLYRETURN;
932 if (!(options & QFileDialogOptions::DontConfirmOverwrite))
933 flags |= FOS_OVERWRITEPROMPT;
934 break;
935 case QFileDialogOptions::ExistingFile:
936 flags |= FOS_FILEMUSTEXIST;
937 break;
938 case QFileDialogOptions::Directory:
939 case QFileDialogOptions::DirectoryOnly:
940 // QTBUG-63645: Restrict to file system items, as Qt cannot deal with
941 // places like 'Network', etc.
942 flags |= FOS_PICKFOLDERS | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM;
943 break;
944 case QFileDialogOptions::ExistingFiles:
945 flags |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
946 break;
947 }
948 qCDebug(lcQpaDialogs) << __FUNCTION__ << "mode=" << mode
949 << "acceptMode=" << acceptMode << "options=" << options
950 << "results in" << Qt::showbase << Qt::hex << flags;
951
952 if (FAILED(m_fileDialog->SetOptions(flags)))
953 qErrnoWarning("%s: SetOptions() failed", __FUNCTION__);
954}
955
956// Split a list of name filters into description and actual filters
962
963static QList<FilterSpec> filterSpecs(const QStringList &filters,
964 bool hideFilterDetails,
965 int *totalStringLength)
966{
967 QList<FilterSpec> result;
968 result.reserve(filters.size());
969 *totalStringLength = 0;
970
971#if QT_CONFIG(regularexpression)
972 const QRegularExpression filterSeparatorRE(QStringLiteral("[;\\s]+"));
973 const QString separator = QStringLiteral(";");
974 Q_ASSERT(filterSeparatorRE.isValid());
975#endif
976
977 // Split filter specification as 'Texts (*.txt[;] *.doc)', '*.txt[;] *.doc'
978 // into description and filters specification as '*.txt;*.doc'
979 for (const QString &filterString : filters) {
980 const int openingParenPos = filterString.lastIndexOf(u'(');
981 const int closingParenPos = openingParenPos != -1 ?
982 filterString.indexOf(u')', openingParenPos + 1) : -1;
983 FilterSpec filterSpec;
984 filterSpec.filter = closingParenPos == -1 ?
985 filterString :
986 filterString.mid(openingParenPos + 1, closingParenPos - openingParenPos - 1).trimmed();
987 if (filterSpec.filter.isEmpty())
988 filterSpec.filter += u'*';
989#if QT_CONFIG(regularexpression)
990 filterSpec.filter.replace(filterSeparatorRE, separator);
991#else
992 filterSpec.filter.replace(u' ', u';');
993#endif
994 filterSpec.description = filterString;
995 if (hideFilterDetails && openingParenPos != -1) { // Do not show pattern in description
996 filterSpec.description.truncate(openingParenPos);
997 while (filterSpec.description.endsWith(u' '))
998 filterSpec.description.truncate(filterSpec.description.size() - 1);
999 }
1000 *totalStringLength += filterSpec.filter.size() + filterSpec.description.size();
1001 result.push_back(filterSpec);
1002 }
1003 return result;
1004}
1005
1006void QWindowsNativeFileDialogBase::setNameFilters(const QStringList &filters)
1007{
1008 /* Populates an array of COMDLG_FILTERSPEC from list of filters,
1009 * store the strings in a flat, contiguous buffer. */
1010 m_nameFilters = filters;
1011 int totalStringLength = 0;
1012 const QList<FilterSpec> specs = filterSpecs(filters, m_hideFiltersDetails, &totalStringLength);
1013 const int size = specs.size();
1014
1015 QScopedArrayPointer<WCHAR> buffer(new WCHAR[totalStringLength + 2 * size]);
1016 QScopedArrayPointer<COMDLG_FILTERSPEC> comFilterSpec(new COMDLG_FILTERSPEC[size]);
1017
1018 WCHAR *ptr = buffer.data();
1019 // Split filter specification as 'Texts (*.txt[;] *.doc)'
1020 // into description and filters specification as '*.txt;*.doc'
1021
1022 for (int i = 0; i < size; ++i) {
1023 // Display glitch (CLSID only): Any filter not filtering on suffix (such as
1024 // '*', 'a.*') will be duplicated in combo: 'All files (*) (*)',
1025 // 'AAA files (a.*) (a.*)'
1026 QString description = specs[i].description;
1027 const QString &filter = specs[i].filter;
1028 if (!m_hideFiltersDetails && !filter.startsWith(u"*.")) {
1029 const int pos = description.lastIndexOf(u'(');
1030 if (pos > 0) {
1031 description.truncate(pos);
1032 while (!description.isEmpty() && description.back().isSpace())
1033 description.chop(1);
1034 }
1035 }
1036 // Add to buffer.
1037 comFilterSpec[i].pszName = ptr;
1038 ptr += description.toWCharArray(ptr);
1039 *ptr++ = 0;
1040 comFilterSpec[i].pszSpec = ptr;
1041 ptr += specs[i].filter.toWCharArray(ptr);
1042 *ptr++ = 0;
1043 }
1044
1045 m_fileDialog->SetFileTypes(size, comFilterSpec.data());
1046}
1047
1049{
1050 setDefaultSuffixSys(s);
1051 m_hasDefaultSuffix = !s.isEmpty();
1052}
1053
1055{
1056 // If this parameter is non-empty, it will be appended by the dialog for the 'Any files'
1057 // filter ('*'). If this parameter is non-empty and the current filter has a suffix,
1058 // the dialog will append the filter's suffix.
1059 auto *wSuffix = const_cast<wchar_t *>(reinterpret_cast<const wchar_t *>(s.utf16()));
1060 m_fileDialog->SetDefaultExtension(wSuffix);
1061}
1062
1063static inline IFileDialog2 *getFileDialog2(IFileDialog *fileDialog)
1064{
1065 IFileDialog2 *result;
1066 return SUCCEEDED(fileDialog->QueryInterface(IID_IFileDialog2, reinterpret_cast<void **>(&result)))
1067 ? result : nullptr;
1068}
1069
1070void QWindowsNativeFileDialogBase::setLabelText(QFileDialogOptions::DialogLabel l, const QString &text)
1071{
1072 auto *wText = const_cast<wchar_t *>(reinterpret_cast<const wchar_t *>(text.utf16()));
1073 switch (l) {
1074 case QFileDialogOptions::FileName:
1075 m_fileDialog->SetFileNameLabel(wText);
1076 break;
1077 case QFileDialogOptions::Accept:
1078 m_fileDialog->SetOkButtonLabel(wText);
1079 break;
1080 case QFileDialogOptions::Reject:
1081 if (IFileDialog2 *dialog2 = getFileDialog2(m_fileDialog)) {
1082 dialog2->SetCancelButtonLabel(wText);
1083 dialog2->Release();
1084 }
1085 break;
1086 case QFileDialogOptions::LookIn:
1087 case QFileDialogOptions::FileType:
1088 case QFileDialogOptions::DialogLabelCount:
1089 break;
1090 }
1091}
1092
1093static bool isHexRange(const QString& s, int start, int end)
1094{
1095 for (;start < end; ++start) {
1096 QChar ch = s.at(start);
1097 if (!(ch.isDigit()
1098 || (ch >= u'a' && ch <= u'f')
1099 || (ch >= u'A' && ch <= u'F')))
1100 return false;
1101 }
1102 return true;
1103}
1104
1105static inline bool isClsid(const QString &s)
1106{
1107 // detect "374DE290-123F-4565-9164-39C4925E467B".
1108 const QChar dash(u'-');
1109 return s.size() == 36
1110 && isHexRange(s, 0, 8)
1111 && s.at(8) == dash
1112 && isHexRange(s, 9, 13)
1113 && s.at(13) == dash
1114 && isHexRange(s, 14, 18)
1115 && s.at(18) == dash
1116 && isHexRange(s, 19, 23)
1117 && s.at(23) == dash
1118 && isHexRange(s, 24, 36);
1119}
1120
1121void QWindowsNativeFileDialogBase::selectFile(const QString &fileName) const
1122{
1123 // Hack to prevent CLSIDs from being set as file name due to
1124 // QFileDialogPrivate::initialSelection() being QString-based.
1125 if (!isClsid(fileName))
1126 m_fileDialog->SetFileName((wchar_t*)fileName.utf16());
1127}
1128
1129// Return the index of the selected filter, accounting for QFileDialog
1130// sometimes stripping the filter specification depending on the
1131// hideFilterDetails setting.
1132static int indexOfNameFilter(const QStringList &filters, const QString &needle)
1133{
1134 const int index = filters.indexOf(needle);
1135 if (index >= 0)
1136 return index;
1137 for (int i = 0; i < filters.size(); ++i)
1138 if (filters.at(i).startsWith(needle))
1139 return i;
1140 return -1;
1141}
1142
1144{
1145 if (filter.isEmpty())
1146 return;
1147 const int index = indexOfNameFilter(m_nameFilters, filter);
1148 if (index < 0) {
1149 qWarning("%s: Invalid parameter '%s' not found in '%s'.",
1150 __FUNCTION__, qPrintable(filter),
1151 qPrintable(m_nameFilters.join(u", ")));
1152 return;
1153 }
1154 m_fileDialog->SetFileTypeIndex(index + 1); // one-based.
1155}
1156
1158{
1159 UINT uIndex = 0;
1160 if (SUCCEEDED(m_fileDialog->GetFileTypeIndex(&uIndex))) {
1161 const int index = uIndex - 1; // one-based
1162 if (index < m_nameFilters.size())
1163 return m_nameFilters.at(index);
1164 }
1165 return QString();
1166}
1167
1169{
1170 if (item) {
1171 const QUrl directory = QWindowsShellItem(item).url();
1172 m_data.setDirectory(directory);
1173 emit directoryEntered(directory);
1174 }
1175}
1176
1178{
1179 const QList<QUrl> current = selectedFiles();
1180 m_data.setSelectedFiles(current);
1181 qCDebug(lcQpaDialogs) << __FUNCTION__ << current << current.size();
1182
1183 if (current.size() == 1)
1184 emit currentChanged(current.front());
1185}
1186
1188{
1189 const QString filter = selectedNameFilter();
1190 m_data.setSelectedNameFilter(filter);
1191 emit filterSelected(filter);
1192}
1193
1195{
1196 // Store selected files as GetResults() returns invalid data after the dialog closes.
1197 m_data.setSelectedFiles(dialogResult());
1198 return true;
1199}
1200
1202{
1203 m_fileDialog->Close(S_OK);
1204 // IFileDialog::Close() does not work unless invoked from a callback.
1205 // Try to find the window and send it a WM_CLOSE in addition.
1206 const HWND hwnd = QWindowsDialogs::getHWND(m_fileDialog);
1207 qCDebug(lcQpaDialogs) << __FUNCTION__ << "closing" << hwnd;
1208 if (hwnd && IsWindowVisible(hwnd))
1209 PostMessageW(hwnd, WM_CLOSE, 0, 0);
1210}
1211
1213{
1214 m_nativeFileDialog->onFolderChange(item);
1215 return S_OK;
1216}
1217
1219{
1220 m_nativeFileDialog->onSelectionChange();
1221 return S_OK;
1222}
1223
1225{
1226 m_nativeFileDialog->onTypeChange();
1227 return S_OK;
1228}
1229
1231{
1232 return m_nativeFileDialog->onFileOk() ? S_OK : S_FALSE;
1233}
1234
1235/*!
1236 \class QWindowsNativeSaveFileDialog
1237 \brief Windows native file save dialog wrapper around IFileSaveDialog.
1238
1239 Implements single-selection methods.
1240
1241 \internal
1242*/
1243
1254
1255// Return the first suffix from the name filter "Foo files (*.foo;*.bar)" -> "foo".
1256// Also handles the simple name filter case "*.txt" -> "txt"
1257static inline QString suffixFromFilter(const QString &filter)
1258{
1259 int suffixPos = filter.indexOf(u"*.");
1260 if (suffixPos < 0)
1261 return QString();
1262 suffixPos += 2;
1263 int endPos = filter.indexOf(u' ', suffixPos + 1);
1264 if (endPos < 0)
1265 endPos = filter.indexOf(u';', suffixPos + 1);
1266 if (endPos < 0)
1267 endPos = filter.indexOf(u')', suffixPos + 1);
1268 if (endPos < 0)
1269 endPos = filter.size();
1270 return filter.mid(suffixPos, endPos - suffixPos);
1271}
1272
1273void QWindowsNativeSaveFileDialog::setNameFilters(const QStringList &f)
1274{
1275 QWindowsNativeFileDialogBase::setNameFilters(f);
1276 // QTBUG-31381, QTBUG-30748: IFileDialog will update the suffix of the selected name
1277 // filter only if a default suffix is set (see docs). Set the first available
1278 // suffix unless we have a defaultSuffix.
1279 if (!hasDefaultSuffix()) {
1280 for (const QString &filter : f) {
1281 const QString suffix = suffixFromFilter(filter);
1282 if (!suffix.isEmpty()) {
1283 setDefaultSuffixSys(suffix);
1284 break;
1285 }
1286 }
1287 } // m_hasDefaultSuffix
1288}
1289
1291{
1292 QList<QUrl> result;
1293 IShellItem *item = nullptr;
1294 if (SUCCEEDED(fileDialog()->GetResult(&item)) && item)
1295 result.append(QWindowsShellItem(item).url());
1296 return result;
1297}
1298
1300{
1301 QList<QUrl> result;
1302 IShellItem *item = nullptr;
1303 const HRESULT hr = fileDialog()->GetCurrentSelection(&item);
1304 if (SUCCEEDED(hr) && item) {
1305 result.append(QWindowsShellItem(item).url());
1306 item->Release();
1307 }
1308 return result;
1309}
1310
1311/*!
1312 \class QWindowsNativeOpenFileDialog
1313 \brief Windows native file save dialog wrapper around IFileOpenDialog.
1314
1315 Implements multi-selection methods.
1316
1317 \internal
1318*/
1319
1321{
1322public:
1325 QList<QUrl> selectedFiles() const override;
1326 QList<QUrl> dialogResult() const override;
1327
1328private:
1329 inline IFileOpenDialog *openFileDialog() const
1330 { return static_cast<IFileOpenDialog *>(fileDialog()); }
1331};
1332
1333// Helpers for managing a list of temporary copies of items with no
1334// file system representation (SFGAO_FILESYSTEM unset, for example devices
1335// using MTP) returned by IFileOpenDialog. This emulates the behavior
1336// of the Win32 API GetOpenFileName() used in Qt 4 (QTBUG-57070).
1337
1339
1341{
1342 for (const QString &file : std::as_const(*temporaryItemCopies()))
1343 QFile::remove(file);
1344}
1345
1346// Determine temporary file pattern from a shell item's display
1347// name. This can be a URL.
1348
1349static bool validFileNameCharacter(QChar c)
1350{
1351 return c.isLetterOrNumber() || c == u'_' || c == u'-';
1352}
1353
1355{
1356 const int lastSlash = qMax(name.lastIndexOf(u'/'),
1357 name.lastIndexOf(u'\\'));
1358 if (lastSlash != -1)
1359 name.remove(0, lastSlash + 1);
1360
1361 int lastDot = name.lastIndexOf(u'.');
1362 if (lastDot < 0)
1363 lastDot = name.size();
1364 name.insert(lastDot, "_XXXXXX"_L1);
1365
1366 for (int i = lastDot - 1; i >= 0; --i) {
1367 if (!validFileNameCharacter(name.at(i)))
1368 name[i] = u'_';
1369 }
1370
1371 name.prepend(QDir::tempPath() + u'/');
1372 return name;
1373}
1374
1375static QString createTemporaryItemCopy(QWindowsShellItem &qItem, QString *errorMessage)
1376{
1377 if (!qItem.canStream()) {
1378 *errorMessage = "Item not streamable"_L1;
1379 return QString();
1380 }
1381
1382 QTemporaryFile targetFile(tempFilePattern(qItem.normalDisplay()));
1383 targetFile.setAutoRemove(false);
1384 if (!targetFile.open()) {
1385 *errorMessage = "Cannot create temporary file: "_L1
1386 + targetFile.errorString();
1387 return QString();
1388 }
1389 if (!qItem.copyData(&targetFile, errorMessage))
1390 return QString();
1391 const QString result = targetFile.fileName();
1392 if (temporaryItemCopies()->isEmpty())
1393 qAddPostRoutine(cleanupTemporaryItemCopies);
1394 temporaryItemCopies()->append(result);
1395 return result;
1396}
1397
1398static QUrl itemToDialogUrl(QWindowsShellItem &qItem, QString *errorMessage)
1399{
1400 QUrl url = qItem.url();
1401 if (url.isLocalFile() || url.scheme().startsWith(u"http"))
1402 return url;
1403 const QString path = qItem.path();
1404 if (path.isEmpty() && !qItem.isDir() && qItem.canStream()) {
1405 const QString temporaryCopy = createTemporaryItemCopy(qItem, errorMessage);
1406 if (temporaryCopy.isEmpty()) {
1407 QDebug(errorMessage).noquote() << "Unable to create a local copy of"
1408 << qItem << ": " << errorMessage;
1409 return QUrl();
1410 }
1411 return QUrl::fromLocalFile(temporaryCopy);
1412 }
1413 if (!url.isValid())
1414 QDebug(errorMessage).noquote() << "Invalid URL obtained from" << qItem;
1415 return url;
1416}
1417
1419{
1420 QList<QUrl> result;
1421 IShellItemArray *items = nullptr;
1422 if (SUCCEEDED(openFileDialog()->GetResults(&items)) && items) {
1423 QString errorMessage;
1424 for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) {
1425 QWindowsShellItem qItem(item);
1426 const QUrl url = itemToDialogUrl(qItem, &errorMessage);
1427 if (!url.isValid()) {
1428 qWarning("%s", qPrintable(errorMessage));
1429 result.clear();
1430 break;
1431 }
1432 result.append(url);
1433 }
1434 }
1435 return result;
1436}
1437
1439{
1440 QList<QUrl> result;
1441 IShellItemArray *items = nullptr;
1442 const HRESULT hr = openFileDialog()->GetSelectedItems(&items);
1443 if (SUCCEEDED(hr) && items) {
1444 for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) {
1445 const QWindowsShellItem qItem(item);
1446 const QUrl url = qItem.url();
1447 if (url.isValid())
1448 result.append(url);
1449 else
1450 qWarning().nospace() << __FUNCTION__<< ": Unable to obtain URL of " << qItem;
1451 }
1452 }
1453 return result;
1454}
1455
1456/*!
1457 \brief Factory method for QWindowsNativeFileDialogBase returning
1458 QWindowsNativeOpenFileDialog or QWindowsNativeSaveFileDialog depending on
1459 QFileDialog::AcceptMode.
1460*/
1461
1462QWindowsNativeFileDialogBase *QWindowsNativeFileDialogBase::create(QFileDialogOptions::AcceptMode am,
1463 const QWindowsFileDialogSharedData &data)
1464{
1465 QWindowsNativeFileDialogBase *result = nullptr;
1466 if (am == QFileDialogOptions::AcceptOpen) {
1467 result = new QWindowsNativeOpenFileDialog(data);
1468 if (!result->init(CLSID_FileOpenDialog, IID_IFileOpenDialog)) {
1469 delete result;
1470 return nullptr;
1471 }
1472 } else {
1473 result = new QWindowsNativeSaveFileDialog(data);
1474 if (!result->init(CLSID_FileSaveDialog, IID_IFileSaveDialog)) {
1475 delete result;
1476 return nullptr;
1477 }
1478 }
1479 return result;
1480}
1481
1482/*!
1483 \class QWindowsFileDialogHelper
1484 \brief Helper for native Windows file dialogs
1485
1486 For Qt 4 compatibility, do not create native non-modal dialogs on widgets,
1487 but only on QQuickWindows, which do not have a fallback.
1488
1489 \internal
1490*/
1491
1493{
1494public:
1496 bool supportsNonModalDialog(const QWindow * /* parent */ = nullptr) const override { return false; }
1498 { return false; }
1499 void setDirectory(const QUrl &directory) override;
1500 QUrl directory() const override;
1501 void selectFile(const QUrl &filename) override;
1502 QList<QUrl> selectedFiles() const override;
1504 void selectNameFilter(const QString &filter) override;
1505 QString selectedNameFilter() const override;
1506
1507private:
1509 inline QWindowsNativeFileDialogBase *nativeFileDialog() const
1510 { return static_cast<QWindowsNativeFileDialogBase *>(nativeDialog()); }
1511
1512 // Cache for the case no native dialog is created.
1514};
1515
1517{
1518 QWindowsNativeFileDialogBase *result = QWindowsNativeFileDialogBase::create(options()->acceptMode(), m_data);
1519 if (!result)
1520 return nullptr;
1521 QObject::connect(result, &QWindowsNativeDialogBase::accepted, this, &QPlatformDialogHelper::accept);
1522 QObject::connect(result, &QWindowsNativeDialogBase::rejected, this, &QPlatformDialogHelper::reject);
1523 QObject::connect(result, &QWindowsNativeFileDialogBase::directoryEntered,
1524 this, &QPlatformFileDialogHelper::directoryEntered);
1525 QObject::connect(result, &QWindowsNativeFileDialogBase::currentChanged,
1526 this, &QPlatformFileDialogHelper::currentChanged);
1527 QObject::connect(result, &QWindowsNativeFileDialogBase::filterSelected,
1528 this, &QPlatformFileDialogHelper::filterSelected);
1529
1530 // Apply settings.
1531 const QSharedPointer<QFileDialogOptions> &opts = options();
1532 m_data.fromOptions(opts);
1533 const QFileDialogOptions::FileMode mode = opts->fileMode();
1534 result->setWindowTitle(opts->windowTitle());
1535 result->setMode(mode, opts->acceptMode(), opts->options());
1536 result->setHideFiltersDetails(opts->testOption(QFileDialogOptions::HideNameFilterDetails));
1537 const QStringList nameFilters = opts->nameFilters();
1538 if (!nameFilters.isEmpty())
1539 result->setNameFilters(nameFilters);
1540 if (opts->isLabelExplicitlySet(QFileDialogOptions::FileName))
1541 result->setLabelText(QFileDialogOptions::FileName, opts->labelText(QFileDialogOptions::FileName));
1542 if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
1543 result->setLabelText(QFileDialogOptions::Accept, opts->labelText(QFileDialogOptions::Accept));
1544 if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
1545 result->setLabelText(QFileDialogOptions::Reject, opts->labelText(QFileDialogOptions::Reject));
1546 result->updateDirectory();
1548 const QList<QUrl> initialSelection = opts->initiallySelectedFiles();
1549 if (!initialSelection.empty()) {
1550 const QUrl &url = initialSelection.constFirst();
1551 if (url.isLocalFile()) {
1552 QFileInfo info(url.toLocalFile());
1553 if (!info.isDir())
1554 result->selectFile(info.fileName());
1555 } else {
1556 result->selectFile(url.fileName());
1557 }
1558 }
1559 // No need to select initialNameFilter if mode is Dir
1560 if (mode != QFileDialogOptions::Directory && mode != QFileDialogOptions::DirectoryOnly) {
1561 const QString initialNameFilter = opts->initiallySelectedNameFilter();
1562 if (!initialNameFilter.isEmpty())
1563 result->selectNameFilter(initialNameFilter);
1564 }
1565 const QString defaultSuffix = opts->defaultSuffix();
1566 if (!defaultSuffix.isEmpty())
1567 result->setDefaultSuffix(defaultSuffix);
1568 return result;
1569}
1570
1571void QWindowsFileDialogHelper::setDirectory(const QUrl &directory)
1572{
1573 qCDebug(lcQpaDialogs) << __FUNCTION__ << directory.toString();
1574
1575 m_data.setDirectory(directory);
1576 if (hasNativeDialog())
1577 nativeFileDialog()->updateDirectory();
1578}
1579
1581{
1582 return m_data.directory();
1583}
1584
1585void QWindowsFileDialogHelper::selectFile(const QUrl &fileName)
1586{
1587 qCDebug(lcQpaDialogs) << __FUNCTION__ << fileName.toString();
1588
1589 if (hasNativeDialog()) // Might be invoked from the QFileDialog constructor.
1590 nativeFileDialog()->selectFile(fileName.fileName());
1591}
1592
1594{
1595 return m_data.selectedFiles();
1596}
1597
1599{
1600 qCDebug(lcQpaDialogs) << __FUNCTION__;
1601}
1602
1603void QWindowsFileDialogHelper::selectNameFilter(const QString &filter)
1604{
1605 m_data.setSelectedNameFilter(filter);
1606 if (hasNativeDialog())
1607 nativeFileDialog()->updateSelectedNameFilter();
1608}
1609
1611{
1612 return m_data.selectedNameFilter();
1613}
1614
1615/*!
1616 \class QWindowsXpNativeFileDialog
1617 \brief Native Windows directory dialog for Windows XP using SHlib-functions.
1618
1619 Uses the synchronous GetOpenFileNameW(), GetSaveFileNameW() from ComDlg32
1620 or SHBrowseForFolder() for directories.
1621
1622 \internal
1623 \sa QWindowsXpFileDialogHelper
1624
1625*/
1626
1628{
1629 Q_OBJECT
1630public:
1632
1633 static QWindowsXpNativeFileDialog *create(const OptionsPtr &options, const QWindowsFileDialogSharedData &data);
1634
1635 void setWindowTitle(const QString &t) override { m_title = t; }
1636 void doExec(HWND owner = nullptr) override;
1637
1638 int existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam);
1639
1640public slots:
1641 void close() override {}
1642
1643private:
1645 void populateOpenFileName(OPENFILENAME *ofn, HWND owner) const;
1646 QList<QUrl> execExistingDir(HWND owner);
1647 QList<QUrl> execFileNames(HWND owner, int *selectedFilterIndex) const;
1648
1649 const OptionsPtr m_options;
1650 QString m_title;
1651 QPlatformDialogHelper::DialogCode m_result;
1653};
1654
1656{
1657 return new QWindowsXpNativeFileDialog(options, data);
1658}
1659
1660QWindowsXpNativeFileDialog::QWindowsXpNativeFileDialog(const OptionsPtr &options,
1661 const QWindowsFileDialogSharedData &data) :
1662 m_options(options), m_result(QPlatformDialogHelper::Rejected), m_data(data)
1663{
1664 setWindowTitle(m_options->windowTitle());
1665}
1666
1668{
1669 int selectedFilterIndex = -1;
1670 const QList<QUrl> selectedFiles =
1671 m_options->fileMode() == QFileDialogOptions::DirectoryOnly ?
1672 execExistingDir(owner) : execFileNames(owner, &selectedFilterIndex);
1673 m_data.setSelectedFiles(selectedFiles);
1675 if (selectedFiles.isEmpty()) {
1676 m_result = QPlatformDialogHelper::Rejected;
1677 emit rejected();
1678 } else {
1679 const QStringList nameFilters = m_options->nameFilters();
1680 if (selectedFilterIndex >= 0 && selectedFilterIndex < nameFilters.size())
1681 m_data.setSelectedNameFilter(nameFilters.at(selectedFilterIndex));
1682 const QUrl &firstFile = selectedFiles.constFirst();
1683 m_data.setDirectory(firstFile.adjusted(QUrl::RemoveFilename));
1684 m_result = QPlatformDialogHelper::Accepted;
1685 emit accepted();
1686 }
1687}
1688
1689// Callback for QWindowsNativeXpFileDialog directory dialog.
1690// MFC Directory Dialog. Contrib: Steve Williams (minor parts from Scott Powers)
1691
1692static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
1693{
1694 auto *dialog = reinterpret_cast<QWindowsXpNativeFileDialog *>(lpData);
1695 return dialog->existingDirCallback(hwnd, uMsg, lParam);
1696}
1697
1698int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam)
1699{
1700 switch (uMsg) {
1701 case BFFM_INITIALIZED: {
1702 if (!m_title.isEmpty())
1703 SetWindowText(hwnd, reinterpret_cast<const wchar_t *>(m_title.utf16()));
1704 const QString initialFile = QDir::toNativeSeparators(m_data.directory().toLocalFile());
1705 if (!initialFile.isEmpty())
1706 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, LPARAM(initialFile.utf16()));
1707 }
1708 break;
1709 case BFFM_SELCHANGED: {
1710 wchar_t path[MAX_PATH];
1711 const bool ok = SHGetPathFromIDList(reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path)
1712 && path[0];
1713 SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1);
1714 }
1715 break;
1716 }
1717 return 0;
1718}
1719
1720QList<QUrl> QWindowsXpNativeFileDialog::execExistingDir(HWND owner)
1721{
1722 BROWSEINFO bi;
1723 wchar_t initPath[MAX_PATH];
1724 initPath[0] = 0;
1725 bi.hwndOwner = owner;
1726 bi.pidlRoot = nullptr;
1727 bi.lpszTitle = nullptr;
1728 bi.pszDisplayName = initPath;
1729 bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE;
1730 bi.lpfn = xpFileDialogGetExistingDirCallbackProc;
1731 bi.lParam = LPARAM(this);
1732 QList<QUrl> selectedFiles;
1733 if (const auto pItemIDList = SHBrowseForFolder(&bi)) {
1734 wchar_t path[MAX_PATH];
1735 path[0] = 0;
1736 if (SHGetPathFromIDList(pItemIDList, path) && path[0])
1737 selectedFiles.push_back(QUrl::fromLocalFile(QDir::cleanPath(QString::fromWCharArray(path))));
1738 IMalloc *pMalloc;
1739 if (SHGetMalloc(&pMalloc) == NOERROR) {
1740 pMalloc->Free(pItemIDList);
1741 pMalloc->Release();
1742 }
1743 }
1744 return selectedFiles;
1745}
1746
1747// Open/Save files
1748void QWindowsXpNativeFileDialog::populateOpenFileName(OPENFILENAME *ofn, HWND owner) const
1749{
1750 ZeroMemory(ofn, sizeof(OPENFILENAME));
1751 ofn->lStructSize = sizeof(OPENFILENAME);
1752 ofn->hwndOwner = owner;
1753
1754 // Create a buffer with the filter strings.
1755 int totalStringLength = 0;
1756 const QList<FilterSpec> specs =
1757 filterSpecs(m_options->nameFilters(), m_options->options() & QFileDialogOptions::HideNameFilterDetails, &totalStringLength);
1758 const int size = specs.size();
1759 auto *ptr = new wchar_t[totalStringLength + 2 * size + 1];
1760 ofn->lpstrFilter = ptr;
1761 for (const FilterSpec &spec : specs) {
1762 ptr += spec.description.toWCharArray(ptr);
1763 *ptr++ = 0;
1764 ptr += spec.filter.toWCharArray(ptr);
1765 *ptr++ = 0;
1766 }
1767 *ptr = 0;
1768 const int nameFilterIndex = indexOfNameFilter(m_options->nameFilters(), m_data.selectedNameFilter());
1769 if (nameFilterIndex >= 0)
1770 ofn->nFilterIndex = nameFilterIndex + 1; // 1..n based.
1771 // lpstrFile receives the initial selection and is the buffer
1772 // for the target. If it contains any invalid character, the dialog
1773 // will not show.
1774 ofn->nMaxFile = 65535;
1775 QString initiallySelectedFile = m_data.selectedFile();
1776 initiallySelectedFile.remove(u'<');
1777 initiallySelectedFile.remove(u'>');
1778 initiallySelectedFile.remove(u'"');
1779 initiallySelectedFile.remove(u'|');
1780 ofn->lpstrFile = qStringToWCharArray(QDir::toNativeSeparators(initiallySelectedFile), ofn->nMaxFile);
1781 ofn->lpstrInitialDir = qStringToWCharArray(QDir::toNativeSeparators(m_data.directory().toLocalFile()));
1782 ofn->lpstrTitle = (wchar_t*)m_title.utf16();
1783 // Determine lpstrDefExt. Note that the current MSDN docs document this
1784 // member wrong. It should rather be documented as "the default extension
1785 // if no extension was given and if the current filter does not have an
1786 // extension (e.g (*)). If the current filter has an extension, use
1787 // the extension of the current filter".
1788 if (m_options->acceptMode() == QFileDialogOptions::AcceptSave) {
1789 QString defaultSuffix = m_options->defaultSuffix();
1790 if (defaultSuffix.startsWith(u'.'))
1791 defaultSuffix.remove(0, 1);
1792 // QTBUG-33156, also create empty strings to trigger the appending mechanism.
1793 ofn->lpstrDefExt = qStringToWCharArray(defaultSuffix);
1794 }
1795 // Flags.
1796 ofn->Flags = (OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_PATHMUSTEXIST);
1797 if (m_options->fileMode() == QFileDialogOptions::ExistingFile
1798 || m_options->fileMode() == QFileDialogOptions::ExistingFiles)
1799 ofn->Flags |= (OFN_FILEMUSTEXIST);
1800 if (m_options->fileMode() == QFileDialogOptions::ExistingFiles)
1801 ofn->Flags |= (OFN_ALLOWMULTISELECT);
1802 if (!(m_options->options() & QFileDialogOptions::DontConfirmOverwrite))
1803 ofn->Flags |= OFN_OVERWRITEPROMPT;
1804}
1805
1806QList<QUrl> QWindowsXpNativeFileDialog::execFileNames(HWND owner, int *selectedFilterIndex) const
1807{
1808 *selectedFilterIndex = -1;
1809 OPENFILENAME ofn;
1810 populateOpenFileName(&ofn, owner);
1811 QList<QUrl> result;
1812 const bool isSave = m_options->acceptMode() == QFileDialogOptions::AcceptSave;
1813 if (isSave ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn)) {
1814 *selectedFilterIndex = ofn.nFilterIndex - 1;
1815 const QString dir = QDir::cleanPath(QString::fromWCharArray(ofn.lpstrFile));
1816 result.push_back(QUrl::fromLocalFile(dir));
1817 // For multiselection, the first item is the path followed
1818 // by "\0<file1>\0<file2>\0\0".
1819 if (ofn.Flags & (OFN_ALLOWMULTISELECT)) {
1820 wchar_t *ptr = ofn.lpstrFile + dir.size() + 1;
1821 if (*ptr) {
1822 result.pop_front();
1823 const QString path = dir + u'/';
1824 while (*ptr) {
1825 const QString fileName = QString::fromWCharArray(ptr);
1826 result.push_back(QUrl::fromLocalFile(path + fileName));
1827 ptr += fileName.size() + 1;
1828 } // extract multiple files
1829 } // has multiple files
1830 } // multiple flag set
1831 }
1832 delete [] ofn.lpstrFile;
1833 delete [] ofn.lpstrInitialDir;
1834 delete [] ofn.lpstrFilter;
1835 delete [] ofn.lpstrDefExt;
1836 return result;
1837}
1838
1839/*!
1840 \class QWindowsXpFileDialogHelper
1841 \brief Dialog helper using QWindowsXpNativeFileDialog
1842
1843 \sa QWindowsXpNativeFileDialog
1844 \internal
1845*/
1846
1848{
1849public:
1851 bool supportsNonModalDialog(const QWindow * /* parent */ = nullptr) const override { return false; }
1853 { return true; }
1854 void setDirectory(const QUrl &directory) override;
1855 QUrl directory() const override;
1856 void selectFile(const QUrl &url) override;
1857 QList<QUrl> selectedFiles() const override;
1859 void selectNameFilter(const QString &) override;
1860 QString selectedNameFilter() const override;
1861
1862private:
1864 inline QWindowsXpNativeFileDialog *nativeFileDialog() const
1865 { return static_cast<QWindowsXpNativeFileDialog *>(nativeDialog()); }
1866
1868};
1869
1871{
1872 m_data.fromOptions(options());
1873 if (QWindowsXpNativeFileDialog *result = QWindowsXpNativeFileDialog::create(options(), m_data)) {
1874 QObject::connect(result, &QWindowsNativeDialogBase::accepted, this, &QPlatformDialogHelper::accept);
1875 QObject::connect(result, &QWindowsNativeDialogBase::rejected, this, &QPlatformDialogHelper::reject);
1876 return result;
1877 }
1878 return nullptr;
1879}
1880
1881void QWindowsXpFileDialogHelper::setDirectory(const QUrl &directory)
1882{
1883 m_data.setDirectory(directory); // Dialog cannot be updated at run-time.
1884}
1885
1887{
1888 return m_data.directory();
1889}
1890
1892{
1893 m_data.setSelectedFiles(QList<QUrl>() << url); // Dialog cannot be updated at run-time.
1894}
1895
1897{
1898 return m_data.selectedFiles();
1899}
1900
1902{
1903 m_data.setSelectedNameFilter(f); // Dialog cannot be updated at run-time.
1904}
1905
1907{
1908 return m_data.selectedNameFilter();
1909}
1910
1911/*!
1912 \class QWindowsNativeColorDialog
1913 \brief Native Windows color dialog.
1914
1915 Wrapper around Comdlg32's ChooseColor() function.
1916 Not currently in use as QColorDialog is equivalent.
1917
1918 \sa QWindowsColorDialogHelper
1919 \sa #define USE_NATIVE_COLOR_DIALOG
1920 \internal
1921*/
1922
1923using SharedPointerColor = QSharedPointer<QColor>;
1924
1925#ifdef USE_NATIVE_COLOR_DIALOG
1926class QWindowsNativeColorDialog : public QWindowsNativeDialogBase
1927{
1928 Q_OBJECT
1929public:
1930 enum { CustomColorCount = 16 };
1931
1932 explicit QWindowsNativeColorDialog(const SharedPointerColor &color);
1933
1934 void setWindowTitle(const QString &) override {}
1935
1936public slots:
1937 void close() override {}
1938
1939private:
1940 void doExec(HWND owner = 0) override;
1941
1942 COLORREF m_customColors[CustomColorCount];
1943 QPlatformDialogHelper::DialogCode m_code;
1944 SharedPointerColor m_color;
1945};
1946
1947QWindowsNativeColorDialog::QWindowsNativeColorDialog(const SharedPointerColor &color) :
1948 m_code(QPlatformDialogHelper::Rejected), m_color(color)
1949{
1950 std::fill(m_customColors, m_customColors + 16, COLORREF(0));
1951}
1952
1953void QWindowsNativeColorDialog::doExec(HWND owner)
1954{
1955 CHOOSECOLOR chooseColor;
1956 ZeroMemory(&chooseColor, sizeof(chooseColor));
1957 chooseColor.lStructSize = sizeof(chooseColor);
1958 chooseColor.hwndOwner = owner;
1959 chooseColor.lpCustColors = m_customColors;
1960 QRgb *qCustomColors = QColorDialogOptions::customColors();
1961 const int customColorCount = qMin(QColorDialogOptions::customColorCount(),
1962 int(CustomColorCount));
1963 for (int c= 0; c < customColorCount; ++c)
1964 m_customColors[c] = qColorToCOLORREF(QColor(qCustomColors[c]));
1965 chooseColor.rgbResult = qColorToCOLORREF(*m_color);
1966 chooseColor.Flags = CC_FULLOPEN | CC_RGBINIT;
1967 m_code = ChooseColorW(&chooseColor) ?
1968 QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected;
1969 QWindowsDialogs::eatMouseMove();
1970 if (m_code == QPlatformDialogHelper::Accepted) {
1971 *m_color = COLORREFToQColor(chooseColor.rgbResult);
1972 for (int c= 0; c < customColorCount; ++c)
1973 qCustomColors[c] = COLORREFToQColor(m_customColors[c]).rgb();
1974 emit accepted();
1975 } else {
1976 emit rejected();
1977 }
1978}
1979
1980/*!
1981 \class QWindowsColorDialogHelper
1982 \brief Helper for native Windows color dialogs
1983
1984 Not currently in use as QColorDialog is equivalent.
1985
1986 \sa #define USE_NATIVE_COLOR_DIALOG
1987 \sa QWindowsNativeColorDialog
1988 \internal
1989*/
1990
1991class QWindowsColorDialogHelper : public QWindowsDialogHelperBase<QPlatformColorDialogHelper>
1992{
1993public:
1994 QWindowsColorDialogHelper() : m_currentColor(new QColor) {}
1995
1996 virtual bool supportsNonModalDialog()
1997 { return false; }
1998
1999 virtual QColor currentColor() const { return *m_currentColor; }
2000 virtual void setCurrentColor(const QColor &c) { *m_currentColor = c; }
2001
2002private:
2003 inline QWindowsNativeColorDialog *nativeFileDialog() const
2004 { return static_cast<QWindowsNativeColorDialog *>(nativeDialog()); }
2005 virtual QWindowsNativeDialogBase *createNativeDialog();
2006
2007 SharedPointerColor m_currentColor;
2008};
2009
2010QWindowsNativeDialogBase *QWindowsColorDialogHelper::createNativeDialog()
2011{
2012 QWindowsNativeColorDialog *nativeDialog = new QWindowsNativeColorDialog(m_currentColor);
2013 nativeDialog->setWindowTitle(options()->windowTitle());
2014 connect(nativeDialog, &QWindowsNativeDialogBase::accepted, this, &QPlatformDialogHelper::accept);
2015 connect(nativeDialog, &QWindowsNativeDialogBase::rejected, this, &QPlatformDialogHelper::reject);
2016 return nativeDialog;
2017}
2018#endif // USE_NATIVE_COLOR_DIALOG
2019
2020namespace QWindowsDialogs {
2021
2022// QWindowsDialogHelperBase creation functions
2024{
2026 return false;
2027 switch (type) {
2029 return true;
2031#ifdef USE_NATIVE_COLOR_DIALOG
2032 return true;
2033#else
2034 break;
2035#endif
2038 break;
2039 default:
2040 break;
2041 }
2042 return false;
2043}
2044
2046{
2048 return nullptr;
2049 switch (type) {
2052 return new QWindowsXpFileDialogHelper();
2053 return new QWindowsFileDialogHelper;
2055#ifdef USE_NATIVE_COLOR_DIALOG
2056 return new QWindowsColorDialogHelper();
2057#else
2058 break;
2059#endif
2062 break;
2063 default:
2064 break;
2065 }
2066 return nullptr;
2067}
2068
2069} // namespace QWindowsDialogs
2070QT_END_NAMESPACE
2071
2072#include "qwindowsdialoghelpers.moc"
Helper for native Windows dialogs.
void timerEvent(QTimerEvent *) override
QWindowsNativeDialogBase * nativeDialog() const
Run a non-modal native dialog in a separate thread.
QWindowsDialogThread(const QWindowsNativeDialogBasePtr &d, HWND owner)
Helper for native Windows file dialogs.
QWindowsNativeDialogBase * createNativeDialog() override
QString selectedNameFilter() const override
bool supportsNonModalDialog(const QWindow *=nullptr) const override
bool defaultNameFilterDisables() const override
void setDirectory(const QUrl &directory) override
void selectFile(const QUrl &filename) override
QList< QUrl > selectedFiles() const override
void selectNameFilter(const QString &filter) override
Explicitly shared file dialog parameters that are not in QFileDialogOptions.
void fromOptions(const QSharedPointer< QFileDialogOptions > &o)
void setSelectedNameFilter(const QString &)
void setSelectedFiles(const QList< QUrl > &)
Base class for Windows native dialogs.
void exec(HWND owner=nullptr)
virtual void doExec(HWND owner=nullptr)=0
Windows native file dialog wrapper around IFileOpenDialog, IFileSaveDialog.
QWindowsFileDialogSharedData & data()
void setDirectory(const QUrl &directory)
void setLabelText(QFileDialogOptions::DialogLabel l, const QString &text)
void selectFile(const QString &fileName) const
void currentChanged(const QUrl &file)
QWindowsNativeFileDialogBase(const QWindowsFileDialogSharedData &data)
virtual void setNameFilters(const QStringList &f)
static IShellItem * shellItem(const QUrl &url)
virtual QList< QUrl > selectedFiles() const =0
void setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options)
void selectNameFilter(const QString &filter)
static QWindowsNativeFileDialogBase * create(QFileDialogOptions::AcceptMode am, const QWindowsFileDialogSharedData &data)
Factory method for QWindowsNativeFileDialogBase returning QWindowsNativeOpenFileDialog or QWindowsNat...
const QWindowsFileDialogSharedData & data() const
void setWindowTitle(const QString &title) override
bool init(const CLSID &clsId, const IID &iid)
void setDefaultSuffixSys(const QString &s)
void filterSelected(const QString &filter)
void doExec(HWND owner=nullptr) override
virtual QList< QUrl > dialogResult() const =0
Listens to IFileDialog events and forwards them to QWindowsNativeFileDialogBase.
IFACEMETHODIMP OnFileOk(IFileDialog *) override
IFACEMETHODIMP OnFolderChange(IFileDialog *) override
QWindowsNativeFileDialogEventHandler(QWindowsNativeFileDialogBase *nativeFileDialog)
IFACEMETHODIMP OnShareViolation(IFileDialog *, IShellItem *, FDE_SHAREVIOLATION_RESPONSE *) override
IFACEMETHODIMP OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) override
IFACEMETHODIMP OnFolderChanging(IFileDialog *, IShellItem *) override
IFACEMETHODIMP OnSelectionChange(IFileDialog *) override
IFACEMETHODIMP OnTypeChange(IFileDialog *) override
Windows native file save dialog wrapper around IFileOpenDialog.
QWindowsNativeOpenFileDialog(const QWindowsFileDialogSharedData &data)
QList< QUrl > dialogResult() const override
QList< QUrl > selectedFiles() const override
Windows native file save dialog wrapper around IFileSaveDialog.
QList< QUrl > dialogResult() const override
QList< QUrl > selectedFiles() const override
Wrapper for IShellItem.
QWindowsShellItem(IShellItem *item)
static IShellItems itemsFromItemArray(IShellItemArray *items)
void format(QDebug &d) const
bool copyData(QIODevice *out, QString *errorMessage)
QString desktopAbsoluteParsing() const
Dialog helper using QWindowsXpNativeFileDialog.
void selectNameFilter(const QString &) override
void selectFile(const QUrl &url) override
bool defaultNameFilterDisables() const override
QList< QUrl > selectedFiles() const override
QWindowsXpFileDialogHelper()=default
bool supportsNonModalDialog(const QWindow *=nullptr) const override
QWindowsNativeDialogBase * createNativeDialog() override
QString selectedNameFilter() const override
void setDirectory(const QUrl &directory) override
Native Windows directory dialog for Windows XP using SHlib-functions.
static QWindowsXpNativeFileDialog * create(const OptionsPtr &options, const QWindowsFileDialogSharedData &data)
void setWindowTitle(const QString &t) override
int existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam)
void doExec(HWND owner=nullptr) override
bool useHelper(QPlatformTheme::DialogType type)
QPlatformDialogHelper * createHelper(QPlatformTheme::DialogType type)
HWND getHWND(IFileDialog *fileDialog)
void eatMouseMove()
After closing a windows dialog with a double click (i.e.
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
static bool validFileNameCharacter(QChar c)
static IFileDialog2 * getFileDialog2(IFileDialog *fileDialog)
static int indexOfNameFilter(const QStringList &filters, const QString &needle)
static void cleanupTemporaryItemCopies()
static bool isClsid(const QString &s)
static QList< FilterSpec > filterSpecs(const QStringList &filters, bool hideFilterDetails, int *totalStringLength)
static QString createTemporaryItemCopy(QWindowsShellItem &qItem, QString *errorMessage)
static IShellLibrary * sHLoadLibraryFromItem(IShellItem *libraryItem, DWORD mode)
QString tempFilePattern(QString name)
static wchar_t * qStringToWCharArray(const QString &s, size_t reserveSize=0)
static bool isHexRange(const QString &s, int start, int end)
static QUrl itemToDialogUrl(QWindowsShellItem &qItem, QString *errorMessage)
static QString suffixFromFilter(const QString &filter)