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/dynlib.h"
47 #include "wx/filename.h"
48 #include "wx/scopeguard.h"
49 #include "wx/tokenzr.h"
50 #include "wx/modalhook.h"
52 // ----------------------------------------------------------------------------
54 // ----------------------------------------------------------------------------
57 # define wxMAXPATH 65534
59 # define wxMAXPATH 1024
62 # define wxMAXFILE 1024
66 // ----------------------------------------------------------------------------
68 // ----------------------------------------------------------------------------
70 // standard dialog size for the old Windows systems where the dialog wasn't
72 static wxRect
gs_rectDialog(0, 0, 428, 266);
74 // ============================================================================
76 // ============================================================================
78 IMPLEMENT_CLASS(wxFileDialog
, wxFileDialogBase
)
80 // ----------------------------------------------------------------------------
85 #if wxUSE_DYNLIB_CLASS
87 typedef BOOL (WINAPI
*GetProcessUserModeExceptionPolicy_t
)(LPDWORD
);
88 typedef BOOL (WINAPI
*SetProcessUserModeExceptionPolicy_t
)(DWORD
);
90 GetProcessUserModeExceptionPolicy_t gs_pfnGetProcessUserModeExceptionPolicy
91 = (GetProcessUserModeExceptionPolicy_t
) -1;
93 SetProcessUserModeExceptionPolicy_t gs_pfnSetProcessUserModeExceptionPolicy
94 = (SetProcessUserModeExceptionPolicy_t
) -1;
96 DWORD gs_oldExceptionPolicyFlags
= 0;
98 bool gs_changedPolicy
= false;
100 #endif // #if wxUSE_DYNLIB_CLASS
103 Since Windows 7 by default (callback) exceptions aren't swallowed anymore
104 with native x64 applications. Exceptions can occur in a file dialog when
105 using the hook procedure in combination with third-party utilities.
106 Since Windows 7 SP1 the swallowing of exceptions can be enabled again
107 by using SetProcessUserModeExceptionPolicy.
109 void ChangeExceptionPolicy()
111 #if wxUSE_DYNLIB_CLASS
112 gs_changedPolicy
= false;
114 wxLoadedDLL
dllKernel32(wxT("kernel32.dll"));
116 if ( gs_pfnGetProcessUserModeExceptionPolicy
117 == (GetProcessUserModeExceptionPolicy_t
) -1)
119 wxDL_INIT_FUNC(gs_pfn
, GetProcessUserModeExceptionPolicy
, dllKernel32
);
120 wxDL_INIT_FUNC(gs_pfn
, SetProcessUserModeExceptionPolicy
, dllKernel32
);
123 if ( !gs_pfnGetProcessUserModeExceptionPolicy
124 || !gs_pfnSetProcessUserModeExceptionPolicy
125 || !gs_pfnGetProcessUserModeExceptionPolicy(&gs_oldExceptionPolicyFlags
) )
130 if ( gs_pfnSetProcessUserModeExceptionPolicy(gs_oldExceptionPolicyFlags
131 | 0x1 /* PROCESS_CALLBACK_FILTER_ENABLED */ ) )
133 gs_changedPolicy
= true;
136 #endif // wxUSE_DYNLIB_CLASS
139 void RestoreExceptionPolicy()
141 #if wxUSE_DYNLIB_CLASS
142 if (gs_changedPolicy
)
144 gs_changedPolicy
= false;
145 (void) gs_pfnSetProcessUserModeExceptionPolicy(gs_oldExceptionPolicyFlags
);
147 #endif // wxUSE_DYNLIB_CLASS
150 } // unnamed namespace
152 // ----------------------------------------------------------------------------
153 // hook function for moving the dialog
154 // ----------------------------------------------------------------------------
157 wxFileDialogHookFunction(HWND hDlg
,
159 WPARAM
WXUNUSED(wParam
),
167 OPENFILENAME
* ofn
= reinterpret_cast<OPENFILENAME
*>(lParam
);
168 reinterpret_cast<wxFileDialog
*>(ofn
->lCustData
)
169 ->MSWOnInitDialogHook((WXHWND
)hDlg
);
172 #endif // __WXWINCE__
177 pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
179 dialog
= reinterpret_cast<wxFileDialog
*>(
180 pNotifyCode
->lpOFN
->lCustData
183 switch ( pNotifyCode
->hdr
.code
)
186 dialog
->MSWOnInitDone((WXHWND
)hDlg
);
190 dialog
->MSWOnSelChange((WXHWND
)hDlg
);
197 // reuse the position used for the dialog the next time by default
199 // NB: at least under Windows 2003 this is useless as after the
200 // first time it's shown the dialog always remembers its size
201 // and position itself and ignores any later SetWindowPos calls
202 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg
)), gs_rectDialog
);
206 // do the default processing
210 // ----------------------------------------------------------------------------
212 // ----------------------------------------------------------------------------
214 wxFileDialog::wxFileDialog(wxWindow
*parent
,
215 const wxString
& message
,
216 const wxString
& defaultDir
,
217 const wxString
& defaultFileName
,
218 const wxString
& wildCard
,
222 const wxString
& name
)
223 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
224 wildCard
, style
, pos
, sz
, name
)
227 // NB: all style checks are done by wxFileDialogBase::Create
229 m_bMovedWindow
= false;
232 // Must set to zero, otherwise the wx routines won't size the window
233 // the second time you call the file dialog, because it thinks it is
234 // already at the requested size.. (when centering)
239 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
244 if ( m_dir
.empty() || m_dir
.Last() != wxT('\\') )
247 size_t count
= m_fileNames
.GetCount();
248 for ( size_t n
= 0; n
< count
; n
++ )
250 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
251 paths
.Add(m_fileNames
[n
]);
253 paths
.Add(dir
+ m_fileNames
[n
]);
257 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
262 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
265 *x
= gs_rectDialog
.x
;
267 *y
= gs_rectDialog
.y
;
270 void wxFileDialog::DoGetSize(int *width
, int *height
) const
273 *width
= gs_rectDialog
.width
;
275 *height
= gs_rectDialog
.height
;
278 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
283 // our HWND is only set when we're called from MSWOnInitDone(), test if
285 HWND hwnd
= GetHwnd();
288 // size of the dialog can't be changed because the controls are not
289 // laid out correctly then
290 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
292 else // just remember that we were requested to move the window
294 m_bMovedWindow
= true;
296 // if Centre() had been called before, it shouldn't be taken into
302 void wxFileDialog::DoCentre(int dir
)
305 m_bMovedWindow
= true;
307 // it's unnecessary to do anything else at this stage as we'll redo it in
308 // MSWOnInitDone() anyhow
311 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
313 // note the dialog is the parent window: hDlg is a child of it when
314 // OFN_EXPLORER is used
315 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
317 // set HWND so that our DoMoveWindow() works correctly
318 SetHWND((WXHWND
)hFileDlg
);
322 // now we have the real dialog size, remember it
324 GetWindowRect(hFileDlg
, &rect
);
325 gs_rectDialog
= wxRectFromRECT(rect
);
327 // and position the window correctly: notice that we must use the base
328 // class version as our own doesn't do anything except setting flags
329 wxFileDialogBase::DoCentre(m_centreDir
);
331 else // need to just move it to the correct place
333 SetPosition(gs_rectDialog
.GetPosition());
336 // Call selection change handler so that update handler will be
337 // called once with no selection.
338 MSWOnSelChange(hDlg
);
340 // we shouldn't destroy this HWND
344 void wxFileDialog::MSWOnSelChange(WXHWND hDlg
)
347 LRESULT len
= SendMessage(::GetParent(hDlg
), CDM_GETFILEPATH
,
348 MAX_PATH
, reinterpret_cast<LPARAM
>(buf
));
351 m_currentlySelectedFilename
= buf
;
353 m_currentlySelectedFilename
.clear();
355 if ( m_extraControl
)
356 m_extraControl
->UpdateWindowUI(wxUPDATE_UI_RECURSE
);
359 // helper used below in ShowCommFileDialog(): style is used to determine
360 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
361 // "Open file" one; returns true on success or false on failure in which case
362 // err is filled with the CDERR_XXX constant
363 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
365 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
371 // according to MSDN, CommDlgExtendedError() should work under CE as
372 // well but apparently in practice it doesn't (anybody has more
374 *err
= GetLastError();
376 *err
= CommDlgExtendedError();
383 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
384 // know if the OPENFILENAME declared in the currently used headers is a V5 or
385 // V4 (smaller) one so we try to manually extend the struct in case it is the
388 // We don't do this on Windows CE nor under Win64, however, as there are no
389 // compilers with old headers for these architectures
390 #if defined(__WXWINCE__) || defined(__WIN64__)
391 typedef OPENFILENAME wxOPENFILENAME
;
393 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
394 #else // !__WXWINCE__ || __WIN64__
395 #define wxTRY_SMALLER_OPENFILENAME
397 struct wxOPENFILENAME
: public OPENFILENAME
399 // fields added in Windows 2000/XP comdlg32.dll version
405 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
406 // because sizeof(OPENFILENAME) in the headers we use when compiling the
407 // library could be less if _WIN32_WINNT is not >= 0x500
408 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
410 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
411 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
413 // always try the new one first
414 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
415 #endif // __WXWINCE__ || __WIN64__/!...
417 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
420 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
422 #ifdef wxTRY_SMALLER_OPENFILENAME
423 // the system might be too old to support the new version file dialog
424 // boxes, try with the old size
425 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
426 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
428 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
430 success
= DoShowCommFileDialog(of
, style
, &errCode
);
432 if ( success
|| !errCode
)
434 // use this struct size for subsequent dialogs
435 gs_ofStructSize
= of
->lStructSize
;
438 #endif // wxTRY_SMALLER_OPENFILENAME
441 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
442 // use CommDlgExtendedError() there anyhow)
444 errCode
== FNERR_INVALIDFILENAME
&&
445 #endif // !__WXWINCE__
448 // this can happen if the default file name is invalid, try without it
450 of
->lpstrFile
[0] = wxT('\0');
451 success
= DoShowCommFileDialog(of
, style
, &errCode
);
456 // common dialog failed - why?
459 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
461 //else: it was just cancelled
470 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
474 CreateExtraControl();
478 #endif // __WXWINCE__
480 int wxFileDialog::ShowModal()
482 WX_HOOK_MODAL_DIALOG();
485 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
486 if (!hWnd
&& wxTheApp
->GetTopWindow())
487 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
489 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
490 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
492 *fileNameBuffer
= wxT('\0');
493 *titleBuffer
= wxT('\0');
495 long msw_flags
= OFN_HIDEREADONLY
;
497 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
498 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
500 If the window has been moved the programmer is probably
501 trying to center or position it. Thus we set the callback
502 or hook function so that we can actually adjust the position.
503 Without moving or centering the dlg, it will just stay
504 in the upper left of the frame, it does not center
507 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
509 ChangeExceptionPolicy();
510 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
512 msw_flags
|= OFN_ENABLESIZING
;
516 wxON_BLOCK_EXIT0(RestoreExceptionPolicy
);
518 if ( HasFdFlag(wxFD_MULTIPLE
) )
520 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
521 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
524 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
525 // standard dialog does by default (notice that under NT it does it anyhow,
526 // OFN_NOCHANGEDIR or not, see below)
527 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
529 msw_flags
|= OFN_NOCHANGEDIR
;
532 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
534 msw_flags
|= OFN_OVERWRITEPROMPT
;
540 of
.lStructSize
= gs_ofStructSize
;
542 of
.lpstrTitle
= m_message
.t_str();
543 of
.lpstrFileTitle
= titleBuffer
;
544 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
548 if ( HasExtraControlCreator() )
550 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
552 hgbl
.Init(256, GMEM_ZEROINIT
);
553 GlobalPtrLock
hgblLock(hgbl
);
554 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
556 // Define a dialog box.
558 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
559 lpdt
->cdit
= 0; // Number of controls
563 // convert the size of the extra controls to the dialog units
564 const wxSize extraSize
= GetExtraControlSize();
565 const LONG baseUnits
= ::GetDialogBaseUnits();
566 lpdt
->cx
= ::MulDiv(extraSize
.x
, 4, LOWORD(baseUnits
));
567 lpdt
->cy
= ::MulDiv(extraSize
.y
, 8, HIWORD(baseUnits
));
569 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
570 // class and title, all three set to zeros.
572 of
.hInstance
= (HINSTANCE
)lpdt
;
574 #endif // __WXWINCE__
576 // Convert forward slashes to backslashes (file selector doesn't like
577 // forward slashes) and also squeeze multiple consecutive slashes into one
578 // as it doesn't like two backslashes in a row neither
581 size_t i
, len
= m_dir
.length();
583 for ( i
= 0; i
< len
; i
++ )
585 wxChar ch
= m_dir
[i
];
589 // convert to backslash
595 while ( i
< len
- 1 )
597 wxChar chNext
= m_dir
[i
+ 1];
598 if ( chNext
!= wxT('\\') && chNext
!= wxT('/') )
601 // ignore the next one, unless it is at the start of a UNC path
615 of
.lpstrInitialDir
= dir
.c_str();
617 of
.Flags
= msw_flags
;
618 of
.lpfnHook
= wxFileDialogHookFunction
;
619 of
.lCustData
= (LPARAM
)this;
621 wxArrayString wildDescriptions
, wildFilters
;
623 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
625 wxASSERT_MSG( items
> 0 , wxT("empty wildcard list") );
627 wxString filterBuffer
;
629 for (i
= 0; i
< items
; i
++)
631 filterBuffer
+= wildDescriptions
[i
];
632 filterBuffer
+= wxT("|");
633 filterBuffer
+= wildFilters
[i
];
634 filterBuffer
+= wxT("|");
638 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
639 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
640 filterBuffer
[i
] = wxT('\0');
644 of
.lpstrFilter
= filterBuffer
.t_str();
645 of
.nFilterIndex
= m_filterIndex
+ 1;
647 //=== Setting defaultFileName >>=========================================
649 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
651 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
652 of
.nMaxFile
= wxMAXPATH
;
654 // we must set the default extension because otherwise Windows would check
655 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
656 // user types "foo" and the default extension is ".bar" we should force it
657 // to check for "foo.bar" existence and not "foo")
658 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
659 if (HasFdFlag(wxFD_SAVE
))
661 const wxChar
* extension
= filterBuffer
.t_str();
662 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
664 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
665 extension
= extension
+ wxStrlen( extension
) + 1;
667 // use dummy name a to avoid assert in AppendExtension
668 defextBuffer
= AppendExtension(wxT("a"), extension
);
669 if (defextBuffer
.StartsWith(wxT("a.")))
671 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
672 of
.lpstrDefExt
= defextBuffer
.c_str();
676 // store off before the standard windows dialog can possibly change it
677 const wxString cwdOrig
= wxGetCwd();
679 //== Execute FileDialog >>=================================================
681 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
684 // GetOpenFileName will always change the current working directory on
685 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
686 // OFN_NOCHANGEDIR has no effect. If the user did not specify
687 // wxFD_CHANGE_DIR let's restore the current working directory to what it
688 // was before the dialog was shown.
689 if ( msw_flags
& OFN_NOCHANGEDIR
)
691 wxSetWorkingDirectory(cwdOrig
);
696 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
697 #if defined(OFN_EXPLORER)
698 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
700 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
701 #endif // OFN_EXPLORER
704 #if defined(OFN_EXPLORER)
705 m_dir
= fileNameBuffer
;
707 m_fileName
= &fileNameBuffer
[i
];
708 m_fileNames
.Add(m_fileName
);
709 i
+= m_fileName
.length() + 1;
711 while (fileNameBuffer
[i
] != wxT('\0'))
713 m_fileNames
.Add(&fileNameBuffer
[i
]);
714 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
717 wxStringTokenizer
toke(fileNameBuffer
, wxT(" \t\r\n"));
718 m_dir
= toke
.GetNextToken();
719 m_fileName
= toke
.GetNextToken();
720 m_fileNames
.Add(m_fileName
);
722 while (toke
.HasMoreTokens())
723 m_fileNames
.Add(toke
.GetNextToken());
724 #endif // OFN_EXPLORER
727 if ( m_dir
.Last() != wxT('\\') )
730 m_path
= dir
+ m_fileName
;
731 m_filterIndex
= (int)of
.nFilterIndex
- 1;
735 //=== Adding the correct extension >>=================================
737 m_filterIndex
= (int)of
.nFilterIndex
- 1;
739 if ( !of
.nFileExtension
||
740 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
742 // User has typed a filename without an extension:
743 const wxChar
* extension
= filterBuffer
.t_str();
744 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
746 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
747 extension
= extension
+ wxStrlen( extension
) + 1;
749 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
750 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
753 m_path
= fileNameBuffer
;
754 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
755 m_fileNames
.Add(m_fileName
);
756 m_dir
= wxPathOnly(fileNameBuffer
);
763 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)