]> git.saurik.com Git - wxWidgets.git/blob - src/msw/filedlg.cpp
Add class factories for filter streams. Also filters now follow the convention
[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
68 static wxRect gs_rectDialog(0, 0, 428, 266);
69
70 // ============================================================================
71 // implementation
72 // ============================================================================
73
74 IMPLEMENT_CLASS(wxFileDialog, wxFileDialogBase)
75
76 // ----------------------------------------------------------------------------
77 // hook function for moving the dialog
78 // ----------------------------------------------------------------------------
79
80 UINT_PTR APIENTRY
81 wxFileDialogHookFunction(HWND hDlg,
82 UINT iMsg,
83 WPARAM WXUNUSED(wParam),
84 LPARAM lParam)
85 {
86 switch ( iMsg )
87 {
88 case WM_NOTIFY:
89 {
90 OFNOTIFY *pNotifyCode = wx_reinterpret_cast(OFNOTIFY *, lParam);
91 if ( pNotifyCode->hdr.code == CDN_INITDONE )
92 {
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 );
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
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 }
149 void 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 {
160 if (wxFileName(m_fileNames[n]).IsAbsolute())
161 paths.Add(m_fileNames[n]);
162 else
163 paths.Add(dir + m_fileNames[n]);
164 }
165 }
166
167 void wxFileDialog::GetFilenames(wxArrayString& files) const
168 {
169 files = m_fileNames;
170 }
171
172 void 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
180 void wxFileDialog::DoGetPosition(int *x, int *y) const
181 {
182 if ( x )
183 *x = gs_rectDialog.x;
184 if ( y )
185 *y = gs_rectDialog.y;
186 }
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 m_bMovedWindow = true;
200
201 gs_rectDialog.x = x;
202 gs_rectDialog.y = y;
203
204 // size of the dialog can't be changed because the controls are not laid
205 // out correctly then
206 }
207
208 // helper used below in ShowModal(): style is used to determine whether to show
209 // the "Save file" dialog (if it contains wxFD_SAVE bit) or "Open file" one;
210 // returns true on success or false on failure in which case err is filled with
211 // the CDERR_XXX constant
212 static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err)
213 {
214 if ( style & wxFD_SAVE ? GetSaveFileName(of) : GetOpenFileName(of) )
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
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.
236 //
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__)
240 typedef OPENFILENAME wxOPENFILENAME;
241
242 static const DWORD gs_ofStructSize = sizeof(OPENFILENAME);
243 #else // !__WXWINCE__ || __WIN64__
244 #define wxTRY_SMALLER_OPENFILENAME
245
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;
261
262 // always try the new one first
263 static DWORD gs_ofStructSize = wxOPENFILENAME_V5_SIZE;
264 #endif // __WXWINCE__ || __WIN64__/!...
265
266 int wxFileDialog::ShowModal()
267 {
268 HWND hWnd = 0;
269 if (m_parent) hWnd = (HWND) m_parent->GetHWND();
270 if (!hWnd && wxTheApp->GetTopWindow())
271 hWnd = (HWND) wxTheApp->GetTopWindow()->GetHWND();
272
273 static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name
274 wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path
275
276 *fileNameBuffer = wxT('\0');
277 *titleBuffer = wxT('\0');
278
279 #if WXWIN_COMPATIBILITY_2_4
280 long msw_flags = 0;
281 if ( HasFdFlag(wxHIDE_READONLY) || HasFdFlag(wxFD_SAVE) )
282 msw_flags |= OFN_HIDEREADONLY;
283 #else
284 long msw_flags = OFN_HIDEREADONLY;
285 #endif
286
287 if ( HasFdFlag(wxFD_FILE_MUST_EXIST) )
288 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
289 /*
290 If the window has been moved the programmer is probably
291 trying to center or position it. Thus we set the callback
292 or hook function so that we can actually adjust the position.
293 Without moving or centering the dlg, it will just stay
294 in the upper left of the frame, it does not center
295 automatically.
296 */
297 if (m_bMovedWindow) // we need these flags.
298 {
299 msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK;
300 #ifndef __WXWINCE__
301 msw_flags |= OFN_ENABLESIZING;
302 #endif
303 }
304
305 if ( HasFdFlag(wxFD_MULTIPLE) )
306 {
307 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
308 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
309 }
310
311 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
312 // standard dialog does by default (notice that under NT it does it anyhow,
313 // OFN_NOCHANGEDIR or not, see below)
314 if ( !HasFdFlag(wxFD_CHANGE_DIR) )
315 {
316 msw_flags |= OFN_NOCHANGEDIR;
317 }
318
319 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT) )
320 {
321 msw_flags |= OFN_OVERWRITEPROMPT;
322 }
323
324 wxOPENFILENAME of;
325 wxZeroMemory(of);
326
327 of.lStructSize = gs_ofStructSize;
328 of.hwndOwner = hWnd;
329 of.lpstrTitle = WXSTRINGCAST m_message;
330 of.lpstrFileTitle = titleBuffer;
331 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT;
332
333 // Convert forward slashes to backslashes (file selector doesn't like
334 // forward slashes) and also squeeze multiple consecutive slashes into one
335 // as it doesn't like two backslashes in a row neither
336
337 wxString dir;
338 size_t i, len = m_dir.length();
339 dir.reserve(len);
340 for ( i = 0; i < len; i++ )
341 {
342 wxChar ch = m_dir[i];
343 switch ( ch )
344 {
345 case _T('/'):
346 // convert to backslash
347 ch = _T('\\');
348
349 // fall through
350
351 case _T('\\'):
352 while ( i < len - 1 )
353 {
354 wxChar chNext = m_dir[i + 1];
355 if ( chNext != _T('\\') && chNext != _T('/') )
356 break;
357
358 // ignore the next one, unless it is at the start of a UNC path
359 if (i > 0)
360 i++;
361 else
362 break;
363 }
364 // fall through
365
366 default:
367 // normal char
368 dir += ch;
369 }
370 }
371
372 of.lpstrInitialDir = dir.c_str();
373
374 of.Flags = msw_flags;
375 of.lpfnHook = wxFileDialogHookFunction;
376
377 wxArrayString wildDescriptions, wildFilters;
378
379 size_t items = wxParseCommonDialogsFilter(m_wildCard, wildDescriptions, wildFilters);
380
381 wxASSERT_MSG( items > 0 , _T("empty wildcard list") );
382
383 wxString filterBuffer;
384
385 for (i = 0; i < items ; i++)
386 {
387 filterBuffer += wildDescriptions[i];
388 filterBuffer += wxT("|");
389 filterBuffer += wildFilters[i];
390 filterBuffer += wxT("|");
391 }
392
393 // Replace | with \0
394 for (i = 0; i < filterBuffer.length(); i++ ) {
395 if ( filterBuffer.GetChar(i) == wxT('|') ) {
396 filterBuffer[i] = wxT('\0');
397 }
398 }
399
400 of.lpstrFilter = (LPTSTR)filterBuffer.c_str();
401 of.nFilterIndex = m_filterIndex + 1;
402
403 //=== Setting defaultFileName >>=========================================
404
405 wxStrncpy( fileNameBuffer, (const wxChar *)m_fileName, wxMAXPATH-1 );
406 fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
407
408 of.lpstrFile = fileNameBuffer; // holds returned filename
409 of.nMaxFile = wxMAXPATH;
410
411 // we must set the default extension because otherwise Windows would check
412 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
413 // user types "foo" and the default extension is ".bar" we should force it
414 // to check for "foo.bar" existence and not "foo")
415 wxString defextBuffer; // we need it to be alive until GetSaveFileName()!
416 if (HasFdFlag(wxFD_SAVE))
417 {
418 const wxChar* extension = filterBuffer;
419 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
420
421 for( int i = 0; i < maxFilter; i++ ) // get extension
422 extension = extension + wxStrlen( extension ) + 1;
423
424 // use dummy name a to avoid assert in AppendExtension
425 defextBuffer = AppendExtension(wxT("a"), extension);
426 if (defextBuffer.StartsWith(wxT("a.")))
427 {
428 defextBuffer.Mid(2);
429 of.lpstrDefExt = defextBuffer.c_str();
430 }
431 }
432
433 // store off before the standard windows dialog can possibly change it
434 const wxString cwdOrig = wxGetCwd();
435
436 //== Execute FileDialog >>=================================================
437
438 DWORD errCode;
439 bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
440
441 #ifdef wxTRY_SMALLER_OPENFILENAME
442 // the system might be too old to support the new version file dialog
443 // boxes, try with the old size
444 if ( !success && errCode == CDERR_STRUCTSIZE &&
445 of.lStructSize != wxOPENFILENAME_V4_SIZE )
446 {
447 of.lStructSize = wxOPENFILENAME_V4_SIZE;
448
449 success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
450
451 if ( success || !errCode )
452 {
453 // use this struct size for subsequent dialogs
454 gs_ofStructSize = of.lStructSize;
455 }
456 }
457 #endif // wxTRY_SMALLER_OPENFILENAME
458
459 if ( success )
460 {
461 // GetOpenFileName will always change the current working directory on
462 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
463 // OFN_NOCHANGEDIR has no effect. If the user did not specify
464 // wxFD_CHANGE_DIR let's restore the current working directory to what it
465 // was before the dialog was shown.
466 if ( msw_flags & OFN_NOCHANGEDIR )
467 {
468 wxSetWorkingDirectory(cwdOrig);
469 }
470
471 m_fileNames.Empty();
472
473 if ( ( HasFdFlag(wxFD_MULTIPLE) ) &&
474 #if defined(OFN_EXPLORER)
475 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
476 #else
477 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') )
478 #endif // OFN_EXPLORER
479 )
480 {
481 #if defined(OFN_EXPLORER)
482 m_dir = fileNameBuffer;
483 i = of.nFileOffset;
484 m_fileName = &fileNameBuffer[i];
485 m_fileNames.Add(m_fileName);
486 i += m_fileName.length() + 1;
487
488 while (fileNameBuffer[i] != wxT('\0'))
489 {
490 m_fileNames.Add(&fileNameBuffer[i]);
491 i += wxStrlen(&fileNameBuffer[i]) + 1;
492 }
493 #else
494 wxStringTokenizer toke(fileNameBuffer, _T(" \t\r\n"));
495 m_dir = toke.GetNextToken();
496 m_fileName = toke.GetNextToken();
497 m_fileNames.Add(m_fileName);
498
499 while (toke.HasMoreTokens())
500 m_fileNames.Add(toke.GetNextToken());
501 #endif // OFN_EXPLORER
502
503 wxString dir(m_dir);
504 if ( m_dir.Last() != _T('\\') )
505 dir += _T('\\');
506
507 m_path = dir + m_fileName;
508 m_filterIndex = (int)of.nFilterIndex - 1;
509 }
510 else
511 {
512 //=== Adding the correct extension >>=================================
513
514 m_filterIndex = (int)of.nFilterIndex - 1;
515
516 if ( !of.nFileExtension ||
517 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
518 {
519 // User has typed a filename without an extension:
520 const wxChar* extension = filterBuffer;
521 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
522
523 for( int i = 0; i < maxFilter; i++ ) // get extension
524 extension = extension + wxStrlen( extension ) + 1;
525
526 m_fileName = AppendExtension(fileNameBuffer, extension);
527 wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.length(), wxMAXPATH-1));
528 fileNameBuffer[wxMin(m_fileName.length(), wxMAXPATH-1)] = wxT('\0');
529 }
530
531 m_path = fileNameBuffer;
532 m_fileName = wxFileNameFromPath(fileNameBuffer);
533 m_fileNames.Add(m_fileName);
534 m_dir = wxPathOnly(fileNameBuffer);
535 }
536 }
537 #ifdef __WXDEBUG__
538 else
539 {
540 // common dialog failed - why?
541 if ( errCode != 0 )
542 {
543 // this msg is only for developers so don't translate it
544 wxLogError(wxT("Common dialog failed with error code %0lx."),
545 errCode);
546 }
547 //else: it was just cancelled
548 }
549 #endif // __WXDEBUG__
550
551 return success ? wxID_OK : wxID_CANCEL;
552
553 }
554
555 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)