From 63e819f2981585279fe8a06629f69b7002eee9dc Mon Sep 17 00:00:00 2001
From: =?utf8?q?V=C3=A1clav=20Slav=C3=ADk?= <vslavik@fastmail.fm>
Date: Mon, 31 Jul 2006 09:27:54 +0000
Subject: [PATCH] added wxEVT_MOUSE_CAPTURE_LOST event and code for correctly
 handling capture loss (bug #1153662)

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@40391 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 docs/latex/wx/category.tex |  1 +
 docs/latex/wx/mcaptevt.tex | 49 +++++++++++++++++++++++++++++++++
 docs/latex/wx/window.tex   | 12 ++++++++-
 include/wx/event.h         | 43 +++++++++++++++++++++++------
 include/wx/html/htmlwin.h  |  1 +
 include/wx/window.h        |  9 +++++++
 src/common/event.cpp       |  2 ++
 src/common/wincmn.cpp      | 55 ++++++++++++++++++++++++++++++++++++++
 src/html/htmlwin.cpp       | 14 ++++++++++
 src/msw/window.cpp         |  9 +++++--
 10 files changed, 184 insertions(+), 11 deletions(-)

diff --git a/docs/latex/wx/category.tex b/docs/latex/wx/category.tex
index 4c095c6535..a4843b42e2 100644
--- a/docs/latex/wx/category.tex
+++ b/docs/latex/wx/category.tex
@@ -257,6 +257,7 @@ An event object contains information about a specific event. Event handlers
 \twocolitem{\helpref{wxMaximizeEvent}{wxmaximizeevent}}{A maximize event}
 \twocolitem{\helpref{wxMenuEvent}{wxmenuevent}}{A menu event}
 \twocolitem{\helpref{wxMouseCaptureChangedEvent}{wxmousecapturechangedevent}}{A mouse capture changed event}
+\twocolitem{\helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}}{A mouse capture lost event}
 \twocolitem{\helpref{wxMouseEvent}{wxmouseevent}}{A mouse event}
 \twocolitem{\helpref{wxMoveEvent}{wxmoveevent}}{A move event}
 \twocolitem{\helpref{wxNotebookEvent}{wxnotebookevent}}{A notebook control event}
diff --git a/docs/latex/wx/mcaptevt.tex b/docs/latex/wx/mcaptevt.tex
index acdec6c832..fc6d032f9d 100644
--- a/docs/latex/wx/mcaptevt.tex
+++ b/docs/latex/wx/mcaptevt.tex
@@ -29,6 +29,7 @@ function that takes a wxMouseCaptureChangedEvent argument.
 
 \wxheading{See also}
 
+\helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}
 \helpref{Event handling overview}{eventhandlingoverview}, 
 \helpref{wxWindow::CaptureMouse}{wxwindowcapturemouse}, 
 \helpref{wxWindow::ReleaseMouse}{wxwindowreleasemouse}, 
@@ -48,3 +49,51 @@ Constructor.
 
 Returns the window that gained the capture, or NULL if it was a non-wxWidgets window.
 
+
+\section{\class{wxMouseCaptureLostEvent}}\label{wxmousecapturelostevent}
+
+An mouse capture lost event is sent to a window that obtained mouse capture,
+which was subsequently loss due to "external" event, for example when a dialog
+box is shown or if another application captures the mouse.
+
+If this happens, this event is sent to all windows that are on capture stack
+(i.e. called CaptureMouse, but didn't call ReleaseMouse yet). The event is
+{\em not} sent if the capture changes because of a call to CaptureMouse or
+ReleaseMouse.
+
+This event is currently emitted under Windows only.
+
+\wxheading{Derived from}
+
+\helpref{wxEvent}{wxevent}\\
+\helpref{wxObject}{wxobject}
+
+\wxheading{Include files}
+
+<wx/event.h>
+
+\wxheading{Event table macros}
+
+To process this event, use the following event handler macro to direct input to
+a member function that takes a wxMouseCaptureLostEvent argument.
+
+\twocolwidtha{7cm}
+\begin{twocollist}\itemsep=0pt
+\twocolitem{{\bf EVT\_MOUSE\_CAPTURE\_LOST(func)}}{Process a wxEVT\_MOUSE\_CAPTURE\_LOST event.}
+\end{twocollist}
+
+\wxheading{See also}
+
+\helpref{wxMouseCaptureChangedEvent}{wxmousecapturechangedevent}
+\helpref{Event handling overview}{eventhandlingoverview}, 
+\helpref{wxWindow::CaptureMouse}{wxwindowcapturemouse}, 
+\helpref{wxWindow::ReleaseMouse}{wxwindowreleasemouse}, 
+\helpref{wxWindow::GetCapture}{wxwindowgetcapture}
+
+\latexignore{\rtfignore{\wxheading{Members}}}
+
+\membersection{wxMouseCaptureLostEvent::wxMouseCaptureLostEvent}\label{wxmousecapturelosteventctor}
+
+\func{}{wxMouseCaptureLostEvent}{\param{wxWindowID }{windowId = 0}}
+
+Constructor.
diff --git a/docs/latex/wx/window.tex b/docs/latex/wx/window.tex
index f30b18f366..9ac167e736 100644
--- a/docs/latex/wx/window.tex
+++ b/docs/latex/wx/window.tex
@@ -203,11 +203,18 @@ Note that wxWidgets maintains the stack of windows having captured the mouse
 and when the mouse is released the capture returns to the window which had had
 captured it previously and it is only really released if there were no previous
 window. In particular, this means that you must release the mouse as many times
-as you capture it.
+as you capture it, unless the window receives
+the \helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent} event.
+
+Any application which captures the mouse in the beginning of some operation
+{\em must} handle \helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}
+and cancel this operation when it receives the event. The event handler must
+not recapture mouse.
 
 \wxheading{See also}
 
 \helpref{wxWindow::ReleaseMouse}{wxwindowreleasemouse}
