Make wxFileDialog::Set/SetPath() behave consistently.
[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 #include "wx/filedlg.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/msw/wrapcdlg.h"
33 #include "wx/msw/missing.h"
34 #include "wx/utils.h"
35 #include "wx/msgdlg.h"
36 #include "wx/filefn.h"
37 #include "wx/intl.h"
38 #include "wx/log.h"
39 #include "wx/app.h"
40 #include "wx/math.h"
41 #endif
42
43 #include <stdlib.h>
44 #include <string.h>
45
46 #include "wx/filename.h"
47 #include "wx/tokenzr.h"
48
49 // ----------------------------------------------------------------------------
50 // constants
51 // ----------------------------------------------------------------------------
52
53 #ifdef __WIN32__
54 # define wxMAXPATH 65534
55 #else
56 # define wxMAXPATH 1024
57 #endif
58
59 # define wxMAXFILE 1024
60
61 # define wxMAXEXT 5
62
63 // ----------------------------------------------------------------------------
64 // globals
65 // ----------------------------------------------------------------------------
66
67 // standard dialog size for the old Windows systems where the dialog wasn't
68 // resizeable
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 #ifndef __WXWINCE__
90 case WM_INITDIALOG:
91 {
92 OPENFILENAME* ofn = reinterpret_cast<OPENFILENAME *>(lParam);
93 reinterpret_cast<wxFileDialog *>(ofn->lCustData)
94 ->MSWOnInitDialogHook((WXHWND)hDlg);
95 }
96 break;
97 #endif // __WXWINCE__
98
99 case WM_NOTIFY:
100 {
101 OFNOTIFY *pNotifyCode = reinterpret_cast<OFNOTIFY *>(lParam);
102 if ( pNotifyCode->hdr.code == CDN_INITDONE )
103 {
104 reinterpret_cast<wxFileDialog *>(
105 pNotifyCode->lpOFN->lCustData)
106 ->MSWOnInitDone((WXHWND)hDlg);
107 }
108 }
109 break;
110
111 case WM_DESTROY:
112 // reuse the position used for the dialog the next time by default
113 //
114 // NB: at least under Windows 2003 this is useless as after the
115 // first time it's shown the dialog always remembers its size
116 // and position itself and ignores any later SetWindowPos calls
117 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg)), gs_rectDialog);
118 break;
119 }
120
121 // do the default processing
122 return 0;
123 }
124
125 // ----------------------------------------------------------------------------
126 // wxFileDialog
127 // ----------------------------------------------------------------------------
128
129 wxFileDialog::wxFileDialog(wxWindow *parent,
130 const wxString& message,
131 const wxString& defaultDir,
132 const wxString& defaultFileName,
133 const wxString& wildCard,
134 long style,
135 const wxPoint& pos,
136 const wxSize& sz,
137 const wxString& name)
138 : wxFileDialogBase(parent, message, defaultDir, defaultFileName,
139 wildCard, style, pos, sz, name)
140
141 {
142 // NB: all style checks are done by wxFileDialogBase::Create
143
144 m_bMovedWindow = false;
145 m_centreDir = 0;
146
147 // Must set to zero, otherwise the wx routines won't size the window
148 // the second time you call the file dialog, because it thinks it is
149 // already at the requested size.. (when centering)
150 gs_rectDialog.x =
151 gs_rectDialog.y = 0;
152 }
153
154 void wxFileDialog::GetPaths(wxArrayString& paths) const
155 {
156 paths.Empty();
157
158 wxString dir(m_dir);
159 if ( m_dir.Last() != wxT('\\') )
160 dir += wxT('\\');
161
162 size_t count = m_fileNames.GetCount();
163 for ( size_t n = 0; n < count; n++ )
164 {
165 if (wxFileName(m_fileNames[n]).IsAbsolute())
166 paths.Add(m_fileNames[n]);
167 else
168 paths.Add(dir + m_fileNames[n]);
169 }
170 }
171
172 void wxFileDialog::GetFilenames(wxArrayString& files) const
173 {
174 files = m_fileNames;
175 }
176
177 void wxFileDialog::DoGetPosition(int *x, int *y) const
178 {
179 if ( x )
180 *x = gs_rectDialog.x;
181 if ( y )
182 *y = gs_rectDialog.y;
183 }
184
185 void wxFileDialog::DoGetSize(int *width, int *height) const
186 {
187 if ( width )
188 *width = gs_rectDialog.width;
189 if ( height )
190 *height = gs_rectDialog.height;
191 }
192
193 void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(w), int WXUNUSED(h))
194 {
195 gs_rectDialog.x = x;
196 gs_rectDialog.y = y;
197
198 // our HWND is only set when we're called from MSWOnInitDone(), test if
199 // this is the case
200 HWND hwnd = GetHwnd();
201 if ( hwnd )
202 {
203 // size of the dialog can't be changed because the controls are not
204 // laid out correctly then
205 ::SetWindowPos(hwnd, HWND_TOP, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
206 }
207 else // just remember that we were requested to move the window
208 {
209 m_bMovedWindow = true;
210
211 // if Centre() had been called before, it shouldn't be taken into
212 // account now
213 m_centreDir = 0;
214 }
215 }
216
217 void wxFileDialog::DoCentre(int dir)
218 {
219 m_centreDir = dir;
220 m_bMovedWindow = true;
221
222 // it's unnecessary to do anything else at this stage as we'll redo it in
223 // MSWOnInitDone() anyhow
224 }
225
226 void wxFileDialog::MSWOnInitDone(WXHWND hDlg)
227 {
228 // note the the dialog is the parent window: hDlg is a child of it when
229 // OFN_EXPLORER is used
230 HWND hFileDlg = ::GetParent((HWND)hDlg);
231
232 // set HWND so that our DoMoveWindow() works correctly
233 SetHWND((WXHWND)hFileDlg);
234
235 if ( m_centreDir )
236 {
237 // now we have the real dialog size, remember it
238 RECT rect;
239 GetWindowRect(hFileDlg, &rect);
240 gs_rectDialog = wxRectFromRECT(rect);
241
242 // and position the window correctly: notice that we must use the base
243 // class version as our own doesn't do anything except setting flags
244 wxFileDialogBase::DoCentre(m_centreDir);
245 }
246 else // need to just move it to the correct place
247 {
248 SetPosition(gs_rectDialog.GetPosition());
249 }
250
251 // we shouldn't destroy this HWND
252 SetHWND(NULL);
253 }
254
255 // helper used below in ShowCommFileDialog(): style is used to determine
256 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
257 // "Open file" one; returns true on success or false on failure in which case
258 // err is filled with the CDERR_XXX constant
259 static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err)
260 {
261 if ( style & wxFD_SAVE ? GetSaveFileName(of) : GetOpenFileName(of) )
262 return true;
263
264 if ( err )
265 {
266 #ifdef __WXWINCE__
267 // according to MSDN, CommDlgExtendedError() should work under CE as
268 // well but apparently in practice it doesn't (anybody has more
269 // details?)
270 *err = GetLastError();
271 #else
272 *err = CommDlgExtendedError();
273 #endif
274 }
275
276 return false;
277 }
278
279 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
280 // know if the OPENFILENAME declared in the currently used headers is a V5 or
281 // V4 (smaller) one so we try to manually extend the struct in case it is the
282 // old one.
283 //
284 // We don't do this on Windows CE nor under Win64, however, as there are no
285 // compilers with old headers for these architectures
286 #if defined(__WXWINCE__) || defined(__WIN64__)
287 typedef OPENFILENAME wxOPENFILENAME;
288
289 static const DWORD gs_ofStructSize = sizeof(OPENFILENAME);
290 #else // !__WXWINCE__ || __WIN64__
291 #define wxTRY_SMALLER_OPENFILENAME
292
293 struct wxOPENFILENAME : public OPENFILENAME
294 {
295 // fields added in Windows 2000/XP comdlg32.dll version
296 void *pVoid;
297 DWORD dw1;
298 DWORD dw2;
299 };
300
301 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
302 // because sizeof(OPENFILENAME) in the headers we use when compiling the
303 // library could be less if _WIN32_WINNT is not >= 0x500
304 static const DWORD wxOPENFILENAME_V5_SIZE = 88;
305
306 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
307 static const DWORD wxOPENFILENAME_V4_SIZE = 76;
308
309 // always try the new one first
310 static DWORD gs_ofStructSize = wxOPENFILENAME_V5_SIZE;
311 #endif // __WXWINCE__ || __WIN64__/!...
312
313 static bool ShowCommFileDialog(OPENFILENAME *of, long style)
314 {
315 DWORD errCode;
316 bool success = DoShowCommFileDialog(of, style, &errCode);
317
318 #ifdef wxTRY_SMALLER_OPENFILENAME
319 // the system might be too old to support the new version file dialog
320 // boxes, try with the old size
321 if ( !success && errCode == CDERR_STRUCTSIZE &&
322 of->lStructSize != wxOPENFILENAME_V4_SIZE )
323 {
324 of->lStructSize = wxOPENFILENAME_V4_SIZE;
325
326 success = DoShowCommFileDialog(of, style, &errCode);
327
328 if ( success || !errCode )
329 {
330 // use this struct size for subsequent dialogs
331 gs_ofStructSize = of->lStructSize;
332 }
333 }
334 #endif // wxTRY_SMALLER_OPENFILENAME
335
336 if ( !success &&
337 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
338 // use CommDlgExtendedError() there anyhow)
339 #ifndef __WXWINCE__
340 errCode == FNERR_INVALIDFILENAME &&
341 #endif // !__WXWINCE__
342 of->lpstrFile[0] )
343 {
344 // this can happen if the default file name is invalid, try without it
345 // now
346 of->lpstrFile[0] = wxT('\0');
347 success = DoShowCommFileDialog(of, style, &errCode);
348 }
349
350 if ( !success )
351 {
352 // common dialog failed - why?
353 if ( errCode != 0 )
354 {
355 wxLogError(_("File dialog failed with error code %0lx."), errCode);
356 }
357 //else: it was just cancelled
358
359 return false;
360 }
361
362 return true;
363 }
364
365 #ifndef __WXWINCE__
366 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd)
367 {
368 SetHWND(hwnd);
369
370 CreateExtraControl();
371
372 SetHWND(NULL);
373 }
374 #endif // __WXWINCE__
375
376 int wxFileDialog::ShowModal()
377 {
378 HWND hWnd = 0;
379 if (m_parent) hWnd = (HWND) m_parent->GetHWND();
380 if (!hWnd && wxTheApp->GetTopWindow())
381 hWnd = (HWND) wxTheApp->GetTopWindow()->GetHWND();
382
383 static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name
384 wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path
385
386 *fileNameBuffer = wxT('\0');
387 *titleBuffer = wxT('\0');
388
389 long msw_flags = OFN_HIDEREADONLY;
390
391 if ( HasFdFlag(wxFD_FILE_MUST_EXIST) )
392 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
393 /*
394 If the window has been moved the programmer is probably
395 trying to center or position it. Thus we set the callback
396 or hook function so that we can actually adjust the position.
397 Without moving or centering the dlg, it will just stay
398 in the upper left of the frame, it does not center
399 automatically.
400 */
401 if (m_bMovedWindow || HasExtraControlCreator()) // we need these flags.
402 {
403 msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK;
404 #ifndef __WXWINCE__
405 msw_flags |= OFN_ENABLESIZING;
406 #endif
407 }
408
409 if ( HasFdFlag(wxFD_MULTIPLE) )
410 {
411 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
412 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
413 }
414
415 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
416 // standard dialog does by default (notice that under NT it does it anyhow,
417 // OFN_NOCHANGEDIR or not, see below)
418 if ( !HasFdFlag(wxFD_CHANGE_DIR) )
419 {
420 msw_flags |= OFN_NOCHANGEDIR;
421 }
422
423 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT) )
424 {
425 msw_flags |= OFN_OVERWRITEPROMPT;
426 }
427
428 wxOPENFILENAME of;
429 wxZeroMemory(of);
430
431 of.lStructSize = gs_ofStructSize;
432 of.hwndOwner = hWnd;
433 of.lpstrTitle = m_message.wx_str();
434 of.lpstrFileTitle = titleBuffer;
435 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT;
436
437 #ifndef __WXWINCE__
438 GlobalPtr hgbl;
439 if ( HasExtraControlCreator() )
440 {
441 msw_flags |= OFN_ENABLETEMPLATEHANDLE;
442
443 hgbl.Init(256, GMEM_ZEROINIT);
444 GlobalPtrLock hgblLock(hgbl);
445 LPDLGTEMPLATE lpdt = static_cast<LPDLGTEMPLATE>(hgblLock.Get());
446
447 // Define a dialog box.
448
449 lpdt->style = DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS;
450 lpdt->cdit = 0; // Number of controls
451 lpdt->x = 0;
452 lpdt->y = 0;
453
454 // convert the size of the extra controls to the dialog units
455 const wxSize extraSize = GetExtraControlSize();
456 const LONG baseUnits = ::GetDialogBaseUnits();
457 lpdt->cx = ::MulDiv(extraSize.x, 4, LOWORD(baseUnits));
458 lpdt->cy = ::MulDiv(extraSize.y, 8, HIWORD(baseUnits));
459
460 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
461 // class and title, all three set to zeros.
462
463 of.hInstance = (HINSTANCE)lpdt;
464 }
465 #endif // __WXWINCE__
466
467 // Convert forward slashes to backslashes (file selector doesn't like
468 // forward slashes) and also squeeze multiple consecutive slashes into one
469 // as it doesn't like two backslashes in a row neither
470
471 wxString dir;
472 size_t i, len = m_dir.length();
473 dir.reserve(len);
474 for ( i = 0; i < len; i++ )
475 {
476 wxChar ch = m_dir[i];
477 switch ( ch )
478 {
479 case wxT('/'):
480 // convert to backslash
481 ch = wxT('\\');
482
483 // fall through
484
485 case wxT('\\'):
486 while ( i < len - 1 )
487 {
488 wxChar chNext = m_dir[i + 1];
489 if ( chNext != wxT('\\') && chNext != wxT('/') )
490 break;
491
492 // ignore the next one, unless it is at the start of a UNC path
493 if (i > 0)
494 i++;
495 else
496 break;
497 }
498 // fall through
499
500 default:
501 // normal char
502 dir += ch;
503 }
504 }
505
506 of.lpstrInitialDir = dir.c_str();
507
508 of.Flags = msw_flags;
509 of.lpfnHook = wxFileDialogHookFunction;
510 of.lCustData = (LPARAM)this;
511
512 wxArrayString wildDescriptions, wildFilters;
513
514 size_t items = wxParseCommonDialogsFilter(m_wildCard, wildDescriptions, wildFilters);
515
516 wxASSERT_MSG( items > 0 , wxT("empty wildcard list") );
517
518 wxString filterBuffer;
519
520 for (i = 0; i < items ; i++)
521 {
522 filterBuffer += wildDescriptions[i];
523 filterBuffer += wxT("|");
524 filterBuffer += wildFilters[i];
525 filterBuffer += wxT("|");
526 }
527
528 // Replace | with \0
529 for (i = 0; i < filterBuffer.length(); i++ ) {
530 if ( filterBuffer.GetChar(i) == wxT('|') ) {
531 filterBuffer[i] = wxT('\0');
532 }
533 }
534
535 of.lpstrFilter = (LPTSTR)filterBuffer.wx_str();
536 of.nFilterIndex = m_filterIndex + 1;
537
538 //=== Setting defaultFileName >>=========================================
539
540 wxStrlcpy(fileNameBuffer, m_fileName.c_str(), WXSIZEOF(fileNameBuffer));
541
542 of.lpstrFile = fileNameBuffer; // holds returned filename
543 of.nMaxFile = wxMAXPATH;
544
545 // we must set the default extension because otherwise Windows would check
546 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
547 // user types "foo" and the default extension is ".bar" we should force it
548 // to check for "foo.bar" existence and not "foo")
549 wxString defextBuffer; // we need it to be alive until GetSaveFileName()!
550 if (HasFdFlag(wxFD_SAVE))
551 {
552 const wxChar* extension = filterBuffer.wx_str();
553 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
554
555 for( int i = 0; i < maxFilter; i++ ) // get extension
556 extension = extension + wxStrlen( extension ) + 1;
557
558 // use dummy name a to avoid assert in AppendExtension
559 defextBuffer = AppendExtension(wxT("a"), extension);
560 if (defextBuffer.StartsWith(wxT("a.")))
561 {
562 defextBuffer = defextBuffer.Mid(2); // remove "a."
563 of.lpstrDefExt = defextBuffer.c_str();
564 }
565 }
566
567 // store off before the standard windows dialog can possibly change it
568 const wxString cwdOrig = wxGetCwd();
569
570 //== Execute FileDialog >>=================================================
571
572 if ( !ShowCommFileDialog(&of, m_windowStyle) )
573 return wxID_CANCEL;
574
575 // GetOpenFileName will always change the current working directory on
576 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
577 // OFN_NOCHANGEDIR has no effect. If the user did not specify
578 // wxFD_CHANGE_DIR let's restore the current working directory to what it
579 // was before the dialog was shown.
580 if ( msw_flags & OFN_NOCHANGEDIR )
581 {
582 wxSetWorkingDirectory(cwdOrig);
583 }
584
585 m_fileNames.Empty();
586
587 if ( ( HasFdFlag(wxFD_MULTIPLE) ) &&
588 #if defined(OFN_EXPLORER)
589 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
590 #else
591 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') )
592 #endif // OFN_EXPLORER
593 )
594 {
595 #if defined(OFN_EXPLORER)
596 m_dir = fileNameBuffer;
597 i = of.nFileOffset;
598 m_fileName = &fileNameBuffer[i];
599 m_fileNames.Add(m_fileName);
600 i += m_fileName.length() + 1;
601
602 while (fileNameBuffer[i] != wxT('\0'))
603 {
604 m_fileNames.Add(&fileNameBuffer[i]);
605 i += wxStrlen(&fileNameBuffer[i]) + 1;
606 }
607 #else
608 wxStringTokenizer toke(fileNameBuffer, wxT(" \t\r\n"));
609 m_dir = toke.GetNextToken();
610 m_fileName = toke.GetNextToken();
611 m_fileNames.Add(m_fileName);
612
613 while (toke.HasMoreTokens())
614 m_fileNames.Add(toke.GetNextToken());
615 #endif // OFN_EXPLORER
616
617 wxString dir(m_dir);
618 if ( m_dir.Last() != wxT('\\') )
619 dir += wxT('\\');
620
621 m_path = dir + m_fileName;
622 m_filterIndex = (int)of.nFilterIndex - 1;
623 }
624 else
625 {
626 //=== Adding the correct extension >>=================================
627
628 m_filterIndex = (int)of.nFilterIndex - 1;
629
630 if ( !of.nFileExtension ||
631 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
632 {
633 // User has typed a filename without an extension:
634 const wxChar* extension = filterBuffer.wx_str();
635 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
636
637 for( int i = 0; i < maxFilter; i++ ) // get extension
638 extension = extension + wxStrlen( extension ) + 1;
639
640 m_fileName = AppendExtension(fileNameBuffer, extension);
641 wxStrlcpy(fileNameBuffer, m_fileName.c_str(), WXSIZEOF(fileNameBuffer));
642 }
643
644 m_path = fileNameBuffer;
645 m_fileName = wxFileNameFromPath(fileNameBuffer);
646 m_fileNames.Add(m_fileName);
647 m_dir = wxPathOnly(fileNameBuffer);
648 }
649
650 return wxID_OK;
651
652 }
653
654 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)