]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/filedlg.cpp
fix crash which happened if you called SetAttr(NULL) followed by SetAttr(attr) (...
[wxWidgets.git] / src / msw / filedlg.cpp
index 9e5f80ea48ece3938481935136f2296e97c92b3f..7ed1c81362c784de7e37f4872445875fca424032 100644 (file)
 // headers
 // ----------------------------------------------------------------------------
 
-#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
-    #pragma implementation "filedlg.h"
-#endif
-
 // For compilers that support precompilation, includes "wx.h".
 #include "wx/wxprec.h"
 
 
 #if wxUSE_FILEDLG && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
 
+#include "wx/filedlg.h"
+
 #ifndef WX_PRECOMP
+    #include "wx/msw/wrapcdlg.h"
+    #include "wx/msw/missing.h"
     #include "wx/utils.h"
     #include "wx/msgdlg.h"
-    #include "wx/filedlg.h"
     #include "wx/filefn.h"
     #include "wx/intl.h"
     #include "wx/log.h"
     #include "wx/app.h"
-#endif
-
-#include "wx/msw/private.h"
-
-#if !defined(__WIN32__) || defined(__WXWINCE__)
-    #include <commdlg.h>
+    #include "wx/math.h"
 #endif
 
 #include <stdlib.h>
@@ -51,9 +45,6 @@
 
 #include "wx/filename.h"
 #include "wx/tokenzr.h"
-#include "wx/math.h"
-
-#include "wx/msw/missing.h"
 
 // ----------------------------------------------------------------------------
 // constants
@@ -73,7 +64,8 @@
 // globals
 // ----------------------------------------------------------------------------
 
-// standard dialog size
+// standard dialog size for the old Windows systems where the dialog wasn't
+// resizeable
 static wxRect gs_rectDialog(0, 0, 428, 266);
 
 // ============================================================================
@@ -86,42 +78,34 @@ IMPLEMENT_CLASS(wxFileDialog, wxFileDialogBase)
 // hook function for moving the dialog
 // ----------------------------------------------------------------------------
 
-UINT APIENTRY
+UINT_PTR APIENTRY
 wxFileDialogHookFunction(HWND      hDlg,
                          UINT      iMsg,
                          WPARAM    WXUNUSED(wParam),
                          LPARAM    lParam)
 {
-    HWND   hwndDialog;
-    hwndDialog = ::GetParent( hDlg );
-    switch (iMsg)
+    switch ( iMsg )
     {
-        case WM_DESTROY:
-            {
-                RECT dlgRect;
-                GetWindowRect( hwndDialog, & dlgRect );
-                gs_rectDialog.x = dlgRect.left;
-                gs_rectDialog.y = dlgRect.top;
-                gs_rectDialog.width = dlgRect.right - dlgRect.left;
-                gs_rectDialog.height = dlgRect.bottom - dlgRect.top;
-            }
-            break;
-
         case WM_NOTIFY:
             {
-                OFNOTIFY *   pNotifyCode;
-                pNotifyCode = (LPOFNOTIFY) lParam;
-                if (CDN_INITDONE == (pNotifyCode->hdr).code)
+                OFNOTIFY *pNotifyCode = wx_reinterpret_cast(OFNOTIFY *, lParam);
+                if ( pNotifyCode->hdr.code == CDN_INITDONE )
                 {
-                    SetWindowPos( hwndDialog, HWND_TOP,
-                                  gs_rectDialog.x,
-                                  gs_rectDialog.y,
-                                  gs_rectDialog.width,
-                                  gs_rectDialog.height,
-                                  SWP_NOZORDER|SWP_NOSIZE);
+                    wx_reinterpret_cast(wxFileDialog *,
+                                        pNotifyCode->lpOFN->lCustData)
+                        ->MSWOnInitDone((WXHWND)hDlg);
                  }
             }
             break;
+
+        case WM_DESTROY:
+            // reuse the position used for the dialog the next time by default
+            //
+            // NB: at least under Windows 2003 this is useless as after the
+            //     first time it's shown the dialog always remembers its size
+            //     and position itself and ignores any later SetWindowPos calls
+            wxCopyRECTToRect(wxGetWindowRect(::GetParent(hDlg)), gs_rectDialog);
+            break;
     }
 
     // do the default processing
@@ -138,23 +122,25 @@ wxFileDialog::wxFileDialog(wxWindow *parent,
                            const wxString& defaultFileName,
                            const wxString& wildCard,
                            long style,
-                           const wxPoint& pos)
+                           const wxPoint& pos,
+                           const wxSize& sz,
+                           const wxString& name)
             : wxFileDialogBase(parent, message, defaultDir, defaultFileName,
-                               wildCard, style, pos)
+                               wildCard, style, pos, sz, name)
 
 {
-    if ( ( m_dialogStyle & wxMULTIPLE ) && ( m_dialogStyle & wxSAVE ) )
-        m_dialogStyle &= ~wxMULTIPLE;
+    // NB: all style checks are done by wxFileDialogBase::Create
 
     m_bMovedWindow = false;
+    m_centreDir = 0;
 
     // Must set to zero, otherwise the wx routines won't size the window
     // the second time you call the file dialog, because it thinks it is
     // already at the requested size.. (when centering)
     gs_rectDialog.x =
     gs_rectDialog.y = 0;
-
 }
