1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/filedlg.cpp
3 // Purpose: wxFileDialog
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
27 #if wxUSE_FILEDLG && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
29 #include "wx/filedlg.h"
32 #include "wx/msw/wrapcdlg.h"
33 #include "wx/msw/missing.h"
35 #include "wx/msgdlg.h"
36 #include "wx/filefn.h"
46 #include "wx/filename.h"
47 #include "wx/tokenzr.h"
49 // ----------------------------------------------------------------------------
51 // ----------------------------------------------------------------------------
54 # define wxMAXPATH 65534
56 # define wxMAXPATH 1024
59 # define wxMAXFILE 1024
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
67 // standard dialog size for the old Windows systems where the dialog wasn't
69 static wxRect
gs_rectDialog(0, 0, 428, 266);
71 // we have no way to retrieve the dialog size before it is shown so calling
72 // Centre() before ShowModal() doesn't work correctly (and we can't do it
73 // after), hence we set a special flag and recenter the dialog when it's about
75 static bool gs_centerDialog
= false;
77 // ============================================================================
79 // ============================================================================
81 IMPLEMENT_CLASS(wxFileDialog
, wxFileDialogBase
)
83 // ----------------------------------------------------------------------------
84 // hook function for moving the dialog
85 // ----------------------------------------------------------------------------
88 wxFileDialogHookFunction(HWND hDlg
,
90 WPARAM
WXUNUSED(wParam
),
97 OFNOTIFY
*pNotifyCode
= wx_reinterpret_cast(OFNOTIFY
*, lParam
);
98 if ( pNotifyCode
->hdr
.code
== CDN_INITDONE
)
100 wx_reinterpret_cast(wxFileDialog
*,
101 pNotifyCode
->lpOFN
->lCustData
)
102 ->MSWOnInitDone((WXHWND
)hDlg
);
108 // reuse the position used for the dialog the next time by default
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
);
117 // do the default processing
121 // ----------------------------------------------------------------------------
123 // ----------------------------------------------------------------------------
125 wxFileDialog::wxFileDialog(wxWindow
*parent
,
126 const wxString
& message
,
127 const wxString
& defaultDir
,
128 const wxString
& defaultFileName
,
129 const wxString
& wildCard
,
133 const wxString
& name
)
134 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
135 wildCard
, style
, pos
, sz
, name
)
138 // NB: all style checks are done by wxFileDialogBase::Create
140 m_bMovedWindow
= false;
143 // Must set to zero, otherwise the wx routines won't size the window
144 // the second time you call the file dialog, because it thinks it is
145 // already at the requested size.. (when centering)
150 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
155 if ( m_dir
.Last() != _T('\\') )
158 size_t count
= m_fileNames
.GetCount();
159 for ( size_t n
= 0; n
< count
; n
++ )
161 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
162 paths
.Add(m_fileNames
[n
]);
164 paths
.Add(dir
+ m_fileNames
[n
]);
168 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
173 void wxFileDialog::SetPath(const wxString
& path
)
176 wxSplitPath(path
, &m_dir
, &m_fileName
, &ext
);
178 m_fileName
<< _T('.') << ext
;
181 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
184 *x
= gs_rectDialog
.x
;
186 *y
= gs_rectDialog
.y
;
189 void wxFileDialog::DoGetSize(int *width
, int *height
) const
192 *width
= gs_rectDialog
.width
;
194 *height
= gs_rectDialog
.height
;
197 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
202 // our HWND is only set when we're called from MSWOnInitDone(), test if
204 HWND hwnd
= GetHwnd();
207 // size of the dialog can't be changed because the controls are not
208 // laid out correctly then
209 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
211 else // just remember that we were requested to move the window
213 m_bMovedWindow
= true;
215 // if Centre() had been called before, it shouldn't be taken into
221 void wxFileDialog::DoCentre(int dir
)
224 m_bMovedWindow
= true;
226 // it's unnecessary to do anything else at this stage as we'll redo it in
227 // MSWOnInitDone() anyhow
230 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
232 // note the the dialog is the parent window: hDlg is a child of it when
233 // OFN_EXPLORER is used
234 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
236 // set HWND so that our DoMoveWindow() works correctly
237 SetHWND((WXHWND
)hFileDlg
);
241 // now we have the real dialog size, remember it
243 GetWindowRect(hFileDlg
, &rect
);
244 gs_rectDialog
= wxRectFromRECT(rect
);
246 // and position the window correctly: notice that we must use the base
247 // class version as our own doesn't do anything except setting flags
248 wxFileDialogBase::DoCentre(m_centreDir
);
250 else // need to just move it to the correct place
252 SetPosition(gs_rectDialog
.GetPosition());
255 // we shouldn't destroy this HWND
259 // helper used below in ShowModal(): style is used to determine whether to show
260 // the "Save file" dialog (if it contains wxFD_SAVE bit) or "Open file" one;
261 // returns true on success or false on failure in which case err is filled with
262 // the CDERR_XXX constant
263 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
265 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
271 // according to MSDN, CommDlgExtendedError() should work under CE as
272 // well but apparently in practice it doesn't (anybody has more
274 *err
= GetLastError();
276 *err
= CommDlgExtendedError();
283 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
284 // know if the OPENFILENAME declared in the currently used headers is a V5 or
285 // V4 (smaller) one so we try to manually extend the struct in case it is the
288 // We don't do this on Windows CE nor under Win64, however, as there are no
289 // compilers with old headers for these architectures
290 #if defined(__WXWINCE__) || defined(__WIN64__)
291 typedef OPENFILENAME wxOPENFILENAME
;
293 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
294 #else // !__WXWINCE__ || __WIN64__
295 #define wxTRY_SMALLER_OPENFILENAME
297 struct wxOPENFILENAME
: public OPENFILENAME
299 // fields added in Windows 2000/XP comdlg32.dll version
305 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
306 // because sizeof(OPENFILENAME) in the headers we use when compiling the
307 // library could be less if _WIN32_WINNT is not >= 0x500
308 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
310 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
311 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
313 // always try the new one first
314 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
315 #endif // __WXWINCE__ || __WIN64__/!...
317 int wxFileDialog::ShowModal()
320 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
321 if (!hWnd
&& wxTheApp
->GetTopWindow())
322 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
324 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
325 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
327 *fileNameBuffer
= wxT('\0');
328 *titleBuffer
= wxT('\0');
330 long msw_flags
= OFN_HIDEREADONLY
;
332 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
333 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
335 If the window has been moved the programmer is probably
336 trying to center or position it. Thus we set the callback
337 or hook function so that we can actually adjust the position.
338 Without moving or centering the dlg, it will just stay
339 in the upper left of the frame, it does not center
342 if (m_bMovedWindow
) // we need these flags.
344 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
346 msw_flags
|= OFN_ENABLESIZING
;
350 if ( HasFdFlag(wxFD_MULTIPLE
) )
352 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
353 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
356 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
357 // standard dialog does by default (notice that under NT it does it anyhow,
358 // OFN_NOCHANGEDIR or not, see below)
359 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
361 msw_flags
|= OFN_NOCHANGEDIR
;
364 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
366 msw_flags
|= OFN_OVERWRITEPROMPT
;
372 of
.lStructSize
= gs_ofStructSize
;
374 of
.lpstrTitle
= m_message
.wx_str();
375 of
.lpstrFileTitle
= titleBuffer
;
376 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
378 // Convert forward slashes to backslashes (file selector doesn't like
379 // forward slashes) and also squeeze multiple consecutive slashes into one
380 // as it doesn't like two backslashes in a row neither
383 size_t i
, len
= m_dir
.length();
385 for ( i
= 0; i
< len
; i
++ )
387 wxChar ch
= m_dir
[i
];
391 // convert to backslash
397 while ( i
< len
- 1 )
399 wxChar chNext
= m_dir
[i
+ 1];
400 if ( chNext
!= _T('\\') && chNext
!= _T('/') )
403 // ignore the next one, unless it is at the start of a UNC path
417 of
.lpstrInitialDir
= dir
.c_str();
419 of
.Flags
= msw_flags
;
420 of
.lpfnHook
= wxFileDialogHookFunction
;
421 of
.lCustData
= (LPARAM
)this;
423 wxArrayString wildDescriptions
, wildFilters
;
425 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
427 wxASSERT_MSG( items
> 0 , _T("empty wildcard list") );
429 wxString filterBuffer
;
431 for (i
= 0; i
< items
; i
++)
433 filterBuffer
+= wildDescriptions
[i
];
434 filterBuffer
+= wxT("|");
435 filterBuffer
+= wildFilters
[i
];
436 filterBuffer
+= wxT("|");
440 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
441 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
442 filterBuffer
[i
] = wxT('\0');
446 of
.lpstrFilter
= (LPTSTR
)filterBuffer
.wx_str();
447 of
.nFilterIndex
= m_filterIndex
+ 1;
449 //=== Setting defaultFileName >>=========================================
451 wxStrncpy(fileNameBuffer
, m_fileName
, wxMAXPATH
-1);
452 fileNameBuffer
[ wxMAXPATH
-1 ] = wxT('\0');
454 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
455 of
.nMaxFile
= wxMAXPATH
;
457 // we must set the default extension because otherwise Windows would check
458 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
459 // user types "foo" and the default extension is ".bar" we should force it
460 // to check for "foo.bar" existence and not "foo")
461 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
462 if (HasFdFlag(wxFD_SAVE
))
464 const wxChar
* extension
= filterBuffer
.wx_str();
465 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
467 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
468 extension
= extension
+ wxStrlen( extension
) + 1;
470 // use dummy name a to avoid assert in AppendExtension
471 defextBuffer
= AppendExtension(wxT("a"), extension
);
472 if (defextBuffer
.StartsWith(wxT("a.")))
474 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
475 of
.lpstrDefExt
= defextBuffer
.c_str();
479 // store off before the standard windows dialog can possibly change it
480 const wxString cwdOrig
= wxGetCwd();
482 //== Execute FileDialog >>=================================================
485 bool success
= DoShowCommFileDialog(&of
, m_windowStyle
, &errCode
);
487 #ifdef wxTRY_SMALLER_OPENFILENAME
488 // the system might be too old to support the new version file dialog
489 // boxes, try with the old size
490 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
491 of
.lStructSize
!= wxOPENFILENAME_V4_SIZE
)
493 of
.lStructSize
= wxOPENFILENAME_V4_SIZE
;
495 success
= DoShowCommFileDialog(&of
, m_windowStyle
, &errCode
);
497 if ( success
|| !errCode
)
499 // use this struct size for subsequent dialogs
500 gs_ofStructSize
= of
.lStructSize
;
503 #endif // wxTRY_SMALLER_OPENFILENAME
507 // GetOpenFileName will always change the current working directory on
508 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
509 // OFN_NOCHANGEDIR has no effect. If the user did not specify
510 // wxFD_CHANGE_DIR let's restore the current working directory to what it
511 // was before the dialog was shown.
512 if ( msw_flags
& OFN_NOCHANGEDIR
)
514 wxSetWorkingDirectory(cwdOrig
);
519 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
520 #if defined(OFN_EXPLORER)
521 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
523 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
524 #endif // OFN_EXPLORER
527 #if defined(OFN_EXPLORER)
528 m_dir
= fileNameBuffer
;
530 m_fileName
= &fileNameBuffer
[i
];
531 m_fileNames
.Add(m_fileName
);
532 i
+= m_fileName
.length() + 1;
534 while (fileNameBuffer
[i
] != wxT('\0'))
536 m_fileNames
.Add(&fileNameBuffer
[i
]);
537 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
540 wxStringTokenizer
toke(fileNameBuffer
, _T(" \t\r\n"));
541 m_dir
= toke
.GetNextToken();
542 m_fileName
= toke
.GetNextToken();
543 m_fileNames
.Add(m_fileName
);
545 while (toke
.HasMoreTokens())
546 m_fileNames
.Add(toke
.GetNextToken());
547 #endif // OFN_EXPLORER
550 if ( m_dir
.Last() != _T('\\') )
553 m_path
= dir
+ m_fileName
;
554 m_filterIndex
= (int)of
.nFilterIndex
- 1;
558 //=== Adding the correct extension >>=================================
560 m_filterIndex
= (int)of
.nFilterIndex
- 1;
562 if ( !of
.nFileExtension
||
563 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
565 // User has typed a filename without an extension:
566 const wxChar
* extension
= filterBuffer
.wx_str();
567 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
569 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
570 extension
= extension
+ wxStrlen( extension
) + 1;
572 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
573 wxStrncpy(fileNameBuffer
, m_fileName
.c_str(), wxMin(m_fileName
.length(), wxMAXPATH
-1));
574 fileNameBuffer
[wxMin(m_fileName
.length(), wxMAXPATH
-1)] = wxT('\0');
577 m_path
= fileNameBuffer
;
578 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
579 m_fileNames
.Add(m_fileName
);
580 m_dir
= wxPathOnly(fileNameBuffer
);
586 // common dialog failed - why?
589 // this msg is only for developers so don't translate it
590 wxLogError(wxT("Common dialog failed with error code %0lx."),
593 //else: it was just cancelled
595 #endif // __WXDEBUG__
597 return success
? wxID_OK
: wxID_CANCEL
;
601 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)