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"
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__
175 OFNOTIFY
*pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
176 if ( pNotifyCode
->hdr
.code
== CDN_INITDONE
)
178 reinterpret_cast<wxFileDialog
*>(
179 pNotifyCode
->lpOFN
->lCustData
)
180 ->MSWOnInitDone((WXHWND
)hDlg
);
186 // reuse the position used for the dialog the next time by default
188 // NB: at least under Windows 2003 this is useless as after the
189 // first time it's shown the dialog always remembers its size
190 // and position itself and ignores any later SetWindowPos calls
191 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg
)), gs_rectDialog
);
195 // do the default processing
199 // ----------------------------------------------------------------------------
201 // ----------------------------------------------------------------------------
203 wxFileDialog::wxFileDialog(wxWindow
*parent
,
204 const wxString
& message
,
205 const wxString
& defaultDir
,
206 const wxString
& defaultFileName
,
207 const wxString
& wildCard
,
211 const wxString
& name
)
212 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
213 wildCard
, style
, pos
, sz
, name
)
216 // NB: all style checks are done by wxFileDialogBase::Create
218 m_bMovedWindow
= false;
221 // Must set to zero, otherwise the wx routines won't size the window
222 // the second time you call the file dialog, because it thinks it is
223 // already at the requested size.. (when centering)
228 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
233 if ( m_dir
.empty() || m_dir
.Last() != wxT('\\') )
236 size_t count
= m_fileNames
.GetCount();
237 for ( size_t n
= 0; n
< count
; n
++ )
239 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
240 paths
.Add(m_fileNames
[n
]);
242 paths
.Add(dir
+ m_fileNames
[n
]);
246 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
251 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
254 *x
= gs_rectDialog
.x
;
256 *y
= gs_rectDialog
.y
;
259 void wxFileDialog::DoGetSize(int *width
, int *height
) const
262 *width
= gs_rectDialog
.width
;
264 *height
= gs_rectDialog
.height
;
267 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
272 // our HWND is only set when we're called from MSWOnInitDone(), test if
274 HWND hwnd
= GetHwnd();
277 // size of the dialog can't be changed because the controls are not
278 // laid out correctly then
279 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
281 else // just remember that we were requested to move the window
283 m_bMovedWindow
= true;
285 // if Centre() had been called before, it shouldn't be taken into
291 void wxFileDialog::DoCentre(int dir
)
294 m_bMovedWindow
= true;
296 // it's unnecessary to do anything else at this stage as we'll redo it in
297 // MSWOnInitDone() anyhow
300 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
302 // note the dialog is the parent window: hDlg is a child of it when
303 // OFN_EXPLORER is used
304 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
306 // set HWND so that our DoMoveWindow() works correctly
307 SetHWND((WXHWND
)hFileDlg
);
311 // now we have the real dialog size, remember it
313 GetWindowRect(hFileDlg
, &rect
);
314 gs_rectDialog
= wxRectFromRECT(rect
);
316 // and position the window correctly: notice that we must use the base
317 // class version as our own doesn't do anything except setting flags
318 wxFileDialogBase::DoCentre(m_centreDir
);
320 else // need to just move it to the correct place
322 SetPosition(gs_rectDialog
.GetPosition());
325 // we shouldn't destroy this HWND
329 // helper used below in ShowCommFileDialog(): style is used to determine
330 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
331 // "Open file" one; returns true on success or false on failure in which case
332 // err is filled with the CDERR_XXX constant
333 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
335 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
341 // according to MSDN, CommDlgExtendedError() should work under CE as
342 // well but apparently in practice it doesn't (anybody has more
344 *err
= GetLastError();
346 *err
= CommDlgExtendedError();
353 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
354 // know if the OPENFILENAME declared in the currently used headers is a V5 or
355 // V4 (smaller) one so we try to manually extend the struct in case it is the
358 // We don't do this on Windows CE nor under Win64, however, as there are no
359 // compilers with old headers for these architectures
360 #if defined(__WXWINCE__) || defined(__WIN64__)
361 typedef OPENFILENAME wxOPENFILENAME
;
363 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
364 #else // !__WXWINCE__ || __WIN64__
365 #define wxTRY_SMALLER_OPENFILENAME
367 struct wxOPENFILENAME
: public OPENFILENAME
369 // fields added in Windows 2000/XP comdlg32.dll version
375 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
376 // because sizeof(OPENFILENAME) in the headers we use when compiling the
377 // library could be less if _WIN32_WINNT is not >= 0x500
378 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
380 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
381 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
383 // always try the new one first
384 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
385 #endif // __WXWINCE__ || __WIN64__/!...
387 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
390 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
392 #ifdef wxTRY_SMALLER_OPENFILENAME
393 // the system might be too old to support the new version file dialog
394 // boxes, try with the old size
395 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
396 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
398 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
400 success
= DoShowCommFileDialog(of
, style
, &errCode
);
402 if ( success
|| !errCode
)
404 // use this struct size for subsequent dialogs
405 gs_ofStructSize
= of
->lStructSize
;
408 #endif // wxTRY_SMALLER_OPENFILENAME
411 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
412 // use CommDlgExtendedError() there anyhow)
414 errCode
== FNERR_INVALIDFILENAME
&&
415 #endif // !__WXWINCE__
418 // this can happen if the default file name is invalid, try without it
420 of
->lpstrFile
[0] = wxT('\0');
421 success
= DoShowCommFileDialog(of
, style
, &errCode
);
426 // common dialog failed - why?
429 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
431 //else: it was just cancelled
440 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
444 CreateExtraControl();
448 #endif // __WXWINCE__
450 int wxFileDialog::ShowModal()
453 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
454 if (!hWnd
&& wxTheApp
->GetTopWindow())
455 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
457 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
458 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
460 *fileNameBuffer
= wxT('\0');
461 *titleBuffer
= wxT('\0');
463 long msw_flags
= OFN_HIDEREADONLY
;
465 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
466 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
468 If the window has been moved the programmer is probably
469 trying to center or position it. Thus we set the callback
470 or hook function so that we can actually adjust the position.
471 Without moving or centering the dlg, it will just stay
472 in the upper left of the frame, it does not center
475 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
477 ChangeExceptionPolicy();
478 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
480 msw_flags
|= OFN_ENABLESIZING
;
484 wxON_BLOCK_EXIT0(RestoreExceptionPolicy
);
486 if ( HasFdFlag(wxFD_MULTIPLE
) )
488 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
489 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
492 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
493 // standard dialog does by default (notice that under NT it does it anyhow,
494 // OFN_NOCHANGEDIR or not, see below)
495 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
497 msw_flags
|= OFN_NOCHANGEDIR
;
500 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
502 msw_flags
|= OFN_OVERWRITEPROMPT
;
508 of
.lStructSize
= gs_ofStructSize
;
510 of
.lpstrTitle
= m_message
.wx_str();
511 of
.lpstrFileTitle
= titleBuffer
;
512 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
516 if ( HasExtraControlCreator() )
518 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
520 hgbl
.Init(256, GMEM_ZEROINIT
);
521 GlobalPtrLock
hgblLock(hgbl
);
522 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
524 // Define a dialog box.
526 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
527 lpdt
->cdit
= 0; // Number of controls
531 // convert the size of the extra controls to the dialog units
532 const wxSize extraSize
= GetExtraControlSize();
533 const LONG baseUnits
= ::GetDialogBaseUnits();
534 lpdt
->cx
= ::MulDiv(extraSize
.x
, 4, LOWORD(baseUnits
));
535 lpdt
->cy
= ::MulDiv(extraSize
.y
, 8, HIWORD(baseUnits
));
537 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
538 // class and title, all three set to zeros.
540 of
.hInstance
= (HINSTANCE
)lpdt
;
542 #endif // __WXWINCE__
544 // Convert forward slashes to backslashes (file selector doesn't like
545 // forward slashes) and also squeeze multiple consecutive slashes into one
546 // as it doesn't like two backslashes in a row neither
549 size_t i
, len
= m_dir
.length();
551 for ( i
= 0; i
< len
; i
++ )
553 wxChar ch
= m_dir
[i
];
557 // convert to backslash
563 while ( i
< len
- 1 )
565 wxChar chNext
= m_dir
[i
+ 1];
566 if ( chNext
!= wxT('\\') && chNext
!= wxT('/') )
569 // ignore the next one, unless it is at the start of a UNC path
583 of
.lpstrInitialDir
= dir
.c_str();
585 of
.Flags
= msw_flags
;
586 of
.lpfnHook
= wxFileDialogHookFunction
;
587 of
.lCustData
= (LPARAM
)this;
589 wxArrayString wildDescriptions
, wildFilters
;
591 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
593 wxASSERT_MSG( items
> 0 , wxT("empty wildcard list") );
595 wxString filterBuffer
;
597 for (i
= 0; i
< items
; i
++)
599 filterBuffer
+= wildDescriptions
[i
];
600 filterBuffer
+= wxT("|");
601 filterBuffer
+= wildFilters
[i
];
602 filterBuffer
+= wxT("|");
606 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
607 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
608 filterBuffer
[i
] = wxT('\0');
612 of
.lpstrFilter
= (LPTSTR
)filterBuffer
.wx_str();
613 of
.nFilterIndex
= m_filterIndex
+ 1;
615 //=== Setting defaultFileName >>=========================================
617 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
619 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
620 of
.nMaxFile
= wxMAXPATH
;
622 // we must set the default extension because otherwise Windows would check
623 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
624 // user types "foo" and the default extension is ".bar" we should force it
625 // to check for "foo.bar" existence and not "foo")
626 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
627 if (HasFdFlag(wxFD_SAVE
))
629 const wxChar
* extension
= filterBuffer
.wx_str();
630 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
632 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
633 extension
= extension
+ wxStrlen( extension
) + 1;
635 // use dummy name a to avoid assert in AppendExtension
636 defextBuffer
= AppendExtension(wxT("a"), extension
);
637 if (defextBuffer
.StartsWith(wxT("a.")))
639 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
640 of
.lpstrDefExt
= defextBuffer
.c_str();
644 // store off before the standard windows dialog can possibly change it
645 const wxString cwdOrig
= wxGetCwd();
647 //== Execute FileDialog >>=================================================
649 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
652 // GetOpenFileName will always change the current working directory on
653 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
654 // OFN_NOCHANGEDIR has no effect. If the user did not specify
655 // wxFD_CHANGE_DIR let's restore the current working directory to what it
656 // was before the dialog was shown.
657 if ( msw_flags
& OFN_NOCHANGEDIR
)
659 wxSetWorkingDirectory(cwdOrig
);
664 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
665 #if defined(OFN_EXPLORER)
666 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
668 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
669 #endif // OFN_EXPLORER
672 #if defined(OFN_EXPLORER)
673 m_dir
= fileNameBuffer
;
675 m_fileName
= &fileNameBuffer
[i
];
676 m_fileNames
.Add(m_fileName
);
677 i
+= m_fileName
.length() + 1;
679 while (fileNameBuffer
[i
] != wxT('\0'))
681 m_fileNames
.Add(&fileNameBuffer
[i
]);
682 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
685 wxStringTokenizer
toke(fileNameBuffer
, wxT(" \t\r\n"));
686 m_dir
= toke
.GetNextToken();
687 m_fileName
= toke
.GetNextToken();
688 m_fileNames
.Add(m_fileName
);
690 while (toke
.HasMoreTokens())
691 m_fileNames
.Add(toke
.GetNextToken());
692 #endif // OFN_EXPLORER
695 if ( m_dir
.Last() != wxT('\\') )
698 m_path
= dir
+ m_fileName
;
699 m_filterIndex
= (int)of
.nFilterIndex
- 1;
703 //=== Adding the correct extension >>=================================
705 m_filterIndex
= (int)of
.nFilterIndex
- 1;
707 if ( !of
.nFileExtension
||
708 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
710 // User has typed a filename without an extension:
711 const wxChar
* extension
= filterBuffer
.wx_str();
712 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
714 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
715 extension
= extension
+ wxStrlen( extension
) + 1;
717 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
718 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
721 m_path
= fileNameBuffer
;
722 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
723 m_fileNames
.Add(m_fileName
);
724 m_dir
= wxPathOnly(fileNameBuffer
);
731 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)