1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qfiledialog.h"
43 
44 #ifndef QT_NO_FILEDIALOG
45 
46 #include <private/qfiledialog_p.h>
47 #include <qapplication.h>
48 #include <private/qapplication_p.h>
49 #include <qt_windows.h>
50 #include <qglobal.h>
51 #include <qregexp.h>
52 #include <qbuffer.h>
53 #include <qdir.h>
54 #include <qstringlist.h>
55 #include <private/qsystemlibrary_p.h>
56 #include "qfiledialog_win_p.h"
57 
58 #ifdef Q_WS_WINCE
59 #include <commdlg.h>
60 bool qt_priv_ptr_valid = false;
61 #else
62 //we have to declare them here because they're not present for all SDK/compilers
63 static const IID   QT_IID_IFileOpenDialog  = {0xd57c7288, 0xd4ad, 0x4768, {0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60} };
64 static const IID   QT_IID_IShellItem       = {0x43826d1e, 0xe718, 0x42ee, {0xbc, 0x55, 0xa1, 0xe2, 0x61, 0xc3, 0x7b, 0xfe} };
65 static const CLSID QT_CLSID_FileOpenDialog = {0xdc1c5a9c, 0xe88a, 0x4dde, {0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7} };
66 #endif
67 
68 
69 typedef qt_LPITEMIDLIST (WINAPI *PtrSHBrowseForFolder)(qt_BROWSEINFO*);
70 static PtrSHBrowseForFolder ptrSHBrowseForFolder = 0;
71 typedef BOOL (WINAPI *PtrSHGetPathFromIDList)(qt_LPITEMIDLIST, LPWSTR);
72 static PtrSHGetPathFromIDList ptrSHGetPathFromIDList = 0;
73 typedef HRESULT (WINAPI *PtrSHGetMalloc)(LPMALLOC *);
74 static PtrSHGetMalloc ptrSHGetMalloc = 0;
75 
76 
77 QT_BEGIN_NAMESPACE
78 
qt_win_resolve_libs()79 static void qt_win_resolve_libs()
80 {
81     static bool triedResolve = false;
82     if (!triedResolve) {
83 #if !defined(Q_WS_WINCE)
84         QSystemLibrary lib(QLatin1String("shell32"));
85         ptrSHBrowseForFolder = (PtrSHBrowseForFolder)lib.resolve("SHBrowseForFolderW");
86         ptrSHGetPathFromIDList = (PtrSHGetPathFromIDList)lib.resolve("SHGetPathFromIDListW");
87         ptrSHGetMalloc = (PtrSHGetMalloc)lib.resolve("SHGetMalloc");
88 #else
89         // CE stores them in a different lib and does not use unicode version
90         QSystemLibrary lib(QLatin1String("Ceshell"));
91         ptrSHBrowseForFolder = (PtrSHBrowseForFolder)lib.resolve("SHBrowseForFolder");
92         ptrSHGetPathFromIDList = (PtrSHGetPathFromIDList)lib.resolve("SHGetPathFromIDList");
93         ptrSHGetMalloc = (PtrSHGetMalloc)lib.resolve("SHGetMalloc");
94         if (ptrSHBrowseForFolder && ptrSHGetPathFromIDList && ptrSHGetMalloc)
95             qt_priv_ptr_valid = true;
96 #endif
97 
98         triedResolve = true;
99     }
100 }
101 
102 extern const char* qt_file_dialog_filter_reg_exp; // defined in qfiledialog.cpp
103 extern QStringList qt_make_filter_list(const QString &filter);
104 
105 const int maxNameLen = 1023;
106 const int maxMultiLen = 65535;
107 
108 // Returns the wildcard part of a filter.
qt_win_extract_filter(const QString & rawFilter)109 static QString qt_win_extract_filter(const QString &rawFilter)
110 {
111     QString result = rawFilter;
112     QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp));
113     int index = r.indexIn(result);
114     if (index >= 0)
115         result = r.cap(2);
116     QStringList list = result.split(QLatin1Char(' '));
117     for(QStringList::iterator it = list.begin(); it < list.end(); ++it) {
118         if (*it == QLatin1String("*")) {
119             *it = QLatin1String("*.*");
120             break;
121         }
122     }
123     return list.join(QLatin1String(";"));
124 }
125 
qt_win_make_filters_list(const QString & filter)126 static QStringList qt_win_make_filters_list(const QString &filter)
127 {
128     QString f(filter);
129 
130     if (f.isEmpty())
131         f = QFileDialog::tr("All Files (*.*)");
132 
133     return qt_make_filter_list(f);
134 }
135 
136 // Makes a NUL-oriented Windows filter from a Qt filter.
qt_win_filter(const QString & filter,bool hideFiltersDetails)137 static QString qt_win_filter(const QString &filter, bool hideFiltersDetails)
138 {
139     QStringList filterLst = qt_win_make_filters_list(filter);
140     QStringList::Iterator it = filterLst.begin();
141     QString winfilters;
142     QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp));
143     for (; it != filterLst.end(); ++it) {
144         QString subfilter = *it;
145         if (!subfilter.isEmpty()) {
146             if (hideFiltersDetails) {
147                 int index = r.indexIn(subfilter);
148                 if (index >= 0)
149                     winfilters += r.cap(1);
150             } else {
151                 winfilters += subfilter;
152             }
153             winfilters += QChar();
154             winfilters += qt_win_extract_filter(subfilter);
155             winfilters += QChar();
156         }
157     }
158     winfilters += QChar();
159     return winfilters;
160 }
161 
qt_win_selected_filter(const QString & filter,DWORD idx)162 static QString qt_win_selected_filter(const QString &filter, DWORD idx)
163 {
164     return qt_win_make_filters_list(filter).at((int)idx - 1);
165 }
166 
167 static QString tFilters, tTitle, tInitDir;
168 
qt_win_make_OFN(QWidget * parent,const QString & initialSelection,const QString & initialDirectory,const QString & title,const QString & filters,QFileDialog::FileMode mode,QFileDialog::Options options)169 static OPENFILENAME* qt_win_make_OFN(QWidget *parent,
170                                      const QString& initialSelection,
171                                      const QString& initialDirectory,
172                                      const QString& title,
173                                      const QString& filters,
174                                      QFileDialog::FileMode mode,
175                                      QFileDialog::Options options)
176 {
177     if (parent)
178         parent = parent->window();
179     else
180         parent = QApplication::activeWindow();
181 
182     tInitDir = QDir::toNativeSeparators(initialDirectory);
183     tFilters = filters;
184     tTitle = title;
185     QString initSel = QDir::toNativeSeparators(initialSelection);
186     if (!initSel.isEmpty()) {
187         initSel.remove(QLatin1Char('<'));
188         initSel.remove(QLatin1Char('>'));
189         initSel.remove(QLatin1Char('\"'));
190         initSel.remove(QLatin1Char('|'));
191     }
192 
193     int maxLen = mode == QFileDialog::ExistingFiles ? maxMultiLen : maxNameLen;
194     wchar_t *tInitSel = new wchar_t[maxLen + 1];
195     if (initSel.length() > 0 && initSel.length() <= maxLen)
196         memcpy(tInitSel, initSel.utf16(), (initSel.length()+1)*sizeof(QChar));
197     else
198         tInitSel[0] = 0;
199 
200     OPENFILENAME* ofn = new OPENFILENAME;
201     memset(ofn, 0, sizeof(OPENFILENAME));
202 
203     ofn->lStructSize = sizeof(OPENFILENAME);
204     ofn->hwndOwner = parent ? parent->winId() : 0;
205     ofn->lpstrFilter = (wchar_t*)tFilters.utf16();
206     ofn->lpstrFile = tInitSel;
207     ofn->nMaxFile = maxLen;
208     ofn->lpstrInitialDir = (wchar_t*)tInitDir.utf16();
209     ofn->lpstrTitle = (wchar_t*)tTitle.utf16();
210     ofn->Flags = (OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_PATHMUSTEXIST);
211     if (mode == QFileDialog::ExistingFile ||
212          mode == QFileDialog::ExistingFiles)
213         ofn->Flags |= (OFN_FILEMUSTEXIST);
214     if (mode == QFileDialog::ExistingFiles)
215         ofn->Flags |= (OFN_ALLOWMULTISELECT);
216     if (!(options & QFileDialog::DontConfirmOverwrite))
217         ofn->Flags |= OFN_OVERWRITEPROMPT;
218 
219     return ofn;
220 }
221 
qt_win_clean_up_OFN(OPENFILENAME ** ofn)222 static void qt_win_clean_up_OFN(OPENFILENAME **ofn)
223 {
224     delete [] (*ofn)->lpstrFile;
225     delete *ofn;
226     *ofn = 0;
227 }
228 
229 extern void qt_win_eatMouseMove();
230 
qt_win_get_open_file_name(const QFileDialogArgs & args,QString * initialDirectory,QString * selectedFilter)231 QString qt_win_get_open_file_name(const QFileDialogArgs &args,
232                                   QString *initialDirectory,
233                                   QString *selectedFilter)
234 {
235     QString result;
236 
237     QString isel = args.selection;
238 
239     if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:"))
240         initialDirectory->remove(0, 5);
241     QFileInfo fi(*initialDirectory);
242 
243     if (initialDirectory && !fi.isDir()) {
244         *initialDirectory = fi.absolutePath();
245         if (isel.isEmpty())
246             isel = fi.fileName();
247     }
248 
249     if (!fi.exists())
250         *initialDirectory = QDir::homePath();
251 
252     DWORD selFilIdx = 0;
253 
254     int idx = 0;
255     if (selectedFilter) {
256         QStringList filterLst = qt_win_make_filters_list(args.filter);
257         idx = filterLst.indexOf(*selectedFilter);
258     }
259 
260     QDialog modal_widget;
261     modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true);
262     modal_widget.setParent(args.parent, Qt::Window);
263     QApplicationPrivate::enterModal(&modal_widget);
264 
265     bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails;
266     OPENFILENAME* ofn = qt_win_make_OFN(args.parent, args.selection,
267                                         args.directory, args.caption,
268                                         qt_win_filter(args.filter, hideFiltersDetails),
269                                         QFileDialog::ExistingFile,
270                                         args.options);
271     if (idx)
272         ofn->nFilterIndex = idx + 1;
273     if (GetOpenFileName(ofn)) {
274         result = QString::fromWCharArray(ofn->lpstrFile);
275         selFilIdx = ofn->nFilterIndex;
276     }
277     qt_win_clean_up_OFN(&ofn);
278 
279     QApplicationPrivate::leaveModal(&modal_widget);
280 
281     qt_win_eatMouseMove();
282 
283     if (result.isEmpty())
284         return result;
285 
286     fi = result;
287     *initialDirectory = fi.path();
288     if (selectedFilter)
289         *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx);
290     return fi.absoluteFilePath();
291 }
292 
qt_win_get_save_file_name(const QFileDialogArgs & args,QString * initialDirectory,QString * selectedFilter)293 QString qt_win_get_save_file_name(const QFileDialogArgs &args,
294                                   QString *initialDirectory,
295                                   QString *selectedFilter)
296 {
297     QString result;
298 
299     QString isel = args.selection;
300     if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:"))
301         initialDirectory->remove(0, 5);
302     QFileInfo fi(*initialDirectory);
303 
304     if (initialDirectory && !fi.isDir()) {
305         *initialDirectory = fi.absolutePath();
306         if (isel.isEmpty())
307             isel = fi.fileName();
308     }
309 
310     if (!fi.exists())
311         *initialDirectory = QDir::homePath();
312 
313     DWORD selFilIdx = 0;
314 
315     int idx = 0;
316     if (selectedFilter) {
317         QStringList filterLst = qt_win_make_filters_list(args.filter);
318         idx = filterLst.indexOf(*selectedFilter);
319     }
320 
321     QDialog modal_widget;
322     modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true);
323     modal_widget.setParent(args.parent, Qt::Window);
324     QApplicationPrivate::enterModal(&modal_widget);
325     bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails;
326     // This block is used below for the lpstrDefExt member.
327     // Note that the current MSDN docs document this member wrong.
328     // It should rather be documented as "the default extension if no extension was given and if the
329     // current filter does not have a extension (e.g (*)). If the current filter have an extension, use
330     // the extension of the current filter"
331     QString defaultSaveExt;
332     if (selectedFilter && !selectedFilter->isEmpty()) {
333         defaultSaveExt = qt_win_extract_filter(*selectedFilter);
334         // make sure we only have the extension
335         int firstDot = defaultSaveExt.indexOf(QLatin1Char('.'));
336         if (firstDot != -1) {
337             defaultSaveExt.remove(0, firstDot + 1);
338         } else {
339             defaultSaveExt.clear();
340         }
341     }
342 
343     OPENFILENAME *ofn = qt_win_make_OFN(args.parent, args.selection,
344                                         args.directory, args.caption,
345                                         qt_win_filter(args.filter, hideFiltersDetails),
346                                         QFileDialog::AnyFile,
347                                         args.options);
348 
349     ofn->lpstrDefExt = (wchar_t*)defaultSaveExt.utf16();
350 
351     if (idx)
352         ofn->nFilterIndex = idx + 1;
353     if (GetSaveFileName(ofn)) {
354         result = QString::fromWCharArray(ofn->lpstrFile);
355         selFilIdx = ofn->nFilterIndex;
356     }
357     qt_win_clean_up_OFN(&ofn);
358 
359 #if defined(Q_WS_WINCE)
360     int semIndex = result.indexOf(QLatin1Char(';'));
361     if (semIndex >= 0)
362         result = result.left(semIndex);
363 #endif
364 
365     QApplicationPrivate::leaveModal(&modal_widget);
366 
367     qt_win_eatMouseMove();
368 
369     if (result.isEmpty())
370         return result;
371 
372     fi = result;
373     *initialDirectory = fi.path();
374     if (selectedFilter)
375         *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx);
376     return fi.absoluteFilePath();
377 }
378 
379 
380 #ifndef Q_WS_WINCE
381 
382 typedef HRESULT (WINAPI *PtrSHCreateItemFromParsingName)(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv);
383 static PtrSHCreateItemFromParsingName pSHCreateItemFromParsingName = 0;
384 
qt_win_set_IFileDialogOptions(IFileDialog * pfd,const QString & initialSelection,const QString & initialDirectory,const QString & title,const QStringList & filterLst,QFileDialog::FileMode mode,QFileDialog::Options options)385 static bool qt_win_set_IFileDialogOptions(IFileDialog *pfd,
386                                           const QString& initialSelection,
387                                           const QString& initialDirectory,
388                                           const QString& title,
389                                           const QStringList& filterLst,
390                                           QFileDialog::FileMode mode,
391                                           QFileDialog::Options options)
392 {
393     if (!pSHCreateItemFromParsingName) {
394         // This function is available only in Vista & above.
395         QSystemLibrary shellLib(QLatin1String("Shell32"));
396         pSHCreateItemFromParsingName = (PtrSHCreateItemFromParsingName)
397             shellLib.resolve("SHCreateItemFromParsingName");
398         if (!pSHCreateItemFromParsingName)
399             return false;
400     }
401     HRESULT hr;
402     QString winfilters;
403     int numFilters = 0;
404     quint32 currentOffset = 0;
405     QList<quint32> offsets;
406     QStringList::ConstIterator it = filterLst.begin();
407     // Create the native filter string and save offset to each entry.
408     for (; it != filterLst.end(); ++it) {
409         QString subfilter = *it;
410         if (!subfilter.isEmpty()) {
411             offsets<<currentOffset;
412             //Here the COMMON_ITEM_DIALOG API always add the details for the filter (e.g. *.txt)
413             //so we don't need to handle the flag HideNameFilterDetails.
414             winfilters += subfilter; // The name of the filter.
415             winfilters += QChar();
416             currentOffset += subfilter.size()+1;
417             offsets<<currentOffset;
418             QString spec = qt_win_extract_filter(subfilter);
419             winfilters += spec; // The actual filter spec.
420             winfilters += QChar();
421             currentOffset += spec.size()+1;
422             numFilters++;
423         }
424     }
425     // Add the filters to the file dialog.
426     if (numFilters) {
427         wchar_t *szData = (wchar_t*)winfilters.utf16();
428         qt_COMDLG_FILTERSPEC *filterSpec = new qt_COMDLG_FILTERSPEC[numFilters];
429         for(int i = 0; i<numFilters; i++) {
430             filterSpec[i].pszName = szData+offsets[i*2];
431             filterSpec[i].pszSpec = szData+offsets[(i*2)+1];
432         }
433         hr = pfd->SetFileTypes(numFilters, filterSpec);
434         delete []filterSpec;
435     }
436     // Set the starting folder.
437     tInitDir = QDir::toNativeSeparators(initialDirectory);
438     if (!tInitDir.isEmpty()) {
439         IShellItem *psiDefaultFolder;
440         hr = pSHCreateItemFromParsingName((wchar_t*)tInitDir.utf16(), NULL, QT_IID_IShellItem,
441             reinterpret_cast<void**>(&psiDefaultFolder));
442 
443         if (SUCCEEDED(hr)) {
444             hr = pfd->SetFolder(psiDefaultFolder);
445             psiDefaultFolder->Release();
446         }
447     }
448     // Set the currently selected file.
449     QString initSel = QDir::toNativeSeparators(initialSelection);
450     if (!initSel.isEmpty()) {
451         initSel.remove(QLatin1Char('<'));
452         initSel.remove(QLatin1Char('>'));
453         initSel.remove(QLatin1Char('\"'));
454         initSel.remove(QLatin1Char('|'));
455     }
456     if (!initSel.isEmpty()) {
457         hr = pfd->SetFileName((wchar_t*)initSel.utf16());
458     }
459     // Set the title for the file dialog.
460     if (!title.isEmpty()) {
461         hr = pfd->SetTitle((wchar_t*)title.utf16());
462     }
463     // Set other flags for the dialog.
464     DWORD newOptions;
465     hr = pfd->GetOptions(&newOptions);
466     if (SUCCEEDED(hr)) {
467         newOptions |= FOS_NOCHANGEDIR;
468         if (mode == QFileDialog::ExistingFile ||
469              mode == QFileDialog::ExistingFiles)
470             newOptions |= (FOS_FILEMUSTEXIST | FOS_PATHMUSTEXIST);
471         if (mode == QFileDialog::ExistingFiles)
472             newOptions |= FOS_ALLOWMULTISELECT;
473         if (!(options & QFileDialog::DontConfirmOverwrite))
474             newOptions |= FOS_OVERWRITEPROMPT;
475         hr = pfd->SetOptions(newOptions);
476     }
477     return SUCCEEDED(hr);
478 }
479 
qt_win_CID_get_open_file_names(const QFileDialogArgs & args,QString * initialDirectory,const QStringList & filterList,QString * selectedFilter,int selectedFilterIndex)480 static QStringList qt_win_CID_get_open_file_names(const QFileDialogArgs &args,
481                                        QString *initialDirectory,
482                                        const QStringList &filterList,
483                                        QString *selectedFilter,
484                                        int selectedFilterIndex)
485 {
486     QStringList result;
487     QDialog modal_widget;
488     modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true);
489     modal_widget.setParent(args.parent, Qt::Window);
490     QApplicationPrivate::enterModal(&modal_widget);
491     // Multiple selection is allowed only in IFileOpenDialog.
492     IFileOpenDialog *pfd = 0;
493     HRESULT hr = CoCreateInstance(QT_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, QT_IID_IFileOpenDialog,
494         reinterpret_cast<void**>(&pfd));
495 
496     if (SUCCEEDED(hr)) {
497         qt_win_set_IFileDialogOptions(pfd, args.selection,
498                                       args.directory, args.caption,
499                                       filterList, QFileDialog::ExistingFiles,
500                                       args.options);
501         // Set the currently selected filter (one-based index).
502         hr = pfd->SetFileTypeIndex(selectedFilterIndex+1);
503         QWidget *parentWindow = args.parent;
504         if (parentWindow)
505             parentWindow = parentWindow->window();
506         else
507             parentWindow = QApplication::activeWindow();
508         // Show the file dialog.
509         hr = pfd->Show(parentWindow ? parentWindow->winId() : 0);
510         if (SUCCEEDED(hr)) {
511             // Retrieve the results.
512             IShellItemArray *psiaResults;
513             hr = pfd->GetResults(&psiaResults);
514             if (SUCCEEDED(hr)) {
515                 DWORD numItems = 0;
516                 psiaResults->GetCount(&numItems);
517                 for (DWORD i = 0; i<numItems; i++) {
518                     IShellItem *psi = 0;
519                     hr = psiaResults->GetItemAt(i, &psi);
520                     if (SUCCEEDED(hr)) {
521                         // Retrieve the file name from shell item.
522                         wchar_t *pszPath;
523                         hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
524                         if (SUCCEEDED(hr)) {
525                             QString fileName = QString::fromWCharArray(pszPath);
526                             result.append(fileName);
527                             CoTaskMemFree(pszPath);
528                         }
529                         psi->Release(); // Free the current item.
530                     }
531                 }
532                 psiaResults->Release(); // Free the array of items.
533             }
534         }
535     }
536     QApplicationPrivate::leaveModal(&modal_widget);
537 
538     qt_win_eatMouseMove();
539 
540     if (!result.isEmpty()) {
541         // Retrieve the current folder name.
542         IShellItem *psi = 0;
543         hr = pfd->GetFolder(&psi);
544         if (SUCCEEDED(hr)) {
545             wchar_t *pszPath;
546             hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
547             if (SUCCEEDED(hr)) {
548                 *initialDirectory = QString::fromWCharArray(pszPath);
549                 CoTaskMemFree(pszPath);
550             }
551             psi->Release();
552         }
553         // Retrieve the currently selected filter.
554         if (selectedFilter) {
555             quint32 filetype = 0;
556             hr = pfd->GetFileTypeIndex(&filetype);
557             if (SUCCEEDED(hr) && filetype && filetype <= (quint32)filterList.length()) {
558                 // This is a one-based index, not zero-based.
559                 *selectedFilter = filterList[filetype-1];
560             }
561         }
562     }
563     if (pfd)
564         pfd->Release();
565     return result;
566 }
567 
qt_win_CID_get_existing_directory(const QFileDialogArgs & args)568 QString qt_win_CID_get_existing_directory(const QFileDialogArgs &args)
569 {
570     QString result;
571     QDialog modal_widget;
572     modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true);
573     modal_widget.setParent(args.parent, Qt::Window);
574     QApplicationPrivate::enterModal(&modal_widget);
575 
576     IFileOpenDialog *pfd = 0;
577     HRESULT hr = CoCreateInstance(QT_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
578                                   QT_IID_IFileOpenDialog, reinterpret_cast<void**>(&pfd));
579 
580     if (SUCCEEDED(hr)) {
581         qt_win_set_IFileDialogOptions(pfd, args.selection,
582                                       args.directory, args.caption,
583                                       QStringList(), QFileDialog::ExistingFile,
584                                       args.options);
585 
586         // Set the FOS_PICKFOLDERS flag
587         DWORD newOptions;
588         hr = pfd->GetOptions(&newOptions);
589         newOptions |= (FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM);
590         if (SUCCEEDED(hr) && SUCCEEDED((hr = pfd->SetOptions(newOptions)))) {
591             QWidget *parentWindow = args.parent;
592             if (parentWindow)
593                 parentWindow = parentWindow->window();
594             else
595                 parentWindow = QApplication::activeWindow();
596 
597             // Show the file dialog.
598             hr = pfd->Show(parentWindow ? parentWindow->winId() : 0);
599             if (SUCCEEDED(hr)) {
600                 // Retrieve the result
601                 IShellItem *psi = 0;
602                 hr = pfd->GetResult(&psi);
603                 if (SUCCEEDED(hr)) {
604                     // Retrieve the file name from shell item.
605                     wchar_t *pszPath;
606                     hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
607                     if (SUCCEEDED(hr)) {
608                         result = QString::fromWCharArray(pszPath);
609                         CoTaskMemFree(pszPath);
610                     }
611                     psi->Release(); // Free the current item.
612                 }
613             }
614         }
615     }
616     QApplicationPrivate::leaveModal(&modal_widget);
617 
618     qt_win_eatMouseMove();
619 
620     if (pfd)
621         pfd->Release();
622     return result;
623 }
624 
625 #endif
626 
qt_win_get_open_file_names(const QFileDialogArgs & args,QString * initialDirectory,QString * selectedFilter)627 QStringList qt_win_get_open_file_names(const QFileDialogArgs &args,
628                                        QString *initialDirectory,
629                                        QString *selectedFilter)
630 {
631     QFileInfo fi;
632     QDir dir;
633 
634     if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:"))
635         initialDirectory->remove(0, 5);
636     fi = QFileInfo(*initialDirectory);
637 
638     if (initialDirectory && !fi.isDir()) {
639         *initialDirectory = fi.absolutePath();
640     }
641 
642     if (!fi.exists())
643         *initialDirectory = QDir::homePath();
644 
645     DWORD selFilIdx = 0;
646 
647     QStringList filterLst = qt_win_make_filters_list(args.filter);
648     int idx = 0;
649     if (selectedFilter) {
650         idx = filterLst.indexOf(*selectedFilter);
651     }
652     // Windows Vista (& above) allows users to search from file dialogs. If user selects
653     // multiple files belonging to different folders from these search results, the
654     // GetOpenFileName() will return only one folder name for all the files. To retrieve
655     // the correct path for all selected files, we have to use Common Item Dialog interfaces.
656 #ifndef Q_WS_WINCE
657     if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based))
658         return qt_win_CID_get_open_file_names(args, initialDirectory, filterLst, selectedFilter, idx);
659 #endif
660 
661     QStringList result;
662     QDialog modal_widget;
663     modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true);
664     modal_widget.setParent(args.parent, Qt::Window);
665     QApplicationPrivate::enterModal(&modal_widget);
666 
667     bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails;
668     OPENFILENAME* ofn = qt_win_make_OFN(args.parent, args.selection,
669                                         args.directory, args.caption,
670                                         qt_win_filter(args.filter, hideFiltersDetails),
671                                         QFileDialog::ExistingFiles,
672                                         args.options);
673     if (idx)
674         ofn->nFilterIndex = idx + 1;
675     if (GetOpenFileName(ofn)) {
676         QString fileOrDir = QString::fromWCharArray(ofn->lpstrFile);
677         selFilIdx = ofn->nFilterIndex;
678         int offset = fileOrDir.length() + 1;
679         if (ofn->lpstrFile[offset] == 0) {
680             // Only one file selected; has full path
681             fi.setFile(fileOrDir);
682             QString res = fi.absoluteFilePath();
683             if (!res.isEmpty())
684                 result.append(res);
685         }
686         else {
687             // Several files selected; first string is path
688             dir.setPath(fileOrDir);
689             QString f;
690             while(!(f = QString::fromWCharArray(ofn->lpstrFile + offset)).isEmpty()) {
691                 fi.setFile(dir, f);
692                 QString res = fi.absoluteFilePath();
693                 if (!res.isEmpty())
694                     result.append(res);
695                 offset += f.length() + 1;
696             }
697         }
698     }
699     qt_win_clean_up_OFN(&ofn);
700 
701     QApplicationPrivate::leaveModal(&modal_widget);
702 
703     qt_win_eatMouseMove();
704 
705     if (!result.isEmpty()) {
706         *initialDirectory = fi.path();    // only save the path if there is a result
707         if (selectedFilter)
708             *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx);
709     }
710     return result;
711 }
712 
713 // MFC Directory Dialog. Contrib: Steve Williams (minor parts from Scott Powers)
714 
winGetExistDirCallbackProc(HWND hwnd,UINT uMsg,LPARAM lParam,LPARAM lpData)715 static int __stdcall winGetExistDirCallbackProc(HWND hwnd,
716                                                 UINT uMsg,
717                                                 LPARAM lParam,
718                                                 LPARAM lpData)
719 {
720     if (uMsg == BFFM_INITIALIZED && lpData != 0) {
721         QString *initDir = (QString *)(lpData);
722         if (!initDir->isEmpty()) {
723             SendMessage(hwnd, BFFM_SETSELECTION, TRUE, LPARAM(initDir->utf16()));
724         }
725     } else if (uMsg == BFFM_SELCHANGED) {
726         qt_win_resolve_libs();
727         if (ptrSHGetPathFromIDList) {
728             wchar_t path[MAX_PATH];
729             ptrSHGetPathFromIDList(qt_LPITEMIDLIST(lParam), path);
730             QString tmpStr = QString::fromWCharArray(path);
731             if (!tmpStr.isEmpty())
732                 SendMessage(hwnd, BFFM_ENABLEOK, 1, 1);
733             else
734                 SendMessage(hwnd, BFFM_ENABLEOK, 0, 0);
735             SendMessage(hwnd, BFFM_SETSTATUSTEXT, 1, LPARAM(path));
736         }
737     }
738     return 0;
739 }
740 
qt_win_get_existing_directory(const QFileDialogArgs & args)741 QString qt_win_get_existing_directory(const QFileDialogArgs &args)
742 {
743 #ifndef Q_WS_WINCE
744     if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based))
745         return qt_win_CID_get_existing_directory(args);
746 #endif
747 
748     QString currentDir = QDir::currentPath();
749     QString result;
750     QWidget *parent = args.parent;
751     if (parent)
752         parent = parent->window();
753     else
754         parent = QApplication::activeWindow();
755     if (parent)
756         parent->createWinId();
757 
758     QDialog modal_widget;
759     modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true);
760     modal_widget.setParent(parent, Qt::Window);
761     QApplicationPrivate::enterModal(&modal_widget);
762 
763     QString initDir = QDir::toNativeSeparators(args.directory);
764     wchar_t path[MAX_PATH];
765     wchar_t initPath[MAX_PATH];
766     initPath[0] = 0;
767     path[0] = 0;
768     tTitle = args.caption;
769 
770     qt_BROWSEINFO bi;
771 
772     Q_ASSERT(!parent ||parent->testAttribute(Qt::WA_WState_Created));
773     bi.hwndOwner = (parent ? parent->winId() : 0);
774     bi.pidlRoot = NULL;
775     //### This does not seem to be respected? - the dialog always displays "Browse for folder"
776     bi.lpszTitle = (wchar_t*)tTitle.utf16();
777     bi.pszDisplayName = initPath;
778     bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE;
779     bi.lpfn = winGetExistDirCallbackProc;
780     bi.lParam = LPARAM(&initDir);
781 
782     qt_win_resolve_libs();
783     if (ptrSHBrowseForFolder) {
784         qt_LPITEMIDLIST pItemIDList = ptrSHBrowseForFolder(&bi);
785         if (pItemIDList) {
786             ptrSHGetPathFromIDList(pItemIDList, path);
787             IMalloc *pMalloc;
788             if (ptrSHGetMalloc(&pMalloc) == NOERROR) {
789                 pMalloc->Free(pItemIDList);
790                 pMalloc->Release();
791                 result = QString::fromWCharArray(path);
792             }
793         }
794     }
795     tTitle = QString();
796 
797     QApplicationPrivate::leaveModal(&modal_widget);
798 
799     qt_win_eatMouseMove();
800 
801     if (!result.isEmpty())
802         result.replace(QLatin1Char('\\'), QLatin1Char('/'));
803     return result;
804 }
805 
806 
807 QT_END_NAMESPACE
808 
809 #endif
810