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