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