X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8ea3a63ea9b48da919a5f9eb3c60e137dc333db8..30cfcda5b541557c598726d272724e2ab1cee45a:/src/cocoa/window.mm diff --git a/src/cocoa/window.mm b/src/cocoa/window.mm index d89d7de1a5..01c776073b 100644 --- a/src/cocoa/window.mm +++ b/src/cocoa/window.mm @@ -540,7 +540,6 @@ void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child) wxASSERT(childView); [m_cocoaNSView addSubview: childView]; - child->m_isShown = !m_cocoaHider; } void wxWindowCocoa::CocoaRemoveFromParent(void) @@ -711,7 +710,7 @@ bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent) 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 @@ -726,7 +725,7 @@ bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent) 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 @@ -811,8 +810,21 @@ void wxWindowCocoa::Cocoa_FrameChanged(void) 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; @@ -826,7 +838,15 @@ bool wxWindowCocoa::SetCursor(const wxCursor &cursor) { 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; } @@ -1153,10 +1173,15 @@ int wxWindow::GetCharWidth() const 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 @@ -1212,11 +1237,44 @@ void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect) // 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) @@ -1412,11 +1470,12 @@ wxWindow* wxFindWindowAtPointer(wxPoint& pt) 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. @@ -1447,7 +1506,7 @@ protected: 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) { @@ -1458,7 +1517,7 @@ void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView 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(); @@ -1495,7 +1554,27 @@ void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver() , 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")); } @@ -1548,6 +1627,7 @@ void wxCocoaTrackingRectManager::ClearTrackingRect() { [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(); @@ -1564,10 +1644,18 @@ 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); } } @@ -1576,6 +1664,21 @@ void wxCocoaTrackingRectManager::BeginSynthesizingEvents() 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();