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