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"
21 #include "wx/tooltip.h"
23 #include "wx/cocoa/autorelease.h"
24 #include "wx/cocoa/string.h"
25 #include "wx/cocoa/trackingrectmanager.h"
26 #include "wx/mac/corefoundation/cfref.h"
28 #import <Foundation/NSArray.h>
29 #import <Foundation/NSRunLoop.h>
30 #include "wx/cocoa/objc/NSView.h"
31 #import <AppKit/NSEvent.h>
32 #import <AppKit/NSScrollView.h>
33 #import <AppKit/NSScroller.h>
34 #import <AppKit/NSColor.h>
35 #import <AppKit/NSClipView.h>
36 #import <Foundation/NSException.h>
37 #import <AppKit/NSApplication.h>
38 #import <AppKit/NSWindow.h>
39 #import <AppKit/NSScreen.h>
41 // Turn this on to paint green over the dummy views for debugging
42 #undef WXCOCOA_FILL_DUMMY_VIEW
44 #ifdef WXCOCOA_FILL_DUMMY_VIEW
45 #import <AppKit/NSBezierPath.h>
46 #endif //def WXCOCOA_FILL_DUMMY_VIEW
48 // STL list used by wxCocoaMouseMovedEventSynthesizer
51 /* NSComparisonResult is typedef'd as an enum pre-Leopard but typedef'd as
52 * NSInteger post-Leopard. Pre-Leopard the Cocoa toolkit expects a function
53 * returning int and not NSComparisonResult. Post-Leopard the Cocoa toolkit
54 * expects a function returning the new non-enum NSComparsionResult.
55 * Hence we create a typedef named CocoaWindowCompareFunctionResult.
57 #if defined(NSINTEGER_DEFINED)
58 typedef NSComparisonResult CocoaWindowCompareFunctionResult;
60 typedef int CocoaWindowCompareFunctionResult;
63 // A category for methods that are only present in Panther's SDK
64 @interface NSView(wxNSViewPrePantherCompatibility)
65 - (void)getRectsBeingDrawn:(const NSRect **)rects count:(int *)count;
68 // ========================================================================
69 // Helper functions for converting to/from wxWidgets coordinates and a
70 // specified NSView's coordinate system.
71 // ========================================================================
72 NSPoint CocoaTransformNSViewBoundsToWx(NSView *nsview, NSPoint pointBounds)
74 wxCHECK_MSG(nsview, pointBounds, wxT("Need to have a Cocoa view to do translation"));
75 if([nsview isFlipped])
77 NSRect ourBounds = [nsview bounds];
80 , ourBounds.size.height - pointBounds.y
84 NSRect CocoaTransformNSViewBoundsToWx(NSView *nsview, NSRect rectBounds)
86 wxCHECK_MSG(nsview, rectBounds, wxT("Need to have a Cocoa view to do translation"));
87 if([nsview isFlipped])
89 NSRect ourBounds = [nsview bounds];
92 , ourBounds.size.height - (rectBounds.origin.y + rectBounds.size.height)
93 , rectBounds.size.width
94 , rectBounds.size.height
98 NSPoint CocoaTransformNSViewWxToBounds(NSView *nsview, NSPoint pointWx)
100 wxCHECK_MSG(nsview, pointWx, wxT("Need to have a Cocoa view to do translation"));
101 if([nsview isFlipped])
103 NSRect ourBounds = [nsview bounds];
106 , ourBounds.size.height - pointWx.y
110 NSRect CocoaTransformNSViewWxToBounds(NSView *nsview, NSRect rectWx)
112 wxCHECK_MSG(nsview, rectWx, wxT("Need to have a Cocoa view to do translation"));
113 if([nsview isFlipped])
115 NSRect ourBounds = [nsview bounds];
118 , ourBounds.size.height - (rectWx.origin.y + rectWx.size.height)
124 // ============================================================================
125 // Screen coordinate helpers
126 // ============================================================================
129 General observation about Cocoa screen coordinates:
130 It is documented that the first object of the [NSScreen screens] array is the screen with the menubar.
132 It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always
133 the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up.
135 It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle
136 relative to that screen's frame. The only real way to test this is to configure two screens one atop
137 the other such that the menubar screen is on top. The Dock at the bottom of the screen will then
138 eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two
139 1920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and
140 visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin.
142 In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen).
143 However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme.
144 This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen.
147 namespace { // file namespace
149 class wxCocoaPrivateScreenCoordinateTransformer
151 DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer)
153 wxCocoaPrivateScreenCoordinateTransformer();
154 ~wxCocoaPrivateScreenCoordinateTransformer();
155 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
156 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
159 NSScreen *m_screenZero;
160 NSRect m_screenZeroFrame;
163 // NOTE: This is intended to be a short-lived object. A future enhancment might
164 // make it a global and reconfigure it upon some notification that the screen layout
166 inline wxCocoaPrivateScreenCoordinateTransformer::wxCocoaPrivateScreenCoordinateTransformer()
168 NSArray *screens = [NSScreen screens];
173 if(screens != nil && [screens count] > 0)
174 m_screenZero = [[screens objectAtIndex:0] retain];
178 if(m_screenZero != nil)
179 m_screenZeroFrame = [m_screenZero frame];
182 wxLogWarning(wxT("Can't translate to/from wx screen coordinates and Cocoa screen coordinates"));
183 // Just blindly assume 1024x768 so that at least we can sort of flip things around into
184 // Cocoa coordinates.
185 // NOTE: Theoretically this case should never happen anyway.
186 m_screenZeroFrame = NSMakeRect(0,0,1024,768);
190 inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
192 [m_screenZero release];
196 inline wxPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
198 // x and y are in wx screen coordinates which we're going to arbitrarily define such that
199 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
200 // NOTE WELL: This means that (0,0) is _NOT_ an appropriate position for a window.
204 // Working in Cocoa's screen coordinates we must realize that the x coordinate we want is
205 // the distance between the left side (origin.x) of the window's frame and the left side of
206 // screen zero's frame.
207 theWxOrigin.x = windowFrame.origin.x - m_screenZeroFrame.origin.x;
209 // Working in Cocoa's screen coordinates we must realize that the y coordinate we want is
210 // actually the distance between the top-left of the screen zero frame and the top-left
211 // of the window's frame.
213 theWxOrigin.y = (m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height) - (windowFrame.origin.y + windowFrame.size.height);
218 inline NSPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
220 NSPoint theCocoaOrigin;
222 // The position is in wx screen coordinates which we're going to arbitrarily define such that
223 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
225 // NOTE: The usable rectangle is smaller and hence we have the keepOriginVisible flag
226 // which will move the origin downward and/or left as necessary if the origin is
227 // inside the screen0 rectangle (i.e. x/y >= 0 in wx coordinates) and outside the
228 // visible frame (i.e. x/y < the top/left of the screen0 visible frame in wx coordinates)
229 // We don't munge origin coordinates < 0 because it actually is possible that the menubar is on
230 // the top of the bottom screen and thus that origin is completely valid!
231 if(keepOriginVisible && (m_screenZero != nil))
233 // Do al of this in wx coordinates because it's far simpler since we're dealing with top/left points
234 wxPoint visibleOrigin = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates([m_screenZero visibleFrame]);
235 if(x >= 0 && x < visibleOrigin.x)
237 if(y >= 0 && y < visibleOrigin.y)
241 // The x coordinate is simple as it's just relative to screen zero's frame
242 theCocoaOrigin.x = m_screenZeroFrame.origin.x + x;
243 // Working in Cocoa's coordinates think to start at the bottom of screen zero's frame and add
244 // the height of that rect which gives us the coordinate for the top of the visible rect. Now realize that
245 // the wx coordinates are flipped so if y is say 10 then we want to be 10 pixels down from that and thus
246 // we subtract y. But then we still need to take into account the size of the window which is h and subtract
247 // that to get the bottom-left origin of the rectangle.
248 theCocoaOrigin.y = m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height - y - height;
250 return theCocoaOrigin;
255 wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
257 wxCocoaPrivateScreenCoordinateTransformer transformer;
258 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
261 NSPoint wxWindowCocoa::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
263 wxCocoaPrivateScreenCoordinateTransformer transformer;
264 return transformer.OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,keepOriginVisible);
267 // ========================================================================
268 // wxWindowCocoaHider
269 // ========================================================================
270 class wxWindowCocoaHider: protected wxCocoaNSView
272 DECLARE_NO_COPY_CLASS(wxWindowCocoaHider)
274 wxWindowCocoaHider(wxWindow *owner);
275 virtual ~wxWindowCocoaHider();
276 inline WX_NSView GetNSView() { return m_dummyNSView; }
278 wxWindowCocoa *m_owner;
279 WX_NSView m_dummyNSView;
280 virtual void Cocoa_FrameChanged(void);
281 virtual void Cocoa_synthesizeMouseMoved(void) {}
282 #ifdef WXCOCOA_FILL_DUMMY_VIEW
283 virtual bool Cocoa_drawRect(const NSRect& rect);
284 #endif //def WXCOCOA_FILL_DUMMY_VIEW
286 wxWindowCocoaHider();
289 // ========================================================================
290 // wxWindowCocoaScrollView
291 // ========================================================================
292 class wxWindowCocoaScrollView: protected wxCocoaNSView
294 DECLARE_NO_COPY_CLASS(wxWindowCocoaScrollView)
296 wxWindowCocoaScrollView(wxWindow *owner);
297 virtual ~wxWindowCocoaScrollView();
298 inline WX_NSScrollView GetNSScrollView() { return m_cocoaNSScrollView; }
299 void ClientSizeToSize(int &width, int &height);
300 void DoGetClientSize(int *x, int *y) const;
302 void Unencapsulate();
304 // wxWindow calls this to do the work. Note that we don't have the refresh parameter
305 // because wxWindow handles that itself.
306 void SetScrollbar(int orientation, int position, int thumbSize, int range);
307 int GetScrollPos(wxOrientation orient);
308 void SetScrollPos(wxOrientation orient, int position);
309 int GetScrollRange(wxOrientation orient);
310 int GetScrollThumb(wxOrientation orient);
311 void ScrollWindow(int dx, int dy, const wxRect*);
314 void _wx_doScroller(NSScroller *sender);
317 wxWindowCocoa *m_owner;
318 WX_NSScrollView m_cocoaNSScrollView;
319 virtual void Cocoa_FrameChanged(void);
320 virtual void Cocoa_synthesizeMouseMoved(void) {}
322 Flag as to whether we're scrolling for a native view or a custom
323 wxWindow. This controls the scrolling behavior. When providing
324 scrolling for a native view we don't catch scroller action messages
325 and thus don't send scroll events and we don't actually scroll the
326 window when the application calls ScrollWindow.
328 When providing scrolling for a custom wxWindow, we make the NSScroller
329 send their action messages to us which we in turn package as wx window
330 scrolling events. At this point, the window will not physically be
331 scrolled. The application will most likely handle the event by calling
332 ScrollWindow which will do the real scrolling. On the other hand,
333 the application may instead not call ScrollWindow until some threshold
334 is reached. This causes the window to only scroll in steps which is
335 what, for instance, wxScrolledWindow does.
339 The range as the application code wishes to see it. That is, the
340 range from the last SetScrollbar call for the appropriate dimension.
341 The horizontal dimension is the first [0] element and the vertical
342 dimension the second [1] element.
344 In wxMSW, a SCROLLINFO with nMin=0 and nMax=range-1 is used which
345 gives exactly range possible positions so long as nPage (which is
346 the thumb size) is less than or equal to 1.
348 int m_scrollRange[2];
350 The thumb size is intended to reflect the size of the visible portion
351 of the scrolled document. As the document size increases, the thumb
352 visible thumb size decreases. As document size decreases, the visible
353 thumb size increases. However, the thumb size on wx is defined in
354 terms of scroll units (which are effectively defined by the scroll
355 range) and so increasing the number of scroll units to reflect increased
356 document size will have the effect of decreasing the visible thumb
357 size even though the number doesn't change.
359 It's also important to note that subtracting the thumb size from the
360 full range gives you the real range that can be used. Microsoft
361 defines nPos (the current scrolling position) to be within the range
362 from nMin to nMax - max(nPage - 1, 0). We know that wxMSW code always
363 sets nMin = 0 and nMax = range -1. So let's algebraically reduce the
364 definition of the maximum allowed position:
367 = nMax - max(nPage - 1, 0)
368 Substitute (range - 1) for nMax and thumbSize for nPage:
369 = range - 1 - max(thumbSize - 1, 0)
370 Add one inside the max conditional and subtract one outside of it:
371 = range - 1 - (max(thumbSize - 1 + 1, 1) - 1)
372 Reduce some constants:
373 = range - 1 - (max(thumbSize, 1) - 1)
374 Distribute the negative across the parenthesis:
375 = range - 1 - max(thumbSize, 1) + 1
376 Reduce the constants:
377 = range - max(thumbSize, 1)
379 Also keep in mind that thumbSize may never be greater than range but
380 can be equal to it. Thus for the smallest possible thumbSize there
381 are exactly range possible scroll positions (numbered from 0 to
382 range - 1) and for the largest possible thumbSize there is exactly
383 one possible scroll position (numbered 0).
385 int m_scrollThumb[2];
388 The origin of the virtual coordinate space expressed in terms of client
389 coordinates. Starts at (0,0) and each call to ScrollWindow accumulates
390 into it. Thus if the user scrolls the window right (thus causing the
391 contents to move left with respect to the client origin, the
392 application code (typically wxScrolledWindow) will be called with
393 dx of -something, for example -20. This is added to m_virtualOrigin
394 and thus m_virtualOrigin will be (-20,0) in this example.
396 wxPoint m_virtualOrigin;
398 wxWindowCocoaScrollView();
401 // ========================================================================
403 // ========================================================================
404 @interface wxDummyNSView : NSView
405 - (NSView *)hitTest:(NSPoint)aPoint;
407 WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
409 @implementation wxDummyNSView : NSView
410 - (NSView *)hitTest:(NSPoint)aPoint
416 WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
418 // ========================================================================
419 // wxWindowCocoaHider
420 // ========================================================================
421 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
425 wxASSERT(owner->GetNSViewForHiding());
426 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
427 initWithFrame:[owner->GetNSViewForHiding() frame]];
428 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
429 AssociateNSView(m_dummyNSView);
432 wxWindowCocoaHider::~wxWindowCocoaHider()
434 DisassociateNSView(m_dummyNSView);
435 [m_dummyNSView release];
438 void wxWindowCocoaHider::Cocoa_FrameChanged(void)
440 // Keep the real window in synch with the dummy
441 wxASSERT(m_dummyNSView);
442 [m_owner->GetNSViewForHiding() setFrame:[m_dummyNSView frame]];
446 #ifdef WXCOCOA_FILL_DUMMY_VIEW
447 bool wxWindowCocoaHider::Cocoa_drawRect(const NSRect& rect)
449 NSBezierPath *bezpath = [NSBezierPath bezierPathWithRect:rect];
450 [[NSColor greenColor] set];
455 #endif //def WXCOCOA_FILL_DUMMY_VIEW
458 /*! @class WXManualScrollView
459 @abstract An NSScrollView subclass which implements wx scroll behavior
461 Overrides default behavior of NSScrollView such that this class receives
462 the scroller action messages and allows the wxCocoaScrollView to act
463 on them accordingly. In particular, because the NSScrollView will not
464 receive action messages from the scroller, it will not adjust the
465 document view. This must be done manually using the ScrollWindow
468 @interface WXManualScrollView : NSScrollView
470 /*! @field m_wxCocoaScrollView
472 wxWindowCocoaScrollView *m_wxCocoaScrollView;
475 // Override these to set up the target/action correctly
476 - (void)setHorizontalScroller:(NSScroller *)aScroller;
477 - (void)setVerticalScroller:(NSScroller *)aScroller;
478 - (void)setHasHorizontalScroller:(BOOL)flag;
479 - (void)setHasVerticalScroller:(BOOL)flag;
481 // NOTE: _wx_ prefix means "private" method like _ that Apple (and only Apple) uses.
482 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView;
483 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView;
485 /*! @method _wx_doScroller
486 @abstract Handles action messages from the scrollers
488 Similar to Apple's _doScroller: method which is private and not documented.
489 We do not, however, call that method. Instead, we effectively override
490 it entirely. We don't override it by naming ourself the same thing because
491 the base class code may or may not call that method for other reasons we
492 simply cannot know about.
494 - (void)_wx_doScroller:(id)sender;
499 @implementation WXManualScrollView : NSScrollView
501 static inline void WXManualScrollView_DoSetScrollerTargetAction(WXManualScrollView *self, NSScroller *aScroller)
503 if(aScroller != NULL && [self _wx_wxCocoaScrollView] != NULL)
505 [aScroller setTarget:self];
506 [aScroller setAction:@selector(_wx_doScroller:)];
510 - (void)setHorizontalScroller:(NSScroller *)aScroller
512 [super setHorizontalScroller:aScroller];
513 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
516 - (void)setVerticalScroller:(NSScroller *)aScroller
518 [super setVerticalScroller:aScroller];
519 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
522 - (void)setHasHorizontalScroller:(BOOL)flag
524 [super setHasHorizontalScroller:flag];
525 WXManualScrollView_DoSetScrollerTargetAction(self, [self horizontalScroller]);
528 - (void)setHasVerticalScroller:(BOOL)flag
530 [super setHasVerticalScroller:flag];
531 WXManualScrollView_DoSetScrollerTargetAction(self, [self verticalScroller]);
534 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView
535 { return m_wxCocoaScrollView; }
537 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView
539 m_wxCocoaScrollView = theWxScrollView;
540 [self setHorizontalScroller:[self horizontalScroller]];
541 [self setVerticalScroller:[self verticalScroller]];
544 - (void)_wx_doScroller:(id)sender
546 if(m_wxCocoaScrollView != NULL)
547 m_wxCocoaScrollView->_wx_doScroller(sender);
549 wxLogError(wxT("Unexpected action message received from NSScroller"));
552 - (void)reflectScrolledClipView:(NSClipView *)aClipView
554 struct _ScrollerBackup
556 _ScrollerBackup(NSScroller *aScroller)
557 : m_scroller(aScroller)
558 , m_floatValue(aScroller!=nil?[aScroller floatValue]:0.0)
559 , m_knobProportion(aScroller!=nil?[aScroller knobProportion]:1.0)
560 , m_isEnabled(aScroller!=nil?[aScroller isEnabled]:false)
563 NSScroller *m_scroller;
564 CGFloat m_floatValue;
565 CGFloat m_knobProportion;
569 if(m_scroller != nil)
571 [m_scroller setFloatValue:m_floatValue knobProportion:m_knobProportion];
572 [m_scroller setEnabled:m_isEnabled];
577 _ScrollerBackup(_ScrollerBackup const&);
578 _ScrollerBackup& operator=(_ScrollerBackup const&);
580 _ScrollerBackup _horizontalBackup([self horizontalScroller]);
581 _ScrollerBackup _verticalBackup([self verticalScroller]);
582 // We MUST call super's implementation or else nothing seems to work right at all.
583 // However, we need our scrollers not to change values due to the document window
584 // moving so we cheat and save/restore their values across this call.
585 [super reflectScrolledClipView: aClipView];
589 WX_IMPLEMENT_GET_OBJC_CLASS(WXManualScrollView,NSScrollView)
591 // ========================================================================
592 // wxFlippedNSClipView
593 // ========================================================================
594 @interface wxFlippedNSClipView : NSClipView
597 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
599 @implementation wxFlippedNSClipView : NSClipView
606 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
608 // ========================================================================
609 // wxWindowCocoaScrollView
610 // ========================================================================
611 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
613 , m_cocoaNSScrollView() // nil
614 , m_scrollRange() // {0,0}
615 , m_scrollThumb() // {0,0}
616 , m_virtualOrigin(0,0)
618 wxAutoNSAutoreleasePool pool;
620 wxASSERT(owner->GetNSView());
621 m_isNativeView = ![owner->GetNSView() isKindOfClass:[WX_GET_OBJC_CLASS(WXNSView) class]];
622 m_cocoaNSScrollView = [(m_isNativeView?[NSScrollView alloc]:[WXManualScrollView alloc])
623 initWithFrame:[owner->GetNSView() frame]];
624 AssociateNSView(m_cocoaNSScrollView);
627 /* Set a bezel border around the entire thing because it looks funny without it.
628 TODO: Be sure to undo any borders on the real view (if any) and apply them
629 to this view if necessary. Right now, there is no border support in wxCocoa
630 so this isn't an issue.
632 [m_cocoaNSScrollView setBorderType:NSBezelBorder];
636 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView: this];
637 // Don't set a bezel because we might be creating a scroll view due to being
638 // the "target window" of a wxScrolledWindow. That is to say that the user
639 // has absolutely no intention of scrolling the clip view used by this
643 /* Replace the default NSClipView with a flipped one. This ensures
644 scrolling is "pinned" to the top-left instead of bottom-right. */
645 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
646 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
647 [m_cocoaNSScrollView setContentView:flippedClip];
648 [flippedClip release];
650 // In all cases we must encapsulate the real NSView properly
654 void wxWindowCocoaScrollView::Encapsulate()
656 // Set the scroll view autoresizingMask to match the current NSView
657 [m_cocoaNSScrollView setAutoresizingMask: [m_owner->GetNSView() autoresizingMask]];
658 [m_owner->GetNSView() setAutoresizingMask: NSViewNotSizable];
659 // NOTE: replaceSubView will cause m_cocaNSView to be released
660 // except when it hasn't been added into an NSView hierarchy in which
661 // case it doesn't need to be and this should work out to a no-op
662 m_owner->CocoaReplaceView(m_owner->GetNSView(), m_cocoaNSScrollView);
663 // The NSView is still retained by owner
664 [m_cocoaNSScrollView setDocumentView: m_owner->GetNSView()];
665 // Now it's also retained by the NSScrollView
668 void wxWindowCocoaScrollView::Unencapsulate()
670 [m_cocoaNSScrollView setDocumentView: nil];
671 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
672 if(![[m_owner->GetNSView() superview] isFlipped])
673 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
676 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
678 DisassociateNSView(m_cocoaNSScrollView);
681 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView:NULL];
683 [m_cocoaNSScrollView release];
686 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
688 NSSize frameSize = [NSScrollView
689 frameSizeForContentSize: NSMakeSize(width,height)
690 hasHorizontalScroller: [m_cocoaNSScrollView hasHorizontalScroller]
691 hasVerticalScroller: [m_cocoaNSScrollView hasVerticalScroller]
692 borderType: [m_cocoaNSScrollView borderType]];
693 width = (int)frameSize.width;
694 height = (int)frameSize.height;
697 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
699 NSSize nssize = [m_cocoaNSScrollView contentSize];
701 *x = (int)nssize.width;
703 *y = (int)nssize.height;
706 static inline void SetCocoaScroller(NSScroller *aScroller, int WXUNUSED(orientation), int position, int thumbSize, int range)
708 wxCHECK_RET(aScroller != nil, wxT("Expected the NSScrollView to have a scroller"));
710 // NOTE: thumbSize is already ensured to be >= 1 and <= range by our caller
711 // unless range = 0 in which case we shouldn't have been be called.
712 wxCHECK_RET(range > 0, wxT("Internal wxCocoa bug: shouldn't have been called with 0 range"));
714 // Range of valid position values is from 0 to effectiveRange
715 // NOTE: if thumbSize == range then effectiveRange is 0.
716 // thumbSize is at least 1 which gives range from 0 to range - 1 inclusive
717 // which is exactly what we want.
718 int effectiveRange = range - thumbSize;
720 // knobProportion is hopefully easy to understand
721 // Note that thumbSize is already guaranteed >= 1 by our caller.
722 CGFloat const knobProportion = CGFloat(thumbSize)/CGFloat(range);
724 // NOTE: When effectiveRange is zero there really is no valid position
725 // We arbitrarily pick 0.0 which is the same as a scroller in the home position.
726 CGFloat const floatValue = (effectiveRange != 0)?CGFloat(position)/CGFloat(effectiveRange):0.0;
728 [aScroller setFloatValue:floatValue knobProportion: knobProportion];
729 // Make sure it's visibly working
730 [aScroller setEnabled:YES];
733 void wxWindowCocoaScrollView::SetScrollPos(wxOrientation orientation, int position)
735 // NOTE: Rather than using only setFloatValue: (which we could do) we instead
736 // simply share the SetCocoaScroller call because all but the knobProportion
737 // calculations have to be done anyway.
738 if(orientation & wxHORIZONTAL)
740 NSScroller *aScroller = [m_cocoaNSScrollView horizontalScroller];
742 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[0], m_scrollRange[0]);
744 if(orientation & wxVERTICAL)
746 NSScroller *aScroller = [m_cocoaNSScrollView verticalScroller];
748 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[1], m_scrollRange[1]);
752 void wxWindowCocoaScrollView::SetScrollbar(int orientation, int position, int thumbSize, int range)
754 // FIXME: API assumptions:
755 // 1. If the user wants to remove a scroller he gives range 0.
756 // 2. If the user wants to disable a scroller he sets thumbSize == range
757 // in which case it is logically impossible to scroll.
758 // The scroller shall still be displayed.
760 // Ensure that range is >= 0.
761 wxASSERT(range >= 0);
765 // Ensure that thumbSize <= range
766 wxASSERT(thumbSize <= range);
767 // Also ensure thumbSize >= 1 but don't complain if it isn't
770 // Now make sure it's really less than range, even if we just set it to 1
771 if(thumbSize > range)
774 bool needScroller = (range != 0);
776 // Can potentially set both horizontal and vertical at the same time although this is
777 // probably not very useful.
778 if(orientation & wxHORIZONTAL)
780 m_scrollRange[0] = range;
781 m_scrollThumb[0] = thumbSize;
784 [m_cocoaNSScrollView setHasHorizontalScroller:needScroller];
786 SetCocoaScroller([m_cocoaNSScrollView horizontalScroller], orientation, position, thumbSize, range);
790 if(orientation & wxVERTICAL)
792 m_scrollRange[1] = range;
793 m_scrollThumb[1] = thumbSize;
796 [m_cocoaNSScrollView setHasVerticalScroller:needScroller];
798 SetCocoaScroller([m_cocoaNSScrollView verticalScroller], orientation, position, thumbSize, range);
803 int wxWindowCocoaScrollView::GetScrollPos(wxOrientation orient)
805 if((orient & wxBOTH) == wxBOTH)
807 wxLogError(wxT("GetScrollPos called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
810 int effectiveScrollRange;
811 NSScroller *cocoaScroller;
812 if(orient & wxHORIZONTAL)
814 effectiveScrollRange = m_scrollRange[0] - m_scrollThumb[0];
815 cocoaScroller = [m_cocoaNSScrollView horizontalScroller];
817 else if(orient & wxVERTICAL)
819 effectiveScrollRange = m_scrollRange[1] - m_scrollThumb[1];
820 cocoaScroller = [m_cocoaNSScrollView verticalScroller];
824 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
827 if(cocoaScroller == nil)
828 { // Document is not scrolled
832 The effective range of a scroll bar as defined by wxWidgets is from 0 to (range - thumbSize).
833 That is a scroller at the left/top position is at 0 and a scroller at the bottom/right
834 position is at range-thumbsize.
836 The range of an NSScroller is 0.0 to 1.0. Much easier! NOTE: Apple doesn't really specify
837 but GNUStep docs do say that 0.0 is top/left and 1.0 is bottom/right. This is actualy
838 in contrast to NSSlider which generally has 1.0 at the TOP when it's done vertically.
840 CGFloat cocoaScrollPos = [cocoaScroller floatValue];
841 return effectiveScrollRange * cocoaScrollPos;
844 int wxWindowCocoaScrollView::GetScrollRange(wxOrientation orient)
846 if((orient & wxBOTH) == wxBOTH)
848 wxLogError(wxT("GetScrollRange called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
851 if(orient & wxHORIZONTAL)
853 return m_scrollRange[0];
855 else if(orient & wxVERTICAL)
857 return m_scrollRange[1];
861 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
866 int wxWindowCocoaScrollView::GetScrollThumb(wxOrientation orient)
868 if((orient & wxBOTH) == wxBOTH)
870 wxLogError(wxT("GetScrollThumb called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
873 if(orient & wxHORIZONTAL)
875 return m_scrollThumb[0];
877 else if(orient & wxVERTICAL)
879 return m_scrollThumb[1];
883 wxLogError(wxT("GetScrollThumb called without an orientation which makes no sense"));
889 Moves the contents (all existing drawing as well as all all child wxWindow) by the specified
890 amount expressed in the wxWindow's own coordinate system. This is used to implement scrolling
891 but the usage is rather interesting. When scrolling right (e.g. increasing the value of
892 the scroller) you must give a negative delta x (e.g. moving the contents LEFT). Likewise,
893 when scrolling the window down, increasing the value of the scroller, you give a negative
894 delta y which moves the contents up.
896 wxCocoa notes: To accomplish this trick in Cocoa we basically do what NSScrollView would
897 have done and that is adjust the content view's bounds origin. The content view is somewhat
898 confusingly the NSClipView which is more or less sort of the pImpl for NSScrollView
899 The real NSView with the user's content (e.g. the "virtual area" in wxWidgets parlance)
900 is called the document view in NSScrollView parlance.
902 The bounds origin is basically the exact opposite concept. Whereas in Windows the client
903 coordinate system remains constant and the content must shift left/up for increases
904 of scrolling, in Cocoa the coordinate system is actually the virtual one. So we must
905 instead shift the bounds rectangle right/down to get the effect of the content moving
906 left/up. Basically, it's a higher level interface than that provided by wxWidgets
907 so essentially we're implementing the low-level move content interface in terms of
908 the high-level move the viewport (the bounds) over top that content (the document
909 view which is the virtual area to wx).
911 For all intents and purposes that basically just means that we subtract the deltas
912 from the bounds origin and thus a negative delta actually increases the bounds origin
913 and a positive delta actually decreases it. This is absolutely true for the horizontal
914 axis but there's a catch in the vertical axis. If the content view (the clip view) is
915 flipped (and we do this by default) then it works exactly like the horizontal axis.
916 If it is not flipped (i.e. it is in postscript coordinates which are opposite to
917 wxWidgets) then the sense needs to be reversed.
919 However, this plays hell with window positions. The frame rects of any child views
920 do not change origin and this is actually important because if they did, the views
921 would send frame changed notifications, not to mention that Cocoa just doesn't really
922 do scrolling that way, it does it the way we do it here.
924 To fix this we implement GetPosition for child windows to not merely consult its
925 superview at the Cocoa level in order to do proper Cocoa->wx coordinate transform
926 but to actually consult is parent wxWindow because it makes a big difference if
927 the parent is scrolled. Argh. (FIXME: This isn't actually implemented yet)
929 void wxWindowCocoaScrollView::ScrollWindow(int dx, int dy, const wxRect*)
931 // Update our internal origin so we know how much the application code
932 // expects us to have been scrolled.
933 m_virtualOrigin.x += dx;
934 m_virtualOrigin.y += dy;
936 // Scroll the window using the standard Cocoa method of adjusting the
937 // clip view's bounds in the opposite fashion.
938 NSClipView *contentView = [m_cocoaNSScrollView contentView];
939 NSRect clipViewBoundsRect = [contentView bounds];
940 clipViewBoundsRect.origin.x -= dx;
941 if([contentView isFlipped])
942 clipViewBoundsRect.origin.y -= dy;
944 clipViewBoundsRect.origin.y += dy;
945 [contentView scrollToPoint:clipViewBoundsRect.origin];
948 void wxWindowCocoaScrollView::_wx_doScroller(NSScroller *sender)
950 wxOrientation orientation;
951 if(sender == [m_cocoaNSScrollView horizontalScroller])
952 orientation = wxHORIZONTAL;
953 else if(sender == [m_cocoaNSScrollView verticalScroller])
954 orientation = wxVERTICAL;
957 wxLogDebug(wxT("Received action message from unexpected NSScroller"));
960 // NOTE: Cocoa does not move the scroller for page up/down or line
961 // up/down events. That means the value will be the old value.
962 // For thumbtrack events, the value is the new value.
963 int scrollpos = GetScrollPos(orientation);
965 switch([sender hitPart])
968 case NSScrollerNoPart:
969 case NSScrollerKnob: // Drag of knob
970 case NSScrollerKnobSlot: // Jump directly to position
971 commandType = wxEVT_SCROLLWIN_THUMBTRACK;
973 case NSScrollerDecrementPage:
974 commandType = wxEVT_SCROLLWIN_PAGEUP;
976 case NSScrollerIncrementPage:
977 commandType = wxEVT_SCROLLWIN_PAGEDOWN;
979 case NSScrollerDecrementLine:
980 commandType = wxEVT_SCROLLWIN_LINEUP;
982 case NSScrollerIncrementLine:
983 commandType = wxEVT_SCROLLWIN_LINEDOWN;
986 wxScrollWinEvent event(commandType, scrollpos, orientation);
987 event.SetEventObject(m_owner);
988 m_owner->GetEventHandler()->ProcessEvent(event);
991 void wxWindowCocoaScrollView::UpdateSizes()
993 // Using the virtual size, figure out what the document frame size should be
994 // NOTE: Assume that the passed in virtualSize is already >= the client size
995 wxSize virtualSize = m_owner->GetVirtualSize();
997 // Get the document's current frame
998 NSRect documentViewFrame = [m_owner->GetNSView() frame];
999 NSRect newFrame = documentViewFrame;
1000 newFrame.size = NSMakeSize(virtualSize.x, virtualSize.y);
1002 if(!NSEqualRects(newFrame, documentViewFrame))
1004 [m_owner->GetNSView() setFrame:newFrame];
1008 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
1010 wxLogTrace(wxTRACE_COCOA,wxT("wxWindowCocoaScrollView=%p::Cocoa_FrameChanged for wxWindow %p"), this, m_owner);
1011 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
1012 event.SetEventObject(m_owner);
1013 m_owner->GetEventHandler()->ProcessEvent(event);
1017 // ========================================================================
1019 // ========================================================================
1020 // normally the base classes aren't included, but wxWindow is special
1021 #ifdef __WXUNIVERSAL__
1022 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
1024 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
1027 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
1030 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
1033 void wxWindowCocoa::Init()
1035 m_cocoaNSView = NULL;
1036 m_cocoaHider = NULL;
1037 m_wxCocoaScrollView = NULL;
1038 m_isBeingDeleted = false;
1039 m_isInPaint = false;
1040 m_visibleTrackingRectManager = NULL;
1044 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
1048 const wxString& name)
1050 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
1053 // TODO: create the window
1054 m_cocoaNSView = NULL;
1055 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
1056 [m_cocoaNSView release];
1060 m_parent->AddChild(this);
1061 m_parent->CocoaAddChild(this);
1062 SetInitialFrameRect(pos,size);
1069 wxWindow::~wxWindow()
1071 wxAutoNSAutoreleasePool pool;
1074 // Make sure our parent (in the wxWidgets sense) is our superview
1075 // before we go removing from it.
1076 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
1077 CocoaRemoveFromParent();
1078 delete m_cocoaHider;
1079 delete m_wxCocoaScrollView;
1085 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
1087 // Pool here due to lack of one during wx init phase
1088 wxAutoNSAutoreleasePool pool;
1090 NSView *childView = child->GetNSViewForSuperview();
1092 wxASSERT(childView);
1093 [m_cocoaNSView addSubview: childView];
1096 void wxWindowCocoa::CocoaRemoveFromParent(void)
1098 [GetNSViewForSuperview() removeFromSuperview];
1101 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
1103 // Clear the visible area tracking rect if we have one.
1104 delete m_visibleTrackingRectManager;
1105 m_visibleTrackingRectManager = NULL;
1107 bool need_debug = cocoaNSView || m_cocoaNSView;
1108 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
1109 DisassociateNSView(m_cocoaNSView);
1110 [cocoaNSView retain];
1111 [m_cocoaNSView release];
1112 m_cocoaNSView = cocoaNSView;
1113 AssociateNSView(m_cocoaNSView);
1114 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
1117 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
1120 ? m_cocoaHider->GetNSView()
1121 : m_wxCocoaScrollView
1122 ? m_wxCocoaScrollView->GetNSScrollView()
1126 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
1128 return m_wxCocoaScrollView
1129 ? m_wxCocoaScrollView->GetNSScrollView()
1133 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
1135 // TODO: Handle scrolling offset
1136 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
1139 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
1141 // TODO: Handle scrolling offset
1142 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
1145 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
1147 // TODO: Handle scrolling offset
1148 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
1151 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
1153 // TODO: Handle scrolling offset
1154 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
1157 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
1159 // TODO: Handle scrolling offset
1160 NSAffineTransform *transform = wxDC::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
1164 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
1166 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
1167 // Recursion can happen if the event loop runs from within the paint
1168 // handler. For instance, if an assertion dialog is shown.
1169 // FIXME: This seems less than ideal.
1172 wxLogDebug(wxT("Paint event recursion!"));
1177 // Set m_updateRegion
1178 const NSRect *rects = ▭ // The bounding box of the region
1179 NSInteger countRects = 1;
1180 // Try replacing the larger rectangle with a list of smaller ones:
1181 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
1182 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
1184 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
1185 for(int i=0; i<countRects; i++)
1187 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
1189 m_updateRegion = wxRegion(transformedRects,countRects);
1190 free(transformedRects);
1192 wxPaintEvent event(m_windowId);
1193 event.SetEventObject(this);
1194 bool ret = GetEventHandler()->ProcessEvent(event);
1195 m_isInPaint = false;
1199 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
1201 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
1202 // Mouse events happen at the NSWindow level so we need to convert
1203 // into our bounds coordinates then convert to wx coordinates.
1204 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
1205 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1206 // FIXME: Should we be adjusting for client area origin?
1207 const wxPoint &clientorigin = GetClientAreaOrigin();
1208 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1209 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1211 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
1212 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
1213 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
1214 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
1216 // TODO: set timestamp?
1217 event.SetEventObject(this);
1218 event.SetId(GetId());
1221 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
1223 wxMouseEvent event(wxEVT_MOTION);
1224 InitMouseEvent(event,theEvent);
1225 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
1226 return GetEventHandler()->ProcessEvent(event);
1229 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
1231 wxMouseEvent event(wxEVT_MOTION);
1232 NSWindow *window = [GetNSView() window];
1233 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
1234 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
1236 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1237 // FIXME: Should we be adjusting for client area origin?
1238 const wxPoint &clientorigin = GetClientAreaOrigin();
1239 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1240 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1242 // TODO: Handle shift, control, alt, meta flags
1243 event.SetEventObject(this);
1244 event.SetId(GetId());
1246 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
1247 GetEventHandler()->ProcessEvent(event);
1250 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
1252 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1254 m_visibleTrackingRectManager->BeginSynthesizingEvents();
1256 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
1257 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
1258 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
1259 // first responder, so the mouseMoved: events are effectively discarded.
1260 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
1262 wxMouseEvent event(wxEVT_ENTER_WINDOW);
1263 InitMouseEvent(event,theEvent);
1264 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
1265 return GetEventHandler()->ProcessEvent(event);
1271 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
1273 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1275 m_visibleTrackingRectManager->StopSynthesizingEvents();
1277 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
1278 InitMouseEvent(event,theEvent);
1279 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
1280 return GetEventHandler()->ProcessEvent(event);
1286 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
1288 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
1289 InitMouseEvent(event,theEvent);
1290 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1291 return GetEventHandler()->ProcessEvent(event);
1294 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
1296 wxMouseEvent event(wxEVT_MOTION);
1297 InitMouseEvent(event,theEvent);
1298 event.m_leftDown = true;
1299 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1300 return GetEventHandler()->ProcessEvent(event);
1303 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
1305 wxMouseEvent event(wxEVT_LEFT_UP);
1306 InitMouseEvent(event,theEvent);
1307 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1308 return GetEventHandler()->ProcessEvent(event);
1311 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
1313 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
1314 InitMouseEvent(event,theEvent);
1315 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1316 return GetEventHandler()->ProcessEvent(event);
1319 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
1321 wxMouseEvent event(wxEVT_MOTION);
1322 InitMouseEvent(event,theEvent);
1323 event.m_rightDown = true;
1324 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1325 return GetEventHandler()->ProcessEvent(event);
1328 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
1330 wxMouseEvent event(wxEVT_RIGHT_UP);
1331 InitMouseEvent(event,theEvent);
1332 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1333 return GetEventHandler()->ProcessEvent(event);
1336 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
1341 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
1346 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
1351 void wxWindowCocoa::Cocoa_FrameChanged(void)
1353 // We always get this message for the real NSView which may have been
1354 // enclosed in an NSScrollView. If that's the case then what we're
1355 // effectively getting here is a notifcation that the
1356 // virtual sized changed.. which we don't need to send on since
1357 // wx has no concept of this whatsoever.
1358 bool isViewForSuperview = (m_wxCocoaScrollView == NULL);
1359 if(isViewForSuperview)
1361 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
1362 if(m_visibleTrackingRectManager != NULL)
1363 m_visibleTrackingRectManager->RebuildTrackingRect();
1364 wxSizeEvent event(GetSize(), m_windowId);
1365 event.SetEventObject(this);
1366 GetEventHandler()->ProcessEvent(event);
1370 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged ignored"),this);
1374 bool wxWindowCocoa::Cocoa_resetCursorRects()
1376 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
1378 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
1379 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
1380 // mouse exited event. If the control happens to share the edge with its parent window which is
1381 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
1382 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
1383 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
1384 // which willl in turn call us before exiting for the next user event.
1386 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
1387 // our mouse exited event because the already queued event will have the old tracking rect
1388 // tag. The simple solution is to only rebuild our tracking rect if we need to.
1390 if(m_visibleTrackingRectManager != NULL)
1391 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
1393 if(!m_cursor.GetNSCursor())
1396 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1401 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
1403 if(!wxWindowBase::SetCursor(cursor))
1406 // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
1407 // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
1408 // rects and thus won't ever call resetCursorRects.
1409 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1411 // Invalidate the cursor rects so the cursor will change
1412 // Note that it is not enough to remove the old one (if any) and add the new one.
1413 // For the rects to work properly, Cocoa itself must call resetCursorRects.
1414 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
1418 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
1420 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
1421 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
1422 if(m_visibleTrackingRectManager != NULL)
1423 m_visibleTrackingRectManager->BuildTrackingRect();
1427 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
1429 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
1430 // Clear tracking rects. It is imperative this be done before the new window is set.
1431 if(m_visibleTrackingRectManager != NULL)
1432 m_visibleTrackingRectManager->ClearTrackingRect();
1436 bool wxWindow::Close(bool force)
1438 // The only reason this function exists is that it is virtual and
1439 // wxTopLevelWindowCocoa will override it.
1440 return wxWindowBase::Close(force);
1443 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
1445 [[oldView superview] replaceSubview:oldView with:newView];
1448 void wxWindow::DoEnable(bool enable)
1450 CocoaSetEnabled(enable);
1453 bool wxWindow::Show(bool show)
1455 wxAutoNSAutoreleasePool pool;
1456 // If the window is marked as visible, then it shouldn't have a dummy view
1457 // If the window is marked hidden, then it should have a dummy view
1458 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
1459 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
1460 // Return false if there isn't a window to show or hide
1461 NSView *cocoaView = GetNSViewForHiding();
1466 // If state isn't changing, return false
1469 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
1470 wxASSERT(![m_cocoaHider->GetNSView() superview]);
1471 delete m_cocoaHider;
1472 m_cocoaHider = NULL;
1473 wxASSERT([cocoaView superview]);
1477 // If state isn't changing, return false
1480 m_cocoaHider = new wxWindowCocoaHider(this);
1481 // NOTE: replaceSubview:with will cause m_cocaNSView to be
1482 // (auto)released which balances out addSubview
1483 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
1484 // m_coocaNSView is now only retained by us
1485 wxASSERT([m_cocoaHider->GetNSView() superview]);
1486 wxASSERT(![cocoaView superview]);
1492 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
1494 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":".");
1495 int currentX, currentY;
1496 int currentW, currentH;
1497 DoGetPosition(¤tX, ¤tY);
1498 DoGetSize(¤tW, ¤tH);
1499 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1501 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1504 AdjustForParentClientOrigin(x,y,sizeFlags);
1506 wxSize size(wxDefaultSize);
1508 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1510 if(sizeFlags&wxSIZE_AUTO_WIDTH)
1512 size=DoGetBestSize();
1518 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1520 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
1523 size=DoGetBestSize();
1529 DoMoveWindow(x,y,width,height);
1534 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
1536 wxWindowBase::DoSetToolTip(tip);
1540 m_tooltip->SetWindow((wxWindow *)this);
1546 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
1548 wxAutoNSAutoreleasePool pool;
1549 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
1551 NSView *nsview = GetNSViewForSuperview();
1552 NSView *superview = [nsview superview];
1554 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1556 NSRect oldFrameRect = [nsview frame];
1557 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
1558 [nsview setFrame:newFrameRect];
1559 // Be sure to redraw the parent to reflect the changed position
1560 [superview setNeedsDisplayInRect:oldFrameRect];
1561 [superview setNeedsDisplayInRect:newFrameRect];
1564 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1566 NSView *nsview = GetNSViewForSuperview();
1567 NSView *superview = [nsview superview];
1568 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
1569 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1570 NSRect frameRect = [nsview frame];
1572 frameRect.size.width = size.x;
1574 frameRect.size.height = size.y;
1575 frameRect.origin.x = pos.x;
1576 frameRect.origin.y = pos.y;
1577 // Tell Cocoa to change the margin between the bottom of the superview
1578 // and the bottom of the control. Keeps the control pinned to the top
1579 // of its superview so that its position in the wxWidgets coordinate
1580 // system doesn't change.
1581 if(![superview isFlipped])
1582 [nsview setAutoresizingMask: NSViewMinYMargin];
1583 // MUST set the mask before setFrame: which can generate a size event
1584 // and cause a scroller to be added!
1585 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1586 [nsview setFrame: frameRect];
1590 void wxWindow::DoGetSize(int *w, int *h) const
1592 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1594 *w=(int)cocoaRect.size.width;
1596 *h=(int)cocoaRect.size.height;
1597 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1600 void wxWindow::DoGetPosition(int *x, int *y) const
1602 NSView *nsview = GetNSViewForSuperview();
1604 NSRect cocoaRect = [nsview frame];
1605 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1607 *x=(int)rectWx.origin.x;
1609 *y=(int)rectWx.origin.y;
1610 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1613 WXWidget wxWindow::GetHandle() const
1615 return m_cocoaNSView;
1618 wxWindow* wxWindow::GetWxWindow() const
1620 return (wxWindow*) this;
1623 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1625 [m_cocoaNSView setNeedsDisplay:YES];
1628 void wxWindow::SetFocus()
1630 if([GetNSView() acceptsFirstResponder])
1631 [[GetNSView() window] makeFirstResponder: GetNSView()];
1634 void wxWindow::DoCaptureMouse()
1637 sm_capturedWindow = this;
1640 void wxWindow::DoReleaseMouse()
1643 sm_capturedWindow = NULL;
1646 void wxWindow::DoScreenToClient(int *x, int *y) const
1648 // Point in cocoa screen coordinates:
1649 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1650 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1651 NSWindow *theWindow = [clientView window];
1653 // Point in window's base coordinate system:
1654 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1655 // Point in view's bounds coordinate system
1656 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1657 // Point in wx client coordinates:
1658 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1660 *x = theWxClientPoint.x;
1662 *y = theWxClientPoint.y;
1665 void wxWindow::DoClientToScreen(int *x, int *y) const
1667 // Point in wx client coordinates
1668 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1670 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1672 // Point in the view's bounds coordinate system
1673 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1675 // Point in the window's base coordinate system
1676 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1678 NSWindow *theWindow = [clientView window];
1679 // Point in Cocoa's screen coordinates
1680 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1682 // Act as though this was the origin of a 0x0 rectangle
1683 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1685 // Convert that rectangle to wx coordinates
1686 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1688 *x = theWxScreenPoint.x;
1690 *y = theWxScreenPoint.y;
1693 // Get size *available for subwindows* i.e. excluding menu bar etc.
1694 void wxWindow::DoGetClientSize(int *x, int *y) const
1696 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1697 if(m_wxCocoaScrollView)
1698 m_wxCocoaScrollView->DoGetClientSize(x,y);
1700 wxWindowCocoa::DoGetSize(x,y);
1703 void wxWindow::DoSetClientSize(int width, int height)
1705 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1706 if(m_wxCocoaScrollView)
1707 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1708 CocoaSetWxWindowSize(width,height);
1711 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1713 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1716 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1718 // Intentional no-op.
1721 wxString wxWindow::GetLabel() const
1723 // General Get/Set of labels is implemented in wxControlBase
1724 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1725 return wxEmptyString;
1728 int wxWindow::GetCharHeight() const
1734 int wxWindow::GetCharWidth() const
1740 void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
1741 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
1743 // FIXME: This obviously ignores the window's font (if any) along with any size
1744 // transformations. However, it's better than nothing.
1745 // We don't create a wxClientDC because we don't want to accidently be able to use
1748 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
1751 // Coordinates relative to the window
1752 void wxWindow::WarpPointer (int x_pos, int y_pos)
1757 int wxWindow::GetScrollPos(int orient) const
1759 if(m_wxCocoaScrollView != NULL)
1760 return m_wxCocoaScrollView->GetScrollPos(static_cast<wxOrientation>(orient & wxBOTH));
1765 // This now returns the whole range, not just the number
1766 // of positions that we can scroll.
1767 int wxWindow::GetScrollRange(int orient) const
1769 if(m_wxCocoaScrollView != NULL)
1770 return m_wxCocoaScrollView->GetScrollRange(static_cast<wxOrientation>(orient & wxBOTH));
1775 int wxWindow::GetScrollThumb(int orient) const
1777 if(m_wxCocoaScrollView != NULL)
1778 return m_wxCocoaScrollView->GetScrollThumb(static_cast<wxOrientation>(orient & wxBOTH));
1783 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1785 if(m_wxCocoaScrollView != NULL)
1786 return m_wxCocoaScrollView->SetScrollPos(static_cast<wxOrientation>(orient & wxBOTH), pos);
1789 void wxWindow::CocoaCreateNSScrollView()
1791 if(!m_wxCocoaScrollView)
1793 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1797 // New function that will replace some of the above.
1798 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1799 int range, bool refresh)
1801 CocoaCreateNSScrollView();
1802 m_wxCocoaScrollView->SetScrollbar(orient, pos, thumbVisible, range);
1803 // TODO: Handle refresh (if we even need to)
1806 // Does a physical scroll
1807 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1809 if(m_wxCocoaScrollView != NULL)
1810 m_wxCocoaScrollView->ScrollWindow(dx, dy, rect);
1813 void wxWindow::DoSetVirtualSize( int x, int y )
1815 // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
1816 // possibly not what the caller passed in. For example, the current implementation
1817 // clamps the width and height to within the min/max virtual ranges.
1818 // wxDefaultCoord is passed through unchanged but then GetVirtualSize() will correct
1819 // that by returning effectively max(virtual, client)
1820 wxWindowBase::DoSetVirtualSize(x,y);
1821 // Create the scroll view if it hasn't been already.
1822 CocoaCreateNSScrollView();
1824 // The GetVirtualSize automatically increases the size to max(client,virtual)
1825 m_wxCocoaScrollView->UpdateSizes();
1828 bool wxWindow::SetFont(const wxFont& font)
1830 // FIXME: We may need to handle wx font inheritance.
1831 return wxWindowBase::SetFont(font);
1834 #if 0 // these are used when debugging the algorithm.
1835 static char const * const comparisonresultStrings[] =
1842 class CocoaWindowCompareContext
1844 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1846 CocoaWindowCompareContext(); // Not implemented
1847 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1850 // Cocoa sorts subviews in-place.. make a copy
1851 m_subviews = [subviews copy];
1853 ~CocoaWindowCompareContext()
1854 { // release the copy
1855 [m_subviews release];
1858 { return m_target; }
1860 { return m_subviews; }
1861 /* Helper function that returns the comparison based off of the original ordering */
1862 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1864 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1865 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1866 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1867 // likely compare higher than the other view which is reasonable considering the only way that
1868 // can happen is if the subview was added after our call to subviews but before the call to
1869 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1870 // that case should never occur anyway because that would imply a multi-threaded GUI call
1871 // which is a big no-no with Cocoa.
1873 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1874 NSComparisonResult result = (firstI < secondI)
1875 ? NSOrderedAscending /* -1 */
1876 : (firstI > secondI)
1877 ? NSOrderedDescending /* 1 */
1878 : NSOrderedSame /* 0 */;
1880 #if 0 // Enable this if you need to debug the algorithm.
1881 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1886 /* The subview we are trying to Raise or Lower */
1888 /* A copy of the original array of subviews */
1889 NSArray *m_subviews;
1892 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1893 * the target view is always higher than every other view. When comparing two views neither of
1894 * which is the target, it returns the correct response based on the original ordering
1896 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1898 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1899 // first should be ordered higher
1900 if(first==compareContext->target())
1901 return NSOrderedDescending;
1902 // second should be ordered higher
1903 if(second==compareContext->target())
1904 return NSOrderedAscending;
1905 return compareContext->CompareUsingOriginalOrdering(first,second);
1908 // Raise the window to the top of the Z order
1909 void wxWindow::Raise()
1911 // wxAutoNSAutoreleasePool pool;
1912 NSView *nsview = GetNSViewForSuperview();
1913 NSView *superview = [nsview superview];
1914 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1916 [superview sortSubviewsUsingFunction:
1917 CocoaRaiseWindowCompareFunction
1918 context: &compareContext];
1921 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1922 * the target view is always lower than every other view. When comparing two views neither of
1923 * which is the target, it returns the correct response based on the original ordering
1925 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1927 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1928 // first should be ordered lower
1929 if(first==compareContext->target())
1930 return NSOrderedAscending;
1931 // second should be ordered lower
1932 if(second==compareContext->target())
1933 return NSOrderedDescending;
1934 return compareContext->CompareUsingOriginalOrdering(first,second);
1937 // Lower the window to the bottom of the Z order
1938 void wxWindow::Lower()
1940 NSView *nsview = GetNSViewForSuperview();
1941 NSView *superview = [nsview superview];
1942 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1945 NSLog(@"Target:\n%@\n", nsview);
1946 NSLog(@"Before:\n%@\n", compareContext.subviews());
1948 [superview sortSubviewsUsingFunction:
1949 CocoaLowerWindowCompareFunction
1950 context: &compareContext];
1952 NSLog(@"After:\n%@\n", [superview subviews]);
1956 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1961 // Get the window with the focus
1962 wxWindow *wxWindowBase::DoFindFocus()
1964 // Basically we are somewhat emulating the responder chain here except
1965 // we are only loking for the first responder in the key window or
1966 // upon failing to find one if the main window is different we look
1967 // for the first responder in the main window.
1969 // Note that the firstResponder doesn't necessarily have to be an
1970 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
1971 // NULL unless it finds its argument in its hash map.
1975 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
1976 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
1978 return win->GetWxWindow();
1980 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
1981 if(mainWindow == keyWindow)
1983 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
1985 return win->GetWxWindow();
1990 /* static */ wxWindow *wxWindowBase::GetCapture()
1993 return wxWindowCocoa::sm_capturedWindow;
1996 wxWindow *wxGetActiveWindow()
2002 wxPoint wxGetMousePosition()
2005 return wxDefaultPosition;
2008 wxMouseState wxGetMouseState()
2015 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
2017 pt = wxGetMousePosition();
2021 // ========================================================================
2022 // wxCocoaMouseMovedEventSynthesizer
2023 // ========================================================================
2025 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
2027 /* This class registers one run loop observer to cover all windows registered with it.
2028 * It will register the observer when the first view is registerd and unregister the
2029 * observer when the last view is unregistered.
2030 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
2031 * is no reason it couldn't be instantiated multiple times.
2033 class wxCocoaMouseMovedEventSynthesizer
2035 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
2037 wxCocoaMouseMovedEventSynthesizer()
2038 { m_lastScreenMouseLocation = NSZeroPoint;
2040 ~wxCocoaMouseMovedEventSynthesizer();
2041 void RegisterWxCocoaView(wxCocoaNSView *aView);
2042 void UnregisterWxCocoaView(wxCocoaNSView *aView);
2043 void SynthesizeMouseMovedEvent();
2046 void AddRunLoopObserver();
2047 void RemoveRunLoopObserver();
2048 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
2049 std::list<wxCocoaNSView*> m_registeredViews;
2050 NSPoint m_lastScreenMouseLocation;
2051 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
2054 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
2056 m_registeredViews.push_back(aView);
2057 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
2059 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
2061 AddRunLoopObserver();
2065 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
2067 m_registeredViews.remove(aView);
2068 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
2069 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
2071 RemoveRunLoopObserver();
2075 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
2077 if(!m_registeredViews.empty())
2079 // This means failure to clean up so we report on it as a debug message.
2080 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
2081 m_registeredViews.clear();
2083 if(m_runLoopObserver != NULL)
2085 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
2086 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
2087 RemoveRunLoopObserver();
2091 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
2093 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
2096 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
2098 CFRunLoopObserverContext observerContext =
2106 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
2107 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
2109 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
2110 // in our observer procedure being called before the run loop has decided that it is going to return control to
2111 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
2112 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
2114 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
2115 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
2116 // around to it before letting the event loop do its work. This has some very odd effects on the way the
2117 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
2118 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
2119 // time the new one is built.
2121 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
2122 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
2123 // the mouseExited: message to be sent properly.
2125 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
2126 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2127 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
2130 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
2132 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2133 m_runLoopObserver.reset();
2134 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
2137 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
2139 NSPoint screenMouseLocation = [NSEvent mouseLocation];
2140 // Checking the last mouse location is done for a few reasons:
2141 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
2142 // telling the app the mouse moved when the user hit a key for instance.
2143 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
2144 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
2145 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
2146 // loop causing the tracking rectangles to constantly be reset.
2147 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
2149 m_lastScreenMouseLocation = screenMouseLocation;
2150 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
2151 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
2153 (*i)->Cocoa_synthesizeMouseMoved();
2158 // Singleton used for all views:
2159 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
2161 // ========================================================================
2162 // wxCocoaTrackingRectManager
2163 // ========================================================================
2165 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
2168 m_isTrackingRectActive = false;
2169 BuildTrackingRect();
2172 void wxCocoaTrackingRectManager::ClearTrackingRect()
2174 if(m_isTrackingRectActive)
2176 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
2177 m_isTrackingRectActive = false;
2178 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2180 // If we were doing periodic events we need to clear those too
2181 StopSynthesizingEvents();
2184 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
2186 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
2189 void wxCocoaTrackingRectManager::BuildTrackingRect()
2191 // Pool here due to lack of one during wx init phase
2192 wxAutoNSAutoreleasePool pool;
2194 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
2196 NSView *theView = m_window->GetNSView();
2198 if([theView window] != nil)
2200 NSRect visibleRect = [theView visibleRect];
2202 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
2203 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
2204 m_isTrackingRectActive = true;
2206 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2210 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
2212 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
2215 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
2217 if(m_isTrackingRectActive)
2219 NSView *theView = m_window->GetNSView();
2220 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
2221 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
2223 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
2227 RebuildTrackingRect();
2230 void wxCocoaTrackingRectManager::RebuildTrackingRect()
2232 ClearTrackingRect();
2233 BuildTrackingRect();
2236 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
2238 ClearTrackingRect();
2241 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
2243 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);