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