]> git.saurik.com Git - wxWidgets.git/commitdiff
Implement mouse entered, exited, and synthesize move events while the mouse is inside.
authorDavid Elliott <dfe@tgwbd.org>
Mon, 28 May 2007 04:22:10 +0000 (04:22 +0000)
committerDavid Elliott <dfe@tgwbd.org>
Mon, 28 May 2007 04:22:10 +0000 (04:22 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@46229 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/cocoa/NSView.h
include/wx/cocoa/objc/NSView.h
include/wx/cocoa/trackingrectmanager.h [new file with mode: 0644]
include/wx/cocoa/window.h
src/cocoa/NSView.mm
src/cocoa/control.mm
src/cocoa/window.mm

index 37e70160835d6f540a99f687c76ea08d56d3f50a..e59dfdbbd2b9596b02bf1e9d4fe428ee474b77ca 100644 (file)
@@ -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() { }
 };
 
index 2f2ab86ff719ea8f012549b25052cce9e0badb1c..885ed39b6c0afe3a3f7af4cc79888a4479414d41 100644 (file)
@@ -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 (file)
index 0000000..3712b0d
--- /dev/null
@@ -0,0 +1,38 @@
+/////////////////////////////////////////////////////////////////////////////
+// Name:        wx/cocoa/trackingrectmanager.h
+// Purpose:     wxCocoaTrackingRectManager
+// Notes:       Source in window.mm
+// Author:      David Elliott <dfe@cox.net>
+// 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 <CoreFoundation/CFRunLoop.h>
+
+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__
index c8b36b275a2e50d42e565475e17a6162595982b3..b30b23ac1d947e95ae112814f992b48c17ce28a9 100644 (file)
@@ -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);
index b794f269497abcbacf1cae19b532a9f06a9cdfb4..5c4b2a5680110e7ec258e1e644a46b9766d01367 100644 (file)
@@ -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)
 
index e45aae8bf74305ec3eb7aae17f96f04ba0a6ad3e..0e498cb52999f9906973b1b270f0fe69872ed882 100644 (file)
@@ -18,6 +18,7 @@
 #endif
 
 #include "wx/cocoa/autorelease.h"
+#include "wx/cocoa/trackingrectmanager.h"
 #include "wx/cocoa/objc/objc_uniquifying.h"
 
 #import <AppKit/NSControl.h>
@@ -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;
 }
 
index 09736de17386e96a44c5e9815ce0ea6d734cdfe5..83874d9ad0f167de021140d10015e7213eb5c0ae 100644 (file)
@@ -22,7 +22,9 @@
 
 #include "wx/cocoa/autorelease.h"
 #include "wx/cocoa/string.h"
+#include "wx/cocoa/trackingrectmanager.h"
 
+#import <Foundation/NSRunLoop.h>
 #include "wx/cocoa/objc/NSView.h"
 #import <AppKit/NSEvent.h>
 #import <AppKit/NSScrollView.h>
@@ -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<wxCocoaNSView*>(info);
+        win->Cocoa_synthesizeMouseMoved();
+    }
+}
+
+void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
+{
+    CFRunLoopObserverContext observerContext =
+    {   0
+    ,   static_cast<wxCocoaNSView*>(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]);
+}
+