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