+\helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}
 
 
 \membersection{wxWindow::Center}\label{wxwindowcenter}
@@ -787,6 +794,7 @@ Returns the currently captured window.
 \helpref{wxWindow::HasCapture}{wxwindowhascapture},
 \helpref{wxWindow::CaptureMouse}{wxwindowcapturemouse},
 \helpref{wxWindow::ReleaseMouse}{wxwindowreleasemouse},
+\helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}
 \helpref{wxMouseCaptureChangedEvent}{wxmousecapturechangedevent}
 
 
@@ -1390,6 +1398,7 @@ Returns {\tt true} if this window has the current mouse capture.
 
 \helpref{wxWindow::CaptureMouse}{wxwindowcapturemouse},
 \helpref{wxWindow::ReleaseMouse}{wxwindowreleasemouse},
+\helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}
 \helpref{wxMouseCaptureChangedEvent}{wxmousecapturechangedevent}
 
 
@@ -2361,6 +2370,7 @@ Releases mouse input captured with \helpref{wxWindow::CaptureMouse}{wxwindowcapt
 \helpref{wxWindow::CaptureMouse}{wxwindowcapturemouse},
 \helpref{wxWindow::HasCapture}{wxwindowhascapture},
 \helpref{wxWindow::ReleaseMouse}{wxwindowreleasemouse},
+\helpref{wxMouseCaptureLostEvent}{wxmousecapturelostevent}
 \helpref{wxMouseCaptureChangedEvent}{wxmousecapturechangedevent}
 
 
diff --git a/include/wx/event.h b/include/wx/event.h
index 63c852c617..4de9cac013 100644
--- a/include/wx/event.h
+++ b/include/wx/event.h
@@ -238,14 +238,14 @@ BEGIN_DECLARE_EVENT_TYPES()
     DECLARE_EVENT_TYPE(wxEVT_ICONIZE, 413)
     DECLARE_EVENT_TYPE(wxEVT_MAXIMIZE, 414)
     DECLARE_EVENT_TYPE(wxEVT_MOUSE_CAPTURE_CHANGED, 415)
-    DECLARE_EVENT_TYPE(wxEVT_PAINT, 416)
-    DECLARE_EVENT_TYPE(wxEVT_ERASE_BACKGROUND, 417)
-    DECLARE_EVENT_TYPE(wxEVT_NC_PAINT, 418)
-    DECLARE_EVENT_TYPE(wxEVT_PAINT_ICON, 419)
-    DECLARE_EVENT_TYPE(wxEVT_MENU_OPEN, 420)
-    DECLARE_EVENT_TYPE(wxEVT_MENU_CLOSE, 421)
-    DECLARE_EVENT_TYPE(wxEVT_MENU_HIGHLIGHT, 422)
-    // DECLARE_EVENT_TYPE(wxEVT_POPUP_MENU_INIT, 423) -- free slot
+    DECLARE_EVENT_TYPE(wxEVT_MOUSE_CAPTURE_LOST, 416)
+    DECLARE_EVENT_TYPE(wxEVT_PAINT, 417)
+    DECLARE_EVENT_TYPE(wxEVT_ERASE_BACKGROUND, 418)
+    DECLARE_EVENT_TYPE(wxEVT_NC_PAINT, 419)
+    DECLARE_EVENT_TYPE(wxEVT_PAINT_ICON, 420)
+    DECLARE_EVENT_TYPE(wxEVT_MENU_OPEN, 421)
+    DECLARE_EVENT_TYPE(wxEVT_MENU_CLOSE, 422)
+    DECLARE_EVENT_TYPE(wxEVT_MENU_HIGHLIGHT, 423)
     DECLARE_EVENT_TYPE(wxEVT_CONTEXT_MENU, 424)
     DECLARE_EVENT_TYPE(wxEVT_SYS_COLOUR_CHANGED, 425)
     DECLARE_EVENT_TYPE(wxEVT_DISPLAY_CHANGED, 426)
@@ -1797,6 +1797,29 @@ private:
     DECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxMouseCaptureChangedEvent)
 };
 
