From 15f37147958a61adb54e0d64c82dc7b93c1f1cbe Mon Sep 17 00:00:00 2001 From: David Elliott Date: Fri, 10 Aug 2007 04:40:33 +0000 Subject: [PATCH] Implement transformation between the wxDisplay coordinate system and the Cocoa screen coordinate system. Use this to fix TLW initial positioning/sizing (including wxTopLevelWindow, wxFrame, and wxDialog) Use this to implement wxWindow::DoScreenToClient and DoClientToScreen Copyright 2007 Software 2000 Ltd. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@47994 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/cocoa/toplevel.h | 1 + include/wx/cocoa/window.h | 18 ++++ src/cocoa/dialog.mm | 8 +- src/cocoa/toplevel.mm | 62 ++++++------ src/cocoa/window.mm | 189 +++++++++++++++++++++++++++++++++++- 5 files changed, 239 insertions(+), 39 deletions(-) diff --git a/include/wx/cocoa/toplevel.h b/include/wx/cocoa/toplevel.h index 4a4780e9b6..0bf28d1056 100644 --- a/include/wx/cocoa/toplevel.h +++ b/include/wx/cocoa/toplevel.h @@ -80,6 +80,7 @@ protected: static wxCocoaNSWindowHash sm_cocoaHash; virtual void CocoaReplaceView(WX_NSView oldView, WX_NSView newView); static unsigned int NSWindowStyleForWxStyle(long style); + static NSRect MakeInitialNSWindowContentRect(const wxPoint& pos, const wxSize& size, unsigned int cocoaStyleMask); static wxTopLevelWindowCocoa *sm_cocoaDeactivateWindow; // ------------------------------------------------------------------------ diff --git a/include/wx/cocoa/window.h b/include/wx/cocoa/window.h index 730790e563..4303324e03 100644 --- a/include/wx/cocoa/window.h +++ b/include/wx/cocoa/window.h @@ -18,6 +18,22 @@ #import #endif //def __OBJC__ +// We can only import Foundation/NSGeometry.h from Objective-C code but it's +// nice to be able to use NSPoint and NSRect in the declarations of helper +// methods so we must define them as opaque structs identically to the way +// they are defined by the real header. +// NOTE: We specifically use these regardless of C++ or Objective-C++ mode so +// the compiler will complain if we got the definitions wrong. In regular +// C++ mode there is no way to know if we got the definitons right so +// we depend on at least one Objective-C++ file including this header. +#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) +typedef struct CGPoint NSPoint; +typedef struct CGRect NSRect; +#else +typedef struct _NSPoint NSPoint; +typedef struct _NSRect NSRect; +#endif + DECLARE_WXCOCOA_OBJC_CLASS(NSAffineTransform); class wxWindowCocoaHider; @@ -129,6 +145,8 @@ protected: NSPoint CocoaTransformWxToBounds(NSPoint pointWx); NSRect CocoaTransformWxToBounds(NSRect rectWx); #endif //def __OBJC__ + static wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame); + static NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible); // ------------------------------------------------------------------------ // Implementation // ------------------------------------------------------------------------ diff --git a/src/cocoa/dialog.mm b/src/cocoa/dialog.mm index f8d3faeafb..1f412b0a33 100644 --- a/src/cocoa/dialog.mm +++ b/src/cocoa/dialog.mm @@ -57,13 +57,9 @@ bool wxDialog::Create(wxWindow *parent, wxWindowID winid, if (parent) parent->AddChild(this); - NSRect cocoaRect = NSMakeRect(300,300,200,200); + unsigned int cocoaStyle = NSWindowStyleForWxStyle(style); - unsigned int cocoaStyle = 0; - cocoaStyle |= NSTitledWindowMask; - cocoaStyle |= NSClosableWindowMask; - cocoaStyle |= NSMiniaturizableWindowMask; - cocoaStyle |= NSResizableWindowMask; + NSRect cocoaRect = MakeInitialNSWindowContentRect(pos,size,cocoaStyle); m_cocoaNSWindow = NULL; SetNSPanel([[NSPanel alloc] initWithContentRect:cocoaRect styleMask:cocoaStyle backing:NSBackingStoreBuffered defer:NO]); diff --git a/src/cocoa/toplevel.mm b/src/cocoa/toplevel.mm index 2567dcc06a..8cce120e00 100644 --- a/src/cocoa/toplevel.mm +++ b/src/cocoa/toplevel.mm @@ -86,6 +86,23 @@ unsigned int wxTopLevelWindowCocoa::NSWindowStyleForWxStyle(long style) return styleMask; } +NSRect wxTopLevelWindowCocoa::MakeInitialNSWindowContentRect(const wxPoint& pos, const wxSize& size, unsigned int cocoaStyleMask) +{ + // Arbitrarily use (100,100) as the origin when default coords are given. + wxCoord x = pos.x!=wxDefaultCoord ? pos.x : 100; + wxCoord y = pos.y!=wxDefaultCoord ? pos.y : 100; + + wxCoord w = WidthDefault(size.x); + wxCoord h = HeightDefault(size.y); + + NSPoint cocoaOrigin = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,w,h,true); + + return [NSWindow + contentRectForFrameRect:NSMakeRect(cocoaOrigin.x,cocoaOrigin.y,w,h) + styleMask:cocoaStyleMask]; + +} + bool wxTopLevelWindowCocoa::Create(wxWindow *parent, wxWindowID winid, const wxString& title, @@ -107,30 +124,7 @@ bool wxTopLevelWindowCocoa::Create(wxWindow *parent, if(style & wxFRAME_TOOL_WINDOW) cocoaStyle |= NSUtilityWindowMask; - // Create frame and check and handle default position and size - int realx, - realy; - - // WX has no set default position - the carbon port caps the low - // end at 20, 50. Here we do the same, except instead of setting - // it to 20 and 50, we set it to 100 and 100 if the values are too low - if (pos.x < 20) - realx = 100; - else - realx = pos.x; - - if (pos.y < 50) - realy = 100; - else - realy = pos.y; - - int realw = WidthDefault(size.x); - int realh = HeightDefault(size.y); - - // NOTE: y-origin needs to be flipped. - NSRect cocoaRect = [NSWindow - contentRectForFrameRect:NSMakeRect(realx,realy,realw,realh) - styleMask:cocoaStyle]; + NSRect cocoaRect = MakeInitialNSWindowContentRect(pos,size,cocoaStyle); m_cocoaNSWindow = NULL; m_cocoaNSView = NULL; @@ -407,7 +401,9 @@ void wxTopLevelWindowCocoa::DoMoveWindow(int x, int y, int width, int height) { wxLogTrace(wxTRACE_COCOA_TopLevelWindow_Size,wxT("wxTopLevelWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height); - NSRect cocoaRect = NSMakeRect(x,y,width,height); + NSPoint cocoaOrigin = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,false); + + NSRect cocoaRect = NSMakeRect(cocoaOrigin.x,cocoaOrigin.y,width,height); [m_cocoaNSWindow setFrame: cocoaRect display:NO]; } @@ -423,10 +419,14 @@ void wxTopLevelWindowCocoa::DoGetSize(int *w, int *h) const void wxTopLevelWindowCocoa::DoGetPosition(int *x, int *y) const { - NSRect cocoaRect = [m_cocoaNSWindow frame]; - if(x) - *x=(int)cocoaRect.origin.x; - if(y) - *y=(int)cocoaRect.origin.y; - wxLogTrace(wxTRACE_COCOA_TopLevelWindow_Size,wxT("wxTopLevelWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y); + NSRect windowFrame = [m_cocoaNSWindow frame]; + + wxPoint theWxOrigin = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame); + + if(*x) + *x = theWxOrigin.x; + if(*y) + *y = theWxOrigin.y; + + wxLogTrace(wxTRACE_COCOA_TopLevelWindow_Size,wxT("wxTopLevelWindow=%p::DoGetPosition = (%d,%d)"),this,(int)theWxOrigin.x,(int)theWxOrigin.y); } diff --git a/src/cocoa/window.mm b/src/cocoa/window.mm index 2eaaafc97b..2e3e230739 100644 --- a/src/cocoa/window.mm +++ b/src/cocoa/window.mm @@ -34,6 +34,7 @@ #import #import #import +#import // Turn this on to paint green over the dummy views for debugging #undef WXCOCOA_FILL_DUMMY_VIEW @@ -59,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")); @@ -111,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 // ======================================================================== @@ -909,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. -- 2.45.2