+
 void wxFileDialog::GetPaths(wxArrayString& paths) const
 {
     paths.Empty();
@@ -186,34 +172,142 @@ void wxFileDialog::SetPath(const wxString& path)
         m_fileName << _T('.') << ext;
 }
 
-void wxFileDialog::DoGetPosition( int *x, int *y ) const
+void wxFileDialog::DoGetPosition(int *x, int *y) const
 {
-    *x = gs_rectDialog.x;
-    *y = gs_rectDialog.y;
+    if ( x )
+        *x = gs_rectDialog.x;
+    if ( y )
+        *y = gs_rectDialog.y;
 }
 
-
 void wxFileDialog::DoGetSize(int *width, int *height) const
 {
-    *width  = gs_rectDialog.width;
-    *height = gs_rectDialog.height;
+    if ( width )
+        *width = gs_rectDialog.width;
+    if ( height )
+        *height = gs_rectDialog.height;
 }
 
-void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(width), int WXUNUSED(height))
+void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(w), int WXUNUSED(h))
 {
-    m_bMovedWindow = true;
-
     gs_rectDialog.x = x;
     gs_rectDialog.y = y;
 
-    /*
-        The width and height can not be set by the programmer
-        its just not possible.  But the program can get the
-        size of the Dlg after it has been shown, in case they need
-        that data.
-    */
+    // our HWND is only set when we're called from MSWOnInitDone(), test if
+    // this is the case
+    HWND hwnd = GetHwnd();
+    if ( hwnd )
+    {
+        // size of the dialog can't be changed because the controls are not
+        // laid out correctly then
+       ::SetWindowPos(hwnd, HWND_TOP, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
+    }
+    else // just remember that we were requested to move the window
+    {
+        m_bMovedWindow = true;
+
+        // if Centre() had been called before, it shouldn't be taken into
+        // account now
+        m_centreDir = 0;
+    }
 }
 
+void wxFileDialog::DoCentre(int dir)
+{
+    m_centreDir = dir;
+    m_bMovedWindow = true;
+
+    // it's unnecessary to do anything else at this stage as we'll redo it in
+    // MSWOnInitDone() anyhow
+}
+
+void wxFileDialog::MSWOnInitDone(WXHWND hDlg)
+{
+    // note the the dialog is the parent window: hDlg is a child of it when
+    // OFN_EXPLORER is used
+    HWND hFileDlg = ::GetParent((HWND)hDlg);
+
+    // set HWND so that our DoMoveWindow() works correctly
+    SetHWND((WXHWND)hFileDlg);
+
+    if ( m_centreDir )
+    {
+        // now we have the real dialog size, remember it
+        RECT rect;
+        GetWindowRect(hFileDlg, &rect);
+        gs_rectDialog = wxRectFromRECT(rect);
+
+        // and position the window correctly: notice that we must use the base
+        // class version as our own doesn't do anything except setting flags
+        wxFileDialogBase::DoCentre(m_centreDir);
+    }
+    else // need to just move it to the correct place
+    {
+        SetPosition(gs_rectDialog.GetPosition());
+    }
+
+    // we shouldn't destroy this HWND
+    SetHWND(NULL);
+}
+
+// helper used below in ShowModal(): style is used to determine whether to show
+// the "Save file" dialog (if it contains wxFD_SAVE bit) or "Open file" one;
+// returns true on success or false on failure in which case err is filled with
+// the CDERR_XXX constant
+static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err)
+{
+    if ( style & wxFD_SAVE ? GetSaveFileName(of) : GetOpenFileName(of) )
+        return true;
+
+    if ( err )
+    {
+#ifdef __WXWINCE__
+        // according to MSDN, CommDlgExtendedError() should work under CE as
+        // well but apparently in practice it doesn't (anybody has more
+        // details?)
+        *err = GetLastError();
+#else
+        *err = CommDlgExtendedError();
+#endif
+    }
+
+    return false;
+}
+
+// We want to use OPENFILENAME struct version 5 (Windows 2000/XP) but we don't
+// know if the OPENFILENAME declared in the currently used headers is a V5 or
+// V4 (smaller) one so we try to manually extend the struct in case it is the
+// old one.
+//
+// We don't do this on Windows CE nor under Win64, however, as there are no
+// compilers with old headers for these architectures
+#if defined(__WXWINCE__) || defined(__WIN64__)
+    typedef OPENFILENAME wxOPENFILENAME;
+
+    static const DWORD gs_ofStructSize = sizeof(OPENFILENAME);
+#else // !__WXWINCE__ || __WIN64__
+    #define wxTRY_SMALLER_OPENFILENAME
+
+    struct wxOPENFILENAME : public OPENFILENAME
+    {
+        // fields added in Windows 2000/XP comdlg32.dll version
+        void *pVoid;
+        DWORD dw1;
+        DWORD dw2;
+    };
+
+    // hardcoded sizeof(OPENFILENAME) in the Platform SDK: we have to do it
+    // because sizeof(OPENFILENAME) in the headers we use when compiling the
+    // library could be less if _WIN32_WINNT is not >= 0x500
+    static const DWORD wxOPENFILENAME_V5_SIZE = 88;
+
+    // this is hardcoded sizeof(OPENFILENAME_NT4) from Platform SDK
+    static const DWORD wxOPENFILENAME_V4_SIZE = 76;
+
+    // always try the new one first
+    static DWORD gs_ofStructSize = wxOPENFILENAME_V5_SIZE;
+#endif // __WXWINCE__ || __WIN64__/!...
+
 int wxFileDialog::ShowModal()
 {
     HWND hWnd = 0;
@@ -227,15 +321,9 @@ int wxFileDialog::ShowModal()
     *fileNameBuffer = wxT('\0');
     *titleBuffer    = wxT('\0');
 
-#if WXWIN_COMPATIBILITY_2_4
-    long msw_flags = 0;
-    if ( (m_dialogStyle & wxHIDE_READONLY) || (m_dialogStyle & wxSAVE) )
-        msw_flags |= OFN_HIDEREADONLY;
-#else
     long msw_flags = OFN_HIDEREADONLY;
-#endif
 
-    if ( m_dialogStyle & wxFILE_MUST_EXIST )
+    if ( HasFdFlag(wxFD_FILE_MUST_EXIST) )
         msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
     /*
         If the window has been moved the programmer is probably
@@ -243,12 +331,7 @@ int wxFileDialog::ShowModal()
         or hook function so that we can actually adjust the position.
         Without moving or centering the dlg, it will just stay
         in the upper left of the frame, it does not center
-        automatically..  One additional note, when the hook is
-        enabled, the PLACES BAR in the dlg (shown on later versions
-        of windows (2000 and XP) will automatically be turned off
-        according to the MSDN docs.  This is normal.  If the
-        programmer needs the PLACES BAR (left side of dlg) they
-        just shouldn't move or center the dlg.
+        automatically.
     */
     if (m_bMovedWindow) // we need these flags.
     {
@@ -258,42 +341,33 @@ int wxFileDialog::ShowModal()
 #endif
     }
 
-    if (m_dialogStyle & wxMULTIPLE )
+    if ( HasFdFlag(wxFD_MULTIPLE) )
     {
         // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
         msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
     }
 
-    // if wxCHANGE_DIR flag is not given we shouldn't change the CWD which the
-    // standard dialog does by default
-    if ( !(m_dialogStyle & wxCHANGE_DIR) )
+    // if wxFD_CHANGE_DIR flag is not given we shouldn't change the CWD which the
+    // standard dialog does by default (notice that under NT it does it anyhow,
+    // OFN_NOCHANGEDIR or not, see below)
+    if ( !HasFdFlag(wxFD_CHANGE_DIR) )
     {
         msw_flags |= OFN_NOCHANGEDIR;
     }
 
-    if ( m_dialogStyle & wxOVERWRITE_PROMPT )
+    if ( HasFdFlag(wxFD_OVERWRITE_PROMPT) )
     {
         msw_flags |= OFN_OVERWRITEPROMPT;
     }
 
-    OPENFILENAME of;
+    wxOPENFILENAME of;
     wxZeroMemory(of);
 
-    // the OPENFILENAME struct has been extended in newer version of
-    // comcdlg32.dll, but as we don't use the extended fields anyhow, set
-    // the struct size to the old value - otherwise, the programs compiled
-    // with new headers will not work with the old libraries
-#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0500)
-    of.lStructSize       = sizeof(OPENFILENAME) -
-                           (sizeof(void *) + 2*sizeof(DWORD));
-#else // old headers
-    of.lStructSize       = sizeof(OPENFILENAME);
-#endif
-
+    of.lStructSize       = gs_ofStructSize;
     of.hwndOwner         = hWnd;
-    of.lpstrTitle        = WXSTRINGCAST m_message;
+    of.lpstrTitle        = m_message.wx_str();
     of.lpstrFileTitle    = titleBuffer;
-    of.nMaxFileTitle     = wxMAXFILE + 1 + wxMAXEXT;    // Windows 3.0 and 3.1
+    of.nMaxFileTitle     = wxMAXFILE + 1 + wxMAXEXT;
 
     // Convert forward slashes to backslashes (file selector doesn't like
     // forward slashes) and also squeeze multiple consecutive slashes into one
@@ -338,6 +412,7 @@ int wxFileDialog::ShowModal()
 
     of.Flags             = msw_flags;
     of.lpfnHook          = wxFileDialogHookFunction;
+    of.lCustData         = (LPARAM)this;
 
     wxArrayString wildDescriptions, wildFilters;
 
@@ -356,31 +431,31 @@ int wxFileDialog::ShowModal()
     }
 
     // Replace | with \0