+/*
+ wxEVT_MOUSE_CAPTURE_LOST
+ The window losing the capture receives this message, unless it released it
+ it itself or unless wxWindow::CaptureMouse was called on another window
+ (and so capture will be restored when the new capturer releases it).
+ */
+
+class WXDLLIMPEXP_CORE wxMouseCaptureLostEvent : public wxEvent
+{
+public:
+    wxMouseCaptureLostEvent(wxWindowID winid = 0)
+        : wxEvent(winid, wxEVT_MOUSE_CAPTURE_LOST)
+    {}
+
+    wxMouseCaptureLostEvent(const wxMouseCaptureLostEvent& event)
+        : wxEvent(event)
+    {}
+
+    virtual wxEvent *Clone() const { return new wxMouseCaptureLostEvent(*this); }
+
+    DECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxMouseCaptureLostEvent)
+};
+
 /*
  wxEVT_DISPLAY_CHANGED
  */
@@ -2584,6 +2607,7 @@ typedef void (wxEvtHandler::*wxNotifyEventFunction)(wxNotifyEvent&);
 typedef void (wxEvtHandler::*wxHelpEventFunction)(wxHelpEvent&);
 typedef void (wxEvtHandler::*wxContextMenuEventFunction)(wxContextMenuEvent&);
 typedef void (wxEvtHandler::*wxMouseCaptureChangedEventFunction)(wxMouseCaptureChangedEvent&);
+typedef void (wxEvtHandler::*wxMouseCaptureLostEventFunction)(wxMouseCaptureLostEvent&);
 typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent&);
 
 // these typedefs don't have the same name structure as the others, keep for
