#include "wx/cocoa/autorelease.h"
#include "wx/cocoa/string.h"
#include "wx/cocoa/trackingrectmanager.h"
+#include "wx/mac/corefoundation/cfref.h"
#import <Foundation/NSArray.h>
#import <Foundation/NSRunLoop.h>
#import <AppKit/NSBezierPath.h>
#endif //def WXCOCOA_FILL_DUMMY_VIEW
+// STL list used by wxCocoaMouseMovedEventSynthesizer
+#include <list>
+
/* NSComparisonResult is typedef'd as an enum pre-Leopard but typedef'd as
* NSInteger post-Leopard. Pre-Leopard the Cocoa toolkit expects a function
* returning int and not NSComparisonResult. Post-Leopard the Cocoa toolkit
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);
+ wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
return GetEventHandler()->ProcessEvent(event);
}
else
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);
+ wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
return GetEventHandler()->ProcessEvent(event);
}
else
bool wxWindowCocoa::Cocoa_resetCursorRects()
{
wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
+
+ // When we are called there may be a queued tracking rect event (mouse entered or exited) and
+ // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
+ // mouse exited event. If the control happens to share the edge with its parent window which is
+ // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
+ // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
+ // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
+ // which willl in turn call us before exiting for the next user event.
+
+ // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
+ // our mouse exited event because the already queued event will have the old tracking rect
+ // tag. The simple solution is to only rebuild our tracking rect if we need to.
+
if(m_visibleTrackingRectManager != NULL)
- m_visibleTrackingRectManager->RebuildTrackingRect();
+ m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
if(!m_cursor.GetNSCursor())
return false;
{
if(!wxWindowBase::SetCursor(cursor))
return false;
+
+ // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
+ // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
+ // rects and thus won't ever call resetCursorRects.
+ [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
+
// Invalidate the cursor rects so the cursor will change
+ // Note that it is not enough to remove the old one (if any) and add the new one.
+ // For the rects to work properly, Cocoa itself must call resetCursorRects.
[[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
return true;
}
return NULL;
}
+// ========================================================================
+// wxCocoaMouseMovedEventSynthesizer
+// ========================================================================
+
+#define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
+
+/* This class registers one run loop observer to cover all windows registered with it.
+ * It will register the observer when the first view is registerd and unregister the
+ * observer when the last view is unregistered.
+ * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
+ * is no reason it couldn't be instantiated multiple times.
+ */
+class wxCocoaMouseMovedEventSynthesizer
+{
+ DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
+public:
+ wxCocoaMouseMovedEventSynthesizer()
+ { m_lastScreenMouseLocation = NSZeroPoint;
+ }
+ ~wxCocoaMouseMovedEventSynthesizer();
+ void RegisterWxCocoaView(wxCocoaNSView *aView);
+ void UnregisterWxCocoaView(wxCocoaNSView *aView);
+ void SynthesizeMouseMovedEvent();
+
+protected:
+ void AddRunLoopObserver();
+ void RemoveRunLoopObserver();
+ wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
+ std::list<wxCocoaNSView*> m_registeredViews;
+ NSPoint m_lastScreenMouseLocation;
+ static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
+};
+
+void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
+{
+ m_registeredViews.push_back(aView);
+ wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
+
+ if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
+ {
+ AddRunLoopObserver();
+ }
+}
+
+void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
+{
+ m_registeredViews.remove(aView);
+ wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
+ if(m_registeredViews.empty() && m_runLoopObserver != NULL)
+ {
+ RemoveRunLoopObserver();
+ }
+}
+
+wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
+{
+ if(!m_registeredViews.empty())
+ {
+ // This means failure to clean up so we report on it as a debug message.
+ wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
+ m_registeredViews.clear();
+ }
+ if(m_runLoopObserver != NULL)
+ {
+ // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
+ wxLogDebug(wxT("Removing run loop observer during static destruction time."));
+ RemoveRunLoopObserver();
+ }
+}
+
+void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
+{
+ reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
+}
+
+void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
+{
+ CFRunLoopObserverContext observerContext =
+ { 0
+ , this
+ , NULL
+ , NULL
+ , NULL
+ };
+
+ // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
+ // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
+
+ // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
+ // in our observer procedure being called before the run loop has decided that it is going to return control to
+ // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
+ // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
+
+ // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
+ // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
+ // around to it before letting the event loop do its work. This has some very odd effects on the way the
+ // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
+ // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
+ // time the new one is built.
+
+ // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
+ // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
+ // the mouseExited: message to be sent properly.
+
+ m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
+ CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
+ wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
+}
+
+void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
+{
+ CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
+ m_runLoopObserver.reset();
+ wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
+}
+
+void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
+{
+ NSPoint screenMouseLocation = [NSEvent mouseLocation];
+ // Checking the last mouse location is done for a few reasons:
+ // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
+ // telling the app the mouse moved when the user hit a key for instance.
+ // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
+ // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
+ // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
+ // loop causing the tracking rectangles to constantly be reset.
+ if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
+ {
+ m_lastScreenMouseLocation = screenMouseLocation;
+ wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
+ for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
+ {
+ (*i)->Cocoa_synthesizeMouseMoved();
+ }
+ }
+}
+
+// Singleton used for all views:
+static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
// ========================================================================
// wxCocoaTrackingRectManager
: m_window(window)
{
m_isTrackingRectActive = false;
- m_runLoopObserver = NULL;
BuildTrackingRect();
}
{
[m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
m_isTrackingRectActive = false;
+ wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
}
// 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;
- }
+ s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
}
void wxCocoaTrackingRectManager::BuildTrackingRect()
wxAutoNSAutoreleasePool pool;
wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
- if([m_window->GetNSView() window] != nil)
+
+ NSView *theView = m_window->GetNSView();
+
+ if([theView window] != nil)
{
- m_trackingRectTag = [m_window->GetNSView() addTrackingRect:[m_window->GetNSView() visibleRect] owner:m_window->GetNSView() userData:NULL assumeInside:NO];
+ NSRect visibleRect = [theView visibleRect];
+
+ m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
+ m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
m_isTrackingRectActive = true;
+
+ wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
}
}
-static NSPoint s_lastScreenMouseLocation = NSZeroPoint;
-
-static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
+void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
{
- NSPoint screenMouseLocation = [NSEvent mouseLocation];
- if(screenMouseLocation.x != s_lastScreenMouseLocation.x || screenMouseLocation.y != s_lastScreenMouseLocation.y)
- {
- wxCocoaNSView *win = reinterpret_cast<wxCocoaNSView*>(info);
- win->Cocoa_synthesizeMouseMoved();
- }
+ s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
}
-void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
+void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
{
- 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);
+ if(m_isTrackingRectActive)
+ {
+ NSView *theView = m_window->GetNSView();
+ NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
+ if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
+ {
+ wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
+ return;
+ }
+ }
+ RebuildTrackingRect();
}
void wxCocoaTrackingRectManager::RebuildTrackingRect()