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
.empty() || 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::DoGetPosition(int *x
, int *y
) const
180 *x
= gs_rectDialog
.x
;
182 *y
= gs_rectDialog
.y
;
185 void wxFileDialog::DoGetSize(int *width
, int *height
) const
188 *width
= gs_rectDialog
.width
;
190 *height
= gs_rectDialog
.height
;
193 void wxFileDialog::DoMoveWindow(int x
, int y
, int WXUNUSED(w
), int WXUNUSED(h
))
198 // our HWND is only set when we're called from MSWOnInitDone(), test if
200 HWND hwnd
= GetHwnd();
203 // size of the dialog can't be changed because the controls are not
204 // laid out correctly then
205 ::SetWindowPos(hwnd
, HWND_TOP
, x
, y
, 0, 0, SWP_NOZORDER
| SWP_NOSIZE
);
207 else // just remember that we were requested to move the window
209 m_bMovedWindow
= true;
211 // if Centre() had been called before, it shouldn't be taken into
217 void wxFileDialog::DoCentre(int dir
)
220 m_bMovedWindow
= true;
222 // it's unnecessary to do anything else at this stage as we'll redo it in
223 // MSWOnInitDone() anyhow
226 void wxFileDialog::MSWOnInitDone(WXHWND hDlg
)
228 // note the dialog is the parent window: hDlg is a child of it when
229 // OFN_EXPLORER is used
230 HWND hFileDlg
= ::GetParent((HWND
)hDlg
);
232 // set HWND so that our DoMoveWindow() works correctly
233 SetHWND((WXHWND
)hFileDlg
);
237 // now we have the real dialog size, remember it
239 GetWindowRect(hFileDlg
, &rect
);
240 gs_rectDialog
= wxRectFromRECT(rect
);
242 // and position the window correctly: notice that we must use the base
243 // class version as our own doesn't do anything except setting flags
244 wxFileDialogBase::DoCentre(m_centreDir
);
246 else // need to just move it to the correct place
248 SetPosition(gs_rectDialog
.GetPosition());
251 // we shouldn't destroy this HWND
255 // helper used below in ShowCommFileDialog(): style is used to determine
256 // whether to show the "Save file" dialog (if it contains wxFD_SAVE bit) or
257 // "Open file" one; returns true on success or false on failure in which case
258 // err is filled with the CDERR_XXX constant
259 static bool DoShowCommFileDialog(OPENFILENAME
*of
, long style
, DWORD
*err
)
261 if ( style
& wxFD_SAVE
? GetSaveFileName(of
) : GetOpenFileName(of
) )
267 // according to MSDN, CommDlgExtendedError() should work under CE as
268 // well but apparently in practice it doesn't (anybody has more
270 *err
= GetLastError();
272 *err
= CommDlgExtendedError();
279 // We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
280 // know if the OPENFILENAME declared in the currently used headers is a V5 or
281 // V4 (smaller) one so we try to manually extend the struct in case it is the
284 // We don't do this on Windows CE nor under Win64, however, as there are no
285 // compilers with old headers for these architectures
286 #if defined(__WXWINCE__) || defined(__WIN64__)
287 typedef OPENFILENAME wxOPENFILENAME
;
289 static const DWORD gs_ofStructSize
= sizeof(OPENFILENAME
);
290 #else // !__WXWINCE__ || __WIN64__
291 #define wxTRY_SMALLER_OPENFILENAME
293 struct wxOPENFILENAME
: public OPENFILENAME
295 // fields added in Windows 2000/XP comdlg32.dll version
301 // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
302 // because sizeof(OPENFILENAME) in the headers we use when compiling the
303 // library could be less if _WIN32_WINNT is not >= 0x500
304 static const DWORD wxOPENFILENAME_V5_SIZE
= 88;
306 // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
307 static const DWORD wxOPENFILENAME_V4_SIZE
= 76;
309 // always try the new one first
310 static DWORD gs_ofStructSize
= wxOPENFILENAME_V5_SIZE
;
311 #endif // __WXWINCE__ || __WIN64__/!...
313 static bool ShowCommFileDialog(OPENFILENAME
*of
, long style
)
316 bool success
= DoShowCommFileDialog(of
, style
, &errCode
);
318 #ifdef wxTRY_SMALLER_OPENFILENAME
319 // the system might be too old to support the new version file dialog
320 // boxes, try with the old size
321 if ( !success
&& errCode
== CDERR_STRUCTSIZE
&&
322 of
->lStructSize
!= wxOPENFILENAME_V4_SIZE
)
324 of
->lStructSize
= wxOPENFILENAME_V4_SIZE
;
326 success
= DoShowCommFileDialog(of
, style
, &errCode
);
328 if ( success
|| !errCode
)
330 // use this struct size for subsequent dialogs
331 gs_ofStructSize
= of
->lStructSize
;
334 #endif // wxTRY_SMALLER_OPENFILENAME
337 // FNERR_INVALIDFILENAME is not defined under CE (besides we don't
338 // use CommDlgExtendedError() there anyhow)
340 errCode
== FNERR_INVALIDFILENAME
&&
341 #endif // !__WXWINCE__
344 // this can happen if the default file name is invalid, try without it
346 of
->lpstrFile
[0] = wxT('\0');
347 success
= DoShowCommFileDialog(of
, style
, &errCode
);
352 // common dialog failed - why?
355 wxLogError(_("File dialog failed with error code %0lx."), errCode
);
357 //else: it was just cancelled
366 void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd
)
370 CreateExtraControl();
374 #endif // __WXWINCE__
376 int wxFileDialog::ShowModal()
379 if (m_parent
) hWnd
= (HWND
) m_parent
->GetHWND();
380 if (!hWnd
&& wxTheApp
->GetTopWindow())
381 hWnd
= (HWND
) wxTheApp
->GetTopWindow()->GetHWND();
383 static wxChar fileNameBuffer
[ wxMAXPATH
]; // the file-name
384 wxChar titleBuffer
[ wxMAXFILE
+1+wxMAXEXT
]; // the file-name, without path
386 *fileNameBuffer
= wxT('\0');
387 *titleBuffer
= wxT('\0');
389 long msw_flags
= OFN_HIDEREADONLY
;
391 if ( HasFdFlag(wxFD_FILE_MUST_EXIST
) )
392 msw_flags
|= OFN_PATHMUSTEXIST
| OFN_FILEMUSTEXIST
;
394 If the window has been moved the programmer is probably
395 trying to center or position it. Thus we set the callback
396 or hook function so that we can actually adjust the position.
397 Without moving or centering the dlg, it will just stay
398 in the upper left of the frame, it does not center
401 if (m_bMovedWindow
|| HasExtraControlCreator()) // we need these flags.
403 msw_flags
|= OFN_EXPLORER
|OFN_ENABLEHOOK
;
405 msw_flags
|= OFN_ENABLESIZING
;
409 if ( HasFdFlag(wxFD_MULTIPLE
) )
411 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
412 msw_flags
|= OFN_EXPLORER
| OFN_ALLOWMULTISELECT
;
415 // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
416 // standard dialog does by default (notice that under NT it does it anyhow,
417 // OFN_NOCHANGEDIR or not, see below)
418 if ( !HasFdFlag(wxFD_CHANGE_DIR
) )
420 msw_flags
|= OFN_NOCHANGEDIR
;
423 if ( HasFdFlag(wxFD_OVERWRITE_PROMPT
) )
425 msw_flags
|= OFN_OVERWRITEPROMPT
;
431 of
.lStructSize
= gs_ofStructSize
;
433 of
.lpstrTitle
= m_message
.wx_str();
434 of
.lpstrFileTitle
= titleBuffer
;
435 of
.nMaxFileTitle
= wxMAXFILE
+ 1 + wxMAXEXT
;
439 if ( HasExtraControlCreator() )
441 msw_flags
|= OFN_ENABLETEMPLATEHANDLE
;
443 hgbl
.Init(256, GMEM_ZEROINIT
);
444 GlobalPtrLock
hgblLock(hgbl
);
445 LPDLGTEMPLATE lpdt
= static_cast<LPDLGTEMPLATE
>(hgblLock
.Get());
447 // Define a dialog box.
449 lpdt
->style
= DS_CONTROL
| WS_CHILD
| WS_CLIPSIBLINGS
;
450 lpdt
->cdit
= 0; // Number of controls
454 // convert the size of the extra controls to the dialog units
455 const wxSize extraSize
= GetExtraControlSize();
456 const LONG baseUnits
= ::GetDialogBaseUnits();
457 lpdt
->cx
= ::MulDiv(extraSize
.x
, 4, LOWORD(baseUnits
));
458 lpdt
->cy
= ::MulDiv(extraSize
.y
, 8, HIWORD(baseUnits
));
460 // after the DLGTEMPLATE there are 3 additional WORDs for dialog menu,
461 // class and title, all three set to zeros.
463 of
.hInstance
= (HINSTANCE
)lpdt
;
465 #endif // __WXWINCE__
467 // Convert forward slashes to backslashes (file selector doesn't like
468 // forward slashes) and also squeeze multiple consecutive slashes into one
469 // as it doesn't like two backslashes in a row neither
472 size_t i
, len
= m_dir
.length();
474 for ( i
= 0; i
< len
; i
++ )
476 wxChar ch
= m_dir
[i
];
480 // convert to backslash
486 while ( i
< len
- 1 )
488 wxChar chNext
= m_dir
[i
+ 1];
489 if ( chNext
!= wxT('\\') && chNext
!= wxT('/') )
492 // ignore the next one, unless it is at the start of a UNC path
506 of
.lpstrInitialDir
= dir
.c_str();
508 of
.Flags
= msw_flags
;
509 of
.lpfnHook
= wxFileDialogHookFunction
;
510 of
.lCustData
= (LPARAM
)this;
512 wxArrayString wildDescriptions
, wildFilters
;
514 size_t items
= wxParseCommonDialogsFilter(m_wildCard
, wildDescriptions
, wildFilters
);
516 wxASSERT_MSG( items
> 0 , wxT("empty wildcard list") );
518 wxString filterBuffer
;
520 for (i
= 0; i
< items
; i
++)
522 filterBuffer
+= wildDescriptions
[i
];
523 filterBuffer
+= wxT("|");
524 filterBuffer
+= wildFilters
[i
];
525 filterBuffer
+= wxT("|");
529 for (i
= 0; i
< filterBuffer
.length(); i
++ ) {
530 if ( filterBuffer
.GetChar(i
) == wxT('|') ) {
531 filterBuffer
[i
] = wxT('\0');
535 of
.lpstrFilter
= (LPTSTR
)filterBuffer
.wx_str();
536 of
.nFilterIndex
= m_filterIndex
+ 1;
538 //=== Setting defaultFileName >>=========================================
540 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
542 of
.lpstrFile
= fileNameBuffer
; // holds returned filename
543 of
.nMaxFile
= wxMAXPATH
;
545 // we must set the default extension because otherwise Windows would check
546 // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
547 // user types "foo" and the default extension is ".bar" we should force it
548 // to check for "foo.bar" existence and not "foo")
549 wxString defextBuffer
; // we need it to be alive until GetSaveFileName()!
550 if (HasFdFlag(wxFD_SAVE
))
552 const wxChar
* extension
= filterBuffer
.wx_str();
553 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
555 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
556 extension
= extension
+ wxStrlen( extension
) + 1;
558 // use dummy name a to avoid assert in AppendExtension
559 defextBuffer
= AppendExtension(wxT("a"), extension
);
560 if (defextBuffer
.StartsWith(wxT("a.")))
562 defextBuffer
= defextBuffer
.Mid(2); // remove "a."
563 of
.lpstrDefExt
= defextBuffer
.c_str();
567 // store off before the standard windows dialog can possibly change it
568 const wxString cwdOrig
= wxGetCwd();
570 //== Execute FileDialog >>=================================================
572 if ( !ShowCommFileDialog(&of
, m_windowStyle
) )
575 // GetOpenFileName will always change the current working directory on
576 // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
577 // OFN_NOCHANGEDIR has no effect. If the user did not specify
578 // wxFD_CHANGE_DIR let's restore the current working directory to what it
579 // was before the dialog was shown.
580 if ( msw_flags
& OFN_NOCHANGEDIR
)
582 wxSetWorkingDirectory(cwdOrig
);
587 if ( ( HasFdFlag(wxFD_MULTIPLE
) ) &&
588 #if defined(OFN_EXPLORER)
589 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT('\0') )
591 ( fileNameBuffer
[of
.nFileOffset
-1] == wxT(' ') )
592 #endif // OFN_EXPLORER
595 #if defined(OFN_EXPLORER)
596 m_dir
= fileNameBuffer
;
598 m_fileName
= &fileNameBuffer
[i
];
599 m_fileNames
.Add(m_fileName
);
600 i
+= m_fileName
.length() + 1;
602 while (fileNameBuffer
[i
] != wxT('\0'))
604 m_fileNames
.Add(&fileNameBuffer
[i
]);
605 i
+= wxStrlen(&fileNameBuffer
[i
]) + 1;
608 wxStringTokenizer
toke(fileNameBuffer
, wxT(" \t\r\n"));
609 m_dir
= toke
.GetNextToken();
610 m_fileName
= toke
.GetNextToken();
611 m_fileNames
.Add(m_fileName
);
613 while (toke
.HasMoreTokens())
614 m_fileNames
.Add(toke
.GetNextToken());
615 #endif // OFN_EXPLORER
618 if ( m_dir
.Last() != wxT('\\') )
621 m_path
= dir
+ m_fileName
;
622 m_filterIndex
= (int)of
.nFilterIndex
- 1;
626 //=== Adding the correct extension >>=================================
628 m_filterIndex
= (int)of
.nFilterIndex
- 1;
630 if ( !of
.nFileExtension
||
631 (of
.nFileExtension
&& fileNameBuffer
[of
.nFileExtension
] == wxT('\0')) )
633 // User has typed a filename without an extension:
634 const wxChar
* extension
= filterBuffer
.wx_str();
635 int maxFilter
= (int)(of
.nFilterIndex
*2L) - 1;
637 for( int i
= 0; i
< maxFilter
; i
++ ) // get extension
638 extension
= extension
+ wxStrlen( extension
) + 1;
640 m_fileName
= AppendExtension(fileNameBuffer
, extension
);
641 wxStrlcpy(fileNameBuffer
, m_fileName
.c_str(), WXSIZEOF(fileNameBuffer
));
644 m_path
= fileNameBuffer
;
645 m_fileName
= wxFileNameFromPath(fileNameBuffer
);
646 m_fileNames
.Add(m_fileName
);
647 m_dir
= wxPathOnly(fileNameBuffer
);
654 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)