@@ -2665,6 +2689,8 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent&
     (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(wxContextMenuEventFunction, &func)
 #define wxMouseCaptureChangedEventHandler(func) \
     (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(wxMouseCaptureChangedEventFunction, &func)
+#define wxMouseCaptureLostEventHandler(func) \
+    (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(wxMouseCaptureLostEventFunction, &func)
 #define wxClipboardTextEventHandler(func) \
     (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(wxClipboardTextEventFunction, &func)
 
@@ -2883,6 +2909,7 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent&
 #define EVT_WINDOW_DESTROY(func) wx__DECLARE_EVT0(wxEVT_DESTROY, wxWindowDestroyEventHandler(func))
 #define EVT_SET_CURSOR(func) wx__DECLARE_EVT0(wxEVT_SET_CURSOR, wxSetCursorEventHandler(func))
 #define EVT_MOUSE_CAPTURE_CHANGED(func) wx__DECLARE_EVT0(wxEVT_MOUSE_CAPTURE_CHANGED, wxMouseCaptureChangedEventHandler(func))
+#define EVT_MOUSE_CAPTURE_LOST(func) wx__DECLARE_EVT0(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(func))
 
 // Mouse events
 #define EVT_LEFT_DOWN(func) wx__DECLARE_EVT0(wxEVT_LEFT_DOWN, wxMouseEventHandler(func))
diff --git a/include/wx/html/htmlwin.h b/include/wx/html/htmlwin.h
index dd22df3b2d..c79131c587 100644
--- a/include/wx/html/htmlwin.h
+++ b/include/wx/html/htmlwin.h
@@ -409,6 +409,7 @@ protected:
     void OnCopy(wxCommandEvent& event);
     void OnMouseEnter(wxMouseEvent& event);
     void OnMouseLeave(wxMouseEvent& event);
+    void OnMouseCaptureLost(wxMouseCaptureLostEvent& event);
 #endif // wxUSE_CLIPBOARD
 
     // Returns new filter (will be stored into m_DefaultFilter variable)
diff --git a/include/wx/window.h b/include/wx/window.h
index 8f76c9e7e7..338073b554 100644
--- a/include/wx/window.h
+++ b/include/wx/window.h
@@ -1328,12 +1328,21 @@ protected:
     // implements the window variants
     virtual void DoSetWindowVariant( wxWindowVariant variant ) ;
 
+    // Must be called when mouse capture is lost to send
+    // wxMouseCaptureLostEvent to windows on capture stack. The argument is
+    // the window gaining capture or NULL if outside of wx code or none.
+    static void NotifyCaptureLost(wxWindow *gainedCapture);
+
 private:
     // contains the last id generated by NewControlId
     static int ms_lastControlId;
 
     // the stack of windows which have captured the mouse
     static struct WXDLLEXPORT wxWindowNext *ms_winCaptureNext;
+    // the window that currently has mouse capture
+    static wxWindow *ms_winCaptureCurrent;
+    // indicates if execution is inside CaptureMouse/ReleaseMouse
+    static bool ms_winCaptureChanging;
 
     DECLARE_ABSTRACT_CLASS(wxWindowBase)
     DECLARE_NO_COPY_CLASS(wxWindowBase)
diff --git a/src/common/event.cpp b/src/common/event.cpp
index a22b1b0407..5e40721437 100644
--- a/src/common/event.cpp
+++ b/src/common/event.cpp
@@ -87,6 +87,7 @@
     IMPLEMENT_DYNAMIC_CLASS(wxHelpEvent, wxCommandEvent)
     IMPLEMENT_DYNAMIC_CLASS(wxContextMenuEvent, wxCommandEvent)
     IMPLEMENT_DYNAMIC_CLASS(wxMouseCaptureChangedEvent, wxEvent)
+    IMPLEMENT_DYNAMIC_CLASS(wxMouseCaptureLostEvent, wxEvent)
     IMPLEMENT_DYNAMIC_CLASS(wxClipboardTextEvent, wxCommandEvent)
 #endif // wxUSE_GUI
 
@@ -260,6 +261,7 @@ DEFINE_EVENT_TYPE(wxEVT_SHOW)
 DEFINE_EVENT_TYPE(wxEVT_ICONIZE)
 DEFINE_EVENT_TYPE(wxEVT_MAXIMIZE)
 DEFINE_EVENT_TYPE(wxEVT_MOUSE_CAPTURE_CHANGED)
+DEFINE_EVENT_TYPE(wxEVT_MOUSE_CAPTURE_LOST)
 DEFINE_EVENT_TYPE(wxEVT_PAINT)
 DEFINE_EVENT_TYPE(wxEVT_ERASE_BACKGROUND)
 DEFINE_EVENT_TYPE(wxEVT_NC_PAINT)
diff --git a/src/common/wincmn.cpp b/src/common/wincmn.cpp
index 7ce145ca24..101c995e7f 100644
--- a/src/common/wincmn.cpp
+++ b/src/common/wincmn.cpp
@@ -2334,11 +2334,17 @@ struct WXDLLEXPORT wxWindowNext
     wxWindow *win;
     wxWindowNext *next;
 } *wxWindowBase::ms_winCaptureNext = NULL;
+wxWindow *wxWindowBase::ms_winCaptureCurrent = NULL;
+bool wxWindowBase::ms_winCaptureChanging = false;
 
 void wxWindowBase::CaptureMouse()
 {
     wxLogTrace(_T("mousecapture"), _T("CaptureMouse(%p)"), wx_static_cast(void*, this));
 
+    wxASSERT_MSG( !ms_winCaptureChanging, _T("recursive CaptureMouse call?") );
+
+    ms_winCaptureChanging = true;
+
     wxWindow *winOld = GetCapture();
     if ( winOld )
     {
@@ -2353,19 +2359,28 @@ void wxWindowBase::CaptureMouse()
     //else: no mouse capture to save
 
     DoCaptureMouse();
+    ms_winCaptureCurrent = (wxWindow*)this;
+
+    ms_winCaptureChanging = false;
 }
 
 void wxWindowBase::ReleaseMouse()
 {
     wxLogTrace(_T("mousecapture"), _T("ReleaseMouse(%p)"), wx_static_cast(void*, this));
 
+    wxASSERT_MSG( !ms_winCaptureChanging, _T("recursive ReleaseMouse call?") );
+
     wxASSERT_MSG( GetCapture() == this, wxT("attempt to release mouse, but this window hasn't captured it") );
 
+    ms_winCaptureChanging = true;
+
     DoReleaseMouse();
+    ms_winCaptureCurrent = NULL;
 
     if ( ms_winCaptureNext )
     {
         ((wxWindowBase*)ms_winCaptureNext->win)->DoCaptureMouse();
+        ms_winCaptureCurrent = ms_winCaptureNext->win;
 
         wxWindowNext *item = ms_winCaptureNext;
         ms_winCaptureNext = item->next;
@@ -2373,11 +2388,51 @@ void wxWindowBase::ReleaseMouse()
     }
     //else: stack is empty, no previous capture
 
+    ms_winCaptureChanging = false;
+
     wxLogTrace(_T("mousecapture"),
         (const wxChar *) _T("After ReleaseMouse() mouse is captured by %p"),
         wx_static_cast(void*, GetCapture()));
 }
 
+static void DoNotifyWindowAboutCaptureLost(wxWindow *win)
+{
+    wxMouseCaptureLostEvent event(win->GetId());
+    event.SetEventObject(win);
+    bool processed = win->GetEventHandler()->ProcessEvent(event);
+
+    wxASSERT_MSG( processed,
+                  _T("window that captured mouse didn't process wxEVT_MOUSE_CAPTURE_LOST") );
+}
+
+/* static */
+void wxWindowBase::NotifyCaptureLost(wxWindow *gainedCapture)
+{
+    // don't do anything if capture lost was expected, i.e. resulted from
+    // a wx call to ReleaseMouse or CaptureMouse:
+    if ( ms_winCaptureChanging )
+        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 )
+    {
+        DoNotifyWindowAboutCaptureLost(ms_winCaptureCurrent);
+        ms_winCaptureCurrent = NULL;
+    }
+
+    while ( ms_winCaptureNext )
+    {
+        wxWindowNext *item = ms_winCaptureNext;
+        ms_winCaptureNext = item->next;
+
+        DoNotifyWindowAboutCaptureLost(item->win);
+
+        delete item;
+    }
+}
+
 #if wxUSE_HOTKEY
 
 bool
diff --git a/src/html/htmlwin.cpp b/src/html/htmlwin.cpp
index aee43219c0..19bc93a9da 100644
--- a/src/html/htmlwin.cpp
+++ b/src/html/htmlwin.cpp
@@ -1135,6 +1135,19 @@ void wxHtmlWindow::OnMouseUp(wxMouseEvent& event)
     wxHtmlWindowMouseHelper::HandleMouseClick(m_Cell, pos, event);
 }
 
+#if wxUSE_CLIPBOARD
+void wxHtmlWindow::OnMouseCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
+{
+    if ( !m_makingSelection )
+        return;
+
+    // discard the selecting operation
+    m_makingSelection = false;
+    wxDELETE(m_selection);
+    m_tmpSelFromCell = NULL;
+    Refresh();
+}
+#endif // wxUSE_CLIPBOARD
 
 
 void wxHtmlWindow::OnInternalIdle()
@@ -1499,6 +1512,7 @@ BEGIN_EVENT_TABLE(wxHtmlWindow, wxScrolledWindow)
     EVT_LEFT_DCLICK(wxHtmlWindow::OnDoubleClick)
     EVT_ENTER_WINDOW(wxHtmlWindow::OnMouseEnter)
     EVT_LEAVE_WINDOW(wxHtmlWindow::OnMouseLeave)
+    EVT_MOUSE_CAPTURE_LOST(wxHtmlWindow::OnMouseCaptureLost)
     EVT_KEY_UP(wxHtmlWindow::OnKeyUp)
     EVT_MENU(wxID_COPY, wxHtmlWindow::OnCopy)
 #endif // wxUSE_CLIPBOARD
diff --git a/src/msw/window.cpp b/src/msw/window.cpp
index 0515fe88be..2d079a5918 100644
--- a/src/msw/window.cpp
+++ b/src/msw/window.cpp
@@ -4107,9 +4107,14 @@ bool wxWindowMSW::HandlePaletteChanged(WXHWND hWndPalChange)
 
 bool wxWindowMSW::HandleCaptureChanged(WXHWND hWndGainedCapture)
 {
-    wxMouseCaptureChangedEvent event(GetId(), wxFindWinFromHandle(hWndGainedCapture));
-    event.SetEventObject(this);
+    wxWindow *win = wxFindWinFromHandle(hWndGainedCapture);
+
+    // notify windows on the capture stack about lost capture
+    // (see http://sourceforge.net/tracker/index.php?func=detail&aid=1153662&group_id=9863&atid=109863):
+    wxWindowBase::NotifyCaptureLost(win);
 
+    wxMouseCaptureChangedEvent event(GetId(), win);
+    event.SetEventObject(this);
     return GetEventHandler()->ProcessEvent(event);
 }
 
-- 
2.50.0

