48using namespace Qt::StringLiterals;
53 const size_t stringSize = s.size();
54 wchar_t *result =
new wchar_t[qMax(stringSize + 1, reserveSize)];
55 s.toWCharArray(result);
56 result[stringSize] = 0;
63
64
65
66
67
68
69
70
71
75 MSG msg = {
nullptr, 0, 0, 0, 0, {0, 0} };
76 while (PeekMessage(&msg,
nullptr, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE))
78 if (msg.message == WM_MOUSEMOVE)
79 PostMessage(msg.hwnd, msg.message, 0, msg.lParam);
80 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"triggered=" << (msg.message == WM_MOUSEMOVE);
85 IOleWindow *oleWindow =
nullptr;
86 if (FAILED(fileDialog->QueryInterface(IID_IOleWindow,
reinterpret_cast<
void **>(&oleWindow)))) {
87 qCWarning(lcQpaDialogs,
"Native file dialog: unable to query IID_IOleWindow interface.");
92 if (FAILED(oleWindow->GetWindow(&result)))
93 qCWarning(lcQpaDialogs,
"Native file dialog: unable to get dialog's window.");
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
137 void exec(HWND owner =
nullptr) { doExec(owner); m_executed =
true; }
150 virtual void doExec(HWND owner =
nullptr) = 0;
156
157
158
159
160
161
162
163
164
165
166
167
168
170template <
class BaseClass>
177template <
class BaseClass>
186 if (m_thread->wait(500))
189 qCCritical(lcQpaDialogs) <<
__FUNCTION__ <<
"Thread failed to finish.";
194template <
class BaseClass>
197 if (m_nativeDialog.isNull()) {
198 qWarning(
"%s invoked with no native dialog present.",
__FUNCTION__);
201 return m_nativeDialog.data();
204template <
class BaseClass>
210template <
class BaseClass>
215 if (m_nativeDialog.isNull() || m_nativeDialog->executed())
216 m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog(), &QObject::deleteLater);
217 return m_nativeDialog.data();
221
222
223
224
225
226
238 const QWindowsNativeDialogBasePtr m_dialog;
244 qCDebug(lcQpaDialogs) <<
'>' <<
__FUNCTION__;
245 QComHelper comInit(COINIT_APARTMENTTHREADED);
246 m_dialog->exec(m_owner);
247 qCDebug(lcQpaDialogs) <<
'<' <<
__FUNCTION__;
250template <
class BaseClass>
252 Qt::WindowModality windowModality,
255 const bool modal = (windowModality != Qt::NonModal);
257 parent = QGuiApplication::focusWindow();
259 m_ownerWindow = QWindowsWindow::handleOf(parent);
261 m_ownerWindow =
nullptr;
263 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"modal=" << modal
264 <<
" modal supported? " << supportsNonModalDialog(parent)
265 <<
"native=" << m_nativeDialog.data() <<
"owner" << m_ownerWindow;
266 if (!modal && !supportsNonModalDialog(parent))
268 if (!ensureNativeDialog())
276 m_timer.start(0ns,
this);
283template <
class BaseClass>
286 Q_ASSERT(!m_nativeDialog.isNull());
288 m_thread =
new QWindowsDialogThread(m_nativeDialog, m_ownerWindow);
293template <
class BaseClass>
299template <
class BaseClass>
302 if (m_nativeDialog) {
303 m_nativeDialog->close();
304 m_nativeDialog.clear();
306 m_ownerWindow =
nullptr;
309template <
class BaseClass>
312 qCDebug(lcQpaDialogs) <<
__FUNCTION__;
315 nd->exec(m_ownerWindow);
316 m_nativeDialog.clear();
321
322
323
324
325
326
327
328
329
330
331
348 class Data :
public QSharedData {
351 QString selectedNameFilter;
352 QList<QUrl> selectedFiles;
355 QExplicitlySharedDataPointer<Data> m_data;
360 m_data->mutex.lock();
361 const QUrl result = m_data->directory;
362 m_data->mutex.unlock();
368 QMutexLocker locker(&m_data->mutex);
369 m_data->directory = d;
374 m_data->mutex.lock();
375 const QString result = m_data->selectedNameFilter;
376 m_data->mutex.unlock();
382 QMutexLocker locker(&m_data->mutex);
383 m_data->selectedNameFilter = f;
388 m_data->mutex.lock();
389 const auto result = m_data->selectedFiles;
390 m_data->mutex.unlock();
396 const auto files = selectedFiles();
397 return files.isEmpty() ? QString() : files.front().toLocalFile();
402 QMutexLocker locker(&m_data->mutex);
403 m_data->selectedFiles = urls;
408 QMutexLocker locker(&m_data->mutex);
409 m_data->directory = o->initialDirectory();
410 m_data->selectedFiles = o->initiallySelectedFiles();
411 m_data->selectedNameFilter = o->initiallySelectedNameFilter();
415
416
417
418
419
420
421
422
423
439 FDE_SHAREVIOLATION_RESPONSE *)
override
450 m_nativeFileDialog(nativeFileDialog) {}
458 IFileDialogEvents *result;
460 if (FAILED(eventHandler->QueryInterface(IID_IFileDialogEvents,
reinterpret_cast<
void **>(&result)))) {
461 qErrnoWarning(
"Unable to obtain IFileDialogEvents");
464 eventHandler->Release();
469
470
471
472
473
474
484 {
return displayName(m_item, SIGDN_NORMALDISPLAY); }
486 {
return displayName(m_item, SIGDN_URL); }
488 {
return displayName(m_item, SIGDN_FILESYSPATH); }
490 {
return displayName(m_item, SIGDN_DESKTOPABSOLUTEPARSING); }
494 bool isFileSystem()
const {
return (m_attributes & SFGAO_FILESYSTEM) != 0; }
495 bool isDir()
const {
return (m_attributes & SFGAO_FOLDER) != 0; }
497 bool canStream()
const {
return (m_attributes & SFGAO_STREAM) != 0; }
499 bool copyData(QIODevice *out, QString *errorMessage);
503#ifndef QT_NO_DEBUG_STREAM
508 static QString displayName(IShellItem *item, SIGDN mode);
509 static QString libraryItemDefaultSaveFolder(IShellItem *item);
510 QUrl urlValue()
const;
520 SFGAOF mask = (SFGAO_CAPABILITYMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK);
523 if (FAILED(item->GetAttributes((SFGAO_STREAM | SFGAO_COMPRESSED), &m_attributes))) {
527 if (m_attributes & (SFGAO_STREAM | SFGAO_COMPRESSED))
528 mask &= ~SFGAO_HASSUBFOLDER;
529 if (FAILED(item->GetAttributes(mask, &m_attributes)))
537 return QDir::cleanPath(QWindowsShellItem::displayName(m_item, SIGDN_FILESYSPATH));
540 return QWindowsShellItem::libraryItemDefaultSaveFolder(m_item);
547 const QString urlString = displayName(m_item, SIGDN_URL);
548 if (!urlString.isEmpty()) {
549 const QUrl parsed = QUrl(urlString);
550 if (parsed.isValid()) {
553 qWarning(
"%s: Unable to decode URL \"%s\": %s",
__FUNCTION__,
554 qPrintable(urlString), qPrintable(parsed.errorString()));
563 const QString fsPath = path();
564 if (!fsPath.isEmpty())
565 return QUrl::fromLocalFile(fsPath);
566 const QUrl urlV = urlValue();
570 const QString data =
"data:text/plain;base64,"_L1
571 + QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64());
577 LPWSTR name =
nullptr;
579 if (SUCCEEDED(item->GetDisplayName(mode, &name))) {
580 result = QString::fromWCharArray(name);
590 if (FAILED(items->GetCount(&itemCount)) || itemCount == 0)
592 result.reserve(itemCount);
593 for (DWORD i = 0; i < itemCount; ++i) {
594 IShellItem *item =
nullptr;
595 if (SUCCEEDED(items->GetItemAt(i, &item)))
596 result.push_back(item);
604 *errorMessage =
"Item not streamable"_L1;
607 IStream *istream =
nullptr;
608 HRESULT hr = m_item->BindToHandler(
nullptr, BHID_Stream, IID_PPV_ARGS(&istream));
610 *errorMessage =
"BindToHandler() failed: "_L1
611 + QSystemError::windowsComString(hr);
614 enum : ULONG { bufSize = 102400 };
615 const auto memory = q20::make_unique_for_overwrite<
char[]>(bufSize);
616 char *
const buffer = memory.get();
620 hr = istream->Read(buffer, bufSize, &bytesRead);
621 if ((hr == S_OK || hr == S_FALSE) && bytesRead)
622 out->write(buffer, bytesRead);
627 if (hr != S_OK && hr != S_FALSE) {
628 *errorMessage =
"Read() failed: "_L1
629 + QSystemError::windowsComString(hr);
643 static const CLSID classId_ShellLibrary = {0xd9b3211d, 0xe57f, 0x4426, {0xaa, 0xef, 0x30, 0xa8, 0x6, 0xad, 0xd3, 0x97}};
644 static const IID iId_IShellLibrary = {0x11a66efa, 0x382e, 0x451a, {0x92, 0x34, 0x1e, 0xe, 0x12, 0xef, 0x30, 0x85}};
646 IShellLibrary *helper =
nullptr;
647 IShellLibrary *result =
nullptr;
648 if (SUCCEEDED(CoCreateInstance(classId_ShellLibrary,
nullptr, CLSCTX_INPROC_SERVER, iId_IShellLibrary,
reinterpret_cast<
void **>(&helper))))
649 if (SUCCEEDED(helper->LoadLibraryFromItem(libraryItem, mode)))
650 helper->QueryInterface(iId_IShellLibrary,
reinterpret_cast<
void **>(&result));
660 if (IShellLibrary *library = sHLoadLibraryFromItem(item, STGM_READ | STGM_SHARE_DENY_WRITE)) {
661 IShellItem *item =
nullptr;
662 if (SUCCEEDED(library->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem,
reinterpret_cast<
void **>(&item)))) {
663 result = QDir::cleanPath(QWindowsShellItem::displayName(item, SIGDN_FILESYSPATH));
671#ifndef QT_NO_DEBUG_STREAM
674 d <<
"attributes=0x" << Qt::hex << attributes() << Qt::dec;
681 d <<
", normalDisplay=\"" << normalDisplay()
682 <<
"\", desktopAbsoluteParsing=\"" << desktopAbsoluteParsing()
683 <<
"\", urlString=\"" << urlString() <<
"\", fileSysPath=\"" << fileSysPath() <<
'"';
684 const QString pathS = path();
685 if (!pathS.isEmpty())
686 d <<
", path=\"" << pathS <<
'"';
687 const QUrl urlV = urlValue();
689 d <<
"\", url=" << urlV;
694 QDebugStateSaver saver(d);
703QDebug operator<<(QDebug d, IShellItem *i)
705 QDebugStateSaver saver(d);
708 d <<
"IShellItem(" <<
static_cast<
const void *>(i);
719
720
721
722
723
724
725
726
727
732 Q_PROPERTY(
bool hideFiltersDetails READ hideFiltersDetails WRITE setHideFiltersDetails)
739 inline void setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options);
743 void doExec(HWND owner =
nullptr)
override;
753 inline void setLabelText(QFileDialogOptions::DialogLabel l,
const QString &text);
776 bool init(
const CLSID &clsId,
const IID &iid);
785 IFileDialog *m_fileDialog =
nullptr;
786 IFileDialogEvents *m_dialogEvents =
nullptr;
788 QStringList m_nameFilters;
789 bool m_hideFiltersDetails =
false;
790 bool m_hasDefaultSuffix =
false;
802 if (m_dialogEvents && m_fileDialog)
803 m_fileDialog->Unadvise(m_cookie);
805 m_dialogEvents->Release();
807 m_fileDialog->Release();
812 HRESULT hr = CoCreateInstance(clsId,
nullptr, CLSCTX_INPROC_SERVER,
813 iid,
reinterpret_cast<
void **>(&m_fileDialog));
815 qErrnoWarning(
"CoCreateInstance failed");
818 m_dialogEvents = QWindowsNativeFileDialogEventHandler::create(
this);
822 hr = m_fileDialog->Advise(m_dialogEvents, &m_cookie);
824 qErrnoWarning(
"IFileDialog::Advise failed");
827 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << m_fileDialog << m_dialogEvents << m_cookie;
835 m_fileDialog->SetTitle(
reinterpret_cast<
const wchar_t *>(title.utf16()));
840 if (url.isLocalFile()) {
841 IShellItem *result =
nullptr;
842 const QString native = QDir::toNativeSeparators(url.toLocalFile());
844 SHCreateItemFromParsingName(
reinterpret_cast<
const wchar_t *>(native.utf16()),
845 nullptr, IID_IShellItem,
846 reinterpret_cast<
void **>(&result));
848 qErrnoWarning(
"%s: SHCreateItemFromParsingName(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
852 }
else if (url.scheme() == u"clsid") {
856 IShellItem *result =
nullptr;
857 const auto uuid = QUuid::fromString(url.path());
859 qWarning() <<
__FUNCTION__ <<
": Invalid CLSID: " << url.path();
862 PIDLIST_ABSOLUTE idList;
863 HRESULT hr = SHGetKnownFolderIDList(uuid, 0,
nullptr, &idList);
865 qErrnoWarning(
"%s: SHGetKnownFolderIDList(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
868 hr = SHCreateItemFromIDList(idList, IID_IShellItem,
reinterpret_cast<
void **>(&result));
869 CoTaskMemFree(idList);
871 qErrnoWarning(
"%s: SHCreateItemFromIDList(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
876 qWarning() <<
__FUNCTION__ <<
": Unhandled scheme: " << url.scheme();
883 if (!directory.isEmpty()) {
884 if (IShellItem *psi = QWindowsNativeFileDialogBase::shellItem(directory)) {
885 m_fileDialog->SetFolder(psi);
894 IShellItem *item =
nullptr;
895 if (m_fileDialog && SUCCEEDED(m_fileDialog->GetFolder(&item)) && item) {
904 qCDebug(lcQpaDialogs) <<
'>' <<
__FUNCTION__;
907 const HRESULT hr = m_fileDialog->Show(owner);
909 qCDebug(lcQpaDialogs) <<
'<' <<
__FUNCTION__ <<
" returns " << Qt::hex << hr;
912 if (hr == S_OK && !m_data.selectedFiles().isEmpty()) {
920 QFileDialogOptions::AcceptMode acceptMode,
921 QFileDialogOptions::FileDialogOptions options)
923 DWORD flags = FOS_PATHMUSTEXIST;
924 if (QWindowsContext::readAdvancedExplorerSettings(L"Hidden", 1) == 1)
925 flags |= FOS_FORCESHOWHIDDEN;
926 if (options & QFileDialogOptions::DontResolveSymlinks)
927 flags |= FOS_NODEREFERENCELINKS;
929 case QFileDialogOptions::AnyFile:
930 if (acceptMode == QFileDialogOptions::AcceptSave)
931 flags |= FOS_NOREADONLYRETURN;
932 if (!(options & QFileDialogOptions::DontConfirmOverwrite))
933 flags |= FOS_OVERWRITEPROMPT;
935 case QFileDialogOptions::ExistingFile:
936 flags |= FOS_FILEMUSTEXIST;
938 case QFileDialogOptions::Directory:
939 case QFileDialogOptions::DirectoryOnly:
942 flags |= FOS_PICKFOLDERS | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM;
944 case QFileDialogOptions::ExistingFiles:
945 flags |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
948 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"mode=" << mode
949 <<
"acceptMode=" << acceptMode <<
"options=" << options
950 <<
"results in" << Qt::showbase << Qt::hex << flags;
952 if (FAILED(m_fileDialog->SetOptions(flags)))
953 qErrnoWarning(
"%s: SetOptions() failed",
__FUNCTION__);
964 bool hideFilterDetails,
965 int *totalStringLength)
967 QList<FilterSpec> result;
968 result.reserve(filters.size());
969 *totalStringLength = 0;
971#if QT_CONFIG(regularexpression)
972 const QRegularExpression filterSeparatorRE(QStringLiteral(
"[;\\s]+"));
973 const QString separator = QStringLiteral(
";");
974 Q_ASSERT(filterSeparatorRE.isValid());
979 for (
const QString &filterString : filters) {
980 const int openingParenPos = filterString.lastIndexOf(u'(');
981 const int closingParenPos = openingParenPos != -1 ?
982 filterString.indexOf(u')', openingParenPos + 1) : -1;
983 FilterSpec filterSpec;
984 filterSpec.filter = closingParenPos == -1 ?
986 filterString.mid(openingParenPos + 1, closingParenPos - openingParenPos - 1).trimmed();
987 if (filterSpec.filter.isEmpty())
988 filterSpec.filter += u'*';
989#if QT_CONFIG(regularexpression)
990 filterSpec.filter.replace(filterSeparatorRE, separator);
992 filterSpec.filter.replace(u' ', u';');
994 filterSpec.description = filterString;
995 if (hideFilterDetails && openingParenPos != -1) {
996 filterSpec.description.truncate(openingParenPos);
997 while (filterSpec.description.endsWith(u' '))
998 filterSpec.description.truncate(filterSpec.description.size() - 1);
1000 *totalStringLength += filterSpec.filter.size() + filterSpec.description.size();
1001 result.push_back(filterSpec);
1009
1010 m_nameFilters = filters;
1011 int totalStringLength = 0;
1012 const QList<FilterSpec> specs = filterSpecs(filters, m_hideFiltersDetails, &totalStringLength);
1013 const int size = specs.size();
1015 QScopedArrayPointer<WCHAR> buffer(
new WCHAR[totalStringLength + 2 * size]);
1016 QScopedArrayPointer<COMDLG_FILTERSPEC> comFilterSpec(
new COMDLG_FILTERSPEC[size]);
1018 WCHAR *ptr = buffer.data();
1022 for (
int i = 0; i < size; ++i) {
1026 QString description = specs[i].description;
1027 const QString &filter = specs[i].filter;
1028 if (!m_hideFiltersDetails && !filter.startsWith(u"*.")) {
1029 const int pos = description.lastIndexOf(u'(');
1031 description.truncate(pos);
1032 while (!description.isEmpty() && description.back().isSpace())
1033 description.chop(1);
1037 comFilterSpec[i].pszName = ptr;
1038 ptr += description.toWCharArray(ptr);
1040 comFilterSpec[i].pszSpec = ptr;
1041 ptr += specs[i].filter.toWCharArray(ptr);
1045 m_fileDialog->SetFileTypes(size, comFilterSpec.data());
1050 setDefaultSuffixSys(s);
1051 m_hasDefaultSuffix = !s.isEmpty();
1059 auto *wSuffix =
const_cast<
wchar_t *>(
reinterpret_cast<
const wchar_t *>(s.utf16()));
1060 m_fileDialog->SetDefaultExtension(wSuffix);
1065 IFileDialog2 *result;
1066 return SUCCEEDED(fileDialog->QueryInterface(IID_IFileDialog2,
reinterpret_cast<
void **>(&result)))
1072 auto *wText =
const_cast<
wchar_t *>(
reinterpret_cast<
const wchar_t *>(text.utf16()));
1074 case QFileDialogOptions::FileName:
1075 m_fileDialog->SetFileNameLabel(wText);
1077 case QFileDialogOptions::Accept:
1078 m_fileDialog->SetOkButtonLabel(wText);
1080 case QFileDialogOptions::Reject:
1081 if (IFileDialog2 *dialog2 = getFileDialog2(m_fileDialog)) {
1082 dialog2->SetCancelButtonLabel(wText);
1086 case QFileDialogOptions::LookIn:
1087 case QFileDialogOptions::FileType:
1088 case QFileDialogOptions::DialogLabelCount:
1095 for (;start < end; ++start) {
1096 QChar ch = s.at(start);
1098 || (ch >= u'a' && ch <= u'f')
1099 || (ch >= u'A' && ch <= u'F')))
1108 const QChar dash(u'-');
1109 return s.size() == 36
1110 && isHexRange(s, 0, 8)
1112 && isHexRange(s, 9, 13)
1114 && isHexRange(s, 14, 18)
1116 && isHexRange(s, 19, 23)
1118 && isHexRange(s, 24, 36);
1125 if (!isClsid(fileName))
1126 m_fileDialog->SetFileName((
wchar_t*)fileName.utf16());
1134 const int index = filters.indexOf(needle);
1137 for (
int i = 0; i < filters.size(); ++i)
1138 if (filters.at(i).startsWith(needle))
1145 if (filter.isEmpty())
1147 const int index = indexOfNameFilter(m_nameFilters, filter);
1149 qWarning(
"%s: Invalid parameter '%s' not found in '%s'.",
1150 __FUNCTION__, qPrintable(filter),
1151 qPrintable(m_nameFilters.join(u", ")));
1154 m_fileDialog->SetFileTypeIndex(index + 1);
1160 if (SUCCEEDED(m_fileDialog->GetFileTypeIndex(&uIndex))) {
1161 const int index = uIndex - 1;
1162 if (index < m_nameFilters.size())
1163 return m_nameFilters.at(index);
1172 m_data.setDirectory(directory);
1173 emit directoryEntered(directory);
1179 const QList<QUrl> current = selectedFiles();
1180 m_data.setSelectedFiles(current);
1181 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << current << current.size();
1183 if (current.size() == 1)
1184 emit currentChanged(current.front());
1189 const QString filter = selectedNameFilter();
1190 m_data.setSelectedNameFilter(filter);
1191 emit filterSelected(filter);
1197 m_data.setSelectedFiles(dialogResult());
1203 m_fileDialog->Close(S_OK);
1206 const HWND hwnd = QWindowsDialogs::getHWND(m_fileDialog);
1207 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"closing" << hwnd;
1208 if (hwnd && IsWindowVisible(hwnd))
1209 PostMessageW(hwnd, WM_CLOSE, 0, 0);
1214 m_nativeFileDialog->onFolderChange(item);
1232 return m_nativeFileDialog->onFileOk() ? S_OK : S_FALSE;
1236
1237
1238
1239
1240
1241
1242
1259 int suffixPos = filter.indexOf(u"*.");
1263 int endPos = filter.indexOf(u' ', suffixPos + 1);
1265 endPos = filter.indexOf(u';', suffixPos + 1);
1267 endPos = filter.indexOf(u')', suffixPos + 1);
1269 endPos = filter.size();
1270 return filter.mid(suffixPos, endPos - suffixPos);
1275 QWindowsNativeFileDialogBase::setNameFilters(f);
1280 for (
const QString &filter : f) {
1281 const QString suffix = suffixFromFilter(filter);
1282 if (!suffix.isEmpty()) {
1283 setDefaultSuffixSys(suffix);
1293 IShellItem *item =
nullptr;
1294 if (SUCCEEDED(fileDialog()->GetResult(&item)) && item)
1302 IShellItem *item =
nullptr;
1303 const HRESULT hr = fileDialog()->GetCurrentSelection(&item);
1304 if (SUCCEEDED(hr) && item) {
1312
1313
1314
1315
1316
1317
1318
1329 inline IFileOpenDialog *openFileDialog()
const
1330 {
return static_cast<IFileOpenDialog *>(fileDialog()); }
1342 for (
const QString &file : std::as_const(*temporaryItemCopies()))
1343 QFile::remove(file);
1351 return c.isLetterOrNumber() || c == u'_' || c == u'-';
1356 const int lastSlash = qMax(name.lastIndexOf(u'/'),
1357 name.lastIndexOf(u'\\'));
1358 if (lastSlash != -1)
1359 name.remove(0, lastSlash + 1);
1361 int lastDot = name.lastIndexOf(u'.');
1363 lastDot = name.size();
1364 name.insert(lastDot,
"_XXXXXX"_L1);
1366 for (
int i = lastDot - 1; i >= 0; --i) {
1367 if (!validFileNameCharacter(name.at(i)))
1371 name.prepend(QDir::tempPath() + u'/');
1378 *errorMessage =
"Item not streamable"_L1;
1382 QTemporaryFile targetFile(tempFilePattern(qItem.normalDisplay()));
1383 targetFile.setAutoRemove(
false);
1384 if (!targetFile.open()) {
1385 *errorMessage =
"Cannot create temporary file: "_L1
1386 + targetFile.errorString();
1389 if (!qItem.copyData(&targetFile, errorMessage))
1391 const QString result = targetFile.fileName();
1392 if (temporaryItemCopies()->isEmpty())
1394 temporaryItemCopies()->append(result);
1400 QUrl url = qItem.url();
1401 if (url.isLocalFile() || url.scheme().startsWith(u"http"))
1403 const QString path = qItem.path();
1405 const QString temporaryCopy = createTemporaryItemCopy(qItem, errorMessage);
1406 if (temporaryCopy.isEmpty()) {
1407 QDebug(errorMessage).noquote() <<
"Unable to create a local copy of"
1408 << qItem <<
": " << errorMessage;
1411 return QUrl::fromLocalFile(temporaryCopy);
1414 QDebug(errorMessage).noquote() <<
"Invalid URL obtained from" << qItem;
1421 IShellItemArray *items =
nullptr;
1422 if (SUCCEEDED(openFileDialog()->GetResults(&items)) && items) {
1423 QString errorMessage;
1424 for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) {
1425 QWindowsShellItem qItem(item);
1426 const QUrl url = itemToDialogUrl(qItem, &errorMessage);
1427 if (!url.isValid()) {
1428 qWarning(
"%s", qPrintable(errorMessage));
1441 IShellItemArray *items =
nullptr;
1442 const HRESULT hr = openFileDialog()->GetSelectedItems(&items);
1443 if (SUCCEEDED(hr) && items) {
1444 for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) {
1445 const QWindowsShellItem qItem(item);
1446 const QUrl url = qItem.url();
1450 qWarning().nospace() <<
__FUNCTION__<<
": Unable to obtain URL of " << qItem;
1457
1458
1459
1460
1466 if (am == QFileDialogOptions::AcceptOpen) {
1468 if (!result->init(CLSID_FileOpenDialog, IID_IFileOpenDialog)) {
1473 result =
new QWindowsNativeSaveFileDialog(data);
1474 if (!result->init(CLSID_FileSaveDialog, IID_IFileSaveDialog)) {
1483
1484
1485
1486
1487
1488
1489
1490
1521 QObject::connect(result, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
1522 QObject::connect(result, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
1523 QObject::connect(result, &QWindowsNativeFileDialogBase::directoryEntered,
1524 this, &QPlatformFileDialogHelper::directoryEntered);
1525 QObject::connect(result, &QWindowsNativeFileDialogBase::currentChanged,
1526 this, &QPlatformFileDialogHelper::currentChanged);
1527 QObject::connect(result, &QWindowsNativeFileDialogBase::filterSelected,
1528 this, &QPlatformFileDialogHelper::filterSelected);
1531 const QSharedPointer<QFileDialogOptions> &opts = options();
1532 m_data.fromOptions(opts);
1533 const QFileDialogOptions::FileMode mode = opts->fileMode();
1534 result->setWindowTitle(opts->windowTitle());
1535 result->setMode(mode, opts->acceptMode(), opts->options());
1537 const QStringList nameFilters = opts->nameFilters();
1538 if (!nameFilters.isEmpty())
1539 result->setNameFilters(nameFilters);
1540 if (opts->isLabelExplicitlySet(QFileDialogOptions::FileName))
1541 result->setLabelText(QFileDialogOptions::FileName, opts->labelText(QFileDialogOptions::FileName));
1542 if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
1543 result->setLabelText(QFileDialogOptions::Accept, opts->labelText(QFileDialogOptions::Accept));
1544 if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
1545 result->setLabelText(QFileDialogOptions::Reject, opts->labelText(QFileDialogOptions::Reject));
1548 const QList<QUrl> initialSelection = opts->initiallySelectedFiles();
1549 if (!initialSelection.empty()) {
1550 const QUrl &url = initialSelection.constFirst();
1551 if (url.isLocalFile()) {
1552 QFileInfo info(url.toLocalFile());
1554 result->selectFile(info.fileName());
1556 result->selectFile(url.fileName());
1560 if (mode != QFileDialogOptions::Directory && mode != QFileDialogOptions::DirectoryOnly) {
1561 const QString initialNameFilter = opts->initiallySelectedNameFilter();
1562 if (!initialNameFilter.isEmpty())
1563 result->selectNameFilter(initialNameFilter);
1565 const QString defaultSuffix = opts->defaultSuffix();
1566 if (!defaultSuffix.isEmpty())
1567 result->setDefaultSuffix(defaultSuffix);
1573 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << directory.toString();
1575 m_data.setDirectory(directory);
1576 if (hasNativeDialog())
1582 return m_data.directory();
1587 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << fileName.toString();
1589 if (hasNativeDialog())
1590 nativeFileDialog()->selectFile(fileName.fileName());
1595 return m_data.selectedFiles();
1600 qCDebug(lcQpaDialogs) <<
__FUNCTION__;
1605 m_data.setSelectedNameFilter(filter);
1606 if (hasNativeDialog())
1612 return m_data.selectedNameFilter();
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1645 void populateOpenFileName(OPENFILENAME *ofn, HWND owner)
const;
1646 QList<QUrl> execExistingDir(HWND owner);
1647 QList<QUrl> execFileNames(HWND owner,
int *selectedFilterIndex)
const;
1649 const OptionsPtr m_options;
1651 QPlatformDialogHelper::DialogCode m_result;
1662 m_options(options), m_result(QPlatformDialogHelper::Rejected), m_data(data)
1664 setWindowTitle(m_options->windowTitle());
1669 int selectedFilterIndex = -1;
1670 const QList<QUrl> selectedFiles =
1671 m_options->fileMode() == QFileDialogOptions::DirectoryOnly ?
1672 execExistingDir(owner) : execFileNames(owner, &selectedFilterIndex);
1673 m_data.setSelectedFiles(selectedFiles);
1675 if (selectedFiles.isEmpty()) {
1676 m_result = QPlatformDialogHelper::Rejected;
1679 const QStringList nameFilters = m_options->nameFilters();
1680 if (selectedFilterIndex >= 0 && selectedFilterIndex < nameFilters.size())
1681 m_data.setSelectedNameFilter(nameFilters.at(selectedFilterIndex));
1682 const QUrl &firstFile = selectedFiles.constFirst();
1683 m_data.setDirectory(firstFile.adjusted(QUrl::RemoveFilename));
1684 m_result = QPlatformDialogHelper::Accepted;
1692static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
1694 auto *dialog =
reinterpret_cast<QWindowsXpNativeFileDialog *>(lpData);
1695 return dialog->existingDirCallback(hwnd, uMsg, lParam);
1701 case BFFM_INITIALIZED: {
1702 if (!m_title.isEmpty())
1703 SetWindowText(hwnd,
reinterpret_cast<
const wchar_t *>(m_title.utf16()));
1704 const QString initialFile = QDir::toNativeSeparators(m_data.directory().toLocalFile());
1705 if (!initialFile.isEmpty())
1706 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, LPARAM(initialFile.utf16()));
1709 case BFFM_SELCHANGED: {
1710 wchar_t path[MAX_PATH];
1711 const bool ok = SHGetPathFromIDList(
reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path)
1713 SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1);
1723 wchar_t initPath[MAX_PATH];
1725 bi.hwndOwner = owner;
1726 bi.pidlRoot =
nullptr;
1727 bi.lpszTitle =
nullptr;
1728 bi.pszDisplayName = initPath;
1729 bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE;
1730 bi.lpfn = xpFileDialogGetExistingDirCallbackProc;
1731 bi.lParam = LPARAM(
this);
1732 QList<QUrl> selectedFiles;
1733 if (
const auto pItemIDList = SHBrowseForFolder(&bi)) {
1734 wchar_t path[MAX_PATH];
1736 if (SHGetPathFromIDList(pItemIDList, path) && path[0])
1737 selectedFiles.push_back(QUrl::fromLocalFile(QDir::cleanPath(QString::fromWCharArray(path))));
1739 if (SHGetMalloc(&pMalloc) == NOERROR) {
1740 pMalloc->Free(pItemIDList);
1744 return selectedFiles;
1750 ZeroMemory(ofn,
sizeof(OPENFILENAME));
1751 ofn->lStructSize =
sizeof(OPENFILENAME);
1752 ofn->hwndOwner = owner;
1755 int totalStringLength = 0;
1756 const QList<FilterSpec> specs =
1757 filterSpecs(m_options->nameFilters(), m_options->options() & QFileDialogOptions::HideNameFilterDetails, &totalStringLength);
1758 const int size = specs.size();
1759 auto *ptr =
new wchar_t[totalStringLength + 2 * size + 1];
1760 ofn->lpstrFilter = ptr;
1761 for (
const FilterSpec &spec : specs) {
1762 ptr += spec.description.toWCharArray(ptr);
1764 ptr += spec.filter.toWCharArray(ptr);
1768 const int nameFilterIndex = indexOfNameFilter(m_options->nameFilters(), m_data.selectedNameFilter());
1769 if (nameFilterIndex >= 0)
1770 ofn->nFilterIndex = nameFilterIndex + 1;
1774 ofn->nMaxFile = 65535;
1775 QString initiallySelectedFile = m_data.selectedFile();
1776 initiallySelectedFile.remove(u'<');
1777 initiallySelectedFile.remove(u'>');
1778 initiallySelectedFile.remove(u'"');
1779 initiallySelectedFile.remove(u'|');
1780 ofn->lpstrFile = qStringToWCharArray(QDir::toNativeSeparators(initiallySelectedFile), ofn->nMaxFile);
1781 ofn->lpstrInitialDir = qStringToWCharArray(QDir::toNativeSeparators(m_data.directory().toLocalFile()));
1782 ofn->lpstrTitle = (
wchar_t*)m_title.utf16();
1788 if (m_options->acceptMode() == QFileDialogOptions::AcceptSave) {
1789 QString defaultSuffix = m_options->defaultSuffix();
1790 if (defaultSuffix.startsWith(u'.'))
1791 defaultSuffix.remove(0, 1);
1793 ofn->lpstrDefExt = qStringToWCharArray(defaultSuffix);
1796 ofn->Flags = (OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_PATHMUSTEXIST);
1797 if (m_options->fileMode() == QFileDialogOptions::ExistingFile
1798 || m_options->fileMode() == QFileDialogOptions::ExistingFiles)
1799 ofn->Flags |= (OFN_FILEMUSTEXIST);
1800 if (m_options->fileMode() == QFileDialogOptions::ExistingFiles)
1801 ofn->Flags |= (OFN_ALLOWMULTISELECT);
1802 if (!(m_options->options() & QFileDialogOptions::DontConfirmOverwrite))
1803 ofn->Flags |= OFN_OVERWRITEPROMPT;
1808 *selectedFilterIndex = -1;
1810 populateOpenFileName(&ofn, owner);
1812 const bool isSave = m_options->acceptMode() == QFileDialogOptions::AcceptSave;
1813 if (isSave ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn)) {
1814 *selectedFilterIndex = ofn.nFilterIndex - 1;
1815 const QString dir = QDir::cleanPath(QString::fromWCharArray(ofn.lpstrFile));
1816 result.push_back(QUrl::fromLocalFile(dir));
1819 if (ofn.Flags & (OFN_ALLOWMULTISELECT)) {
1820 wchar_t *ptr = ofn.lpstrFile + dir.size() + 1;
1823 const QString path = dir + u'/';
1825 const QString fileName = QString::fromWCharArray(ptr);
1826 result.push_back(QUrl::fromLocalFile(path + fileName));
1827 ptr += fileName.size() + 1;
1832 delete [] ofn.lpstrFile;
1833 delete [] ofn.lpstrInitialDir;
1834 delete [] ofn.lpstrFilter;
1835 delete [] ofn.lpstrDefExt;
1840
1841
1842
1843
1844
1845
1872 m_data.fromOptions(options());
1874 QObject::connect(result, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
1875 QObject::connect(result, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
1883 m_data.setDirectory(directory);
1888 return m_data.directory();
1893 m_data.setSelectedFiles(QList<QUrl>() << url);
1898 return m_data.selectedFiles();
1903 m_data.setSelectedNameFilter(f);
1908 return m_data.selectedNameFilter();
1913
1914
1915
1916
1917
1918
1919
1920
1921
1923using SharedPointerColor = QSharedPointer<QColor>;
1925#ifdef USE_NATIVE_COLOR_DIALOG
1926class QWindowsNativeColorDialog :
public QWindowsNativeDialogBase
1930 enum { CustomColorCount = 16 };
1932 explicit QWindowsNativeColorDialog(
const SharedPointerColor &color);
1934 void setWindowTitle(
const QString &) override {}
1937 void close() override {}
1940 void doExec(HWND owner = 0) override;
1942 COLORREF m_customColors[CustomColorCount];
1943 QPlatformDialogHelper::DialogCode m_code;
1944 SharedPointerColor m_color;
1947QWindowsNativeColorDialog::QWindowsNativeColorDialog(
const SharedPointerColor &color) :
1948 m_code(QPlatformDialogHelper::Rejected), m_color(color)
1950 std::fill(m_customColors, m_customColors + 16, COLORREF(0));
1953void QWindowsNativeColorDialog::doExec(HWND owner)
1955 CHOOSECOLOR chooseColor;
1956 ZeroMemory(&chooseColor,
sizeof(chooseColor));
1957 chooseColor.lStructSize =
sizeof(chooseColor);
1958 chooseColor.hwndOwner = owner;
1959 chooseColor.lpCustColors = m_customColors;
1960 QRgb *qCustomColors = QColorDialogOptions::customColors();
1961 const int customColorCount = qMin(QColorDialogOptions::customColorCount(),
1962 int(CustomColorCount));
1963 for (
int c= 0; c < customColorCount; ++c)
1964 m_customColors[c] = qColorToCOLORREF(QColor(qCustomColors[c]));
1965 chooseColor.rgbResult = qColorToCOLORREF(*m_color);
1966 chooseColor.Flags = CC_FULLOPEN | CC_RGBINIT;
1967 m_code = ChooseColorW(&chooseColor) ?
1968 QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected;
1969 QWindowsDialogs::eatMouseMove();
1970 if (m_code == QPlatformDialogHelper::Accepted) {
1971 *m_color = COLORREFToQColor(chooseColor.rgbResult);
1972 for (
int c= 0; c < customColorCount; ++c)
1973 qCustomColors[c] = COLORREFToQColor(m_customColors[c]).rgb();
1981
1982
1983
1984
1985
1986
1987
1988
1989
1991class QWindowsColorDialogHelper :
public QWindowsDialogHelperBase<QPlatformColorDialogHelper>
1994 QWindowsColorDialogHelper() : m_currentColor(
new QColor) {}
1996 virtual bool supportsNonModalDialog()
1999 virtual QColor currentColor()
const {
return *m_currentColor; }
2000 virtual void setCurrentColor(
const QColor &c) { *m_currentColor = c; }
2003 inline QWindowsNativeColorDialog *nativeFileDialog()
const
2004 {
return static_cast<QWindowsNativeColorDialog *>(nativeDialog()); }
2005 virtual QWindowsNativeDialogBase *createNativeDialog();
2007 SharedPointerColor m_currentColor;
2010QWindowsNativeDialogBase *QWindowsColorDialogHelper::createNativeDialog()
2012 QWindowsNativeColorDialog *nativeDialog =
new QWindowsNativeColorDialog(m_currentColor);
2013 nativeDialog->setWindowTitle(options()->windowTitle());
2014 connect(nativeDialog, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
2015 connect(nativeDialog, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
2016 return nativeDialog;
2031#ifdef USE_NATIVE_COLOR_DIALOG
2055#ifdef USE_NATIVE_COLOR_DIALOG
2072#include "qwindowsdialoghelpers.moc"