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;
62
63
64
65
66
67
68
69
70
74 MSG msg = {
nullptr, 0, 0, 0, 0, {0, 0} };
75 while (PeekMessage(&msg,
nullptr, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE))
77 if (msg.message == WM_MOUSEMOVE)
78 PostMessage(msg.hwnd, msg.message, 0, msg.lParam);
79 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"triggered=" << (msg.message == WM_MOUSEMOVE);
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.");
91 if (FAILED(oleWindow->GetWindow(&result)))
92 qCWarning(lcQpaDialogs,
"Native file dialog: unable to get dialog's window.");
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
128
136 void exec(HWND owner =
nullptr) { doExec(owner); m_executed =
true; }
149 virtual void doExec(HWND owner =
nullptr) = 0;
155
156
157
158
159
160
161
162
163
164
165
166
167
169template <
class BaseClass>
176template <
class BaseClass>
185 if (m_thread->wait(500))
188 qCCritical(lcQpaDialogs) <<
__FUNCTION__ <<
"Thread failed to finish.";
193template <
class BaseClass>
196 if (m_nativeDialog.isNull()) {
197 qWarning(
"%s invoked with no native dialog present.",
__FUNCTION__);
200 return m_nativeDialog.data();
203template <
class BaseClass>
209template <
class BaseClass>
214 if (m_nativeDialog.isNull() || m_nativeDialog->executed())
215 m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog(), &QObject::deleteLater);
216 return m_nativeDialog.data();
220
221
222
223
224
225
237 const QWindowsNativeDialogBasePtr m_dialog;
243 qCDebug(lcQpaDialogs) <<
'>' <<
__FUNCTION__;
244 QComHelper comInit(COINIT_APARTMENTTHREADED);
245 m_dialog->exec(m_owner);
246 qCDebug(lcQpaDialogs) <<
'<' <<
__FUNCTION__;
249template <
class BaseClass>
251 Qt::WindowModality windowModality,
254 const bool modal = (windowModality != Qt::NonModal);
256 parent = QGuiApplication::focusWindow();
258 m_ownerWindow = QWindowsWindow::handleOf(parent);
260 m_ownerWindow =
nullptr;
262 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"modal=" << modal
263 <<
" modal supported? " << supportsNonModalDialog(parent)
264 <<
"native=" << m_nativeDialog.data() <<
"owner" << m_ownerWindow;
265 if (!modal && !supportsNonModalDialog(parent))
267 if (!ensureNativeDialog())
275 m_timer.start(0ns,
this);
282template <
class BaseClass>
285 Q_ASSERT(!m_nativeDialog.isNull());
287 m_thread =
new QWindowsDialogThread(m_nativeDialog, m_ownerWindow);
292template <
class BaseClass>
298template <
class BaseClass>
301 if (m_nativeDialog) {
302 m_nativeDialog->close();
303 m_nativeDialog.clear();
305 m_ownerWindow =
nullptr;
308template <
class BaseClass>
311 qCDebug(lcQpaDialogs) <<
__FUNCTION__;
314 nd->exec(m_ownerWindow);
315 m_nativeDialog.clear();
320
321
322
323
324
325
326
327
328
329
330
347 class Data :
public QSharedData {
350 QString selectedNameFilter;
351 QList<QUrl> selectedFiles;
354 QExplicitlySharedDataPointer<Data> m_data;
359 m_data->mutex.lock();
360 const QUrl result = m_data->directory;
361 m_data->mutex.unlock();
367 QMutexLocker locker(&m_data->mutex);
368 m_data->directory = d;
373 m_data->mutex.lock();
374 const QString result = m_data->selectedNameFilter;
375 m_data->mutex.unlock();
381 QMutexLocker locker(&m_data->mutex);
382 m_data->selectedNameFilter = f;
387 m_data->mutex.lock();
388 const auto result = m_data->selectedFiles;
389 m_data->mutex.unlock();
395 const auto files = selectedFiles();
396 return files.isEmpty() ? QString() : files.front().toLocalFile();
401 QMutexLocker locker(&m_data->mutex);
402 m_data->selectedFiles = urls;
407 QMutexLocker locker(&m_data->mutex);
408 m_data->directory = o->initialDirectory();
409 m_data->selectedFiles = o->initiallySelectedFiles();
410 m_data->selectedNameFilter = o->initiallySelectedNameFilter();
414
415
416
417
418
419
420
421
422
438 FDE_SHAREVIOLATION_RESPONSE *)
override
449 m_nativeFileDialog(nativeFileDialog) {}
457 IFileDialogEvents *result;
459 if (FAILED(eventHandler->QueryInterface(IID_IFileDialogEvents,
reinterpret_cast<
void **>(&result)))) {
460 qErrnoWarning(
"Unable to obtain IFileDialogEvents");
463 eventHandler->Release();
468
469
470
471
472
473
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); }
493 bool isFileSystem()
const {
return (m_attributes & SFGAO_FILESYSTEM) != 0; }
494 bool isDir()
const {
return (m_attributes & SFGAO_FOLDER) != 0; }
496 bool canStream()
const {
return (m_attributes & SFGAO_STREAM) != 0; }
498 bool copyData(QIODevice *out, QString *errorMessage);
502#ifndef QT_NO_DEBUG_STREAM
507 static QString displayName(IShellItem *item, SIGDN mode);
508 static QString libraryItemDefaultSaveFolder(IShellItem *item);
509 QUrl urlValue()
const;
519 SFGAOF mask = (SFGAO_CAPABILITYMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK);
522 if (FAILED(item->GetAttributes((SFGAO_STREAM | SFGAO_COMPRESSED), &m_attributes))) {
526 if (m_attributes & (SFGAO_STREAM | SFGAO_COMPRESSED))
527 mask &= ~SFGAO_HASSUBFOLDER;
528 if (FAILED(item->GetAttributes(mask, &m_attributes)))
536 return QDir::cleanPath(QWindowsShellItem::displayName(m_item, SIGDN_FILESYSPATH));
539 return QWindowsShellItem::libraryItemDefaultSaveFolder(m_item);
546 const QString urlString = displayName(m_item, SIGDN_URL);
547 if (!urlString.isEmpty()) {
548 const QUrl parsed = QUrl(urlString);
549 if (parsed.isValid()) {
552 qWarning(
"%s: Unable to decode URL \"%s\": %s",
__FUNCTION__,
553 qPrintable(urlString), qPrintable(parsed.errorString()));
562 const QString fsPath = path();
563 if (!fsPath.isEmpty())
564 return QUrl::fromLocalFile(fsPath);
565 const QUrl urlV = urlValue();
569 const QString data =
"data:text/plain;base64,"_L1
570 + QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64());
576 LPWSTR name =
nullptr;
578 if (SUCCEEDED(item->GetDisplayName(mode, &name))) {
579 result = QString::fromWCharArray(name);
589 if (FAILED(items->GetCount(&itemCount)) || itemCount == 0)
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);
603 *errorMessage =
"Item not streamable"_L1;
606 IStream *istream =
nullptr;
607 HRESULT hr = m_item->BindToHandler(
nullptr, BHID_Stream, IID_PPV_ARGS(&istream));
609 *errorMessage =
"BindToHandler() failed: "_L1
610 + QSystemError::windowsComString(hr);
613 enum : ULONG { bufSize = 102400 };
614 char buffer[bufSize];
618 hr = istream->Read(buffer, bufSize, &bytesRead);
619 if ((hr == S_OK || hr == S_FALSE) && bytesRead)
620 out->write(buffer, bytesRead);
625 if (hr != S_OK && hr != S_FALSE) {
626 *errorMessage =
"Read() failed: "_L1
627 + QSystemError::windowsComString(hr);
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}};
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));
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));
669#ifndef QT_NO_DEBUG_STREAM
672 d <<
"attributes=0x" << Qt::hex << attributes() << Qt::dec;
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();
687 d <<
"\", url=" << urlV;
692 QDebugStateSaver saver(d);
701QDebug operator<<(QDebug d, IShellItem *i)
703 QDebugStateSaver saver(d);
706 d <<
"IShellItem(" <<
static_cast<
const void *>(i);
717
718
719
720
721
722
723
724
725
730 Q_PROPERTY(
bool hideFiltersDetails READ hideFiltersDetails WRITE setHideFiltersDetails)
737 inline void setMode(QFileDialogOptions::FileMode mode, QFileDialogOptions::AcceptMode acceptMode, QFileDialogOptions::FileDialogOptions options);
741 void doExec(HWND owner =
nullptr)
override;
751 inline void setLabelText(QFileDialogOptions::DialogLabel l,
const QString &text);
774 bool init(
const CLSID &clsId,
const IID &iid);
783 IFileDialog *m_fileDialog =
nullptr;
784 IFileDialogEvents *m_dialogEvents =
nullptr;
786 QStringList m_nameFilters;
787 bool m_hideFiltersDetails =
false;
788 bool m_hasDefaultSuffix =
false;
800 if (m_dialogEvents && m_fileDialog)
801 m_fileDialog->Unadvise(m_cookie);
803 m_dialogEvents->Release();
805 m_fileDialog->Release();
810 HRESULT hr = CoCreateInstance(clsId,
nullptr, CLSCTX_INPROC_SERVER,
811 iid,
reinterpret_cast<
void **>(&m_fileDialog));
813 qErrnoWarning(
"CoCreateInstance failed");
816 m_dialogEvents = QWindowsNativeFileDialogEventHandler::create(
this);
820 hr = m_fileDialog->Advise(m_dialogEvents, &m_cookie);
822 qErrnoWarning(
"IFileDialog::Advise failed");
825 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << m_fileDialog << m_dialogEvents << m_cookie;
833 m_fileDialog->SetTitle(
reinterpret_cast<
const wchar_t *>(title.utf16()));
838 if (url.isLocalFile()) {
839 IShellItem *result =
nullptr;
840 const QString native = QDir::toNativeSeparators(url.toLocalFile());
842 SHCreateItemFromParsingName(
reinterpret_cast<
const wchar_t *>(native.utf16()),
843 nullptr, IID_IShellItem,
844 reinterpret_cast<
void **>(&result));
846 qErrnoWarning(
"%s: SHCreateItemFromParsingName(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
850 }
else if (url.scheme() == u"clsid") {
854 IShellItem *result =
nullptr;
855 const auto uuid = QUuid::fromString(url.path());
857 qWarning() <<
__FUNCTION__ <<
": Invalid CLSID: " << url.path();
860 PIDLIST_ABSOLUTE idList;
861 HRESULT hr = SHGetKnownFolderIDList(uuid, 0,
nullptr, &idList);
863 qErrnoWarning(
"%s: SHGetKnownFolderIDList(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
866 hr = SHCreateItemFromIDList(idList, IID_IShellItem,
reinterpret_cast<
void **>(&result));
867 CoTaskMemFree(idList);
869 qErrnoWarning(
"%s: SHCreateItemFromIDList(%s)) failed",
__FUNCTION__, qPrintable(url.toString()));
874 qWarning() <<
__FUNCTION__ <<
": Unhandled scheme: " << url.scheme();
881 if (!directory.isEmpty()) {
882 if (IShellItem *psi = QWindowsNativeFileDialogBase::shellItem(directory)) {
883 m_fileDialog->SetFolder(psi);
892 IShellItem *item =
nullptr;
893 if (m_fileDialog && SUCCEEDED(m_fileDialog->GetFolder(&item)) && item) {
902 qCDebug(lcQpaDialogs) <<
'>' <<
__FUNCTION__;
905 const HRESULT hr = m_fileDialog->Show(owner);
907 qCDebug(lcQpaDialogs) <<
'<' <<
__FUNCTION__ <<
" returns " << Qt::hex << hr;
910 if (hr == S_OK && !m_data.selectedFiles().isEmpty()) {
918 QFileDialogOptions::AcceptMode acceptMode,
919 QFileDialogOptions::FileDialogOptions options)
921 DWORD flags = FOS_PATHMUSTEXIST;
922 if (QWindowsContext::readAdvancedExplorerSettings(L"Hidden", 1) == 1)
923 flags |= FOS_FORCESHOWHIDDEN;
924 if (options & QFileDialogOptions::DontResolveSymlinks)
925 flags |= FOS_NODEREFERENCELINKS;
927 case QFileDialogOptions::AnyFile:
928 if (acceptMode == QFileDialogOptions::AcceptSave)
929 flags |= FOS_NOREADONLYRETURN;
930 if (!(options & QFileDialogOptions::DontConfirmOverwrite))
931 flags |= FOS_OVERWRITEPROMPT;
933 case QFileDialogOptions::ExistingFile:
934 flags |= FOS_FILEMUSTEXIST;
936 case QFileDialogOptions::Directory:
937 case QFileDialogOptions::DirectoryOnly:
940 flags |= FOS_PICKFOLDERS | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM;
942 case QFileDialogOptions::ExistingFiles:
943 flags |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
946 qCDebug(lcQpaDialogs) <<
__FUNCTION__ <<
"mode=" << mode
947 <<
"acceptMode=" << acceptMode <<
"options=" << options
948 <<
"results in" << Qt::showbase << Qt::hex << flags;
950 if (FAILED(m_fileDialog->SetOptions(flags)))
951 qErrnoWarning(
"%s: SetOptions() failed",
__FUNCTION__);
962 bool hideFilterDetails,
963 int *totalStringLength)
965 QList<FilterSpec> result;
966 result.reserve(filters.size());
967 *totalStringLength = 0;
969#if QT_CONFIG(regularexpression)
970 const QRegularExpression filterSeparatorRE(QStringLiteral(
"[;\\s]+"));
971 const QString separator = QStringLiteral(
";");
972 Q_ASSERT(filterSeparatorRE.isValid());
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 ?
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);
990 filterSpec.filter.replace(u' ', u';');
992 filterSpec.description = filterString;
993 if (hideFilterDetails && openingParenPos != -1) {
994 filterSpec.description.truncate(openingParenPos);
995 while (filterSpec.description.endsWith(u' '))
996 filterSpec.description.truncate(filterSpec.description.size() - 1);
998 *totalStringLength += filterSpec.filter.size() + filterSpec.description.size();
999 result.push_back(filterSpec);
1007
1008 m_nameFilters = filters;
1009 int totalStringLength = 0;
1010 const QList<FilterSpec> specs = filterSpecs(filters, m_hideFiltersDetails, &totalStringLength);
1011 const int size = specs.size();
1013 QScopedArrayPointer<WCHAR> buffer(
new WCHAR[totalStringLength + 2 * size]);
1014 QScopedArrayPointer<COMDLG_FILTERSPEC> comFilterSpec(
new COMDLG_FILTERSPEC[size]);
1016 WCHAR *ptr = buffer.data();
1020 for (
int i = 0; i < size; ++i) {
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'(');
1029 description.truncate(pos);
1030 while (!description.isEmpty() && description.back().isSpace())
1031 description.chop(1);
1035 comFilterSpec[i].pszName = ptr;
1036 ptr += description.toWCharArray(ptr);
1038 comFilterSpec[i].pszSpec = ptr;
1039 ptr += specs[i].filter.toWCharArray(ptr);
1043 m_fileDialog->SetFileTypes(size, comFilterSpec.data());
1048 setDefaultSuffixSys(s);
1049 m_hasDefaultSuffix = !s.isEmpty();
1057 auto *wSuffix =
const_cast<
wchar_t *>(
reinterpret_cast<
const wchar_t *>(s.utf16()));
1058 m_fileDialog->SetDefaultExtension(wSuffix);
1063 IFileDialog2 *result;
1064 return SUCCEEDED(fileDialog->QueryInterface(IID_IFileDialog2,
reinterpret_cast<
void **>(&result)))
1070 auto *wText =
const_cast<
wchar_t *>(
reinterpret_cast<
const wchar_t *>(text.utf16()));
1072 case QFileDialogOptions::FileName:
1073 m_fileDialog->SetFileNameLabel(wText);
1075 case QFileDialogOptions::Accept:
1076 m_fileDialog->SetOkButtonLabel(wText);
1078 case QFileDialogOptions::Reject:
1079 if (IFileDialog2 *dialog2 = getFileDialog2(m_fileDialog)) {
1080 dialog2->SetCancelButtonLabel(wText);
1084 case QFileDialogOptions::LookIn:
1085 case QFileDialogOptions::FileType:
1086 case QFileDialogOptions::DialogLabelCount:
1093 for (;start < end; ++start) {
1094 QChar ch = s.at(start);
1096 || (ch >= u'a' && ch <= u'f')
1097 || (ch >= u'A' && ch <= u'F')))
1106 const QChar dash(u'-');
1107 return s.size() == 36
1108 && isHexRange(s, 0, 8)
1110 && isHexRange(s, 9, 13)
1112 && isHexRange(s, 14, 18)
1114 && isHexRange(s, 19, 23)
1116 && isHexRange(s, 24, 36);
1123 if (!isClsid(fileName))
1124 m_fileDialog->SetFileName((
wchar_t*)fileName.utf16());
1132 const int index = filters.indexOf(needle);
1135 for (
int i = 0; i < filters.size(); ++i)
1136 if (filters.at(i).startsWith(needle))
1143 if (filter.isEmpty())
1145 const int index = indexOfNameFilter(m_nameFilters, filter);
1147 qWarning(
"%s: Invalid parameter '%s' not found in '%s'.",
1148 __FUNCTION__, qPrintable(filter),
1149 qPrintable(m_nameFilters.join(u", ")));
1152 m_fileDialog->SetFileTypeIndex(index + 1);
1158 if (SUCCEEDED(m_fileDialog->GetFileTypeIndex(&uIndex))) {
1159 const int index = uIndex - 1;
1160 if (index < m_nameFilters.size())
1161 return m_nameFilters.at(index);
1170 m_data.setDirectory(directory);
1171 emit directoryEntered(directory);
1177 const QList<QUrl> current = selectedFiles();
1178 m_data.setSelectedFiles(current);
1179 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << current << current.size();
1181 if (current.size() == 1)
1182 emit currentChanged(current.front());
1187 const QString filter = selectedNameFilter();
1188 m_data.setSelectedNameFilter(filter);
1189 emit filterSelected(filter);
1195 m_data.setSelectedFiles(dialogResult());
1201 m_fileDialog->Close(S_OK);
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);
1212 m_nativeFileDialog->onFolderChange(item);
1230 return m_nativeFileDialog->onFileOk() ? S_OK : S_FALSE;
1234
1235
1236
1237
1238
1239
1240
1257 int suffixPos = filter.indexOf(u"*.");
1261 int endPos = filter.indexOf(u' ', suffixPos + 1);
1263 endPos = filter.indexOf(u';', suffixPos + 1);
1265 endPos = filter.indexOf(u')', suffixPos + 1);
1267 endPos = filter.size();
1268 return filter.mid(suffixPos, endPos - suffixPos);
1273 QWindowsNativeFileDialogBase::setNameFilters(f);
1278 for (
const QString &filter : f) {
1279 const QString suffix = suffixFromFilter(filter);
1280 if (!suffix.isEmpty()) {
1281 setDefaultSuffixSys(suffix);
1291 IShellItem *item =
nullptr;
1292 if (SUCCEEDED(fileDialog()->GetResult(&item)) && item)
1300 IShellItem *item =
nullptr;
1301 const HRESULT hr = fileDialog()->GetCurrentSelection(&item);
1302 if (SUCCEEDED(hr) && item) {
1310
1311
1312
1313
1314
1315
1316
1327 inline IFileOpenDialog *openFileDialog()
const
1328 {
return static_cast<IFileOpenDialog *>(fileDialog()); }
1340 for (
const QString &file : std::as_const(*temporaryItemCopies()))
1341 QFile::remove(file);
1349 return c.isLetterOrNumber() || c == u'_' || c == u'-';
1354 const int lastSlash = qMax(name.lastIndexOf(u'/'),
1355 name.lastIndexOf(u'\\'));
1356 if (lastSlash != -1)
1357 name.remove(0, lastSlash + 1);
1359 int lastDot = name.lastIndexOf(u'.');
1361 lastDot = name.size();
1362 name.insert(lastDot,
"_XXXXXX"_L1);
1364 for (
int i = lastDot - 1; i >= 0; --i) {
1365 if (!validFileNameCharacter(name.at(i)))
1369 name.prepend(QDir::tempPath() + u'/');
1376 *errorMessage =
"Item not streamable"_L1;
1380 QTemporaryFile targetFile(tempFilePattern(qItem.normalDisplay()));
1381 targetFile.setAutoRemove(
false);
1382 if (!targetFile.open()) {
1383 *errorMessage =
"Cannot create temporary file: "_L1
1384 + targetFile.errorString();
1387 if (!qItem.copyData(&targetFile, errorMessage))
1389 const QString result = targetFile.fileName();
1390 if (temporaryItemCopies()->isEmpty())
1392 temporaryItemCopies()->append(result);
1398 QUrl url = qItem.url();
1399 if (url.isLocalFile() || url.scheme().startsWith(u"http"))
1401 const QString path = qItem.path();
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;
1409 return QUrl::fromLocalFile(temporaryCopy);
1412 QDebug(errorMessage).noquote() <<
"Invalid URL obtained from" << qItem;
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));
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();
1448 qWarning().nospace() <<
__FUNCTION__<<
": Unable to obtain URL of " << qItem;
1455
1456
1457
1458
1464 if (am == QFileDialogOptions::AcceptOpen) {
1466 if (!result->init(CLSID_FileOpenDialog, IID_IFileOpenDialog)) {
1471 result =
new QWindowsNativeSaveFileDialog(data);
1472 if (!result->init(CLSID_FileSaveDialog, IID_IFileSaveDialog)) {
1481
1482
1483
1484
1485
1486
1487
1488
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);
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());
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));
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());
1552 result->selectFile(info.fileName());
1554 result->selectFile(url.fileName());
1558 if (mode != QFileDialogOptions::Directory && mode != QFileDialogOptions::DirectoryOnly) {
1559 const QString initialNameFilter = opts->initiallySelectedNameFilter();
1560 if (!initialNameFilter.isEmpty())
1561 result->selectNameFilter(initialNameFilter);
1563 const QString defaultSuffix = opts->defaultSuffix();
1564 if (!defaultSuffix.isEmpty())
1565 result->setDefaultSuffix(defaultSuffix);
1571 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << directory.toString();
1573 m_data.setDirectory(directory);
1574 if (hasNativeDialog())
1580 return m_data.directory();
1585 qCDebug(lcQpaDialogs) <<
__FUNCTION__ << fileName.toString();
1587 if (hasNativeDialog())
1588 nativeFileDialog()->selectFile(fileName.fileName());
1593 return m_data.selectedFiles();
1598 qCDebug(lcQpaDialogs) <<
__FUNCTION__;
1603 m_data.setSelectedNameFilter(filter);
1604 if (hasNativeDialog())
1610 return m_data.selectedNameFilter();
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1643 void populateOpenFileName(OPENFILENAME *ofn, HWND owner)
const;
1644 QList<QUrl> execExistingDir(HWND owner);
1645 QList<QUrl> execFileNames(HWND owner,
int *selectedFilterIndex)
const;
1647 const OptionsPtr m_options;
1649 QPlatformDialogHelper::DialogCode m_result;
1660 m_options(options), m_result(QPlatformDialogHelper::Rejected), m_data(data)
1662 setWindowTitle(m_options->windowTitle());
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;
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;
1690static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
1692 auto *dialog =
reinterpret_cast<QWindowsXpNativeFileDialog *>(lpData);
1693 return dialog->existingDirCallback(hwnd, uMsg, lParam);
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()));
1707 case BFFM_SELCHANGED: {
1708 wchar_t path[MAX_PATH];
1709 const bool ok = SHGetPathFromIDList(
reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path)
1711 SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1);
1721 wchar_t initPath[MAX_PATH];
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];
1734 if (SHGetPathFromIDList(pItemIDList, path) && path[0])
1735 selectedFiles.push_back(QUrl::fromLocalFile(QDir::cleanPath(QString::fromWCharArray(path))));
1737 if (SHGetMalloc(&pMalloc) == NOERROR) {
1738 pMalloc->Free(pItemIDList);
1742 return selectedFiles;
1748 ZeroMemory(ofn,
sizeof(OPENFILENAME));
1749 ofn->lStructSize =
sizeof(OPENFILENAME);
1750 ofn->hwndOwner = owner;
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);
1762 ptr += spec.filter.toWCharArray(ptr);
1766 const int nameFilterIndex = indexOfNameFilter(m_options->nameFilters(), m_data.selectedNameFilter());
1767 if (nameFilterIndex >= 0)
1768 ofn->nFilterIndex = nameFilterIndex + 1;
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();
1786 if (m_options->acceptMode() == QFileDialogOptions::AcceptSave) {
1787 QString defaultSuffix = m_options->defaultSuffix();
1788 if (defaultSuffix.startsWith(u'.'))
1789 defaultSuffix.remove(0, 1);
1791 ofn->lpstrDefExt = qStringToWCharArray(defaultSuffix);
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;
1806 *selectedFilterIndex = -1;
1808 populateOpenFileName(&ofn, owner);
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));
1817 if (ofn.Flags & (OFN_ALLOWMULTISELECT)) {
1818 wchar_t *ptr = ofn.lpstrFile + dir.size() + 1;
1821 const QString path = dir + u'/';
1823 const QString fileName = QString::fromWCharArray(ptr);
1824 result.push_back(QUrl::fromLocalFile(path + fileName));
1825 ptr += fileName.size() + 1;
1830 delete [] ofn.lpstrFile;
1831 delete [] ofn.lpstrInitialDir;
1832 delete [] ofn.lpstrFilter;
1833 delete [] ofn.lpstrDefExt;
1838
1839
1840
1841
1842
1843
1870 m_data.fromOptions(options());
1872 QObject::connect(result, &QWindowsNativeDialogBase::accepted,
this, &QPlatformDialogHelper::accept);
1873 QObject::connect(result, &QWindowsNativeDialogBase::rejected,
this, &QPlatformDialogHelper::reject);
1881 m_data.setDirectory(directory);
1886 return m_data.directory();
1891 m_data.setSelectedFiles(QList<QUrl>() << url);
1896 return m_data.selectedFiles();
1901 m_data.setSelectedNameFilter(f);
1906 return m_data.selectedNameFilter();
1911
1912
1913
1914
1915
1916
1917
1918
1919
1921using SharedPointerColor = QSharedPointer<QColor>;
1923#ifdef USE_NATIVE_COLOR_DIALOG
1924class QWindowsNativeColorDialog :
public QWindowsNativeDialogBase
1928 enum { CustomColorCount = 16 };
1930 explicit QWindowsNativeColorDialog(
const SharedPointerColor &color);
1932 void setWindowTitle(
const QString &) override {}
1935 void close() override {}
1938 void doExec(HWND owner = 0) override;
1940 COLORREF m_customColors[CustomColorCount];
1941 QPlatformDialogHelper::DialogCode m_code;
1942 SharedPointerColor m_color;
1945QWindowsNativeColorDialog::QWindowsNativeColorDialog(
const SharedPointerColor &color) :
1946 m_code(QPlatformDialogHelper::Rejected), m_color(color)
1948 std::fill(m_customColors, m_customColors + 16, COLORREF(0));
1951void QWindowsNativeColorDialog::doExec(HWND owner)
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();
1979
1980
1981
1982
1983
1984
1985
1986
1987
1989class QWindowsColorDialogHelper :
public QWindowsDialogHelperBase<QPlatformColorDialogHelper>
1992 QWindowsColorDialogHelper() : m_currentColor(
new QColor) {}
1994 virtual bool supportsNonModalDialog()
1997 virtual QColor currentColor()
const {
return *m_currentColor; }
1998 virtual void setCurrentColor(
const QColor &c) { *m_currentColor = c; }
2001 inline QWindowsNativeColorDialog *nativeFileDialog()
const
2002 {
return static_cast<QWindowsNativeColorDialog *>(nativeDialog()); }
2003 virtual QWindowsNativeDialogBase *createNativeDialog();
2005 SharedPointerColor m_currentColor;
2008QWindowsNativeDialogBase *QWindowsColorDialogHelper::createNativeDialog()
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;
2029#ifdef USE_NATIVE_COLOR_DIALOG
2053#ifdef USE_NATIVE_COLOR_DIALOG
2070#include "qwindowsdialoghelpers.moc"