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 OPENFILENAME
* ofn
= reinterpret_cast<OPENFILENAME
*>(lParam
);
92 reinterpret_cast<wxFileDialog
*>(ofn
->lCustData
)
93 ->MSWOnInitDialogHook((WXHWND
)hDlg
);
99 OFNOTIFY
*pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
100 if ( pNotifyCode
->hdr
.code
== CDN_INITDONE
)
102 reinterpret_cast<wxFileDialog
*>(
103 pNotifyCode
->lpOFN
->lCustData
)
104 ->MSWOnInitDone((WXHWND
)hDlg
);
110 // reuse the position used for the dialog the next time by default
112 // NB: at least under Windows 2003 this is useless as after the
113 // first time it's shown the dialog always remembers its size
114 // and position itself and ignores any later SetWindowPos calls
115 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg
)), gs_rectDialog
);
119 // do the default processing
123 // ----------------------------------------------------------------------------
125 // ----------------------------------------------------------------------------
127 wxFileDialog::wxFileDialog(wxWindow
*parent
,
128 const wxString
& message
,
129 const wxString
& defaultDir
,
130 const wxString
& defaultFileName
,
131 const wxString
& wildCard
,
135 const wxString
& name
)
136 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
137 wildCard
, style
, pos
, sz
, name
)
140 // NB: all style checks are done by wxFileDialogBase::Create
142 m_bMovedWindow
= false;
145 // Must set to zero, otherwise the wx routines won't size the window
146 // the second time you call the file dialog, because it thinks it is
147 // already at the requested size.. (when centering)
152 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
157 if ( m_dir
.Last() != _T('\\') )
160 size_t count
= m_fileNames
.GetCount();
161 for ( size_t n
= 0; n
< count
; n
++ )
163 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
164 paths
.Add(m_fileNames
[n
]);
166 paths
.Add(dir
+ m_fileNames
[n
]);
170 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
175 void wxFileDialog::SetPath(const wxString
& path
)
178 wxFileName::SplitPath(path
, &m_dir
, &m_fileName
, &ext
);
180 m_fileName
<< _T('.') << ext
;
183 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
186 *x
= gs_rectDialog
.x
;
188 *y
= gs_rectDialog
.y
;
191 void wxFileDialog::DoGetSize(int *width
, int *height
) const
194 *width
= gs_rectDialog
.width
;
196 *height
= gs_rectDialog
.height
;
199 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
204 // our HWND is only set when we're called from MSWOnInitDone(), test if
206 HWND hwnd
= GetHwnd();
209 // size of the dialog can't be changed because the controls are not
210 // laid out correctly then
211 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
213 else // just remember that we were requested to move the window
215 m_bMovedWindow
= true;
217 // if Centre() had been called before, it shouldn't be taken into
223 void wxFileDialog::DoCentre(int dir
)
226 m_bMovedWindow
= true;
228 // it's unnecessary to do anything else at this stage as we'll redo it in
229 // MSWOnInitDone() anyhow
232 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
234 // note the the dialog is the parent window: hDlg is a child of it when
235 // OFN_EXPLORER is used
236 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
238 // set HWND so that our DoMoveWindow() works correctly
239 SetHWND((WXHWND
)hFileDlg
);
243 // now we have the real dialog size, remember it
245 GetWindowRect(hFileDlg
, &rect
);
246 gs_rectDialog
= wxRectFromRECT(rect
);
248 // and position the window correctly: notice that we must use the base
249 // class version as our own doesn't do anything except setting flags
250 wxFileDialogBase::DoCentre(m_centreDir
);
252 else // need to just move it to the correct place
254 SetPosition(gs_rectDialog
.GetPosition());
257 // we shouldn't destroy this HWND
261 // helper used below in ShowCommFileDialog(): style is used to determine
262 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
263 // "Open file" one; returns true on success or false on failure in which case
264 // err is filled with the CDERR_XXX constant
265 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
267 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
273 // according to MSDN, CommDlgExtendedError() should work under CE as
274 // well but apparently in practice it doesn't (anybody has more
276 *err
= GetLastError();
278 *err
= CommDlgExtendedError();
285 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
286 // know if the OPENFILENAME declared in the currently used headers is a V5 or
287 // V4 (smaller) one so we try to manually extend the struct in case it is the
290 // We don't do this on Windows CE nor under Win64, however, as there are no
291 // compilers with old headers for these architectures
292 #if defined(__WXWINCE__) || defined(__WIN64__)
293 typedef OPENFILENAME wxOPENFILENAME
;
295 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
296 #else // !__WXWINCE__ || __WIN64__
297 #define wxTRY_SMALLER_OPENFILENAME
299 struct wxOPENFILENAME
: public OPENFILENAME
301 // fields added in Windows 2000/XP comdlg32.dll version
307 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
308 // because sizeof(OPENFILENAME) in the headers we use when compiling the
309 // library could be less if _WIN32_WINNT is not >= 0x500
310 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
312 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
313 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
315 // always try the new one first
316 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
317 #endif // __WXWINCE__ || __WIN64__/!...
319 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
322 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
324 #ifdef wxTRY_SMALLER_OPENFILENAME
325 // the system might be too old to support the new version file dialog
326 // boxes, try with the old size
327 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
328 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
330 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
332 success
= DoShowCommFileDialog(of
, style
, &errCode
);
334 if ( success
|| !errCode
)
336 // use this struct size for subsequent dialogs
337 gs_ofStructSize
= of
->lStructSize
;
340 #endif // wxTRY_SMALLER_OPENFILENAME
343 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
344 // use CommDlgExtendedError() there anyhow)
346 errCode
== FNERR_INVALIDFILENAME
&&
347 #endif // !__WXWINCE__
350 // this can happen if the default file name is invalid, try without it
352 of
->lpstrFile
[0] = _T('\0');
353 success
= DoShowCommFileDialog(of
, style
, &errCode
);
358 // common dialog failed - why?
361 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
363 //else: it was just cancelled
371 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
375 CreateExtraControl();
380 int wxFileDialog::ShowModal()
383 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
384 if (!hWnd
&& wxTheApp
->GetTopWindow())
385 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
387 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
388 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
390 *fileNameBuffer
= wxT('\0');
391 *titleBuffer
= wxT('\0');
393 long msw_flags
= OFN_HIDEREADONLY
;
395 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
396 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
398 If the window has been moved the programmer is probably
399 trying to center or position it. Thus we set the callback
400 or hook function so that we can actually adjust the position.
401 Without moving or centering the dlg, it will just stay
402 in the upper left of the frame, it does not center
405 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
407 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
409 msw_flags
|= OFN_ENABLESIZING
;
413 if ( HasFdFlag(wxFD_MULTIPLE
) )
415 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
416 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
419 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
420 // standard dialog does by default (notice that under NT it does it anyhow,
421 // OFN_NOCHANGEDIR or not, see below)
422 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
424 msw_flags
|= OFN_NOCHANGEDIR
;
427 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
429 msw_flags
|= OFN_OVERWRITEPROMPT
;
435 of
.lStructSize
= gs_ofStructSize
;
437 of
.lpstrTitle
= m_message
.wx_str();
438 of
.lpstrFileTitle
= titleBuffer
;
439 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
443 if ( HasExtraControlCreator() )
445 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
447 hgbl
.Init(256, GMEM_ZEROINIT
);
448 GlobalPtrLock
hgblLock(hgbl
);
449 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
451 // Define a dialog box.
453 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
454 lpdt
->cdit
= 0; // Number of controls
458 wxSize extra_size
= GetExtraControlSize();
459 // setting cx doesn't change the width of the dialog
460 lpdt
->cx
= extra_size
.GetWidth();
461 // Dividing by 2 gives expected height on WinXP and Wine.
462 // I don't know why (MW).
463 lpdt
->cy
= extra_size
.GetHeight() / 2;
465 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
466 // class and title, all three set to zeros.
468 of
.hInstance
= (HINSTANCE
)lpdt
;
470 #endif // __WXWINCE__
472 // Convert forward slashes to backslashes (file selector doesn't like
473 // forward slashes) and also squeeze multiple consecutive slashes into one
474 // as it doesn't like two backslashes in a row neither
477 size_t i
, len
= m_dir
.length();
479 for ( i
= 0; i
< len
; i
++ )
481 wxChar ch
= m_dir
[i
];
485 // convert to backslash
491 while ( i
< len
- 1 )
493 wxChar chNext
= m_dir
[i
+ 1];
494 if ( chNext
!= _T('\\') && chNext
!= _T('/') )
497 // ignore the next one, unless it is at the start of a UNC path
511 of
.lpstrInitialDir
= dir
.c_str();
513 of
.Flags
= msw_flags
;
514 of
.lpfnHook
= wxFileDialogHookFunction
;
515 of
.lCustData
= (LPARAM
)this;
517 wxArrayString wildDescriptions
, wildFilters
;
519 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
521 wxASSERT_MSG( items
> 0 , _T("empty wildcard list") );
523 wxString filterBuffer
;
525 for (i
= 0; i
< items
; i
++)
527 filterBuffer
+= wildDescriptions
[i
];
528 filterBuffer
+= wxT("|");
529 filterBuffer
+= wildFilters
[i
];
530 filterBuffer
+= wxT("|");
534 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
535 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
536 filterBuffer
[i
] = wxT('\0');
540 of
.lpstrFilter
= (LPTSTR
)filterBuffer
.wx_str();
541 of
.nFilterIndex
= m_filterIndex
+ 1;
543 //=== Setting defaultFileName >>=========================================
545 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
547 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
548 of
.nMaxFile
= wxMAXPATH
;
550 // we must set the default extension because otherwise Windows would check
551 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
552 // user types "foo" and the default extension is ".bar" we should force it
553 // to check for "foo.bar" existence and not "foo")
554 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
555 if (HasFdFlag(wxFD_SAVE
))
557 const wxChar
* extension
= filterBuffer
.wx_str();
558 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
560 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
561 extension
= extension
+ wxStrlen( extension
) + 1;
563 // use dummy name a to avoid assert in AppendExtension
564 defextBuffer
= AppendExtension(wxT("a"), extension
);
565 if (defextBuffer
.StartsWith(wxT("a.")))
567 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
568 of
.lpstrDefExt
= defextBuffer
.c_str();
572 // store off before the standard windows dialog can possibly change it
573 const wxString cwdOrig
= wxGetCwd();
575 //== Execute FileDialog >>=================================================
577 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
580 // GetOpenFileName will always change the current working directory on
581 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
582 // OFN_NOCHANGEDIR has no effect. If the user did not specify
583 // wxFD_CHANGE_DIR let's restore the current working directory to what it
584 // was before the dialog was shown.
585 if ( msw_flags
& OFN_NOCHANGEDIR
)
587 wxSetWorkingDirectory(cwdOrig
);
592 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
593 #if defined(OFN_EXPLORER)
594 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
596 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
597 #endif // OFN_EXPLORER
600 #if defined(OFN_EXPLORER)
601 m_dir
= fileNameBuffer
;
603 m_fileName
= &fileNameBuffer
[i
];
604 m_fileNames
.Add(m_fileName
);
605 i
+= m_fileName
.length() + 1;
607 while (fileNameBuffer
[i
] != wxT('\0'))
609 m_fileNames
.Add(&fileNameBuffer
[i
]);
610 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
613 wxStringTokenizer
toke(fileNameBuffer
, _T(" \t\r\n"));
614 m_dir
= toke
.GetNextToken();
615 m_fileName
= toke
.GetNextToken();
616 m_fileNames
.Add(m_fileName
);
618 while (toke
.HasMoreTokens())
619 m_fileNames
.Add(toke
.GetNextToken());
620 #endif // OFN_EXPLORER
623 if ( m_dir
.Last() != _T('\\') )
626 m_path
= dir
+ m_fileName
;
627 m_filterIndex
= (int)of
.nFilterIndex
- 1;
631 //=== Adding the correct extension >>=================================
633 m_filterIndex
= (int)of
.nFilterIndex
- 1;
635 if ( !of
.nFileExtension
||
636 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
638 // User has typed a filename without an extension:
639 const wxChar
* extension
= filterBuffer
.wx_str();
640 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
642 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
643 extension
= extension
+ wxStrlen( extension
) + 1;
645 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
646 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
649 m_path
= fileNameBuffer
;
650 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
651 m_fileNames
.Add(m_fileName
);
652 m_dir
= wxPathOnly(fileNameBuffer
);
659 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)