]> git.saurik.com Git - wxWidgets.git/blob - src/cocoa/window.mm
implement wxListBox::EnsureVisible() in wxGTK; add a test for it to the widgets sample
[wxWidgets.git] / src / cocoa / window.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/cocoa/window.mm
3 // Purpose: wxWindowCocoa
4 // Author: David Elliott
5 // Modified by:
6 // Created: 2002/12/26
7 // RCS-ID: $Id$
8 // Copyright: (c) 2002 David Elliott
9 // Licence: wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #ifndef WX_PRECOMP
15 #include "wx/log.h"
16 #include "wx/window.h"
17 #include "wx/dc.h"
18 #include "wx/dcclient.h"
19 #include "wx/utils.h"
20 #endif //WX_PRECOMP
21
22 #include "wx/tooltip.h"
23
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/mac/corefoundation/cfref.h"
29 #include "wx/cocoa/ObjcRef.h"
30
31 #import <Foundation/NSArray.h>
32 #import <Foundation/NSRunLoop.h>
33 #include "wx/cocoa/objc/NSView.h"
34 #import <AppKit/NSEvent.h>
35 #import <AppKit/NSScrollView.h>
36 #import <AppKit/NSScroller.h>
37 #import <AppKit/NSColor.h>
38 #import <AppKit/NSClipView.h>
39 #import <Foundation/NSException.h>
40 #import <AppKit/NSApplication.h>
41 #import <AppKit/NSWindow.h>
42 #import <AppKit/NSScreen.h>
43
44 // Turn this on to paint green over the dummy views for debugging
45 #undef WXCOCOA_FILL_DUMMY_VIEW
46
47 #ifdef WXCOCOA_FILL_DUMMY_VIEW
48 #import <AppKit/NSBezierPath.h>
49 #endif //def WXCOCOA_FILL_DUMMY_VIEW
50
51 // STL list used by wxCocoaMouseMovedEventSynthesizer
52 #include <list>
53
54 /* NSComparisonResult is typedef'd as an enum pre-Leopard but typedef'd as
55 * NSInteger post-Leopard. Pre-Leopard the Cocoa toolkit expects a function
56 * returning int and not NSComparisonResult. Post-Leopard the Cocoa toolkit
57 * expects a function returning the new non-enum NSComparsionResult.
58 * Hence we create a typedef named CocoaWindowCompareFunctionResult.
59 */
60 #if defined(NSINTEGER_DEFINED)
61 typedef NSComparisonResult CocoaWindowCompareFunctionResult;
62 #else
63 typedef int CocoaWindowCompareFunctionResult;
64 #endif
65
66 // A category for methods that are only present in Panther's SDK
67 @interface NSView(wxNSViewPrePantherCompatibility)
68 - (void)getRectsBeingDrawn:(const NSRect **)rects count:(int *)count;
69 @end
70
71 // ========================================================================
72 // Helper functions for converting to/from wxWidgets coordinates and a
73 // specified NSView's coordinate system.
74 // ========================================================================
75 NSPoint CocoaTransformNSViewBoundsToWx(NSView *nsview, NSPoint pointBounds)
76 {
77 wxCHECK_MSG(nsview, pointBounds, wxT("Need to have a Cocoa view to do translation"));
78 if([nsview isFlipped])
79 return pointBounds;
80 NSRect ourBounds = [nsview bounds];
81 return NSMakePoint
82 ( pointBounds.x
83 , ourBounds.size.height - pointBounds.y
84 );
85 }
86
87 NSRect CocoaTransformNSViewBoundsToWx(NSView *nsview, NSRect rectBounds)
88 {
89 wxCHECK_MSG(nsview, rectBounds, wxT("Need to have a Cocoa view to do translation"));
90 if([nsview isFlipped])
91 return rectBounds;
92 NSRect ourBounds = [nsview bounds];
93 return NSMakeRect
94 ( rectBounds.origin.x
95 , ourBounds.size.height - (rectBounds.origin.y + rectBounds.size.height)
96 , rectBounds.size.width
97 , rectBounds.size.height
98 );
99 }
100
101 NSPoint CocoaTransformNSViewWxToBounds(NSView *nsview, NSPoint pointWx)
102 {
103 wxCHECK_MSG(nsview, pointWx, wxT("Need to have a Cocoa view to do translation"));
104 if([nsview isFlipped])
105 return pointWx;
106 NSRect ourBounds = [nsview bounds];
107 return NSMakePoint
108 ( pointWx.x
109 , ourBounds.size.height - pointWx.y
110 );
111 }
112
113 NSRect CocoaTransformNSViewWxToBounds(NSView *nsview, NSRect rectWx)
114 {
115 wxCHECK_MSG(nsview, rectWx, wxT("Need to have a Cocoa view to do translation"));
116 if([nsview isFlipped])
117 return rectWx;
118 NSRect ourBounds = [nsview bounds];
119 return NSMakeRect
120 ( rectWx.origin.x
121 , ourBounds.size.height - (rectWx.origin.y + rectWx.size.height)
122 , rectWx.size.width
123 , rectWx.size.height
124 );
125 }
126
127 // ============================================================================
128 // Screen coordinate helpers
129 // ============================================================================
130
131 /*
132 General observation about Cocoa screen coordinates:
133 It is documented that the first object of the [NSScreen screens] array is the screen with the menubar.
134
135 It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always
136 the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up.
137
138 It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle
139 relative to that screen's frame. The only real way to test this is to configure two screens one atop
140 the other such that the menubar screen is on top. The Dock at the bottom of the screen will then
141 eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two
142 1920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and
143 visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin.
144
145 In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen).
146 However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme.
147 This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen.
148 */
149
150 namespace { // file namespace
151
152 class wxCocoaPrivateScreenCoordinateTransformer
153 {
154 DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer)
155 public:
156 wxCocoaPrivateScreenCoordinateTransformer();
157 ~wxCocoaPrivateScreenCoordinateTransformer();
158 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
159 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
160
161 protected:
162 NSScreen *m_screenZero;
163 NSRect m_screenZeroFrame;
164 };
165
166 // NOTE: This is intended to be a short-lived object. A future enhancment might
167 // make it a global and reconfigure it upon some notification that the screen layout
168 // has changed.
169 inline wxCocoaPrivateScreenCoordinateTransformer::wxCocoaPrivateScreenCoordinateTransformer()
170 {
171 NSArray *screens = [NSScreen screens];
172
173 [screens retain];
174
175 m_screenZero = nil;
176 if(screens != nil && [screens count] > 0)
177 m_screenZero = [[screens objectAtIndex:0] retain];
178
179 [screens release];
180
181 if(m_screenZero != nil)
182 m_screenZeroFrame = [m_screenZero frame];
183 else
184 {
185 wxLogWarning(wxT("Can't translate to/from wx screen coordinates and Cocoa screen coordinates"));
186 // Just blindly assume 1024x768 so that at least we can sort of flip things around into
187 // Cocoa coordinates.
188 // NOTE: Theoretically this case should never happen anyway.
189 m_screenZeroFrame = NSMakeRect(0,0,1024,768);
190 }
191 }
192
193 inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
194 {
195 [m_screenZero release];
196 m_screenZero = nil;
197 }
198
199 inline wxPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
200 {
201 // x and y are in wx screen coordinates which we're going to arbitrarily define such that
202 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
203 // NOTE WELL: This means that (0,0) is _NOT_ an appropriate position for a window.
204
205 wxPoint theWxOrigin;
206
207 // Working in Cocoa's screen coordinates we must realize that the x coordinate we want is
208 // the distance between the left side (origin.x) of the window's frame and the left side of
209 // screen zero's frame.
210 theWxOrigin.x = windowFrame.origin.x - m_screenZeroFrame.origin.x;
211
212 // Working in Cocoa's screen coordinates we must realize that the y coordinate we want is
213 // actually the distance between the top-left of the screen zero frame and the top-left
214 // of the window's frame.
215
216 theWxOrigin.y = (m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height) - (windowFrame.origin.y + windowFrame.size.height);
217
218 return theWxOrigin;
219 }
220
221 inline NSPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
222 {
223 NSPoint theCocoaOrigin;
224
225 // The position is in wx screen coordinates which we're going to arbitrarily define such that
226 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
227
228 // NOTE: The usable rectangle is smaller and hence we have the keepOriginVisible flag
229 // which will move the origin downward and/or left as necessary if the origin is
230 // inside the screen0 rectangle (i.e. x/y >= 0 in wx coordinates) and outside the
231 // visible frame (i.e. x/y < the top/left of the screen0 visible frame in wx coordinates)
232 // We don't munge origin coordinates < 0 because it actually is possible that the menubar is on
233 // the top of the bottom screen and thus that origin is completely valid!
234 if(keepOriginVisible && (m_screenZero != nil))
235 {
236 // Do al of this in wx coordinates because it's far simpler since we're dealing with top/left points
237 wxPoint visibleOrigin = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates([m_screenZero visibleFrame]);
238 if(x >= 0 && x < visibleOrigin.x)
239 x = visibleOrigin.x;
240 if(y >= 0 && y < visibleOrigin.y)
241 y = visibleOrigin.y;
242 }
243
244 // The x coordinate is simple as it's just relative to screen zero's frame
245 theCocoaOrigin.x = m_screenZeroFrame.origin.x + x;
246 // Working in Cocoa's coordinates think to start at the bottom of screen zero's frame and add
247 // the height of that rect which gives us the coordinate for the top of the visible rect. Now realize that
248 // the wx coordinates are flipped so if y is say 10 then we want to be 10 pixels down from that and thus
249 // we subtract y. But then we still need to take into account the size of the window which is h and subtract
250 // that to get the bottom-left origin of the rectangle.
251 theCocoaOrigin.y = m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height - y - height;
252
253 return theCocoaOrigin;
254 }
255
256 } // namespace
257
258 wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
259 {
260 wxCocoaPrivateScreenCoordinateTransformer transformer;
261 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
262 }
263
264 NSPoint wxWindowCocoa::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
265 {
266 wxCocoaPrivateScreenCoordinateTransformer transformer;
267 return transformer.OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,keepOriginVisible);
268 }
269
270 // ========================================================================
271 // wxWindowCocoaHider
272 // ========================================================================
273 class wxWindowCocoaHider: protected wxCocoaNSView
274 {
275 DECLARE_NO_COPY_CLASS(wxWindowCocoaHider)
276 public:
277 wxWindowCocoaHider(wxWindow *owner);
278 virtual ~wxWindowCocoaHider();
279 inline WX_NSView GetNSView() { return m_dummyNSView; }
280 protected:
281 wxWindowCocoa *m_owner;
282 WX_NSView m_dummyNSView;
283 virtual void Cocoa_FrameChanged(void);
284 virtual void Cocoa_synthesizeMouseMoved(void) {}
285 #ifdef WXCOCOA_FILL_DUMMY_VIEW
286 virtual bool Cocoa_drawRect(const NSRect& rect);
287 #endif //def WXCOCOA_FILL_DUMMY_VIEW
288 private:
289 wxWindowCocoaHider();
290 };
291
292 // ========================================================================
293 // wxWindowCocoaScrollView
294 // ========================================================================
295 class wxWindowCocoaScrollView: protected wxCocoaNSView
296 {
297 DECLARE_NO_COPY_CLASS(wxWindowCocoaScrollView)
298 public:
299 wxWindowCocoaScrollView(wxWindow *owner);
300 virtual ~wxWindowCocoaScrollView();
301 inline WX_NSScrollView GetNSScrollView() { return m_cocoaNSScrollView; }
302 void ClientSizeToSize(int &width, int &height);
303 void DoGetClientSize(int *x, int *y) const;
304 void Encapsulate();
305 void Unencapsulate();
306
307 // wxWindow calls this to do the work. Note that we don't have the refresh parameter
308 // because wxWindow handles that itself.
309 void SetScrollbar(int orientation, int position, int thumbSize, int range);
310 int GetScrollPos(wxOrientation orient);
311 void SetScrollPos(wxOrientation orient, int position);
312 int GetScrollRange(wxOrientation orient);
313 int GetScrollThumb(wxOrientation orient);
314 void ScrollWindow(int dx, int dy, const wxRect*);
315 void UpdateSizes();
316
317 void _wx_doScroller(NSScroller *sender);
318
319 protected:
320 wxWindowCocoa *m_owner;
321 WX_NSScrollView m_cocoaNSScrollView;
322 virtual void Cocoa_FrameChanged(void);
323 virtual void Cocoa_synthesizeMouseMoved(void) {}
324 /*!
325 Flag as to whether we're scrolling for a native view or a custom
326 wxWindow. This controls the scrolling behavior. When providing
327 scrolling for a native view we don't catch scroller action messages
328 and thus don't send scroll events and we don't actually scroll the
329 window when the application calls ScrollWindow.
330
331 When providing scrolling for a custom wxWindow, we make the NSScroller
332 send their action messages to us which we in turn package as wx window
333 scrolling events. At this point, the window will not physically be
334 scrolled. The application will most likely handle the event by calling
335 ScrollWindow which will do the real scrolling. On the other hand,
336 the application may instead not call ScrollWindow until some threshold
337 is reached. This causes the window to only scroll in steps which is
338 what, for instance, wxScrolledWindow does.
339 */
340 bool m_isNativeView;
341 /*!
342 The range as the application code wishes to see it. That is, the
343 range from the last SetScrollbar call for the appropriate dimension.
344 The horizontal dimension is the first [0] element and the vertical
345 dimension the second [1] element.
346
347 In wxMSW, a SCROLLINFO with nMin=0 and nMax=range-1 is used which
348 gives exactly range possible positions so long as nPage (which is
349 the thumb size) is less than or equal to 1.
350 */
351 int m_scrollRange[2];
352 /*!
353 The thumb size is intended to reflect the size of the visible portion
354 of the scrolled document. As the document size increases, the thumb
355 visible thumb size decreases. As document size decreases, the visible
356 thumb size increases. However, the thumb size on wx is defined in
357 terms of scroll units (which are effectively defined by the scroll
358 range) and so increasing the number of scroll units to reflect increased
359 document size will have the effect of decreasing the visible thumb
360 size even though the number doesn't change.
361
362 It's also important to note that subtracting the thumb size from the
363 full range gives you the real range that can be used. Microsoft
364 defines nPos (the current scrolling position) to be within the range
365 from nMin to nMax - max(nPage - 1, 0). We know that wxMSW code always
366 sets nMin = 0 and nMax = range -1. So let's algebraically reduce the
367 definition of the maximum allowed position:
368
369 Begin:
370 = nMax - max(nPage - 1, 0)
371 Substitute (range - 1) for nMax and thumbSize for nPage:
372 = range - 1 - max(thumbSize - 1, 0)
373 Add one inside the max conditional and subtract one outside of it:
374 = range - 1 - (max(thumbSize - 1 + 1, 1) - 1)
375 Reduce some constants:
376 = range - 1 - (max(thumbSize, 1) - 1)
377 Distribute the negative across the parenthesis:
378 = range - 1 - max(thumbSize, 1) + 1
379 Reduce the constants:
380 = range - max(thumbSize, 1)
381
382 Also keep in mind that thumbSize may never be greater than range but
383 can be equal to it. Thus for the smallest possible thumbSize there
384 are exactly range possible scroll positions (numbered from 0 to
385 range - 1) and for the largest possible thumbSize there is exactly
386 one possible scroll position (numbered 0).
387 */
388 int m_scrollThumb[2];
389
390 /*!
391 The origin of the virtual coordinate space expressed in terms of client
392 coordinates. Starts at (0,0) and each call to ScrollWindow accumulates
393 into it. Thus if the user scrolls the window right (thus causing the
394 contents to move left with respect to the client origin, the
395 application code (typically wxScrolledWindow) will be called with
396 dx of -something, for example -20. This is added to m_virtualOrigin
397 and thus m_virtualOrigin will be (-20,0) in this example.
398 */
399 wxPoint m_virtualOrigin;
400 private:
401 wxWindowCocoaScrollView();
402 };
403
404 // ========================================================================
405 // wxDummyNSView
406 // ========================================================================
407 @interface wxDummyNSView : NSView
408 - (NSView *)hitTest:(NSPoint)aPoint;
409 @end
410 WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
411
412 @implementation wxDummyNSView : NSView
413 - (NSView *)hitTest:(NSPoint)aPoint
414 {
415 return nil;
416 }
417
418 @end
419 WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
420
421 // ========================================================================
422 // wxWindowCocoaHider
423 // ========================================================================
424 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
425 : m_owner(owner)
426 {
427 wxASSERT(owner);
428 wxASSERT(owner->GetNSViewForHiding());
429 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
430 initWithFrame:[owner->GetNSViewForHiding() frame]];
431 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
432 AssociateNSView(m_dummyNSView);
433 }
434
435 wxWindowCocoaHider::~wxWindowCocoaHider()
436 {
437 DisassociateNSView(m_dummyNSView);
438 [m_dummyNSView release];
439 }
440
441 void wxWindowCocoaHider::Cocoa_FrameChanged(void)
442 {
443 // Keep the real window in synch with the dummy
444 wxASSERT(m_dummyNSView);
445 [m_owner->GetNSViewForHiding() setFrame:[m_dummyNSView frame]];
446 }
447
448
449 #ifdef WXCOCOA_FILL_DUMMY_VIEW
450 bool wxWindowCocoaHider::Cocoa_drawRect(const NSRect& rect)
451 {
452 NSBezierPath *bezpath = [NSBezierPath bezierPathWithRect:rect];
453 [[NSColor greenColor] set];
454 [bezpath stroke];
455 [bezpath fill];
456 return true;
457 }
458 #endif //def WXCOCOA_FILL_DUMMY_VIEW
459
460
461 /*! @class WXManualScrollView
462 @abstract An NSScrollView subclass which implements wx scroll behavior
463 @discussion
464 Overrides default behavior of NSScrollView such that this class receives
465 the scroller action messages and allows the wxCocoaScrollView to act
466 on them accordingly. In particular, because the NSScrollView will not
467 receive action messages from the scroller, it will not adjust the
468 document view. This must be done manually using the ScrollWindow
469 method of wxWindow.
470 */
471 @interface WXManualScrollView : NSScrollView
472 {
473 /*! @field m_wxCocoaScrollView
474 */
475 wxWindowCocoaScrollView *m_wxCocoaScrollView;
476 }
477
478 // Override these to set up the target/action correctly
479 - (void)setHorizontalScroller:(NSScroller *)aScroller;
480 - (void)setVerticalScroller:(NSScroller *)aScroller;
481 - (void)setHasHorizontalScroller:(BOOL)flag;
482 - (void)setHasVerticalScroller:(BOOL)flag;
483
484 // NOTE: _wx_ prefix means "private" method like _ that Apple (and only Apple) uses.
485 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView;
486 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView;
487
488 /*! @method _wx_doScroller
489 @abstract Handles action messages from the scrollers
490 @discussion
491 Similar to Apple's _doScroller: method which is private and not documented.
492 We do not, however, call that method. Instead, we effectively override
493 it entirely. We don't override it by naming ourself the same thing because
494 the base class code may or may not call that method for other reasons we
495 simply cannot know about.
496 */
497 - (void)_wx_doScroller:(id)sender;
498
499 @end
500
501
502 @implementation WXManualScrollView : NSScrollView
503
504 static inline void WXManualScrollView_DoSetScrollerTargetAction(WXManualScrollView *self, NSScroller *aScroller)
505 {
506 if(aScroller != NULL && [self _wx_wxCocoaScrollView] != NULL)
507 {
508 [aScroller setTarget:self];
509 [aScroller setAction:@selector(_wx_doScroller:)];
510 }
511 }
512
513 - (void)setHorizontalScroller:(NSScroller *)aScroller
514 {
515 [super setHorizontalScroller:aScroller];
516 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
517 }
518
519 - (void)setVerticalScroller:(NSScroller *)aScroller
520 {
521 [super setVerticalScroller:aScroller];
522 WXManualScrollView_DoSetScrollerTargetAction(self, aScroller);
523 }
524
525 - (void)setHasHorizontalScroller:(BOOL)flag
526 {
527 [super setHasHorizontalScroller:flag];
528 WXManualScrollView_DoSetScrollerTargetAction(self, [self horizontalScroller]);
529 }
530
531 - (void)setHasVerticalScroller:(BOOL)flag
532 {
533 [super setHasVerticalScroller:flag];
534 WXManualScrollView_DoSetScrollerTargetAction(self, [self verticalScroller]);
535 }
536
537 - (wxWindowCocoaScrollView*)_wx_wxCocoaScrollView
538 { return m_wxCocoaScrollView; }
539
540 - (void)_wx_setWxCocoaScrollView:(wxWindowCocoaScrollView*)theWxScrollView
541 {
542 m_wxCocoaScrollView = theWxScrollView;
543 [self setHorizontalScroller:[self horizontalScroller]];
544 [self setVerticalScroller:[self verticalScroller]];
545 }
546
547 - (void)_wx_doScroller:(id)sender
548 {
549 if(m_wxCocoaScrollView != NULL)
550 m_wxCocoaScrollView->_wx_doScroller(sender);
551 else
552 wxLogError(wxT("Unexpected action message received from NSScroller"));
553 }
554
555 - (void)reflectScrolledClipView:(NSClipView *)aClipView
556 {
557 struct _ScrollerBackup
558 {
559 _ScrollerBackup(NSScroller *aScroller)
560 : m_scroller(aScroller)
561 , m_floatValue(aScroller!=nil?[aScroller floatValue]:0.0)
562 , m_knobProportion(aScroller!=nil?[aScroller knobProportion]:1.0)
563 , m_isEnabled(aScroller!=nil?[aScroller isEnabled]:false)
564 {
565 }
566 NSScroller *m_scroller;
567 CGFloat m_floatValue;
568 CGFloat m_knobProportion;
569 BOOL m_isEnabled;
570 ~_ScrollerBackup()
571 {
572 if(m_scroller != nil)
573 {
574 [m_scroller setFloatValue:m_floatValue knobProportion:m_knobProportion];
575 [m_scroller setEnabled:m_isEnabled];
576 }
577 }
578 private:
579 _ScrollerBackup();
580 _ScrollerBackup(_ScrollerBackup const&);
581 _ScrollerBackup& operator=(_ScrollerBackup const&);
582 };
583 _ScrollerBackup _horizontalBackup([self horizontalScroller]);
584 _ScrollerBackup _verticalBackup([self verticalScroller]);
585 // We MUST call super's implementation or else nothing seems to work right at all.
586 // However, we need our scrollers not to change values due to the document window
587 // moving so we cheat and save/restore their values across this call.
588 [super reflectScrolledClipView: aClipView];
589 }
590
591 @end
592 WX_IMPLEMENT_GET_OBJC_CLASS(WXManualScrollView,NSScrollView)
593
594 // ========================================================================
595 // wxFlippedNSClipView
596 // ========================================================================
597 @interface wxFlippedNSClipView : NSClipView
598 - (BOOL)isFlipped;
599 @end
600 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
601
602 @implementation wxFlippedNSClipView : NSClipView
603 - (BOOL)isFlipped
604 {
605 return YES;
606 }
607
608 @end
609 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
610
611 // ========================================================================
612 // wxWindowCocoaScrollView
613 // ========================================================================
614 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
615 : m_owner(owner)
616 , m_cocoaNSScrollView() // nil
617 , m_scrollRange() // {0,0}
618 , m_scrollThumb() // {0,0}
619 , m_virtualOrigin(0,0)
620 {
621 wxAutoNSAutoreleasePool pool;
622 wxASSERT(owner);
623 wxASSERT(owner->GetNSView());
624 m_isNativeView = ![owner->GetNSView() isKindOfClass:[WX_GET_OBJC_CLASS(WXNSView) class]];
625 m_cocoaNSScrollView = [(m_isNativeView?[NSScrollView alloc]:[WXManualScrollView alloc])
626 initWithFrame:[owner->GetNSView() frame]];
627 AssociateNSView(m_cocoaNSScrollView);
628 if(m_isNativeView)
629 {
630 /* Set a bezel border around the entire thing because it looks funny without it.
631 TODO: Be sure to undo any borders on the real view (if any) and apply them
632 to this view if necessary. Right now, there is no border support in wxCocoa
633 so this isn't an issue.
634 */
635 [m_cocoaNSScrollView setBorderType:NSBezelBorder];
636 }
637 else
638 {
639 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView: this];
640 // Don't set a bezel because we might be creating a scroll view due to being
641 // the "target window" of a wxScrolledWindow. That is to say that the user
642 // has absolutely no intention of scrolling the clip view used by this
643 // NSScrollView.
644 }
645
646 /* Replace the default NSClipView with a flipped one. This ensures
647 scrolling is "pinned" to the top-left instead of bottom-right. */
648 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
649 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
650 [m_cocoaNSScrollView setContentView:flippedClip];
651 [flippedClip release];
652
653 // In all cases we must encapsulate the real NSView properly
654 Encapsulate();
655 }
656
657 void wxWindowCocoaScrollView::Encapsulate()
658 {
659 // Set the scroll view autoresizingMask to match the current NSView
660 [m_cocoaNSScrollView setAutoresizingMask: [m_owner->GetNSView() autoresizingMask]];
661 [m_owner->GetNSView() setAutoresizingMask: NSViewNotSizable];
662 // NOTE: replaceSubView will cause m_cocaNSView to be released
663 // except when it hasn't been added into an NSView hierarchy in which
664 // case it doesn't need to be and this should work out to a no-op
665 m_owner->CocoaReplaceView(m_owner->GetNSView(), m_cocoaNSScrollView);
666 // The NSView is still retained by owner
667 [m_cocoaNSScrollView setDocumentView: m_owner->GetNSView()];
668 // Now it's also retained by the NSScrollView
669 }
670
671 void wxWindowCocoaScrollView::Unencapsulate()
672 {
673 [m_cocoaNSScrollView setDocumentView: nil];
674 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
675 if(![[m_owner->GetNSView() superview] isFlipped])
676 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
677 }
678
679 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
680 {
681 DisassociateNSView(m_cocoaNSScrollView);
682 if(!m_isNativeView)
683 {
684 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView:NULL];
685 }
686 [m_cocoaNSScrollView release];
687 }
688
689 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
690 {
691 NSSize frameSize = [NSScrollView
692 frameSizeForContentSize: NSMakeSize(width,height)
693 hasHorizontalScroller: [m_cocoaNSScrollView hasHorizontalScroller]
694 hasVerticalScroller: [m_cocoaNSScrollView hasVerticalScroller]
695 borderType: [m_cocoaNSScrollView borderType]];
696 width = (int)frameSize.width;
697 height = (int)frameSize.height;
698 }
699
700 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
701 {
702 NSSize nssize = [m_cocoaNSScrollView contentSize];
703 if(x)
704 *x = (int)nssize.width;
705 if(y)
706 *y = (int)nssize.height;
707 }
708
709 static inline void SetCocoaScroller(NSScroller *aScroller, int WXUNUSED(orientation), int position, int thumbSize, int range)
710 {
711 wxCHECK_RET(aScroller != nil, wxT("Expected the NSScrollView to have a scroller"));
712
713 // NOTE: thumbSize is already ensured to be >= 1 and <= range by our caller
714 // unless range = 0 in which case we shouldn't have been be called.
715 wxCHECK_RET(range > 0, wxT("Internal wxCocoa bug: shouldn't have been called with 0 range"));
716
717 // Range of valid position values is from 0 to effectiveRange
718 // NOTE: if thumbSize == range then effectiveRange is 0.
719 // thumbSize is at least 1 which gives range from 0 to range - 1 inclusive
720 // which is exactly what we want.
721 int effectiveRange = range - thumbSize;
722
723 // knobProportion is hopefully easy to understand
724 // Note that thumbSize is already guaranteed >= 1 by our caller.
725 CGFloat const knobProportion = CGFloat(thumbSize)/CGFloat(range);
726
727 // NOTE: When effectiveRange is zero there really is no valid position
728 // We arbitrarily pick 0.0 which is the same as a scroller in the home position.
729 CGFloat const floatValue = (effectiveRange != 0)?CGFloat(position)/CGFloat(effectiveRange):0.0;
730
731 [aScroller setFloatValue:floatValue knobProportion: knobProportion];
732 // Make sure it's visibly working
733 [aScroller setEnabled:YES];
734 }
735
736 void wxWindowCocoaScrollView::SetScrollPos(wxOrientation orientation, int position)
737 {
738 // NOTE: Rather than using only setFloatValue: (which we could do) we instead
739 // simply share the SetCocoaScroller call because all but the knobProportion
740 // calculations have to be done anyway.
741 if(orientation & wxHORIZONTAL)
742 {
743 NSScroller *aScroller = [m_cocoaNSScrollView horizontalScroller];
744 if(aScroller != nil)
745 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[0], m_scrollRange[0]);
746 }
747 if(orientation & wxVERTICAL)
748 {
749 NSScroller *aScroller = [m_cocoaNSScrollView verticalScroller];
750 if(aScroller != nil)
751 SetCocoaScroller(aScroller, orientation, position, m_scrollThumb[1], m_scrollRange[1]);
752 }
753 }
754
755 void wxWindowCocoaScrollView::SetScrollbar(int orientation, int position, int thumbSize, int range)
756 {
757 // FIXME: API assumptions:
758 // 1. If the user wants to remove a scroller he gives range 0.
759 // 2. If the user wants to disable a scroller he sets thumbSize == range
760 // in which case it is logically impossible to scroll.
761 // The scroller shall still be displayed.
762
763 // Ensure that range is >= 0.
764 wxASSERT(range >= 0);
765 if(range < 0)
766 range = 0;
767
768 // Ensure that thumbSize <= range
769 wxASSERT(thumbSize <= range);
770 // Also ensure thumbSize >= 1 but don't complain if it isn't
771 if(thumbSize < 1)
772 thumbSize = 1;
773 // Now make sure it's really less than range, even if we just set it to 1
774 if(thumbSize > range)
775 thumbSize = range;
776
777 bool needScroller = (range != 0);
778
779 // Can potentially set both horizontal and vertical at the same time although this is
780 // probably not very useful.
781 if(orientation & wxHORIZONTAL)
782 {
783 m_scrollRange[0] = range;
784 m_scrollThumb[0] = thumbSize;
785 if(!m_isNativeView)
786 {
787 [m_cocoaNSScrollView setHasHorizontalScroller:needScroller];
788 if(needScroller)
789 SetCocoaScroller([m_cocoaNSScrollView horizontalScroller], orientation, position, thumbSize, range);
790 }
791 }
792
793 if(orientation & wxVERTICAL)
794 {
795 m_scrollRange[1] = range;
796 m_scrollThumb[1] = thumbSize;
797 if(!m_isNativeView)
798 {
799 [m_cocoaNSScrollView setHasVerticalScroller:needScroller];
800 if(needScroller)
801 SetCocoaScroller([m_cocoaNSScrollView verticalScroller], orientation, position, thumbSize, range);
802 }
803 }
804 }
805
806 int wxWindowCocoaScrollView::GetScrollPos(wxOrientation orient)
807 {
808 if((orient & wxBOTH) == wxBOTH)
809 {
810 wxLogError(wxT("GetScrollPos called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
811 return 0;
812 }
813 int effectiveScrollRange;
814 NSScroller *cocoaScroller;
815 if(orient & wxHORIZONTAL)
816 {
817 effectiveScrollRange = m_scrollRange[0] - m_scrollThumb[0];
818 cocoaScroller = [m_cocoaNSScrollView horizontalScroller];
819 }
820 else if(orient & wxVERTICAL)
821 {
822 effectiveScrollRange = m_scrollRange[1] - m_scrollThumb[1];
823 cocoaScroller = [m_cocoaNSScrollView verticalScroller];
824 }
825 else
826 {
827 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
828 return 0;
829 }
830 if(cocoaScroller == nil)
831 { // Document is not scrolled
832 return 0;
833 }
834 /*
835 The effective range of a scroll bar as defined by wxWidgets is from 0 to (range - thumbSize).
836 That is a scroller at the left/top position is at 0 and a scroller at the bottom/right
837 position is at range-thumbsize.
838
839 The range of an NSScroller is 0.0 to 1.0. Much easier! NOTE: Apple doesn't really specify
840 but GNUStep docs do say that 0.0 is top/left and 1.0 is bottom/right. This is actualy
841 in contrast to NSSlider which generally has 1.0 at the TOP when it's done vertically.
842 */
843 CGFloat cocoaScrollPos = [cocoaScroller floatValue];
844 return effectiveScrollRange * cocoaScrollPos;
845 }
846
847 int wxWindowCocoaScrollView::GetScrollRange(wxOrientation orient)
848 {
849 if((orient & wxBOTH) == wxBOTH)
850 {
851 wxLogError(wxT("GetScrollRange called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
852 return 0;
853 }
854 if(orient & wxHORIZONTAL)
855 {
856 return m_scrollRange[0];
857 }
858 else if(orient & wxVERTICAL)
859 {
860 return m_scrollRange[1];
861 }
862 else
863 {
864 wxLogError(wxT("GetScrollPos called without an orientation which makes no sense"));
865 return 0;
866 }
867 }
868
869 int wxWindowCocoaScrollView::GetScrollThumb(wxOrientation orient)
870 {
871 if((orient & wxBOTH) == wxBOTH)
872 {
873 wxLogError(wxT("GetScrollThumb called for wxHORIZONTAL and wxVERTICAL together which makes no sense"));
874 return 0;
875 }
876 if(orient & wxHORIZONTAL)
877 {
878 return m_scrollThumb[0];
879 }
880 else if(orient & wxVERTICAL)
881 {
882 return m_scrollThumb[1];
883 }
884 else
885 {
886 wxLogError(wxT("GetScrollThumb called without an orientation which makes no sense"));
887 return 0;
888 }
889 }
890
891 /*!
892 Moves the contents (all existing drawing as well as all all child wxWindow) by the specified
893 amount expressed in the wxWindow's own coordinate system. This is used to implement scrolling
894 but the usage is rather interesting. When scrolling right (e.g. increasing the value of
895 the scroller) you must give a negative delta x (e.g. moving the contents LEFT). Likewise,
896 when scrolling the window down, increasing the value of the scroller, you give a negative
897 delta y which moves the contents up.
898
899 wxCocoa notes: To accomplish this trick in Cocoa we basically do what NSScrollView would
900 have done and that is adjust the content view's bounds origin. The content view is somewhat
901 confusingly the NSClipView which is more or less sort of the pImpl for NSScrollView
902 The real NSView with the user's content (e.g. the "virtual area" in wxWidgets parlance)
903 is called the document view in NSScrollView parlance.
904
905 The bounds origin is basically the exact opposite concept. Whereas in Windows the client
906 coordinate system remains constant and the content must shift left/up for increases
907 of scrolling, in Cocoa the coordinate system is actually the virtual one. So we must
908 instead shift the bounds rectangle right/down to get the effect of the content moving
909 left/up. Basically, it's a higher level interface than that provided by wxWidgets
910 so essentially we're implementing the low-level move content interface in terms of
911 the high-level move the viewport (the bounds) over top that content (the document
912 view which is the virtual area to wx).
913
914 For all intents and purposes that basically just means that we subtract the deltas
915 from the bounds origin and thus a negative delta actually increases the bounds origin
916 and a positive delta actually decreases it. This is absolutely true for the horizontal
917 axis but there's a catch in the vertical axis. If the content view (the clip view) is
918 flipped (and we do this by default) then it works exactly like the horizontal axis.
919 If it is not flipped (i.e. it is in postscript coordinates which are opposite to
920 wxWidgets) then the sense needs to be reversed.
921
922 However, this plays hell with window positions. The frame rects of any child views
923 do not change origin and this is actually important because if they did, the views
924 would send frame changed notifications, not to mention that Cocoa just doesn't really
925 do scrolling that way, it does it the way we do it here.
926
927 To fix this we implement GetPosition for child windows to not merely consult its
928 superview at the Cocoa level in order to do proper Cocoa->wx coordinate transform
929 but to actually consult is parent wxWindow because it makes a big difference if
930 the parent is scrolled. Argh. (FIXME: This isn't actually implemented yet)
931 */
932 void wxWindowCocoaScrollView::ScrollWindow(int dx, int dy, const wxRect*)
933 {
934 // Update our internal origin so we know how much the application code
935 // expects us to have been scrolled.
936 m_virtualOrigin.x += dx;
937 m_virtualOrigin.y += dy;
938
939 // Scroll the window using the standard Cocoa method of adjusting the
940 // clip view's bounds in the opposite fashion.
941 NSClipView *contentView = [m_cocoaNSScrollView contentView];
942 NSRect clipViewBoundsRect = [contentView bounds];
943 clipViewBoundsRect.origin.x -= dx;
944 if([contentView isFlipped])
945 clipViewBoundsRect.origin.y -= dy;
946 else
947 clipViewBoundsRect.origin.y += dy;
948 [contentView scrollToPoint:clipViewBoundsRect.origin];
949 }
950
951 void wxWindowCocoaScrollView::_wx_doScroller(NSScroller *sender)
952 {
953 wxOrientation orientation;
954 if(sender == [m_cocoaNSScrollView horizontalScroller])
955 orientation = wxHORIZONTAL;
956 else if(sender == [m_cocoaNSScrollView verticalScroller])
957 orientation = wxVERTICAL;
958 else
959 {
960 wxLogDebug(wxT("Received action message from unexpected NSScroller"));
961 return;
962 }
963 // NOTE: Cocoa does not move the scroller for page up/down or line
964 // up/down events. That means the value will be the old value.
965 // For thumbtrack events, the value is the new value.
966 int scrollpos = GetScrollPos(orientation);
967 int commandType;
968 switch([sender hitPart])
969 {
970 default:
971 case NSScrollerNoPart:
972 case NSScrollerKnob: // Drag of knob
973 case NSScrollerKnobSlot: // Jump directly to position
974 commandType = wxEVT_SCROLLWIN_THUMBTRACK;
975 break;
976 case NSScrollerDecrementPage:
977 commandType = wxEVT_SCROLLWIN_PAGEUP;
978 break;
979 case NSScrollerIncrementPage:
980 commandType = wxEVT_SCROLLWIN_PAGEDOWN;
981 break;
982 case NSScrollerDecrementLine:
983 commandType = wxEVT_SCROLLWIN_LINEUP;
984 break;
985 case NSScrollerIncrementLine:
986 commandType = wxEVT_SCROLLWIN_LINEDOWN;
987 break;
988 }
989 wxScrollWinEvent event(commandType, scrollpos, orientation);
990 event.SetEventObject(m_owner);
991 m_owner->HandleWindowEvent(event);
992 }
993
994 void wxWindowCocoaScrollView::UpdateSizes()
995 {
996 // Using the virtual size, figure out what the document frame size should be
997 // NOTE: Assume that the passed in virtualSize is already >= the client size
998 wxSize virtualSize = m_owner->GetVirtualSize();
999
1000 // Get the document's current frame
1001 NSRect documentViewFrame = [m_owner->GetNSView() frame];
1002 NSRect newFrame = documentViewFrame;
1003 newFrame.size = NSMakeSize(virtualSize.x, virtualSize.y);
1004
1005 if(!NSEqualRects(newFrame, documentViewFrame))
1006 {
1007 [m_owner->GetNSView() setFrame:newFrame];
1008 }
1009 }
1010
1011 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
1012 {
1013 wxLogTrace(wxTRACE_COCOA,wxT("wxWindowCocoaScrollView=%p::Cocoa_FrameChanged for wxWindow %p"), this, m_owner);
1014 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
1015 event.SetEventObject(m_owner);
1016 m_owner->HandleWindowEvent(event);
1017 UpdateSizes();
1018 }
1019
1020 // ========================================================================
1021 // wxWindowCocoa
1022 // ========================================================================
1023 // normally the base classes aren't included, but wxWindow is special
1024 #ifdef __WXUNIVERSAL__
1025 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
1026 #else
1027 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
1028 #endif
1029
1030 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
1031 END_EVENT_TABLE()
1032
1033 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
1034
1035 // Constructor
1036 void wxWindowCocoa::Init()
1037 {
1038 m_cocoaNSView = NULL;
1039 m_cocoaHider = NULL;
1040 m_wxCocoaScrollView = NULL;
1041 m_isBeingDeleted = false;
1042 m_isInPaint = false;
1043 m_visibleTrackingRectManager = NULL;
1044 }
1045
1046 // Constructor
1047 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
1048 const wxPoint& pos,
1049 const wxSize& size,
1050 long style,
1051 const wxString& name)
1052 {
1053 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
1054 return false;
1055
1056 // TODO: create the window
1057 m_cocoaNSView = NULL;
1058 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
1059 [m_cocoaNSView release];
1060
1061 if (m_parent)
1062 {
1063 m_parent->AddChild(this);
1064 m_parent->CocoaAddChild(this);
1065 SetInitialFrameRect(pos,size);
1066 }
1067
1068 return true;
1069 }
1070
1071 // Destructor
1072 wxWindow::~wxWindow()
1073 {
1074 wxAutoNSAutoreleasePool pool;
1075 DestroyChildren();
1076
1077 // Make sure our parent (in the wxWidgets sense) is our superview
1078 // before we go removing from it.
1079 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
1080 CocoaRemoveFromParent();
1081 delete m_cocoaHider;
1082 delete m_wxCocoaScrollView;
1083 if(m_cocoaNSView)
1084 SendDestroyEvent();
1085 SetNSView(NULL);
1086 }
1087
1088 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
1089 {
1090 // Pool here due to lack of one during wx init phase
1091 wxAutoNSAutoreleasePool pool;
1092
1093 NSView *childView = child->GetNSViewForSuperview();
1094
1095 wxASSERT(childView);
1096 [m_cocoaNSView addSubview: childView];
1097 }
1098
1099 void wxWindowCocoa::CocoaRemoveFromParent(void)
1100 {
1101 [GetNSViewForSuperview() removeFromSuperview];
1102 }
1103
1104 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
1105 {
1106 // Clear the visible area tracking rect if we have one.
1107 delete m_visibleTrackingRectManager;
1108 m_visibleTrackingRectManager = NULL;
1109
1110 bool need_debug = cocoaNSView || m_cocoaNSView;
1111 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
1112 DisassociateNSView(m_cocoaNSView);
1113 wxGCSafeRetain(cocoaNSView);
1114 wxGCSafeRelease(m_cocoaNSView);
1115 m_cocoaNSView = cocoaNSView;
1116 AssociateNSView(m_cocoaNSView);
1117 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
1118 }
1119
1120 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
1121 {
1122 return m_cocoaHider
1123 ? m_cocoaHider->GetNSView()
1124 : m_wxCocoaScrollView
1125 ? m_wxCocoaScrollView->GetNSScrollView()
1126 : m_cocoaNSView;
1127 }
1128
1129 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
1130 {
1131 return m_wxCocoaScrollView
1132 ? m_wxCocoaScrollView->GetNSScrollView()
1133 : m_cocoaNSView;
1134 }
1135
1136 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
1137 {
1138 // TODO: Handle scrolling offset
1139 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
1140 }
1141
1142 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
1143 {
1144 // TODO: Handle scrolling offset
1145 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
1146 }
1147
1148 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
1149 {
1150 // TODO: Handle scrolling offset
1151 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
1152 }
1153
1154 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
1155 {
1156 // TODO: Handle scrolling offset
1157 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
1158 }
1159
1160 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
1161 {
1162 // TODO: Handle scrolling offset
1163 NSAffineTransform *transform = wxCocoaDCImpl::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
1164 return transform;
1165 }
1166
1167 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
1168 {
1169 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
1170 // Recursion can happen if the event loop runs from within the paint
1171 // handler. For instance, if an assertion dialog is shown.
1172 // FIXME: This seems less than ideal.
1173 if(m_isInPaint)
1174 {
1175 wxLogDebug(wxT("Paint event recursion!"));
1176 return false;
1177 }
1178 m_isInPaint = true;
1179
1180 // Set m_updateRegion
1181 const NSRect *rects = &rect; // The bounding box of the region
1182 NSInteger countRects = 1;
1183 // Try replacing the larger rectangle with a list of smaller ones:
1184 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
1185 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
1186
1187 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
1188 for(int i=0; i<countRects; i++)
1189 {
1190 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
1191 }
1192 m_updateRegion = wxRegion(transformedRects,countRects);
1193 free(transformedRects);
1194
1195 wxPaintEvent event(m_windowId);
1196 event.SetEventObject(this);
1197 bool ret = HandleWindowEvent(event);
1198 m_isInPaint = false;
1199 return ret;
1200 }
1201
1202 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
1203 {
1204 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
1205 // Mouse events happen at the NSWindow level so we need to convert
1206 // into our bounds coordinates then convert to wx coordinates.
1207 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
1208 if( m_wxCocoaScrollView != NULL)
1209 {
1210 // This gets the wx client area (i.e. the visible portion of the content) in
1211 // the coordinate system of our (the doucment) view.
1212 NSRect documentVisibleRect = [[m_wxCocoaScrollView->GetNSScrollView() contentView] documentVisibleRect];
1213 // For horizontal, simply subtract the origin.
1214 // e.g. if the origin is at 123 and the user clicks as far left as possible then
1215 // the coordinate that wx wants is 0.
1216 cocoaPoint.x -= documentVisibleRect.origin.x;
1217 if([m_cocoaNSView isFlipped])
1218 {
1219 // In the flipped view case this works exactly like horizontal.
1220 cocoaPoint.y -= documentVisibleRect.origin.y;
1221 }
1222 // For vertical we have to mind non-flipped (e.g. y=0 at bottom) views.
1223 // We also need to mind the fact that we're still in Cocoa coordinates
1224 // and not wx coordinates. The wx coordinate translation will still occur
1225 // and that is going to be wxY = boundsH - cocoaY for non-flipped views.
1226
1227 // When we consider scrolling we are truly interested in how far the top
1228 // edge of the bounds rectangle is scrolled off the screen.
1229 // Assuming the bounds origin is 0 (which is an assumption we make in
1230 // wxCocoa since wxWidgets has no analog to it) then the top edge of
1231 // the bounds rectangle is simply its height. The top edge of the
1232 // documentVisibleRect (e.g. the client area) is its height plus
1233 // its origin.
1234 // Thus, we simply need add the distance between the bounds top
1235 // and the client (docuemntVisibleRect) top.
1236 // Or putting it another way, we subtract the distance between the
1237 // client top and the bounds top.
1238 else
1239 {
1240 NSRect bounds = [m_cocoaNSView bounds];
1241 CGFloat scrollYOrigin = (bounds.size.height - (documentVisibleRect.origin.y + documentVisibleRect.size.height));
1242 cocoaPoint.y += scrollYOrigin;
1243 }
1244 }
1245
1246 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1247 // FIXME: Should we be adjusting for client area origin?
1248 const wxPoint &clientorigin = GetClientAreaOrigin();
1249 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1250 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1251
1252 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
1253 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
1254 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
1255 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
1256
1257 // TODO: set timestamp?
1258 event.SetEventObject(this);
1259 event.SetId(GetId());
1260 }
1261
1262 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
1263 {
1264 wxMouseEvent event(wxEVT_MOTION);
1265 InitMouseEvent(event,theEvent);
1266 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
1267 return HandleWindowEvent(event);
1268 }
1269
1270 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
1271 {
1272 wxMouseEvent event(wxEVT_MOTION);
1273 NSWindow *window = [GetNSView() window];
1274 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
1275 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
1276
1277 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1278 // FIXME: Should we be adjusting for client area origin?
1279 const wxPoint &clientorigin = GetClientAreaOrigin();
1280 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1281 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1282
1283 // TODO: Handle shift, control, alt, meta flags
1284 event.SetEventObject(this);
1285 event.SetId(GetId());
1286
1287 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
1288 HandleWindowEvent(event);
1289 }
1290
1291 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
1292 {
1293 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1294 {
1295 m_visibleTrackingRectManager->BeginSynthesizingEvents();
1296
1297 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
1298 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
1299 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
1300 // first responder, so the mouseMoved: events are effectively discarded.
1301 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
1302
1303 wxMouseEvent event(wxEVT_ENTER_WINDOW);
1304 InitMouseEvent(event,theEvent);
1305 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
1306 return HandleWindowEvent(event);
1307 }
1308 else
1309 return false;
1310 }
1311
1312 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
1313 {
1314 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1315 {
1316 m_visibleTrackingRectManager->StopSynthesizingEvents();
1317
1318 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
1319 InitMouseEvent(event,theEvent);
1320 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
1321 return HandleWindowEvent(event);
1322 }
1323 else
1324 return false;
1325 }
1326
1327 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
1328 {
1329 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
1330 InitMouseEvent(event,theEvent);
1331 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1332 return HandleWindowEvent(event);
1333 }
1334
1335 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
1336 {
1337 wxMouseEvent event(wxEVT_MOTION);
1338 InitMouseEvent(event,theEvent);
1339 event.m_leftDown = true;
1340 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1341 return HandleWindowEvent(event);
1342 }
1343
1344 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
1345 {
1346 wxMouseEvent event(wxEVT_LEFT_UP);
1347 InitMouseEvent(event,theEvent);
1348 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1349 return HandleWindowEvent(event);
1350 }
1351
1352 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
1353 {
1354 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
1355 InitMouseEvent(event,theEvent);
1356 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1357 return HandleWindowEvent(event);
1358 }
1359
1360 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
1361 {
1362 wxMouseEvent event(wxEVT_MOTION);
1363 InitMouseEvent(event,theEvent);
1364 event.m_rightDown = true;
1365 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1366 return HandleWindowEvent(event);
1367 }
1368
1369 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
1370 {
1371 wxMouseEvent event(wxEVT_RIGHT_UP);
1372 InitMouseEvent(event,theEvent);
1373 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1374 return HandleWindowEvent(event);
1375 }
1376
1377 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
1378 {
1379 return false;
1380 }
1381
1382 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
1383 {
1384 return false;
1385 }
1386
1387 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
1388 {
1389 return false;
1390 }
1391
1392 void wxWindowCocoa::Cocoa_FrameChanged(void)
1393 {
1394 // We always get this message for the real NSView which may have been
1395 // enclosed in an NSScrollView. If that's the case then what we're
1396 // effectively getting here is a notifcation that the
1397 // virtual sized changed.. which we don't need to send on since
1398 // wx has no concept of this whatsoever.
1399 bool isViewForSuperview = (m_wxCocoaScrollView == NULL);
1400 if(isViewForSuperview)
1401 {
1402 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
1403 if(m_visibleTrackingRectManager != NULL)
1404 m_visibleTrackingRectManager->RebuildTrackingRect();
1405 wxSizeEvent event(GetSize(), m_windowId);
1406 event.SetEventObject(this);
1407 HandleWindowEvent(event);
1408 }
1409 else
1410 {
1411 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged ignored"),this);
1412 }
1413 }
1414
1415 bool wxWindowCocoa::Cocoa_resetCursorRects()
1416 {
1417 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
1418
1419 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
1420 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
1421 // mouse exited event. If the control happens to share the edge with its parent window which is
1422 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
1423 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
1424 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
1425 // which willl in turn call us before exiting for the next user event.
1426
1427 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
1428 // our mouse exited event because the already queued event will have the old tracking rect
1429 // tag. The simple solution is to only rebuild our tracking rect if we need to.
1430
1431 if(m_visibleTrackingRectManager != NULL)
1432 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
1433
1434 if(!m_cursor.GetNSCursor())
1435 return false;
1436
1437 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1438
1439 return true;
1440 }
1441
1442 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
1443 {
1444 if(!wxWindowBase::SetCursor(cursor))
1445 return false;
1446
1447 // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
1448 // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
1449 // rects and thus won't ever call resetCursorRects.
1450 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1451
1452 // Invalidate the cursor rects so the cursor will change
1453 // Note that it is not enough to remove the old one (if any) and add the new one.
1454 // For the rects to work properly, Cocoa itself must call resetCursorRects.
1455 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
1456 return true;
1457 }
1458
1459 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
1460 {
1461 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
1462 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
1463 if(m_visibleTrackingRectManager != NULL)
1464 m_visibleTrackingRectManager->BuildTrackingRect();
1465 return false;
1466 }
1467
1468 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
1469 {
1470 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
1471 // Clear tracking rects. It is imperative this be done before the new window is set.
1472 if(m_visibleTrackingRectManager != NULL)
1473 m_visibleTrackingRectManager->ClearTrackingRect();
1474 return false;
1475 }
1476
1477 bool wxWindow::Close(bool force)
1478 {
1479 // The only reason this function exists is that it is virtual and
1480 // wxTopLevelWindowCocoa will override it.
1481 return wxWindowBase::Close(force);
1482 }
1483
1484 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
1485 {
1486 [[oldView superview] replaceSubview:oldView with:newView];
1487 }
1488
1489 void wxWindow::DoEnable(bool enable)
1490 {
1491 CocoaSetEnabled(enable);
1492 }
1493
1494 bool wxWindow::Show(bool show)
1495 {
1496 wxAutoNSAutoreleasePool pool;
1497 // If the window is marked as visible, then it shouldn't have a dummy view
1498 // If the window is marked hidden, then it should have a dummy view
1499 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
1500 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
1501 // Return false if there isn't a window to show or hide
1502 NSView *cocoaView = GetNSViewForHiding();
1503 if(!cocoaView)
1504 return false;
1505 if(show)
1506 {
1507 // If state isn't changing, return false
1508 if(!m_cocoaHider)
1509 return false;
1510 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
1511 wxASSERT(![m_cocoaHider->GetNSView() superview]);
1512 delete m_cocoaHider;
1513 m_cocoaHider = NULL;
1514 wxASSERT([cocoaView superview]);
1515 }
1516 else
1517 {
1518 // If state isn't changing, return false
1519 if(m_cocoaHider)
1520 return false;
1521 m_cocoaHider = new wxWindowCocoaHider(this);
1522 // NOTE: replaceSubview:with will cause m_cocaNSView to be
1523 // (auto)released which balances out addSubview
1524 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
1525 // m_coocaNSView is now only retained by us
1526 wxASSERT([m_cocoaHider->GetNSView() superview]);
1527 wxASSERT(![cocoaView superview]);
1528 }
1529 m_isShown = show;
1530 return true;
1531 }
1532
1533 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
1534 {
1535 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":".");
1536 int currentX, currentY;
1537 int currentW, currentH;
1538 DoGetPosition(&currentX, &currentY);
1539 DoGetSize(&currentW, &currentH);
1540 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1541 x=currentX;
1542 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1543 y=currentY;
1544
1545 AdjustForParentClientOrigin(x,y,sizeFlags);
1546
1547 wxSize size(wxDefaultSize);
1548
1549 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1550 {
1551 if(sizeFlags&wxSIZE_AUTO_WIDTH)
1552 {
1553 size=DoGetBestSize();
1554 width=size.x;
1555 }
1556 else
1557 width=currentW;
1558 }
1559 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1560 {
1561 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
1562 {
1563 if(size.x==-1)
1564 size=DoGetBestSize();
1565 height=size.y;
1566 }
1567 else
1568 height=currentH;
1569 }
1570 DoMoveWindow(x,y,width,height);
1571 }
1572
1573 #if wxUSE_TOOLTIPS
1574
1575 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
1576 {
1577 wxWindowBase::DoSetToolTip(tip);
1578
1579 if ( m_tooltip )
1580 {
1581 m_tooltip->SetWindow((wxWindow *)this);
1582 }
1583 }
1584
1585 #endif
1586
1587 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
1588 {
1589 wxAutoNSAutoreleasePool pool;
1590 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
1591
1592 NSView *nsview = GetNSViewForSuperview();
1593 NSView *superview = [nsview superview];
1594
1595 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1596
1597 NSRect oldFrameRect = [nsview frame];
1598 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
1599 [nsview setFrame:newFrameRect];
1600 // Be sure to redraw the parent to reflect the changed position
1601 [superview setNeedsDisplayInRect:oldFrameRect];
1602 [superview setNeedsDisplayInRect:newFrameRect];
1603 }
1604
1605 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1606 {
1607 NSView *nsview = GetNSViewForSuperview();
1608 NSView *superview = [nsview superview];
1609 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
1610 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1611 NSRect frameRect = [nsview frame];
1612 if(size.x!=-1)
1613 frameRect.size.width = size.x;
1614 if(size.y!=-1)
1615 frameRect.size.height = size.y;
1616 frameRect.origin.x = pos.x;
1617 frameRect.origin.y = pos.y;
1618 // Tell Cocoa to change the margin between the bottom of the superview
1619 // and the bottom of the control. Keeps the control pinned to the top
1620 // of its superview so that its position in the wxWidgets coordinate
1621 // system doesn't change.
1622 if(![superview isFlipped])
1623 [nsview setAutoresizingMask: NSViewMinYMargin];
1624 // MUST set the mask before setFrame: which can generate a size event
1625 // and cause a scroller to be added!
1626 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1627 [nsview setFrame: frameRect];
1628 }
1629
1630 // Get total size
1631 void wxWindow::DoGetSize(int *w, int *h) const
1632 {
1633 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1634 if(w)
1635 *w=(int)cocoaRect.size.width;
1636 if(h)
1637 *h=(int)cocoaRect.size.height;
1638 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1639 }
1640
1641 void wxWindow::DoGetPosition(int *x, int *y) const
1642 {
1643 NSView *nsview = GetNSViewForSuperview();
1644
1645 NSRect cocoaRect = [nsview frame];
1646 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1647 if(x)
1648 *x=(int)rectWx.origin.x;
1649 if(y)
1650 *y=(int)rectWx.origin.y;
1651 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1652 }
1653
1654 WXWidget wxWindow::GetHandle() const
1655 {
1656 return m_cocoaNSView;
1657 }
1658
1659 wxWindow* wxWindow::GetWxWindow() const
1660 {
1661 return (wxWindow*) this;
1662 }
1663
1664 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1665 {
1666 [m_cocoaNSView setNeedsDisplay:YES];
1667 }
1668
1669 void wxWindow::SetFocus()
1670 {
1671 if([GetNSView() acceptsFirstResponder])
1672 [[GetNSView() window] makeFirstResponder: GetNSView()];
1673 }
1674
1675 void wxWindow::DoCaptureMouse()
1676 {
1677 // TODO
1678 sm_capturedWindow = this;
1679 }
1680
1681 void wxWindow::DoReleaseMouse()
1682 {
1683 // TODO
1684 sm_capturedWindow = NULL;
1685 }
1686
1687 void wxWindow::DoScreenToClient(int *x, int *y) const
1688 {
1689 // Point in cocoa screen coordinates:
1690 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1691 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1692 NSWindow *theWindow = [clientView window];
1693
1694 // Point in window's base coordinate system:
1695 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1696 // Point in view's bounds coordinate system
1697 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1698 // Point in wx client coordinates:
1699 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1700 if(x!=NULL)
1701 *x = theWxClientPoint.x;
1702 if(y!=NULL)
1703 *y = theWxClientPoint.y;
1704 }
1705
1706 void wxWindow::DoClientToScreen(int *x, int *y) const
1707 {
1708 // Point in wx client coordinates
1709 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1710
1711 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1712
1713 // Point in the view's bounds coordinate system
1714 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1715
1716 // Point in the window's base coordinate system
1717 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1718
1719 NSWindow *theWindow = [clientView window];
1720 // Point in Cocoa's screen coordinates
1721 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1722
1723 // Act as though this was the origin of a 0x0 rectangle
1724 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1725
1726 // Convert that rectangle to wx coordinates
1727 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1728 if(*x)
1729 *x = theWxScreenPoint.x;
1730 if(*y)
1731 *y = theWxScreenPoint.y;
1732 }
1733
1734 // Get size *available for subwindows* i.e. excluding menu bar etc.
1735 void wxWindow::DoGetClientSize(int *x, int *y) const
1736 {
1737 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1738 if(m_wxCocoaScrollView)
1739 m_wxCocoaScrollView->DoGetClientSize(x,y);
1740 else
1741 wxWindowCocoa::DoGetSize(x,y);
1742 }
1743
1744 void wxWindow::DoSetClientSize(int width, int height)
1745 {
1746 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1747 if(m_wxCocoaScrollView)
1748 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1749 CocoaSetWxWindowSize(width,height);
1750 }
1751
1752 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1753 {
1754 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1755 }
1756
1757 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1758 {
1759 // Intentional no-op.
1760 }
1761
1762 wxString wxWindow::GetLabel() const
1763 {
1764 // General Get/Set of labels is implemented in wxControlBase
1765 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1766 return wxEmptyString;
1767 }
1768
1769 int wxWindow::GetCharHeight() const
1770 {
1771 // TODO
1772 return 10;
1773 }
1774
1775 int wxWindow::GetCharWidth() const
1776 {
1777 // TODO
1778 return 5;
1779 }
1780
1781 void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
1782 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
1783 {
1784 // FIXME: This obviously ignores the window's font (if any) along with any size
1785 // transformations. However, it's better than nothing.
1786 // We don't create a wxClientDC because we don't want to accidently be able to use
1787 // it for drawing.
1788 wxClientDC tmpdc(const_cast<wxWindow*>(this));
1789 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
1790 }
1791
1792 // Coordinates relative to the window
1793 void wxWindow::WarpPointer (int x_pos, int y_pos)
1794 {
1795 // TODO
1796 }
1797
1798 int wxWindow::GetScrollPos(int orient) const
1799 {
1800 if(m_wxCocoaScrollView != NULL)
1801 return m_wxCocoaScrollView->GetScrollPos(static_cast<wxOrientation>(orient & wxBOTH));
1802 else
1803 return 0;
1804 }
1805
1806 // This now returns the whole range, not just the number
1807 // of positions that we can scroll.
1808 int wxWindow::GetScrollRange(int orient) const
1809 {
1810 if(m_wxCocoaScrollView != NULL)
1811 return m_wxCocoaScrollView->GetScrollRange(static_cast<wxOrientation>(orient & wxBOTH));
1812 else
1813 return 0;
1814 }
1815
1816 int wxWindow::GetScrollThumb(int orient) const
1817 {
1818 if(m_wxCocoaScrollView != NULL)
1819 return m_wxCocoaScrollView->GetScrollThumb(static_cast<wxOrientation>(orient & wxBOTH));
1820 else
1821 return 0;
1822 }
1823
1824 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1825 {
1826 if(m_wxCocoaScrollView != NULL)
1827 return m_wxCocoaScrollView->SetScrollPos(static_cast<wxOrientation>(orient & wxBOTH), pos);
1828 }
1829
1830 void wxWindow::CocoaCreateNSScrollView()
1831 {
1832 if(!m_wxCocoaScrollView)
1833 {
1834 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1835 }
1836 }
1837
1838 // New function that will replace some of the above.
1839 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1840 int range, bool refresh)
1841 {
1842 CocoaCreateNSScrollView();
1843 m_wxCocoaScrollView->SetScrollbar(orient, pos, thumbVisible, range);
1844 // TODO: Handle refresh (if we even need to)
1845 }
1846
1847 // Does a physical scroll
1848 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1849 {
1850 if(m_wxCocoaScrollView != NULL)
1851 m_wxCocoaScrollView->ScrollWindow(dx, dy, rect);
1852 }
1853
1854 void wxWindow::DoSetVirtualSize( int x, int y )
1855 {
1856 // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
1857 // possibly not what the caller passed in. For example, the current implementation
1858 // clamps the width and height to within the min/max virtual ranges.
1859 // wxDefaultCoord is passed through unchanged but then GetVirtualSize() will correct
1860 // that by returning effectively max(virtual, client)
1861 wxWindowBase::DoSetVirtualSize(x,y);
1862 // Create the scroll view if it hasn't been already.
1863 CocoaCreateNSScrollView();
1864
1865 // The GetVirtualSize automatically increases the size to max(client,virtual)
1866 m_wxCocoaScrollView->UpdateSizes();
1867 }
1868
1869 bool wxWindow::SetFont(const wxFont& font)
1870 {
1871 // FIXME: We may need to handle wx font inheritance.
1872 return wxWindowBase::SetFont(font);
1873 }
1874
1875 #if 0 // these are used when debugging the algorithm.
1876 static char const * const comparisonresultStrings[] =
1877 { "<"
1878 , "=="
1879 , ">"
1880 };
1881 #endif
1882
1883 class CocoaWindowCompareContext
1884 {
1885 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1886 public:
1887 CocoaWindowCompareContext(); // Not implemented
1888 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1889 {
1890 m_target = target;
1891 // Cocoa sorts subviews in-place.. make a copy
1892 m_subviews = [subviews copy];
1893 }
1894 ~CocoaWindowCompareContext()
1895 { // release the copy
1896 [m_subviews release];
1897 }
1898 NSView* target()
1899 { return m_target; }
1900 NSArray* subviews()
1901 { return m_subviews; }
1902 /* Helper function that returns the comparison based off of the original ordering */
1903 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1904 {
1905 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1906 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1907 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1908 // likely compare higher than the other view which is reasonable considering the only way that
1909 // can happen is if the subview was added after our call to subviews but before the call to
1910 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1911 // that case should never occur anyway because that would imply a multi-threaded GUI call
1912 // which is a big no-no with Cocoa.
1913
1914 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1915 NSComparisonResult result = (firstI < secondI)
1916 ? NSOrderedAscending /* -1 */
1917 : (firstI > secondI)
1918 ? NSOrderedDescending /* 1 */
1919 : NSOrderedSame /* 0 */;
1920
1921 #if 0 // Enable this if you need to debug the algorithm.
1922 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1923 #endif
1924 return result;
1925 }
1926 private:
1927 /* The subview we are trying to Raise or Lower */
1928 NSView *m_target;
1929 /* A copy of the original array of subviews */
1930 NSArray *m_subviews;
1931 };
1932
1933 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1934 * the target view is always higher than every other view. When comparing two views neither of
1935 * which is the target, it returns the correct response based on the original ordering
1936 */
1937 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1938 {
1939 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1940 // first should be ordered higher
1941 if(first==compareContext->target())
1942 return NSOrderedDescending;
1943 // second should be ordered higher
1944 if(second==compareContext->target())
1945 return NSOrderedAscending;
1946 return compareContext->CompareUsingOriginalOrdering(first,second);
1947 }
1948
1949 // Raise the window to the top of the Z order
1950 void wxWindow::Raise()
1951 {
1952 // wxAutoNSAutoreleasePool pool;
1953 NSView *nsview = GetNSViewForSuperview();
1954 NSView *superview = [nsview superview];
1955 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1956
1957 [superview sortSubviewsUsingFunction:
1958 CocoaRaiseWindowCompareFunction
1959 context: &compareContext];
1960 }
1961
1962 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1963 * the target view is always lower than every other view. When comparing two views neither of
1964 * which is the target, it returns the correct response based on the original ordering
1965 */
1966 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1967 {
1968 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1969 // first should be ordered lower
1970 if(first==compareContext->target())
1971 return NSOrderedAscending;
1972 // second should be ordered lower
1973 if(second==compareContext->target())
1974 return NSOrderedDescending;
1975 return compareContext->CompareUsingOriginalOrdering(first,second);
1976 }
1977
1978 // Lower the window to the bottom of the Z order
1979 void wxWindow::Lower()
1980 {
1981 NSView *nsview = GetNSViewForSuperview();
1982 NSView *superview = [nsview superview];
1983 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1984
1985 #if 0
1986 NSLog(@"Target:\n%@\n", nsview);
1987 NSLog(@"Before:\n%@\n", compareContext.subviews());
1988 #endif
1989 [superview sortSubviewsUsingFunction:
1990 CocoaLowerWindowCompareFunction
1991 context: &compareContext];
1992 #if 0
1993 NSLog(@"After:\n%@\n", [superview subviews]);
1994 #endif
1995 }
1996
1997 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1998 {
1999 return false;
2000 }
2001
2002 // Get the window with the focus
2003 wxWindow *wxWindowBase::DoFindFocus()
2004 {
2005 // Basically we are somewhat emulating the responder chain here except
2006 // we are only loking for the first responder in the key window or
2007 // upon failing to find one if the main window is different we look
2008 // for the first responder in the main window.
2009
2010 // Note that the firstResponder doesn't necessarily have to be an
2011 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
2012 // NULL unless it finds its argument in its hash map.
2013
2014 wxCocoaNSView *win;
2015
2016 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
2017 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
2018 if(win)
2019 return win->GetWxWindow();
2020
2021 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
2022 if(mainWindow == keyWindow)
2023 return NULL;
2024 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
2025 if(win)
2026 return win->GetWxWindow();
2027
2028 return NULL;
2029 }
2030
2031 /* static */ wxWindow *wxWindowBase::GetCapture()
2032 {
2033 // TODO
2034 return wxWindowCocoa::sm_capturedWindow;
2035 }
2036
2037 wxWindow *wxGetActiveWindow()
2038 {
2039 // TODO
2040 return NULL;
2041 }
2042
2043 wxPoint wxGetMousePosition()
2044 {
2045 // TODO
2046 return wxDefaultPosition;
2047 }
2048
2049 wxMouseState wxGetMouseState()
2050 {
2051 wxMouseState ms;
2052 // TODO
2053 return ms;
2054 }
2055
2056 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
2057 {
2058 pt = wxGetMousePosition();
2059 return NULL;
2060 }
2061
2062 // ========================================================================
2063 // wxCocoaMouseMovedEventSynthesizer
2064 // ========================================================================
2065
2066 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
2067
2068 /* This class registers one run loop observer to cover all windows registered with it.
2069 * It will register the observer when the first view is registerd and unregister the
2070 * observer when the last view is unregistered.
2071 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
2072 * is no reason it couldn't be instantiated multiple times.
2073 */
2074 class wxCocoaMouseMovedEventSynthesizer
2075 {
2076 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
2077 public:
2078 wxCocoaMouseMovedEventSynthesizer()
2079 { m_lastScreenMouseLocation = NSZeroPoint;
2080 }
2081 ~wxCocoaMouseMovedEventSynthesizer();
2082 void RegisterWxCocoaView(wxCocoaNSView *aView);
2083 void UnregisterWxCocoaView(wxCocoaNSView *aView);
2084 void SynthesizeMouseMovedEvent();
2085
2086 protected:
2087 void AddRunLoopObserver();
2088 void RemoveRunLoopObserver();
2089 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
2090 std::list<wxCocoaNSView*> m_registeredViews;
2091 NSPoint m_lastScreenMouseLocation;
2092 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
2093 };
2094
2095 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
2096 {
2097 m_registeredViews.push_back(aView);
2098 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
2099
2100 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
2101 {
2102 AddRunLoopObserver();
2103 }
2104 }
2105
2106 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
2107 {
2108 m_registeredViews.remove(aView);
2109 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
2110 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
2111 {
2112 RemoveRunLoopObserver();
2113 }
2114 }
2115
2116 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
2117 {
2118 if(!m_registeredViews.empty())
2119 {
2120 // This means failure to clean up so we report on it as a debug message.
2121 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
2122 m_registeredViews.clear();
2123 }
2124 if(m_runLoopObserver != NULL)
2125 {
2126 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
2127 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
2128 RemoveRunLoopObserver();
2129 }
2130 }
2131
2132 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
2133 {
2134 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
2135 }
2136
2137 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
2138 {
2139 CFRunLoopObserverContext observerContext =
2140 { 0
2141 , this
2142 , NULL
2143 , NULL
2144 , NULL
2145 };
2146
2147 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
2148 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
2149
2150 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
2151 // in our observer procedure being called before the run loop has decided that it is going to return control to
2152 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
2153 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
2154
2155 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
2156 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
2157 // around to it before letting the event loop do its work. This has some very odd effects on the way the
2158 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
2159 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
2160 // time the new one is built.
2161
2162 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
2163 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
2164 // the mouseExited: message to be sent properly.
2165
2166 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
2167 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2168 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
2169 }
2170
2171 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
2172 {
2173 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2174 m_runLoopObserver.reset();
2175 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
2176 }
2177
2178 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
2179 {
2180 NSPoint screenMouseLocation = [NSEvent mouseLocation];
2181 // Checking the last mouse location is done for a few reasons:
2182 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
2183 // telling the app the mouse moved when the user hit a key for instance.
2184 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
2185 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
2186 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
2187 // loop causing the tracking rectangles to constantly be reset.
2188 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
2189 {
2190 m_lastScreenMouseLocation = screenMouseLocation;
2191 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
2192 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
2193 {
2194 (*i)->Cocoa_synthesizeMouseMoved();
2195 }
2196 }
2197 }
2198
2199 // Singleton used for all views:
2200 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
2201
2202 // ========================================================================
2203 // wxCocoaTrackingRectManager
2204 // ========================================================================
2205
2206 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
2207 : m_window(window)
2208 {
2209 m_isTrackingRectActive = false;
2210 BuildTrackingRect();
2211 }
2212
2213 void wxCocoaTrackingRectManager::ClearTrackingRect()
2214 {
2215 if(m_isTrackingRectActive)
2216 {
2217 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
2218 m_isTrackingRectActive = false;
2219 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2220 }
2221 // If we were doing periodic events we need to clear those too
2222 StopSynthesizingEvents();
2223 }
2224
2225 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
2226 {
2227 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
2228 }
2229
2230 void wxCocoaTrackingRectManager::BuildTrackingRect()
2231 {
2232 // Pool here due to lack of one during wx init phase
2233 wxAutoNSAutoreleasePool pool;
2234
2235 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
2236
2237 NSView *theView = m_window->GetNSView();
2238
2239 if([theView window] != nil)
2240 {
2241 NSRect visibleRect = [theView visibleRect];
2242
2243 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
2244 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
2245 m_isTrackingRectActive = true;
2246
2247 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2248 }
2249 }
2250
2251 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
2252 {
2253 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
2254 }
2255
2256 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
2257 {
2258 if(m_isTrackingRectActive)
2259 {
2260 NSView *theView = m_window->GetNSView();
2261 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
2262 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
2263 {
2264 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
2265 return;
2266 }
2267 }
2268 RebuildTrackingRect();
2269 }
2270
2271 void wxCocoaTrackingRectManager::RebuildTrackingRect()
2272 {
2273 ClearTrackingRect();
2274 BuildTrackingRect();
2275 }
2276
2277 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
2278 {
2279 ClearTrackingRect();
2280 }
2281
2282 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
2283 {
2284 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);
2285 }
2286