]> git.saurik.com Git - wxWidgets.git/blobdiff - src/common/wincmn.cpp
Return NULL from wxWindow::GetCapture() when the capture is being lost.
[wxWidgets.git] / src / common / wincmn.cpp
index ff653cb8309226b19215708bde9456183bde331a..5d50b212b29464127899fe069301f7cd4c1b1995 100644 (file)
@@ -4,7 +4,6 @@
 // Author:      Julian Smart, Vadim Zeitlin
 // Modified by:
 // Created:     13/07/98
-// RCS-ID:      $Id$
 // Copyright:   (c) wxWidgets team
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
@@ -73,6 +72,7 @@
 #endif
 
 #include "wx/platinfo.h"
+#include "wx/recguard.h"
 #include "wx/private/window.h"
 
 #ifdef __WINDOWS__
@@ -89,6 +89,14 @@ wxMenu *wxCurrentPopupMenu = NULL;
 
 extern WXDLLEXPORT_DATA(const char) wxPanelNameStr[] = "panel";
 
+namespace wxMouseCapture
+{
+
+// Check if the given window is in the capture stack.
+bool IsInCaptureStack(wxWindowBase* win);
+
+} // wxMouseCapture
+
 // ----------------------------------------------------------------------------
 // static data
 // ----------------------------------------------------------------------------
@@ -448,7 +456,9 @@ bool wxWindowBase::ToggleWindowStyle(int flag)
 // common clean up
 wxWindowBase::~wxWindowBase()
 {
-    wxASSERT_MSG( GetCapture() != this, wxT("attempt to destroy window with mouse capture") );
+    wxASSERT_MSG( !wxMouseCapture::IsInCaptureStack(this),
+                    "Destroying window before releasing mouse capture: this "
+                    "will result in a crash later." );
 
     // FIXME if these 2 cases result from programming errors in the user code
     //       we should probably assert here instead of silently fixing them
@@ -1149,8 +1159,6 @@ void wxWindowBase::NotifyWindowOnEnableChange(bool enabled)
     DoEnable(enabled);
 #endif // !defined(wxHAS_NATIVE_ENABLED_MANAGEMENT)
 
-    OnEnabled(enabled);
-
     // Disabling a top level window is typically done when showing a modal
     // dialog and we don't need to disable its children in this case, they will
     // be logically disabled anyhow (i.e. their IsEnabled() will return false)
@@ -1166,9 +1174,7 @@ void wxWindowBase::NotifyWindowOnEnableChange(bool enabled)
     // they would still show as enabled even though they wouldn't actually
     // accept any input (at least under MSW where children don't accept input
     // if any of the windows in their parent chain is enabled).
-    //
-    // Notice that we must do this even for wxHAS_NATIVE_ENABLED_MANAGEMENT
-    // platforms as we still need to call the children OnEnabled() recursively.
+#ifndef wxHAS_NATIVE_ENABLED_MANAGEMENT
     for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
           node;
           node = node->GetNext() )
@@ -1177,6 +1183,7 @@ void wxWindowBase::NotifyWindowOnEnableChange(bool enabled)
         if ( !child->IsTopLevel() && child->IsThisEnabled() )
             child->NotifyWindowOnEnableChange(enabled);
     }
+#endif // !defined(wxHAS_NATIVE_ENABLED_MANAGEMENT)
 }
 
 bool wxWindowBase::Enable(bool enable)
@@ -2038,6 +2045,11 @@ public:
         return true;
     }
 
+    // Give it a virtual dtor just to suppress gcc warnings about a class with
+    // virtual methods but non-virtual dtor -- even if this is completely safe
+    // here as we never use the objects of this class polymorphically.
+    virtual ~ValidationTraverserBase() { }
+
 protected:
     // Called for each child, validator is guaranteed to be non-NULL.
     virtual bool OnDo(wxValidator* validator) = 0;
