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