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