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