]> git.saurik.com Git - wxWidgets.git/blob - src/cocoa/window.mm
compilation fix after r61232
[wxWidgets.git] / src / cocoa / window.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/cocoa/window.mm
3 // Purpose: wxWindowCocoa
4 // Author: David Elliott
5 // Modified by:
6 // Created: 2002/12/26
7 // RCS-ID: $Id$
8 // Copyright: (c) 2002 David Elliott
9 // Licence: wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #ifndef WX_PRECOMP
15 #include "wx/log.h"
16 #include "wx/window.h"
17 #include "wx/dc.h"
18 #include "wx/dcclient.h"
19 #include "wx/utils.h"
20 #endif //WX_PRECOMP
21
22 #include "wx/tooltip.h"
23
24 #include "wx/cocoa/dc.h"
25 #include "wx/cocoa/autorelease.h"
26 #include "wx/cocoa/string.h"
27 #include "wx/cocoa/trackingrectmanager.h"
28 #include "wx/cocoa/private/scrollview.h"
29 #include "wx/osx/core/cfref.h"
30 #include "wx/cocoa/ObjcRef.h"
31
32 #import <Foundation/NSArray.h>
33 #import <Foundation/NSRunLoop.h>
34 #include "wx/cocoa/objc/NSView.h"
35 #import <AppKit/NSEvent.h>
36 #import <AppKit/NSScrollView.h>
37 #import <AppKit/NSScroller.h>
38 #import <AppKit/NSColor.h>
39 #import <AppKit/NSClipView.h>
40 #import <Foundation/NSException.h>
41 #import <AppKit/NSApplication.h>
42 #import <AppKit/NSWindow.h>
43 #import <AppKit/NSScreen.h>
44
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
52 // STL list used by wxCocoaMouseMovedEventSynthesizer
53 #include <list>
54
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)
62 typedef NSComparisonResult CocoaWindowCompareFunctionResult;
63 #else
64 typedef int CocoaWindowCompareFunctionResult;
65 #endif
66
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
72 // ========================================================================
73 // Helper functions for converting to/from wxWidgets coordinates and a
74 // specified NSView's coordinate system.
75 // ========================================================================
76 NSPoint 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
88 NSRect 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
102 NSPoint 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
114 NSRect 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
128 // ============================================================================
129 // Screen coordinate helpers
130 // ============================================================================
131
132 /*
133 General observation about Cocoa screen coordinates:
134 It is documented that the first object of the [NSScreen screens] array is the screen with the menubar.
135
136 It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always
137 the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up.
138
139 It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle
140 relative to that screen's frame. The only real way to test this is to configure two screens one atop
141 the other such that the menubar screen is on top. The Dock at the bottom of the screen will then
142 eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two
143 1920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and
144 visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin.
145
146 In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen).
147 However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme.
148 This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen.
149 */
150
151 namespace { // file namespace
152
153 class wxCocoaPrivateScreenCoordinateTransformer
154 {
155 wxDECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer);
156 public:
157 wxCocoaPrivateScreenCoordinateTransformer();
158 ~wxCocoaPrivateScreenCoordinateTransformer();
159 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
160 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
161
162 protected:
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.
170 inline 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
194 inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
195 {
196 [m_screenZero release];
197 m_screenZero = nil;
198 }
199
200 inline 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
222 inline 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
259 wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
260 {
261 wxCocoaPrivateScreenCoordinateTransformer transformer;
262 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
263 }
264
265 NSPoint 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
271 // ========================================================================
272 // wxWindowCocoaHider
273 // ========================================================================
274 class wxWindowCocoaHider: protected wxCocoaNSView
275 {
276 wxDECLARE_NO_COPY_CLASS(wxWindowCocoaHider);
277 public:
278 wxWindowCocoaHider(wxWindow *owner);
279 virtual ~wxWindowCocoaHider();
280 inline WX_NSView GetNSView() { return m_dummyNSView; }
281 protected:
282 wxWindowCocoa *m_owner;
283 WX_NSView m_dummyNSView;
284 virtual void Cocoa_FrameChanged(void);
285 virtual void Cocoa_synthesizeMouseMoved(void) {}
286 #ifdef WXCOCOA_FILL_DUMMY_VIEW
287 virtual bool Cocoa_drawRect(const NSRect& rect);
288 #endif //def WXCOCOA_FILL_DUMMY_VIEW
289 private:
290 wxWindowCocoaHider();
291 };
292
293 // ========================================================================
294 // wxDummyNSView
295 // ========================================================================
296 @interface wxDummyNSView : NSView
297 - (NSView *)hitTest:(NSPoint)aPoint;
298 @end
299 WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
300
301 @implementation wxDummyNSView : NSView
302 - (NSView *)hitTest:(NSPoint)aPoint
303 {
304 return nil;
305 }
306
307 @end
308 WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
309
310 // ========================================================================
311 // wxWindowCocoaHider
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.
318 // ========================================================================
319 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
320 : m_owner(owner)
321 {
322 wxASSERT(owner);
323 wxASSERT(owner->GetNSViewForHiding());
324 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
325 initWithFrame:[owner->GetNSViewForHiding() frame]];
326 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
327 AssociateNSView(m_dummyNSView);
328
329 if([m_dummyNSView respondsToSelector:@selector(setHidden:)])
330 [m_dummyNSView setHidden:YES];
331 }
332
333 wxWindowCocoaHider::~wxWindowCocoaHider()
334 {
335 DisassociateNSView(m_dummyNSView);
336 [m_dummyNSView release];
337 }
338
339 void 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
346
347 #ifdef WXCOCOA_FILL_DUMMY_VIEW
348 bool 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
358
359 /*! @class WXManualScrollView
360 @abstract An NSScrollView subclass which implements wx scroll behavior
361 @discussion
362 Overrides default behavior of NSScrollView such that this class receives
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
402 static 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
490 WX_IMPLEMENT_GET_OBJC_CLASS(WXManualScrollView,NSScrollView)
491
492 // ========================================================================
493 // wxFlippedNSClipView
494 // ========================================================================
495 @interface wxFlippedNSClipView : NSClipView
496 - (BOOL)isFlipped;
497 @end
498 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
499
500 @implementation wxFlippedNSClipView : NSClipView
501 - (BOOL)isFlipped
502 {
503 return YES;
504 }
505
506 @end
507 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
508
509 // ========================================================================
510 // wxWindowCocoaScrollView
511 // ========================================================================
512 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
513 : m_owner(owner)
514 , m_cocoaNSScrollView() // nil
515 , m_scrollRange() // {0,0}
516 , m_scrollThumb() // {0,0}
517 , m_virtualOrigin(0,0)
518 {
519 wxAutoNSAutoreleasePool pool;
520 wxASSERT(owner);
521 wxASSERT(owner->GetNSView());
522 m_isNativeView = ![owner->GetNSView() isKindOfClass:[WX_GET_OBJC_CLASS(WXNSView) class]];
523 m_cocoaNSScrollView = [(m_isNativeView?[NSScrollView alloc]:[WXManualScrollView alloc])
524 initWithFrame:[owner->GetNSView() frame]];
525 AssociateNSView(m_cocoaNSScrollView);
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 }
543
544 /* Replace the default NSClipView with a flipped one. This ensures
545 scrolling is "pinned" to the top-left instead of bottom-right. */
546 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
547 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
548 [m_cocoaNSScrollView setContentView:flippedClip];
549 [flippedClip release];
550
551 // In all cases we must encapsulate the real NSView properly
552 Encapsulate();
553 }
554
555 void wxWindowCocoaScrollView::Encapsulate()
556 {
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];
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
569 void wxWindowCocoaScrollView::Unencapsulate()
570 {
571 [m_cocoaNSScrollView setDocumentView: nil];
572 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
573 if(![[m_owner->GetNSView() superview] isFlipped])
574 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
575 }
576
577 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
578 {
579 DisassociateNSView(m_cocoaNSScrollView);
580 if(!m_isNativeView)
581 {
582 [(WXManualScrollView*)m_cocoaNSScrollView _wx_setWxCocoaScrollView:NULL];
583 }
584 [m_cocoaNSScrollView release];
585 }
586
587 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
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]];
594 width = (int)frameSize.width;
595 height = (int)frameSize.height;
596 }
597
598 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
599 {
600 NSSize nssize = [m_cocoaNSScrollView contentSize];
601 if(x)
602 *x = (int)nssize.width;
603 if(y)
604 *y = (int)nssize.height;
605 }
606
607 static 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
634 void 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
653 void 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
704 int 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
738 but GNUStep docs do say that 0.0 is top/left and 1.0 is bottom/right. This is actualy
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
745 int 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
767 int 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 */
830 void 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
849 void 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);
889 m_owner->HandleWindowEvent(event);
890 }
891
892 void 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
909 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
910 {
911 wxLogTrace(wxTRACE_COCOA,wxT("wxWindowCocoaScrollView=%p::Cocoa_FrameChanged for wxWindow %p"), this, m_owner);
912 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
913 event.SetEventObject(m_owner);
914 m_owner->HandleWindowEvent(event);
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();
923 }
924
925 // ========================================================================
926 // wxWindowCocoa
927 // ========================================================================
928 // normally the base classes aren't included, but wxWindow is special
929 #ifdef __WXUNIVERSAL__
930 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
931 #else
932 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
933 #endif
934
935 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
936 END_EVENT_TABLE()
937
938 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
939
940 // Constructor
941 void wxWindowCocoa::Init()
942 {
943 m_cocoaNSView = NULL;
944 m_cocoaHider = NULL;
945 m_wxCocoaScrollView = NULL;
946 m_isInPaint = false;
947 m_visibleTrackingRectManager = NULL;
948 }
949
950 // Constructor
951 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
952 const wxPoint& pos,
953 const wxSize& size,
954 long style,
955 const wxString& name)
956 {
957 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
958 return false;
959
960 // TODO: create the window
961 m_cocoaNSView = NULL;
962 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
963 [m_cocoaNSView release];
964
965 if (m_parent)
966 {
967 m_parent->AddChild(this);
968 m_parent->CocoaAddChild(this);
969 SetInitialFrameRect(pos,size);
970 }
971
972 return true;
973 }
974
975 // Destructor
976 wxWindow::~wxWindow()
977 {
978 wxAutoNSAutoreleasePool pool;
979 DestroyChildren();
980
981 // Make sure our parent (in the wxWidgets sense) is our superview
982 // before we go removing from it.
983 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
984 CocoaRemoveFromParent();
985 delete m_cocoaHider;
986 delete m_wxCocoaScrollView;
987 if(m_cocoaNSView)
988 SendDestroyEvent();
989 SetNSView(NULL);
990 }
991
992 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
993 {
994 // Pool here due to lack of one during wx init phase
995 wxAutoNSAutoreleasePool pool;
996
997 NSView *childView = child->GetNSViewForSuperview();
998
999 wxASSERT(childView);
1000 [m_cocoaNSView addSubview: childView];
1001 }
1002
1003 void wxWindowCocoa::CocoaRemoveFromParent(void)
1004 {
1005 [GetNSViewForSuperview() removeFromSuperview];
1006 }
1007
1008 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
1009 {
1010 // Clear the visible area tracking rect if we have one.
1011 delete m_visibleTrackingRectManager;
1012 m_visibleTrackingRectManager = NULL;
1013
1014 bool need_debug = cocoaNSView || m_cocoaNSView;
1015 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
1016 DisassociateNSView(m_cocoaNSView);
1017 wxGCSafeRetain(cocoaNSView);
1018 wxGCSafeRelease(m_cocoaNSView);
1019 m_cocoaNSView = cocoaNSView;
1020 AssociateNSView(m_cocoaNSView);
1021 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
1022 }
1023
1024 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
1025 {
1026 return m_cocoaHider
1027 ? m_cocoaHider->GetNSView()
1028 : m_wxCocoaScrollView
1029 ? m_wxCocoaScrollView->GetNSScrollView()
1030 : m_cocoaNSView;
1031 }
1032
1033 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
1034 {
1035 return m_wxCocoaScrollView
1036 ? m_wxCocoaScrollView->GetNSScrollView()
1037 : m_cocoaNSView;
1038 }
1039
1040 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
1041 {
1042 // TODO: Handle scrolling offset
1043 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
1044 }
1045
1046 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
1047 {
1048 // TODO: Handle scrolling offset
1049 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
1050 }
1051
1052 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
1053 {
1054 // TODO: Handle scrolling offset
1055 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
1056 }
1057
1058 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
1059 {
1060 // TODO: Handle scrolling offset
1061 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
1062 }
1063
1064 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
1065 {
1066 // TODO: Handle scrolling offset
1067 NSAffineTransform *transform = wxCocoaDCImpl::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
1068 return transform;
1069 }
1070
1071 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
1072 {
1073 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
1074 // Recursion can happen if the event loop runs from within the paint
1075 // handler. For instance, if an assertion dialog is shown.
1076 // FIXME: This seems less than ideal.
1077 if(m_isInPaint)
1078 {
1079 wxLogDebug(wxT("Paint event recursion!"));
1080 return false;
1081 }
1082 m_isInPaint = true;
1083
1084 // Set m_updateRegion
1085 const NSRect *rects = &rect; // The bounding box of the region
1086 NSInteger countRects = 1;
1087 // Try replacing the larger rectangle with a list of smaller ones:
1088 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
1089 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
1090
1091 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
1092 for(int i=0; i<countRects; i++)
1093 {
1094 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
1095 }
1096 m_updateRegion = wxRegion(transformedRects,countRects);
1097 free(transformedRects);
1098
1099 wxPaintEvent event(m_windowId);
1100 event.SetEventObject(this);
1101 bool ret = HandleWindowEvent(event);
1102 m_isInPaint = false;
1103 return ret;
1104 }
1105
1106 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
1107 {
1108 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
1109 // Mouse events happen at the NSWindow level so we need to convert
1110 // into our bounds coordinates then convert to wx coordinates.
1111 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
1112 if( m_wxCocoaScrollView != NULL)
1113 {
1114 // This gets the wx client area (i.e. the visible portion of the content) in
1115 // the coordinate system of our (the doucment) view.
1116 NSRect documentVisibleRect = [[m_wxCocoaScrollView->GetNSScrollView() contentView] documentVisibleRect];
1117 // For horizontal, simply subtract the origin.
1118 // e.g. if the origin is at 123 and the user clicks as far left as possible then
1119 // the coordinate that wx wants is 0.
1120 cocoaPoint.x -= documentVisibleRect.origin.x;
1121 if([m_cocoaNSView isFlipped])
1122 {
1123 // In the flipped view case this works exactly like horizontal.
1124 cocoaPoint.y -= documentVisibleRect.origin.y;
1125 }
1126 // For vertical we have to mind non-flipped (e.g. y=0 at bottom) views.
1127 // We also need to mind the fact that we're still in Cocoa coordinates
1128 // and not wx coordinates. The wx coordinate translation will still occur
1129 // and that is going to be wxY = boundsH - cocoaY for non-flipped views.
1130
1131 // When we consider scrolling we are truly interested in how far the top
1132 // edge of the bounds rectangle is scrolled off the screen.
1133 // Assuming the bounds origin is 0 (which is an assumption we make in
1134 // wxCocoa since wxWidgets has no analog to it) then the top edge of
1135 // the bounds rectangle is simply its height. The top edge of the
1136 // documentVisibleRect (e.g. the client area) is its height plus
1137 // its origin.
1138 // Thus, we simply need add the distance between the bounds top
1139 // and the client (docuemntVisibleRect) top.
1140 // Or putting it another way, we subtract the distance between the
1141 // client top and the bounds top.
1142 else
1143 {
1144 NSRect bounds = [m_cocoaNSView bounds];
1145 CGFloat scrollYOrigin = (bounds.size.height - (documentVisibleRect.origin.y + documentVisibleRect.size.height));
1146 cocoaPoint.y += scrollYOrigin;
1147 }
1148 }
1149
1150 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1151 // FIXME: Should we be adjusting for client area origin?
1152 const wxPoint &clientorigin = GetClientAreaOrigin();
1153 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1154 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1155
1156 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
1157 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
1158 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
1159 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
1160
1161 // TODO: set timestamp?
1162 event.SetEventObject(this);
1163 event.SetId(GetId());
1164 }
1165
1166 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
1167 {
1168 wxMouseEvent event(wxEVT_MOTION);
1169 InitMouseEvent(event,theEvent);
1170 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
1171 return HandleWindowEvent(event);
1172 }
1173
1174 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
1175 {
1176 wxMouseEvent event(wxEVT_MOTION);
1177 NSWindow *window = [GetNSView() window];
1178 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
1179 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
1180
1181 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
1182 // FIXME: Should we be adjusting for client area origin?
1183 const wxPoint &clientorigin = GetClientAreaOrigin();
1184 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
1185 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
1186
1187 // TODO: Handle shift, control, alt, meta flags
1188 event.SetEventObject(this);
1189 event.SetId(GetId());
1190
1191 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
1192 HandleWindowEvent(event);
1193 }
1194
1195 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
1196 {
1197 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1198 {
1199 m_visibleTrackingRectManager->BeginSynthesizingEvents();
1200
1201 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
1202 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
1203 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
1204 // first responder, so the mouseMoved: events are effectively discarded.
1205 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
1206
1207 wxMouseEvent event(wxEVT_ENTER_WINDOW);
1208 InitMouseEvent(event,theEvent);
1209 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
1210 return HandleWindowEvent(event);
1211 }
1212 else
1213 return false;
1214 }
1215
1216 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
1217 {
1218 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
1219 {
1220 m_visibleTrackingRectManager->StopSynthesizingEvents();
1221
1222 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
1223 InitMouseEvent(event,theEvent);
1224 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
1225 return HandleWindowEvent(event);
1226 }
1227 else
1228 return false;
1229 }
1230
1231 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
1232 {
1233 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
1234 InitMouseEvent(event,theEvent);
1235 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1236 return HandleWindowEvent(event);
1237 }
1238
1239 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
1240 {
1241 wxMouseEvent event(wxEVT_MOTION);
1242 InitMouseEvent(event,theEvent);
1243 event.m_leftDown = true;
1244 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1245 return HandleWindowEvent(event);
1246 }
1247
1248 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
1249 {
1250 wxMouseEvent event(wxEVT_LEFT_UP);
1251 InitMouseEvent(event,theEvent);
1252 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1253 return HandleWindowEvent(event);
1254 }
1255
1256 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
1257 {
1258 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
1259 InitMouseEvent(event,theEvent);
1260 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
1261 return HandleWindowEvent(event);
1262 }
1263
1264 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
1265 {
1266 wxMouseEvent event(wxEVT_MOTION);
1267 InitMouseEvent(event,theEvent);
1268 event.m_rightDown = true;
1269 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
1270 return HandleWindowEvent(event);
1271 }
1272
1273 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
1274 {
1275 wxMouseEvent event(wxEVT_RIGHT_UP);
1276 InitMouseEvent(event,theEvent);
1277 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
1278 return HandleWindowEvent(event);
1279 }
1280
1281 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
1282 {
1283 return false;
1284 }
1285
1286 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
1287 {
1288 return false;
1289 }
1290
1291 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
1292 {
1293 return false;
1294 }
1295
1296 void wxWindowCocoa::Cocoa_FrameChanged(void)
1297 {
1298 // We always get this message for the real NSView which may have been
1299 // enclosed in an NSScrollView. If that's the case then what we're
1300 // effectively getting here is a notifcation that the
1301 // virtual sized changed.. which we don't need to send on since
1302 // wx has no concept of this whatsoever.
1303 bool isViewForSuperview = (m_wxCocoaScrollView == NULL);
1304 if(isViewForSuperview)
1305 {
1306 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
1307 if(m_visibleTrackingRectManager != NULL)
1308 m_visibleTrackingRectManager->RebuildTrackingRect();
1309 wxSizeEvent event(GetSize(), m_windowId);
1310 event.SetEventObject(this);
1311 HandleWindowEvent(event);
1312 }
1313 else
1314 {
1315 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged ignored"),this);
1316 }
1317 }
1318
1319 bool wxWindowCocoa::Cocoa_resetCursorRects()
1320 {
1321 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
1322
1323 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
1324 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
1325 // mouse exited event. If the control happens to share the edge with its parent window which is
1326 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
1327 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
1328 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
1329 // which willl in turn call us before exiting for the next user event.
1330
1331 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
1332 // our mouse exited event because the already queued event will have the old tracking rect
1333 // tag. The simple solution is to only rebuild our tracking rect if we need to.
1334
1335 if(m_visibleTrackingRectManager != NULL)
1336 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
1337
1338 if(!m_cursor.GetNSCursor())
1339 return false;
1340
1341 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1342
1343 return true;
1344 }
1345
1346 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
1347 {
1348 if(!wxWindowBase::SetCursor(cursor))
1349 return false;
1350
1351 // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
1352 // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
1353 // rects and thus won't ever call resetCursorRects.
1354 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
1355
1356 // Invalidate the cursor rects so the cursor will change
1357 // Note that it is not enough to remove the old one (if any) and add the new one.
1358 // For the rects to work properly, Cocoa itself must call resetCursorRects.
1359 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
1360 return true;
1361 }
1362
1363 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
1364 {
1365 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
1366 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
1367 if(m_visibleTrackingRectManager != NULL)
1368 m_visibleTrackingRectManager->BuildTrackingRect();
1369 return false;
1370 }
1371
1372 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
1373 {
1374 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
1375 // Clear tracking rects. It is imperative this be done before the new window is set.
1376 if(m_visibleTrackingRectManager != NULL)
1377 m_visibleTrackingRectManager->ClearTrackingRect();
1378 return false;
1379 }
1380
1381 bool wxWindow::Close(bool force)
1382 {
1383 // The only reason this function exists is that it is virtual and
1384 // wxTopLevelWindowCocoa will override it.
1385 return wxWindowBase::Close(force);
1386 }
1387
1388 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
1389 {
1390 [[oldView superview] replaceSubview:oldView with:newView];
1391 }
1392
1393 void wxWindow::DoEnable(bool enable)
1394 {
1395 CocoaSetEnabled(enable);
1396 }
1397
1398 bool wxWindow::Show(bool show)
1399 {
1400 wxAutoNSAutoreleasePool pool;
1401 // If the window is marked as visible, then it shouldn't have a dummy view
1402 // If the window is marked hidden, then it should have a dummy view
1403 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
1404 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
1405 // Return false if there isn't a window to show or hide
1406 NSView *cocoaView = GetNSViewForHiding();
1407 if(!cocoaView)
1408 return false;
1409 if(show)
1410 {
1411 // If state isn't changing, return false
1412 if(!m_cocoaHider)
1413 return false;
1414
1415 // Replace the stand-in view with the real one and destroy the dummy view
1416 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
1417 wxASSERT(![m_cocoaHider->GetNSView() superview]);
1418 delete m_cocoaHider;
1419 m_cocoaHider = NULL;
1420 wxASSERT([cocoaView superview]);
1421
1422 // Schedule an update of the key view loop (NOTE: 10.4+ only.. argh)
1423 NSWindow *window = [cocoaView window];
1424 if(window != nil)
1425 {
1426 // Delay this until returning to the event loop for a couple of reasons:
1427 // 1. If a lot of stuff is shown/hidden we avoid recalculating needlessly
1428 // 2. NSWindow does not seem to see the newly shown views if we do it right now.
1429 if([window respondsToSelector:@selector(recalculateKeyViewLoop)])
1430 [window performSelector:@selector(recalculateKeyViewLoop) withObject:nil afterDelay:0.0];
1431 }
1432 }
1433 else
1434 {
1435 // If state isn't changing, return false
1436 if(m_cocoaHider)
1437 return false;
1438
1439 // Handle the first responder
1440 NSWindow *window = [cocoaView window];
1441 if(window != nil)
1442 {
1443 NSResponder *firstResponder = [window firstResponder];
1444 if([firstResponder isKindOfClass:[NSView class]] && [(NSView*)firstResponder isDescendantOf:cocoaView])
1445 {
1446 BOOL didResign = [window makeFirstResponder:nil];
1447 // If the current first responder (one of our subviews) refuses to give
1448 // up its status, then fail now and don't hide this view
1449 if(didResign == NO)
1450 return false;
1451 }
1452 }
1453
1454 // Create a new view to stand in for the real one (via wxWindowCocoaHider) and replace
1455 // the real one with the stand in.
1456 m_cocoaHider = new wxWindowCocoaHider(this);
1457 // NOTE: replaceSubview:with will cause m_cocaNSView to be
1458 // (auto)released which balances out addSubview
1459 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
1460 // m_coocaNSView is now only retained by us
1461 wxASSERT([m_cocoaHider->GetNSView() superview]);
1462 wxASSERT(![cocoaView superview]);
1463 }
1464 m_isShown = show;
1465 return true;
1466 }
1467
1468 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
1469 {
1470 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":".");
1471 int currentX, currentY;
1472 int currentW, currentH;
1473 DoGetPosition(&currentX, &currentY);
1474 DoGetSize(&currentW, &currentH);
1475 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1476 x=currentX;
1477 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1478 y=currentY;
1479
1480 AdjustForParentClientOrigin(x,y,sizeFlags);
1481
1482 wxSize size(wxDefaultSize);
1483
1484 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1485 {
1486 if(sizeFlags&wxSIZE_AUTO_WIDTH)
1487 {
1488 size=DoGetBestSize();
1489 width=size.x;
1490 }
1491 else
1492 width=currentW;
1493 }
1494 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
1495 {
1496 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
1497 {
1498 if(size.x==-1)
1499 size=DoGetBestSize();
1500 height=size.y;
1501 }
1502 else
1503 height=currentH;
1504 }
1505 DoMoveWindow(x,y,width,height);
1506 }
1507
1508 #if wxUSE_TOOLTIPS
1509
1510 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
1511 {
1512 wxWindowBase::DoSetToolTip(tip);
1513
1514 if ( m_tooltip )
1515 {
1516 m_tooltip->SetWindow((wxWindow *)this);
1517 }
1518 }
1519
1520 #endif
1521
1522 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
1523 {
1524 wxAutoNSAutoreleasePool pool;
1525 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
1526
1527 NSView *nsview = GetNSViewForSuperview();
1528 NSView *superview = [nsview superview];
1529
1530 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1531
1532 NSRect oldFrameRect = [nsview frame];
1533 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
1534 [nsview setFrame:newFrameRect];
1535 // Be sure to redraw the parent to reflect the changed position
1536 [superview setNeedsDisplayInRect:oldFrameRect];
1537 [superview setNeedsDisplayInRect:newFrameRect];
1538 }
1539
1540 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1541 {
1542 NSView *nsview = GetNSViewForSuperview();
1543 NSView *superview = [nsview superview];
1544 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
1545 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1546 NSRect frameRect = [nsview frame];
1547 if(size.x!=-1)
1548 frameRect.size.width = size.x;
1549 if(size.y!=-1)
1550 frameRect.size.height = size.y;
1551 frameRect.origin.x = pos.x;
1552 frameRect.origin.y = pos.y;
1553 // Tell Cocoa to change the margin between the bottom of the superview
1554 // and the bottom of the control. Keeps the control pinned to the top
1555 // of its superview so that its position in the wxWidgets coordinate
1556 // system doesn't change.
1557 if(![superview isFlipped])
1558 [nsview setAutoresizingMask: NSViewMinYMargin];
1559 // MUST set the mask before setFrame: which can generate a size event
1560 // and cause a scroller to be added!
1561 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1562 [nsview setFrame: frameRect];
1563 }
1564
1565 // Get total size
1566 void wxWindow::DoGetSize(int *w, int *h) const
1567 {
1568 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1569 if(w)
1570 *w=(int)cocoaRect.size.width;
1571 if(h)
1572 *h=(int)cocoaRect.size.height;
1573 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1574 }
1575
1576 void wxWindow::DoGetPosition(int *x, int *y) const
1577 {
1578 NSView *nsview = GetNSViewForSuperview();
1579
1580 NSRect cocoaRect = [nsview frame];
1581 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1582 if(x)
1583 *x=(int)rectWx.origin.x;
1584 if(y)
1585 *y=(int)rectWx.origin.y;
1586 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1587 }
1588
1589 WXWidget wxWindow::GetHandle() const
1590 {
1591 return m_cocoaNSView;
1592 }
1593
1594 wxWindow* wxWindow::GetWxWindow() const
1595 {
1596 return (wxWindow*) this;
1597 }
1598
1599 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1600 {
1601 [m_cocoaNSView setNeedsDisplay:YES];
1602 }
1603
1604 void wxWindow::SetFocus()
1605 {
1606 if([GetNSView() acceptsFirstResponder])
1607 [[GetNSView() window] makeFirstResponder: GetNSView()];
1608 }
1609
1610 void wxWindow::DoCaptureMouse()
1611 {
1612 // TODO
1613 sm_capturedWindow = this;
1614 }
1615
1616 void wxWindow::DoReleaseMouse()
1617 {
1618 // TODO
1619 sm_capturedWindow = NULL;
1620 }
1621
1622 void wxWindow::DoScreenToClient(int *x, int *y) const
1623 {
1624 // Point in cocoa screen coordinates:
1625 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1626 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1627 NSWindow *theWindow = [clientView window];
1628
1629 // Point in window's base coordinate system:
1630 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1631 // Point in view's bounds coordinate system
1632 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1633 // Point in wx client coordinates:
1634 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1635 if(x!=NULL)
1636 *x = theWxClientPoint.x;
1637 if(y!=NULL)
1638 *y = theWxClientPoint.y;
1639 }
1640
1641 void wxWindow::DoClientToScreen(int *x, int *y) const
1642 {
1643 // Point in wx client coordinates
1644 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1645
1646 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1647
1648 // Point in the view's bounds coordinate system
1649 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1650
1651 // Point in the window's base coordinate system
1652 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1653
1654 NSWindow *theWindow = [clientView window];
1655 // Point in Cocoa's screen coordinates
1656 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1657
1658 // Act as though this was the origin of a 0x0 rectangle
1659 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1660
1661 // Convert that rectangle to wx coordinates
1662 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1663 if(*x)
1664 *x = theWxScreenPoint.x;
1665 if(*y)
1666 *y = theWxScreenPoint.y;
1667 }
1668
1669 // Get size *available for subwindows* i.e. excluding menu bar etc.
1670 void wxWindow::DoGetClientSize(int *x, int *y) const
1671 {
1672 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1673 if(m_wxCocoaScrollView)
1674 m_wxCocoaScrollView->DoGetClientSize(x,y);
1675 else
1676 wxWindowCocoa::DoGetSize(x,y);
1677 }
1678
1679 void wxWindow::DoSetClientSize(int width, int height)
1680 {
1681 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1682 if(m_wxCocoaScrollView)
1683 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1684 CocoaSetWxWindowSize(width,height);
1685 }
1686
1687 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1688 {
1689 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1690 }
1691
1692 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1693 {
1694 // Intentional no-op.
1695 }
1696
1697 wxString wxWindow::GetLabel() const
1698 {
1699 // General Get/Set of labels is implemented in wxControlBase
1700 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1701 return wxEmptyString;
1702 }
1703
1704 int wxWindow::GetCharHeight() const
1705 {
1706 // TODO
1707 return 10;
1708 }
1709
1710 int wxWindow::GetCharWidth() const
1711 {
1712 // TODO
1713 return 5;
1714 }
1715
1716 void wxWindow::DoGetTextExtent(const wxString& string, int *outX, int *outY,
1717 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
1718 {
1719 // FIXME: This obviously ignores the window's font (if any) along with any size
1720 // transformations. However, it's better than nothing.
1721 // We don't create a wxClientDC because we don't want to accidently be able to use
1722 // it for drawing.
1723 wxClientDC tmpdc(const_cast<wxWindow*>(this));
1724 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
1725 }
1726
1727 // Coordinates relative to the window
1728 void wxWindow::WarpPointer (int x_pos, int y_pos)
1729 {
1730 // TODO
1731 }
1732
1733 int wxWindow::GetScrollPos(int orient) const
1734 {
1735 if(m_wxCocoaScrollView != NULL)
1736 return m_wxCocoaScrollView->GetScrollPos(static_cast<wxOrientation>(orient & wxBOTH));
1737 else
1738 return 0;
1739 }
1740
1741 // This now returns the whole range, not just the number
1742 // of positions that we can scroll.
1743 int wxWindow::GetScrollRange(int orient) const
1744 {
1745 if(m_wxCocoaScrollView != NULL)
1746 return m_wxCocoaScrollView->GetScrollRange(static_cast<wxOrientation>(orient & wxBOTH));
1747 else
1748 return 0;
1749 }
1750
1751 int wxWindow::GetScrollThumb(int orient) const
1752 {
1753 if(m_wxCocoaScrollView != NULL)
1754 return m_wxCocoaScrollView->GetScrollThumb(static_cast<wxOrientation>(orient & wxBOTH));
1755 else
1756 return 0;
1757 }
1758
1759 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1760 {
1761 if(m_wxCocoaScrollView != NULL)
1762 return m_wxCocoaScrollView->SetScrollPos(static_cast<wxOrientation>(orient & wxBOTH), pos);
1763 }
1764
1765 void wxWindow::CocoaCreateNSScrollView()
1766 {
1767 if(!m_wxCocoaScrollView)
1768 {
1769 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1770 }
1771 }
1772
1773 // New function that will replace some of the above.
1774 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1775 int range, bool refresh)
1776 {
1777 CocoaCreateNSScrollView();
1778 m_wxCocoaScrollView->SetScrollbar(orient, pos, thumbVisible, range);
1779 // TODO: Handle refresh (if we even need to)
1780 }
1781
1782 // Does a physical scroll
1783 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1784 {
1785 if(m_wxCocoaScrollView != NULL)
1786 m_wxCocoaScrollView->ScrollWindow(dx, dy, rect);
1787 }
1788
1789 void wxWindow::DoSetVirtualSize( int x, int y )
1790 {
1791 // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
1792 // possibly not what the caller passed in. For example, the current implementation
1793 // clamps the width and height to within the min/max virtual ranges.
1794 // wxDefaultCoord is passed through unchanged but then GetVirtualSize() will correct
1795 // that by returning effectively max(virtual, client)
1796 wxWindowBase::DoSetVirtualSize(x,y);
1797 // Create the scroll view if it hasn't been already.
1798 CocoaCreateNSScrollView();
1799
1800 // The GetVirtualSize automatically increases the size to max(client,virtual)
1801 m_wxCocoaScrollView->UpdateSizes();
1802 }
1803
1804 bool wxWindow::SetFont(const wxFont& font)
1805 {
1806 // FIXME: We may need to handle wx font inheritance.
1807 return wxWindowBase::SetFont(font);
1808 }
1809
1810 #if 0 // these are used when debugging the algorithm.
1811 static char const * const comparisonresultStrings[] =
1812 { "<"
1813 , "=="
1814 , ">"
1815 };
1816 #endif
1817
1818 class CocoaWindowCompareContext
1819 {
1820 wxDECLARE_NO_COPY_CLASS(CocoaWindowCompareContext);
1821 public:
1822 CocoaWindowCompareContext(); // Not implemented
1823 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1824 {
1825 m_target = target;
1826 // Cocoa sorts subviews in-place.. make a copy
1827 m_subviews = [subviews copy];
1828 }
1829 ~CocoaWindowCompareContext()
1830 { // release the copy
1831 [m_subviews release];
1832 }
1833 NSView* target()
1834 { return m_target; }
1835 NSArray* subviews()
1836 { return m_subviews; }
1837 /* Helper function that returns the comparison based off of the original ordering */
1838 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1839 {
1840 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1841 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1842 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1843 // likely compare higher than the other view which is reasonable considering the only way that
1844 // can happen is if the subview was added after our call to subviews but before the call to
1845 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1846 // that case should never occur anyway because that would imply a multi-threaded GUI call
1847 // which is a big no-no with Cocoa.
1848
1849 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1850 NSComparisonResult result = (firstI < secondI)
1851 ? NSOrderedAscending /* -1 */
1852 : (firstI > secondI)
1853 ? NSOrderedDescending /* 1 */
1854 : NSOrderedSame /* 0 */;
1855
1856 #if 0 // Enable this if you need to debug the algorithm.
1857 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1858 #endif
1859 return result;
1860 }
1861 private:
1862 /* The subview we are trying to Raise or Lower */
1863 NSView *m_target;
1864 /* A copy of the original array of subviews */
1865 NSArray *m_subviews;
1866 };
1867
1868 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1869 * the target view is always higher than every other view. When comparing two views neither of
1870 * which is the target, it returns the correct response based on the original ordering
1871 */
1872 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1873 {
1874 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1875 // first should be ordered higher
1876 if(first==compareContext->target())
1877 return NSOrderedDescending;
1878 // second should be ordered higher
1879 if(second==compareContext->target())
1880 return NSOrderedAscending;
1881 return compareContext->CompareUsingOriginalOrdering(first,second);
1882 }
1883
1884 // Raise the window to the top of the Z order
1885 void wxWindow::Raise()
1886 {
1887 // wxAutoNSAutoreleasePool pool;
1888 NSView *nsview = GetNSViewForSuperview();
1889 NSView *superview = [nsview superview];
1890 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1891
1892 [superview sortSubviewsUsingFunction:
1893 CocoaRaiseWindowCompareFunction
1894 context: &compareContext];
1895 }
1896
1897 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1898 * the target view is always lower than every other view. When comparing two views neither of
1899 * which is the target, it returns the correct response based on the original ordering
1900 */
1901 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1902 {
1903 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1904 // first should be ordered lower
1905 if(first==compareContext->target())
1906 return NSOrderedAscending;
1907 // second should be ordered lower
1908 if(second==compareContext->target())
1909 return NSOrderedDescending;
1910 return compareContext->CompareUsingOriginalOrdering(first,second);
1911 }
1912
1913 // Lower the window to the bottom of the Z order
1914 void wxWindow::Lower()
1915 {
1916 NSView *nsview = GetNSViewForSuperview();
1917 NSView *superview = [nsview superview];
1918 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1919
1920 #if 0
1921 NSLog(@"Target:\n%@\n", nsview);
1922 NSLog(@"Before:\n%@\n", compareContext.subviews());
1923 #endif
1924 [superview sortSubviewsUsingFunction:
1925 CocoaLowerWindowCompareFunction
1926 context: &compareContext];
1927 #if 0
1928 NSLog(@"After:\n%@\n", [superview subviews]);
1929 #endif
1930 }
1931
1932 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1933 {
1934 return false;
1935 }
1936
1937 // Get the window with the focus
1938 wxWindow *wxWindowBase::DoFindFocus()
1939 {
1940 // Basically we are somewhat emulating the responder chain here except
1941 // we are only loking for the first responder in the key window or
1942 // upon failing to find one if the main window is different we look
1943 // for the first responder in the main window.
1944
1945 // Note that the firstResponder doesn't necessarily have to be an
1946 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
1947 // NULL unless it finds its argument in its hash map.
1948
1949 wxCocoaNSView *win;
1950
1951 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
1952 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
1953 if(win)
1954 return win->GetWxWindow();
1955
1956 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
1957 if(mainWindow == keyWindow)
1958 return NULL;
1959 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
1960 if(win)
1961 return win->GetWxWindow();
1962
1963 return NULL;
1964 }
1965
1966 /* static */ wxWindow *wxWindowBase::GetCapture()
1967 {
1968 // TODO
1969 return wxWindowCocoa::sm_capturedWindow;
1970 }
1971
1972 wxWindow *wxGetActiveWindow()
1973 {
1974 // TODO
1975 return NULL;
1976 }
1977
1978 wxPoint wxGetMousePosition()
1979 {
1980 // TODO
1981 return wxDefaultPosition;
1982 }
1983
1984 wxMouseState wxGetMouseState()
1985 {
1986 wxMouseState ms;
1987 // TODO
1988 return ms;
1989 }
1990
1991 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
1992 {
1993 pt = wxGetMousePosition();
1994 return NULL;
1995 }
1996
1997 // ========================================================================
1998 // wxCocoaMouseMovedEventSynthesizer
1999 // ========================================================================
2000
2001 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
2002
2003 /* This class registers one run loop observer to cover all windows registered with it.
2004 * It will register the observer when the first view is registerd and unregister the
2005 * observer when the last view is unregistered.
2006 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
2007 * is no reason it couldn't be instantiated multiple times.
2008 */
2009 class wxCocoaMouseMovedEventSynthesizer
2010 {
2011 wxDECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer);
2012 public:
2013 wxCocoaMouseMovedEventSynthesizer()
2014 { m_lastScreenMouseLocation = NSZeroPoint;
2015 }
2016 ~wxCocoaMouseMovedEventSynthesizer();
2017 void RegisterWxCocoaView(wxCocoaNSView *aView);
2018 void UnregisterWxCocoaView(wxCocoaNSView *aView);
2019 void SynthesizeMouseMovedEvent();
2020
2021 protected:
2022 void AddRunLoopObserver();
2023 void RemoveRunLoopObserver();
2024 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
2025 std::list<wxCocoaNSView*> m_registeredViews;
2026 NSPoint m_lastScreenMouseLocation;
2027 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
2028 };
2029
2030 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
2031 {
2032 m_registeredViews.push_back(aView);
2033 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
2034
2035 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
2036 {
2037 AddRunLoopObserver();
2038 }
2039 }
2040
2041 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
2042 {
2043 m_registeredViews.remove(aView);
2044 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
2045 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
2046 {
2047 RemoveRunLoopObserver();
2048 }
2049 }
2050
2051 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
2052 {
2053 if(!m_registeredViews.empty())
2054 {
2055 // This means failure to clean up so we report on it as a debug message.
2056 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
2057 m_registeredViews.clear();
2058 }
2059 if(m_runLoopObserver != NULL)
2060 {
2061 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
2062 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
2063 RemoveRunLoopObserver();
2064 }
2065 }
2066
2067 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
2068 {
2069 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
2070 }
2071
2072 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
2073 {
2074 CFRunLoopObserverContext observerContext =
2075 { 0
2076 , this
2077 , NULL
2078 , NULL
2079 , NULL
2080 };
2081
2082 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
2083 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
2084
2085 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
2086 // in our observer procedure being called before the run loop has decided that it is going to return control to
2087 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
2088 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
2089
2090 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
2091 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
2092 // around to it before letting the event loop do its work. This has some very odd effects on the way the
2093 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
2094 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
2095 // time the new one is built.
2096
2097 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
2098 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
2099 // the mouseExited: message to be sent properly.
2100
2101 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
2102 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2103 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
2104 }
2105
2106 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
2107 {
2108 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
2109 m_runLoopObserver.reset();
2110 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
2111 }
2112
2113 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
2114 {
2115 NSPoint screenMouseLocation = [NSEvent mouseLocation];
2116 // Checking the last mouse location is done for a few reasons:
2117 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
2118 // telling the app the mouse moved when the user hit a key for instance.
2119 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
2120 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
2121 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
2122 // loop causing the tracking rectangles to constantly be reset.
2123 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
2124 {
2125 m_lastScreenMouseLocation = screenMouseLocation;
2126 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
2127 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
2128 {
2129 (*i)->Cocoa_synthesizeMouseMoved();
2130 }
2131 }
2132 }
2133
2134 // Singleton used for all views:
2135 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
2136
2137 // ========================================================================
2138 // wxCocoaTrackingRectManager
2139 // ========================================================================
2140
2141 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
2142 : m_window(window)
2143 {
2144 m_isTrackingRectActive = false;
2145 BuildTrackingRect();
2146 }
2147
2148 void wxCocoaTrackingRectManager::ClearTrackingRect()
2149 {
2150 if(m_isTrackingRectActive)
2151 {
2152 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
2153 m_isTrackingRectActive = false;
2154 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2155 }
2156 // If we were doing periodic events we need to clear those too
2157 StopSynthesizingEvents();
2158 }
2159
2160 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
2161 {
2162 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
2163 }
2164
2165 void wxCocoaTrackingRectManager::BuildTrackingRect()
2166 {
2167 // Pool here due to lack of one during wx init phase
2168 wxAutoNSAutoreleasePool pool;
2169
2170 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
2171
2172 NSView *theView = m_window->GetNSView();
2173
2174 if([theView window] != nil)
2175 {
2176 NSRect visibleRect = [theView visibleRect];
2177
2178 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
2179 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
2180 m_isTrackingRectActive = true;
2181
2182 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
2183 }
2184 }
2185
2186 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
2187 {
2188 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
2189 }
2190
2191 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
2192 {
2193 if(m_isTrackingRectActive)
2194 {
2195 NSView *theView = m_window->GetNSView();
2196 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
2197 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
2198 {
2199 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
2200 return;
2201 }
2202 }
2203 RebuildTrackingRect();
2204 }
2205
2206 void wxCocoaTrackingRectManager::RebuildTrackingRect()
2207 {
2208 ClearTrackingRect();
2209 BuildTrackingRect();
2210 }
2211
2212 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
2213 {
2214 ClearTrackingRect();
2215 }
2216
2217 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
2218 {
2219 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);
2220 }
2221