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