wxASSERT(childView);
[m_cocoaNSView addSubview: childView];
- child->m_isShown = !m_cocoaHider;
}
void wxWindowCocoa::CocoaRemoveFromParent(void)
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 5;
}
-void wxWindow::GetTextExtent(const wxString& string, int *x, int *y,
- int *descent, int *externalLeading, const wxFont *theFont) const
+void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
+ int *outDescent, int *outExternalLeading, const wxFont *inFont) const
{
- // TODO
+ // FIXME: This obviously ignores the window's font (if any) along with any size
+ // transformations. However, it's better than nothing.
+ // We don't create a wxClientDC because we don't want to accidently be able to use
+ // it for drawing.
+ wxDC tmpdc;
+ return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
}
// Coordinates relative to the window
// TODO
}
+static inline int _DoFixupDistance(int vDistance, int cDistance)
+{
+ // If the virtual distance is wxDefaultCoord, set it to the client distance
+ // This definitely has to be done or else we literally get views with a -1 size component!
+ if(vDistance == wxDefaultCoord)
+ vDistance = cDistance;
+ // NOTE: Since cDistance should always be >= 0 and since wxDefaultCoord is -1, the above
+ // test is more or less useless because it gets covered by the next one. However, just in
+ // case anyone decides that the next test is not correct, I want them to be aware that
+ // the above test would still be needed.
+
+ // I am not entirely sure about this next one but I believe it makes sense because
+ // otherwise the virtual view (which is the m_cocoaNSView that got wrapped by the scrolling
+ // machinery) can be smaller than the NSClipView (the client area) which
+ // means that, for instance, mouse clicks inside the client area as wx sees it but outside
+ // the virtual area as wx sees it won't be seen by the m_cocoaNSView.
+ // We make the assumption that if a virtual distance is less than the client distance that
+ // the real view must already be or will soon be positioned at coordinate 0 within the
+ // NSClipView that represents the client area. This way, when we increase the distance to
+ // be the client distance, the real view will exactly fit in the clip view.
+ else if(vDistance < cDistance)
+ vDistance = cDistance;
+ return vDistance;
+}
+
void wxWindow::DoSetVirtualSize( int x, int y )
{
+ // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
+ // possibly not what the caller passed in. For example, the current implementation
+ // clamps the width and height to within the min/max virtual ranges.
+ // wxDefaultCoord is passed through unchanged which means we need to handle it ourselves
+ // which we do by using the _DoFixupDistance helper method.
wxWindowBase::DoSetVirtualSize(x,y);
+ // Create the scroll view if it hasn't been already.
CocoaCreateNSScrollView();
- [m_cocoaNSView setFrameSize:NSMakeSize(m_virtualSize.x,m_virtualSize.y)];
+ // Now use fixed-up distances when setting the frame size
+ wxSize clientSize = GetClientSize();
+ [m_cocoaNSView setFrameSize:NSMakeSize(_DoFixupDistance(m_virtualSize.x, clientSize.x), _DoFixupDistance(m_virtualSize.y, clientSize.y))];
}
bool wxWindow::SetFont(const wxFont& font)
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.
void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
{
m_registeredViews.push_back(aView);
- wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Registered wxCocoaNSView=%p"), aView);
+ wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
{
void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
{
m_registeredViews.remove(aView);
- wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Unregistered wxCocoaNSView=%p"), aView);
+ wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
if(m_registeredViews.empty() && m_runLoopObserver != NULL)
{
RemoveRunLoopObserver();
, NULL
, NULL
};
- m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
+
+ // 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"));
}
{
[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();
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);
}
}
s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
}
+void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
+{
+ 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()
{
ClearTrackingRect();