]> git.saurik.com Git - wxWidgets.git/blame - src/cocoa/window.mm
When Cocoa sends [tabView:didSelectTabViewItem:] create a page changed event
[wxWidgets.git] / src / cocoa / window.mm
CommitLineData
fb896a32
DE
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/cocoa/window.mm
3// Purpose: wxWindowCocoa
4// Author: David Elliott
5// Modified by:
6// Created: 2002/12/26
8d8d3633 7// RCS-ID: $Id$
fb896a32 8// Copyright: (c) 2002 David Elliott
8d8d3633 9// Licence: wxWidgets licence
fb896a32
DE
10/////////////////////////////////////////////////////////////////////////////
11
449c5673 12#include "wx/wxprec.h"
da80ae71 13
449c5673
DE
14#ifndef WX_PRECOMP
15 #include "wx/log.h"
16 #include "wx/window.h"
4db3c8ac 17 #include "wx/dc.h"
938156b2 18 #include "wx/dcclient.h"
ca5db7b2 19 #include "wx/utils.h"
449c5673 20#endif //WX_PRECOMP
da80ae71 21
e73ae747 22#include "wx/tooltip.h"
fb896a32 23
938156b2 24#include "wx/cocoa/dc.h"
7fc77f30 25#include "wx/cocoa/autorelease.h"
26191790 26#include "wx/cocoa/string.h"
7c5a378f 27#include "wx/cocoa/trackingrectmanager.h"
8ea3a63e 28#include "wx/mac/corefoundation/cfref.h"
6a5c31c2 29#include "wx/cocoa/ObjcRef.h"
7fc77f30 30
369559ca 31#import <Foundation/NSArray.h>
7c5a378f 32#import <Foundation/NSRunLoop.h>
829a2e95 33#include "wx/cocoa/objc/NSView.h"
69dbb709 34#import <AppKit/NSEvent.h>
816c52cf 35#import <AppKit/NSScrollView.h>
618d53b8 36#import <AppKit/NSScroller.h>
816c52cf
DE
37#import <AppKit/NSColor.h>
38#import <AppKit/NSClipView.h>
dc5bcaef 39#import <Foundation/NSException.h>
67d2dac8
DE
40#import <AppKit/NSApplication.h>
41#import <AppKit/NSWindow.h>
15f37147 42#import <AppKit/NSScreen.h>
dc5bcaef 43
75e4856b
DE
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
8ea3a63e
DE
51// STL list used by wxCocoaMouseMovedEventSynthesizer
52#include <list>
53
4799f3ba
DE
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)
61typedef NSComparisonResult CocoaWindowCompareFunctionResult;
62#else
63typedef int CocoaWindowCompareFunctionResult;
64#endif
65
5dc47140
DE
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
15f37147
DE
71// ========================================================================
72// Helper functions for converting to/from wxWidgets coordinates and a
73// specified NSView's coordinate system.
74// ========================================================================
ee022549
DE
75NSPoint 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
87NSRect 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
101NSPoint 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
113NSRect 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
15f37147
DE
127// ============================================================================
128// Screen coordinate helpers
129// ============================================================================
130
131/*
132General observation about Cocoa screen coordinates:
133It is documented that the first object of the [NSScreen screens] array is the screen with the menubar.
134
135It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always
136the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up.
137
138It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle
139relative to that screen's frame. The only real way to test this is to configure two screens one atop
140the other such that the menubar screen is on top. The Dock at the bottom of the screen will then
141eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two
1421920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and
143visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin.
144
145In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen).
146However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme.
147This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen.
148*/
149
150namespace { // file namespace
151
152class wxCocoaPrivateScreenCoordinateTransformer
153{
154 DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer)
155public:
156 wxCocoaPrivateScreenCoordinateTransformer();
157 ~wxCocoaPrivateScreenCoordinateTransformer();
158 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
159 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
160
161protected:
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.
169inline 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
193inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
194{
195 [m_screenZero release];
196 m_screenZero = nil;
197}
198
199inline 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
221inline 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
258wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
259{
260 wxCocoaPrivateScreenCoordinateTransformer transformer;
261 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
262}
263
264NSPoint 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
a82b8141
DE
270// ========================================================================
271// wxWindowCocoaHider
272// ========================================================================
273class wxWindowCocoaHider: protected wxCocoaNSView
274{
275 DECLARE_NO_COPY_CLASS(wxWindowCocoaHider)
276public:
277 wxWindowCocoaHider(wxWindow *owner);
278 virtual ~wxWindowCocoaHider();
279 inline WX_NSView GetNSView() { return m_dummyNSView; }
280protected:
281 wxWindowCocoa *m_owner;
282 WX_NSView m_dummyNSView;
283 virtual void Cocoa_FrameChanged(void);
7c5a378f 284 virtual void Cocoa_synthesizeMouseMoved(void) {}
75e4856b
DE
285#ifdef WXCOCOA_FILL_DUMMY_VIEW
286 virtual bool Cocoa_drawRect(const NSRect& rect);
287#endif //def WXCOCOA_FILL_DUMMY_VIEW
a82b8141
DE
288private:
289 wxWindowCocoaHider();
290};
291
816c52cf 292// ========================================================================
f298b203 293// wxWindowCocoaScrollView
816c52cf 294// ========================================================================
f298b203 295class wxWindowCocoaScrollView: protected wxCocoaNSView
816c52cf 296{
f298b203 297 DECLARE_NO_COPY_CLASS(wxWindowCocoaScrollView)
816c52cf 298public:
f298b203
DE
299 wxWindowCocoaScrollView(wxWindow *owner);
300 virtual ~wxWindowCocoaScrollView();
816c52cf
DE
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();
618d53b8
DE
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
816c52cf
DE
319protected:
320 wxWindowCocoa *m_owner;
321 WX_NSScrollView m_cocoaNSScrollView;
322 virtual void Cocoa_FrameChanged(void);
7c5a378f 323 virtual void Cocoa_synthesizeMouseMoved(void) {}
618d53b8
DE
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;
816c52cf 400private:
f298b203 401 wxWindowCocoaScrollView();
816c52cf
DE
402};
403
75e4856b
DE
404// ========================================================================
405// wxDummyNSView
406// ========================================================================
407@interface wxDummyNSView : NSView
408- (NSView *)hitTest:(NSPoint)aPoint;
409@end
a24aa427 410WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
75e4856b
DE
411
412@implementation wxDummyNSView : NSView
413- (NSView *)hitTest:(NSPoint)aPoint
414{
415 return nil;
416}
417
418@end
a24aa427 419WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
75e4856b 420
a82b8141
DE
421// ========================================================================
422// wxWindowCocoaHider
423// ========================================================================
424wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
425: m_owner(owner)
426{
427 wxASSERT(owner);
428 wxASSERT(owner->GetNSViewForHiding());
a24aa427 429 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
a82b8141 430 initWithFrame:[owner->GetNSViewForHiding() frame]];
75e4856b 431 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
a82b8141
DE
432 AssociateNSView(m_dummyNSView);
433}
434
435wxWindowCocoaHider::~wxWindowCocoaHider()
436{
437 DisassociateNSView(m_dummyNSView);
438 [m_dummyNSView release];
439}
440
441void 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
5558135c 448
75e4856b
DE
449#ifdef WXCOCOA_FILL_DUMMY_VIEW
450bool 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
618d53b8
DE
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
504static 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
592WX_IMPLEMENT_GET_OBJC_CLASS(WXManualScrollView,NSScrollView)
593
816c52cf
DE
594// ========================================================================
595// wxFlippedNSClipView
596// ========================================================================
597@interface wxFlippedNSClipView : NSClipView
598- (BOOL)isFlipped;
599@end
a24aa427 600WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
816c52cf
DE
601
602@implementation wxFlippedNSClipView : NSClipView
603- (BOOL)isFlipped
604{
605 return YES;
606}
607
608@end
a24aa427 609WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
816c52cf
DE
610
611// ========================================================================
f298b203 612// wxWindowCocoaScrollView
816c52cf 613// ========================================================================
f298b203 614wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
816c52cf 615: m_owner(owner)
618d53b8
DE
616, m_cocoaNSScrollView() // nil
617, m_scrollRange() // {0,0}
618, m_scrollThumb() // {0,0}
619, m_virtualOrigin(0,0)
816c52cf 620{
a9861c57 621 wxAutoNSAutoreleasePool pool;
816c52cf
DE
622 wxASSERT(owner);
623 wxASSERT(owner->GetNSView());
618d53b8
DE
624 m_isNativeView = ![owner->GetNSView() isKindOfClass:[WX_GET_OBJC_CLASS(WXNSView) class]];
625 m_cocoaNSScrollView = [(m_isNativeView?[NSScrollView alloc]:[WXManualScrollView alloc])
816c52cf
DE
626 initWithFrame:[owner->GetNSView() frame]];
627 AssociateNSView(m_cocoaNSScrollView);
618d53b8
DE
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 }
816c52cf
DE
645
646 /* Replace the default NSClipView with a flipped one. This ensures
647 scrolling is "pinned" to the top-left instead of bottom-right. */
a24aa427 648 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
816c52cf
DE
649 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
650 [m_cocoaNSScrollView setContentView:flippedClip];
651 [flippedClip release];
652
618d53b8 653 // In all cases we must encapsulate the real NSView properly
816c52cf
DE
654 Encapsulate();
655}
656
f298b203 657void wxWindowCocoaScrollView::Encapsulate()
816c52cf 658{
6f2ec3c3
DE
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];
816c52cf
DE
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
f298b203 671void wxWindowCocoaScrollView::Unencapsulate()
816c52cf
DE
672{
673 [m_cocoaNSScrollView setDocumentView: nil];
674 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
6f2ec3c3
DE
675 if(![[m_owner->GetNSView() superview] isFlipped])
676 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
816c52cf
DE
677}
678
f298b203 679wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
816c52cf
DE
680{
681 DisassociateNSView(m_cocoaNSScrollView);
618d53b8
DE
682 if(!m_isNativeView)
683 {
684 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView:NULL];
685 }
816c52cf
DE
686 [m_cocoaNSScrollView release];
687}
688
f298b203 689void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
816c52cf
DE
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]];
e9cece45
VZ
696 width = (int)frameSize.width;
697 height = (int)frameSize.height;
816c52cf
DE
698}
699
f298b203 700void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
816c52cf
DE
701{
702 NSSize nssize = [m_cocoaNSScrollView contentSize];
703 if(x)
e9cece45 704 *x = (int)nssize.width;
816c52cf 705 if(y)
e9cece45 706 *y = (int)nssize.height;
816c52cf
DE
707}
708
618d53b8
DE
709static 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
736void 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
755void 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
806int 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
847int 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
869int 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 */
932void 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
951void 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);
937013e0 991 m_owner->HandleWindowEvent(event);
618d53b8
DE
992}
993
994void 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
f298b203 1011void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
816c52cf 1012{
618d53b8 1013 wxLogTrace(wxTRACE_COCOA,wxT("wxWindowCocoaScrollView=%p::Cocoa_FrameChanged for wxWindow %p"), this, m_owner);
816c52cf
DE
1014 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
1015 event.SetEventObject(m_owner);
937013e0 1016 m_owner->HandleWindowEvent(event);
618d53b8 1017 UpdateSizes();
816c52cf
DE
1018}
1019
a82b8141
DE
1020// ========================================================================
1021// wxWindowCocoa
1022// ========================================================================
fb896a32
DE
1023// normally the base classes aren't included, but wxWindow is special
1024#ifdef __WXUNIVERSAL__
1025IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
1026#else
1027IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
1028#endif
1029
1030BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
1031END_EVENT_TABLE()
1032
b9505233
DE
1033wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
1034
fb896a32
DE
1035// Constructor
1036void wxWindowCocoa::Init()
1037{
fb896a32 1038 m_cocoaNSView = NULL;
a82b8141 1039 m_cocoaHider = NULL;
f298b203 1040 m_wxCocoaScrollView = NULL;
8d8d3633
WS
1041 m_isBeingDeleted = false;
1042 m_isInPaint = false;
7c5a378f 1043 m_visibleTrackingRectManager = NULL;
fb896a32
DE
1044}
1045
1046// Constructor
1047bool 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
fb896a32 1057 m_cocoaNSView = NULL;
a24aa427 1058 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
fb896a32
DE
1059 [m_cocoaNSView release];
1060
1061 if (m_parent)
1062 {
1063 m_parent->AddChild(this);
1064 m_parent->CocoaAddChild(this);
6d034f7d 1065 SetInitialFrameRect(pos,size);
fb896a32
DE
1066 }
1067
8d8d3633 1068 return true;
fb896a32
DE
1069}
1070
1071// Destructor
1072wxWindow::~wxWindow()
1073{
7fc77f30 1074 wxAutoNSAutoreleasePool pool;
fb896a32
DE
1075 DestroyChildren();
1076
065e208e 1077 // Make sure our parent (in the wxWidgets sense) is our superview
6ba13ca4
DE
1078 // before we go removing from it.
1079 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
1080 CocoaRemoveFromParent();
a82b8141 1081 delete m_cocoaHider;
f298b203 1082 delete m_wxCocoaScrollView;
9c85202a
DE
1083 if(m_cocoaNSView)
1084 SendDestroyEvent();
fb896a32
DE
1085 SetNSView(NULL);
1086}
1087
1088void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
1089{
b0a207df
DE
1090 // Pool here due to lack of one during wx init phase
1091 wxAutoNSAutoreleasePool pool;
1092
a82b8141
DE
1093 NSView *childView = child->GetNSViewForSuperview();
1094
1095 wxASSERT(childView);
1096 [m_cocoaNSView addSubview: childView];
fb896a32
DE
1097}
1098
1099void wxWindowCocoa::CocoaRemoveFromParent(void)
1100{
a82b8141 1101 [GetNSViewForSuperview() removeFromSuperview];
fb896a32
DE
1102}
1103
1104void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
1105{
7c5a378f
DE
1106 // Clear the visible area tracking rect if we have one.
1107 delete m_visibleTrackingRectManager;
1108 m_visibleTrackingRectManager = NULL;
1109
fb896a32 1110 bool need_debug = cocoaNSView || m_cocoaNSView;
48580976 1111 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
bac6f234 1112 DisassociateNSView(m_cocoaNSView);
6a5c31c2
DE
1113 wxGCSafeRetain(cocoaNSView);
1114 wxGCSafeRelease(m_cocoaNSView);
fb896a32 1115 m_cocoaNSView = cocoaNSView;
bac6f234 1116 AssociateNSView(m_cocoaNSView);
48580976 1117 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
fb896a32
DE
1118}
1119
a82b8141
DE
1120WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
1121{
1122 return m_cocoaHider
1123 ? m_cocoaHider->GetNSView()
f298b203
DE
1124 : m_wxCocoaScrollView
1125 ? m_wxCocoaScrollView->GetNSScrollView()
816c52cf 1126 : m_cocoaNSView;
a82b8141
DE
1127}
1128
1129WX_NSView wxWindowCocoa::GetNSViewForHiding() const
1130{
f298b203
DE
1131 return m_wxCocoaScrollView
1132 ? m_wxCocoaScrollView->GetNSScrollView()
816c52cf 1133 : m_cocoaNSView;
a82b8141
DE
1134}
1135
34c9978d
DE
1136NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
1137{
1138 // TODO: Handle scrolling offset
ee022549 1139 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
34c9978d
DE
1140}
1141
1142NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
1143{
1144 // TODO: Handle scrolling offset
ee022549 1145 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
34c9978d
DE
1146}
1147
1148NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
1149{
1150 // TODO: Handle scrolling offset
ee022549 1151 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
34c9978d
DE
1152}
1153
1154NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
1155{
1156 // TODO: Handle scrolling offset
ee022549 1157 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
34c9978d
DE
1158}
1159
4db3c8ac
DE
1160WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
1161{
1162 // TODO: Handle scrolling offset
938156b2 1163 NSAffineTransform *transform = wxCocoaDCImpl::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
4db3c8ac
DE
1164 return transform;
1165}
1166
8ea5271e
DE
1167bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
1168{
48580976 1169 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
55c5be5e
DE
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 {
2b030203 1175 wxLogDebug(wxT("Paint event recursion!"));
55c5be5e
DE
1176 return false;
1177 }
8d8d3633 1178 m_isInPaint = true;
dc5bcaef
DE
1179
1180 // Set m_updateRegion
1181 const NSRect *rects = &rect; // The bounding box of the region
4799f3ba 1182 NSInteger countRects = 1;
dc5bcaef 1183 // Try replacing the larger rectangle with a list of smaller ones:
5dc47140
DE
1184 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
1185 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
34c9978d
DE
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);
dc5bcaef 1194
8ea5271e
DE
1195 wxPaintEvent event(m_windowId);
1196 event.SetEventObject(this);
937013e0 1197 bool ret = HandleWindowEvent(event);
8d8d3633 1198 m_isInPaint = false;
55c5be5e 1199 return ret;
8ea5271e
DE
1200}
1201
69dbb709
DE
1202void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
1203{
2b030203 1204 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
34c9978d
DE
1205 // Mouse events happen at the NSWindow level so we need to convert
1206 // into our bounds coordinates then convert to wx coordinates.
a82b8141 1207 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
a4ee5059
DE
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
34c9978d
DE
1246 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1247 // FIXME: Should we be adjusting for client area origin?
69dbb709 1248 const wxPoint &clientorigin = GetClientAreaOrigin();
34c9978d
DE
1249 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1250 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
69dbb709
DE
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
1262bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
1263{
1264 wxMouseEvent event(wxEVT_MOTION);
1265 InitMouseEvent(event,theEvent);
7c5a378f 1266 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
937013e0 1267 return HandleWindowEvent(event);
69dbb709
DE
1268}
1269
7c5a378f
DE
1270void 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);
937013e0 1288 HandleWindowEvent(event);
7c5a378f
DE
1289}
1290
69dbb709
DE
1291bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
1292{
7c5a378f
DE
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);
18b32eb5 1305 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
937013e0 1306 return HandleWindowEvent(event);
7c5a378f
DE
1307 }
1308 else
1309 return false;
69dbb709
DE
1310}
1311
1312bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
1313{
7c5a378f
DE
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);
18b32eb5 1320 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
937013e0 1321 return HandleWindowEvent(event);
7c5a378f
DE
1322 }
1323 else
1324 return false;
69dbb709
DE
1325}
1326
1327bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
1328{
1329 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
1330 InitMouseEvent(event,theEvent);
48580976 1331 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
937013e0 1332 return HandleWindowEvent(event);
69dbb709
DE
1333}
1334
1335bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
1336{
1337 wxMouseEvent event(wxEVT_MOTION);
1338 InitMouseEvent(event,theEvent);
1339 event.m_leftDown = true;
48580976 1340 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
937013e0 1341 return HandleWindowEvent(event);
69dbb709
DE
1342}
1343
1344bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
1345{
1346 wxMouseEvent event(wxEVT_LEFT_UP);
1347 InitMouseEvent(event,theEvent);
48580976 1348 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
937013e0 1349 return HandleWindowEvent(event);
69dbb709
DE
1350}
1351
1352bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
1353{
eafde5c7
DE
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]);
937013e0 1357 return HandleWindowEvent(event);
69dbb709
DE
1358}
1359
1360bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
1361{
eafde5c7
DE
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);
937013e0 1366 return HandleWindowEvent(event);
69dbb709
DE
1367}
1368
1369bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
1370{
eafde5c7
DE
1371 wxMouseEvent event(wxEVT_RIGHT_UP);
1372 InitMouseEvent(event,theEvent);
1373 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
937013e0 1374 return HandleWindowEvent(event);
69dbb709
DE
1375}
1376
1377bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
1378{
1379 return false;
1380}
1381
1382bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
1383{
1384 return false;
1385}
1386
1387bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
1388{
1389 return false;
1390}
1391
fb896a32
DE
1392void wxWindowCocoa::Cocoa_FrameChanged(void)
1393{
618d53b8
DE
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);
937013e0 1407 HandleWindowEvent(event);
618d53b8
DE
1408 }
1409 else
1410 {
1411 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged ignored"),this);
1412 }
fb896a32
DE
1413}
1414
5558135c
RN
1415bool wxWindowCocoa::Cocoa_resetCursorRects()
1416{
7c5a378f 1417 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
ea29564c
DE
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
7c5a378f 1431 if(m_visibleTrackingRectManager != NULL)
ea29564c 1432 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
7c5a378f 1433
5558135c
RN
1434 if(!m_cursor.GetNSCursor())
1435 return false;
8d8d3633
WS
1436
1437 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1438
5558135c
RN
1439 return true;
1440}
1441
a8780ad5
DE
1442bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
1443{
1444 if(!wxWindowBase::SetCursor(cursor))
1445 return false;
6ebbf01f
DE
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
a8780ad5 1452 // Invalidate the cursor rects so the cursor will change
6ebbf01f
DE
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.
a8780ad5
DE
1455 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
1456 return true;
1457}
1458
7c5a378f
DE
1459bool 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
1468bool 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
fb896a32
DE
1477bool wxWindow::Close(bool force)
1478{
cc6f960f
DE
1479 // The only reason this function exists is that it is virtual and
1480 // wxTopLevelWindowCocoa will override it.
1481 return wxWindowBase::Close(force);
fb896a32
DE
1482}
1483
a82b8141
DE
1484void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
1485{
1486 [[oldView superview] replaceSubview:oldView with:newView];
1487}
1488
47a8a4d5 1489void wxWindow::DoEnable(bool enable)
adb4816c 1490{
47a8a4d5 1491 CocoaSetEnabled(enable);
adb4816c
DE
1492}
1493
fb896a32
DE
1494bool wxWindow::Show(bool show)
1495{
7fc77f30 1496 wxAutoNSAutoreleasePool pool;
fb896a32
DE
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
addbdd29 1499 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
2b030203 1500// wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
fb896a32 1501 // Return false if there isn't a window to show or hide
a82b8141
DE
1502 NSView *cocoaView = GetNSViewForHiding();
1503 if(!cocoaView)
fb896a32 1504 return false;
fb896a32
DE
1505 if(show)
1506 {
addbdd29 1507 // If state isn't changing, return false
a82b8141 1508 if(!m_cocoaHider)
addbdd29 1509 return false;
a82b8141
DE
1510 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
1511 wxASSERT(![m_cocoaHider->GetNSView() superview]);
1512 delete m_cocoaHider;
1513 m_cocoaHider = NULL;
1514 wxASSERT([cocoaView superview]);
fb896a32
DE
1515 }
1516 else
1517 {
addbdd29 1518 // If state isn't changing, return false
a82b8141 1519 if(m_cocoaHider)
addbdd29 1520 return false;
a82b8141
DE
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());
fb896a32 1525 // m_coocaNSView is now only retained by us
a82b8141
DE
1526 wxASSERT([m_cocoaHider->GetNSView() superview]);
1527 wxASSERT(![cocoaView superview]);
fb896a32 1528 }
a6b4ff2e
DE
1529 m_isShown = show;
1530 return true;
fb896a32
DE
1531}
1532
1533void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
1534{
9879fa84 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":".");
fb896a32
DE
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
8d8d3633 1547 wxSize size(wxDefaultSize);
fb896a32
DE
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
1e151594 1573#if wxUSE_TOOLTIPS
26191790
RN
1574
1575void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
1576{
1577 wxWindowBase::DoSetToolTip(tip);
1578
26191790
RN
1579 if ( m_tooltip )
1580 {
1581 m_tooltip->SetWindow((wxWindow *)this);
26191790
RN
1582 }
1583}
1584
1e151594
RN
1585#endif
1586
fb896a32
DE
1587void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
1588{
bed6fe0c 1589 wxAutoNSAutoreleasePool pool;
9879fa84 1590 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
fb896a32 1591
a82b8141 1592 NSView *nsview = GetNSViewForSuperview();
d449cf47 1593 NSView *superview = [nsview superview];
fb896a32 1594
34c9978d
DE
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];
b915b805 1600 // Be sure to redraw the parent to reflect the changed position
34c9978d
DE
1601 [superview setNeedsDisplayInRect:oldFrameRect];
1602 [superview setNeedsDisplayInRect:newFrameRect];
fb896a32
DE
1603}
1604
d139c3a8
DE
1605void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1606{
1607 NSView *nsview = GetNSViewForSuperview();
1608 NSView *superview = [nsview superview];
2b030203 1609 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
34c9978d 1610 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
d139c3a8
DE
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;
34c9978d 1617 frameRect.origin.y = pos.y;
c5bd9191
DE
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
065e208e 1620 // of its superview so that its position in the wxWidgets coordinate
c5bd9191
DE
1621 // system doesn't change.
1622 if(![superview isFlipped])
1623 [nsview setAutoresizingMask: NSViewMinYMargin];
6f2ec3c3
DE
1624 // MUST set the mask before setFrame: which can generate a size event
1625 // and cause a scroller to be added!
34c9978d 1626 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
6f2ec3c3 1627 [nsview setFrame: frameRect];
d139c3a8
DE
1628}
1629
fb896a32
DE
1630// Get total size
1631void wxWindow::DoGetSize(int *w, int *h) const
1632{
a82b8141 1633 NSRect cocoaRect = [GetNSViewForSuperview() frame];
fb896a32
DE
1634 if(w)
1635 *w=(int)cocoaRect.size.width;
1636 if(h)
1637 *h=(int)cocoaRect.size.height;
9879fa84 1638 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
fb896a32
DE
1639}
1640
1641void wxWindow::DoGetPosition(int *x, int *y) const
1642{
a82b8141 1643 NSView *nsview = GetNSViewForSuperview();
fb896a32 1644
576a1544 1645 NSRect cocoaRect = [nsview frame];
34c9978d 1646 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
fb896a32 1647 if(x)
34c9978d 1648 *x=(int)rectWx.origin.x;
fb896a32 1649 if(y)
34c9978d 1650 *y=(int)rectWx.origin.y;
9879fa84 1651 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
fb896a32
DE
1652}
1653
1654WXWidget wxWindow::GetHandle() const
1655{
1656 return m_cocoaNSView;
1657}
1658
f7e98dee
RN
1659wxWindow* wxWindow::GetWxWindow() const
1660{
1661 return (wxWindow*) this;
1662}
1663
ddf7346a
DE
1664void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1665{
1666 [m_cocoaNSView setNeedsDisplay:YES];
1667}
1668
fb896a32
DE
1669void wxWindow::SetFocus()
1670{
67d2dac8
DE
1671 if([GetNSView() acceptsFirstResponder])
1672 [[GetNSView() window] makeFirstResponder: GetNSView()];
fb896a32
DE
1673}
1674
1675void wxWindow::DoCaptureMouse()
1676{
1677 // TODO
b9505233 1678 sm_capturedWindow = this;
fb896a32
DE
1679}
1680
1681void wxWindow::DoReleaseMouse()
1682{
1683 // TODO
b9505233 1684 sm_capturedWindow = NULL;
fb896a32
DE
1685}
1686
1687void wxWindow::DoScreenToClient(int *x, int *y) const
1688{
15f37147
DE
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;
fb896a32
DE
1704}
1705
1706void wxWindow::DoClientToScreen(int *x, int *y) const
1707{
15f37147
DE
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;
fb896a32
DE
1732}
1733
1734// Get size *available for subwindows* i.e. excluding menu bar etc.
1735void wxWindow::DoGetClientSize(int *x, int *y) const
1736{
48580976 1737 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
f298b203
DE
1738 if(m_wxCocoaScrollView)
1739 m_wxCocoaScrollView->DoGetClientSize(x,y);
816c52cf
DE
1740 else
1741 wxWindowCocoa::DoGetSize(x,y);
fb896a32
DE
1742}
1743
1744void wxWindow::DoSetClientSize(int width, int height)
1745{
48580976 1746 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
f298b203
DE
1747 if(m_wxCocoaScrollView)
1748 m_wxCocoaScrollView->ClientSizeToSize(width,height);
e08efb8d
DE
1749 CocoaSetWxWindowSize(width,height);
1750}
1751
1752void wxWindow::CocoaSetWxWindowSize(int width, int height)
1753{
8d8d3633
WS
1754 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1755}
1756
1757void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1758{
ba64d0b6 1759 // Intentional no-op.
8d8d3633
WS
1760}
1761
1762wxString wxWindow::GetLabel() const
1763{
ba64d0b6
DE
1764 // General Get/Set of labels is implemented in wxControlBase
1765 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
8d8d3633 1766 return wxEmptyString;
fb896a32
DE
1767}
1768
1769int wxWindow::GetCharHeight() const
1770{
1771 // TODO
d89e391b 1772 return 10;
fb896a32
DE
1773}
1774
1775int wxWindow::GetCharWidth() const
1776{
1777 // TODO
d89e391b 1778 return 5;
fb896a32
DE
1779}
1780
d3978051
DE
1781void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
1782 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
fb896a32 1783{
d3978051
DE
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.
938156b2 1788 wxClientDC tmpdc(const_cast<wxWindow*>(this));
d3978051 1789 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
fb896a32
DE
1790}
1791
fb896a32
DE
1792// Coordinates relative to the window
1793void wxWindow::WarpPointer (int x_pos, int y_pos)
1794{
1795 // TODO
1796}
1797
1798int wxWindow::GetScrollPos(int orient) const
1799{
618d53b8
DE
1800 if(m_wxCocoaScrollView != NULL)
1801 return m_wxCocoaScrollView->GetScrollPos(static_cast<wxOrientation>(orient & wxBOTH));
1802 else
1803 return 0;
fb896a32
DE
1804}
1805
1806// This now returns the whole range, not just the number
1807// of positions that we can scroll.
1808int wxWindow::GetScrollRange(int orient) const
1809{
618d53b8
DE
1810 if(m_wxCocoaScrollView != NULL)
1811 return m_wxCocoaScrollView->GetScrollRange(static_cast<wxOrientation>(orient & wxBOTH));
1812 else
1813 return 0;
fb896a32
DE
1814}
1815
1816int wxWindow::GetScrollThumb(int orient) const
1817{
618d53b8
DE
1818 if(m_wxCocoaScrollView != NULL)
1819 return m_wxCocoaScrollView->GetScrollThumb(static_cast<wxOrientation>(orient & wxBOTH));
1820 else
1821 return 0;
fb896a32
DE
1822}
1823
1824void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1825{
618d53b8
DE
1826 if(m_wxCocoaScrollView != NULL)
1827 return m_wxCocoaScrollView->SetScrollPos(static_cast<wxOrientation>(orient & wxBOTH), pos);
fb896a32
DE
1828}
1829
816c52cf
DE
1830void wxWindow::CocoaCreateNSScrollView()
1831{
f298b203 1832 if(!m_wxCocoaScrollView)
816c52cf 1833 {
f298b203 1834 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
816c52cf
DE
1835 }
1836}
1837
fb896a32
DE
1838// New function that will replace some of the above.
1839void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1840 int range, bool refresh)
1841{
e08efb8d 1842 CocoaCreateNSScrollView();
618d53b8
DE
1843 m_wxCocoaScrollView->SetScrollbar(orient, pos, thumbVisible, range);
1844 // TODO: Handle refresh (if we even need to)
fb896a32
DE
1845}
1846
1847// Does a physical scroll
1848void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1849{
618d53b8
DE
1850 if(m_wxCocoaScrollView != NULL)
1851 m_wxCocoaScrollView->ScrollWindow(dx, dy, rect);
d3978051
DE
1852}
1853
816c52cf
DE
1854void wxWindow::DoSetVirtualSize( int x, int y )
1855{
d3978051
DE
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.
618d53b8
DE
1859 // wxDefaultCoord is passed through unchanged but then GetVirtualSize() will correct
1860 // that by returning effectively max(virtual, client)
816c52cf 1861 wxWindowBase::DoSetVirtualSize(x,y);
d3978051 1862 // Create the scroll view if it hasn't been already.
816c52cf 1863 CocoaCreateNSScrollView();
618d53b8
DE
1864
1865 // The GetVirtualSize automatically increases the size to max(client,virtual)
1866 m_wxCocoaScrollView->UpdateSizes();
816c52cf
DE
1867}
1868
fb896a32
DE
1869bool wxWindow::SetFont(const wxFont& font)
1870{
e7e97a59
DE
1871 // FIXME: We may need to handle wx font inheritance.
1872 return wxWindowBase::SetFont(font);
fb896a32
DE
1873}
1874
aa25b7f9
DE
1875#if 0 // these are used when debugging the algorithm.
1876static char const * const comparisonresultStrings[] =
1877{ "<"
1878, "=="
1879, ">"
1880};
1881#endif
1882
1883class CocoaWindowCompareContext
1884{
1885 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1886public:
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 }
1926private:
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 */
1937static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
4328a6ba 1938{
aa25b7f9 1939 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
4328a6ba 1940 // first should be ordered higher
aa25b7f9 1941 if(first==compareContext->target())
4328a6ba
DE
1942 return NSOrderedDescending;
1943 // second should be ordered higher
aa25b7f9 1944 if(second==compareContext->target())
4328a6ba 1945 return NSOrderedAscending;
aa25b7f9 1946 return compareContext->CompareUsingOriginalOrdering(first,second);
4328a6ba
DE
1947}
1948
fb896a32
DE
1949// Raise the window to the top of the Z order
1950void wxWindow::Raise()
1951{
4328a6ba 1952// wxAutoNSAutoreleasePool pool;
a82b8141 1953 NSView *nsview = GetNSViewForSuperview();
aa25b7f9
DE
1954 NSView *superview = [nsview superview];
1955 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1956
1957 [superview sortSubviewsUsingFunction:
4328a6ba 1958 CocoaRaiseWindowCompareFunction
aa25b7f9 1959 context: &compareContext];
4328a6ba
DE
1960}
1961
aa25b7f9
DE
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 */
1966static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
4328a6ba 1967{
aa25b7f9 1968 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
4328a6ba 1969 // first should be ordered lower
aa25b7f9 1970 if(first==compareContext->target())
4328a6ba
DE
1971 return NSOrderedAscending;
1972 // second should be ordered lower
aa25b7f9 1973 if(second==compareContext->target())
4328a6ba 1974 return NSOrderedDescending;
aa25b7f9 1975 return compareContext->CompareUsingOriginalOrdering(first,second);
fb896a32
DE
1976}
1977
1978// Lower the window to the bottom of the Z order
1979void wxWindow::Lower()
1980{
4328a6ba 1981 NSView *nsview = GetNSViewForSuperview();
aa25b7f9
DE
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:
4328a6ba 1990 CocoaLowerWindowCompareFunction
aa25b7f9
DE
1991 context: &compareContext];
1992#if 0
1993 NSLog(@"After:\n%@\n", [superview subviews]);
1994#endif
fb896a32
DE
1995}
1996
1997bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1998{
8d8d3633 1999 return false;
fb896a32
DE
2000}
2001
2002// Get the window with the focus
dcb68102 2003wxWindow *wxWindowBase::DoFindFocus()
fb896a32 2004{
67d2dac8
DE
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];
9151dcec 2017 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
67d2dac8
DE
2018 if(win)
2019 return win->GetWxWindow();
2020
2021 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
2022 if(mainWindow == keyWindow)
f7e98dee 2023 return NULL;
9151dcec 2024 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
67d2dac8
DE
2025 if(win)
2026 return win->GetWxWindow();
2027
2028 return NULL;
fb896a32
DE
2029}
2030
2031/* static */ wxWindow *wxWindowBase::GetCapture()
2032{
2033 // TODO
b9505233 2034 return wxWindowCocoa::sm_capturedWindow;
fb896a32
DE
2035}
2036
2037wxWindow *wxGetActiveWindow()
2038{
2039 // TODO
2040 return NULL;
2041}
2042
7c9428ab
DE
2043wxPoint wxGetMousePosition()
2044{
2045 // TODO
2046 return wxDefaultPosition;
2047}
2048
ca5db7b2
WS
2049wxMouseState wxGetMouseState()
2050{
2051 wxMouseState ms;
2052 // TODO
2053 return ms;
2054}
2055
7c9428ab
DE
2056wxWindow* wxFindWindowAtPointer(wxPoint& pt)
2057{
2058 pt = wxGetMousePosition();
2059 return NULL;
2060}
7c5a378f 2061
8ea3a63e
DE
2062// ========================================================================
2063// wxCocoaMouseMovedEventSynthesizer
2064// ========================================================================
2065
18b32eb5
DE
2066#define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
2067
8ea3a63e
DE
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 */
2074class wxCocoaMouseMovedEventSynthesizer
2075{
2076 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
2077public:
2078 wxCocoaMouseMovedEventSynthesizer()
2079 { m_lastScreenMouseLocation = NSZeroPoint;
2080 }
2081 ~wxCocoaMouseMovedEventSynthesizer();
2082 void RegisterWxCocoaView(wxCocoaNSView *aView);
2083 void UnregisterWxCocoaView(wxCocoaNSView *aView);
2084 void SynthesizeMouseMovedEvent();
2085
2086protected:
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
2095void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
2096{
2097 m_registeredViews.push_back(aView);
18b32eb5 2098 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
8ea3a63e
DE
2099
2100 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
2101 {
2102 AddRunLoopObserver();
2103 }
2104}
2105
2106void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
2107{
2108 m_registeredViews.remove(aView);
18b32eb5 2109 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
8ea3a63e
DE
2110 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
2111 {
2112 RemoveRunLoopObserver();
2113 }
2114}
2115
2116wxCocoaMouseMovedEventSynthesizer::~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
2132void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
2133{
2134 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
2135}
2136
2137void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
2138{
2139 CFRunLoopObserverContext observerContext =
2140 { 0
2141 , this
2142 , NULL
2143 , NULL
2144 , NULL
2145 };
1f361be2
DE
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));
8ea3a63e
DE
2167 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2168 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
2169}
2170
2171void 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
2178void 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:
2200static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
2201
7c5a378f
DE
2202// ========================================================================
2203// wxCocoaTrackingRectManager
2204// ========================================================================
2205
2206wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
2207: m_window(window)
2208{
2209 m_isTrackingRectActive = false;
7c5a378f
DE
2210 BuildTrackingRect();
2211}
2212
2213void wxCocoaTrackingRectManager::ClearTrackingRect()
2214{
2215 if(m_isTrackingRectActive)
2216 {
2217 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
2218 m_isTrackingRectActive = false;
18b32eb5 2219 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
7c5a378f
DE
2220 }
2221 // If we were doing periodic events we need to clear those too
2222 StopSynthesizingEvents();
2223}
2224
2225void wxCocoaTrackingRectManager::StopSynthesizingEvents()
2226{
8ea3a63e 2227 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
7c5a378f
DE
2228}
2229
2230void wxCocoaTrackingRectManager::BuildTrackingRect()
2231{
b0a207df
DE
2232 // Pool here due to lack of one during wx init phase
2233 wxAutoNSAutoreleasePool pool;
2234
7c5a378f 2235 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
18b32eb5
DE
2236
2237 NSView *theView = m_window->GetNSView();
2238
2239 if([theView window] != nil)
7c5a378f 2240 {
ea29564c
DE
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];
7c5a378f 2245 m_isTrackingRectActive = true;
ea29564c 2246
18b32eb5 2247 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
7c5a378f
DE
2248 }
2249}
2250
7c5a378f
DE
2251void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
2252{
8ea3a63e 2253 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
7c5a378f
DE
2254}
2255
ea29564c
DE
2256void 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
7c5a378f
DE
2271void wxCocoaTrackingRectManager::RebuildTrackingRect()
2272{
2273 ClearTrackingRect();
2274 BuildTrackingRect();
2275}
2276
2277wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
2278{
2279 ClearTrackingRect();
2280}
2281
2282bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
2283{
2284 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);
2285}
2286