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