]> git.saurik.com Git - wxWidgets.git/blob - src/msw/filedlg.cpp
fix off by one (or rather "off by border width") bug in ScrollWindow() (part of patch...
[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 long msw_flags = OFN_HIDEREADONLY;
280
281 if ( HasFdFlag(wxFD_FILE_MUST_EXIST) )
282 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
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
289 automatically.
290 */
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 }
298
299 if ( HasFdFlag(wxFD_MULTIPLE) )
300 {
301 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
302 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
303 }
304
305 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
306 // standard dialog does by default (notice that under NT it does it anyhow,
307 // OFN_NOCHANGEDIR or not, see below)
308 if ( !HasFdFlag(wxFD_CHANGE_DIR) )
309 {
310 msw_flags |= OFN_NOCHANGEDIR;
311 }
312
313 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT) )
314 {
315 msw_flags |= OFN_OVERWRITEPROMPT;
316 }
317
318 wxOPENFILENAME of;
319 wxZeroMemory(of);
320
321 of.lStructSize = gs_ofStructSize;
322 of.hwndOwner = hWnd;
323 of.lpstrTitle = m_message.wx_str();
324 of.lpstrFileTitle = titleBuffer;
325 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT;
326
327 // Convert forward slashes to backslashes (file selector doesn't like
328 // forward slashes) and also squeeze multiple consecutive slashes into one
329 // as it doesn't like two backslashes in a row neither
330
331 wxString dir;
332 size_t i, len = m_dir.length();
333 dir.reserve(len);
334 for ( i = 0; i < len; i++ )
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
344
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
352 // ignore the next one, unless it is at the start of a UNC path
353 if (i > 0)
354 i++;
355 else
356 break;
357 }
358 // fall through
359
360 default:
361 // normal char
362 dir += ch;
363 }
364 }
365
366 of.lpstrInitialDir = dir.c_str();
367
368 of.Flags = msw_flags;
369 of.lpfnHook = wxFileDialogHookFunction;
370
371 wxArrayString wildDescriptions, wildFilters;
372
373 size_t items = wxParseCommonDialogsFilter(m_wildCard, wildDescriptions, wildFilters);
374
375 wxASSERT_MSG( items > 0 , _T("empty wildcard list") );
376
377 wxString filterBuffer;
378
379 for (i = 0; i < items ; i++)
380 {
381 filterBuffer += wildDescriptions[i];
382 filterBuffer += wxT("|");
383 filterBuffer += wildFilters[i];
384 filterBuffer += wxT("|");
385 }
386
387 // Replace | with \0
388 for (i = 0; i < filterBuffer.length(); i++ ) {
389 if ( filterBuffer.GetChar(i) == wxT('|') ) {
390 filterBuffer[i] = wxT('\0');
391 }
392 }
393
394 of.lpstrFilter = (LPTSTR)filterBuffer.wx_str();
395 of.nFilterIndex = m_filterIndex + 1;
396
397 //=== Setting defaultFileName >>=========================================
398
399 wxStrncpy(fileNameBuffer, m_fileName, wxMAXPATH-1);
400 fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
401
402 of.lpstrFile = fileNameBuffer; // holds returned filename
403 of.nMaxFile = wxMAXPATH;
404
405 // we must set the default extension because otherwise Windows would check
406 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
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()!
410 if (HasFdFlag(wxFD_SAVE))
411 {
412 const wxChar* extension = filterBuffer.wx_str();
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 {
422 defextBuffer = defextBuffer.Mid(2); // remove "a."
423 of.lpstrDefExt = defextBuffer.c_str();
424 }
425 }
426
427 // store off before the standard windows dialog can possibly change it
428 const wxString cwdOrig = wxGetCwd();
429
430 //== Execute FileDialog >>=================================================
431
432 DWORD errCode;
433 bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
434
435 #ifdef wxTRY_SMALLER_OPENFILENAME
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 )
440 {
441 of.lStructSize = wxOPENFILENAME_V4_SIZE;
442
443 success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
444
445 if ( success || !errCode )
446 {
447 // use this struct size for subsequent dialogs
448 gs_ofStructSize = of.lStructSize;
449 }
450 }
451 #endif // wxTRY_SMALLER_OPENFILENAME
452
453 if ( success )
454 {
455 // GetOpenFileName will always change the current working directory on
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
458 // wxFD_CHANGE_DIR let's restore the current working directory to what it
459 // was before the dialog was shown.
460 if ( msw_flags & OFN_NOCHANGEDIR )
461 {
462 wxSetWorkingDirectory(cwdOrig);
463 }
464
465 m_fileNames.Empty();
466
467 if ( ( HasFdFlag(wxFD_MULTIPLE) ) &&
468 #if defined(OFN_EXPLORER)
469 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
470 #else
471 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') )
472 #endif // OFN_EXPLORER
473 )
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);
480 i += m_fileName.length() + 1;
481
482 while (fileNameBuffer[i] != wxT('\0'))
483 {
484 m_fileNames.Add(&fileNameBuffer[i]);
485 i += wxStrlen(&fileNameBuffer[i]) + 1;
486 }
487 #else
488 wxStringTokenizer toke(fileNameBuffer, _T(" \t\r\n"));
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
501 m_path = dir + m_fileName;
502 m_filterIndex = (int)of.nFilterIndex - 1;
503 }
504 else
505 {
506 //=== Adding the correct extension >>=================================
507
508 m_filterIndex = (int)of.nFilterIndex - 1;
509
510 if ( !of.nFileExtension ||
511 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
512 {
513 // User has typed a filename without an extension:
514 const wxChar* extension = filterBuffer.wx_str();
515 int maxFilter = (int)(of.nFilterIndex*2L) - 1;
516
517 for( int i = 0; i < maxFilter; i++ ) // get extension
518 extension = extension + wxStrlen( extension ) + 1;
519
520 m_fileName = AppendExtension(fileNameBuffer, extension);
521 wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.length(), wxMAXPATH-1));
522 fileNameBuffer[wxMin(m_fileName.length(), wxMAXPATH-1)] = wxT('\0');
523 }
524
525 m_path = fileNameBuffer;
526 m_fileName = wxFileNameFromPath(fileNameBuffer);
527 m_fileNames.Add(m_fileName);
528 m_dir = wxPathOnly(fileNameBuffer);
529 }
530 }
531 #ifdef __WXDEBUG__
532 else
533 {
534 // common dialog failed - why?
535 if ( errCode != 0 )
536 {
537 // this msg is only for developers so don't translate it
538 wxLogError(wxT("Common dialog failed with error code %0lx."),
539 errCode);
540 }
541 //else: it was just cancelled
542 }
543 #endif // __WXDEBUG__
544
545 return success ? wxID_OK : wxID_CANCEL;
546
547 }
548
549 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)