From 7c5a378ff21de766bab2fd76293be1008610afc2 Mon Sep 17 00:00:00 2001 From: David Elliott Date: Mon, 28 May 2007 04:22:10 +0000 Subject: [PATCH] Implement mouse entered, exited, and synthesize move events while the mouse is inside. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@46229 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/cocoa/NSView.h | 5 + include/wx/cocoa/objc/NSView.h | 2 + include/wx/cocoa/trackingrectmanager.h | 38 ++++++ include/wx/cocoa/window.h | 6 + src/cocoa/NSView.mm | 22 ++++ src/cocoa/control.mm | 20 +++ src/cocoa/window.mm | 174 ++++++++++++++++++++++++- 7 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 include/wx/cocoa/trackingrectmanager.h diff --git a/include/wx/cocoa/NSView.h b/include/wx/cocoa/NSView.h index 37e7016083..e59dfdbbd2 100644 --- a/include/wx/cocoa/NSView.h +++ b/include/wx/cocoa/NSView.h @@ -32,6 +32,7 @@ public: virtual wxWindow* GetWxWindow() const { return NULL; } virtual void Cocoa_FrameChanged(void) = 0; + virtual void Cocoa_synthesizeMouseMoved(void) = 0; virtual bool Cocoa_acceptsFirstMouse(bool &acceptsFirstMouse, WX_NSEvent theEvent) { return false; } virtual bool Cocoa_drawRect(const NSRect &rect) @@ -62,6 +63,10 @@ public: { return false; } virtual bool Cocoa_resetCursorRects() { return false; } + virtual bool Cocoa_viewDidMoveToWindow() + { return false; } + virtual bool Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow) + { return false; } virtual ~wxCocoaNSView() { } }; diff --git a/include/wx/cocoa/objc/NSView.h b/include/wx/cocoa/objc/NSView.h index 2f2ab86ff7..885ed39b6c 100644 --- a/include/wx/cocoa/objc/NSView.h +++ b/include/wx/cocoa/objc/NSView.h @@ -37,6 +37,8 @@ - (void)otherMouseDragged:(NSEvent *)theEvent; - (void)otherMouseUp:(NSEvent *)theEvent; - (void)resetCursorRects; +- (void)viewDidMoveToWindow; +- (void)viewWillMoveToWindow:(NSWindow *)newWindow; @end // WXNSView WX_DECLARE_GET_OBJC_CLASS(WXNSView,NSView) diff --git a/include/wx/cocoa/trackingrectmanager.h b/include/wx/cocoa/trackingrectmanager.h new file mode 100644 index 0000000000..3712b0d87a --- /dev/null +++ b/include/wx/cocoa/trackingrectmanager.h @@ -0,0 +1,38 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wx/cocoa/trackingrectmanager.h +// Purpose: wxCocoaTrackingRectManager +// Notes: Source in window.mm +// Author: David Elliott +// Modified by: +// Created: 2007/05/02 +// RCS-ID: $Id$ +// Copyright: (c) 2007 Software 2000 Ltd. +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// +#ifndef __WX_COCOA_TRACKINGRECTMANAGER_H__ +#define __WX_COCOA_TRACKINGRECTMANAGER_H__ + +#include + +class wxCocoaTrackingRectManager +{ + DECLARE_NO_COPY_CLASS(wxCocoaTrackingRectManager) +public: + wxCocoaTrackingRectManager(wxWindow *window); + void ClearTrackingRect(); + void BuildTrackingRect(); + void RebuildTrackingRect(); + bool IsOwnerOfEvent(NSEvent *anEvent); + ~wxCocoaTrackingRectManager(); + void BeginSynthesizingEvents(); + void StopSynthesizingEvents(); +protected: + wxWindow *m_window; + bool m_isTrackingRectActive; + int m_trackingRectTag; + CFRunLoopObserverRef m_runLoopObserver; +private: + wxCocoaTrackingRectManager(); +}; + +#endif // ndef __WX_COCOA_TRACKINGRECTMANAGER_H__ diff --git a/include/wx/cocoa/window.h b/include/wx/cocoa/window.h index c8b36b275a..b30b23ac1d 100644 --- a/include/wx/cocoa/window.h +++ b/include/wx/cocoa/window.h @@ -22,6 +22,7 @@ DECLARE_WXCOCOA_OBJC_CLASS(NSAffineTransform); class wxWindowCocoaHider; class wxWindowCocoaScrollView; +class wxCocoaTrackingRectManager; // ======================================================================== // wxWindowCocoa @@ -33,6 +34,7 @@ class WXDLLEXPORT wxWindowCocoa: public wxWindowBase, protected wxCocoaNSView DECLARE_EVENT_TABLE() friend wxWindow *wxWindowBase::GetCapture(); friend class wxWindowCocoaScrollView; + friend class wxCocoaTrackingRectManager; // ------------------------------------------------------------------------ // initialization // ------------------------------------------------------------------------ @@ -88,6 +90,7 @@ protected: void InitMouseEvent(wxMouseEvent &event, WX_NSEvent cocoaEvent); virtual wxWindow* GetWxWindow() const; virtual void Cocoa_FrameChanged(void); + virtual void Cocoa_synthesizeMouseMoved(void); virtual bool Cocoa_drawRect(const NSRect &rect); virtual bool Cocoa_mouseDown(WX_NSEvent theEvent); virtual bool Cocoa_mouseDragged(WX_NSEvent theEvent); @@ -102,11 +105,14 @@ protected: virtual bool Cocoa_otherMouseDragged(WX_NSEvent theEvent); virtual bool Cocoa_otherMouseUp(WX_NSEvent theEvent); virtual bool Cocoa_resetCursorRects(); + virtual bool Cocoa_viewDidMoveToWindow(); + virtual bool Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow); void SetNSView(WX_NSView cocoaNSView); WX_NSView m_cocoaNSView; wxWindowCocoaHider *m_cocoaHider; wxWindowCocoaScrollView *m_wxCocoaScrollView; bool m_isInPaint; + wxCocoaTrackingRectManager *m_visibleTrackingRectManager; static wxWindow *sm_capturedWindow; virtual void CocoaReplaceView(WX_NSView oldView, WX_NSView newView); void SetInitialFrameRect(const wxPoint& pos, const wxSize& size); diff --git a/src/cocoa/NSView.mm b/src/cocoa/NSView.mm index b794f26949..5c4b2a5680 100644 --- a/src/cocoa/NSView.mm +++ b/src/cocoa/NSView.mm @@ -166,6 +166,20 @@ void wxCocoaNSView::DisassociateNSView(WX_NSView cocoaNSView) [super resetCursorRects]; } +- (void)viewDidMoveToWindow +{ + wxCocoaNSView *win = wxCocoaNSView::GetFromCocoa(self); + if( !win || !win->Cocoa_viewDidMoveToWindow() ) + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow +{ + wxCocoaNSView *win = wxCocoaNSView::GetFromCocoa(self); + if( !win || !win->Cocoa_viewWillMoveToWindow(newWindow) ) + [super viewWillMoveToWindow:newWindow]; +} + @end // implementation WXNSView WX_IMPLEMENT_GET_OBJC_CLASS(WXNSView,NSView) @@ -178,6 +192,7 @@ WX_IMPLEMENT_GET_OBJC_CLASS(WXNSView,NSView) } - (void)notificationFrameChanged: (NSNotification *)notification; +- (void)synthesizeMouseMovedForView: (NSView *)theView; @end // interface wxNSViewNotificationObserver WX_DECLARE_GET_OBJC_CLASS(wxNSViewNotificationObserver,NSObject) @@ -190,6 +205,13 @@ WX_DECLARE_GET_OBJC_CLASS(wxNSViewNotificationObserver,NSObject) win->Cocoa_FrameChanged(); } +- (void)synthesizeMouseMovedForView: (NSView *)theView +{ + wxCocoaNSView *win = wxCocoaNSView::GetFromCocoa(theView); + wxCHECK_RET(win,wxT("synthesizeMouseMovedForView received but no wxWindow exists")); + win->Cocoa_synthesizeMouseMoved(); +} + @end // implementation wxNSViewNotificationObserver WX_IMPLEMENT_GET_OBJC_CLASS(wxNSViewNotificationObserver,NSObject) diff --git a/src/cocoa/control.mm b/src/cocoa/control.mm index e45aae8bf7..0e498cb529 100644 --- a/src/cocoa/control.mm +++ b/src/cocoa/control.mm @@ -18,6 +18,7 @@ #endif #include "wx/cocoa/autorelease.h" +#include "wx/cocoa/trackingrectmanager.h" #include "wx/cocoa/objc/objc_uniquifying.h" #import @@ -44,6 +45,8 @@ - (void)otherMouseDragged:(NSEvent *)theEvent; - (void)otherMouseUp:(NSEvent *)theEvent; - (void)resetCursorRects; +- (void)viewDidMoveToWindow; +- (void)viewWillMoveToWindow:(NSWindow *)newWindow; @end // wxNonControlNSControl WX_DECLARE_GET_OBJC_CLASS(wxNonControlNSControl,NSControl) @@ -156,6 +159,20 @@ WX_DECLARE_GET_OBJC_CLASS(wxNonControlNSControl,NSControl) [super resetCursorRects]; } +- (void)viewDidMoveToWindow +{ + wxCocoaNSView *win = wxCocoaNSView::GetFromCocoa(self); + if( !win || !win->Cocoa_viewDidMoveToWindow() ) + [super viewDidMoveToWindow]; +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow +{ + wxCocoaNSView *win = wxCocoaNSView::GetFromCocoa(self); + if( !win || !win->Cocoa_viewWillMoveToWindow(newWindow) ) + [super viewWillMoveToWindow:newWindow]; +} + @end // wxNonControlNSControl WX_IMPLEMENT_GET_OBJC_CLASS(wxNonControlNSControl,NSControl) @@ -184,6 +201,9 @@ bool wxControl::Create(wxWindow *parent, wxWindowID winid, m_parent->CocoaAddChild(this); SetInitialFrameRect(pos,size); + // Controls should have a viewable-area tracking rect by default + m_visibleTrackingRectManager = new wxCocoaTrackingRectManager(this); + return true; } diff --git a/src/cocoa/window.mm b/src/cocoa/window.mm index 09736de173..83874d9ad0 100644 --- a/src/cocoa/window.mm +++ b/src/cocoa/window.mm @@ -22,7 +22,9 @@ #include "wx/cocoa/autorelease.h" #include "wx/cocoa/string.h" +#include "wx/cocoa/trackingrectmanager.h" +#import #include "wx/cocoa/objc/NSView.h" #import #import @@ -110,6 +112,7 @@ protected: wxWindowCocoa *m_owner; WX_NSView m_dummyNSView; virtual void Cocoa_FrameChanged(void); + virtual void Cocoa_synthesizeMouseMoved(void) {} #ifdef WXCOCOA_FILL_DUMMY_VIEW virtual bool Cocoa_drawRect(const NSRect& rect); #endif //def WXCOCOA_FILL_DUMMY_VIEW @@ -135,6 +138,7 @@ protected: wxWindowCocoa *m_owner; WX_NSScrollView m_cocoaNSScrollView; virtual void Cocoa_FrameChanged(void); + virtual void Cocoa_synthesizeMouseMoved(void) {} private: wxWindowCocoaScrollView(); }; @@ -317,6 +321,7 @@ void wxWindowCocoa::Init() m_wxCocoaScrollView = NULL; m_isBeingDeleted = false; m_isInPaint = false; + m_visibleTrackingRectManager = NULL; } // Constructor @@ -377,6 +382,10 @@ void wxWindowCocoa::CocoaRemoveFromParent(void) void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView) { + // Clear the visible area tracking rect if we have one. + delete m_visibleTrackingRectManager; + m_visibleTrackingRectManager = NULL; + bool need_debug = cocoaNSView || m_cocoaNSView; if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]); DisassociateNSView(m_cocoaNSView); @@ -495,18 +504,65 @@ bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent) { wxMouseEvent event(wxEVT_MOTION); InitMouseEvent(event,theEvent); - wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y); + wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y); return GetEventHandler()->ProcessEvent(event); } +void wxWindowCocoa::Cocoa_synthesizeMouseMoved() +{ + wxMouseEvent event(wxEVT_MOTION); + NSWindow *window = [GetNSView() window]; + NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream]; + NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil]; + + NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint); + // FIXME: Should we be adjusting for client area origin? + const wxPoint &clientorigin = GetClientAreaOrigin(); + event.m_x = (wxCoord)pointWx.x - clientorigin.x; + event.m_y = (wxCoord)pointWx.y - clientorigin.y; + + // TODO: Handle shift, control, alt, meta flags + event.SetEventObject(this); + event.SetId(GetId()); + + wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y); + GetEventHandler()->ProcessEvent(event); +} + bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent) { - return false; + if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent)) + { + m_visibleTrackingRectManager->BeginSynthesizingEvents(); + + // Although we synthesize the mouse moved events we don't poll for them but rather send them only when + // some other event comes in. That other event is (guess what) mouse moved events that will be sent + // to the NSWindow which will forward them on to the first responder. We are not likely to be the + // first responder, so the mouseMoved: events are effectively discarded. + [[GetNSView() window] setAcceptsMouseMovedEvents:YES]; + + wxMouseEvent event(wxEVT_ENTER_WINDOW); + InitMouseEvent(event,theEvent); + wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Mouse Entered @%d,%d"),this,event.m_x,event.m_y); + return GetEventHandler()->ProcessEvent(event); + } + else + return false; } bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent) { - return false; + if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent)) + { + m_visibleTrackingRectManager->StopSynthesizingEvents(); + + wxMouseEvent event(wxEVT_LEAVE_WINDOW); + InitMouseEvent(event,theEvent); + wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Mouse Exited @%d,%d"),this,event.m_x,event.m_y); + return GetEventHandler()->ProcessEvent(event); + } + else + return false; } bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent) @@ -576,7 +632,9 @@ bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent) void wxWindowCocoa::Cocoa_FrameChanged(void) { - wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_FrameChanged")); + wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this); + if(m_visibleTrackingRectManager != NULL) + m_visibleTrackingRectManager->RebuildTrackingRect(); wxSizeEvent event(GetSize(), m_windowId); event.SetEventObject(this); GetEventHandler()->ProcessEvent(event); @@ -584,6 +642,10 @@ void wxWindowCocoa::Cocoa_FrameChanged(void) bool wxWindowCocoa::Cocoa_resetCursorRects() { + wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this); + if(m_visibleTrackingRectManager != NULL) + m_visibleTrackingRectManager->RebuildTrackingRect(); + if(!m_cursor.GetNSCursor()) return false; @@ -592,6 +654,24 @@ bool wxWindowCocoa::Cocoa_resetCursorRects() return true; } +bool wxWindowCocoa::Cocoa_viewDidMoveToWindow() +{ + wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this); + // Set up new tracking rects. I am reasonably sure the new window must be set before doing this. + if(m_visibleTrackingRectManager != NULL) + m_visibleTrackingRectManager->BuildTrackingRect(); + return false; +} + +bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow) +{ + wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow); + // Clear tracking rects. It is imperative this be done before the new window is set. + if(m_visibleTrackingRectManager != NULL) + m_visibleTrackingRectManager->ClearTrackingRect(); + return false; +} + bool wxWindow::Close(bool force) { // The only reason this function exists is that it is virtual and @@ -1036,3 +1116,89 @@ wxWindow* wxFindWindowAtPointer(wxPoint& pt) pt = wxGetMousePosition(); return NULL; } + + +// ======================================================================== +// wxCocoaTrackingRectManager +// ======================================================================== + +wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window) +: m_window(window) +{ + m_isTrackingRectActive = false; + m_runLoopObserver = NULL; + BuildTrackingRect(); +} + +void wxCocoaTrackingRectManager::ClearTrackingRect() +{ + if(m_isTrackingRectActive) + { + [m_window->GetNSView() removeTrackingRect:m_trackingRectTag]; + m_isTrackingRectActive = false; + } + // If we were doing periodic events we need to clear those too + StopSynthesizingEvents(); +} + +void wxCocoaTrackingRectManager::StopSynthesizingEvents() +{ + if(m_runLoopObserver != NULL) + { + CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes); + CFRelease(m_runLoopObserver); + m_runLoopObserver = NULL; + } +} + +void wxCocoaTrackingRectManager::BuildTrackingRect() +{ + wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared")); + if([m_window->GetNSView() window] != nil) + { + m_trackingRectTag = [m_window->GetNSView() addTrackingRect:[m_window->GetNSView() visibleRect] owner:m_window->GetNSView() userData:NULL assumeInside:NO]; + m_isTrackingRectActive = true; + } +} + +static NSPoint s_lastScreenMouseLocation = NSZeroPoint; + +static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) +{ + NSPoint screenMouseLocation = [NSEvent mouseLocation]; + if(screenMouseLocation.x != s_lastScreenMouseLocation.x || screenMouseLocation.y != s_lastScreenMouseLocation.y) + { + wxCocoaNSView *win = reinterpret_cast(info); + win->Cocoa_synthesizeMouseMoved(); + } +} + +void wxCocoaTrackingRectManager::BeginSynthesizingEvents() +{ + CFRunLoopObserverContext observerContext = + { 0 + , static_cast(m_window) + , NULL + , NULL + , NULL + }; + m_runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext); + CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes); +} + +void wxCocoaTrackingRectManager::RebuildTrackingRect() +{ + ClearTrackingRect(); + BuildTrackingRect(); +} + +wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager() +{ + ClearTrackingRect(); +} + +bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent) +{ + return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]); +} + -- 2.45.2