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