-    for (i = 0; i < filterBuffer.Len(); i++ ) {
+    for (i = 0; i < filterBuffer.length(); i++ ) {
         if ( filterBuffer.GetChar(i) == wxT('|') ) {
             filterBuffer[i] = wxT('\0');
         }
     }
 
-    of.lpstrFilter  = (LPTSTR)filterBuffer.c_str();
+    of.lpstrFilter  = (LPTSTR)filterBuffer.wx_str();
     of.nFilterIndex = m_filterIndex + 1;
 
     //=== Setting defaultFileName >>=========================================
 
-    wxStrncpy( fileNameBuffer, (const wxChar *)m_fileName, wxMAXPATH-1 );
+    wxStrncpy(fileNameBuffer, m_fileName, wxMAXPATH-1);
     fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
 
     of.lpstrFile = fileNameBuffer;  // holds returned filename
     of.nMaxFile  = wxMAXPATH;
 
     // we must set the default extension because otherwise Windows would check
-    // for the existing of a wrong file with wxOVERWRITE_PROMPT (i.e. if the
+    // for the existing of a wrong file with wxFD_OVERWRITE_PROMPT (i.e. if the
     // user types "foo" and the default extension is ".bar" we should force it
     // to check for "foo.bar" existence and not "foo")
     wxString defextBuffer; // we need it to be alive until GetSaveFileName()!
-    if (m_dialogStyle & wxSAVE)
+    if (HasFdFlag(wxFD_SAVE))
     {
-        const wxChar* extension = filterBuffer;
+        const wxChar* extension = filterBuffer.wx_str();
         int maxFilter = (int)(of.nFilterIndex*2L) - 1;
 
         for( int i = 0; i < maxFilter; i++ )           // get extension
@@ -390,45 +465,52 @@ int wxFileDialog::ShowModal()
         defextBuffer = AppendExtension(wxT("a"), extension);
         if (defextBuffer.StartsWith(wxT("a.")))
         {
-            defextBuffer.Mid(2);
+            defextBuffer = defextBuffer.Mid(2); // remove "a."
             of.lpstrDefExt = defextBuffer.c_str();
         }
     }
 
-     //== Execute FileDialog >>=================================================
+    // store off before the standard windows dialog can possibly change it
+    const wxString cwdOrig = wxGetCwd();
 
     //== Execute FileDialog >>=================================================
 
-    bool success = (m_dialogStyle & wxSAVE ? GetSaveFileName(&of)
-                                           : GetOpenFileName(&of)) != 0;
-
-    DWORD errCode = CommDlgExtendedError();
+    DWORD errCode;
+    bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
 
-#ifdef __WIN32__
-    if (!success && (errCode == CDERR_STRUCTSIZE))
+#ifdef wxTRY_SMALLER_OPENFILENAME
+    // the system might be too old to support the new version file dialog
+    // boxes, try with the old size
+    if ( !success && errCode == CDERR_STRUCTSIZE &&
+            of.lStructSize != wxOPENFILENAME_V4_SIZE )
     {
-        // The struct size has changed so try a smaller or bigger size
+        of.lStructSize = wxOPENFILENAME_V4_SIZE;
 
-        int oldStructSize = of.lStructSize;
-        of.lStructSize       = oldStructSize - (sizeof(void *) + 2*sizeof(DWORD));
-        success = (m_dialogStyle & wxSAVE) ? (GetSaveFileName(&of) != 0)
-                                            : (GetOpenFileName(&of) != 0);
-        errCode = CommDlgExtendedError();
+        success = DoShowCommFileDialog(&of, m_windowStyle, &errCode);
 
-        if (!success && (errCode == CDERR_STRUCTSIZE))
+        if ( success || !errCode )
         {
-            of.lStructSize       = oldStructSize + (sizeof(void *) + 2*sizeof(DWORD));
-            success = (m_dialogStyle & wxSAVE) ? (GetSaveFileName(&of) != 0)
-                                            : (GetOpenFileName(&of) != 0);
+            // use this struct size for subsequent dialogs
+            gs_ofStructSize = of.lStructSize;
         }
     }
-#endif // __WIN32__
+#endif // wxTRY_SMALLER_OPENFILENAME
 
     if ( success )
     {
+        // GetOpenFileName will always change the current working directory on
+        // (according to MSDN) "Windows NT 4.0/2000/XP" because the flag
+        // OFN_NOCHANGEDIR has no effect.  If the user did not specify
+        // wxFD_CHANGE_DIR let's restore the current working directory to what it
+        // was before the dialog was shown.
+        if ( msw_flags & OFN_NOCHANGEDIR )
+        {
+            wxSetWorkingDirectory(cwdOrig);
+        }
+
         m_fileNames.Empty();
 
-        if ( ( m_dialogStyle & wxMULTIPLE ) &&
+        if ( ( HasFdFlag(wxFD_MULTIPLE) ) &&
 #if defined(OFN_EXPLORER)
              ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') )
 #else
@@ -441,7 +523,7 @@ int wxFileDialog::ShowModal()
             i = of.nFileOffset;
             m_fileName = &fileNameBuffer[i];
             m_fileNames.Add(m_fileName);
-            i += m_fileName.Len() + 1;
+            i += m_fileName.length() + 1;
 
             while (fileNameBuffer[i] != wxT('\0'))
             {
@@ -475,15 +557,15 @@ int wxFileDialog::ShowModal()
                  (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
             {
                 // User has typed a filename without an extension:
-                const wxChar* extension = filterBuffer;
+                const wxChar* extension = filterBuffer.wx_str();
                 int   maxFilter = (int)(of.nFilterIndex*2L) - 1;
 
                 for( int i = 0; i < maxFilter; i++ )           // get extension
                     extension = extension + wxStrlen( extension ) + 1;
 
                 m_fileName = AppendExtension(fileNameBuffer, extension);
-                wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.Len(), wxMAXPATH-1));
-                fileNameBuffer[wxMin(m_fileName.Len(), wxMAXPATH-1)] = wxT('\0');
+                wxStrncpy(fileNameBuffer, m_fileName.c_str(), wxMin(m_fileName.length(), wxMAXPATH-1));
+                fileNameBuffer[wxMin(m_fileName.length(), wxMAXPATH-1)] = wxT('\0');
             }
 
             m_path = fileNameBuffer;
@@ -492,24 +574,22 @@ int wxFileDialog::ShowModal()
             m_dir = wxPathOnly(fileNameBuffer);
         }
     }
+#ifdef __WXDEBUG__
     else
     {
         // common dialog failed - why?
-#ifdef __WXDEBUG__
-        DWORD dwErr = CommDlgExtendedError();
-        if ( dwErr != 0 )
+        if ( errCode != 0 )
         {
-            // this msg is only for developers
+            // this msg is only for developers so don't translate it
             wxLogError(wxT("Common dialog failed with error code %0lx."),
-                       dwErr);
+                       errCode);
         }
         //else: it was just cancelled
-#endif
     }
+#endif // __WXDEBUG__
 
     return success ? wxID_OK : wxID_CANCEL;
 
 }
 
 #endif // wxUSE_FILEDLG && !(__SMARTPHONE__ && __WXWINCE__)
-