Commit pickers-fixes.patch added to 1472329 (Francesco Montorsi)
[wxWidgets.git] / src / msw / filedlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/filedlg.cpp
3 // Purpose: wxFileDialog
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 01/02/97
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #if wxUSE_FILEDLG && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
28
29 #ifndef WX_PRECOMP
30 #include "wx/utils.h"
31 #include "wx/msgdlg.h"
32 #include "wx/filedlg.h"
33 #include "wx/filefn.h"
34 #include "wx/intl.h"
35 #include "wx/log.h"
36 #include "wx/app.h"
37 #endif
38
39 #include "wx/msw/wrapcdlg.h"
40
41 #include <stdlib.h>
42 #include <string.h>
43
44 #include "wx/filename.h"
45 #include "wx/tokenzr.h"
46 #include "wx/math.h"
47
48 #include "wx/msw/missing.h"
49
50 // ----------------------------------------------------------------------------
51 // constants
52 // ----------------------------------------------------------------------------
53
54 #ifdef __WIN32__
55 # define wxMAXPATH 65534
56 #else
57 # define wxMAXPATH 1024
58 #endif
59
60 # define wxMAXFILE 1024
61
62 # define wxMAXEXT 5
63
64 // ----------------------------------------------------------------------------
65 // globals
66 // ----------------------------------------------------------------------------
67
68 // standard dialog size
69 static wxRect gs_rectDialog(0, 0, 428, 266);
70
71 // ============================================================================
72 // implementation
73 // ============================================================================
74
75 IMPLEMENT_CLASS(wxFileDialog, wxFileDialogBase)
76
77 // ----------------------------------------------------------------------------
78 // hook function for moving the dialog
79 // ----------------------------------------------------------------------------
80
81 UINT_PTR APIENTRY
82 wxFileDialogHookFunction(HWND hDlg,
83 UINT iMsg,
84 WPARAM WXUNUSED(wParam),
85 LPARAM lParam)
86 {
87 switch ( iMsg )
88 {
89 case WM_NOTIFY:
90 {
91 OFNOTIFY *pNotifyCode = wx_reinterpret_cast(OFNOTIFY *, lParam);
92 if ( pNotifyCode->hdr.code == CDN_INITDONE )
93 {
94 // note that we need to move the parent window: hDlg is a
95 // child of it when OFN_EXPLORER is used
96 ::SetWindowPos
97 (
98 ::GetParent(hDlg),
99 HWND_TOP,
100 gs_rectDialog.x, gs_rectDialog.y,
101 0, 0,
102 SWP_NOZORDER | SWP_NOSIZE
103 );
104 }
105 }
106 break;
107
108 case WM_DESTROY:
109 // reuse the position used for the dialog the next time by default
110 //
111 // NB: at least under Windows 2003 this is useless as after the
112 // first time it's shown the dialog always remembers its size
113 // and position itself and ignores any later SetWindowPos calls
114 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg)), gs_rectDialog);
115 break;
116 }
117
118 // do the default processing
119 return 0;
120 }
121
122 // ----------------------------------------------------------------------------
123 // wxFileDialog
124 // ----------------------------------------------------------------------------
125
126 wxFileDialog::wxFileDialog(wxWindow *parent,
127 const wxString& message,
128 const wxString& defaultDir,
129 const wxString& defaultFileName,
130 const wxString& wildCard,
131 long style,
132 const wxPoint& pos,
133 const wxSize& sz,
134 const wxString& name)
135 : wxFileDialogBase(parent, message, defaultDir, defaultFileName,
136 wildCard, style, pos, sz, name)
137
138 {
139 // NB: all style checks are done by wxFileDialogBase::Create
140
141 m_bMovedWindow = false;
142
143 // Must set to zero, otherwise the wx routines won't size the window
144 // the second time you call the file dialog, because it thinks it is
145 // already at the requested size.. (when centering)
146 gs_rectDialog.x =
147 gs_rectDialog.y = 0;
148
149 }
150 void wxFileDialog::GetPaths(wxArrayString& paths) const
151 {
152 paths.Empty();
153
154 wxString dir(m_dir);
155 if ( m_dir.Last() != _T('\\') )
156 dir += _T('\\');
157
158 size_t count = m_fileNames.GetCount();
159 for ( size_t n = 0; n < count; n++ )
160 {
161 if (wxFileName(m_fileNames[n]).IsAbsolute())
162 paths.Add(m_fileNames[n]);
163 else
164 paths.Add(dir + m_fileNames[n]);
165 }
166 }
167
168 void wxFileDialog::GetFilenames(wxArrayString& files) const
169 {
170 files = m_fileNames;
171 }
172
173 void wxFileDialog::SetPath(const wxString& path)
174 {
175 wxString ext;
176 wxSplitPath(path, &m_dir, &m_fileName, &ext);
177 if ( !ext.empty() )
178 m_fileName << _T('.') << ext;
179 }
180
181 void wxFileDialog::DoGetPosition(int *x, int *y) const
182 {
183 if ( x )
184 *x = gs_rectDialog.x;
185 if ( y )
186 *y = gs_rectDialog.y;
187 }
188
189
190 void wxFileDialog::DoGetSize(int *width, int *height) const
191 {
192 if ( width )
193 *width = gs_rectDialog.width;
194 if ( height )
195 *height = gs_rectDialog.height;
196 }
197
198 void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(w), int WXUNUSED(h))
199 {
200 m_bMovedWindow = true;
201
202 gs_rectDialog.x = x;
203 gs_rectDialog.y = y;
204
205 // size of the dialog can't be changed because the controls are not laid
206 // out correctly then
207 }
208
209 // helper used below in ShowModal(): style is used to determine whether to show
210 // the "Save file" dialog (if it contains wxFD_SAVE bit) or "Open file" one;
211 // returns true on success or false on failure in which case err is filled with
212 // the CDERR_XXX constant
213 static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err)
214 {
215 if ( style & wxFD_SAVE ? GetSaveFileName(of) : GetOpenFileName(of) )
216 return true;
217
218 if ( err )
219 {
220 #ifdef __WXWINCE__
221 // according to MSDN, CommDlgExtendedError() should work under CE as
222 // well but apparently in practice it doesn't (anybody has more
223 // details?)
224 *err = GetLastError();
225 #else
226 *err = CommDlgExtendedError();
227 #endif
228 }
229
230 return false;
231 }
232
233 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
234 // know if the OPENFILENAME declared in the currently used headers is a V5 or
235 // V4 (smaller) one so we try to manually extend the struct in case it is the
236 // old one.
237 //
238 // We don't do this on Windows CE nor under Win64, however, as there are no
239 // compilers with old headers for these architectures
240 #if defined(__WXWINCE__) || defined(__WIN64__)
241 typedef OPENFILENAME wxOPENFILENAME;
242
243 static const DWORD gs_ofStructSize = sizeof(OPENFILENAME);
244 #else // !__WXWINCE__ || __WIN64__
245 #define wxTRY_SMALLER_OPENFILENAME
246
247 struct wxOPENFILENAME : public OPENFILENAME
248 {
249 // fields added in Windows 2000/XP comdlg32.dll version
250 void *pVoid;
251 DWORD dw1;
252 DWORD dw2;
253 };
254
255 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
256 // because sizeof(OPENFILENAME) in the headers we use when compiling the
257 // library could be less if _WIN32_WINNT is not >= 0x500
258 static const DWORD wxOPENFILENAME_V5_SIZE = 88;
259
260 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
261 static const DWORD wxOPENFILENAME_V4_SIZE = 76;
262
263 // always try the new one first
264 static DWORD gs_ofStructSize = wxOPENFILENAME_V5_SIZE;
265 #endif // __WXWINCE__ || __WIN64__/!...
266
267 int wxFileDialog::ShowModal()
268 {
269 HWND hWnd = 0;
270 if (m_parent) hWnd = (HWND) m_parent->GetHWND();
271 if (!hWnd && wxTheApp->GetTopWindow())
272 hWnd = (HWND) wxTheApp->GetTopWindow()->GetHWND();
273
274 static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name
275 wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path
276
277 *fileNameBuffer = wxT('\0');
278 *titleBuffer = wxT('\0');
279
280 #if WXWIN_COMPATIBILITY_2_4
281 long msw_flags = 0;
282 if ( (m_windowStyle & wxHIDE_READONLY) || (m_windowStyle & wxFD_SAVE) )
283 msw_flags |= OFN_HIDEREADONLY;
284 #else
285 long msw_flags = OFN_HIDEREADONLY;
286 #endif
287
288 if ( m_windowStyle & wxFD_FILE_MUST_EXIST )
289 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
290 /*
291 If the window has been moved the programmer is probably
292 trying to center or position it. Thus we set the callback
293 or hook function so that we can actually adjust the position.
294 Without moving or centering the dlg, it will just stay
295 in the upper left of the frame, it does not center
296 automatically.
297 */
298 if (m_bMovedWindow) // we need these flags.
299 {
300 msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK;
301 #ifndef __WXWINCE__
302 msw_flags |= OFN_ENABLESIZING;
303 #endif
304 }
305
306 if (m_windowStyle & wxFD_MULTIPLE )
307 {
308 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
309 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
310 }
311
312 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
313 // standard dialog does by default (notice that under NT it does it anyhow,
314 // OFN_NOCHANGEDIR or not, see below)
315 if ( !(m_windowStyle & wxFD_CHANGE_DIR) )
316 {
317 msw_flags |= OFN_NOCHANGEDIR;
318 }
319
320 if ( m_windowStyle & wxFD_OVERWRITE_PROMPT )
321 {
322 msw_flags |= OFN_OVERWRITEPROMPT;
323 }
324
325 wxOPENFILENAME of;
326 wxZeroMemory(of);
327
328 of.lStructSize = gs_ofStructSize;
329 of.hwndOwner = hWnd;
330 of.lpstrTitle = WXSTRINGCAST m_message;
331 of.lpstrFileTitle = titleBuffer;
332 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT;
333
334 // Convert forward slashes to backslashes (file selector doesn't like
335 // forward slashes) and also squeeze multiple consecutive slashes into one
336 // as it doesn't like two backslashes in a row neither
337
338 wxString dir;
339 size_t i, len = m_dir.length();
340 dir.reserve(len);
341 for ( i = 0; i < len; i++ )
342 {
343 wxChar ch = m_dir[i];
344 switch ( ch )
345 {
346 case _T('/'):
347 // convert to backslash
348 ch = _T('\\');
349
350 // fall through
351
352 case _T('\\'):
353 while ( i < len - 1 )
354 {
355 wxChar chNext = m_dir[i + 1];
356 if ( chNext != _T('\\') && chNext != _T('/') )
357 break;
358
359 // ignore the next one, unless it is at the start of a UNC path
360 if (i > 0)
361 i++;
362 else
363 break;
364 }
365 // fall through
366
367 default:
368 // normal char
369 dir += ch;
370 }
371 }
372
373 of.lpstrInitialDir = dir.c_str();
374
375 of.Flags = msw_flags;
376 of.lpfnHook = wxFileDialogHookFunction;
377
378 wxArrayString wildDescriptions, wildFilters;
379
380 size_t items = wxParseCommonDialogsFilter(m_wildCard, wildDescriptions, wildFilters);
381
382 wxASSERT_MSG( items > 0 , _T("empty wildcard list") );
383
384 wxString filterBuffer;
385
386 for (i = 0; i < items ; i++)
387 {
388 filterBuffer += wildDescriptions[i];
389 filterBuffer += wxT("|");
390 filterBuffer += wildFilters[i];
391 filterBuffer += wxT("|");
392 }
393
394 // Replace | with \0
395 for (i = 0; i < filterBuffer.length(); i++ ) {
396 if ( filterBuffer.GetChar(i) == wxT('|') ) {
397 filterBuffer[i] = wxT('\0');
398 }
399 }
400
401 of.lpstrFilter = (LPTSTR)filterBuffer.c_str();
402 of.nFilterIndex = m_filterIndex + 1;
403
404 //=== Setting defaultFileName >>=========================================
405
406 wxStrncpy( fileNameBuffer, (const wxChar *)m_fileName, wxMAXPATH-1 );
407 fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
408
409 of.lpstrFile = fileNameBuffer; // holds returned filename
410 of.nMaxFile = wxMAXPATH;
411
412 // we must set the default extension because otherwise Windows would check
413 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
414 // user types "foo" and the default extension is ".bar" we should force it
415 // to check for "foo.bar" existence and not "foo")
416 wxString defextBuffer; // we need it to be alive until GetSaveFileName()!
417 if (m_windowStyle & wxFD_SAVE)
418 {
419 const wxChar* extension = filterBuffer;
420 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
421
422 for( int i = 0; i < maxFilter; i++ ) // get extension
423 extension = extension + wxStrlen( extension ) + 1;
424
425 // use dummy name a to avoid assert in AppendExtension
426 defextBuffer = AppendExtension(wxT("a"), extension);
427 if (defextBuffer.StartsWith(wxT("a.")))
428 {
429 defextBuffer.Mid(2);
430 of.lpstrDefExt = defextBuffer.c_str();
431 }
432 }
433
434 // store off before the standard windows dialog can possibly change it
435 const wxString cwdOrig = wxGetCwd();
436
437 //== Execute FileDialog >>=================================================
438
439 DWORD errCode;
440 bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
441
442 #ifdef wxTRY_SMALLER_OPENFILENAME
443 // the system might be too old to support the new version file dialog
444 // boxes, try with the old size
445 if ( !success && errCode == CDERR_STRUCTSIZE &&
446 of.lStructSize != wxOPENFILENAME_V4_SIZE )
447 {
448 of.lStructSize = wxOPENFILENAME_V4_SIZE;
449
450 success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
451
452 if ( success || !errCode )
453 {
454 // use this struct size for subsequent dialogs
455 gs_ofStructSize = of.lStructSize;
456 }
457 }
458 #endif // wxTRY_SMALLER_OPENFILENAME
459
460 if ( success )
461 {
462 // GetOpenFileName will always change the current working directory on
463 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
464 // OFN_NOCHANGEDIR has no effect. If the user did not specify
465 // wxFD_CHANGE_DIR let's restore the current working directory to what it
466 // was before the dialog was shown.
467 if ( msw_flags & OFN_NOCHANGEDIR )
468 {
469 wxSetWorkingDirectory(cwdOrig);
470 }
471
472 m_fileNames.Empty();
473
474 if ( ( m_windowStyle & wxFD_MULTIPLE ) &&
475 #if defined(OFN_EXPLORER)
476 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
477 #else
478 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') )
479 #endif // OFN_EXPLORER
480 )
481 {
482 #if defined(OFN_EXPLORER)
483 m_dir = fileNameBuffer;
484 i = of.nFileOffset;
485 m_fileName = &fileNameBuffer[i];
486 m_fileNames.Add(m_fileName);
487 i += m_fileName.length() + 1;
488
489 while (fileNameBuffer[i] != wxT('\0'))
490 {
491 m_fileNames.Add(&fileNameBuffer[i]);
492 i += wxStrlen(&fileNameBuffer[i]) + 1;
493 }
494 #else
495 wxStringTokenizer toke(fileNameBuffer, _T(" \t\r\n"));
496 m_dir = toke.GetNextToken();
497 m_fileName = toke.GetNextToken();
498 m_fileNames.Add(m_fileName);
499
500 while (toke.HasMoreTokens())
501 m_fileNames.Add(toke.GetNextToken());
502 #endif // OFN_EXPLORER
503
504 wxString dir(m_dir);
505 if ( m_dir.Last() != _T('\\') )
506 dir += _T('\\');
507
508 m_path = dir + m_fileName;
509 m_filterIndex = (int)of.nFilterIndex - 1;
510 }
511 else
512 {
513 //=== Adding the correct extension >>=================================
514
515 m_filterIndex = (int)of.nFilterIndex - 1;
516
517 if ( !of.nFileExtension ||
518 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
519 {
520 // User has typed a filename without an extension:
521 const wxChar* extension = filterBuffer;
522 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
523
524 for( int i = 0; i < maxFilter; i++ ) // get extension
525 extension = extension + wxStrlen( extension ) + 1;
526
527 m_fileName = AppendExtension(fileNameBuffer, extension);
528 wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.length(), wxMAXPATH-1));
529 fileNameBuffer[wxMin(m_fileName.length(), wxMAXPATH-1)] = wxT('\0');
530 }
531
532 m_path = fileNameBuffer;
533 m_fileName = wxFileNameFromPath(fileNameBuffer);
534 m_fileNames.Add(m_fileName);
535 m_dir = wxPathOnly(fileNameBuffer);
536 }
537 }
538 #ifdef __WXDEBUG__
539 else
540 {
541 // common dialog failed - why?
542 if ( errCode != 0 )
543 {
544 // this msg is only for developers so don't translate it
545 wxLogError(wxT("Common dialog failed with error code %0lx."),
546 errCode);
547 }
548 //else: it was just cancelled
549 }
550 #endif // __WXDEBUG__
551
552 return success ? wxID_OK : wxID_CANCEL;
553
554 }
555
556 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)