1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/cocoa/window.mm
3 // Purpose: wxWindowCocoa
4 // Author: David Elliott
8 // Copyright: (c) 2002 David Elliott
9 // Licence: wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
12 #include "wx/wxprec.h"
16 #include "wx/window.h"
18 #include "wx/dcclient.h"
22 #include "wx/tooltip.h"
24 #include "wx/cocoa/dc.h"
25 #include "wx/cocoa/autorelease.h"
26 #include "wx/cocoa/string.h"
27 #include "wx/cocoa/trackingrectmanager.h"
28 #include "wx/cocoa/private/scrollview.h"
29 #include "wx/mac/corefoundation/cfref.h"
30 #include "wx/cocoa/ObjcRef.h"
32 #import <Foundation/NSArray.h>
33 #import <Foundation/NSRunLoop.h>
34 #include "wx/cocoa/objc/NSView.h"
35 #import <AppKit/NSEvent.h>
36 #import <AppKit/NSScrollView.h>
37 #import <AppKit/NSScroller.h>
38 #import <AppKit/NSColor.h>
39 #import <AppKit/NSClipView.h>
40 #import <Foundation/NSException.h>
41 #import <AppKit/NSApplication.h>
42 #import <AppKit/NSWindow.h>
43 #import <AppKit/NSScreen.h>
45 // Turn this on to paint green over the dummy views for debugging
46 #undef WXCOCOA_FILL_DUMMY_VIEW
48 #ifdef WXCOCOA_FILL_DUMMY_VIEW
49 #import <AppKit/NSBezierPath.h>
50 #endif //def WXCOCOA_FILL_DUMMY_VIEW
52 // STL list used by wxCocoaMouseMovedEventSynthesizer
55 /* NSComparisonResult is typedef'd as an enum pre-Leopard but typedef'd as
56 * NSInteger post-Leopard. Pre-Leopard the Cocoa toolkit expects a function
57 * returning int and not NSComparisonResult. Post-Leopard the Cocoa toolkit
58 * expects a function returning the new non-enum NSComparsionResult.
59 * Hence we create a typedef named CocoaWindowCompareFunctionResult.
61 #if defined(NSINTEGER_DEFINED)
62 typedef NSComparisonResult CocoaWindowCompareFunctionResult;
64 typedef int CocoaWindowCompareFunctionResult;
67 // A category for methods that are only present in Panther's SDK
68 @interface NSView(wxNSViewPrePantherCompatibility)
69 - (void)getRectsBeingDrawn:(const NSRect **)rects count:(int *)count;
72 // ========================================================================
73 // Helper functions for converting to/from wxWidgets coordinates and a
74 // specified NSView's coordinate system.
75 // ========================================================================
76 NSPoint CocoaTransformNSViewBoundsToWx(NSView *nsview, NSPoint pointBounds)
78 wxCHECK_MSG(nsview, pointBounds, wxT("Need to have a Cocoa view to do translation"));
79 if([nsview isFlipped])
81 NSRect ourBounds = [nsview bounds];
84 , ourBounds.size.height - pointBounds.y
88 NSRect CocoaTransformNSViewBoundsToWx(NSView *nsview, NSRect rectBounds)
90 wxCHECK_MSG(nsview, rectBounds, wxT("Need to have a Cocoa view to do translation"));
91 if([nsview isFlipped])
93 NSRect ourBounds = [nsview bounds];
96 , ourBounds.size.height - (rectBounds.origin.y + rectBounds.size.height)
97 , rectBounds.size.width
98 , rectBounds.size.height
102 NSPoint CocoaTransformNSViewWxToBounds(NSView *nsview, NSPoint pointWx)
104 wxCHECK_MSG(nsview, pointWx, wxT("Need to have a Cocoa view to do translation"));
105 if([nsview isFlipped])
107 NSRect ourBounds = [nsview bounds];
110 , ourBounds.size.height - pointWx.y
114 NSRect CocoaTransformNSViewWxToBounds(NSView *nsview, NSRect rectWx)
116 wxCHECK_MSG(nsview, rectWx, wxT("Need to have a Cocoa view to do translation"));
117 if([nsview isFlipped])
119 NSRect ourBounds = [nsview bounds];
122 , ourBounds.size.height - (rectWx.origin.y + rectWx.size.height)
128 // ============================================================================
129 // Screen coordinate helpers
130 // ============================================================================
133 General observation about Cocoa screen coordinates:
134 It is documented that the first object of the [NSScreen screens] array is the screen with the menubar.
136 It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always
137 the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up.
139 It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle
140 relative to that screen's frame. The only real way to test this is to configure two screens one atop
141 the other such that the menubar screen is on top. The Dock at the bottom of the screen will then
142 eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two
143 1920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and
144 visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin.
146 In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen).
147 However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme.
148 This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen.
151 namespace { // file namespace
153 class wxCocoaPrivateScreenCoordinateTransformer
155 DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer)
157 wxCocoaPrivateScreenCoordinateTransformer();
158 ~wxCocoaPrivateScreenCoordinateTransformer();
159 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
160 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
163 NSScreen *m_screenZero;
164 NSRect m_screenZeroFrame;
167 // NOTE: This is intended to be a short-lived object. A future enhancment might
168 // make it a global and reconfigure it upon some notification that the screen layout
170 inline wxCocoaPrivateScreenCoordinateTransformer::wxCocoaPrivateScreenCoordinateTransformer()
172 NSArray *screens = [NSScreen screens];
177 if(screens != nil && [screens count] > 0)
178 m_screenZero = [[screens objectAtIndex:0] retain];
182 if(m_screenZero != nil)
183 m_screenZeroFrame = [m_screenZero frame];
186 wxLogWarning(wxT("Can't translate to/from wx screen coordinates and Cocoa screen coordinates"));
187 // Just blindly assume 1024x768 so that at least we can sort of flip things around into
188 // Cocoa coordinates.
189 // NOTE: Theoretically this case should never happen anyway.
190 m_screenZeroFrame = NSMakeRect(0,0,1024,768);
194 inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
196 [m_screenZero release];
200 inline wxPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
202 // x and y are in wx screen coordinates which we're going to arbitrarily define such that
203 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
204 // NOTE WELL: This means that (0,0) is _NOT_ an appropriate position for a window.
208 // Working in Cocoa's screen coordinates we must realize that the x coordinate we want is
209 // the distance between the left side (origin.x) of the window's frame and the left side of
210 // screen zero's frame.
211 theWxOrigin.x = windowFrame.origin.x - m_screenZeroFrame.origin.x;
213 // Working in Cocoa's screen coordinates we must realize that the y coordinate we want is
214 // actually the distance between the top-left of the screen zero frame and the top-left
215 // of the window's frame.
217 theWxOrigin.y = (m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height) - (windowFrame.origin.y + windowFrame.size.height);
222 inline NSPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
224 NSPoint theCocoaOrigin;
226 // The position is in wx screen coordinates which we're going to arbitrarily define such that
227 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
229 // NOTE: The usable rectangle is smaller and hence we have the keepOriginVisible flag
230 // which will move the origin downward and/or left as necessary if the origin is
231 // inside the screen0 rectangle (i.e. x/y >= 0 in wx coordinates) and outside the
232 // visible frame (i.e. x/y < the top/left of the screen0 visible frame in wx coordinates)
233 // We don't munge origin coordinates < 0 because it actually is possible that the menubar is on
234 // the top of the bottom screen and thus that origin is completely valid!
235 if(keepOriginVisible && (m_screenZero != nil))
237 // Do al of this in wx coordinates because it's far simpler since we're dealing with top/left points
238 wxPoint visibleOrigin = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates([m_screenZero visibleFrame]);
239 if(x >= 0 && x < visibleOrigin.x)
241 if(y >= 0 && y < visibleOrigin.y)
245 // The x coordinate is simple as it's just relative to screen zero's frame
246 theCocoaOrigin.x = m_screenZeroFrame.origin.x + x;
247 // Working in Cocoa's coordinates think to start at the bottom of screen zero's frame and add
248 // the height of that rect which gives us the coordinate for the top of the visible rect. Now realize that
249 // the wx coordinates are flipped so if y is say 10 then we want to be 10 pixels down from that and thus
250 // we subtract y. But then we still need to take into account the size of the window which is h and subtract
251 // that to get the bottom-left origin of the rectangle.
252 theCocoaOrigin.y = m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height - y - height;
254 return theCocoaOrigin;
259 wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
261 wxCocoaPrivateScreenCoordinateTransformer transformer;
262 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
265 NSPoint wxWindowCocoa::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
267 wxCocoaPrivateScreenCoordinateTransformer transformer;
268 return transformer.OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,keepOriginVisible);
271 // ========================================================================
272 // wxWindowCocoaHider
273 // ========================================================================
274 class wxWindowCocoaHider: protected wxCocoaNSView
276 DECLARE_NO_COPY_CLASS(wxWindowCocoaHider)
278 wxWindowCocoaHider(wxWindow *owner);
279 virtual ~wxWindowCocoaHider();
280 inline WX_NSView GetNSView() { return m_dummyNSView; }
282 wxWindowCocoa *m_owner;
283 WX_NSView m_dummyNSView;
284 virtual void Cocoa_FrameChanged(void);
285 virtual void Cocoa_synthesizeMouseMoved(void) {}
286 #ifdef WXCOCOA_FILL_DUMMY_VIEW
287 virtual bool Cocoa_drawRect(const NSRect& rect);
288 #endif //def WXCOCOA_FILL_DUMMY_VIEW
290 wxWindowCocoaHider();
293 // ========================================================================
295 // ========================================================================
296 @interface wxDummyNSView : NSView
297 - (NSView *)hitTest:(NSPoint)aPoint;
299 WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
301 @implementation wxDummyNSView : NSView
302 - (NSView *)hitTest:(NSPoint)aPoint
308 WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
310 // ========================================================================
311 // wxWindowCocoaHider
312 // ========================================================================
313 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
317 wxASSERT(owner->GetNSViewForHiding());
318 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
319 initWithFrame:[owner->GetNSViewForHiding() frame]];
320 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
321 AssociateNSView(m_dummyNSView);
324 wxWindowCocoaHider::~wxWindowCocoaHider()
326 DisassociateNSView(m_dummyNSView);
327 [m_dummyNSView release];
330 void wxWindowCocoaHider::Cocoa_FrameChanged(void)
332 // Keep the real window in synch with the dummy
333 wxASSERT(m_dummyNSView);
334 [m_owner->GetNSViewForHiding() setFrame:[m_dummyNSView frame]];
338 #ifdef WXCOCOA_FILL_DUMMY_VIEW
339 bool wxWindowCocoaHider::Cocoa_drawRect(const NSRect& rect)
341 NSBezierPath *bezpath = [NSBezierPath bezierPathWithRect:rect];
342 [[NSColor greenColor] set];
347 #endif //def WXCOCOA_FILL_DUMMY_VIEW
350 /*! @class WXManualScrollView
351 @abstract An NSScrollView subclass which implements wx scroll behavior
353 Overrides default behavior of NSScrollView such that this class receives
354 the scroller action messages and allows the wxCocoaScrollView to act
355 on them accordingly. In particular, because the NSScrollView will not
356 receive action messages from the scroller, it will not adjust the
357 document view. This must be done manually using the ScrollWindow
360 @interface WXManualScrollView : NSScrollView
362 /*! @field m_wxCocoaScrollView
364 wxWindowCocoaScrollView *m_wxCocoaScrollView;
367 // Override these to set up the target/action correctly
368 - (void)setHorizontalScroller:(NSScroller *)aScroller;
369 - (void)setVerticalScroller:(NSScroller *)aScroller;
370 - (void)setHasHorizontalScroller:(BOOL)flag;
371 - (void)setHasVerticalScroller:(BOOL)flag;
373 // NOTE: _wx_ prefix means "private" method like _ that Apple (and only Apple) uses.
374 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView;
375 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView;
377 /*! @method _wx_doScroller
378 @abstract Handles action messages from the scrollers
380 Similar to Apple's _doScroller: method which is private and not documented.
381 We do not, however, call that method. Instead, we effectively override
382 it entirely. We don't override it by naming ourself the same thing because
383 the base class code may or may not call that method for other reasons we
384 simply cannot know about.
386 - (void)_wx_doScroller:(id)sender;
391 @implementation WXManualScrollView : NSScrollView
393 static inline void WXManualScrollView_DoSetScrollerTargetAction(WXManualScrollView *self, NSScroller *aScroller)
395 if(aScroller != NULL && [self _wx_wxCocoaScrollView] != NULL)
397 [aScroller setTarget:self];
398 [aScroller setAction:@selector(_wx_doScroller:)];
402 - (void)setHorizontalScroller:(NSScroller *)aScroller
404 [super setHorizontalScroller:aScroller];
405 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
408 - (void)setVerticalScroller:(NSScroller *)aScroller
410 [super setVerticalScroller:aScroller];
411 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
414 - (void)setHasHorizontalScroller:(BOOL)flag
416 [super setHasHorizontalScroller:flag];
417 WXManualScrollView_DoSetScrollerTargetAction(self, [self horizontalScroller]);
420 - (void)setHasVerticalScroller:(BOOL)flag
422 [super setHasVerticalScroller:flag];
423 WXManualScrollView_DoSetScrollerTargetAction(self, [self verticalScroller]);
426 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView
427 { return m_wxCocoaScrollView; }
429 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView
431 m_wxCocoaScrollView = theWxScrollView;
432 [self setHorizontalScroller:[self horizontalScroller]];
433 [self setVerticalScroller:[self verticalScroller]];
436 - (void)_wx_doScroller:(id)sender
438 if(m_wxCocoaScrollView != NULL)
439 m_wxCocoaScrollView->_wx_doScroller(sender);
441 wxLogError(wxT("Unexpected action message received from NSScroller"));
444 - (void)reflectScrolledClipView:(NSClipView *)aClipView
446 struct _ScrollerBackup
448 _ScrollerBackup(NSScroller *aScroller)
449 : m_scroller(aScroller)
450 , m_floatValue(aScroller!=nil?[aScroller floatValue]:0.0)
451 , m_knobProportion(aScroller!=nil?[aScroller knobProportion]:1.0)
452 , m_isEnabled(aScroller!=nil?[aScroller isEnabled]:false)
455 NSScroller *m_scroller;
456 CGFloat m_floatValue;
457 CGFloat m_knobProportion;
461 if(m_scroller != nil)
463 [m_scroller setFloatValue:m_floatValue knobProportion:m_knobProportion];
464 [m_scroller setEnabled:m_isEnabled];
469 _ScrollerBackup(_ScrollerBackup const&);
470 _ScrollerBackup& operator=(_ScrollerBackup const&);
472 _ScrollerBackup _horizontalBackup([self horizontalScroller]);
473 _ScrollerBackup _verticalBackup([self verticalScroller]);
474 // We MUST call super's implementation or else nothing seems to work right at all.
475 // However, we need our scrollers not to change values due to the document window
476 // moving so we cheat and save/restore their values across this call.
477 [super reflectScrolledClipView: aClipView];
481 WX_IMPLEMENT_GET_OBJC_CLASS(WXManualScrollView,NSScrollView)
483 // ========================================================================
484 // wxFlippedNSClipView
485 // ========================================================================
486 @interface wxFlippedNSClipView : NSClipView
489 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
491 @implementation wxFlippedNSClipView : NSClipView
498 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
500 // ========================================================================
501 // wxWindowCocoaScrollView
502 // ========================================================================
503 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
505 , m_cocoaNSScrollView() // nil
506 , m_scrollRange() // {0,0}
507 , m_scrollThumb() // {0,0}
508 , m_virtualOrigin(0,0)
510 wxAutoNSAutoreleasePool pool;
512 wxASSERT(owner->GetNSView());
513 m_isNativeView = ![owner->GetNSView() isKindOfClass:[WX_GET_OBJC_CLASS(WXNSView) class]];
514 m_cocoaNSScrollView = [(m_isNativeView?[NSScrollView alloc]:[WXManualScrollView alloc])
515 initWithFrame:[owner->GetNSView() frame]];
516 AssociateNSView(m_cocoaNSScrollView);
519 /* Set a bezel border around the entire thing because it looks funny without it.
520 TODO: Be sure to undo any borders on the real view (if any) and apply them
521 to this view if necessary. Right now, there is no border support in wxCocoa
522 so this isn't an issue.
524 [m_cocoaNSScrollView setBorderType:NSBezelBorder];
528 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView: this];
529 // Don't set a bezel because we might be creating a scroll view due to being
530 // the "target window" of a wxScrolledWindow. That is to say that the user
531 // has absolutely no intention of scrolling the clip view used by this
535 /* Replace the default NSClipView with a flipped one. This ensures
536 scrolling is "pinned" to the top-left instead of bottom-right. */
537 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
538 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
539 [m_cocoaNSScrollView setContentView:flippedClip];
540 [flippedClip release];
542 // In all cases we must encapsulate the real NSView properly
546 void wxWindowCocoaScrollView::Encapsulate()
548 // Set the scroll view autoresizingMask to match the current NSView
549 [m_cocoaNSScrollView setAutoresizingMask: [m_owner->GetNSView() autoresizingMask]];
550 [m_owner->GetNSView() setAutoresizingMask: NSViewNotSizable];
551 // NOTE: replaceSubView will cause m_cocaNSView to be released
552 // except when it hasn't been added into an NSView hierarchy in which
553 // case it doesn't need to be and this should work out to a no-op
554 m_owner->CocoaReplaceView(m_owner->GetNSView(), m_cocoaNSScrollView);
555 // The NSView is still retained by owner
556 [m_cocoaNSScrollView setDocumentView: m_owner->GetNSView()];
557 // Now it's also retained by the NSScrollView
560 void wxWindowCocoaScrollView::Unencapsulate()
562 [m_cocoaNSScrollView setDocumentView: nil];
563 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
564 if(![[m_owner->GetNSView() superview] isFlipped])
565 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
568 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
570 DisassociateNSView(m_cocoaNSScrollView);
573 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView:NULL];
575 [m_cocoaNSScrollView release];
578 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
580 NSSize frameSize = [NSScrollView
581 frameSizeForContentSize: NSMakeSize(width,height)
582 hasHorizontalScroller: [m_cocoaNSScrollView hasHorizontalScroller]
583 hasVerticalScroller: [m_cocoaNSScrollView hasVerticalScroller]
584 borderType: [m_cocoaNSScrollView borderType]];
585 width = (int)frameSize.width;
586 height = (int)frameSize.height;
589 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
591 NSSize nssize = [m_cocoaNSScrollView contentSize];
593 *x = (int)nssize.width;
595 *y = (int)nssize.height;
598 static inline void SetCocoaScroller(NSScroller *aScroller, int WXUNUSED(orientation), int position, int thumbSize, int range)
600 wxCHECK_RET(aScroller != nil, wxT("Expected the NSScrollView to have a scroller"));
602 // NOTE: thumbSize is already ensured to be >= 1 and <= range by our caller
603 // unless range = 0 in which case we shouldn't have been be called.
604 wxCHECK_RET(range > 0, wxT("Internal wxCocoa bug: shouldn't have been called with 0 range"));
606 // Range of valid position values is from 0 to effectiveRange
607 // NOTE: if thumbSize == range then effectiveRange is 0.
608 // thumbSize is at least 1 which gives range from 0 to range - 1 inclusive
609 // which is exactly what we want.
610 int effectiveRange = range - thumbSize;
612 // knobProportion is hopefully easy to understand
613 // Note that thumbSize is already guaranteed >= 1 by our caller.
614 CGFloat const knobProportion = CGFloat(thumbSize)/CGFloat(range);
616 // NOTE: When effectiveRange is zero there really is no valid position
617 // We arbitrarily pick 0.0 which is the same as a scroller in the home position.
618 CGFloat const floatValue = (effectiveRange != 0)?CGFloat(position)/CGFloat(effectiveRange):0.0;
620 [aScroller setFloatValue:floatValue knobProportion: knobProportion];
621 // Make sure it's visibly working
622 [aScroller setEnabled:YES];
625 void wxWindowCocoaScrollView::SetScrollPos(wxOrientation orientation, int position)
627 // NOTE: Rather than using only setFloatValue: (which we could do) we instead
628 // simply share the SetCocoaScroller call because all but the knobProportion
629 // calculations have to be done anyway.
630 if(orientation & wxHORIZONTAL)
632 NSScroller *aScroller = [m_cocoaNSScrollView horizontalScroller];
634 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[0], m_scrollRange[0]);
636 if(orientation & wxVERTICAL)
638 NSScroller *aScroller = [m_cocoaNSScrollView verticalScroller];
640 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[1], m_scrollRange[1]);
644 void wxWindowCocoaScrollView::SetScrollbar(int orientation, int position, int thumbSize, int range)
646 // FIXME: API assumptions:
647 // 1. If the user wants to remove a scroller he gives range 0.
648 // 2. If the user wants to disable a scroller he sets thumbSize == range
649 // in which case it is logically impossible to scroll.
650 // The scroller shall still be displayed.
652 // Ensure that range is >= 0.
653 wxASSERT(range >= 0);
657 // Ensure that thumbSize <= range
658 wxASSERT(thumbSize <= range);
659 // Also ensure thumbSize >= 1 but don't complain if it isn't
662 // Now make sure it's really less than range, even if we just set it to 1
663 if(thumbSize > range)
666 bool needScroller = (range != 0);
668 // Can potentially set both horizontal and vertical at the same time although this is
669 // probably not very useful.
670 if(orientation & wxHORIZONTAL)
672 m_scrollRange[0] = range;
673 m_scrollThumb[0] = thumbSize;
676 [m_cocoaNSScrollView setHasHorizontalScroller:needScroller];
678 SetCocoaScroller([m_cocoaNSScrollView horizontalScroller], orientation, position, thumbSize, range);
682 if(orientation & wxVERTICAL)
684 m_scrollRange[1] = range;
685 m_scrollThumb[1] = thumbSize;
688 [m_cocoaNSScrollView setHasVerticalScroller:needScroller];
690 SetCocoaScroller([m_cocoaNSScrollView verticalScroller], orientation, position, thumbSize, range);
695 int wxWindowCocoaScrollView::GetScrollPos(wxOrientation orient)
697 if((orient & wxBOTH) == wxBOTH)
699 wxLogError(wxT("GetScrollPos called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
702 int effectiveScrollRange;
703 NSScroller *cocoaScroller;
704 if(orient & wxHORIZONTAL)
706 effectiveScrollRange = m_scrollRange[0] - m_scrollThumb[0];
707 cocoaScroller = [m_cocoaNSScrollView horizontalScroller];
709 else if(orient & wxVERTICAL)
711 effectiveScrollRange = m_scrollRange[1] - m_scrollThumb[1];
712 cocoaScroller = [m_cocoaNSScrollView verticalScroller];
716 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
719 if(cocoaScroller == nil)
720 { // Document is not scrolled
724 The effective range of a scroll bar as defined by wxWidgets is from 0 to (range - thumbSize).
725 That is a scroller at the left/top position is at 0 and a scroller at the bottom/right
726 position is at range-thumbsize.
728 The range of an NSScroller is 0.0 to 1.0. Much easier! NOTE: Apple doesn't really specify
729 but GNUStep docs do say that 0.0 is top/left and 1.0 is bottom/right. This is actualy
730 in contrast to NSSlider which generally has 1.0 at the TOP when it's done vertically.
732 CGFloat cocoaScrollPos = [cocoaScroller floatValue];
733 return effectiveScrollRange * cocoaScrollPos;
736 int wxWindowCocoaScrollView::GetScrollRange(wxOrientation orient)
738 if((orient & wxBOTH) == wxBOTH)
740 wxLogError(wxT("GetScrollRange called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
743 if(orient & wxHORIZONTAL)
745 return m_scrollRange[0];
747 else if(orient & wxVERTICAL)
749 return m_scrollRange[1];
753 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
758 int wxWindowCocoaScrollView::GetScrollThumb(wxOrientation orient)
760 if((orient & wxBOTH) == wxBOTH)
762 wxLogError(wxT("GetScrollThumb called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
765 if(orient & wxHORIZONTAL)
767 return m_scrollThumb[0];
769 else if(orient & wxVERTICAL)
771 return m_scrollThumb[1];
775 wxLogError(wxT("GetScrollThumb called without an orientation which makes no sense"));
781 Moves the contents (all existing drawing as well as all all child wxWindow) by the specified
782 amount expressed in the wxWindow's own coordinate system. This is used to implement scrolling
783 but the usage is rather interesting. When scrolling right (e.g. increasing the value of
784 the scroller) you must give a negative delta x (e.g. moving the contents LEFT). Likewise,
785 when scrolling the window down, increasing the value of the scroller, you give a negative
786 delta y which moves the contents up.
788 wxCocoa notes: To accomplish this trick in Cocoa we basically do what NSScrollView would
789 have done and that is adjust the content view's bounds origin. The content view is somewhat
790 confusingly the NSClipView which is more or less sort of the pImpl for NSScrollView
791 The real NSView with the user's content (e.g. the "virtual area" in wxWidgets parlance)
792 is called the document view in NSScrollView parlance.
794 The bounds origin is basically the exact opposite concept. Whereas in Windows the client
795 coordinate system remains constant and the content must shift left/up for increases
796 of scrolling, in Cocoa the coordinate system is actually the virtual one. So we must
797 instead shift the bounds rectangle right/down to get the effect of the content moving
798 left/up. Basically, it's a higher level interface than that provided by wxWidgets
799 so essentially we're implementing the low-level move content interface in terms of
800 the high-level move the viewport (the bounds) over top that content (the document
801 view which is the virtual area to wx).
803 For all intents and purposes that basically just means that we subtract the deltas
804 from the bounds origin and thus a negative delta actually increases the bounds origin
805 and a positive delta actually decreases it. This is absolutely true for the horizontal
806 axis but there's a catch in the vertical axis. If the content view (the clip view) is
807 flipped (and we do this by default) then it works exactly like the horizontal axis.
808 If it is not flipped (i.e. it is in postscript coordinates which are opposite to
809 wxWidgets) then the sense needs to be reversed.
811 However, this plays hell with window positions. The frame rects of any child views
812 do not change origin and this is actually important because if they did, the views
813 would send frame changed notifications, not to mention that Cocoa just doesn't really
814 do scrolling that way, it does it the way we do it here.
816 To fix this we implement GetPosition for child windows to not merely consult its
817 superview at the Cocoa level in order to do proper Cocoa->wx coordinate transform
818 but to actually consult is parent wxWindow because it makes a big difference if
819 the parent is scrolled. Argh. (FIXME: This isn't actually implemented yet)
821 void wxWindowCocoaScrollView::ScrollWindow(int dx, int dy, const wxRect*)
823 // Update our internal origin so we know how much the application code
824 // expects us to have been scrolled.
825 m_virtualOrigin.x += dx;
826 m_virtualOrigin.y += dy;
828 // Scroll the window using the standard Cocoa method of adjusting the
829 // clip view's bounds in the opposite fashion.
830 NSClipView *contentView = [m_cocoaNSScrollView contentView];
831 NSRect clipViewBoundsRect = [contentView bounds];
832 clipViewBoundsRect.origin.x -= dx;
833 if([contentView isFlipped])
834 clipViewBoundsRect.origin.y -= dy;
836 clipViewBoundsRect.origin.y += dy;
837 [contentView scrollToPoint:clipViewBoundsRect.origin];
840 void wxWindowCocoaScrollView::_wx_doScroller(NSScroller *sender)
842 wxOrientation orientation;
843 if(sender == [m_cocoaNSScrollView horizontalScroller])
844 orientation = wxHORIZONTAL;
845 else if(sender == [m_cocoaNSScrollView verticalScroller])
846 orientation = wxVERTICAL;
849 wxLogDebug(wxT("Received action message from unexpected NSScroller"));
852 // NOTE: Cocoa does not move the scroller for page up/down or line
853 // up/down events. That means the value will be the old value.
854 // For thumbtrack events, the value is the new value.
855 int scrollpos = GetScrollPos(orientation);
857 switch([sender hitPart])
860 case NSScrollerNoPart:
861 case NSScrollerKnob: // Drag of knob
862 case NSScrollerKnobSlot: // Jump directly to position
863 commandType = wxEVT_SCROLLWIN_THUMBTRACK;
865 case NSScrollerDecrementPage:
866 commandType = wxEVT_SCROLLWIN_PAGEUP;
868 case NSScrollerIncrementPage:
869 commandType = wxEVT_SCROLLWIN_PAGEDOWN;
871 case NSScrollerDecrementLine:
872 commandType = wxEVT_SCROLLWIN_LINEUP;
874 case NSScrollerIncrementLine:
875 commandType = wxEVT_SCROLLWIN_LINEDOWN;
878 wxScrollWinEvent event(commandType, scrollpos, orientation);
879 event.SetEventObject(m_owner);
880 m_owner->HandleWindowEvent(event);
883 void wxWindowCocoaScrollView::UpdateSizes()
885 // Using the virtual size, figure out what the document frame size should be
886 // NOTE: Assume that the passed in virtualSize is already >= the client size
887 wxSize virtualSize = m_owner->GetVirtualSize();
889 // Get the document's current frame
890 NSRect documentViewFrame = [m_owner->GetNSView() frame];
891 NSRect newFrame = documentViewFrame;
892 newFrame.size = NSMakeSize(virtualSize.x, virtualSize.y);
894 if(!NSEqualRects(newFrame, documentViewFrame))
896 [m_owner->GetNSView() setFrame:newFrame];
900 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
902 wxLogTrace(wxTRACE_COCOA,wxT("wxWindowCocoaScrollView=%p::Cocoa_FrameChanged for wxWindow %p"), this, m_owner);
903 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
904 event.SetEventObject(m_owner);
905 m_owner->HandleWindowEvent(event);
909 // ========================================================================
911 // ========================================================================
912 // normally the base classes aren't included, but wxWindow is special
913 #ifdef __WXUNIVERSAL__
914 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
916 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
919 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
922 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
925 void wxWindowCocoa::Init()
927 m_cocoaNSView = NULL;
929 m_wxCocoaScrollView = NULL;
930 m_isBeingDeleted = false;
932 m_visibleTrackingRectManager = NULL;
936 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
940 const wxString& name)
942 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
945 // TODO: create the window
946 m_cocoaNSView = NULL;
947 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
948 [m_cocoaNSView release];
952 m_parent->AddChild(this);
953 m_parent->CocoaAddChild(this);
954 SetInitialFrameRect(pos,size);
961 wxWindow::~wxWindow()
963 wxAutoNSAutoreleasePool pool;
966 // Make sure our parent (in the wxWidgets sense) is our superview
967 // before we go removing from it.
968 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
969 CocoaRemoveFromParent();
971 delete m_wxCocoaScrollView;
977 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
979 // Pool here due to lack of one during wx init phase
980 wxAutoNSAutoreleasePool pool;
982 NSView *childView = child->GetNSViewForSuperview();
985 [m_cocoaNSView addSubview: childView];
988 void wxWindowCocoa::CocoaRemoveFromParent(void)
990 [GetNSViewForSuperview() removeFromSuperview];
993 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
995 // Clear the visible area tracking rect if we have one.
996 delete m_visibleTrackingRectManager;
997 m_visibleTrackingRectManager = NULL;
999 bool need_debug = cocoaNSView || m_cocoaNSView;
1000 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
1001 DisassociateNSView(m_cocoaNSView);
1002 wxGCSafeRetain(cocoaNSView);
1003 wxGCSafeRelease(m_cocoaNSView);
1004 m_cocoaNSView = cocoaNSView;
1005 AssociateNSView(m_cocoaNSView);
1006 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
1009 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
1012 ? m_cocoaHider->GetNSView()
1013 : m_wxCocoaScrollView
1014 ? m_wxCocoaScrollView->GetNSScrollView()
1018 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
1020 return m_wxCocoaScrollView
1021 ? m_wxCocoaScrollView->GetNSScrollView()
1025 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
1027 // TODO: Handle scrolling offset
1028 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
1031 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
1033 // TODO: Handle scrolling offset
1034 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
1037 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
1039 // TODO: Handle scrolling offset
1040 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
1043 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
1045 // TODO: Handle scrolling offset
1046 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
1049 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
1051 // TODO: Handle scrolling offset
1052 NSAffineTransform *transform = wxCocoaDCImpl::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
1056 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
1058 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
1059 // Recursion can happen if the event loop runs from within the paint
1060 // handler. For instance, if an assertion dialog is shown.
1061 // FIXME: This seems less than ideal.
1064 wxLogDebug(wxT("Paint event recursion!"));
1069 // Set m_updateRegion
1070 const NSRect *rects = ▭ // The bounding box of the region
1071 NSInteger countRects = 1;
1072 // Try replacing the larger rectangle with a list of smaller ones:
1073 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
1074 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
1076 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
1077 for(int i=0; i<countRects; i++)
1079 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
1081 m_updateRegion = wxRegion(transformedRects,countRects);
1082 free(transformedRects);
1084 wxPaintEvent event(m_windowId);
1085 event.SetEventObject(this);
1086 bool ret = HandleWindowEvent(event);
1087 m_isInPaint = false;
1091 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
1093 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
1094 // Mouse events happen at the NSWindow level so we need to convert
1095 // into our bounds coordinates then convert to wx coordinates.
1096 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
1097 if( m_wxCocoaScrollView != NULL)
1099 // This gets the wx client area (i.e. the visible portion of the content) in
1100 // the coordinate system of our (the doucment) view.
1101 NSRect documentVisibleRect = [[m_wxCocoaScrollView->GetNSScrollView() contentView] documentVisibleRect];
1102 // For horizontal, simply subtract the origin.
1103 // e.g. if the origin is at 123 and the user clicks as far left as possible then
1104 // the coordinate that wx wants is 0.
1105 cocoaPoint.x -= documentVisibleRect.origin.x;
1106 if([m_cocoaNSView isFlipped])
1108 // In the flipped view case this works exactly like horizontal.
1109 cocoaPoint.y -= documentVisibleRect.origin.y;
1111 // For vertical we have to mind non-flipped (e.g. y=0 at bottom) views.
1112 // We also need to mind the fact that we're still in Cocoa coordinates
1113 // and not wx coordinates. The wx coordinate translation will still occur
1114 // and that is going to be wxY = boundsH - cocoaY for non-flipped views.
1116 // When we consider scrolling we are truly interested in how far the top
1117 // edge of the bounds rectangle is scrolled off the screen.
1118 // Assuming the bounds origin is 0 (which is an assumption we make in
1119 // wxCocoa since wxWidgets has no analog to it) then the top edge of
1120 // the bounds rectangle is simply its height. The top edge of the
1121 // documentVisibleRect (e.g. the client area) is its height plus
1123 // Thus, we simply need add the distance between the bounds top
1124 // and the client (docuemntVisibleRect) top.
1125 // Or putting it another way, we subtract the distance between the
1126 // client top and the bounds top.
1129 NSRect bounds = [m_cocoaNSView bounds];
1130 CGFloat scrollYOrigin = (bounds.size.height - (documentVisibleRect.origin.y + documentVisibleRect.size.height));
1131 cocoaPoint.y += scrollYOrigin;
1135 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1136 // FIXME: Should we be adjusting for client area origin?
1137 const wxPoint &clientorigin = GetClientAreaOrigin();
1138 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1139 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1141 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
1142 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
1143 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
1144 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
1146 // TODO: set timestamp?
1147 event.SetEventObject(this);
1148 event.SetId(GetId());
1151 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
1153 wxMouseEvent event(wxEVT_MOTION);
1154 InitMouseEvent(event,theEvent);
1155 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
1156 return HandleWindowEvent(event);
1159 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
1161 wxMouseEvent event(wxEVT_MOTION);
1162 NSWindow *window = [GetNSView() window];
1163 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
1164 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
1166 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1167 // FIXME: Should we be adjusting for client area origin?
1168 const wxPoint &clientorigin = GetClientAreaOrigin();
1169 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1170 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1172 // TODO: Handle shift, control, alt, meta flags
1173 event.SetEventObject(this);
1174 event.SetId(GetId());
1176 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
1177 HandleWindowEvent(event);
1180 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
1182 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1184 m_visibleTrackingRectManager->BeginSynthesizingEvents();
1186 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
1187 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
1188 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
1189 // first responder, so the mouseMoved: events are effectively discarded.
1190 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
1192 wxMouseEvent event(wxEVT_ENTER_WINDOW);
1193 InitMouseEvent(event,theEvent);
1194 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
1195 return HandleWindowEvent(event);
1201 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
1203 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1205 m_visibleTrackingRectManager->StopSynthesizingEvents();
1207 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
1208 InitMouseEvent(event,theEvent);
1209 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
1210 return HandleWindowEvent(event);
1216 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
1218 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
1219 InitMouseEvent(event,theEvent);
1220 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1221 return HandleWindowEvent(event);
1224 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
1226 wxMouseEvent event(wxEVT_MOTION);
1227 InitMouseEvent(event,theEvent);
1228 event.m_leftDown = true;
1229 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1230 return HandleWindowEvent(event);
1233 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
1235 wxMouseEvent event(wxEVT_LEFT_UP);
1236 InitMouseEvent(event,theEvent);
1237 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1238 return HandleWindowEvent(event);
1241 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
1243 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
1244 InitMouseEvent(event,theEvent);
1245 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1246 return HandleWindowEvent(event);
1249 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
1251 wxMouseEvent event(wxEVT_MOTION);
1252 InitMouseEvent(event,theEvent);
1253 event.m_rightDown = true;
1254 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1255 return HandleWindowEvent(event);
1258 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
1260 wxMouseEvent event(wxEVT_RIGHT_UP);
1261 InitMouseEvent(event,theEvent);
1262 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1263 return HandleWindowEvent(event);
1266 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
1271 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
1276 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
1281 void wxWindowCocoa::Cocoa_FrameChanged(void)
1283 // We always get this message for the real NSView which may have been
1284 // enclosed in an NSScrollView. If that's the case then what we're
1285 // effectively getting here is a notifcation that the
1286 // virtual sized changed.. which we don't need to send on since
1287 // wx has no concept of this whatsoever.
1288 bool isViewForSuperview = (m_wxCocoaScrollView == NULL);
1289 if(isViewForSuperview)
1291 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
1292 if(m_visibleTrackingRectManager != NULL)
1293 m_visibleTrackingRectManager->RebuildTrackingRect();
1294 wxSizeEvent event(GetSize(), m_windowId);
1295 event.SetEventObject(this);
1296 HandleWindowEvent(event);
1300 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged ignored"),this);
1304 bool wxWindowCocoa::Cocoa_resetCursorRects()
1306 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
1308 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
1309 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
1310 // mouse exited event. If the control happens to share the edge with its parent window which is
1311 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
1312 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
1313 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
1314 // which willl in turn call us before exiting for the next user event.
1316 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
1317 // our mouse exited event because the already queued event will have the old tracking rect
1318 // tag. The simple solution is to only rebuild our tracking rect if we need to.
1320 if(m_visibleTrackingRectManager != NULL)
1321 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
1323 if(!m_cursor.GetNSCursor())
1326 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1331 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
1333 if(!wxWindowBase::SetCursor(cursor))
1336 // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
1337 // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
1338 // rects and thus won't ever call resetCursorRects.
1339 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1341 // Invalidate the cursor rects so the cursor will change
1342 // Note that it is not enough to remove the old one (if any) and add the new one.
1343 // For the rects to work properly, Cocoa itself must call resetCursorRects.
1344 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
1348 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
1350 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
1351 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
1352 if(m_visibleTrackingRectManager != NULL)
1353 m_visibleTrackingRectManager->BuildTrackingRect();
1357 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
1359 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
1360 // Clear tracking rects. It is imperative this be done before the new window is set.
1361 if(m_visibleTrackingRectManager != NULL)
1362 m_visibleTrackingRectManager->ClearTrackingRect();
1366 bool wxWindow::Close(bool force)
1368 // The only reason this function exists is that it is virtual and
1369 // wxTopLevelWindowCocoa will override it.
1370 return wxWindowBase::Close(force);
1373 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
1375 [[oldView superview] replaceSubview:oldView with:newView];
1378 void wxWindow::DoEnable(bool enable)
1380 CocoaSetEnabled(enable);
1383 bool wxWindow::Show(bool show)
1385 wxAutoNSAutoreleasePool pool;
1386 // If the window is marked as visible, then it shouldn't have a dummy view
1387 // If the window is marked hidden, then it should have a dummy view
1388 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
1389 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
1390 // Return false if there isn't a window to show or hide
1391 NSView *cocoaView = GetNSViewForHiding();
1396 // If state isn't changing, return false
1399 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
1400 wxASSERT(![m_cocoaHider->GetNSView() superview]);
1401 delete m_cocoaHider;
1402 m_cocoaHider = NULL;
1403 wxASSERT([cocoaView superview]);
1407 // If state isn't changing, return false
1410 m_cocoaHider = new wxWindowCocoaHider(this);
1411 // NOTE: replaceSubview:with will cause m_cocaNSView to be
1412 // (auto)released which balances out addSubview
1413 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
1414 // m_coocaNSView is now only retained by us
1415 wxASSERT([m_cocoaHider->GetNSView() superview]);
1416 wxASSERT(![cocoaView superview]);
1422 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
1424 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoSetSizeWindow(%d,%d,%d,%d,Auto: %s%s)"),this,x,y,width,height,(sizeFlags&wxSIZE_AUTO_WIDTH)?"W":".",sizeFlags&wxSIZE_AUTO_HEIGHT?"H":".");
1425 int currentX, currentY;
1426 int currentW, currentH;
1427 DoGetPosition(¤tX, ¤tY);
1428 DoGetSize(¤tW, ¤tH);
1429 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1431 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1434 AdjustForParentClientOrigin(x,y,sizeFlags);
1436 wxSize size(wxDefaultSize);
1438 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1440 if(sizeFlags&wxSIZE_AUTO_WIDTH)
1442 size=DoGetBestSize();
1448 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1450 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
1453 size=DoGetBestSize();
1459 DoMoveWindow(x,y,width,height);
1464 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
1466 wxWindowBase::DoSetToolTip(tip);
1470 m_tooltip->SetWindow((wxWindow *)this);
1476 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
1478 wxAutoNSAutoreleasePool pool;
1479 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
1481 NSView *nsview = GetNSViewForSuperview();
1482 NSView *superview = [nsview superview];
1484 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1486 NSRect oldFrameRect = [nsview frame];
1487 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
1488 [nsview setFrame:newFrameRect];
1489 // Be sure to redraw the parent to reflect the changed position
1490 [superview setNeedsDisplayInRect:oldFrameRect];
1491 [superview setNeedsDisplayInRect:newFrameRect];
1494 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1496 NSView *nsview = GetNSViewForSuperview();
1497 NSView *superview = [nsview superview];
1498 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
1499 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1500 NSRect frameRect = [nsview frame];
1502 frameRect.size.width = size.x;
1504 frameRect.size.height = size.y;
1505 frameRect.origin.x = pos.x;
1506 frameRect.origin.y = pos.y;
1507 // Tell Cocoa to change the margin between the bottom of the superview
1508 // and the bottom of the control. Keeps the control pinned to the top
1509 // of its superview so that its position in the wxWidgets coordinate
1510 // system doesn't change.
1511 if(![superview isFlipped])
1512 [nsview setAutoresizingMask: NSViewMinYMargin];
1513 // MUST set the mask before setFrame: which can generate a size event
1514 // and cause a scroller to be added!
1515 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1516 [nsview setFrame: frameRect];
1520 void wxWindow::DoGetSize(int *w, int *h) const
1522 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1524 *w=(int)cocoaRect.size.width;
1526 *h=(int)cocoaRect.size.height;
1527 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1530 void wxWindow::DoGetPosition(int *x, int *y) const
1532 NSView *nsview = GetNSViewForSuperview();
1534 NSRect cocoaRect = [nsview frame];
1535 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1537 *x=(int)rectWx.origin.x;
1539 *y=(int)rectWx.origin.y;
1540 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1543 WXWidget wxWindow::GetHandle() const
1545 return m_cocoaNSView;
1548 wxWindow* wxWindow::GetWxWindow() const
1550 return (wxWindow*) this;
1553 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1555 [m_cocoaNSView setNeedsDisplay:YES];
1558 void wxWindow::SetFocus()
1560 if([GetNSView() acceptsFirstResponder])
1561 [[GetNSView() window] makeFirstResponder: GetNSView()];
1564 void wxWindow::DoCaptureMouse()
1567 sm_capturedWindow = this;
1570 void wxWindow::DoReleaseMouse()
1573 sm_capturedWindow = NULL;
1576 void wxWindow::DoScreenToClient(int *x, int *y) const
1578 // Point in cocoa screen coordinates:
1579 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1580 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1581 NSWindow *theWindow = [clientView window];
1583 // Point in window's base coordinate system:
1584 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1585 // Point in view's bounds coordinate system
1586 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1587 // Point in wx client coordinates:
1588 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1590 *x = theWxClientPoint.x;
1592 *y = theWxClientPoint.y;
1595 void wxWindow::DoClientToScreen(int *x, int *y) const
1597 // Point in wx client coordinates
1598 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1600 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1602 // Point in the view's bounds coordinate system
1603 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1605 // Point in the window's base coordinate system
1606 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1608 NSWindow *theWindow = [clientView window];
1609 // Point in Cocoa's screen coordinates
1610 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1612 // Act as though this was the origin of a 0x0 rectangle
1613 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1615 // Convert that rectangle to wx coordinates
1616 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1618 *x = theWxScreenPoint.x;
1620 *y = theWxScreenPoint.y;
1623 // Get size *available for subwindows* i.e. excluding menu bar etc.
1624 void wxWindow::DoGetClientSize(int *x, int *y) const
1626 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1627 if(m_wxCocoaScrollView)
1628 m_wxCocoaScrollView->DoGetClientSize(x,y);
1630 wxWindowCocoa::DoGetSize(x,y);
1633 void wxWindow::DoSetClientSize(int width, int height)
1635 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1636 if(m_wxCocoaScrollView)
1637 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1638 CocoaSetWxWindowSize(width,height);
1641 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1643 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1646 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1648 // Intentional no-op.
1651 wxString wxWindow::GetLabel() const
1653 // General Get/Set of labels is implemented in wxControlBase
1654 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1655 return wxEmptyString;
1658 int wxWindow::GetCharHeight() const
1664 int wxWindow::GetCharWidth() const
1670 void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
1671 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
1673 // FIXME: This obviously ignores the window's font (if any) along with any size
1674 // transformations. However, it's better than nothing.
1675 // We don't create a wxClientDC because we don't want to accidently be able to use
1677 wxClientDC tmpdc(const_cast<wxWindow*>(this));
1678 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
1681 // Coordinates relative to the window
1682 void wxWindow::WarpPointer (int x_pos, int y_pos)
1687 int wxWindow::GetScrollPos(int orient) const
1689 if(m_wxCocoaScrollView != NULL)
1690 return m_wxCocoaScrollView->GetScrollPos(static_cast<wxOrientation>(orient & wxBOTH));
1695 // This now returns the whole range, not just the number
1696 // of positions that we can scroll.
1697 int wxWindow::GetScrollRange(int orient) const
1699 if(m_wxCocoaScrollView != NULL)
1700 return m_wxCocoaScrollView->GetScrollRange(static_cast<wxOrientation>(orient & wxBOTH));
1705 int wxWindow::GetScrollThumb(int orient) const
1707 if(m_wxCocoaScrollView != NULL)
1708 return m_wxCocoaScrollView->GetScrollThumb(static_cast<wxOrientation>(orient & wxBOTH));
1713 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1715 if(m_wxCocoaScrollView != NULL)
1716 return m_wxCocoaScrollView->SetScrollPos(static_cast<wxOrientation>(orient & wxBOTH), pos);
1719 void wxWindow::CocoaCreateNSScrollView()
1721 if(!m_wxCocoaScrollView)
1723 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1727 // New function that will replace some of the above.
1728 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1729 int range, bool refresh)
1731 CocoaCreateNSScrollView();
1732 m_wxCocoaScrollView->SetScrollbar(orient, pos, thumbVisible, range);
1733 // TODO: Handle refresh (if we even need to)
1736 // Does a physical scroll
1737 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1739 if(m_wxCocoaScrollView != NULL)
1740 m_wxCocoaScrollView->ScrollWindow(dx, dy, rect);
1743 void wxWindow::DoSetVirtualSize( int x, int y )
1745 // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
1746 // possibly not what the caller passed in. For example, the current implementation
1747 // clamps the width and height to within the min/max virtual ranges.
1748 // wxDefaultCoord is passed through unchanged but then GetVirtualSize() will correct
1749 // that by returning effectively max(virtual, client)
1750 wxWindowBase::DoSetVirtualSize(x,y);
1751 // Create the scroll view if it hasn't been already.
1752 CocoaCreateNSScrollView();
1754 // The GetVirtualSize automatically increases the size to max(client,virtual)
1755 m_wxCocoaScrollView->UpdateSizes();
1758 bool wxWindow::SetFont(const wxFont& font)
1760 // FIXME: We may need to handle wx font inheritance.
1761 return wxWindowBase::SetFont(font);
1764 #if 0 // these are used when debugging the algorithm.
1765 static char const * const comparisonresultStrings[] =
1772 class CocoaWindowCompareContext
1774 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1776 CocoaWindowCompareContext(); // Not implemented
1777 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1780 // Cocoa sorts subviews in-place.. make a copy
1781 m_subviews = [subviews copy];
1783 ~CocoaWindowCompareContext()
1784 { // release the copy
1785 [m_subviews release];
1788 { return m_target; }
1790 { return m_subviews; }
1791 /* Helper function that returns the comparison based off of the original ordering */
1792 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1794 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1795 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1796 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1797 // likely compare higher than the other view which is reasonable considering the only way that
1798 // can happen is if the subview was added after our call to subviews but before the call to
1799 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1800 // that case should never occur anyway because that would imply a multi-threaded GUI call
1801 // which is a big no-no with Cocoa.
1803 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1804 NSComparisonResult result = (firstI < secondI)
1805 ? NSOrderedAscending /* -1 */
1806 : (firstI > secondI)
1807 ? NSOrderedDescending /* 1 */
1808 : NSOrderedSame /* 0 */;
1810 #if 0 // Enable this if you need to debug the algorithm.
1811 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1816 /* The subview we are trying to Raise or Lower */
1818 /* A copy of the original array of subviews */
1819 NSArray *m_subviews;
1822 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1823 * the target view is always higher than every other view. When comparing two views neither of
1824 * which is the target, it returns the correct response based on the original ordering
1826 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1828 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1829 // first should be ordered higher
1830 if(first==compareContext->target())
1831 return NSOrderedDescending;
1832 // second should be ordered higher
1833 if(second==compareContext->target())
1834 return NSOrderedAscending;
1835 return compareContext->CompareUsingOriginalOrdering(first,second);
1838 // Raise the window to the top of the Z order
1839 void wxWindow::Raise()
1841 // wxAutoNSAutoreleasePool pool;
1842 NSView *nsview = GetNSViewForSuperview();
1843 NSView *superview = [nsview superview];
1844 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1846 [superview sortSubviewsUsingFunction:
1847 CocoaRaiseWindowCompareFunction
1848 context: &compareContext];
1851 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1852 * the target view is always lower than every other view. When comparing two views neither of
1853 * which is the target, it returns the correct response based on the original ordering
1855 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1857 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1858 // first should be ordered lower
1859 if(first==compareContext->target())
1860 return NSOrderedAscending;
1861 // second should be ordered lower
1862 if(second==compareContext->target())
1863 return NSOrderedDescending;
1864 return compareContext->CompareUsingOriginalOrdering(first,second);
1867 // Lower the window to the bottom of the Z order
1868 void wxWindow::Lower()
1870 NSView *nsview = GetNSViewForSuperview();
1871 NSView *superview = [nsview superview];
1872 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1875 NSLog(@"Target:\n%@\n", nsview);
1876 NSLog(@"Before:\n%@\n", compareContext.subviews());
1878 [superview sortSubviewsUsingFunction:
1879 CocoaLowerWindowCompareFunction
1880 context: &compareContext];
1882 NSLog(@"After:\n%@\n", [superview subviews]);
1886 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1891 // Get the window with the focus
1892 wxWindow *wxWindowBase::DoFindFocus()
1894 // Basically we are somewhat emulating the responder chain here except
1895 // we are only loking for the first responder in the key window or
1896 // upon failing to find one if the main window is different we look
1897 // for the first responder in the main window.
1899 // Note that the firstResponder doesn't necessarily have to be an
1900 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
1901 // NULL unless it finds its argument in its hash map.
1905 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
1906 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
1908 return win->GetWxWindow();
1910 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
1911 if(mainWindow == keyWindow)
1913 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
1915 return win->GetWxWindow();
1920 /* static */ wxWindow *wxWindowBase::GetCapture()
1923 return wxWindowCocoa::sm_capturedWindow;
1926 wxWindow *wxGetActiveWindow()
1932 wxPoint wxGetMousePosition()
1935 return wxDefaultPosition;
1938 wxMouseState wxGetMouseState()
1945 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
1947 pt = wxGetMousePosition();
1951 // ========================================================================
1952 // wxCocoaMouseMovedEventSynthesizer
1953 // ========================================================================
1955 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
1957 /* This class registers one run loop observer to cover all windows registered with it.
1958 * It will register the observer when the first view is registerd and unregister the
1959 * observer when the last view is unregistered.
1960 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
1961 * is no reason it couldn't be instantiated multiple times.
1963 class wxCocoaMouseMovedEventSynthesizer
1965 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
1967 wxCocoaMouseMovedEventSynthesizer()
1968 { m_lastScreenMouseLocation = NSZeroPoint;
1970 ~wxCocoaMouseMovedEventSynthesizer();
1971 void RegisterWxCocoaView(wxCocoaNSView *aView);
1972 void UnregisterWxCocoaView(wxCocoaNSView *aView);
1973 void SynthesizeMouseMovedEvent();
1976 void AddRunLoopObserver();
1977 void RemoveRunLoopObserver();
1978 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
1979 std::list<wxCocoaNSView*> m_registeredViews;
1980 NSPoint m_lastScreenMouseLocation;
1981 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
1984 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
1986 m_registeredViews.push_back(aView);
1987 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
1989 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
1991 AddRunLoopObserver();
1995 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
1997 m_registeredViews.remove(aView);
1998 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
1999 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
2001 RemoveRunLoopObserver();
2005 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
2007 if(!m_registeredViews.empty())
2009 // This means failure to clean up so we report on it as a debug message.
2010 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
2011 m_registeredViews.clear();
2013 if(m_runLoopObserver != NULL)
2015 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
2016 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
2017 RemoveRunLoopObserver();
2021 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
2023 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
2026 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
2028 CFRunLoopObserverContext observerContext =
2036 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
2037 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
2039 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
2040 // in our observer procedure being called before the run loop has decided that it is going to return control to
2041 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
2042 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
2044 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
2045 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
2046 // around to it before letting the event loop do its work. This has some very odd effects on the way the
2047 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
2048 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
2049 // time the new one is built.
2051 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
2052 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
2053 // the mouseExited: message to be sent properly.
2055 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
2056 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2057 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
2060 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
2062 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2063 m_runLoopObserver.reset();
2064 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
2067 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
2069 NSPoint screenMouseLocation = [NSEvent mouseLocation];
2070 // Checking the last mouse location is done for a few reasons:
2071 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
2072 // telling the app the mouse moved when the user hit a key for instance.
2073 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
2074 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
2075 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
2076 // loop causing the tracking rectangles to constantly be reset.
2077 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
2079 m_lastScreenMouseLocation = screenMouseLocation;
2080 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
2081 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
2083 (*i)->Cocoa_synthesizeMouseMoved();
2088 // Singleton used for all views:
2089 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
2091 // ========================================================================
2092 // wxCocoaTrackingRectManager
2093 // ========================================================================
2095 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
2098 m_isTrackingRectActive = false;
2099 BuildTrackingRect();
2102 void wxCocoaTrackingRectManager::ClearTrackingRect()
2104 if(m_isTrackingRectActive)
2106 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
2107 m_isTrackingRectActive = false;
2108 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2110 // If we were doing periodic events we need to clear those too
2111 StopSynthesizingEvents();
2114 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
2116 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
2119 void wxCocoaTrackingRectManager::BuildTrackingRect()
2121 // Pool here due to lack of one during wx init phase
2122 wxAutoNSAutoreleasePool pool;
2124 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
2126 NSView *theView = m_window->GetNSView();
2128 if([theView window] != nil)
2130 NSRect visibleRect = [theView visibleRect];
2132 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
2133 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
2134 m_isTrackingRectActive = true;
2136 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2140 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
2142 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
2145 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
2147 if(m_isTrackingRectActive)
2149 NSView *theView = m_window->GetNSView();
2150 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
2151 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
2153 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
2157 RebuildTrackingRect();
2160 void wxCocoaTrackingRectManager::RebuildTrackingRect()
2162 ClearTrackingRect();
2163 BuildTrackingRect();
2166 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
2168 ClearTrackingRect();
2171 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
2173 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);