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__)