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/osx/core/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 // NOTE: This class and method of hiding predates setHidden: support in
313 // the toolkit. The hack used here is to replace the view with a stand-in
314 // that will be subject to the usual Cocoa resizing rules.
315 // When possible (i.e. when running on 10.3 or higher) we make it hidden
316 // mostly as an optimization so Cocoa doesn't have to consider it when
317 // drawing or finding key views.
318 // ========================================================================
319 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
323 wxASSERT(owner->GetNSViewForHiding());
324 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
325 initWithFrame:[owner->GetNSViewForHiding() frame]];
326 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
327 AssociateNSView(m_dummyNSView);
329 if([m_dummyNSView respondsToSelector:@selector(setHidden:)])
330 [m_dummyNSView setHidden:YES];
333 wxWindowCocoaHider::~wxWindowCocoaHider()
335 DisassociateNSView(m_dummyNSView);
336 [m_dummyNSView release];
339 void wxWindowCocoaHider::Cocoa_FrameChanged(void)
341 // Keep the real window in synch with the dummy
342 wxASSERT(m_dummyNSView);
343 [m_owner->GetNSViewForHiding() setFrame:[m_dummyNSView frame]];
347 #ifdef WXCOCOA_FILL_DUMMY_VIEW
348 bool wxWindowCocoaHider::Cocoa_drawRect(const NSRect& rect)
350 NSBezierPath *bezpath = [NSBezierPath bezierPathWithRect:rect];
351 [[NSColor greenColor] set];
356 #endif //def WXCOCOA_FILL_DUMMY_VIEW
359 /*! @class WXManualScrollView
360 @abstract An NSScrollView subclass which implements wx scroll behavior
362 Overrides default behavior of NSScrollView such that this class receives
363 the scroller action messages and allows the wxCocoaScrollView to act
364 on them accordingly. In particular, because the NSScrollView will not
365 receive action messages from the scroller, it will not adjust the
366 document view. This must be done manually using the ScrollWindow
369 @interface WXManualScrollView : NSScrollView
371 /*! @field m_wxCocoaScrollView
373 wxWindowCocoaScrollView *m_wxCocoaScrollView;
376 // Override these to set up the target/action correctly
377 - (void)setHorizontalScroller:(NSScroller *)aScroller;
378 - (void)setVerticalScroller:(NSScroller *)aScroller;
379 - (void)setHasHorizontalScroller:(BOOL)flag;
380 - (void)setHasVerticalScroller:(BOOL)flag;
382 // NOTE: _wx_ prefix means "private" method like _ that Apple (and only Apple) uses.
383 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView;
384 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView;
386 /*! @method _wx_doScroller
387 @abstract Handles action messages from the scrollers
389 Similar to Apple's _doScroller: method which is private and not documented.
390 We do not, however, call that method. Instead, we effectively override
391 it entirely. We don't override it by naming ourself the same thing because
392 the base class code may or may not call that method for other reasons we
393 simply cannot know about.
395 - (void)_wx_doScroller:(id)sender;
400 @implementation WXManualScrollView : NSScrollView
402 static inline void WXManualScrollView_DoSetScrollerTargetAction(WXManualScrollView *self, NSScroller *aScroller)
404 if(aScroller != NULL && [self _wx_wxCocoaScrollView] != NULL)
406 [aScroller setTarget:self];
407 [aScroller setAction:@selector(_wx_doScroller:)];
411 - (void)setHorizontalScroller:(NSScroller *)aScroller
413 [super setHorizontalScroller:aScroller];
414 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
417 - (void)setVerticalScroller:(NSScroller *)aScroller
419 [super setVerticalScroller:aScroller];
420 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
423 - (void)setHasHorizontalScroller:(BOOL)flag
425 [super setHasHorizontalScroller:flag];
426 WXManualScrollView_DoSetScrollerTargetAction(self, [self horizontalScroller]);
429 - (void)setHasVerticalScroller:(BOOL)flag
431 [super setHasVerticalScroller:flag];
432 WXManualScrollView_DoSetScrollerTargetAction(self, [self verticalScroller]);
435 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView
436 { return m_wxCocoaScrollView; }
438 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView
440 m_wxCocoaScrollView = theWxScrollView;
441 [self setHorizontalScroller:[self horizontalScroller]];
442 [self setVerticalScroller:[self verticalScroller]];
445 - (void)_wx_doScroller:(id)sender
447 if(m_wxCocoaScrollView != NULL)
448 m_wxCocoaScrollView->_wx_doScroller(sender);
450 wxLogError(wxT("Unexpected action message received from NSScroller"));
453 - (void)reflectScrolledClipView:(NSClipView *)aClipView
455 struct _ScrollerBackup
457 _ScrollerBackup(NSScroller *aScroller)
458 : m_scroller(aScroller)
459 , m_floatValue(aScroller!=nil?[aScroller floatValue]:0.0)
460 , m_knobProportion(aScroller!=nil?[aScroller knobProportion]:1.0)
461 , m_isEnabled(aScroller!=nil?[aScroller isEnabled]:false)
464 NSScroller *m_scroller;
465 CGFloat m_floatValue;
466 CGFloat m_knobProportion;
470 if(m_scroller != nil)
472 [m_scroller setFloatValue:m_floatValue knobProportion:m_knobProportion];
473 [m_scroller setEnabled:m_isEnabled];
478 _ScrollerBackup(_ScrollerBackup const&);
479 _ScrollerBackup& operator=(_ScrollerBackup const&);
481 _ScrollerBackup _horizontalBackup([self horizontalScroller]);
482 _ScrollerBackup _verticalBackup([self verticalScroller]);
483 // We MUST call super's implementation or else nothing seems to work right at all.
484 // However, we need our scrollers not to change values due to the document window
485 // moving so we cheat and save/restore their values across this call.
486 [super reflectScrolledClipView: aClipView];
490 WX_IMPLEMENT_GET_OBJC_CLASS(WXManualScrollView,NSScrollView)
492 // ========================================================================
493 // wxFlippedNSClipView
494 // ========================================================================
495 @interface wxFlippedNSClipView : NSClipView
498 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
500 @implementation wxFlippedNSClipView : NSClipView
507 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
509 // ========================================================================
510 // wxWindowCocoaScrollView
511 // ========================================================================
512 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
514 , m_cocoaNSScrollView() // nil
515 , m_scrollRange() // {0,0}
516 , m_scrollThumb() // {0,0}
517 , m_virtualOrigin(0,0)
519 wxAutoNSAutoreleasePool pool;
521 wxASSERT(owner->GetNSView());
522 m_isNativeView = ![owner->GetNSView() isKindOfClass:[WX_GET_OBJC_CLASS(WXNSView) class]];
523 m_cocoaNSScrollView = [(m_isNativeView?[NSScrollView alloc]:[WXManualScrollView alloc])
524 initWithFrame:[owner->GetNSView() frame]];
525 AssociateNSView(m_cocoaNSScrollView);
528 /* Set a bezel border around the entire thing because it looks funny without it.
529 TODO: Be sure to undo any borders on the real view (if any) and apply them
530 to this view if necessary. Right now, there is no border support in wxCocoa
531 so this isn't an issue.
533 [m_cocoaNSScrollView setBorderType:NSBezelBorder];
537 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView: this];
538 // Don't set a bezel because we might be creating a scroll view due to being
539 // the "target window" of a wxScrolledWindow. That is to say that the user
540 // has absolutely no intention of scrolling the clip view used by this
544 /* Replace the default NSClipView with a flipped one. This ensures
545 scrolling is "pinned" to the top-left instead of bottom-right. */
546 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
547 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
548 [m_cocoaNSScrollView setContentView:flippedClip];
549 [flippedClip release];
551 // In all cases we must encapsulate the real NSView properly
555 void wxWindowCocoaScrollView::Encapsulate()
557 // Set the scroll view autoresizingMask to match the current NSView
558 [m_cocoaNSScrollView setAutoresizingMask: [m_owner->GetNSView() autoresizingMask]];
559 [m_owner->GetNSView() setAutoresizingMask: NSViewNotSizable];
560 // NOTE: replaceSubView will cause m_cocaNSView to be released
561 // except when it hasn't been added into an NSView hierarchy in which
562 // case it doesn't need to be and this should work out to a no-op
563 m_owner->CocoaReplaceView(m_owner->GetNSView(), m_cocoaNSScrollView);
564 // The NSView is still retained by owner
565 [m_cocoaNSScrollView setDocumentView: m_owner->GetNSView()];
566 // Now it's also retained by the NSScrollView
569 void wxWindowCocoaScrollView::Unencapsulate()
571 [m_cocoaNSScrollView setDocumentView: nil];
572 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
573 if(![[m_owner->GetNSView() superview] isFlipped])
574 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
577 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
579 DisassociateNSView(m_cocoaNSScrollView);
582 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView:NULL];
584 [m_cocoaNSScrollView release];
587 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
589 NSSize frameSize = [NSScrollView
590 frameSizeForContentSize: NSMakeSize(width,height)
591 hasHorizontalScroller: [m_cocoaNSScrollView hasHorizontalScroller]
592 hasVerticalScroller: [m_cocoaNSScrollView hasVerticalScroller]
593 borderType: [m_cocoaNSScrollView borderType]];
594 width = (int)frameSize.width;
595 height = (int)frameSize.height;
598 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
600 NSSize nssize = [m_cocoaNSScrollView contentSize];
602 *x = (int)nssize.width;
604 *y = (int)nssize.height;
607 static inline void SetCocoaScroller(NSScroller *aScroller, int WXUNUSED(orientation), int position, int thumbSize, int range)
609 wxCHECK_RET(aScroller != nil, wxT("Expected the NSScrollView to have a scroller"));
611 // NOTE: thumbSize is already ensured to be >= 1 and <= range by our caller
612 // unless range = 0 in which case we shouldn't have been be called.
613 wxCHECK_RET(range > 0, wxT("Internal wxCocoa bug: shouldn't have been called with 0 range"));
615 // Range of valid position values is from 0 to effectiveRange
616 // NOTE: if thumbSize == range then effectiveRange is 0.
617 // thumbSize is at least 1 which gives range from 0 to range - 1 inclusive
618 // which is exactly what we want.
619 int effectiveRange = range - thumbSize;
621 // knobProportion is hopefully easy to understand
622 // Note that thumbSize is already guaranteed >= 1 by our caller.
623 CGFloat const knobProportion = CGFloat(thumbSize)/CGFloat(range);
625 // NOTE: When effectiveRange is zero there really is no valid position
626 // We arbitrarily pick 0.0 which is the same as a scroller in the home position.
627 CGFloat const floatValue = (effectiveRange != 0)?CGFloat(position)/CGFloat(effectiveRange):0.0;
629 [aScroller setFloatValue:floatValue knobProportion: knobProportion];
630 // Make sure it's visibly working
631 [aScroller setEnabled:YES];
634 void wxWindowCocoaScrollView::SetScrollPos(wxOrientation orientation, int position)
636 // NOTE: Rather than using only setFloatValue: (which we could do) we instead
637 // simply share the SetCocoaScroller call because all but the knobProportion
638 // calculations have to be done anyway.
639 if(orientation & wxHORIZONTAL)
641 NSScroller *aScroller = [m_cocoaNSScrollView horizontalScroller];
643 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[0], m_scrollRange[0]);
645 if(orientation & wxVERTICAL)
647 NSScroller *aScroller = [m_cocoaNSScrollView verticalScroller];
649 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[1], m_scrollRange[1]);
653 void wxWindowCocoaScrollView::SetScrollbar(int orientation, int position, int thumbSize, int range)
655 // FIXME: API assumptions:
656 // 1. If the user wants to remove a scroller he gives range 0.
657 // 2. If the user wants to disable a scroller he sets thumbSize == range
658 // in which case it is logically impossible to scroll.
659 // The scroller shall still be displayed.
661 // Ensure that range is >= 0.
662 wxASSERT(range >= 0);
666 // Ensure that thumbSize <= range
667 wxASSERT(thumbSize <= range);
668 // Also ensure thumbSize >= 1 but don't complain if it isn't
671 // Now make sure it's really less than range, even if we just set it to 1
672 if(thumbSize > range)
675 bool needScroller = (range != 0);
677 // Can potentially set both horizontal and vertical at the same time although this is
678 // probably not very useful.
679 if(orientation & wxHORIZONTAL)
681 m_scrollRange[0] = range;
682 m_scrollThumb[0] = thumbSize;
685 [m_cocoaNSScrollView setHasHorizontalScroller:needScroller];
687 SetCocoaScroller([m_cocoaNSScrollView horizontalScroller], orientation, position, thumbSize, range);
691 if(orientation & wxVERTICAL)
693 m_scrollRange[1] = range;
694 m_scrollThumb[1] = thumbSize;
697 [m_cocoaNSScrollView setHasVerticalScroller:needScroller];
699 SetCocoaScroller([m_cocoaNSScrollView verticalScroller], orientation, position, thumbSize, range);
704 int wxWindowCocoaScrollView::GetScrollPos(wxOrientation orient)
706 if((orient & wxBOTH) == wxBOTH)
708 wxLogError(wxT("GetScrollPos called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
711 int effectiveScrollRange;
712 NSScroller *cocoaScroller;
713 if(orient & wxHORIZONTAL)
715 effectiveScrollRange = m_scrollRange[0] - m_scrollThumb[0];
716 cocoaScroller = [m_cocoaNSScrollView horizontalScroller];
718 else if(orient & wxVERTICAL)
720 effectiveScrollRange = m_scrollRange[1] - m_scrollThumb[1];
721 cocoaScroller = [m_cocoaNSScrollView verticalScroller];
725 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
728 if(cocoaScroller == nil)
729 { // Document is not scrolled
733 The effective range of a scroll bar as defined by wxWidgets is from 0 to (range - thumbSize).
734 That is a scroller at the left/top position is at 0 and a scroller at the bottom/right
735 position is at range-thumbsize.
737 The range of an NSScroller is 0.0 to 1.0. Much easier! NOTE: Apple doesn't really specify
738 but GNUStep docs do say that 0.0 is top/left and 1.0 is bottom/right. This is actualy
739 in contrast to NSSlider which generally has 1.0 at the TOP when it's done vertically.
741 CGFloat cocoaScrollPos = [cocoaScroller floatValue];
742 return effectiveScrollRange * cocoaScrollPos;
745 int wxWindowCocoaScrollView::GetScrollRange(wxOrientation orient)
747 if((orient & wxBOTH) == wxBOTH)
749 wxLogError(wxT("GetScrollRange called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
752 if(orient & wxHORIZONTAL)
754 return m_scrollRange[0];
756 else if(orient & wxVERTICAL)
758 return m_scrollRange[1];
762 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
767 int wxWindowCocoaScrollView::GetScrollThumb(wxOrientation orient)
769 if((orient & wxBOTH) == wxBOTH)
771 wxLogError(wxT("GetScrollThumb called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
774 if(orient & wxHORIZONTAL)
776 return m_scrollThumb[0];
778 else if(orient & wxVERTICAL)
780 return m_scrollThumb[1];
784 wxLogError(wxT("GetScrollThumb called without an orientation which makes no sense"));
790 Moves the contents (all existing drawing as well as all all child wxWindow) by the specified
791 amount expressed in the wxWindow's own coordinate system. This is used to implement scrolling
792 but the usage is rather interesting. When scrolling right (e.g. increasing the value of
793 the scroller) you must give a negative delta x (e.g. moving the contents LEFT). Likewise,
794 when scrolling the window down, increasing the value of the scroller, you give a negative
795 delta y which moves the contents up.
797 wxCocoa notes: To accomplish this trick in Cocoa we basically do what NSScrollView would
798 have done and that is adjust the content view's bounds origin. The content view is somewhat
799 confusingly the NSClipView which is more or less sort of the pImpl for NSScrollView
800 The real NSView with the user's content (e.g. the "virtual area" in wxWidgets parlance)
801 is called the document view in NSScrollView parlance.
803 The bounds origin is basically the exact opposite concept. Whereas in Windows the client
804 coordinate system remains constant and the content must shift left/up for increases
805 of scrolling, in Cocoa the coordinate system is actually the virtual one. So we must
806 instead shift the bounds rectangle right/down to get the effect of the content moving
807 left/up. Basically, it's a higher level interface than that provided by wxWidgets
808 so essentially we're implementing the low-level move content interface in terms of
809 the high-level move the viewport (the bounds) over top that content (the document
810 view which is the virtual area to wx).
812 For all intents and purposes that basically just means that we subtract the deltas
813 from the bounds origin and thus a negative delta actually increases the bounds origin
814 and a positive delta actually decreases it. This is absolutely true for the horizontal
815 axis but there's a catch in the vertical axis. If the content view (the clip view) is
816 flipped (and we do this by default) then it works exactly like the horizontal axis.
817 If it is not flipped (i.e. it is in postscript coordinates which are opposite to
818 wxWidgets) then the sense needs to be reversed.
820 However, this plays hell with window positions. The frame rects of any child views
821 do not change origin and this is actually important because if they did, the views
822 would send frame changed notifications, not to mention that Cocoa just doesn't really
823 do scrolling that way, it does it the way we do it here.
825 To fix this we implement GetPosition for child windows to not merely consult its
826 superview at the Cocoa level in order to do proper Cocoa->wx coordinate transform
827 but to actually consult is parent wxWindow because it makes a big difference if
828 the parent is scrolled. Argh. (FIXME: This isn't actually implemented yet)
830 void wxWindowCocoaScrollView::ScrollWindow(int dx, int dy, const wxRect*)
832 // Update our internal origin so we know how much the application code
833 // expects us to have been scrolled.
834 m_virtualOrigin.x += dx;
835 m_virtualOrigin.y += dy;
837 // Scroll the window using the standard Cocoa method of adjusting the
838 // clip view's bounds in the opposite fashion.
839 NSClipView *contentView = [m_cocoaNSScrollView contentView];
840 NSRect clipViewBoundsRect = [contentView bounds];
841 clipViewBoundsRect.origin.x -= dx;
842 if([contentView isFlipped])
843 clipViewBoundsRect.origin.y -= dy;
845 clipViewBoundsRect.origin.y += dy;
846 [contentView scrollToPoint:clipViewBoundsRect.origin];
849 void wxWindowCocoaScrollView::_wx_doScroller(NSScroller *sender)
851 wxOrientation orientation;
852 if(sender == [m_cocoaNSScrollView horizontalScroller])
853 orientation = wxHORIZONTAL;
854 else if(sender == [m_cocoaNSScrollView verticalScroller])
855 orientation = wxVERTICAL;
858 wxLogDebug(wxT("Received action message from unexpected NSScroller"));
861 // NOTE: Cocoa does not move the scroller for page up/down or line
862 // up/down events. That means the value will be the old value.
863 // For thumbtrack events, the value is the new value.
864 int scrollpos = GetScrollPos(orientation);
866 switch([sender hitPart])
869 case NSScrollerNoPart:
870 case NSScrollerKnob: // Drag of knob
871 case NSScrollerKnobSlot: // Jump directly to position
872 commandType = wxEVT_SCROLLWIN_THUMBTRACK;
874 case NSScrollerDecrementPage:
875 commandType = wxEVT_SCROLLWIN_PAGEUP;
877 case NSScrollerIncrementPage:
878 commandType = wxEVT_SCROLLWIN_PAGEDOWN;
880 case NSScrollerDecrementLine:
881 commandType = wxEVT_SCROLLWIN_LINEUP;
883 case NSScrollerIncrementLine:
884 commandType = wxEVT_SCROLLWIN_LINEDOWN;
887 wxScrollWinEvent event(commandType, scrollpos, orientation);
888 event.SetEventObject(m_owner);
889 m_owner->HandleWindowEvent(event);
892 void wxWindowCocoaScrollView::UpdateSizes()
894 // Using the virtual size, figure out what the document frame size should be
895 // NOTE: Assume that the passed in virtualSize is already >= the client size
896 wxSize virtualSize = m_owner->GetVirtualSize();
898 // Get the document's current frame
899 NSRect documentViewFrame = [m_owner->GetNSView() frame];
900 NSRect newFrame = documentViewFrame;
901 newFrame.size = NSMakeSize(virtualSize.x, virtualSize.y);
903 if(!NSEqualRects(newFrame, documentViewFrame))
905 [m_owner->GetNSView() setFrame:newFrame];
909 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
911 wxLogTrace(wxTRACE_COCOA,wxT("wxWindowCocoaScrollView=%p::Cocoa_FrameChanged for wxWindow %p"), this, m_owner);
912 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
913 event.SetEventObject(m_owner);
914 m_owner->HandleWindowEvent(event);
916 /* If the view is not a native one then it's being managed by wx. In this case the control
917 may decide to change its virtual size and we must update the document view's size to
918 match. For native views the virtual size will never have been set so we do not want
925 // ========================================================================
927 // ========================================================================
928 // normally the base classes aren't included, but wxWindow is special
929 #ifdef __WXUNIVERSAL__
930 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
932 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
935 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
938 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
941 void wxWindowCocoa::Init()
943 m_cocoaNSView = NULL;
945 m_wxCocoaScrollView = NULL;
946 m_isBeingDeleted = false;
948 m_visibleTrackingRectManager = NULL;
952 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
956 const wxString& name)
958 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
961 // TODO: create the window
962 m_cocoaNSView = NULL;
963 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
964 [m_cocoaNSView release];
968 m_parent->AddChild(this);
969 m_parent->CocoaAddChild(this);
970 SetInitialFrameRect(pos,size);
977 wxWindow::~wxWindow()
979 wxAutoNSAutoreleasePool pool;
982 // Make sure our parent (in the wxWidgets sense) is our superview
983 // before we go removing from it.
984 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
985 CocoaRemoveFromParent();
987 delete m_wxCocoaScrollView;
993 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
995 // Pool here due to lack of one during wx init phase
996 wxAutoNSAutoreleasePool pool;
998 NSView *childView = child->GetNSViewForSuperview();
1000 wxASSERT(childView);
1001 [m_cocoaNSView addSubview: childView];
1004 void wxWindowCocoa::CocoaRemoveFromParent(void)
1006 [GetNSViewForSuperview() removeFromSuperview];
1009 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
1011 // Clear the visible area tracking rect if we have one.
1012 delete m_visibleTrackingRectManager;
1013 m_visibleTrackingRectManager = NULL;
1015 bool need_debug = cocoaNSView || m_cocoaNSView;
1016 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
1017 DisassociateNSView(m_cocoaNSView);
1018 wxGCSafeRetain(cocoaNSView);
1019 wxGCSafeRelease(m_cocoaNSView);
1020 m_cocoaNSView = cocoaNSView;
1021 AssociateNSView(m_cocoaNSView);
1022 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
1025 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
1028 ? m_cocoaHider->GetNSView()
1029 : m_wxCocoaScrollView
1030 ? m_wxCocoaScrollView->GetNSScrollView()
1034 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
1036 return m_wxCocoaScrollView
1037 ? m_wxCocoaScrollView->GetNSScrollView()
1041 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
1043 // TODO: Handle scrolling offset
1044 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
1047 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
1049 // TODO: Handle scrolling offset
1050 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
1053 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
1055 // TODO: Handle scrolling offset
1056 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
1059 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
1061 // TODO: Handle scrolling offset
1062 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
1065 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
1067 // TODO: Handle scrolling offset
1068 NSAffineTransform *transform = wxCocoaDCImpl::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
1072 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
1074 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
1075 // Recursion can happen if the event loop runs from within the paint
1076 // handler. For instance, if an assertion dialog is shown.
1077 // FIXME: This seems less than ideal.
1080 wxLogDebug(wxT("Paint event recursion!"));
1085 // Set m_updateRegion
1086 const NSRect *rects = ▭ // The bounding box of the region
1087 NSInteger countRects = 1;
1088 // Try replacing the larger rectangle with a list of smaller ones:
1089 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
1090 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
1092 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
1093 for(int i=0; i<countRects; i++)
1095 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
1097 m_updateRegion = wxRegion(transformedRects,countRects);
1098 free(transformedRects);
1100 wxPaintEvent event(m_windowId);
1101 event.SetEventObject(this);
1102 bool ret = HandleWindowEvent(event);
1103 m_isInPaint = false;
1107 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
1109 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
1110 // Mouse events happen at the NSWindow level so we need to convert
1111 // into our bounds coordinates then convert to wx coordinates.
1112 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
1113 if( m_wxCocoaScrollView != NULL)
1115 // This gets the wx client area (i.e. the visible portion of the content) in
1116 // the coordinate system of our (the doucment) view.
1117 NSRect documentVisibleRect = [[m_wxCocoaScrollView->GetNSScrollView() contentView] documentVisibleRect];
1118 // For horizontal, simply subtract the origin.
1119 // e.g. if the origin is at 123 and the user clicks as far left as possible then
1120 // the coordinate that wx wants is 0.
1121 cocoaPoint.x -= documentVisibleRect.origin.x;
1122 if([m_cocoaNSView isFlipped])
1124 // In the flipped view case this works exactly like horizontal.
1125 cocoaPoint.y -= documentVisibleRect.origin.y;
1127 // For vertical we have to mind non-flipped (e.g. y=0 at bottom) views.
1128 // We also need to mind the fact that we're still in Cocoa coordinates
1129 // and not wx coordinates. The wx coordinate translation will still occur
1130 // and that is going to be wxY = boundsH - cocoaY for non-flipped views.
1132 // When we consider scrolling we are truly interested in how far the top
1133 // edge of the bounds rectangle is scrolled off the screen.
1134 // Assuming the bounds origin is 0 (which is an assumption we make in
1135 // wxCocoa since wxWidgets has no analog to it) then the top edge of
1136 // the bounds rectangle is simply its height. The top edge of the
1137 // documentVisibleRect (e.g. the client area) is its height plus
1139 // Thus, we simply need add the distance between the bounds top
1140 // and the client (docuemntVisibleRect) top.
1141 // Or putting it another way, we subtract the distance between the
1142 // client top and the bounds top.
1145 NSRect bounds = [m_cocoaNSView bounds];
1146 CGFloat scrollYOrigin = (bounds.size.height - (documentVisibleRect.origin.y + documentVisibleRect.size.height));
1147 cocoaPoint.y += scrollYOrigin;
1151 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1152 // FIXME: Should we be adjusting for client area origin?
1153 const wxPoint &clientorigin = GetClientAreaOrigin();
1154 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1155 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1157 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
1158 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
1159 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
1160 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
1162 // TODO: set timestamp?
1163 event.SetEventObject(this);
1164 event.SetId(GetId());
1167 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
1169 wxMouseEvent event(wxEVT_MOTION);
1170 InitMouseEvent(event,theEvent);
1171 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
1172 return HandleWindowEvent(event);
1175 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
1177 wxMouseEvent event(wxEVT_MOTION);
1178 NSWindow *window = [GetNSView() window];
1179 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
1180 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
1182 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1183 // FIXME: Should we be adjusting for client area origin?
1184 const wxPoint &clientorigin = GetClientAreaOrigin();
1185 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1186 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1188 // TODO: Handle shift, control, alt, meta flags
1189 event.SetEventObject(this);
1190 event.SetId(GetId());
1192 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
1193 HandleWindowEvent(event);
1196 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
1198 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1200 m_visibleTrackingRectManager->BeginSynthesizingEvents();
1202 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
1203 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
1204 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
1205 // first responder, so the mouseMoved: events are effectively discarded.
1206 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
1208 wxMouseEvent event(wxEVT_ENTER_WINDOW);
1209 InitMouseEvent(event,theEvent);
1210 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
1211 return HandleWindowEvent(event);
1217 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
1219 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1221 m_visibleTrackingRectManager->StopSynthesizingEvents();
1223 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
1224 InitMouseEvent(event,theEvent);
1225 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
1226 return HandleWindowEvent(event);
1232 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
1234 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
1235 InitMouseEvent(event,theEvent);
1236 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1237 return HandleWindowEvent(event);
1240 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
1242 wxMouseEvent event(wxEVT_MOTION);
1243 InitMouseEvent(event,theEvent);
1244 event.m_leftDown = true;
1245 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1246 return HandleWindowEvent(event);
1249 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
1251 wxMouseEvent event(wxEVT_LEFT_UP);
1252 InitMouseEvent(event,theEvent);
1253 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1254 return HandleWindowEvent(event);
1257 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
1259 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
1260 InitMouseEvent(event,theEvent);
1261 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1262 return HandleWindowEvent(event);
1265 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
1267 wxMouseEvent event(wxEVT_MOTION);
1268 InitMouseEvent(event,theEvent);
1269 event.m_rightDown = true;
1270 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1271 return HandleWindowEvent(event);
1274 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
1276 wxMouseEvent event(wxEVT_RIGHT_UP);
1277 InitMouseEvent(event,theEvent);
1278 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1279 return HandleWindowEvent(event);
1282 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
1287 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
1292 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
1297 void wxWindowCocoa::Cocoa_FrameChanged(void)
1299 // We always get this message for the real NSView which may have been
1300 // enclosed in an NSScrollView. If that's the case then what we're
1301 // effectively getting here is a notifcation that the
1302 // virtual sized changed.. which we don't need to send on since
1303 // wx has no concept of this whatsoever.
1304 bool isViewForSuperview = (m_wxCocoaScrollView == NULL);
1305 if(isViewForSuperview)
1307 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
1308 if(m_visibleTrackingRectManager != NULL)
1309 m_visibleTrackingRectManager->RebuildTrackingRect();
1310 wxSizeEvent event(GetSize(), m_windowId);
1311 event.SetEventObject(this);
1312 HandleWindowEvent(event);
1316 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged ignored"),this);
1320 bool wxWindowCocoa::Cocoa_resetCursorRects()
1322 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
1324 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
1325 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
1326 // mouse exited event. If the control happens to share the edge with its parent window which is
1327 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
1328 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
1329 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
1330 // which willl in turn call us before exiting for the next user event.
1332 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
1333 // our mouse exited event because the already queued event will have the old tracking rect
1334 // tag. The simple solution is to only rebuild our tracking rect if we need to.
1336 if(m_visibleTrackingRectManager != NULL)
1337 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
1339 if(!m_cursor.GetNSCursor())
1342 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1347 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
1349 if(!wxWindowBase::SetCursor(cursor))
1352 // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
1353 // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
1354 // rects and thus won't ever call resetCursorRects.
1355 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1357 // Invalidate the cursor rects so the cursor will change
1358 // Note that it is not enough to remove the old one (if any) and add the new one.
1359 // For the rects to work properly, Cocoa itself must call resetCursorRects.
1360 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
1364 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
1366 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
1367 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
1368 if(m_visibleTrackingRectManager != NULL)
1369 m_visibleTrackingRectManager->BuildTrackingRect();
1373 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
1375 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
1376 // Clear tracking rects. It is imperative this be done before the new window is set.
1377 if(m_visibleTrackingRectManager != NULL)
1378 m_visibleTrackingRectManager->ClearTrackingRect();
1382 bool wxWindow::Close(bool force)
1384 // The only reason this function exists is that it is virtual and
1385 // wxTopLevelWindowCocoa will override it.
1386 return wxWindowBase::Close(force);
1389 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
1391 [[oldView superview] replaceSubview:oldView with:newView];
1394 void wxWindow::DoEnable(bool enable)
1396 CocoaSetEnabled(enable);
1399 bool wxWindow::Show(bool show)
1401 wxAutoNSAutoreleasePool pool;
1402 // If the window is marked as visible, then it shouldn't have a dummy view
1403 // If the window is marked hidden, then it should have a dummy view
1404 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
1405 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
1406 // Return false if there isn't a window to show or hide
1407 NSView *cocoaView = GetNSViewForHiding();
1412 // If state isn't changing, return false
1416 // Replace the stand-in view with the real one and destroy the dummy view
1417 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
1418 wxASSERT(![m_cocoaHider->GetNSView() superview]);
1419 delete m_cocoaHider;
1420 m_cocoaHider = NULL;
1421 wxASSERT([cocoaView superview]);
1423 // Schedule an update of the key view loop (NOTE: 10.4+ only.. argh)
1424 NSWindow *window = [cocoaView window];
1427 // Delay this until returning to the event loop for a couple of reasons:
1428 // 1. If a lot of stuff is shown/hidden we avoid recalculating needlessly
1429 // 2. NSWindow does not seem to see the newly shown views if we do it right now.
1430 if([window respondsToSelector:@selector(recalculateKeyViewLoop)])
1431 [window performSelector:@selector(recalculateKeyViewLoop) withObject:nil afterDelay:0.0];
1436 // If state isn't changing, return false
1440 // Handle the first responder
1441 NSWindow *window = [cocoaView window];
1444 NSResponder *firstResponder = [window firstResponder];
1445 if([firstResponder isKindOfClass:[NSView class]] && [(NSView*)firstResponder isDescendantOf:cocoaView])
1447 BOOL didResign = [window makeFirstResponder:nil];
1448 // If the current first responder (one of our subviews) refuses to give
1449 // up its status, then fail now and don't hide this view
1455 // Create a new view to stand in for the real one (via wxWindowCocoaHider) and replace
1456 // the real one with the stand in.
1457 m_cocoaHider = new wxWindowCocoaHider(this);
1458 // NOTE: replaceSubview:with will cause m_cocaNSView to be
1459 // (auto)released which balances out addSubview
1460 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
1461 // m_coocaNSView is now only retained by us
1462 wxASSERT([m_cocoaHider->GetNSView() superview]);
1463 wxASSERT(![cocoaView superview]);
1469 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
1471 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":".");
1472 int currentX, currentY;
1473 int currentW, currentH;
1474 DoGetPosition(¤tX, ¤tY);
1475 DoGetSize(¤tW, ¤tH);
1476 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1478 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1481 AdjustForParentClientOrigin(x,y,sizeFlags);
1483 wxSize size(wxDefaultSize);
1485 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1487 if(sizeFlags&wxSIZE_AUTO_WIDTH)
1489 size=DoGetBestSize();
1495 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1497 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
1500 size=DoGetBestSize();
1506 DoMoveWindow(x,y,width,height);
1511 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
1513 wxWindowBase::DoSetToolTip(tip);
1517 m_tooltip->SetWindow((wxWindow *)this);
1523 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
1525 wxAutoNSAutoreleasePool pool;
1526 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
1528 NSView *nsview = GetNSViewForSuperview();
1529 NSView *superview = [nsview superview];
1531 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1533 NSRect oldFrameRect = [nsview frame];
1534 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
1535 [nsview setFrame:newFrameRect];
1536 // Be sure to redraw the parent to reflect the changed position
1537 [superview setNeedsDisplayInRect:oldFrameRect];
1538 [superview setNeedsDisplayInRect:newFrameRect];
1541 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1543 NSView *nsview = GetNSViewForSuperview();
1544 NSView *superview = [nsview superview];
1545 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
1546 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1547 NSRect frameRect = [nsview frame];
1549 frameRect.size.width = size.x;
1551 frameRect.size.height = size.y;
1552 frameRect.origin.x = pos.x;
1553 frameRect.origin.y = pos.y;
1554 // Tell Cocoa to change the margin between the bottom of the superview
1555 // and the bottom of the control. Keeps the control pinned to the top
1556 // of its superview so that its position in the wxWidgets coordinate
1557 // system doesn't change.
1558 if(![superview isFlipped])
1559 [nsview setAutoresizingMask: NSViewMinYMargin];
1560 // MUST set the mask before setFrame: which can generate a size event
1561 // and cause a scroller to be added!
1562 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1563 [nsview setFrame: frameRect];
1567 void wxWindow::DoGetSize(int *w, int *h) const
1569 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1571 *w=(int)cocoaRect.size.width;
1573 *h=(int)cocoaRect.size.height;
1574 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1577 void wxWindow::DoGetPosition(int *x, int *y) const
1579 NSView *nsview = GetNSViewForSuperview();
1581 NSRect cocoaRect = [nsview frame];
1582 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1584 *x=(int)rectWx.origin.x;
1586 *y=(int)rectWx.origin.y;
1587 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1590 WXWidget wxWindow::GetHandle() const
1592 return m_cocoaNSView;
1595 wxWindow* wxWindow::GetWxWindow() const
1597 return (wxWindow*) this;
1600 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1602 [m_cocoaNSView setNeedsDisplay:YES];
1605 void wxWindow::SetFocus()
1607 if([GetNSView() acceptsFirstResponder])
1608 [[GetNSView() window] makeFirstResponder: GetNSView()];
1611 void wxWindow::DoCaptureMouse()
1614 sm_capturedWindow = this;
1617 void wxWindow::DoReleaseMouse()
1620 sm_capturedWindow = NULL;
1623 void wxWindow::DoScreenToClient(int *x, int *y) const
1625 // Point in cocoa screen coordinates:
1626 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1627 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1628 NSWindow *theWindow = [clientView window];
1630 // Point in window's base coordinate system:
1631 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1632 // Point in view's bounds coordinate system
1633 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1634 // Point in wx client coordinates:
1635 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1637 *x = theWxClientPoint.x;
1639 *y = theWxClientPoint.y;
1642 void wxWindow::DoClientToScreen(int *x, int *y) const
1644 // Point in wx client coordinates
1645 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1647 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1649 // Point in the view's bounds coordinate system
1650 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1652 // Point in the window's base coordinate system
1653 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1655 NSWindow *theWindow = [clientView window];
1656 // Point in Cocoa's screen coordinates
1657 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1659 // Act as though this was the origin of a 0x0 rectangle
1660 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1662 // Convert that rectangle to wx coordinates
1663 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1665 *x = theWxScreenPoint.x;
1667 *y = theWxScreenPoint.y;
1670 // Get size *available for subwindows* i.e. excluding menu bar etc.
1671 void wxWindow::DoGetClientSize(int *x, int *y) const
1673 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1674 if(m_wxCocoaScrollView)
1675 m_wxCocoaScrollView->DoGetClientSize(x,y);
1677 wxWindowCocoa::DoGetSize(x,y);
1680 void wxWindow::DoSetClientSize(int width, int height)
1682 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1683 if(m_wxCocoaScrollView)
1684 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1685 CocoaSetWxWindowSize(width,height);
1688 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1690 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1693 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1695 // Intentional no-op.
1698 wxString wxWindow::GetLabel() const
1700 // General Get/Set of labels is implemented in wxControlBase
1701 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1702 return wxEmptyString;
1705 int wxWindow::GetCharHeight() const
1711 int wxWindow::GetCharWidth() const
1717 void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
1718 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
1720 // FIXME: This obviously ignores the window's font (if any) along with any size
1721 // transformations. However, it's better than nothing.
1722 // We don't create a wxClientDC because we don't want to accidently be able to use
1724 wxClientDC tmpdc(const_cast<wxWindow*>(this));
1725 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
1728 // Coordinates relative to the window
1729 void wxWindow::WarpPointer (int x_pos, int y_pos)
1734 int wxWindow::GetScrollPos(int orient) const
1736 if(m_wxCocoaScrollView != NULL)
1737 return m_wxCocoaScrollView->GetScrollPos(static_cast<wxOrientation>(orient & wxBOTH));
1742 // This now returns the whole range, not just the number
1743 // of positions that we can scroll.
1744 int wxWindow::GetScrollRange(int orient) const
1746 if(m_wxCocoaScrollView != NULL)
1747 return m_wxCocoaScrollView->GetScrollRange(static_cast<wxOrientation>(orient & wxBOTH));
1752 int wxWindow::GetScrollThumb(int orient) const
1754 if(m_wxCocoaScrollView != NULL)
1755 return m_wxCocoaScrollView->GetScrollThumb(static_cast<wxOrientation>(orient & wxBOTH));
1760 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1762 if(m_wxCocoaScrollView != NULL)
1763 return m_wxCocoaScrollView->SetScrollPos(static_cast<wxOrientation>(orient & wxBOTH), pos);
1766 void wxWindow::CocoaCreateNSScrollView()
1768 if(!m_wxCocoaScrollView)
1770 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1774 // New function that will replace some of the above.
1775 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1776 int range, bool refresh)
1778 CocoaCreateNSScrollView();
1779 m_wxCocoaScrollView->SetScrollbar(orient, pos, thumbVisible, range);
1780 // TODO: Handle refresh (if we even need to)
1783 // Does a physical scroll
1784 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1786 if(m_wxCocoaScrollView != NULL)
1787 m_wxCocoaScrollView->ScrollWindow(dx, dy, rect);
1790 void wxWindow::DoSetVirtualSize( int x, int y )
1792 // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
1793 // possibly not what the caller passed in. For example, the current implementation
1794 // clamps the width and height to within the min/max virtual ranges.
1795 // wxDefaultCoord is passed through unchanged but then GetVirtualSize() will correct
1796 // that by returning effectively max(virtual, client)
1797 wxWindowBase::DoSetVirtualSize(x,y);
1798 // Create the scroll view if it hasn't been already.
1799 CocoaCreateNSScrollView();
1801 // The GetVirtualSize automatically increases the size to max(client,virtual)
1802 m_wxCocoaScrollView->UpdateSizes();
1805 bool wxWindow::SetFont(const wxFont& font)
1807 // FIXME: We may need to handle wx font inheritance.
1808 return wxWindowBase::SetFont(font);
1811 #if 0 // these are used when debugging the algorithm.
1812 static char const * const comparisonresultStrings[] =
1819 class CocoaWindowCompareContext
1821 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1823 CocoaWindowCompareContext(); // Not implemented
1824 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1827 // Cocoa sorts subviews in-place.. make a copy
1828 m_subviews = [subviews copy];
1830 ~CocoaWindowCompareContext()
1831 { // release the copy
1832 [m_subviews release];
1835 { return m_target; }
1837 { return m_subviews; }
1838 /* Helper function that returns the comparison based off of the original ordering */
1839 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1841 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1842 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1843 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1844 // likely compare higher than the other view which is reasonable considering the only way that
1845 // can happen is if the subview was added after our call to subviews but before the call to
1846 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1847 // that case should never occur anyway because that would imply a multi-threaded GUI call
1848 // which is a big no-no with Cocoa.
1850 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1851 NSComparisonResult result = (firstI < secondI)
1852 ? NSOrderedAscending /* -1 */
1853 : (firstI > secondI)
1854 ? NSOrderedDescending /* 1 */
1855 : NSOrderedSame /* 0 */;
1857 #if 0 // Enable this if you need to debug the algorithm.
1858 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1863 /* The subview we are trying to Raise or Lower */
1865 /* A copy of the original array of subviews */
1866 NSArray *m_subviews;
1869 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1870 * the target view is always higher than every other view. When comparing two views neither of
1871 * which is the target, it returns the correct response based on the original ordering
1873 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1875 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1876 // first should be ordered higher
1877 if(first==compareContext->target())
1878 return NSOrderedDescending;
1879 // second should be ordered higher
1880 if(second==compareContext->target())
1881 return NSOrderedAscending;
1882 return compareContext->CompareUsingOriginalOrdering(first,second);
1885 // Raise the window to the top of the Z order
1886 void wxWindow::Raise()
1888 // wxAutoNSAutoreleasePool pool;
1889 NSView *nsview = GetNSViewForSuperview();
1890 NSView *superview = [nsview superview];
1891 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1893 [superview sortSubviewsUsingFunction:
1894 CocoaRaiseWindowCompareFunction
1895 context: &compareContext];
1898 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1899 * the target view is always lower than every other view. When comparing two views neither of
1900 * which is the target, it returns the correct response based on the original ordering
1902 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1904 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1905 // first should be ordered lower
1906 if(first==compareContext->target())
1907 return NSOrderedAscending;
1908 // second should be ordered lower
1909 if(second==compareContext->target())
1910 return NSOrderedDescending;
1911 return compareContext->CompareUsingOriginalOrdering(first,second);
1914 // Lower the window to the bottom of the Z order
1915 void wxWindow::Lower()
1917 NSView *nsview = GetNSViewForSuperview();
1918 NSView *superview = [nsview superview];
1919 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1922 NSLog(@"Target:\n%@\n", nsview);
1923 NSLog(@"Before:\n%@\n", compareContext.subviews());
1925 [superview sortSubviewsUsingFunction:
1926 CocoaLowerWindowCompareFunction
1927 context: &compareContext];
1929 NSLog(@"After:\n%@\n", [superview subviews]);
1933 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1938 // Get the window with the focus
1939 wxWindow *wxWindowBase::DoFindFocus()
1941 // Basically we are somewhat emulating the responder chain here except
1942 // we are only loking for the first responder in the key window or
1943 // upon failing to find one if the main window is different we look
1944 // for the first responder in the main window.
1946 // Note that the firstResponder doesn't necessarily have to be an
1947 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
1948 // NULL unless it finds its argument in its hash map.
1952 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
1953 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
1955 return win->GetWxWindow();
1957 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
1958 if(mainWindow == keyWindow)
1960 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
1962 return win->GetWxWindow();
1967 /* static */ wxWindow *wxWindowBase::GetCapture()
1970 return wxWindowCocoa::sm_capturedWindow;
1973 wxWindow *wxGetActiveWindow()
1979 wxPoint wxGetMousePosition()
1982 return wxDefaultPosition;
1985 wxMouseState wxGetMouseState()
1992 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
1994 pt = wxGetMousePosition();
1998 // ========================================================================
1999 // wxCocoaMouseMovedEventSynthesizer
2000 // ========================================================================
2002 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
2004 /* This class registers one run loop observer to cover all windows registered with it.
2005 * It will register the observer when the first view is registerd and unregister the
2006 * observer when the last view is unregistered.
2007 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
2008 * is no reason it couldn't be instantiated multiple times.
2010 class wxCocoaMouseMovedEventSynthesizer
2012 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
2014 wxCocoaMouseMovedEventSynthesizer()
2015 { m_lastScreenMouseLocation = NSZeroPoint;
2017 ~wxCocoaMouseMovedEventSynthesizer();
2018 void RegisterWxCocoaView(wxCocoaNSView *aView);
2019 void UnregisterWxCocoaView(wxCocoaNSView *aView);
2020 void SynthesizeMouseMovedEvent();
2023 void AddRunLoopObserver();
2024 void RemoveRunLoopObserver();
2025 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
2026 std::list<wxCocoaNSView*> m_registeredViews;
2027 NSPoint m_lastScreenMouseLocation;
2028 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
2031 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
2033 m_registeredViews.push_back(aView);
2034 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
2036 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
2038 AddRunLoopObserver();
2042 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
2044 m_registeredViews.remove(aView);
2045 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
2046 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
2048 RemoveRunLoopObserver();
2052 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
2054 if(!m_registeredViews.empty())
2056 // This means failure to clean up so we report on it as a debug message.
2057 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
2058 m_registeredViews.clear();
2060 if(m_runLoopObserver != NULL)
2062 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
2063 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
2064 RemoveRunLoopObserver();
2068 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
2070 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
2073 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
2075 CFRunLoopObserverContext observerContext =
2083 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
2084 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
2086 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
2087 // in our observer procedure being called before the run loop has decided that it is going to return control to
2088 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
2089 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
2091 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
2092 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
2093 // around to it before letting the event loop do its work. This has some very odd effects on the way the
2094 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
2095 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
2096 // time the new one is built.
2098 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
2099 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
2100 // the mouseExited: message to be sent properly.
2102 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
2103 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2104 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
2107 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
2109 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2110 m_runLoopObserver.reset();
2111 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
2114 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
2116 NSPoint screenMouseLocation = [NSEvent mouseLocation];
2117 // Checking the last mouse location is done for a few reasons:
2118 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
2119 // telling the app the mouse moved when the user hit a key for instance.
2120 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
2121 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
2122 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
2123 // loop causing the tracking rectangles to constantly be reset.
2124 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
2126 m_lastScreenMouseLocation = screenMouseLocation;
2127 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
2128 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
2130 (*i)->Cocoa_synthesizeMouseMoved();
2135 // Singleton used for all views:
2136 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
2138 // ========================================================================
2139 // wxCocoaTrackingRectManager
2140 // ========================================================================
2142 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
2145 m_isTrackingRectActive = false;
2146 BuildTrackingRect();
2149 void wxCocoaTrackingRectManager::ClearTrackingRect()
2151 if(m_isTrackingRectActive)
2153 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
2154 m_isTrackingRectActive = false;
2155 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2157 // If we were doing periodic events we need to clear those too
2158 StopSynthesizingEvents();
2161 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
2163 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
2166 void wxCocoaTrackingRectManager::BuildTrackingRect()
2168 // Pool here due to lack of one during wx init phase
2169 wxAutoNSAutoreleasePool pool;
2171 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
2173 NSView *theView = m_window->GetNSView();
2175 if([theView window] != nil)
2177 NSRect visibleRect = [theView visibleRect];
2179 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
2180 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
2181 m_isTrackingRectActive = true;
2183 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2187 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
2189 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
2192 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
2194 if(m_isTrackingRectActive)
2196 NSView *theView = m_window->GetNSView();
2197 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
2198 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
2200 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
2204 RebuildTrackingRect();
2207 void wxCocoaTrackingRectManager::RebuildTrackingRect()
2209 ClearTrackingRect();
2210 BuildTrackingRect();
2213 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
2215 ClearTrackingRect();
2218 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
2220 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);