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
),
92 OPENFILENAME
* ofn
= reinterpret_cast<OPENFILENAME
*>(lParam
);
93 reinterpret_cast<wxFileDialog
*>(ofn
->lCustData
)
94 ->MSWOnInitDialogHook((WXHWND
)hDlg
);
101 OFNOTIFY
*pNotifyCode
= reinterpret_cast<OFNOTIFY
*>(lParam
);
102 if ( pNotifyCode
->hdr
.code
== CDN_INITDONE
)
104 reinterpret_cast<wxFileDialog
*>(
105 pNotifyCode
->lpOFN
->lCustData
)
106 ->MSWOnInitDone((WXHWND
)hDlg
);
112 // reuse the position used for the dialog the next time by default
114 // NB: at least under Windows 2003 this is useless as after the
115 // first time it's shown the dialog always remembers its size
116 // and position itself and ignores any later SetWindowPos calls
117 wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg
)), gs_rectDialog
);
121 // do the default processing
125 // ----------------------------------------------------------------------------
127 // ----------------------------------------------------------------------------
129 wxFileDialog::wxFileDialog(wxWindow
*parent
,
130 const wxString
& message
,
131 const wxString
& defaultDir
,
132 const wxString
& defaultFileName
,
133 const wxString
& wildCard
,
137 const wxString
& name
)
138 : wxFileDialogBase(parent
, message
, defaultDir
, defaultFileName
,
139 wildCard
, style
, pos
, sz
, name
)
142 // NB: all style checks are done by wxFileDialogBase::Create
144 m_bMovedWindow
= false;
147 // Must set to zero, otherwise the wx routines won't size the window
148 // the second time you call the file dialog, because it thinks it is
149 // already at the requested size.. (when centering)
154 void wxFileDialog::GetPaths(wxArrayString
& paths
) const
159 if ( m_dir
.Last() != wxT('\\') )
162 size_t count
= m_fileNames
.GetCount();
163 for ( size_t n
= 0; n
< count
; n
++ )
165 if (wxFileName(m_fileNames
[n
]).IsAbsolute())
166 paths
.Add(m_fileNames
[n
]);
168 paths
.Add(dir
+ m_fileNames
[n
]);
172 void wxFileDialog::GetFilenames(wxArrayString
& files
) const
177 void wxFileDialog::SetPath(const wxString
& path
)
180 wxFileName::SplitPath(path
, &m_dir
, &m_fileName
, &ext
);
182 m_fileName
<< wxT('.') << ext
;
185 void wxFileDialog::DoGetPosition(int *x
, int *y
) const
188 *x
= gs_rectDialog
.x
;
190 *y
= gs_rectDialog
.y
;
193 void wxFileDialog::DoGetSize(int *width
, int *height
) const
196 *width
= gs_rectDialog
.width
;
198 *height
= gs_rectDialog
.height
;
201 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
206 // our HWND is only set when we're called from MSWOnInitDone(), test if
208 HWND hwnd
= GetHwnd();
211 // size of the dialog can't be changed because the controls are not
212 // laid out correctly then
213 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
215 else // just remember that we were requested to move the window
217 m_bMovedWindow
= true;
219 // if Centre() had been called before, it shouldn't be taken into
225 void wxFileDialog::DoCentre(int dir
)
228 m_bMovedWindow
= true;
230 // it's unnecessary to do anything else at this stage as we'll redo it in
231 // MSWOnInitDone() anyhow
234 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
236 // note the the dialog is the parent window: hDlg is a child of it when
237 // OFN_EXPLORER is used
238 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
240 // set HWND so that our DoMoveWindow() works correctly
241 SetHWND((WXHWND
)hFileDlg
);
245 // now we have the real dialog size, remember it
247 GetWindowRect(hFileDlg
, &rect
);
248 gs_rectDialog
= wxRectFromRECT(rect
);
250 // and position the window correctly: notice that we must use the base
251 // class version as our own doesn't do anything except setting flags
252 wxFileDialogBase::DoCentre(m_centreDir
);
254 else // need to just move it to the correct place
256 SetPosition(gs_rectDialog
.GetPosition());
259 // we shouldn't destroy this HWND
263 // helper used below in ShowCommFileDialog(): style is used to determine
264 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
265 // "Open file" one; returns true on success or false on failure in which case
266 // err is filled with the CDERR_XXX constant
267 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
269 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
275 // according to MSDN, CommDlgExtendedError() should work under CE as
276 // well but apparently in practice it doesn't (anybody has more
278 *err
= GetLastError();
280 *err
= CommDlgExtendedError();
287 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
288 // know if the OPENFILENAME declared in the currently used headers is a V5 or
289 // V4 (smaller) one so we try to manually extend the struct in case it is the
292 // We don't do this on Windows CE nor under Win64, however, as there are no
293 // compilers with old headers for these architectures
294 #if defined(__WXWINCE__) || defined(__WIN64__)
295 typedef OPENFILENAME wxOPENFILENAME
;
297 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
298 #else // !__WXWINCE__ || __WIN64__
299 #define wxTRY_SMALLER_OPENFILENAME
301 struct wxOPENFILENAME
: public OPENFILENAME
303 // fields added in Windows 2000/XP comdlg32.dll version
309 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
310 // because sizeof(OPENFILENAME) in the headers we use when compiling the
311 // library could be less if _WIN32_WINNT is not >= 0x500
312 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
314 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
315 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
317 // always try the new one first
318 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
319 #endif // __WXWINCE__ || __WIN64__/!...
321 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
324 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
326 #ifdef wxTRY_SMALLER_OPENFILENAME
327 // the system might be too old to support the new version file dialog
328 // boxes, try with the old size
329 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
330 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
332 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
334 success
= DoShowCommFileDialog(of
, style
, &errCode
);
336 if ( success
|| !errCode
)
338 // use this struct size for subsequent dialogs
339 gs_ofStructSize
= of
->lStructSize
;
342 #endif // wxTRY_SMALLER_OPENFILENAME
345 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
346 // use CommDlgExtendedError() there anyhow)
348 errCode
== FNERR_INVALIDFILENAME
&&
349 #endif // !__WXWINCE__
352 // this can happen if the default file name is invalid, try without it
354 of
->lpstrFile
[0] = wxT('\0');
355 success
= DoShowCommFileDialog(of
, style
, &errCode
);
360 // common dialog failed - why?
363 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
365 //else: it was just cancelled
374 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
378 CreateExtraControl();
382 #endif // __WXWINCE__
384 int wxFileDialog::ShowModal()
387 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
388 if (!hWnd
&& wxTheApp
->GetTopWindow())
389 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
391 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
392 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
394 *fileNameBuffer
= wxT('\0');
395 *titleBuffer
= wxT('\0');
397 long msw_flags
= OFN_HIDEREADONLY
;
399 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
400 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
402 If the window has been moved the programmer is probably
403 trying to center or position it. Thus we set the callback
404 or hook function so that we can actually adjust the position.
405 Without moving or centering the dlg, it will just stay
406 in the upper left of the frame, it does not center
409 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
411 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
413 msw_flags
|= OFN_ENABLESIZING
;
417 if ( HasFdFlag(wxFD_MULTIPLE
) )
419 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
420 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
423 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
424 // standard dialog does by default (notice that under NT it does it anyhow,
425 // OFN_NOCHANGEDIR or not, see below)
426 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
428 msw_flags
|= OFN_NOCHANGEDIR
;
431 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
433 msw_flags
|= OFN_OVERWRITEPROMPT
;
439 of
.lStructSize
= gs_ofStructSize
;
441 of
.lpstrTitle
= m_message
.wx_str();
442 of
.lpstrFileTitle
= titleBuffer
;
443 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
447 if ( HasExtraControlCreator() )
449 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
451 hgbl
.Init(256, GMEM_ZEROINIT
);
452 GlobalPtrLock
hgblLock(hgbl
);
453 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
455 // Define a dialog box.
457 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
458 lpdt
->cdit
= 0; // Number of controls
462 // convert the size of the extra controls to the dialog units
463 const wxSize extraSize
= GetExtraControlSize();
464 const LONG baseUnits
= ::GetDialogBaseUnits();
465 lpdt
->cx
= ::MulDiv(extraSize
.x
, 4, LOWORD(baseUnits
));
466 lpdt
->cy
= ::MulDiv(extraSize
.y
, 8, HIWORD(baseUnits
));
468 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
469 // class and title, all three set to zeros.
471 of
.hInstance
= (HINSTANCE
)lpdt
;
473 #endif // __WXWINCE__
475 // Convert forward slashes to backslashes (file selector doesn't like
476 // forward slashes) and also squeeze multiple consecutive slashes into one
477 // as it doesn't like two backslashes in a row neither
480 size_t i
, len
= m_dir
.length();
482 for ( i
= 0; i
< len
; i
++ )
484 wxChar ch
= m_dir
[i
];
488 // convert to backslash
494 while ( i
< len
- 1 )
496 wxChar chNext
= m_dir
[i
+ 1];
497 if ( chNext
!= wxT('\\') && chNext
!= wxT('/') )
500 // ignore the next one, unless it is at the start of a UNC path
514 of
.lpstrInitialDir
= dir
.c_str();
516 of
.Flags
= msw_flags
;
517 of
.lpfnHook
= wxFileDialogHookFunction
;
518 of
.lCustData
= (LPARAM
)this;
520 wxArrayString wildDescriptions
, wildFilters
;
522 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
524 wxASSERT_MSG( items
> 0 , wxT("empty wildcard list") );
526 wxString filterBuffer
;
528 for (i
= 0; i
< items
; i
++)
530 filterBuffer
+= wildDescriptions
[i
];
531 filterBuffer
+= wxT("|");
532 filterBuffer
+= wildFilters
[i
];
533 filterBuffer
+= wxT("|");
537 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
538 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
539 filterBuffer
[i
] = wxT('\0');
543 of
.lpstrFilter
= (LPTSTR
)filterBuffer
.wx_str();
544 of
.nFilterIndex
= m_filterIndex
+ 1;
546 //=== Setting defaultFileName >>=========================================
548 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
550 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
551 of
.nMaxFile
= wxMAXPATH
;
553 // we must set the default extension because otherwise Windows would check
554 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
555 // user types "foo" and the default extension is ".bar" we should force it
556 // to check for "foo.bar" existence and not "foo")
557 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
558 if (HasFdFlag(wxFD_SAVE
))
560 const wxChar
* extension
= filterBuffer
.wx_str();
561 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
563 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
564 extension
= extension
+ wxStrlen( extension
) + 1;
566 // use dummy name a to avoid assert in AppendExtension
567 defextBuffer
= AppendExtension(wxT("a"), extension
);
568 if (defextBuffer
.StartsWith(wxT("a.")))
570 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
571 of
.lpstrDefExt
= defextBuffer
.c_str();
575 // store off before the standard windows dialog can possibly change it
576 const wxString cwdOrig
= wxGetCwd();
578 //== Execute FileDialog >>=================================================
580 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
583 // GetOpenFileName will always change the current working directory on
584 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
585 // OFN_NOCHANGEDIR has no effect. If the user did not specify
586 // wxFD_CHANGE_DIR let's restore the current working directory to what it
587 // was before the dialog was shown.
588 if ( msw_flags
& OFN_NOCHANGEDIR
)
590 wxSetWorkingDirectory(cwdOrig
);
595 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
596 #if defined(OFN_EXPLORER)
597 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
599 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
600 #endif // OFN_EXPLORER
603 #if defined(OFN_EXPLORER)
604 m_dir
= fileNameBuffer
;
606 m_fileName
= &fileNameBuffer
[i
];
607 m_fileNames
.Add(m_fileName
);
608 i
+= m_fileName
.length() + 1;
610 while (fileNameBuffer
[i
] != wxT('\0'))
612 m_fileNames
.Add(&fileNameBuffer
[i
]);
613 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
616 wxStringTokenizer
toke(fileNameBuffer
, wxT(" \t\r\n"));
617 m_dir
= toke
.GetNextToken();
618 m_fileName
= toke
.GetNextToken();
619 m_fileNames
.Add(m_fileName
);
621 while (toke
.HasMoreTokens())
622 m_fileNames
.Add(toke
.GetNextToken());
623 #endif // OFN_EXPLORER
626 if ( m_dir
.Last() != wxT('\\') )
629 m_path
= dir
+ m_fileName
;
630 m_filterIndex
= (int)of
.nFilterIndex
- 1;
634 //=== Adding the correct extension >>=================================
636 m_filterIndex
= (int)of
.nFilterIndex
- 1;
638 if ( !of
.nFileExtension
||
639 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
641 // User has typed a filename without an extension:
642 const wxChar
* extension
= filterBuffer
.wx_str();
643 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
645 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
646 extension
= extension
+ wxStrlen( extension
) + 1;
648 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
649 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
652 m_path
= fileNameBuffer
;
653 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
654 m_fileNames
.Add(m_fileName
);
655 m_dir
= wxPathOnly(fileNameBuffer
);
662 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)