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