File/dir dialog styles and other changes (patch 1488371):
[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 if ( ( m_windowStyle & wxMULTIPLE ) && ( m_windowStyle & wxSAVE ) )
140 m_windowStyle &= ~wxMULTIPLE;
141
142 m_bMovedWindow = false;
143
144 // Must set to zero, otherwise the wx routines won't size the window
145 // the second time you call the file dialog, because it thinks it is
146 // already at the requested size.. (when centering)
147 gs_rectDialog.x =
148 gs_rectDialog.y = 0;
149
150 }
151 void wxFileDialog::GetPaths(wxArrayString& paths) const
152 {
153 paths.Empty();
154
155 wxString dir(m_dir);
156 if ( m_dir.Last() != _T('\\') )
157 dir += _T('\\');
158
159 size_t count = m_fileNames.GetCount();
160 for ( size_t n = 0; n < count; n++ )
161 {
162 if (wxFileName(m_fileNames[n]).IsAbsolute())
163 paths.Add(m_fileNames[n]);
164 else
165 paths.Add(dir + m_fileNames[n]);
166 }
167 }
168
169 void wxFileDialog::GetFilenames(wxArrayString& files) const
170 {
171 files = m_fileNames;
172 }
173
174 void wxFileDialog::SetPath(const wxString& path)
175 {
176 wxString ext;
177 wxSplitPath(path, &m_dir, &m_fileName, &ext);
178 if ( !ext.empty() )
179 m_fileName << _T('.') << ext;
180 }
181
182 void wxFileDialog::DoGetPosition(int *x, int *y) const
183 {
184 if ( x )
185 *x = gs_rectDialog.x;
186 if ( y )
187 *y = gs_rectDialog.y;
188 }
189
190
191 void wxFileDialog::DoGetSize(int *width, int *height) const
192 {
193 if ( width )
194 *width = gs_rectDialog.width;
195 if ( height )
196 *height = gs_rectDialog.height;
197 }
198
199 void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(w), int WXUNUSED(h))
200 {
201 m_bMovedWindow = true;
202
203 gs_rectDialog.x = x;
204 gs_rectDialog.y = y;
205
206 // size of the dialog can't be changed because the controls are not laid
207 // out correctly then
208 }
209
210 // helper used below in ShowModal(): style is used to determine whether to show
211 // the "Save file" dialog (if it contains wxSAVE bit) or "Open file" one;
212 // returns true on success or false on failure in which case err is filled with
213 // the CDERR_XXX constant
214 static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err)
215 {
216 if ( style & wxSAVE ? GetSaveFileName(of) : GetOpenFileName(of) )
217 return true;
218
219 if ( err )
220 {
221 #ifdef __WXWINCE__
222 // according to MSDN, CommDlgExtendedError() should work under CE as
223 // well but apparently in practice it doesn't (anybody has more
224 // details?)
225 *err = GetLastError();
226 #else
227 *err = CommDlgExtendedError();
228 #endif
229 }
230
231 return false;
232 }
233
234 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
235 // know if the OPENFILENAME declared in the currently used headers is a V5 or
236 // V4 (smaller) one so we try to manually extend the struct in case it is the
237 // old one.
238 //
239 // We don't do this on Windows CE nor under Win64, however, as there are no
240 // compilers with old headers for these architectures
241 #if defined(__WXWINCE__) || defined(__WIN64__)
242 typedef OPENFILENAME wxOPENFILENAME;
243
244 static const DWORD gs_ofStructSize = sizeof(OPENFILENAME);
245 #else // !__WXWINCE__ || __WIN64__
246 #define wxTRY_SMALLER_OPENFILENAME
247
248 struct wxOPENFILENAME : public OPENFILENAME
249 {
250 // fields added in Windows 2000/XP comdlg32.dll version
251 void *pVoid;
252 DWORD dw1;
253 DWORD dw2;
254 };
255
256 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
257 // because sizeof(OPENFILENAME) in the headers we use when compiling the
258 // library could be less if _WIN32_WINNT is not >= 0x500
259 static const DWORD wxOPENFILENAME_V5_SIZE = 88;
260
261 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
262 static const DWORD wxOPENFILENAME_V4_SIZE = 76;
263
264 // always try the new one first
265 static DWORD gs_ofStructSize = wxOPENFILENAME_V5_SIZE;
266 #endif // __WXWINCE__ || __WIN64__/!...
267
268 int wxFileDialog::ShowModal()
269 {
270 HWND hWnd = 0;
271 if (m_parent) hWnd = (HWND) m_parent->GetHWND();
272 if (!hWnd && wxTheApp->GetTopWindow())
273 hWnd = (HWND) wxTheApp->GetTopWindow()->GetHWND();
274
275 static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name
276 wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path
277
278 *fileNameBuffer = wxT('\0');
279 *titleBuffer = wxT('\0');
280
281 #if WXWIN_COMPATIBILITY_2_4
282 long msw_flags = 0;
283 if ( (m_windowStyle & wxHIDE_READONLY) || (m_windowStyle & wxSAVE) )
284 msw_flags |= OFN_HIDEREADONLY;
285 #else
286 long msw_flags = OFN_HIDEREADONLY;
287 #endif
288
289 if ( m_windowStyle & wxFILE_MUST_EXIST )
290 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
291 /*
292 If the window has been moved the programmer is probably
293 trying to center or position it. Thus we set the callback
294 or hook function so that we can actually adjust the position.
295 Without moving or centering the dlg, it will just stay
296 in the upper left of the frame, it does not center
297 automatically.
298 */
299 if (m_bMovedWindow) // we need these flags.
300 {
301 msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK;
302 #ifndef __WXWINCE__
303 msw_flags |= OFN_ENABLESIZING;
304 #endif
305 }
306
307 if (m_windowStyle & wxMULTIPLE )
308 {
309 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
310 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
311 }
312
313 // if wxCHANGE_DIR flag is not given we shouldn't change the CWD which the
314 // standard dialog does by default (notice that under NT it does it anyhow,
315 // OFN_NOCHANGEDIR or not, see below)
316 if ( !(m_windowStyle & wxCHANGE_DIR) )
317 {
318 msw_flags |= OFN_NOCHANGEDIR;
319 }
320
321 if ( m_windowStyle & wxOVERWRITE_PROMPT )
322 {
323 msw_flags |= OFN_OVERWRITEPROMPT;
324 }
325
326 wxOPENFILENAME of;
327 wxZeroMemory(of);
328
329 of.lStructSize = gs_ofStructSize;
330 of.hwndOwner = hWnd;
331 of.lpstrTitle = WXSTRINGCAST m_message;
332 of.lpstrFileTitle = titleBuffer;
333 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT;
334
335 // Convert forward slashes to backslashes (file selector doesn't like
336 // forward slashes) and also squeeze multiple consecutive slashes into one
337 // as it doesn't like two backslashes in a row neither
338
339 wxString dir;
340 size_t i, len = m_dir.length();
341 dir.reserve(len);
342 for ( i = 0; i < len; i++ )
343 {
344 wxChar ch = m_dir[i];
345 switch ( ch )
346 {
347 case _T('/'):
348 // convert to backslash
349 ch = _T('\\');
350
351 // fall through
352
353 case _T('\\'):
354 while ( i < len - 1 )
355 {
356 wxChar chNext = m_dir[i + 1];
357 if ( chNext != _T('\\') && chNext != _T('/') )
358 break;
359
360 // ignore the next one, unless it is at the start of a UNC path
361 if (i > 0)
362 i++;
363 else
364 break;
365 }
366 // fall through
367
368 default:
369 // normal char
370 dir += ch;
371 }
372 }
373
374 of.lpstrInitialDir = dir.c_str();
375
376 of.Flags = msw_flags;
377 of.lpfnHook = wxFileDialogHookFunction;
378
379 wxArrayString wildDescriptions, wildFilters;
380
381 size_t items = wxParseCommonDialogsFilter(m_wildCard, wildDescriptions, wildFilters);
382
383 wxASSERT_MSG( items > 0 , _T("empty wildcard list") );
384
385 wxString filterBuffer;
386
387 for (i = 0; i < items ; i++)
388 {
389 filterBuffer += wildDescriptions[i];
390 filterBuffer += wxT("|");
391 filterBuffer += wildFilters[i];
392 filterBuffer += wxT("|");
393 }
394
395 // Replace | with \0
396 for (i = 0; i < filterBuffer.Len(); i++ ) {
397 if ( filterBuffer.GetChar(i) == wxT('|') ) {
398 filterBuffer[i] = wxT('\0');
399 }
400 }
401
402 of.lpstrFilter = (LPTSTR)filterBuffer.c_str();
403 of.nFilterIndex = m_filterIndex + 1;
404
405 //=== Setting defaultFileName >>=========================================
406
407 wxStrncpy( fileNameBuffer, (const wxChar *)m_fileName, wxMAXPATH-1 );
408 fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
409
410 of.lpstrFile = fileNameBuffer; // holds returned filename
411 of.nMaxFile = wxMAXPATH;
412
413 // we must set the default extension because otherwise Windows would check
414 // for the existing of a wrong file with wxOVERWRITE_PROMPT (i.e. if the
415 // user types "foo" and the default extension is ".bar" we should force it
416 // to check for "foo.bar" existence and not "foo")
417 wxString defextBuffer; // we need it to be alive until GetSaveFileName()!
418 if (m_windowStyle & wxSAVE)
419 {
420 const wxChar* extension = filterBuffer;
421 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
422
423 for( int i = 0; i < maxFilter; i++ ) // get extension
424 extension = extension + wxStrlen( extension ) + 1;
425
426 // use dummy name a to avoid assert in AppendExtension
427 defextBuffer = AppendExtension(wxT("a"), extension);
428 if (defextBuffer.StartsWith(wxT("a.")))
429 {
430 defextBuffer.Mid(2);
431 of.lpstrDefExt = defextBuffer.c_str();
432 }
433 }
434
435 // store off before the standard windows dialog can possibly change it
436 const wxString cwdOrig = wxGetCwd();
437
438 //== Execute FileDialog >>=================================================
439
440 DWORD errCode;
441 bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
442
443 #ifdef wxTRY_SMALLER_OPENFILENAME
444 // the system might be too old to support the new version file dialog
445 // boxes, try with the old size
446 if ( !success && errCode == CDERR_STRUCTSIZE &&
447 of.lStructSize != wxOPENFILENAME_V4_SIZE )
448 {
449 of.lStructSize = wxOPENFILENAME_V4_SIZE;
450
451 success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
452
453 if ( success || !errCode )
454 {
455 // use this struct size for subsequent dialogs
456 gs_ofStructSize = of.lStructSize;
457 }
458 }
459 #endif // wxTRY_SMALLER_OPENFILENAME
460
461 if ( success )
462 {
463 // GetOpenFileName will always change the current working directory on
464 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
465 // OFN_NOCHANGEDIR has no effect. If the user did not specify
466 // wxCHANGE_DIR let's restore the current working directory to what it
467 // was before the dialog was shown.
468 if ( msw_flags & OFN_NOCHANGEDIR )
469 {
470 wxSetWorkingDirectory(cwdOrig);
471 }
472
473 m_fileNames.Empty();
474
475 if ( ( m_windowStyle & wxMULTIPLE ) &&
476 #if defined(OFN_EXPLORER)
477 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
478 #else
479 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') )
480 #endif // OFN_EXPLORER
481 )
482 {
483 #if defined(OFN_EXPLORER)
484 m_dir = fileNameBuffer;
485 i = of.nFileOffset;
486 m_fileName = &fileNameBuffer[i];
487 m_fileNames.Add(m_fileName);
488 i += m_fileName.Len() + 1;
489
490 while (fileNameBuffer[i] != wxT('\0'))
491 {
492 m_fileNames.Add(&fileNameBuffer[i]);
493 i += wxStrlen(&fileNameBuffer[i]) + 1;
494 }
495 #else
496 wxStringTokenizer toke(fileNameBuffer, _T(" \t\r\n"));
497 m_dir = toke.GetNextToken();
498 m_fileName = toke.GetNextToken();
499 m_fileNames.Add(m_fileName);
500
501 while (toke.HasMoreTokens())
502 m_fileNames.Add(toke.GetNextToken());
503 #endif // OFN_EXPLORER
504
505 wxString dir(m_dir);
506 if ( m_dir.Last() != _T('\\') )
507 dir += _T('\\');
508
509 m_path = dir + m_fileName;
510 m_filterIndex = (int)of.nFilterIndex - 1;
511 }
512 else
513 {
514 //=== Adding the correct extension >>=================================
515
516 m_filterIndex = (int)of.nFilterIndex - 1;
517
518 if ( !of.nFileExtension ||
519 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
520 {
521 // User has typed a filename without an extension:
522 const wxChar* extension = filterBuffer;
523 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
524
525 for( int i = 0; i < maxFilter; i++ ) // get extension
526 extension = extension + wxStrlen( extension ) + 1;
527
528 m_fileName = AppendExtension(fileNameBuffer, extension);
529 wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.Len(), wxMAXPATH-1));
530 fileNameBuffer[wxMin(m_fileName.Len(), wxMAXPATH-1)] = wxT('\0');
531 }
532
533 m_path = fileNameBuffer;
534 m_fileName = wxFileNameFromPath(fileNameBuffer);
535 m_fileNames.Add(m_fileName);
536 m_dir = wxPathOnly(fileNameBuffer);
537 }
538 }
539 #ifdef __WXDEBUG__
540 else
541 {
542 // common dialog failed - why?
543 if ( errCode != 0 )
544 {
545 // this msg is only for developers so don't translate it
546 wxLogError(wxT("Common dialog failed with error code %0lx."),
547 errCode);
548 }
549 //else: it was just cancelled
550 }
551 #endif // __WXDEBUG__
552
553 return success ? wxID_OK : wxID_CANCEL;
554
555 }
556
557 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)
558