@@ -2608,6 +2620,8 @@ void wxWindowBase::SetConstraintSizes(bool recurse)
     wxLayoutConstraints *constr = GetConstraints();
     if ( constr && constr->AreSatisfied() )
     {
+        ChildrenRepositioningGuard repositionGuard(this);
+
         int x = constr->left.GetValue();
         int y = constr->top.GetValue();
         int w = constr->width.GetValue();
@@ -2956,7 +2970,7 @@ wxWindowBase::DoGetPopupMenuSelectionFromUser(wxMenu& menu, int x, int y)
 {
     gs_popupMenuSelection = wxID_NONE;
 
-    Connect(wxEVT_COMMAND_MENU_SELECTED,
+    Connect(wxEVT_MENU,
             wxCommandEventHandler(wxWindowBase::InternalOnPopupMenu),
             NULL,
             this);
@@ -2979,7 +2993,7 @@ wxWindowBase::DoGetPopupMenuSelectionFromUser(wxMenu& menu, int x, int y)
                wxUpdateUIEventHandler(wxWindowBase::InternalOnPopupMenuUpdate),
                NULL,
                this);
-    Disconnect(wxEVT_COMMAND_MENU_SELECTED,
+    Disconnect(wxEVT_MENU,
                wxCommandEventHandler(wxWindowBase::InternalOnPopupMenu),
                NULL,
                this);
@@ -3199,72 +3213,106 @@ wxHitTest wxWindowBase::DoHitTest(wxCoord x, wxCoord y) const
 // mouse capture
 // ----------------------------------------------------------------------------
 
-struct WXDLLEXPORT wxWindowNext
+// Private data used for mouse capture tracking.
+namespace wxMouseCapture
 {
-    wxWindow *win;
-    wxWindowNext *next;
-} *wxWindowBase::ms_winCaptureNext = NULL;
-wxWindow *wxWindowBase::ms_winCaptureCurrent = NULL;
-bool wxWindowBase::ms_winCaptureChanging = false;
+
+// Stack of the windows which previously had the capture, the top most element
+// is the window that has the mouse capture now.
+//
+// NB: We use wxVector and not wxStack to be able to examine all of the stack
+//     elements for debug checks, but only the stack operations should be
+//     performed with this vector.
+wxVector<wxWindow*> stack;
+
+// Flag preventing reentrancy in {Capture,Release}Mouse().
+wxRecursionGuardFlag changing;
+
+bool IsInCaptureStack(wxWindowBase* win)
+{
+    for ( wxVector<wxWindow*>::const_iterator it = stack.begin();
+          it != stack.end();
+          ++it )
+    {
+        if ( *it == win )
+            return true;
+    }
+
+    return false;
+}
+
+} // wxMouseCapture
 
 void wxWindowBase::CaptureMouse()
 {
     wxLogTrace(wxT("mousecapture"), wxT("CaptureMouse(%p)"), static_cast<void*>(this));
 
-    wxASSERT_MSG( !ms_winCaptureChanging, wxT("recursive CaptureMouse call?") );
+    wxRecursionGuard guard(wxMouseCapture::changing);
+    wxASSERT_MSG( !guard.IsInside(), wxT("recursive CaptureMouse call?") );
 
-    ms_winCaptureChanging = true;
+    wxASSERT_MSG( !wxMouseCapture::IsInCaptureStack(this),
+                    "Recapturing the mouse in the same window?" );
 
     wxWindow *winOld = GetCapture();
     if ( winOld )
-    {
         ((wxWindowBase*) winOld)->DoReleaseMouse();
 
-        // save it on stack
-        wxWindowNext *item = new wxWindowNext;
-        item->win = winOld;
-        item->next = ms_winCaptureNext;
-        ms_winCaptureNext = item;
-    }
-    //else: no mouse capture to save
-
     DoCaptureMouse();
-    ms_winCaptureCurrent = (wxWindow*)this;
 
-    ms_winCaptureChanging = false;
+    wxMouseCapture::stack.push_back(static_cast<wxWindow*>(this));
 }
 
 void wxWindowBase::ReleaseMouse()
 {
     wxLogTrace(wxT("mousecapture"), wxT("ReleaseMouse(%p)"), static_cast<void*>(this));
 
-    wxASSERT_MSG( !ms_winCaptureChanging, wxT("recursive ReleaseMouse call?") );
-
-    wxASSERT_MSG( GetCapture() == this,
-                  "attempt to release mouse, but this window hasn't captured it" );
-    wxASSERT_MSG( ms_winCaptureCurrent == this,
-                  "attempt to release mouse, but this window hasn't captured it" );
+    wxRecursionGuard guard(wxMouseCapture::changing);
+    wxASSERT_MSG( !guard.IsInside(), wxT("recursive ReleaseMouse call?") );
 
-    ms_winCaptureChanging = true;
+#if wxDEBUG_LEVEL
+    wxWindow* const winCapture = GetCapture();
+    if ( !winCapture )
+    {
+        wxFAIL_MSG
+        (
+          wxString::Format
+          (
+            "Releasing mouse in %p(%s) but it is not captured",
+            this, GetClassInfo()->GetClassName()
+          )
+        );
+    }
+    else if ( winCapture != this )
+    {
+        wxFAIL_MSG
+        (
+          wxString::Format
+          (
+            "Releasing mouse in %p(%s) but it is captured by %p(%s)",
+            this, GetClassInfo()->GetClassName(),
+            winCapture, winCapture->GetClassInfo()->GetClassName()
+          )
+        );
+    }
+#endif // wxDEBUG_LEVEL
 
     DoReleaseMouse();
-    ms_winCaptureCurrent = NULL;
 
-    if ( ms_winCaptureNext )
-    {
-        ((wxWindowBase*)ms_winCaptureNext->win)->DoCaptureMouse();
-        ms_winCaptureCurrent = ms_winCaptureNext->win;
+    wxCHECK_RET( !wxMouseCapture::stack.empty(),
+                    "Releasing mouse capture but capture stack empty?" );
+    wxCHECK_RET( wxMouseCapture::stack.back() == this,
+                    "Window releasing mouse capture not top of capture stack?" );
 
-        wxWindowNext *item = ms_winCaptureNext;
-        ms_winCaptureNext = item->next;
-        delete item;
-    }
-    //else: stack is empty, no previous capture
+    wxMouseCapture::stack.pop_back();
 
-    ms_winCaptureChanging = false;
+    // Restore the capture to the previous window, if any.
+    if ( !wxMouseCapture::stack.empty() )
+    {
+        ((wxWindowBase*)wxMouseCapture::stack.back())->DoCaptureMouse();
+    }
 
     wxLogTrace(wxT("mousecapture"),
-        (const wxChar *) wxT("After ReleaseMouse() mouse is captured by %p"),
+        wxT("After ReleaseMouse() mouse is captured by %p"),
         static_cast<void*>(GetCapture()));
 }
 
@@ -3287,26 +3335,17 @@ void wxWindowBase::NotifyCaptureLost()
 {
     // don't do anything if capture lost was expected, i.e. resulted from
     // a wx call to ReleaseMouse or CaptureMouse:
-    if ( ms_winCaptureChanging )
+    wxRecursionGuard guard(wxMouseCapture::changing);
+    if ( guard.IsInside() )
         return;
 
     // if the capture was lost unexpectedly, notify every window that has
     // capture (on stack or current) about it and clear the stack:
-
-    if ( ms_winCaptureCurrent )
+    while ( !wxMouseCapture::stack.empty() )
     {
-        DoNotifyWindowAboutCaptureLost(ms_winCaptureCurrent);
-        ms_winCaptureCurrent = NULL;
-    }
-
-    while ( ms_winCaptureNext )
-    {
-        wxWindowNext *item = ms_winCaptureNext;
-        ms_winCaptureNext = item->next;
-
-        DoNotifyWindowAboutCaptureLost(item->win);
+        DoNotifyWindowAboutCaptureLost(wxMouseCapture::stack.back());
 
-        delete item;
+        wxMouseCapture::stack.pop_back();
     }
 }
 
@@ -3366,7 +3405,7 @@ bool wxWindowBase::TryAfter(wxEvent& event)
             wxWindow *parent = GetParent();
             if ( parent && !parent->IsBeingDeleted() )
             {
-                wxPropagateOnce propagateOnce(event);
+                wxPropagateOnce propagateOnce(event, this);
 
                 return parent->GetEventHandler()->ProcessEvent(event);
             }