1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/filedlg.cpp
3 // Purpose: wxFileDialog
4 // Author: Julian Smart
7 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
26 #if wxUSE_FILEDLG && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
28 #include "wx/filedlg.h"
31 #include "wx/msw/wrapcdlg.h"
32 #include "wx/msw/missing.h"
34 #include "wx/msgdlg.h"
35 #include "wx/filefn.h"
45 #include "wx/dynlib.h"
46 #include "wx/filename.h"
47 #include "wx/scopeguard.h"
48 #include "wx/tokenzr.h"
49 #include "wx/modalhook.h"
51 // ----------------------------------------------------------------------------
53 // ----------------------------------------------------------------------------
56 # define wxMAXPATH 65534
58 # define wxMAXPATH 1024
61 # define wxMAXFILE 1024
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
69 // standard dialog size for the old Windows systems where the dialog wasn't
71 static wxRect
gs_rectDialog(0, 0, 428, 266);
73 // ============================================================================
75 // ============================================================================
77 IMPLEMENT_CLASS(wxFileDialog
, wxFileDialogBase
)
79 // ----------------------------------------------------------------------------
84 #if wxUSE_DYNLIB_CLASS
86 typedef BOOL (WINAPI
*GetProcessUserModeExceptionPolicy_t
)(LPDWORD
);
87 typedef BOOL (WINAPI
*SetProcessUserModeExceptionPolicy_t
)(DWORD
);
89 GetProcessUserModeExceptionPolicy_t gs_pfnGetProcessUserModeExceptionPolicy
90 = (GetProcessUserModeExceptionPolicy_t
) -1;
92 SetProcessUserModeExceptionPolicy_t gs_pfnSetProcessUserModeExceptionPolicy
93 = (SetProcessUserModeExceptionPolicy_t
) -1;
95 DWORD gs_oldExceptionPolicyFlags
= 0;
97 bool gs_changedPolicy
= false;
99 #endif // #if wxUSE_DYNLIB_CLASS
102 Since Windows 7 by default (callback) exceptions aren't swallowed anymore
103 with native x64 applications. Exceptions can occur in a file dialog when
104 using the hook procedure in combination with third-party utilities.
105 Since Windows 7 SP1 the swallowing of exceptions can be enabled again
106 by using SetProcessUserModeExceptionPolicy.
108 void ChangeExceptionPolicy()
110 #if wxUSE_DYNLIB_CLASS
111 gs_changedPolicy
= false;
113 wxLoadedDLL
dllKernel32(wxT("kernel32.dll"));
115 if ( gs_pfnGetProcessUserModeExceptionPolicy
116 == (GetProcessUserModeExceptionPolicy_t
) -1)
118 wxDL_INIT_FUNC(gs_pfn
, GetProcessUserModeExceptionPolicy
, dllKernel32
);
119 wxDL_INIT_FUNC(gs_pfn
, SetProcessUserModeExceptionPolicy
, dllKernel32
);
122 if ( !gs_pfnGetProcessUserModeExceptionPolicy
123 || !gs_pfnSetProcessUserModeExceptionPolicy
124 || !gs_pfnGetProcessUserModeExceptionPolicy(&gs_oldExceptionPolicyFlags
) )
129 if ( gs_pfnSetProcessUserModeExceptionPolicy(gs_oldExceptionPolicyFlags
130 | 0x1 /* PROCESS_CALLBACK_FILTER_ENABLED */ ) )
132 gs_changedPolicy
= true;
135 #endif // wxUSE_DYNLIB_CLASS
138 void RestoreExceptionPolicy()
140 #if wxUSE_DYNLIB_CLASS
141 if (gs_changedPolicy
)
143 gs_changedPolicy
= false;
144 (void) gs_pfnSetProcessUserModeExceptionPolicy(gs_oldExceptionPolicyFlags
);
146 #endif // wxUSE_DYNLIB_CLASS
149 } // unnamed namespace
151 // ----------------------------------------------------------------------------
152 // hook function for moving the dialog
153 // ----------------------------------------------------------------------------
156 wxFileDialogHookFunction(HWND hDlg
,
158 WPARAM
WXUNUSED(wParam
),
166 OPENFILENAME
* ofn
= reinterpret_cast<OPENFILENAME
*>(lParam
);
167 reinterpret_cast<wxFileDialog
*>(ofn
->lCustData
)
168 ->MSWOnInitDialogHook((WXHWND
)hDlg
);
171 #endif // __WXWINCE__
176 pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
178 dialog
= reinterpret_cast<wxFileDialog
*>(
179 pNotifyCode
->lpOFN
->lCustData
182 switch ( pNotifyCode
->hdr
.code
)
185 dialog
->MSWOnInitDone((WXHWND
)hDlg
);
189 dialog
->MSWOnSelChange((WXHWND
)hDlg
);
196 // reuse the position used for the dialog the next time by default
198 // NB: at least under Windows 2003 this is useless as after the
199 // first time it's shown the dialog always remembers its size
200 // and position itself and ignores any later SetWindowPos calls
201 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg
)), gs_rectDialog
);
205 // do the default processing
209 // ----------------------------------------------------------------------------
211 // ----------------------------------------------------------------------------
213 wxFileDialog::wxFileDialog(wxWindow
*parent
,
214 const wxString
& message
,
215 const wxString
& defaultDir
,
216 const wxString
& defaultFileName
,
217 const wxString
& wildCard
,
221 const wxString
& name
)
222 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
223 wildCard
, style
, pos
, sz
, name
)
226 // NB: all style checks are done by wxFileDialogBase::Create
228 m_bMovedWindow
= false;
231 // Must set to zero, otherwise the wx routines won't size the window
232 // the second time you call the file dialog, because it thinks it is
233 // already at the requested size.. (when centering)
238 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
243 if ( m_dir
.empty() || m_dir
.Last() != wxT('\\') )
246 size_t count
= m_fileNames
.GetCount();
247 for ( size_t n
= 0; n
< count
; n
++ )
249 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
250 paths
.Add(m_fileNames
[n
]);
252 paths
.Add(dir
+ m_fileNames
[n
]);
256 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
261 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
264 *x
= gs_rectDialog
.x
;
266 *y
= gs_rectDialog
.y
;
269 void wxFileDialog::DoGetSize(int *width
, int *height
) const
272 *width
= gs_rectDialog
.width
;
274 *height
= gs_rectDialog
.height
;
277 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
282 // our HWND is only set when we're called from MSWOnInitDone(), test if
284 HWND hwnd
= GetHwnd();
287 // size of the dialog can't be changed because the controls are not
288 // laid out correctly then
289 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
291 else // just remember that we were requested to move the window
293 m_bMovedWindow
= true;
295 // if Centre() had been called before, it shouldn't be taken into
301 void wxFileDialog::DoCentre(int dir
)
304 m_bMovedWindow
= true;
306 // it's unnecessary to do anything else at this stage as we'll redo it in
307 // MSWOnInitDone() anyhow
310 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
312 // note the dialog is the parent window: hDlg is a child of it when
313 // OFN_EXPLORER is used
314 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
316 // set HWND so that our DoMoveWindow() works correctly
317 SetHWND((WXHWND
)hFileDlg
);
321 // now we have the real dialog size, remember it
323 GetWindowRect(hFileDlg
, &rect
);
324 gs_rectDialog
= wxRectFromRECT(rect
);
326 // and position the window correctly: notice that we must use the base
327 // class version as our own doesn't do anything except setting flags
328 wxFileDialogBase::DoCentre(m_centreDir
);
330 else // need to just move it to the correct place
332 SetPosition(gs_rectDialog
.GetPosition());
335 // Call selection change handler so that update handler will be
336 // called once with no selection.
337 MSWOnSelChange(hDlg
);
339 // we shouldn't destroy this HWND
343 void wxFileDialog::MSWOnSelChange(WXHWND hDlg
)
346 LRESULT len
= SendMessage(::GetParent(hDlg
), CDM_GETFILEPATH
,
347 MAX_PATH
, reinterpret_cast<LPARAM
>(buf
));
350 m_currentlySelectedFilename
= buf
;
352 m_currentlySelectedFilename
.clear();
354 if ( m_extraControl
)
355 m_extraControl
->UpdateWindowUI(wxUPDATE_UI_RECURSE
);
358 // helper used below in ShowCommFileDialog(): style is used to determine
359 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
360 // "Open file" one; returns true on success or false on failure in which case
361 // err is filled with the CDERR_XXX constant
362 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
364 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
370 // according to MSDN, CommDlgExtendedError() should work under CE as
371 // well but apparently in practice it doesn't (anybody has more
373 *err
= GetLastError();
375 *err
= CommDlgExtendedError();
382 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
383 // know if the OPENFILENAME declared in the currently used headers is a V5 or
384 // V4 (smaller) one so we try to manually extend the struct in case it is the
387 // We don't do this on Windows CE nor under Win64, however, as there are no
388 // compilers with old headers for these architectures
389 #if defined(__WXWINCE__) || defined(__WIN64__)
390 typedef OPENFILENAME wxOPENFILENAME
;
392 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
393 #else // !__WXWINCE__ || __WIN64__
394 #define wxTRY_SMALLER_OPENFILENAME
396 struct wxOPENFILENAME
: public OPENFILENAME
398 // fields added in Windows 2000/XP comdlg32.dll version
404 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
405 // because sizeof(OPENFILENAME) in the headers we use when compiling the
406 // library could be less if _WIN32_WINNT is not >= 0x500
407 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
409 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
410 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
412 // always try the new one first
413 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
414 #endif // __WXWINCE__ || __WIN64__/!...
416 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
419 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
421 #ifdef wxTRY_SMALLER_OPENFILENAME
422 // the system might be too old to support the new version file dialog
423 // boxes, try with the old size
424 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
425 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
427 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
429 success
= DoShowCommFileDialog(of
, style
, &errCode
);
431 if ( success
|| !errCode
)
433 // use this struct size for subsequent dialogs
434 gs_ofStructSize
= of
->lStructSize
;
437 #endif // wxTRY_SMALLER_OPENFILENAME
440 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
441 // use CommDlgExtendedError() there anyhow)
443 errCode
== FNERR_INVALIDFILENAME
&&
444 #endif // !__WXWINCE__
447 // this can happen if the default file name is invalid, try without it
449 of
->lpstrFile
[0] = wxT('\0');
450 success
= DoShowCommFileDialog(of
, style
, &errCode
);
455 // common dialog failed - why?
458 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
460 //else: it was just cancelled
469 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
473 CreateExtraControl();
477 #endif // __WXWINCE__
479 int wxFileDialog::ShowModal()
481 WX_HOOK_MODAL_DIALOG();
484 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
485 if (!hWnd
&& wxTheApp
->GetTopWindow())
486 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
488 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
489 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
491 *fileNameBuffer
= wxT('\0');
492 *titleBuffer
= wxT('\0');
494 long msw_flags
= OFN_HIDEREADONLY
;
496 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
497 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
499 If the window has been moved the programmer is probably
500 trying to center or position it. Thus we set the callback
501 or hook function so that we can actually adjust the position.
502 Without moving or centering the dlg, it will just stay
503 in the upper left of the frame, it does not center
506 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
508 ChangeExceptionPolicy();
509 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
511 msw_flags
|= OFN_ENABLESIZING
;
515 wxON_BLOCK_EXIT0(RestoreExceptionPolicy
);
517 if ( HasFdFlag(wxFD_MULTIPLE
) )
519 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
520 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
523 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
524 // standard dialog does by default (notice that under NT it does it anyhow,
525 // OFN_NOCHANGEDIR or not, see below)
526 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
528 msw_flags
|= OFN_NOCHANGEDIR
;
531 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
533 msw_flags
|= OFN_OVERWRITEPROMPT
;
539 of
.lStructSize
= gs_ofStructSize
;
541 of
.lpstrTitle
= m_message
.t_str();
542 of
.lpstrFileTitle
= titleBuffer
;
543 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
547 if ( HasExtraControlCreator() )
549 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
551 hgbl
.Init(256, GMEM_ZEROINIT
);
552 GlobalPtrLock
hgblLock(hgbl
);
553 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
555 // Define a dialog box.
557 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
558 lpdt
->cdit
= 0; // Number of controls
562 // convert the size of the extra controls to the dialog units
563 const wxSize extraSize
= GetExtraControlSize();
564 const LONG baseUnits
= ::GetDialogBaseUnits();
565 lpdt
->cx
= ::MulDiv(extraSize
.x
, 4, LOWORD(baseUnits
));
566 lpdt
->cy
= ::MulDiv(extraSize
.y
, 8, HIWORD(baseUnits
));
568 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
569 // class and title, all three set to zeros.
571 of
.hInstance
= (HINSTANCE
)lpdt
;
573 #endif // __WXWINCE__
575 // Convert forward slashes to backslashes (file selector doesn't like
576 // forward slashes) and also squeeze multiple consecutive slashes into one
577 // as it doesn't like two backslashes in a row neither
580 size_t i
, len
= m_dir
.length();
582 for ( i
= 0; i
< len
; i
++ )
584 wxChar ch
= m_dir
[i
];
588 // convert to backslash
594 while ( i
< len
- 1 )
596 wxChar chNext
= m_dir
[i
+ 1];
597 if ( chNext
!= wxT('\\') && chNext
!= wxT('/') )
600 // ignore the next one, unless it is at the start of a UNC path
614 of
.lpstrInitialDir
= dir
.c_str();
616 of
.Flags
= msw_flags
;
617 of
.lpfnHook
= wxFileDialogHookFunction
;
618 of
.lCustData
= (LPARAM
)this;
620 wxArrayString wildDescriptions
, wildFilters
;
622 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
624 wxASSERT_MSG( items
> 0 , wxT("empty wildcard list") );
626 wxString filterBuffer
;
628 for (i
= 0; i
< items
; i
++)
630 filterBuffer
+= wildDescriptions
[i
];
631 filterBuffer
+= wxT("|");
632 filterBuffer
+= wildFilters
[i
];
633 filterBuffer
+= wxT("|");
637 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
638 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
639 filterBuffer
[i
] = wxT('\0');
643 of
.lpstrFilter
= filterBuffer
.t_str();
644 of
.nFilterIndex
= m_filterIndex
+ 1;
646 //=== Setting defaultFileName >>=========================================
648 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
650 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
651 of
.nMaxFile
= wxMAXPATH
;
653 // we must set the default extension because otherwise Windows would check
654 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
655 // user types "foo" and the default extension is ".bar" we should force it
656 // to check for "foo.bar" existence and not "foo")
657 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
658 if (HasFdFlag(wxFD_SAVE
))
660 const wxChar
* extension
= filterBuffer
.t_str();
661 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
663 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
664 extension
= extension
+ wxStrlen( extension
) + 1;
666 // use dummy name a to avoid assert in AppendExtension
667 defextBuffer
= AppendExtension(wxT("a"), extension
);
668 if (defextBuffer
.StartsWith(wxT("a.")))
670 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
671 of
.lpstrDefExt
= defextBuffer
.c_str();
675 // store off before the standard windows dialog can possibly change it
676 const wxString cwdOrig
= wxGetCwd();
678 //== Execute FileDialog >>=================================================
680 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
683 // GetOpenFileName will always change the current working directory on
684 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
685 // OFN_NOCHANGEDIR has no effect. If the user did not specify
686 // wxFD_CHANGE_DIR let's restore the current working directory to what it
687 // was before the dialog was shown.
688 if ( msw_flags
& OFN_NOCHANGEDIR
)
690 wxSetWorkingDirectory(cwdOrig
);
695 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
696 #if defined(OFN_EXPLORER)
697 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
699 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
700 #endif // OFN_EXPLORER
703 #if defined(OFN_EXPLORER)
704 m_dir
= fileNameBuffer
;
706 m_fileName
= &fileNameBuffer
[i
];
707 m_fileNames
.Add(m_fileName
);
708 i
+= m_fileName
.length() + 1;
710 while (fileNameBuffer
[i
] != wxT('\0'))
712 m_fileNames
.Add(&fileNameBuffer
[i
]);
713 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
716 wxStringTokenizer
toke(fileNameBuffer
, wxT(" \t\r\n"));
717 m_dir
= toke
.GetNextToken();
718 m_fileName
= toke
.GetNextToken();
719 m_fileNames
.Add(m_fileName
);
721 while (toke
.HasMoreTokens())
722 m_fileNames
.Add(toke
.GetNextToken());
723 #endif // OFN_EXPLORER
726 if ( m_dir
.Last() != wxT('\\') )
729 m_path
= dir
+ m_fileName
;
730 m_filterIndex
= (int)of
.nFilterIndex
- 1;
734 //=== Adding the correct extension >>=================================
736 m_filterIndex
= (int)of
.nFilterIndex
- 1;
738 if ( !of
.nFileExtension
||
739 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
741 // User has typed a filename without an extension:
742 const wxChar
* extension
= filterBuffer
.t_str();
743 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
745 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
746 extension
= extension
+ wxStrlen( extension
) + 1;
748 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
749 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
752 m_path
= fileNameBuffer
;
753 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
754 m_fileNames
.Add(m_fileName
);
755 m_dir
= wxPathOnly(fileNameBuffer
);
762 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)