X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/4799f3baab3805e51cc1754b89d855d32e94c232..511383f91b1443d576276c826fdfd5286f9dbbdb:/src/cocoa/window.mm diff --git a/src/cocoa/window.mm b/src/cocoa/window.mm index caca70f317..24b9d1c64c 100644 --- a/src/cocoa/window.mm +++ b/src/cocoa/window.mm @@ -24,6 +24,7 @@ #include "wx/cocoa/string.h" #include "wx/cocoa/trackingrectmanager.h" +#import #import #include "wx/cocoa/objc/NSView.h" #import @@ -33,6 +34,7 @@ #import #import #import +#import // Turn this on to paint green over the dummy views for debugging #undef WXCOCOA_FILL_DUMMY_VIEW @@ -58,6 +60,10 @@ typedef int CocoaWindowCompareFunctionResult; - (void)getRectsBeingDrawn:(const NSRect **)rects count:(int *)count; @end +// ======================================================================== +// Helper functions for converting to/from wxWidgets coordinates and a +// specified NSView's coordinate system. +// ======================================================================== NSPoint CocoaTransformNSViewBoundsToWx(NSView *nsview, NSPoint pointBounds) { wxCHECK_MSG(nsview, pointBounds, wxT("Need to have a Cocoa view to do translation")); @@ -110,6 +116,149 @@ NSRect CocoaTransformNSViewWxToBounds(NSView *nsview, NSRect rectWx) ); } +// ============================================================================ +// Screen coordinate helpers +// ============================================================================ + +/* +General observation about Cocoa screen coordinates: +It is documented that the first object of the [NSScreen screens] array is the screen with the menubar. + +It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always +the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up. + +It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle +relative to that screen's frame. The only real way to test this is to configure two screens one atop +the other such that the menubar screen is on top. The Dock at the bottom of the screen will then +eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two +1920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and +visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin. + +In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen). +However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme. +This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen. +*/ + +namespace { // file namespace + +class wxCocoaPrivateScreenCoordinateTransformer +{ + DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer) +public: + wxCocoaPrivateScreenCoordinateTransformer(); + ~wxCocoaPrivateScreenCoordinateTransformer(); + wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame); + NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible); + +protected: + NSScreen *m_screenZero; + NSRect m_screenZeroFrame; +}; + +// NOTE: This is intended to be a short-lived object. A future enhancment might +// make it a global and reconfigure it upon some notification that the screen layout +// has changed. +inline wxCocoaPrivateScreenCoordinateTransformer::wxCocoaPrivateScreenCoordinateTransformer() +{ + NSArray *screens = [NSScreen screens]; + + [screens retain]; + + m_screenZero = nil; + if(screens != nil && [screens count] > 0) + m_screenZero = [[screens objectAtIndex:0] retain]; + + [screens release]; + + if(m_screenZero != nil) + m_screenZeroFrame = [m_screenZero frame]; + else + { + wxLogWarning(wxT("Can't translate to/from wx screen coordinates and Cocoa screen coordinates")); + // Just blindly assume 1024x768 so that at least we can sort of flip things around into + // Cocoa coordinates. + // NOTE: Theoretically this case should never happen anyway. + m_screenZeroFrame = NSMakeRect(0,0,1024,768); + } +} + +inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer() +{ + [m_screenZero release]; + m_screenZero = nil; +} + +inline wxPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame) +{ + // x and y are in wx screen coordinates which we're going to arbitrarily define such that + // (0,0) is the TOP-left of screen 0 (the one with the menubar) + // NOTE WELL: This means that (0,0) is _NOT_ an appropriate position for a window. + + wxPoint theWxOrigin; + + // Working in Cocoa's screen coordinates we must realize that the x coordinate we want is + // the distance between the left side (origin.x) of the window's frame and the left side of + // screen zero's frame. + theWxOrigin.x = windowFrame.origin.x - m_screenZeroFrame.origin.x; + + // Working in Cocoa's screen coordinates we must realize that the y coordinate we want is + // actually the distance between the top-left of the screen zero frame and the top-left + // of the window's frame. + + theWxOrigin.y = (m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height) - (windowFrame.origin.y + windowFrame.size.height); + + return theWxOrigin; +} + +inline NSPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible) +{ + NSPoint theCocoaOrigin; + + // The position is in wx screen coordinates which we're going to arbitrarily define such that + // (0,0) is the TOP-left of screen 0 (the one with the menubar) + + // NOTE: The usable rectangle is smaller and hence we have the keepOriginVisible flag + // which will move the origin downward and/or left as necessary if the origin is + // inside the screen0 rectangle (i.e. x/y >= 0 in wx coordinates) and outside the + // visible frame (i.e. x/y < the top/left of the screen0 visible frame in wx coordinates) + // We don't munge origin coordinates < 0 because it actually is possible that the menubar is on + // the top of the bottom screen and thus that origin is completely valid! + if(keepOriginVisible && (m_screenZero != nil)) + { + // Do al of this in wx coordinates because it's far simpler since we're dealing with top/left points + wxPoint visibleOrigin = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates([m_screenZero visibleFrame]); + if(x >= 0 && x < visibleOrigin.x) + x = visibleOrigin.x; + if(y >= 0 && y < visibleOrigin.y) + y = visibleOrigin.y; + } + + // The x coordinate is simple as it's just relative to screen zero's frame + theCocoaOrigin.x = m_screenZeroFrame.origin.x + x; + // Working in Cocoa's coordinates think to start at the bottom of screen zero's frame and add + // the height of that rect which gives us the coordinate for the top of the visible rect. Now realize that + // the wx coordinates are flipped so if y is say 10 then we want to be 10 pixels down from that and thus + // we subtract y. But then we still need to take into account the size of the window which is h and subtract + // that to get the bottom-left origin of the rectangle. + theCocoaOrigin.y = m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height - y - height; + + return theCocoaOrigin; +} + +} // namespace + +wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame) +{ + wxCocoaPrivateScreenCoordinateTransformer transformer; + return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame); +} + +NSPoint wxWindowCocoa::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible) +{ + wxCocoaPrivateScreenCoordinateTransformer transformer; + return transformer.OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,keepOriginVisible); +} + // ======================================================================== // wxWindowCocoaHider // ======================================================================== @@ -380,6 +529,9 @@ wxWindow::~wxWindow() void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child) { + // Pool here due to lack of one during wx init phase + wxAutoNSAutoreleasePool pool; + NSView *childView = child->GetNSViewForSuperview(); wxASSERT(childView); @@ -666,6 +818,15 @@ bool wxWindowCocoa::Cocoa_resetCursorRects() return true; } +bool wxWindowCocoa::SetCursor(const wxCursor &cursor) +{ + if(!wxWindowBase::SetCursor(cursor)) + return false; + // Invalidate the cursor rects so the cursor will change + [[GetNSView() window] invalidateCursorRectsForView:GetNSView()]; + return true; +} + bool wxWindowCocoa::Cocoa_viewDidMoveToWindow() { wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this); @@ -896,12 +1057,49 @@ void wxWindow::DoReleaseMouse() void wxWindow::DoScreenToClient(int *x, int *y) const { - // TODO + // Point in cocoa screen coordinates: + NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false); + NSView *clientView = const_cast(this)->GetNSView(); + NSWindow *theWindow = [clientView window]; + + // Point in window's base coordinate system: + NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint]; + // Point in view's bounds coordinate system + NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil]; + // Point in wx client coordinates: + NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint); + if(x!=NULL) + *x = theWxClientPoint.x; + if(y!=NULL) + *y = theWxClientPoint.y; } void wxWindow::DoClientToScreen(int *x, int *y) const { - // TODO + // Point in wx client coordinates + NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0); + + NSView *clientView = const_cast(this)->GetNSView(); + + // Point in the view's bounds coordinate system + NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint); + + // Point in the window's base coordinate system + NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil]; + + NSWindow *theWindow = [clientView window]; + // Point in Cocoa's screen coordinates + NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint]; + + // Act as though this was the origin of a 0x0 rectangle + NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0); + + // Convert that rectangle to wx coordinates + wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect); + if(*x) + *x = theWxScreenPoint.x; + if(*y) + *y = theWxScreenPoint.y; } // Get size *available for subwindows* i.e. excluding menu bar etc. @@ -942,13 +1140,13 @@ wxString wxWindow::GetLabel() const int wxWindow::GetCharHeight() const { // TODO - return 0; + return 10; } int wxWindow::GetCharWidth() const { // TODO - return 0; + return 5; } void wxWindow::GetTextExtent(const wxString& string, int *x, int *y, @@ -1019,19 +1217,82 @@ void wxWindow::DoSetVirtualSize( int x, int y ) bool wxWindow::SetFont(const wxFont& font) { - // TODO - return true; + // FIXME: We may need to handle wx font inheritance. + return wxWindowBase::SetFont(font); } -static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *target) +#if 0 // these are used when debugging the algorithm. +static char const * const comparisonresultStrings[] = +{ "<" +, "==" +, ">" +}; +#endif + +class CocoaWindowCompareContext { + DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext) +public: + CocoaWindowCompareContext(); // Not implemented + CocoaWindowCompareContext(NSView *target, NSArray *subviews) + { + m_target = target; + // Cocoa sorts subviews in-place.. make a copy + m_subviews = [subviews copy]; + } + ~CocoaWindowCompareContext() + { // release the copy + [m_subviews release]; + } + NSView* target() + { return m_target; } + NSArray* subviews() + { return m_subviews; } + /* Helper function that returns the comparison based off of the original ordering */ + CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second) + { + NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first]; + NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second]; + // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will + // likely compare higher than the other view which is reasonable considering the only way that + // can happen is if the subview was added after our call to subviews but before the call to + // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because + // that case should never occur anyway because that would imply a multi-threaded GUI call + // which is a big no-no with Cocoa. + + // Subviews are ordered from back to front meaning one that is already lower will have an lower index. + NSComparisonResult result = (firstI < secondI) + ? NSOrderedAscending /* -1 */ + : (firstI > secondI) + ? NSOrderedDescending /* 1 */ + : NSOrderedSame /* 0 */; + +#if 0 // Enable this if you need to debug the algorithm. + NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI); +#endif + return result; + } +private: + /* The subview we are trying to Raise or Lower */ + NSView *m_target; + /* A copy of the original array of subviews */ + NSArray *m_subviews; +}; + +/* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that + * the target view is always higher than every other view. When comparing two views neither of + * which is the target, it returns the correct response based on the original ordering + */ +static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx) +{ + CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx; // first should be ordered higher - if(first==target) + if(first==compareContext->target()) return NSOrderedDescending; // second should be ordered higher - if(second==target) + if(second==compareContext->target()) return NSOrderedAscending; - return NSOrderedSame; + return compareContext->CompareUsingOriginalOrdering(first,second); } // Raise the window to the top of the Z order @@ -1039,29 +1300,47 @@ void wxWindow::Raise() { // wxAutoNSAutoreleasePool pool; NSView *nsview = GetNSViewForSuperview(); - [[nsview superview] sortSubviewsUsingFunction: + NSView *superview = [nsview superview]; + CocoaWindowCompareContext compareContext(nsview, [superview subviews]); + + [superview sortSubviewsUsingFunction: CocoaRaiseWindowCompareFunction - context: nsview]; + context: &compareContext]; } -static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *target) +/* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that + * the target view is always lower than every other view. When comparing two views neither of + * which is the target, it returns the correct response based on the original ordering + */ +static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx) { + CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx; // first should be ordered lower - if(first==target) + if(first==compareContext->target()) return NSOrderedAscending; // second should be ordered lower - if(second==target) + if(second==compareContext->target()) return NSOrderedDescending; - return NSOrderedSame; + return compareContext->CompareUsingOriginalOrdering(first,second); } // Lower the window to the bottom of the Z order void wxWindow::Lower() { NSView *nsview = GetNSViewForSuperview(); - [[nsview superview] sortSubviewsUsingFunction: + NSView *superview = [nsview superview]; + CocoaWindowCompareContext compareContext(nsview, [superview subviews]); + +#if 0 + NSLog(@"Target:\n%@\n", nsview); + NSLog(@"Before:\n%@\n", compareContext.subviews()); +#endif + [superview sortSubviewsUsingFunction: CocoaLowerWindowCompareFunction - context: nsview]; + context: &compareContext]; +#if 0 + NSLog(@"After:\n%@\n", [superview subviews]); +#endif } bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y) @@ -1165,6 +1444,9 @@ void wxCocoaTrackingRectManager::StopSynthesizingEvents() void wxCocoaTrackingRectManager::BuildTrackingRect() { + // Pool here due to lack of one during wx init phase + wxAutoNSAutoreleasePool pool; + wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared")); if([m_window->GetNSView() window] != nil) {