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/testing.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__
176 OFNOTIFY
*pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
177 if ( pNotifyCode
->hdr
.code
== CDN_INITDONE
)
179 reinterpret_cast<wxFileDialog
*>(
180 pNotifyCode
->lpOFN
->lCustData
)
181 ->MSWOnInitDone((WXHWND
)hDlg
);
187 // reuse the position used for the dialog the next time by default
189 // NB: at least under Windows 2003 this is useless as after the
190 // first time it's shown the dialog always remembers its size
191 // and position itself and ignores any later SetWindowPos calls
192 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg
)), gs_rectDialog
);
196 // do the default processing
200 // ----------------------------------------------------------------------------
202 // ----------------------------------------------------------------------------
204 wxFileDialog::wxFileDialog(wxWindow
*parent
,
205 const wxString
& message
,
206 const wxString
& defaultDir
,
207 const wxString
& defaultFileName
,
208 const wxString
& wildCard
,
212 const wxString
& name
)
213 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
214 wildCard
, style
, pos
, sz
, name
)
217 // NB: all style checks are done by wxFileDialogBase::Create
219 m_bMovedWindow
= false;
222 // Must set to zero, otherwise the wx routines won't size the window
223 // the second time you call the file dialog, because it thinks it is
224 // already at the requested size.. (when centering)
229 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
234 if ( m_dir
.empty() || m_dir
.Last() != wxT('\\') )
237 size_t count
= m_fileNames
.GetCount();
238 for ( size_t n
= 0; n
< count
; n
++ )
240 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
241 paths
.Add(m_fileNames
[n
]);
243 paths
.Add(dir
+ m_fileNames
[n
]);
247 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
252 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
255 *x
= gs_rectDialog
.x
;
257 *y
= gs_rectDialog
.y
;
260 void wxFileDialog::DoGetSize(int *width
, int *height
) const
263 *width
= gs_rectDialog
.width
;
265 *height
= gs_rectDialog
.height
;
268 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
273 // our HWND is only set when we're called from MSWOnInitDone(), test if
275 HWND hwnd
= GetHwnd();
278 // size of the dialog can't be changed because the controls are not
279 // laid out correctly then
280 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
282 else // just remember that we were requested to move the window
284 m_bMovedWindow
= true;
286 // if Centre() had been called before, it shouldn't be taken into
292 void wxFileDialog::DoCentre(int dir
)
295 m_bMovedWindow
= true;
297 // it's unnecessary to do anything else at this stage as we'll redo it in
298 // MSWOnInitDone() anyhow
301 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
303 // note the dialog is the parent window: hDlg is a child of it when
304 // OFN_EXPLORER is used
305 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
307 // set HWND so that our DoMoveWindow() works correctly
308 SetHWND((WXHWND
)hFileDlg
);
312 // now we have the real dialog size, remember it
314 GetWindowRect(hFileDlg
, &rect
);
315 gs_rectDialog
= wxRectFromRECT(rect
);
317 // and position the window correctly: notice that we must use the base
318 // class version as our own doesn't do anything except setting flags
319 wxFileDialogBase::DoCentre(m_centreDir
);
321 else // need to just move it to the correct place
323 SetPosition(gs_rectDialog
.GetPosition());
326 // we shouldn't destroy this HWND
330 // helper used below in ShowCommFileDialog(): style is used to determine
331 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
332 // "Open file" one; returns true on success or false on failure in which case
333 // err is filled with the CDERR_XXX constant
334 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
336 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
342 // according to MSDN, CommDlgExtendedError() should work under CE as
343 // well but apparently in practice it doesn't (anybody has more
345 *err
= GetLastError();
347 *err
= CommDlgExtendedError();
354 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
355 // know if the OPENFILENAME declared in the currently used headers is a V5 or
356 // V4 (smaller) one so we try to manually extend the struct in case it is the
359 // We don't do this on Windows CE nor under Win64, however, as there are no
360 // compilers with old headers for these architectures
361 #if defined(__WXWINCE__) || defined(__WIN64__)
362 typedef OPENFILENAME wxOPENFILENAME
;
364 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
365 #else // !__WXWINCE__ || __WIN64__
366 #define wxTRY_SMALLER_OPENFILENAME
368 struct wxOPENFILENAME
: public OPENFILENAME
370 // fields added in Windows 2000/XP comdlg32.dll version
376 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
377 // because sizeof(OPENFILENAME) in the headers we use when compiling the
378 // library could be less if _WIN32_WINNT is not >= 0x500
379 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
381 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
382 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
384 // always try the new one first
385 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
386 #endif // __WXWINCE__ || __WIN64__/!...
388 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
391 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
393 #ifdef wxTRY_SMALLER_OPENFILENAME
394 // the system might be too old to support the new version file dialog
395 // boxes, try with the old size
396 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
397 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
399 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
401 success
= DoShowCommFileDialog(of
, style
, &errCode
);
403 if ( success
|| !errCode
)
405 // use this struct size for subsequent dialogs
406 gs_ofStructSize
= of
->lStructSize
;
409 #endif // wxTRY_SMALLER_OPENFILENAME
412 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
413 // use CommDlgExtendedError() there anyhow)
415 errCode
== FNERR_INVALIDFILENAME
&&
416 #endif // !__WXWINCE__
419 // this can happen if the default file name is invalid, try without it
421 of
->lpstrFile
[0] = wxT('\0');
422 success
= DoShowCommFileDialog(of
, style
, &errCode
);
427 // common dialog failed - why?
430 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
432 //else: it was just cancelled
441 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
445 CreateExtraControl();
449 #endif // __WXWINCE__
451 int wxFileDialog::ShowModal()
453 WX_TESTING_SHOW_MODAL_HOOK();
456 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
457 if (!hWnd
&& wxTheApp
->GetTopWindow())
458 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
460 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
461 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
463 *fileNameBuffer
= wxT('\0');
464 *titleBuffer
= wxT('\0');
466 long msw_flags
= OFN_HIDEREADONLY
;
468 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
469 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
471 If the window has been moved the programmer is probably
472 trying to center or position it. Thus we set the callback
473 or hook function so that we can actually adjust the position.
474 Without moving or centering the dlg, it will just stay
475 in the upper left of the frame, it does not center
478 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
480 ChangeExceptionPolicy();
481 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
483 msw_flags
|= OFN_ENABLESIZING
;
487 wxON_BLOCK_EXIT0(RestoreExceptionPolicy
);
489 if ( HasFdFlag(wxFD_MULTIPLE
) )
491 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
492 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
495 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
496 // standard dialog does by default (notice that under NT it does it anyhow,
497 // OFN_NOCHANGEDIR or not, see below)
498 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
500 msw_flags
|= OFN_NOCHANGEDIR
;
503 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
505 msw_flags
|= OFN_OVERWRITEPROMPT
;
511 of
.lStructSize
= gs_ofStructSize
;
513 of
.lpstrTitle
= m_message
.t_str();
514 of
.lpstrFileTitle
= titleBuffer
;
515 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
519 if ( HasExtraControlCreator() )
521 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
523 hgbl
.Init(256, GMEM_ZEROINIT
);
524 GlobalPtrLock
hgblLock(hgbl
);
525 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
527 // Define a dialog box.
529 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
530 lpdt
->cdit
= 0; // Number of controls
534 // convert the size of the extra controls to the dialog units
535 const wxSize extraSize
= GetExtraControlSize();
536 const LONG baseUnits
= ::GetDialogBaseUnits();
537 lpdt
->cx
= ::MulDiv(extraSize
.x
, 4, LOWORD(baseUnits
));
538 lpdt
->cy
= ::MulDiv(extraSize
.y
, 8, HIWORD(baseUnits
));
540 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
541 // class and title, all three set to zeros.
543 of
.hInstance
= (HINSTANCE
)lpdt
;
545 #endif // __WXWINCE__
547 // Convert forward slashes to backslashes (file selector doesn't like
548 // forward slashes) and also squeeze multiple consecutive slashes into one
549 // as it doesn't like two backslashes in a row neither
552 size_t i
, len
= m_dir
.length();
554 for ( i
= 0; i
< len
; i
++ )
556 wxChar ch
= m_dir
[i
];
560 // convert to backslash
566 while ( i
< len
- 1 )
568 wxChar chNext
= m_dir
[i
+ 1];
569 if ( chNext
!= wxT('\\') && chNext
!= wxT('/') )
572 // ignore the next one, unless it is at the start of a UNC path
586 of
.lpstrInitialDir
= dir
.c_str();
588 of
.Flags
= msw_flags
;
589 of
.lpfnHook
= wxFileDialogHookFunction
;
590 of
.lCustData
= (LPARAM
)this;
592 wxArrayString wildDescriptions
, wildFilters
;
594 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
596 wxASSERT_MSG( items
> 0 , wxT("empty wildcard list") );
598 wxString filterBuffer
;
600 for (i
= 0; i
< items
; i
++)
602 filterBuffer
+= wildDescriptions
[i
];
603 filterBuffer
+= wxT("|");
604 filterBuffer
+= wildFilters
[i
];
605 filterBuffer
+= wxT("|");
609 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
610 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
611 filterBuffer
[i
] = wxT('\0');
615 of
.lpstrFilter
= filterBuffer
.t_str();
616 of
.nFilterIndex
= m_filterIndex
+ 1;
618 //=== Setting defaultFileName >>=========================================
620 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
622 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
623 of
.nMaxFile
= wxMAXPATH
;
625 // we must set the default extension because otherwise Windows would check
626 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
627 // user types "foo" and the default extension is ".bar" we should force it
628 // to check for "foo.bar" existence and not "foo")
629 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
630 if (HasFdFlag(wxFD_SAVE
))
632 const wxChar
* extension
= filterBuffer
.t_str();
633 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
635 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
636 extension
= extension
+ wxStrlen( extension
) + 1;
638 // use dummy name a to avoid assert in AppendExtension
639 defextBuffer
= AppendExtension(wxT("a"), extension
);
640 if (defextBuffer
.StartsWith(wxT("a.")))
642 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
643 of
.lpstrDefExt
= defextBuffer
.c_str();
647 // store off before the standard windows dialog can possibly change it
648 const wxString cwdOrig
= wxGetCwd();
650 //== Execute FileDialog >>=================================================
652 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
655 // GetOpenFileName will always change the current working directory on
656 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
657 // OFN_NOCHANGEDIR has no effect. If the user did not specify
658 // wxFD_CHANGE_DIR let's restore the current working directory to what it
659 // was before the dialog was shown.
660 if ( msw_flags
& OFN_NOCHANGEDIR
)
662 wxSetWorkingDirectory(cwdOrig
);
667 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
668 #if defined(OFN_EXPLORER)
669 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
671 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
672 #endif // OFN_EXPLORER
675 #if defined(OFN_EXPLORER)
676 m_dir
= fileNameBuffer
;
678 m_fileName
= &fileNameBuffer
[i
];
679 m_fileNames
.Add(m_fileName
);
680 i
+= m_fileName
.length() + 1;
682 while (fileNameBuffer
[i
] != wxT('\0'))
684 m_fileNames
.Add(&fileNameBuffer
[i
]);
685 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
688 wxStringTokenizer
toke(fileNameBuffer
, wxT(" \t\r\n"));
689 m_dir
= toke
.GetNextToken();
690 m_fileName
= toke
.GetNextToken();
691 m_fileNames
.Add(m_fileName
);
693 while (toke
.HasMoreTokens())
694 m_fileNames
.Add(toke
.GetNextToken());
695 #endif // OFN_EXPLORER
698 if ( m_dir
.Last() != wxT('\\') )
701 m_path
= dir
+ m_fileName
;
702 m_filterIndex
= (int)of
.nFilterIndex
- 1;
706 //=== Adding the correct extension >>=================================
708 m_filterIndex
= (int)of
.nFilterIndex
- 1;
710 if ( !of
.nFileExtension
||
711 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
713 // User has typed a filename without an extension:
714 const wxChar
* extension
= filterBuffer
.t_str();
715 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
717 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
718 extension
= extension
+ wxStrlen( extension
) + 1;
720 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
721 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
724 m_path
= fileNameBuffer
;
725 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
726 m_fileNames
.Add(m_fileName
);
727 m_dir
= wxPathOnly(fileNameBuffer
);
734 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)