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 // ============================================================================
73 // ============================================================================
75 IMPLEMENT_CLASS(wxFileDialog
, wxFileDialogBase
)
77 // ----------------------------------------------------------------------------
78 // hook function for moving the dialog
79 // ----------------------------------------------------------------------------
82 wxFileDialogHookFunction(HWND hDlg
,
84 WPARAM
WXUNUSED(wParam
),
91 OFNOTIFY
*pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
92 if ( pNotifyCode
->hdr
.code
== CDN_INITDONE
)
94 reinterpret_cast<wxFileDialog
*>(
95 pNotifyCode
->lpOFN
->lCustData
)
96 ->MSWOnInitDone((WXHWND
)hDlg
);
102 // reuse the position used for the dialog the next time by default
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
);
111 // do the default processing
115 // ----------------------------------------------------------------------------
117 // ----------------------------------------------------------------------------
119 wxFileDialog::wxFileDialog(wxWindow
*parent
,
120 const wxString
& message
,
121 const wxString
& defaultDir
,
122 const wxString
& defaultFileName
,
123 const wxString
& wildCard
,
127 const wxString
& name
)
128 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
129 wildCard
, style
, pos
, sz
, name
)
132 // NB: all style checks are done by wxFileDialogBase::Create
134 m_bMovedWindow
= false;
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)
144 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
149 if ( m_dir
.Last() != _T('\\') )
152 size_t count
= m_fileNames
.GetCount();
153 for ( size_t n
= 0; n
< count
; n
++ )
155 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
156 paths
.Add(m_fileNames
[n
]);
158 paths
.Add(dir
+ m_fileNames
[n
]);
162 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
167 void wxFileDialog::SetPath(const wxString
& path
)
170 wxFileName::SplitPath(path
, &m_dir
, &m_fileName
, &ext
);
172 m_fileName
<< _T('.') << ext
;
175 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
178 *x
= gs_rectDialog
.x
;
180 *y
= gs_rectDialog
.y
;
183 void wxFileDialog::DoGetSize(int *width
, int *height
) const
186 *width
= gs_rectDialog
.width
;
188 *height
= gs_rectDialog
.height
;
191 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
196 // our HWND is only set when we're called from MSWOnInitDone(), test if
198 HWND hwnd
= GetHwnd();
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
);
205 else // just remember that we were requested to move the window
207 m_bMovedWindow
= true;
209 // if Centre() had been called before, it shouldn't be taken into
215 void wxFileDialog::DoCentre(int dir
)
218 m_bMovedWindow
= true;
220 // it's unnecessary to do anything else at this stage as we'll redo it in
221 // MSWOnInitDone() anyhow
224 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
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
);
230 // set HWND so that our DoMoveWindow() works correctly
231 SetHWND((WXHWND
)hFileDlg
);
235 // now we have the real dialog size, remember it
237 GetWindowRect(hFileDlg
, &rect
);
238 gs_rectDialog
= wxRectFromRECT(rect
);
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
);
244 else // need to just move it to the correct place
246 SetPosition(gs_rectDialog
.GetPosition());
249 // we shouldn't destroy this HWND
253 // helper used below in ShowCommFileDialog(): style is used to determine
254 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
255 // "Open file" one; returns true on success or false on failure in which case
256 // err is filled with the CDERR_XXX constant
257 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
259 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
265 // according to MSDN, CommDlgExtendedError() should work under CE as
266 // well but apparently in practice it doesn't (anybody has more
268 *err
= GetLastError();
270 *err
= CommDlgExtendedError();
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
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
;
287 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
288 #else // !__WXWINCE__ || __WIN64__
289 #define wxTRY_SMALLER_OPENFILENAME
291 struct wxOPENFILENAME
: public OPENFILENAME
293 // fields added in Windows 2000/XP comdlg32.dll version
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;
304 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
305 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
307 // always try the new one first
308 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
309 #endif // __WXWINCE__ || __WIN64__/!...
311 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
314 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
316 #ifdef wxTRY_SMALLER_OPENFILENAME
317 // the system might be too old to support the new version file dialog
318 // boxes, try with the old size
319 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
320 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
322 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
324 success
= DoShowCommFileDialog(of
, style
, &errCode
);
326 if ( success
|| !errCode
)
328 // use this struct size for subsequent dialogs
329 gs_ofStructSize
= of
->lStructSize
;
332 #endif // wxTRY_SMALLER_OPENFILENAME
335 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
336 // use CommDlgExtendedError() there anyhow)
338 errCode
== FNERR_INVALIDFILENAME
&&
339 #endif // !__WXWINCE__
342 // this can happen if the default file name is invalid, try without it
344 of
->lpstrFile
[0] = _T('\0');
345 success
= DoShowCommFileDialog(of
, style
, &errCode
);
350 // common dialog failed - why?
353 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
355 //else: it was just cancelled
363 int wxFileDialog::ShowModal()
366 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
367 if (!hWnd
&& wxTheApp
->GetTopWindow())
368 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
370 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
371 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
373 *fileNameBuffer
= wxT('\0');
374 *titleBuffer
= wxT('\0');
376 long msw_flags
= OFN_HIDEREADONLY
;
378 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
379 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
381 If the window has been moved the programmer is probably
382 trying to center or position it. Thus we set the callback
383 or hook function so that we can actually adjust the position.
384 Without moving or centering the dlg, it will just stay
385 in the upper left of the frame, it does not center
388 if (m_bMovedWindow
) // we need these flags.
390 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
392 msw_flags
|= OFN_ENABLESIZING
;
396 if ( HasFdFlag(wxFD_MULTIPLE
) )
398 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
399 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
402 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
403 // standard dialog does by default (notice that under NT it does it anyhow,
404 // OFN_NOCHANGEDIR or not, see below)
405 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
407 msw_flags
|= OFN_NOCHANGEDIR
;
410 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
412 msw_flags
|= OFN_OVERWRITEPROMPT
;
418 of
.lStructSize
= gs_ofStructSize
;
420 of
.lpstrTitle
= m_message
.wx_str();
421 of
.lpstrFileTitle
= titleBuffer
;
422 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
424 // Convert forward slashes to backslashes (file selector doesn't like
425 // forward slashes) and also squeeze multiple consecutive slashes into one
426 // as it doesn't like two backslashes in a row neither
429 size_t i
, len
= m_dir
.length();
431 for ( i
= 0; i
< len
; i
++ )
433 wxChar ch
= m_dir
[i
];
437 // convert to backslash
443 while ( i
< len
- 1 )
445 wxChar chNext
= m_dir
[i
+ 1];
446 if ( chNext
!= _T('\\') && chNext
!= _T('/') )
449 // ignore the next one, unless it is at the start of a UNC path
463 of
.lpstrInitialDir
= dir
.c_str();
465 of
.Flags
= msw_flags
;
466 of
.lpfnHook
= wxFileDialogHookFunction
;
467 of
.lCustData
= (LPARAM
)this;
469 wxArrayString wildDescriptions
, wildFilters
;
471 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
473 wxASSERT_MSG( items
> 0 , _T("empty wildcard list") );
475 wxString filterBuffer
;
477 for (i
= 0; i
< items
; i
++)
479 filterBuffer
+= wildDescriptions
[i
];
480 filterBuffer
+= wxT("|");
481 filterBuffer
+= wildFilters
[i
];
482 filterBuffer
+= wxT("|");
486 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
487 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
488 filterBuffer
[i
] = wxT('\0');
492 of
.lpstrFilter
= (LPTSTR
)filterBuffer
.wx_str();
493 of
.nFilterIndex
= m_filterIndex
+ 1;
495 //=== Setting defaultFileName >>=========================================
497 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
499 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
500 of
.nMaxFile
= wxMAXPATH
;
502 // we must set the default extension because otherwise Windows would check
503 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
504 // user types "foo" and the default extension is ".bar" we should force it
505 // to check for "foo.bar" existence and not "foo")
506 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
507 if (HasFdFlag(wxFD_SAVE
))
509 const wxChar
* extension
= filterBuffer
.wx_str();
510 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
512 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
513 extension
= extension
+ wxStrlen( extension
) + 1;
515 // use dummy name a to avoid assert in AppendExtension
516 defextBuffer
= AppendExtension(wxT("a"), extension
);
517 if (defextBuffer
.StartsWith(wxT("a.")))
519 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
520 of
.lpstrDefExt
= defextBuffer
.c_str();
524 // store off before the standard windows dialog can possibly change it
525 const wxString cwdOrig
= wxGetCwd();
527 //== Execute FileDialog >>=================================================
529 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
532 // GetOpenFileName will always change the current working directory on
533 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
534 // OFN_NOCHANGEDIR has no effect. If the user did not specify
535 // wxFD_CHANGE_DIR let's restore the current working directory to what it
536 // was before the dialog was shown.
537 if ( msw_flags
& OFN_NOCHANGEDIR
)
539 wxSetWorkingDirectory(cwdOrig
);
544 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
545 #if defined(OFN_EXPLORER)
546 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
548 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
549 #endif // OFN_EXPLORER
552 #if defined(OFN_EXPLORER)
553 m_dir
= fileNameBuffer
;
555 m_fileName
= &fileNameBuffer
[i
];
556 m_fileNames
.Add(m_fileName
);
557 i
+= m_fileName
.length() + 1;
559 while (fileNameBuffer
[i
] != wxT('\0'))
561 m_fileNames
.Add(&fileNameBuffer
[i
]);
562 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
565 wxStringTokenizer
toke(fileNameBuffer
, _T(" \t\r\n"));
566 m_dir
= toke
.GetNextToken();
567 m_fileName
= toke
.GetNextToken();
568 m_fileNames
.Add(m_fileName
);
570 while (toke
.HasMoreTokens())
571 m_fileNames
.Add(toke
.GetNextToken());
572 #endif // OFN_EXPLORER
575 if ( m_dir
.Last() != _T('\\') )
578 m_path
= dir
+ m_fileName
;
579 m_filterIndex
= (int)of
.nFilterIndex
- 1;
583 //=== Adding the correct extension >>=================================
585 m_filterIndex
= (int)of
.nFilterIndex
- 1;
587 if ( !of
.nFileExtension
||
588 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
590 // User has typed a filename without an extension:
591 const wxChar
* extension
= filterBuffer
.wx_str();
592 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
594 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
595 extension
= extension
+ wxStrlen( extension
) + 1;
597 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
598 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
601 m_path
= fileNameBuffer
;
602 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
603 m_fileNames
.Add(m_fileName
);
604 m_dir
= wxPathOnly(fileNameBuffer
);
611 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)