]> git.saurik.com Git - wxWidgets.git/blame - src/msw/filedlg.cpp
Include wx/scrolwin.h and wx/dirdlg.h according to precompiled headers of wx/wx.h...
[wxWidgets.git] / src / msw / filedlg.cpp
CommitLineData
2bda0e17 1/////////////////////////////////////////////////////////////////////////////
f6bcfd97 2// Name: src/msw/filedlg.cpp
2bda0e17
KB
3// Purpose: wxFileDialog
4// Author: Julian Smart
5// Modified by:
6// Created: 01/02/97
7// RCS-ID: $Id$
6c9a19aa 8// Copyright: (c) Julian Smart
65571936 9// Licence: wxWindows licence
2bda0e17
KB
10/////////////////////////////////////////////////////////////////////////////
11
f6bcfd97
BP
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
2bda0e17
KB
20// For compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
ba681060 24 #pragma hdrstop
2bda0e17
KB
25#endif
26
3180bc0e 27#if wxUSE_FILEDLG && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
1e6feb95 28
2bda0e17 29#ifndef WX_PRECOMP
ba681060
VZ
30 #include "wx/utils.h"
31 #include "wx/msgdlg.h"
ba681060 32 #include "wx/filedlg.h"
2b5f62a0 33 #include "wx/filefn.h"
ba681060 34 #include "wx/intl.h"
2662e49e 35 #include "wx/log.h"
f6bcfd97 36 #include "wx/app.h"
8f177c8e 37#endif
2bda0e17 38
660296aa 39#include "wx/msw/wrapcdlg.h"
2bda0e17 40
2bda0e17
KB
41#include <stdlib.h>
42#include <string.h>
43
8ad9ca97 44#include "wx/filename.h"
8f177c8e 45#include "wx/tokenzr.h"
b713f891 46#include "wx/math.h"
8f177c8e 47
41b8fe99 48#include "wx/msw/missing.h"
6e8aa701 49
f6bcfd97
BP
50// ----------------------------------------------------------------------------
51// constants
52// ----------------------------------------------------------------------------
53
54#ifdef __WIN32__
2b5f62a0 55# define wxMAXPATH 65534
f6bcfd97
BP
56#else
57# define wxMAXPATH 1024
58#endif
59
60# define wxMAXFILE 1024
61
62# define wxMAXEXT 5
63
0b11099d
VZ
64// ----------------------------------------------------------------------------
65// globals
66// ----------------------------------------------------------------------------
67
68// standard dialog size
69static wxRect gs_rectDialog(0, 0, 428, 266);
70
f6bcfd97
BP
71// ============================================================================
72// implementation
73// ============================================================================
74
f74172ab 75IMPLEMENT_CLASS(wxFileDialog, wxFileDialogBase)
2bda0e17 76
0b11099d
VZ
77// ----------------------------------------------------------------------------
78// hook function for moving the dialog
79// ----------------------------------------------------------------------------
80
106d80ad 81UINT_PTR APIENTRY
0b11099d
VZ
82wxFileDialogHookFunction(HWND hDlg,
83 UINT iMsg,
84 WPARAM WXUNUSED(wParam),
85 LPARAM lParam)
86{
cb80db46 87 switch ( iMsg )
0b11099d 88 {
0b11099d
VZ
89 case WM_NOTIFY:
90 {
cb80db46
VZ
91 OFNOTIFY *pNotifyCode = wx_reinterpret_cast(OFNOTIFY *, lParam);
92 if ( pNotifyCode->hdr.code == CDN_INITDONE )
0b11099d 93 {
cb80db46
VZ
94 // note that we need to move the parent window: hDlg is a
95 // child of it when OFN_EXPLORER is used
96 ::SetWindowPos
97 (
98 ::GetParent(hDlg),
99 HWND_TOP,
100 gs_rectDialog.x, gs_rectDialog.y,
101 0, 0,
102 SWP_NOZORDER | SWP_NOSIZE
103 );
0b11099d
VZ
104 }
105 }
106 break;
cb80db46
VZ
107
108 case WM_DESTROY:
109 // reuse the position used for the dialog the next time by default
110 //
111 // NB: at least under Windows 2003 this is useless as after the
112 // first time it's shown the dialog always remembers its size
113 // and position itself and ignores any later SetWindowPos calls
114 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg)), gs_rectDialog);
115 break;
0b11099d
VZ
116 }
117
118 // do the default processing
119 return 0;
120}
121
f6bcfd97 122// ----------------------------------------------------------------------------
b600ed13 123// wxFileDialog
f6bcfd97
BP
124// ----------------------------------------------------------------------------
125
2b5f62a0
VZ
126wxFileDialog::wxFileDialog(wxWindow *parent,
127 const wxString& message,
128 const wxString& defaultDir,
129 const wxString& defaultFileName,
130 const wxString& wildCard,
131 long style,
ff3e84ff
VZ
132 const wxPoint& pos,
133 const wxSize& sz,
134 const wxString& name)
0b11099d 135 : wxFileDialogBase(parent, message, defaultDir, defaultFileName,
ff3e84ff 136 wildCard, style, pos, sz, name)
f74172ab 137
2bda0e17 138{
556151f5 139 // NB: all style checks are done by wxFileDialogBase::Create
2bda0e17 140
0b11099d
VZ
141 m_bMovedWindow = false;
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}
c61f4f6d
VZ
150void 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 {
8ad9ca97
JS
161 if (wxFileName(m_fileNames[n]).IsAbsolute())
162 paths.Add(m_fileNames[n]);
163 else
164 paths.Add(dir + m_fileNames[n]);
c61f4f6d
VZ
165 }
166}
167
89654c9a
VZ
168void wxFileDialog::GetFilenames(wxArrayString& files) const
169{
170 files = m_fileNames;
171}
172
2b5f62a0
VZ
173void 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
cb80db46 181void wxFileDialog::DoGetPosition(int *x, int *y) const
0b11099d 182{
cb80db46
VZ
183 if ( x )
184 *x = gs_rectDialog.x;
185 if ( y )
186 *y = gs_rectDialog.y;
0b11099d
VZ
187}
188
189
190void wxFileDialog::DoGetSize(int *width, int *height) const
191{
cb80db46
VZ
192 if ( width )
193 *width = gs_rectDialog.width;
194 if ( height )
195 *height = gs_rectDialog.height;
0b11099d
VZ
196}
197
cb80db46 198void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(w), int WXUNUSED(h))
0b11099d
VZ
199{
200 m_bMovedWindow = true;
201
202 gs_rectDialog.x = x;
203 gs_rectDialog.y = y;
204
cb80db46
VZ
205 // size of the dialog can't be changed because the controls are not laid
206 // out correctly then
0b11099d
VZ
207}
208
c46c1fb8 209// helper used below in ShowModal(): style is used to determine whether to show
e031f1df 210// the "Save file" dialog (if it contains wxFD_SAVE bit) or "Open file" one;
c46c1fb8
VZ
211// returns true on success or false on failure in which case err is filled with
212// the CDERR_XXX constant
213static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err)
214{
e031f1df 215 if ( style & wxFD_SAVE ? GetSaveFileName(of) : GetOpenFileName(of) )
c46c1fb8
VZ
216 return true;
217
218 if ( err )
219 {
220#ifdef __WXWINCE__
221 // according to MSDN, CommDlgExtendedError() should work under CE as
222 // well but apparently in practice it doesn't (anybody has more
223 // details?)
224 *err = GetLastError();
225#else
226 *err = CommDlgExtendedError();
227#endif
228 }
229
230 return false;
231}
232
04227efc
VZ
233// We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
234// know if the OPENFILENAME declared in the currently used headers is a V5 or
235// V4 (smaller) one so we try to manually extend the struct in case it is the
236// old one.
5bb37216 237//
04227efc
VZ
238// We don't do this on Windows CE nor under Win64, however, as there are no
239// compilers with old headers for these architectures
240#if defined(__WXWINCE__) || defined(__WIN64__)
5bb37216
VZ
241 typedef OPENFILENAME wxOPENFILENAME;
242
04227efc
VZ
243 static const DWORD gs_ofStructSize = sizeof(OPENFILENAME);
244#else // !__WXWINCE__ || __WIN64__
245 #define wxTRY_SMALLER_OPENFILENAME
246
5bb37216
VZ
247 struct wxOPENFILENAME : public OPENFILENAME
248 {
249 // fields added in Windows 2000/XP comdlg32.dll version
250 void *pVoid;
251 DWORD dw1;
252 DWORD dw2;
253 };
254
255 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
256 // because sizeof(OPENFILENAME) in the headers we use when compiling the
257 // library could be less if _WIN32_WINNT is not >= 0x500
258 static const DWORD wxOPENFILENAME_V5_SIZE = 88;
259
260 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
261 static const DWORD wxOPENFILENAME_V4_SIZE = 76;
5bb37216 262
04227efc
VZ
263 // always try the new one first
264 static DWORD gs_ofStructSize = wxOPENFILENAME_V5_SIZE;
265#endif // __WXWINCE__ || __WIN64__/!...
5bb37216 266
c61f4f6d 267int wxFileDialog::ShowModal()
2bda0e17 268{
1f2f0331
VZ
269 HWND hWnd = 0;
270 if (m_parent) hWnd = (HWND) m_parent->GetHWND();
f6bcfd97
BP
271 if (!hWnd && wxTheApp->GetTopWindow())
272 hWnd = (HWND) wxTheApp->GetTopWindow()->GetHWND();
2bda0e17 273
f6bcfd97
BP
274 static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name
275 wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path
2bda0e17 276
223d09f6
KB
277 *fileNameBuffer = wxT('\0');
278 *titleBuffer = wxT('\0');
2bda0e17 279
21416306 280#if WXWIN_COMPATIBILITY_2_4
2bda0e17 281 long msw_flags = 0;
e031f1df 282 if ( (m_windowStyle & wxHIDE_READONLY) || (m_windowStyle & wxFD_SAVE) )
1f2f0331 283 msw_flags |= OFN_HIDEREADONLY;
21416306
WS
284#else
285 long msw_flags = OFN_HIDEREADONLY;
286#endif
287
e031f1df 288 if ( m_windowStyle & wxFD_FILE_MUST_EXIST )
1f2f0331 289 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
0b11099d
VZ
290 /*
291 If the window has been moved the programmer is probably
292 trying to center or position it. Thus we set the callback
293 or hook function so that we can actually adjust the position.
294 Without moving or centering the dlg, it will just stay
295 in the upper left of the frame, it does not center
5bb37216 296 automatically.
0b11099d 297 */
503528dc
JS
298 if (m_bMovedWindow) // we need these flags.
299 {
300 msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK;
301#ifndef __WXWINCE__
302 msw_flags |= OFN_ENABLESIZING;
303#endif
304 }
6e8aa701 305
e031f1df 306 if (m_windowStyle & wxFD_MULTIPLE )
6e8aa701
VZ
307 {
308 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
309 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
310 }
311
e031f1df 312 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
af1f0a76
VZ
313 // standard dialog does by default (notice that under NT it does it anyhow,
314 // OFN_NOCHANGEDIR or not, see below)
e031f1df 315 if ( !(m_windowStyle & wxFD_CHANGE_DIR) )
6e8aa701
VZ
316 {
317 msw_flags |= OFN_NOCHANGEDIR;
318 }
ac95e671 319
e031f1df 320 if ( m_windowStyle & wxFD_OVERWRITE_PROMPT )
99d1b93d
VZ
321 {
322 msw_flags |= OFN_OVERWRITEPROMPT;
323 }
ac95e671 324
5bb37216 325 wxOPENFILENAME of;
f6bcfd97
BP
326 wxZeroMemory(of);
327
5bb37216 328 of.lStructSize = gs_ofStructSize;
e15e548b 329 of.hwndOwner = hWnd;
837e5743 330 of.lpstrTitle = WXSTRINGCAST m_message;
e15e548b 331 of.lpstrFileTitle = titleBuffer;
5bb37216 332 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT;
2bda0e17 333
0bc9b25e 334 // Convert forward slashes to backslashes (file selector doesn't like
99d1b93d
VZ
335 // forward slashes) and also squeeze multiple consecutive slashes into one
336 // as it doesn't like two backslashes in a row neither
0627d091 337
cbe874bd
WS
338 wxString dir;
339 size_t i, len = m_dir.length();
99d1b93d 340 dir.reserve(len);
0627d091 341 for ( i = 0; i < len; i++ )
99d1b93d
VZ
342 {
343 wxChar ch = m_dir[i];
344 switch ( ch )
345 {
346 case _T('/'):
347 // convert to backslash
348 ch = _T('\\');
349
350 // fall through
0bc9b25e 351
99d1b93d
VZ
352 case _T('\\'):
353 while ( i < len - 1 )
354 {
355 wxChar chNext = m_dir[i + 1];
356 if ( chNext != _T('\\') && chNext != _T('/') )
357 break;
358
04d93c3a
CE
359 // ignore the next one, unless it is at the start of a UNC path
360 if (i > 0)
361 i++;
362 else
0b11099d 363 break;
99d1b93d
VZ
364 }
365 // fall through
366
367 default:
368 // normal char
369 dir += ch;
370 }
371 }
372
373 of.lpstrInitialDir = dir.c_str();
2bda0e17 374
e15e548b 375 of.Flags = msw_flags;
0b11099d 376 of.lpfnHook = wxFileDialogHookFunction;
2bda0e17 377
daf32463 378 wxArrayString wildDescriptions, wildFilters;
2bda0e17 379
daf32463 380 size_t items = wxParseCommonDialogsFilter(m_wildCard, wildDescriptions, wildFilters);
2bda0e17 381
daf32463 382 wxASSERT_MSG( items > 0 , _T("empty wildcard list") );
2bda0e17 383
1f2f0331 384 wxString filterBuffer;
2bda0e17 385
daf32463
WS
386 for (i = 0; i < items ; i++)
387 {
388 filterBuffer += wildDescriptions[i];
389 filterBuffer += wxT("|");
390 filterBuffer += wildFilters[i];
391 filterBuffer += wxT("|");
574c0bbf
JS
392 }
393
574c0bbf 394 // Replace | with \0
e031f1df 395 for (i = 0; i < filterBuffer.length(); i++ ) {
223d09f6
KB
396 if ( filterBuffer.GetChar(i) == wxT('|') ) {
397 filterBuffer[i] = wxT('\0');
e15e548b
VZ
398 }
399 }
2bda0e17 400
daf32463 401 of.lpstrFilter = (LPTSTR)filterBuffer.c_str();
cc42eb7a 402 of.nFilterIndex = m_filterIndex + 1;
2bda0e17
KB
403
404 //=== Setting defaultFileName >>=========================================
405
f6bcfd97
BP
406 wxStrncpy( fileNameBuffer, (const wxChar *)m_fileName, wxMAXPATH-1 );
407 fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
2bda0e17 408
e15e548b 409 of.lpstrFile = fileNameBuffer; // holds returned filename
f6bcfd97 410 of.nMaxFile = wxMAXPATH;
2bda0e17 411
90bddb85 412 // we must set the default extension because otherwise Windows would check
e031f1df 413 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
90bddb85
VZ
414 // user types "foo" and the default extension is ".bar" we should force it
415 // to check for "foo.bar" existence and not "foo")
416 wxString defextBuffer; // we need it to be alive until GetSaveFileName()!
e031f1df 417 if (m_windowStyle & wxFD_SAVE)
90bddb85
VZ
418 {
419 const wxChar* extension = filterBuffer;
420 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
421
422 for( int i = 0; i < maxFilter; i++ ) // get extension
423 extension = extension + wxStrlen( extension ) + 1;
424
425 // use dummy name a to avoid assert in AppendExtension
426 defextBuffer = AppendExtension(wxT("a"), extension);
427 if (defextBuffer.StartsWith(wxT("a.")))
428 {
429 defextBuffer.Mid(2);
430 of.lpstrDefExt = defextBuffer.c_str();
431 }
432 }
0b11099d 433
af1f0a76
VZ
434 // store off before the standard windows dialog can possibly change it
435 const wxString cwdOrig = wxGetCwd();
436
2bda0e17
KB
437 //== Execute FileDialog >>=================================================
438
c46c1fb8 439 DWORD errCode;
ff3e84ff 440 bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
2bda0e17 441
04227efc 442#ifdef wxTRY_SMALLER_OPENFILENAME
5bb37216
VZ
443 // the system might be too old to support the new version file dialog
444 // boxes, try with the old size
445 if ( !success && errCode == CDERR_STRUCTSIZE &&
446 of.lStructSize != wxOPENFILENAME_V4_SIZE )
f6bcfd97 447 {
5bb37216
VZ
448 of.lStructSize = wxOPENFILENAME_V4_SIZE;
449
ff3e84ff 450 success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
f6bcfd97 451
5bb37216 452 if ( success || !errCode )
f6bcfd97 453 {
5bb37216
VZ
454 // use this struct size for subsequent dialogs
455 gs_ofStructSize = of.lStructSize;
f6bcfd97
BP
456 }
457 }
04227efc 458#endif // wxTRY_SMALLER_OPENFILENAME
f6bcfd97 459
2bda0e17
KB
460 if ( success )
461 {
5bb37216 462 // GetOpenFileName will always change the current working directory on
c46c1fb8
VZ
463 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
464 // OFN_NOCHANGEDIR has no effect. If the user did not specify
e031f1df 465 // wxFD_CHANGE_DIR let's restore the current working directory to what it
c46c1fb8
VZ
466 // was before the dialog was shown.
467 if ( msw_flags & OFN_NOCHANGEDIR )
468 {
469 wxSetWorkingDirectory(cwdOrig);
470 }
471
c61f4f6d
VZ
472 m_fileNames.Empty();
473
e031f1df 474 if ( ( m_windowStyle & wxFD_MULTIPLE ) &&
c61f4f6d 475#if defined(OFN_EXPLORER)
c39e82f0 476 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
c61f4f6d 477#else
c39e82f0 478 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') )
c61f4f6d 479#endif // OFN_EXPLORER
c39e82f0 480 )
c61f4f6d
VZ
481 {
482#if defined(OFN_EXPLORER)
483 m_dir = fileNameBuffer;
484 i = of.nFileOffset;
485 m_fileName = &fileNameBuffer[i];
486 m_fileNames.Add(m_fileName);
e031f1df 487 i += m_fileName.length() + 1;
c61f4f6d
VZ
488
489 while (fileNameBuffer[i] != wxT('\0'))
490 {
491 m_fileNames.Add(&fileNameBuffer[i]);
492 i += wxStrlen(&fileNameBuffer[i]) + 1;
493 }
494#else
c6603ac2 495 wxStringTokenizer toke(fileNameBuffer, _T(" \t\r\n"));
c61f4f6d
VZ
496 m_dir = toke.GetNextToken();
497 m_fileName = toke.GetNextToken();
498 m_fileNames.Add(m_fileName);
499
500 while (toke.HasMoreTokens())
501 m_fileNames.Add(toke.GetNextToken());
502#endif // OFN_EXPLORER
503
504 wxString dir(m_dir);
505 if ( m_dir.Last() != _T('\\') )
506 dir += _T('\\');
507
c61f4f6d 508 m_path = dir + m_fileName;
f0f43012 509 m_filterIndex = (int)of.nFilterIndex - 1;
c61f4f6d
VZ
510 }
511 else
512 {
c61f4f6d 513 //=== Adding the correct extension >>=================================
2bda0e17 514
cc42eb7a 515 m_filterIndex = (int)of.nFilterIndex - 1;
2bda0e17 516
0b11099d 517 if ( !of.nFileExtension ||
c6603ac2
VS
518 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
519 {
520 // User has typed a filename without an extension:
f74172ab
VZ
521 const wxChar* extension = filterBuffer;
522 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
2bda0e17 523
f74172ab
VZ
524 for( int i = 0; i < maxFilter; i++ ) // get extension
525 extension = extension + wxStrlen( extension ) + 1;
a039ccbf 526
f74172ab 527 m_fileName = AppendExtension(fileNameBuffer, extension);
e031f1df
WS
528 wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.length(), wxMAXPATH-1));
529 fileNameBuffer[wxMin(m_fileName.length(), wxMAXPATH-1)] = wxT('\0');
2bda0e17 530 }
2bda0e17 531
c61f4f6d
VZ
532 m_path = fileNameBuffer;
533 m_fileName = wxFileNameFromPath(fileNameBuffer);
534 m_fileNames.Add(m_fileName);
535 m_dir = wxPathOnly(fileNameBuffer);
536 }
7cc98b3e 537 }
c46c1fb8 538#ifdef __WXDEBUG__
7cc98b3e
VZ
539 else
540 {
541 // common dialog failed - why?
c46c1fb8 542 if ( errCode != 0 )
e8615999 543 {
c46c1fb8 544 // this msg is only for developers so don't translate it
223d09f6 545 wxLogError(wxT("Common dialog failed with error code %0lx."),
c46c1fb8 546 errCode);
7cc98b3e
VZ
547 }
548 //else: it was just cancelled
7cc98b3e 549 }
c46c1fb8 550#endif // __WXDEBUG__
2bda0e17 551
7cc98b3e 552 return success ? wxID_OK : wxID_CANCEL;
2bda0e17
KB
553
554}
555
3180bc0e 556#endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)