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