51 const size_t stringSize = s.size();
52 wchar_t *result =
new wchar_t[qMax(stringSize + 1, reserveSize)];
53 s.toWCharArray(result);
54 result[stringSize] = 0;
61
62
63
64
65
66
67
68
69
73 MSG msg = {
nullptr, 0, 0, 0, 0, {0, 0} };
74 while (PeekMessage(&msg,
nullptr, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE))
76 if (msg.message == WM_MOUSEMOVE)
77 PostMessage(msg.hwnd, msg.message, 0, msg.lParam);
78 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"triggered=" << (msg.message == WM_MOUSEMOVE);
83 IOleWindow *oleWindow =
nullptr;
84 if (FAILED(fileDialog->QueryInterface(IID_IOleWindow,
reinterpret_cast<
void **>(&oleWindow)))) {
85 qCWarning(lcQpaDialogs,
"Native file dialog: unable to query IID_IOleWindow interface.");
90 if (FAILED(oleWindow->GetWindow(&result)))
91 qCWarning(lcQpaDialogs,
"Native file dialog: unable to get dialog's window.");
100
101
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
135 void exec(HWND owner =
nullptr) { doExec(owner); m_executed =
true; }
148 virtual void doExec(HWND owner =
nullptr) = 0;
154
155
156
157
158
159
160
161
162
163
164
165
166
168template <
class BaseClass>
175template <
class BaseClass>
184 if (m_thread->wait(500))
187 qCCritical(lcQpaDialogs) <<
__FUNCTION__ <<
"Thread failed to finish.";
192template <
class BaseClass>
195 if (m_nativeDialog.isNull()) {
196 qWarning(
"%s invoked with no native dialog present.",
__FUNCTION__);
199 return m_nativeDialog.data();
202template <
class BaseClass>
208template <
class BaseClass>
213 if (m_nativeDialog.isNull() || m_nativeDialog->executed())
214 m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog(), &QObject::deleteLater);
215 return m_nativeDialog.data();
219
220
221
222
223
224
236 const QWindowsNativeDialogBasePtr m_dialog;
242 qCDebug(lcQpaDialogs) <<
'>' <<
__FUNCTION__;
243 QComHelper comInit(COINIT_APARTMENTTHREADED);
244 m_dialog->exec(m_owner);
245 qCDebug(lcQpaDialogs) <<
'<' <<
__FUNCTION__;
248template <
class BaseClass>
250 Qt::WindowModality windowModality,
253 const bool modal = (windowModality != Qt::NonModal);
255 parent = QGuiApplication::focusWindow();
257 m_ownerWindow = QWindowsWindow::handleOf(parent);
259 m_ownerWindow =
nullptr;
261 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"modal=" << modal
262 <<
" modal supported? " << supportsNonModalDialog(parent)
263 <<
"native=" << m_nativeDialog.data() <<
"owner" << m_ownerWindow;
264 if (!modal && !supportsNonModalDialog(parent))
266 if (!ensureNativeDialog())
274 m_timer.start(0ns,
this);
281template <
class BaseClass>
284 Q_ASSERT(!m_nativeDialog.isNull());
286 m_thread =
new QWindowsDialogThread(m_nativeDialog, m_ownerWindow);
291template <
class BaseClass>
297template <
class BaseClass>
300 if (m_nativeDialog) {
301 m_nativeDialog->close();
302 m_nativeDialog.clear();
304 m_ownerWindow =
nullptr;
307template <
class BaseClass>
310 qCDebug(lcQpaDialogs) <<
__FUNCTION__;
313 nd->exec(m_ownerWindow);
314 m_nativeDialog.clear();
319
320
321
322
323
324
325
326
327
328
329
346 class Data :
public QSharedData {
349 QString selectedNameFilter;
350 QList<QUrl> selectedFiles;
353 QExplicitlySharedDataPointer<Data> m_data;
358 m_data->mutex.lock();
359 const QUrl result = m_data->directory;
360 m_data->mutex.unlock();
366 QMutexLocker locker(&m_data->mutex);
367 m_data->directory = d;
372 m_data->mutex.lock();
373 const QString result = m_data->selectedNameFilter;
374 m_data->mutex.unlock();
380 QMutexLocker locker(&m_data->mutex);
381 m_data->selectedNameFilter = f;
386 m_data->mutex.lock();
387 const auto result = m_data->selectedFiles;
388 m_data->mutex.unlock();
394 const auto files = selectedFiles();
395 return files.isEmpty() ? QString() : files.front().toLocalFile();
400 QMutexLocker locker(&m_data->mutex);
401 m_data->selectedFiles = urls;
406 QMutexLocker locker(&m_data->mutex);
407 m_data->directory = o->initialDirectory();
408 m_data->selectedFiles = o->initiallySelectedFiles();
409 m_data->selectedNameFilter = o->initiallySelectedNameFilter();
413
414
415
416
417
418
419
420
421
437 FDE_SHAREVIOLATION_RESPONSE *)
override
448 m_nativeFileDialog(nativeFileDialog) {}
456 IFileDialogEvents *result;
458 if (FAILED(eventHandler->QueryInterface(IID_IFileDialogEvents,
reinterpret_cast<
void **>(&result)))) {
459 qErrnoWarning(
"Unable to obtain IFileDialogEvents");
462 eventHandler->Release();
467
468
469
470
471
472
482 {
return displayName(m_item, SIGDN_NORMALDISPLAY); }
484 {
return displayName(m_item, SIGDN_URL); }
486 {
return displayName(m_item, SIGDN_FILESYSPATH); }
488 {
return displayName(m_item, SIGDN_DESKTOPABSOLUTEPARSING); }
492 bool isFileSystem()
const {
return (m_attributes & SFGAO_FILESYSTEM) != 0; }
493 bool isDir()
const {
return (m_attributes & SFGAO_FOLDER) != 0; }
495 bool canStream()
const {
return (m_attributes & SFGAO_STREAM) != 0; }
497 bool copyData(QIODevice *out, QString *errorMessage);
501#ifndef QT_NO_DEBUG_STREAM
506 static QString displayName(IShellItem *item, SIGDN mode);
507 static QString libraryItemDefaultSaveFolder(IShellItem *item);
508 QUrl urlValue()
const;
518 SFGAOF mask = (SFGAO_CAPABILITYMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK);
521 if (FAILED(item->GetAttributes((SFGAO_STREAM | SFGAO_COMPRESSED), &m_attributes))) {
525 if (m_attributes & (SFGAO_STREAM | SFGAO_COMPRESSED))
526 mask &= ~SFGAO_HASSUBFOLDER;
527 if (FAILED(item->GetAttributes(mask, &m_attributes)))
535 return QDir::cleanPath(QWindowsShellItem::displayName(m_item, SIGDN_FILESYSPATH));
538 return QWindowsShellItem::libraryItemDefaultSaveFolder(m_item);
545 const QString urlString = displayName(m_item, SIGDN_URL);
546 if (!urlString.isEmpty()) {
547 const QUrl parsed = QUrl(urlString);
548 if (parsed.isValid()) {
551 qWarning(
"%s: Unable to decode URL \"%s\": %s",
__FUNCTION__,
552 qPrintable(urlString), qPrintable(parsed.errorString()));
561 const QString fsPath = path();
562 if (!fsPath.isEmpty())
563 return QUrl::fromLocalFile(fsPath);
564 const QUrl urlV = urlValue();
568 const QString data =
"data:text/plain;base64,"_L1
569 + QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64());
575 LPWSTR name =
nullptr;
577 if (SUCCEEDED(item->GetDisplayName(mode, &name))) {
578 result = QString::fromWCharArray(name);
588 if (FAILED(items->GetCount(&itemCount)) || itemCount == 0)
590 result.reserve(itemCount);
591 for (DWORD i = 0; i < itemCount; ++i) {
592 IShellItem *item =
nullptr;
593 if (SUCCEEDED(items->GetItemAt(i, &item)))
594 result.push_back(item);
602 *errorMessage =
"Item not streamable"_L1;
605 IStream *istream =
nullptr;
606 HRESULT hr = m_item->BindToHandler(
nullptr, BHID_Stream, IID_PPV_ARGS(&istream));
608 *errorMessage =
"BindToHandler() failed: "_L1
609 + QSystemError::windowsComString(hr);
612 enum : ULONG { bufSize = 102400 };
613 char buffer[bufSize];
617 hr = istream->Read(buffer, bufSize, &bytesRead);
618 if ((hr == S_OK || hr == S_FALSE) && bytesRead)
619 out->write(buffer, bytesRead);
624 if (hr != S_OK && hr != S_FALSE) {
625 *errorMessage =
"Read() failed: "_L1
626 + QSystemError::windowsComString(hr);
640 static const CLSID classId_ShellLibrary = {0xd9b3211d, 0xe57f, 0x4426, {0xaa, 0xef, 0x30, 0xa8, 0x6, 0xad, 0xd3, 0x97}};
641 static const IID iId_IShellLibrary = {0x11a66efa, 0x382e, 0x451a, {0x92, 0x34, 0x1e, 0xe, 0x12, 0xef, 0x30, 0x85}};
643 IShellLibrary *helper =
nullptr;
644 IShellLibrary *result =
nullptr;
645 if (SUCCEEDED(CoCreateInstance(classId_ShellLibrary,
nullptr, CLSCTX_INPROC_SERVER, iId_IShellLibrary,
reinterpret_cast<
void **>(&helper))))
646 if (SUCCEEDED(helper->LoadLibraryFromItem(libraryItem, mode)))
647 helper->QueryInterface(iId_IShellLibrary,
reinterpret_cast<
void **>(&result));
657 if (IShellLibrary *library = sHLoadLibraryFromItem(item, STGM_READ | STGM_SHARE_DENY_WRITE)) {
658 IShellItem *item =
nullptr;
659 if (SUCCEEDED(library->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem,
reinterpret_cast<
void **>(&item)))) {
660 result = QDir::cleanPath(QWindowsShellItem::displayName(item, SIGDN_FILESYSPATH));
668#ifndef QT_NO_DEBUG_STREAM
671 d <<
"attributes=0x" << Qt::hex << attributes() << Qt::dec;
678 d <<
", normalDisplay=\"" << normalDisplay()
679 <<
"\", desktopAbsoluteParsing=\"" << desktopAbsoluteParsing()
680 <<
"\", urlString=\"" << urlString() <<
"\", fileSysPath=\"" << fileSysPath() <<
'"';
681 const QString pathS = path();
682 if (!pathS.isEmpty())
683 d <<
", path=\"" << pathS <<
'"';
684 const QUrl urlV = urlValue();
686 d <<
"\", url=" << urlV;
691 QDebugStateSaver saver(d);
700QDebug operator<<(QDebug d, IShellItem *i)
702 QDebugStateSaver saver(d);
705 d <<
"IShellItem(" <<
static_cast<
const void *>(i);
716
717
718
719
720
721
722
723
724
729 Q_PROPERTY(
bool hideFiltersDetails READ hideFiltersDetails WRITE setHideFiltersDetails)
736 inline void setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options);
740 void doExec(HWND owner =
nullptr)
override;
750 inline void setLabelText(QFileDialogOptions::DialogLabel l,
const QString &text);
773 bool init(
const CLSID &clsId,
const IID &iid);
782 IFileDialog *m_fileDialog =
nullptr;
783 IFileDialogEvents *m_dialogEvents =
nullptr;
785 QStringList m_nameFilters;
786 bool m_hideFiltersDetails =
false;
787 bool m_hasDefaultSuffix =
false;
799 if (m_dialogEvents && m_fileDialog)
800 m_fileDialog->Unadvise(m_cookie);
802 m_dialogEvents->Release();
804 m_fileDialog->Release();
809 HRESULT hr = CoCreateInstance(clsId,
nullptr, CLSCTX_INPROC_SERVER,
810 iid,
reinterpret_cast<
void **>(&m_fileDialog));
812 qErrnoWarning(
"CoCreateInstance failed");
815 m_dialogEvents = QWindowsNativeFileDialogEventHandler::create(
this);
819 hr = m_fileDialog->Advise(m_dialogEvents, &m_cookie);
821 qErrnoWarning(
"IFileDialog::Advise failed");
824 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << m_fileDialog << m_dialogEvents << m_cookie;
832 m_fileDialog->SetTitle(
reinterpret_cast<
const wchar_t *>(title.utf16()));
837 if (url.isLocalFile()) {
838 IShellItem *result =
nullptr;
839 const QString native = QDir::toNativeSeparators(url.toLocalFile());
841 SHCreateItemFromParsingName(
reinterpret_cast<
const wchar_t *>(native.utf16()),
842 nullptr, IID_IShellItem,
843 reinterpret_cast<
void **>(&result));
845 qErrnoWarning(
"%s: SHCreateItemFromParsingName(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
849 }
else if (url.scheme() == u"clsid") {
853 IShellItem *result =
nullptr;
854 const auto uuid = QUuid::fromString(url.path());
856 qWarning() <<
__FUNCTION__ <<
": Invalid CLSID: " << url.path();
859 PIDLIST_ABSOLUTE idList;
860 HRESULT hr = SHGetKnownFolderIDList(uuid, 0,
nullptr, &idList);
862 qErrnoWarning(
"%s: SHGetKnownFolderIDList(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
865 hr = SHCreateItemFromIDList(idList, IID_IShellItem,
reinterpret_cast<
void **>(&result));
866 CoTaskMemFree(idList);
868 qErrnoWarning(
"%s: SHCreateItemFromIDList(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
873 qWarning() <<
__FUNCTION__ <<
": Unhandled scheme: " << url.scheme();
880 if (!directory.isEmpty()) {
881 if (IShellItem *psi = QWindowsNativeFileDialogBase::shellItem(directory)) {
882 m_fileDialog->SetFolder(psi);
891 IShellItem *item =
nullptr;
892 if (m_fileDialog && SUCCEEDED(m_fileDialog->GetFolder(&item)) && item) {
901 qCDebug(lcQpaDialogs) <<
'>' <<
__FUNCTION__;
904 const HRESULT hr = m_fileDialog->Show(owner);
906 qCDebug(lcQpaDialogs) <<
'<' <<
__FUNCTION__ <<
" returns " << Qt::hex << hr;
909 if (hr == S_OK && !m_data.selectedFiles().isEmpty()) {
917 QFileDialogOptions::AcceptMode acceptMode,
918 QFileDialogOptions::FileDialogOptions options)
920 DWORD flags = FOS_PATHMUSTEXIST;
921 if (QWindowsContext::readAdvancedExplorerSettings(L"Hidden", 1) == 1)
922 flags |= FOS_FORCESHOWHIDDEN;
923 if (options & QFileDialogOptions::DontResolveSymlinks)
924 flags |= FOS_NODEREFERENCELINKS;
926 case QFileDialogOptions::AnyFile:
927 if (acceptMode == QFileDialogOptions::AcceptSave)
928 flags |= FOS_NOREADONLYRETURN;
929 if (!(options & QFileDialogOptions::DontConfirmOverwrite))
930 flags |= FOS_OVERWRITEPROMPT;
932 case QFileDialogOptions::ExistingFile:
933 flags |= FOS_FILEMUSTEXIST;
935 case QFileDialogOptions::Directory:
936 case QFileDialogOptions::DirectoryOnly:
939 flags |= FOS_PICKFOLDERS | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM;
941 case QFileDialogOptions::ExistingFiles:
942 flags |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
945 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"mode=" << mode
946 <<
"acceptMode=" << acceptMode <<
"options=" << options
947 <<
"results in" << Qt::showbase << Qt::hex << flags;
949 if (FAILED(m_fileDialog->SetOptions(flags)))
950 qErrnoWarning(
"%s: SetOptions() failed",
__FUNCTION__);
961 bool hideFilterDetails,
962 int *totalStringLength)
964 QList<FilterSpec> result;
965 result.reserve(filters.size());
966 *totalStringLength = 0;
968#if QT_CONFIG(regularexpression)
969 const QRegularExpression filterSeparatorRE(QStringLiteral(
"[;\\s]+"));
970 const QString separator = QStringLiteral(
";");
971 Q_ASSERT(filterSeparatorRE.isValid());
976 for (
const QString &filterString : filters) {
977 const int openingParenPos = filterString.lastIndexOf(u'(');
978 const int closingParenPos = openingParenPos != -1 ?
979 filterString.indexOf(u')', openingParenPos + 1) : -1;
980 FilterSpec filterSpec;
981 filterSpec.filter = closingParenPos == -1 ?
983 filterString.mid(openingParenPos + 1, closingParenPos - openingParenPos - 1).trimmed();
984 if (filterSpec.filter.isEmpty())
985 filterSpec.filter += u'*';
986#if QT_CONFIG(regularexpression)
987 filterSpec.filter.replace(filterSeparatorRE, separator);
989 filterSpec.filter.replace(u' ', u';');
991 filterSpec.description = filterString;
992 if (hideFilterDetails && openingParenPos != -1) {
993 filterSpec.description.truncate(openingParenPos);
994 while (filterSpec.description.endsWith(u' '))
995 filterSpec.description.truncate(filterSpec.description.size() - 1);
997 *totalStringLength += filterSpec.filter.size() + filterSpec.description.size();
998 result.push_back(filterSpec);
1006
1007 m_nameFilters = filters;
1008 int totalStringLength = 0;
1009 const QList<FilterSpec> specs = filterSpecs(filters, m_hideFiltersDetails, &totalStringLength);
1010 const int size = specs.size();
1012 QScopedArrayPointer<WCHAR> buffer(
new WCHAR[totalStringLength + 2 * size]);
1013 QScopedArrayPointer<COMDLG_FILTERSPEC> comFilterSpec(
new COMDLG_FILTERSPEC[size]);
1015 WCHAR *ptr = buffer.data();
1019 for (
int i = 0; i < size; ++i) {
1023 QString description = specs[i].description;
1024 const QString &filter = specs[i].filter;
1025 if (!m_hideFiltersDetails && !filter.startsWith(u"*.")) {
1026 const int pos = description.lastIndexOf(u'(');
1028 description.truncate(pos);
1029 while (!description.isEmpty() && description.back().isSpace())
1030 description.chop(1);
1034 comFilterSpec[i].pszName = ptr;
1035 ptr += description.toWCharArray(ptr);
1037 comFilterSpec[i].pszSpec = ptr;
1038 ptr += specs[i].filter.toWCharArray(ptr);
1042 m_fileDialog->SetFileTypes(size, comFilterSpec.data());
1047 setDefaultSuffixSys(s);
1048 m_hasDefaultSuffix = !s.isEmpty();
1056 auto *wSuffix =
const_cast<
wchar_t *>(
reinterpret_cast<
const wchar_t *>(s.utf16()));
1057 m_fileDialog->SetDefaultExtension(wSuffix);
1062 IFileDialog2 *result;
1063 return SUCCEEDED(fileDialog->QueryInterface(IID_IFileDialog2,
reinterpret_cast<
void **>(&result)))
1069 auto *wText =
const_cast<
wchar_t *>(
reinterpret_cast<
const wchar_t *>(text.utf16()));
1071 case QFileDialogOptions::FileName:
1072 m_fileDialog->SetFileNameLabel(wText);
1074 case QFileDialogOptions::Accept:
1075 m_fileDialog->SetOkButtonLabel(wText);
1077 case QFileDialogOptions::Reject:
1078 if (IFileDialog2 *dialog2 = getFileDialog2(m_fileDialog)) {
1079 dialog2->SetCancelButtonLabel(wText);
1083 case QFileDialogOptions::LookIn:
1084 case QFileDialogOptions::FileType:
1085 case QFileDialogOptions::DialogLabelCount:
1092 for (;start < end; ++start) {
1093 QChar ch = s.at(start);
1095 || (ch >= u'a' && ch <= u'f')
1096 || (ch >= u'A' && ch <= u'F')))
1105 const QChar dash(u'-');
1106 return s.size() == 36
1107 && isHexRange(s, 0, 8)
1109 && isHexRange(s, 9, 13)
1111 && isHexRange(s, 14, 18)
1113 && isHexRange(s, 19, 23)
1115 && isHexRange(s, 24, 36);
1122 if (!isClsid(fileName))
1123 m_fileDialog->SetFileName((
wchar_t*)fileName.utf16());
1131 const int index = filters.indexOf(needle);
1134 for (
int i = 0; i < filters.size(); ++i)
1135 if (filters.at(i).startsWith(needle))
1142 if (filter.isEmpty())
1144 const int index = indexOfNameFilter(m_nameFilters, filter);
1146 qWarning(
"%s: Invalid parameter '%s' not found in '%s'.",
1147 __FUNCTION__, qPrintable(filter),
1148 qPrintable(m_nameFilters.join(u", ")));
1151 m_fileDialog->SetFileTypeIndex(index + 1);
1157 if (SUCCEEDED(m_fileDialog->GetFileTypeIndex(&uIndex))) {
1158 const int index = uIndex - 1;
1159 if (index < m_nameFilters.size())
1160 return m_nameFilters.at(index);
1169 m_data.setDirectory(directory);
1170 emit directoryEntered(directory);
1176 const QList<QUrl> current = selectedFiles();
1177 m_data.setSelectedFiles(current);
1178 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << current << current.size();
1180 if (current.size() == 1)
1181 emit currentChanged(current.front());
1186 const QString filter = selectedNameFilter();
1187 m_data.setSelectedNameFilter(filter);
1188 emit filterSelected(filter);
1194 m_data.setSelectedFiles(dialogResult());
1200 m_fileDialog->Close(S_OK);
1203 const HWND hwnd = QWindowsDialogs::getHWND(m_fileDialog);
1204 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"closing" << hwnd;
1205 if (hwnd && IsWindowVisible(hwnd))
1206 PostMessageW(hwnd, WM_CLOSE, 0, 0);
1211 m_nativeFileDialog->onFolderChange(item);
1229 return m_nativeFileDialog->onFileOk() ? S_OK : S_FALSE;
1233
1234
1235
1236
1237
1238
1239
1256 int suffixPos = filter.indexOf(u"*.");
1260 int endPos = filter.indexOf(u' ', suffixPos + 1);
1262 endPos = filter.indexOf(u';', suffixPos + 1);
1264 endPos = filter.indexOf(u')', suffixPos + 1);
1266 endPos = filter.size();
1267 return filter.mid(suffixPos, endPos - suffixPos);
1272 QWindowsNativeFileDialogBase::setNameFilters(f);
1277 for (
const QString &filter : f) {
1278 const QString suffix = suffixFromFilter(filter);
1279 if (!suffix.isEmpty()) {
1280 setDefaultSuffixSys(suffix);
1290 IShellItem *item =
nullptr;
1291 if (SUCCEEDED(fileDialog()->GetResult(&item)) && item)
1299 IShellItem *item =
nullptr;
1300 const HRESULT hr = fileDialog()->GetCurrentSelection(&item);
1301 if (SUCCEEDED(hr) && item) {
1309
1310
1311
1312
1313
1314
1315
1326 inline IFileOpenDialog *openFileDialog()
const
1327 {
return static_cast<IFileOpenDialog *>(fileDialog()); }
1339 for (
const QString &file : std::as_const(*temporaryItemCopies()))
1340 QFile::remove(file);
1348 return c.isLetterOrNumber() || c == u'_' || c == u'-';
1353 const int lastSlash = qMax(name.lastIndexOf(u'/'),
1354 name.lastIndexOf(u'\\'));
1355 if (lastSlash != -1)
1356 name.remove(0, lastSlash + 1);
1358 int lastDot = name.lastIndexOf(u'.');
1360 lastDot = name.size();
1361 name.insert(lastDot,
"_XXXXXX"_L1);
1363 for (
int i = lastDot - 1; i >= 0; --i) {
1364 if (!validFileNameCharacter(name.at(i)))
1368 name.prepend(QDir::tempPath() + u'/');
1375 *errorMessage =
"Item not streamable"_L1;
1379 QTemporaryFile targetFile(tempFilePattern(qItem.normalDisplay()));
1380 targetFile.setAutoRemove(
false);
1381 if (!targetFile.open()) {
1382 *errorMessage =
"Cannot create temporary file: "_L1
1383 + targetFile.errorString();
1386 if (!qItem.copyData(&targetFile, errorMessage))
1388 const QString result = targetFile.fileName();
1389 if (temporaryItemCopies()->isEmpty())
1391 temporaryItemCopies()->append(result);
1397 QUrl url = qItem.url();
1398 if (url.isLocalFile() || url.scheme().startsWith(u"http"))
1400 const QString path = qItem.path();
1402 const QString temporaryCopy = createTemporaryItemCopy(qItem, errorMessage);
1403 if (temporaryCopy.isEmpty()) {
1404 QDebug(errorMessage).noquote() <<
"Unable to create a local copy of"
1405 << qItem <<
": " << errorMessage;
1408 return QUrl::fromLocalFile(temporaryCopy);
1411 QDebug(errorMessage).noquote() <<
"Invalid URL obtained from" << qItem;
1418 IShellItemArray *items =
nullptr;
1419 if (SUCCEEDED(openFileDialog()->GetResults(&items)) && items) {
1420 QString errorMessage;
1421 for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) {
1422 QWindowsShellItem qItem(item);
1423 const QUrl url = itemToDialogUrl(qItem, &errorMessage);
1424 if (!url.isValid()) {
1425 qWarning(
"%s", qPrintable(errorMessage));
1438 IShellItemArray *items =
nullptr;
1439 const HRESULT hr = openFileDialog()->GetSelectedItems(&items);
1440 if (SUCCEEDED(hr) && items) {
1441 for (IShellItem *item : QWindowsShellItem::itemsFromItemArray(items)) {
1442 const QWindowsShellItem qItem(item);
1443 const QUrl url = qItem.url();
1447 qWarning().nospace() <<
__FUNCTION__<<
": Unable to obtain URL of " << qItem;
1454
1455
1456
1457
1463 if (am == QFileDialogOptions::AcceptOpen) {
1465 if (!result->init(CLSID_FileOpenDialog, IID_IFileOpenDialog)) {
1470 result =
new QWindowsNativeSaveFileDialog(data);
1471 if (!result->init(CLSID_FileSaveDialog, IID_IFileSaveDialog)) {
1480
1481
1482
1483
1484
1485
1486
1487
1518 QObject::connect(result, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
1519 QObject::connect(result, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
1520 QObject::connect(result, &QWindowsNativeFileDialogBase::directoryEntered,
1521 this, &QPlatformFileDialogHelper::directoryEntered);
1522 QObject::connect(result, &QWindowsNativeFileDialogBase::currentChanged,
1523 this, &QPlatformFileDialogHelper::currentChanged);
1524 QObject::connect(result, &QWindowsNativeFileDialogBase::filterSelected,
1525 this, &QPlatformFileDialogHelper::filterSelected);
1528 const QSharedPointer<QFileDialogOptions> &opts = options();
1529 m_data.fromOptions(opts);
1530 const QFileDialogOptions::FileMode mode = opts->fileMode();
1531 result->setWindowTitle(opts->windowTitle());
1532 result->setMode(mode, opts->acceptMode(), opts->options());
1533 result->setHideFiltersDetails(opts->testOption(QFileDialogOptions::HideNameFilterDetails));
1534 const QStringList nameFilters = opts->nameFilters();
1535 if (!nameFilters.isEmpty())
1536 result->setNameFilters(nameFilters);
1537 if (opts->isLabelExplicitlySet(QFileDialogOptions::FileName))
1538 result->setLabelText(QFileDialogOptions::FileName, opts->labelText(QFileDialogOptions::FileName));
1539 if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
1540 result->setLabelText(QFileDialogOptions::Accept, opts->labelText(QFileDialogOptions::Accept));
1541 if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
1542 result->setLabelText(QFileDialogOptions::Reject, opts->labelText(QFileDialogOptions::Reject));
1545 const QList<QUrl> initialSelection = opts->initiallySelectedFiles();
1546 if (!initialSelection.empty()) {
1547 const QUrl &url = initialSelection.constFirst();
1548 if (url.isLocalFile()) {
1549 QFileInfo info(url.toLocalFile());
1551 result->selectFile(info.fileName());
1553 result->selectFile(url.fileName());
1557 if (mode != QFileDialogOptions::Directory && mode != QFileDialogOptions::DirectoryOnly) {
1558 const QString initialNameFilter = opts->initiallySelectedNameFilter();
1559 if (!initialNameFilter.isEmpty())
1560 result->selectNameFilter(initialNameFilter);
1562 const QString defaultSuffix = opts->defaultSuffix();
1563 if (!defaultSuffix.isEmpty())
1564 result->setDefaultSuffix(defaultSuffix);
1570 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << directory.toString();
1572 m_data.setDirectory(directory);
1573 if (hasNativeDialog())
1579 return m_data.directory();
1584 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << fileName.toString();
1586 if (hasNativeDialog())
1587 nativeFileDialog()->selectFile(fileName.fileName());
1592 return m_data.selectedFiles();
1597 qCDebug(lcQpaDialogs) <<
__FUNCTION__;
1602 m_data.setSelectedNameFilter(filter);
1603 if (hasNativeDialog())
1609 return m_data.selectedNameFilter();
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1642 void populateOpenFileName(OPENFILENAME *ofn, HWND owner)
const;
1643 QList<QUrl> execExistingDir(HWND owner);
1644 QList<QUrl> execFileNames(HWND owner,
int *selectedFilterIndex)
const;
1646 const OptionsPtr m_options;
1648 QPlatformDialogHelper::DialogCode m_result;
1659 m_options(options), m_result(QPlatformDialogHelper::Rejected), m_data(data)
1661 setWindowTitle(m_options->windowTitle());
1666 int selectedFilterIndex = -1;
1667 const QList<QUrl> selectedFiles =
1668 m_options->fileMode() == QFileDialogOptions::DirectoryOnly ?
1669 execExistingDir(owner) : execFileNames(owner, &selectedFilterIndex);
1670 m_data.setSelectedFiles(selectedFiles);
1672 if (selectedFiles.isEmpty()) {
1673 m_result = QPlatformDialogHelper::Rejected;
1676 const QStringList nameFilters = m_options->nameFilters();
1677 if (selectedFilterIndex >= 0 && selectedFilterIndex < nameFilters.size())
1678 m_data.setSelectedNameFilter(nameFilters.at(selectedFilterIndex));
1679 const QUrl &firstFile = selectedFiles.constFirst();
1680 m_data.setDirectory(firstFile.adjusted(QUrl::RemoveFilename));
1681 m_result = QPlatformDialogHelper::Accepted;
1689static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
1691 auto *dialog =
reinterpret_cast<QWindowsXpNativeFileDialog *>(lpData);
1692 return dialog->existingDirCallback(hwnd, uMsg, lParam);
1698 case BFFM_INITIALIZED: {
1699 if (!m_title.isEmpty())
1700 SetWindowText(hwnd,
reinterpret_cast<
const wchar_t *>(m_title.utf16()));
1701 const QString initialFile = QDir::toNativeSeparators(m_data.directory().toLocalFile());
1702 if (!initialFile.isEmpty())
1703 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, LPARAM(initialFile.utf16()));
1706 case BFFM_SELCHANGED: {
1707 wchar_t path[MAX_PATH];
1708 const bool ok = SHGetPathFromIDList(
reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path)
1710 SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1);
1720 wchar_t initPath[MAX_PATH];
1722 bi.hwndOwner = owner;
1723 bi.pidlRoot =
nullptr;
1724 bi.lpszTitle =
nullptr;
1725 bi.pszDisplayName = initPath;
1726 bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE;
1727 bi.lpfn = xpFileDialogGetExistingDirCallbackProc;
1728 bi.lParam = LPARAM(
this);
1729 QList<QUrl> selectedFiles;
1730 if (
const auto pItemIDList = SHBrowseForFolder(&bi)) {
1731 wchar_t path[MAX_PATH];
1733 if (SHGetPathFromIDList(pItemIDList, path) && path[0])
1734 selectedFiles.push_back(QUrl::fromLocalFile(QDir::cleanPath(QString::fromWCharArray(path))));
1736 if (SHGetMalloc(&pMalloc) == NOERROR) {
1737 pMalloc->Free(pItemIDList);
1741 return selectedFiles;
1747 ZeroMemory(ofn,
sizeof(OPENFILENAME));
1748 ofn->lStructSize =
sizeof(OPENFILENAME);
1749 ofn->hwndOwner = owner;
1752 int totalStringLength = 0;
1753 const QList<FilterSpec> specs =
1754 filterSpecs(m_options->nameFilters(), m_options->options() & QFileDialogOptions::HideNameFilterDetails, &totalStringLength);
1755 const int size = specs.size();
1756 auto *ptr =
new wchar_t[totalStringLength + 2 * size + 1];
1757 ofn->lpstrFilter = ptr;
1758 for (
const FilterSpec &spec : specs) {
1759 ptr += spec.description.toWCharArray(ptr);
1761 ptr += spec.filter.toWCharArray(ptr);
1765 const int nameFilterIndex = indexOfNameFilter(m_options->nameFilters(), m_data.selectedNameFilter());
1766 if (nameFilterIndex >= 0)
1767 ofn->nFilterIndex = nameFilterIndex + 1;
1771 ofn->nMaxFile = 65535;
1772 QString initiallySelectedFile = m_data.selectedFile();
1773 initiallySelectedFile.remove(u'<');
1774 initiallySelectedFile.remove(u'>');
1775 initiallySelectedFile.remove(u'"');
1776 initiallySelectedFile.remove(u'|');
1777 ofn->lpstrFile = qStringToWCharArray(QDir::toNativeSeparators(initiallySelectedFile), ofn->nMaxFile);
1778 ofn->lpstrInitialDir = qStringToWCharArray(QDir::toNativeSeparators(m_data.directory().toLocalFile()));
1779 ofn->lpstrTitle = (
wchar_t*)m_title.utf16();
1785 if (m_options->acceptMode() == QFileDialogOptions::AcceptSave) {
1786 QString defaultSuffix = m_options->defaultSuffix();
1787 if (defaultSuffix.startsWith(u'.'))
1788 defaultSuffix.remove(0, 1);
1790 ofn->lpstrDefExt = qStringToWCharArray(defaultSuffix);
1793 ofn->Flags = (OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_PATHMUSTEXIST);
1794 if (m_options->fileMode() == QFileDialogOptions::ExistingFile
1795 || m_options->fileMode() == QFileDialogOptions::ExistingFiles)
1796 ofn->Flags |= (OFN_FILEMUSTEXIST);
1797 if (m_options->fileMode() == QFileDialogOptions::ExistingFiles)
1798 ofn->Flags |= (OFN_ALLOWMULTISELECT);
1799 if (!(m_options->options() & QFileDialogOptions::DontConfirmOverwrite))
1800 ofn->Flags |= OFN_OVERWRITEPROMPT;
1805 *selectedFilterIndex = -1;
1807 populateOpenFileName(&ofn, owner);
1809 const bool isSave = m_options->acceptMode() == QFileDialogOptions::AcceptSave;
1810 if (isSave ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn)) {
1811 *selectedFilterIndex = ofn.nFilterIndex - 1;
1812 const QString dir = QDir::cleanPath(QString::fromWCharArray(ofn.lpstrFile));
1813 result.push_back(QUrl::fromLocalFile(dir));
1816 if (ofn.Flags & (OFN_ALLOWMULTISELECT)) {
1817 wchar_t *ptr = ofn.lpstrFile + dir.size() + 1;
1820 const QString path = dir + u'/';
1822 const QString fileName = QString::fromWCharArray(ptr);
1823 result.push_back(QUrl::fromLocalFile(path + fileName));
1824 ptr += fileName.size() + 1;
1829 delete [] ofn.lpstrFile;
1830 delete [] ofn.lpstrInitialDir;
1831 delete [] ofn.lpstrFilter;
1832 delete [] ofn.lpstrDefExt;
1837
1838
1839
1840
1841
1842
1869 m_data.fromOptions(options());
1871 QObject::connect(result, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
1872 QObject::connect(result, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
1880 m_data.setDirectory(directory);
1885 return m_data.directory();
1890 m_data.setSelectedFiles(QList<QUrl>() << url);
1895 return m_data.selectedFiles();
1900 m_data.setSelectedNameFilter(f);
1905 return m_data.selectedNameFilter();
1910
1911
1912
1913
1914
1915
1916
1917
1918
1920using SharedPointerColor = QSharedPointer<QColor>;
1922#ifdef USE_NATIVE_COLOR_DIALOG
1923class QWindowsNativeColorDialog :
public QWindowsNativeDialogBase
1927 enum { CustomColorCount = 16 };
1929 explicit QWindowsNativeColorDialog(
const SharedPointerColor &color);
1931 void setWindowTitle(
const QString &) override {}
1934 void close() override {}
1937 void doExec(HWND owner = 0) override;
1939 COLORREF m_customColors[CustomColorCount];
1940 QPlatformDialogHelper::DialogCode m_code;
1941 SharedPointerColor m_color;
1944QWindowsNativeColorDialog::QWindowsNativeColorDialog(
const SharedPointerColor &color) :
1945 m_code(QPlatformDialogHelper::Rejected), m_color(color)
1947 std::fill(m_customColors, m_customColors + 16, COLORREF(0));
1950void QWindowsNativeColorDialog::doExec(HWND owner)
1952 CHOOSECOLOR chooseColor;
1953 ZeroMemory(&chooseColor,
sizeof(chooseColor));
1954 chooseColor.lStructSize =
sizeof(chooseColor);
1955 chooseColor.hwndOwner = owner;
1956 chooseColor.lpCustColors = m_customColors;
1957 QRgb *qCustomColors = QColorDialogOptions::customColors();
1958 const int customColorCount = qMin(QColorDialogOptions::customColorCount(),
1959 int(CustomColorCount));
1960 for (
int c= 0; c < customColorCount; ++c)
1961 m_customColors[c] = qColorToCOLORREF(QColor(qCustomColors[c]));
1962 chooseColor.rgbResult = qColorToCOLORREF(*m_color);
1963 chooseColor.Flags = CC_FULLOPEN | CC_RGBINIT;
1964 m_code = ChooseColorW(&chooseColor) ?
1965 QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected;
1966 QWindowsDialogs::eatMouseMove();
1967 if (m_code == QPlatformDialogHelper::Accepted) {
1968 *m_color = COLORREFToQColor(chooseColor.rgbResult);
1969 for (
int c= 0; c < customColorCount; ++c)
1970 qCustomColors[c] = COLORREFToQColor(m_customColors[c]).rgb();
1978
1979
1980
1981
1982
1983
1984
1985
1986
1988class QWindowsColorDialogHelper :
public QWindowsDialogHelperBase<QPlatformColorDialogHelper>
1991 QWindowsColorDialogHelper() : m_currentColor(
new QColor) {}
1993 virtual bool supportsNonModalDialog()
1996 virtual QColor currentColor()
const {
return *m_currentColor; }
1997 virtual void setCurrentColor(
const QColor &c) { *m_currentColor = c; }
2000 inline QWindowsNativeColorDialog *nativeFileDialog()
const
2001 {
return static_cast<QWindowsNativeColorDialog *>(nativeDialog()); }
2002 virtual QWindowsNativeDialogBase *createNativeDialog();
2004 SharedPointerColor m_currentColor;
2007QWindowsNativeDialogBase *QWindowsColorDialogHelper::createNativeDialog()
2009 QWindowsNativeColorDialog *nativeDialog =
new QWindowsNativeColorDialog(m_currentColor);
2010 nativeDialog->setWindowTitle(options()->windowTitle());
2011 connect(nativeDialog, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
2012 connect(nativeDialog, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
2013 return nativeDialog;
2028#ifdef USE_NATIVE_COLOR_DIALOG
2052#ifdef USE_NATIVE_COLOR_DIALOG
2069#include "qwindowsdialoghelpers.moc"