]> git.saurik.com Git - wxWidgets.git/blob - src/cocoa/window.mm
Fix a number of problems with tracking rectangles by avoiding rebuilding them when...
[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/utils.h"
19 #endif //WX_PRECOMP
20
21 #include "wx/tooltip.h"
22
23 #include "wx/cocoa/autorelease.h"
24 #include "wx/cocoa/string.h"
25 #include "wx/cocoa/trackingrectmanager.h"
26 #include "wx/mac/corefoundation/cfref.h"
27
28 #import <Foundation/NSArray.h>
29 #import <Foundation/NSRunLoop.h>
30 #include "wx/cocoa/objc/NSView.h"
31 #import <AppKit/NSEvent.h>
32 #import <AppKit/NSScrollView.h>
33 #import <AppKit/NSColor.h>
34 #import <AppKit/NSClipView.h>
35 #import <Foundation/NSException.h>
36 #import <AppKit/NSApplication.h>
37 #import <AppKit/NSWindow.h>
38 #import <AppKit/NSScreen.h>
39
40 // Turn this on to paint green over the dummy views for debugging
41 #undef WXCOCOA_FILL_DUMMY_VIEW
42
43 #ifdef WXCOCOA_FILL_DUMMY_VIEW
44 #import <AppKit/NSBezierPath.h>
45 #endif //def WXCOCOA_FILL_DUMMY_VIEW
46
47 // STL list used by wxCocoaMouseMovedEventSynthesizer
48 #include <list>
49
50 /* NSComparisonResult is typedef'd as an enum pre-Leopard but typedef'd as
51 * NSInteger post-Leopard. Pre-Leopard the Cocoa toolkit expects a function
52 * returning int and not NSComparisonResult. Post-Leopard the Cocoa toolkit
53 * expects a function returning the new non-enum NSComparsionResult.
54 * Hence we create a typedef named CocoaWindowCompareFunctionResult.
55 */
56 #if defined(NSINTEGER_DEFINED)
57 typedef NSComparisonResult CocoaWindowCompareFunctionResult;
58 #else
59 typedef int CocoaWindowCompareFunctionResult;
60 #endif
61
62 // A category for methods that are only present in Panther's SDK
63 @interface NSView(wxNSViewPrePantherCompatibility)
64 - (void)getRectsBeingDrawn:(const NSRect **)rects count:(int *)count;
65 @end
66
67 // ========================================================================
68 // Helper functions for converting to/from wxWidgets coordinates and a
69 // specified NSView's coordinate system.
70 // ========================================================================
71 NSPoint CocoaTransformNSViewBoundsToWx(NSView *nsview, NSPoint pointBounds)
72 {
73 wxCHECK_MSG(nsview, pointBounds, wxT("Need to have a Cocoa view to do translation"));
74 if([nsview isFlipped])
75 return pointBounds;
76 NSRect ourBounds = [nsview bounds];
77 return NSMakePoint
78 ( pointBounds.x
79 , ourBounds.size.height - pointBounds.y
80 );
81 }
82
83 NSRect CocoaTransformNSViewBoundsToWx(NSView *nsview, NSRect rectBounds)
84 {
85 wxCHECK_MSG(nsview, rectBounds, wxT("Need to have a Cocoa view to do translation"));
86 if([nsview isFlipped])
87 return rectBounds;
88 NSRect ourBounds = [nsview bounds];
89 return NSMakeRect
90 ( rectBounds.origin.x
91 , ourBounds.size.height - (rectBounds.origin.y + rectBounds.size.height)
92 , rectBounds.size.width
93 , rectBounds.size.height
94 );
95 }
96
97 NSPoint CocoaTransformNSViewWxToBounds(NSView *nsview, NSPoint pointWx)
98 {
99 wxCHECK_MSG(nsview, pointWx, wxT("Need to have a Cocoa view to do translation"));
100 if([nsview isFlipped])
101 return pointWx;
102 NSRect ourBounds = [nsview bounds];
103 return NSMakePoint
104 ( pointWx.x
105 , ourBounds.size.height - pointWx.y
106 );
107 }
108
109 NSRect CocoaTransformNSViewWxToBounds(NSView *nsview, NSRect rectWx)
110 {
111 wxCHECK_MSG(nsview, rectWx, wxT("Need to have a Cocoa view to do translation"));
112 if([nsview isFlipped])
113 return rectWx;
114 NSRect ourBounds = [nsview bounds];
115 return NSMakeRect
116 ( rectWx.origin.x
117 , ourBounds.size.height - (rectWx.origin.y + rectWx.size.height)
118 , rectWx.size.width
119 , rectWx.size.height
120 );
121 }
122
123 // ============================================================================
124 // Screen coordinate helpers
125 // ============================================================================
126
127 /*
128 General observation about Cocoa screen coordinates:
129 It is documented that the first object of the [NSScreen screens] array is the screen with the menubar.
130
131 It is not documented (but true as far as I can tell) that (0,0) in Cocoa screen coordinates is always
132 the BOTTOM-right corner of this screen. Recall that Cocoa uses cartesian coordinates so y-increase is up.
133
134 It isn't clearly documented but visibleFrame returns a rectangle in screen coordinates, not a rectangle
135 relative to that screen's frame. The only real way to test this is to configure two screens one atop
136 the other such that the menubar screen is on top. The Dock at the bottom of the screen will then
137 eat into the visibleFrame of screen 1 by incrementing it's y-origin. Thus if you arrange two
138 1920x1200 screens top/bottom then screen 1 (the bottom screen) will have frame origin (0,-1200) and
139 visibleFrame origin (0,-1149) which is exactly 51 pixels higher than the full frame origin.
140
141 In wxCocoa, we somewhat arbitrarily declare that wx (0,0) is the TOP-left of screen 0's frame (the entire screen).
142 However, this isn't entirely arbitrary because the Quartz Display Services (CGDisplay) uses this same scheme.
143 This works out nicely because wxCocoa's wxDisplay is implemented using Quartz Display Services instead of NSScreen.
144 */
145
146 namespace { // file namespace
147
148 class wxCocoaPrivateScreenCoordinateTransformer
149 {
150 DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer)
151 public:
152 wxCocoaPrivateScreenCoordinateTransformer();
153 ~wxCocoaPrivateScreenCoordinateTransformer();
154 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
155 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
156
157 protected:
158 NSScreen *m_screenZero;
159 NSRect m_screenZeroFrame;
160 };
161
162 // NOTE: This is intended to be a short-lived object. A future enhancment might
163 // make it a global and reconfigure it upon some notification that the screen layout
164 // has changed.
165 inline wxCocoaPrivateScreenCoordinateTransformer::wxCocoaPrivateScreenCoordinateTransformer()
166 {
167 NSArray *screens = [NSScreen screens];
168
169 [screens retain];
170
171 m_screenZero = nil;
172 if(screens != nil && [screens count] > 0)
173 m_screenZero = [[screens objectAtIndex:0] retain];
174
175 [screens release];
176
177 if(m_screenZero != nil)
178 m_screenZeroFrame = [m_screenZero frame];
179 else
180 {
181 wxLogWarning(wxT("Can't translate to/from wx screen coordinates and Cocoa screen coordinates"));
182 // Just blindly assume 1024x768 so that at least we can sort of flip things around into
183 // Cocoa coordinates.
184 // NOTE: Theoretically this case should never happen anyway.
185 m_screenZeroFrame = NSMakeRect(0,0,1024,768);
186 }
187 }
188
189 inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
190 {
191 [m_screenZero release];
192 m_screenZero = nil;
193 }
194
195 inline wxPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
196 {
197 // x and y are in wx screen coordinates which we're going to arbitrarily define such that
198 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
199 // NOTE WELL: This means that (0,0) is _NOT_ an appropriate position for a window.
200
201 wxPoint theWxOrigin;
202
203 // Working in Cocoa's screen coordinates we must realize that the x coordinate we want is
204 // the distance between the left side (origin.x) of the window's frame and the left side of
205 // screen zero's frame.
206 theWxOrigin.x = windowFrame.origin.x - m_screenZeroFrame.origin.x;
207
208 // Working in Cocoa's screen coordinates we must realize that the y coordinate we want is
209 // actually the distance between the top-left of the screen zero frame and the top-left
210 // of the window's frame.
211
212 theWxOrigin.y = (m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height) - (windowFrame.origin.y + windowFrame.size.height);
213
214 return theWxOrigin;
215 }
216
217 inline NSPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
218 {
219 NSPoint theCocoaOrigin;
220
221 // The position is in wx screen coordinates which we're going to arbitrarily define such that
222 // (0,0) is the TOP-left of screen 0 (the one with the menubar)
223
224 // NOTE: The usable rectangle is smaller and hence we have the keepOriginVisible flag
225 // which will move the origin downward and/or left as necessary if the origin is
226 // inside the screen0 rectangle (i.e. x/y >= 0 in wx coordinates) and outside the
227 // visible frame (i.e. x/y < the top/left of the screen0 visible frame in wx coordinates)
228 // We don't munge origin coordinates < 0 because it actually is possible that the menubar is on
229 // the top of the bottom screen and thus that origin is completely valid!
230 if(keepOriginVisible && (m_screenZero != nil))
231 {
232 // Do al of this in wx coordinates because it's far simpler since we're dealing with top/left points
233 wxPoint visibleOrigin = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates([m_screenZero visibleFrame]);
234 if(x >= 0 && x < visibleOrigin.x)
235 x = visibleOrigin.x;
236 if(y >= 0 && y < visibleOrigin.y)
237 y = visibleOrigin.y;
238 }
239
240 // The x coordinate is simple as it's just relative to screen zero's frame
241 theCocoaOrigin.x = m_screenZeroFrame.origin.x + x;
242 // Working in Cocoa's coordinates think to start at the bottom of screen zero's frame and add
243 // the height of that rect which gives us the coordinate for the top of the visible rect. Now realize that
244 // the wx coordinates are flipped so if y is say 10 then we want to be 10 pixels down from that and thus
245 // we subtract y. But then we still need to take into account the size of the window which is h and subtract
246 // that to get the bottom-left origin of the rectangle.
247 theCocoaOrigin.y = m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height - y - height;
248
249 return theCocoaOrigin;
250 }
251
252 } // namespace
253
254 wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
255 {
256 wxCocoaPrivateScreenCoordinateTransformer transformer;
257 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
258 }
259
260 NSPoint wxWindowCocoa::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
261 {
262 wxCocoaPrivateScreenCoordinateTransformer transformer;
263 return transformer.OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,keepOriginVisible);
264 }
265
266 // ========================================================================
267 // wxWindowCocoaHider
268 // ========================================================================
269 class wxWindowCocoaHider: protected wxCocoaNSView
270 {
271 DECLARE_NO_COPY_CLASS(wxWindowCocoaHider)
272 public:
273 wxWindowCocoaHider(wxWindow *owner);
274 virtual ~wxWindowCocoaHider();
275 inline WX_NSView GetNSView() { return m_dummyNSView; }
276 protected:
277 wxWindowCocoa *m_owner;
278 WX_NSView m_dummyNSView;
279 virtual void Cocoa_FrameChanged(void);
280 virtual void Cocoa_synthesizeMouseMoved(void) {}
281 #ifdef WXCOCOA_FILL_DUMMY_VIEW
282 virtual bool Cocoa_drawRect(const NSRect& rect);
283 #endif //def WXCOCOA_FILL_DUMMY_VIEW
284 private:
285 wxWindowCocoaHider();
286 };
287
288 // ========================================================================
289 // wxWindowCocoaScrollView
290 // ========================================================================
291 class wxWindowCocoaScrollView: protected wxCocoaNSView
292 {
293 DECLARE_NO_COPY_CLASS(wxWindowCocoaScrollView)
294 public:
295 wxWindowCocoaScrollView(wxWindow *owner);
296 virtual ~wxWindowCocoaScrollView();
297 inline WX_NSScrollView GetNSScrollView() { return m_cocoaNSScrollView; }
298 void ClientSizeToSize(int &width, int &height);
299 void DoGetClientSize(int *x, int *y) const;
300 void Encapsulate();
301 void Unencapsulate();
302 protected:
303 wxWindowCocoa *m_owner;
304 WX_NSScrollView m_cocoaNSScrollView;
305 virtual void Cocoa_FrameChanged(void);
306 virtual void Cocoa_synthesizeMouseMoved(void) {}
307 private:
308 wxWindowCocoaScrollView();
309 };
310
311 // ========================================================================
312 // wxDummyNSView
313 // ========================================================================
314 @interface wxDummyNSView : NSView
315 - (NSView *)hitTest:(NSPoint)aPoint;
316 @end
317 WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
318
319 @implementation wxDummyNSView : NSView
320 - (NSView *)hitTest:(NSPoint)aPoint
321 {
322 return nil;
323 }
324
325 @end
326 WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
327
328 // ========================================================================
329 // wxWindowCocoaHider
330 // ========================================================================
331 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *owner)
332 : m_owner(owner)
333 {
334 wxASSERT(owner);
335 wxASSERT(owner->GetNSViewForHiding());
336 m_dummyNSView = [[WX_GET_OBJC_CLASS(wxDummyNSView) alloc]
337 initWithFrame:[owner->GetNSViewForHiding() frame]];
338 [m_dummyNSView setAutoresizingMask: [owner->GetNSViewForHiding() autoresizingMask]];
339 AssociateNSView(m_dummyNSView);
340 }
341
342 wxWindowCocoaHider::~wxWindowCocoaHider()
343 {
344 DisassociateNSView(m_dummyNSView);
345 [m_dummyNSView release];
346 }
347
348 void wxWindowCocoaHider::Cocoa_FrameChanged(void)
349 {
350 // Keep the real window in synch with the dummy
351 wxASSERT(m_dummyNSView);
352 [m_owner->GetNSViewForHiding() setFrame:[m_dummyNSView frame]];
353 }
354
355
356 #ifdef WXCOCOA_FILL_DUMMY_VIEW
357 bool wxWindowCocoaHider::Cocoa_drawRect(const NSRect& rect)
358 {
359 NSBezierPath *bezpath = [NSBezierPath bezierPathWithRect:rect];
360 [[NSColor greenColor] set];
361 [bezpath stroke];
362 [bezpath fill];
363 return true;
364 }
365 #endif //def WXCOCOA_FILL_DUMMY_VIEW
366
367 // ========================================================================
368 // wxFlippedNSClipView
369 // ========================================================================
370 @interface wxFlippedNSClipView : NSClipView
371 - (BOOL)isFlipped;
372 @end
373 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
374
375 @implementation wxFlippedNSClipView : NSClipView
376 - (BOOL)isFlipped
377 {
378 return YES;
379 }
380
381 @end
382 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
383
384 // ========================================================================
385 // wxWindowCocoaScrollView
386 // ========================================================================
387 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
388 : m_owner(owner)
389 {
390 wxAutoNSAutoreleasePool pool;
391 wxASSERT(owner);
392 wxASSERT(owner->GetNSView());
393 m_cocoaNSScrollView = [[NSScrollView alloc]
394 initWithFrame:[owner->GetNSView() frame]];
395 AssociateNSView(m_cocoaNSScrollView);
396
397 /* Replace the default NSClipView with a flipped one. This ensures
398 scrolling is "pinned" to the top-left instead of bottom-right. */
399 NSClipView *flippedClip = [[WX_GET_OBJC_CLASS(wxFlippedNSClipView) alloc]
400 initWithFrame: [[m_cocoaNSScrollView contentView] frame]];
401 [m_cocoaNSScrollView setContentView:flippedClip];
402 [flippedClip release];
403
404 [m_cocoaNSScrollView setBackgroundColor: [NSColor windowBackgroundColor]];
405 [m_cocoaNSScrollView setHasHorizontalScroller: YES];
406 [m_cocoaNSScrollView setHasVerticalScroller: YES];
407 Encapsulate();
408 }
409
410 void wxWindowCocoaScrollView::Encapsulate()
411 {
412 // Set the scroll view autoresizingMask to match the current NSView
413 [m_cocoaNSScrollView setAutoresizingMask: [m_owner->GetNSView() autoresizingMask]];
414 [m_owner->GetNSView() setAutoresizingMask: NSViewNotSizable];
415 // NOTE: replaceSubView will cause m_cocaNSView to be released
416 // except when it hasn't been added into an NSView hierarchy in which
417 // case it doesn't need to be and this should work out to a no-op
418 m_owner->CocoaReplaceView(m_owner->GetNSView(), m_cocoaNSScrollView);
419 // The NSView is still retained by owner
420 [m_cocoaNSScrollView setDocumentView: m_owner->GetNSView()];
421 // Now it's also retained by the NSScrollView
422 }
423
424 void wxWindowCocoaScrollView::Unencapsulate()
425 {
426 [m_cocoaNSScrollView setDocumentView: nil];
427 m_owner->CocoaReplaceView(m_cocoaNSScrollView, m_owner->GetNSView());
428 if(![[m_owner->GetNSView() superview] isFlipped])
429 [m_owner->GetNSView() setAutoresizingMask: NSViewMinYMargin];
430 }
431
432 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
433 {
434 DisassociateNSView(m_cocoaNSScrollView);
435 [m_cocoaNSScrollView release];
436 }
437
438 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
439 {
440 NSSize frameSize = [NSScrollView
441 frameSizeForContentSize: NSMakeSize(width,height)
442 hasHorizontalScroller: [m_cocoaNSScrollView hasHorizontalScroller]
443 hasVerticalScroller: [m_cocoaNSScrollView hasVerticalScroller]
444 borderType: [m_cocoaNSScrollView borderType]];
445 width = (int)frameSize.width;
446 height = (int)frameSize.height;
447 }
448
449 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
450 {
451 NSSize nssize = [m_cocoaNSScrollView contentSize];
452 if(x)
453 *x = (int)nssize.width;
454 if(y)
455 *y = (int)nssize.height;
456 }
457
458 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
459 {
460 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_FrameChanged"));
461 wxSizeEvent event(m_owner->GetSize(), m_owner->GetId());
462 event.SetEventObject(m_owner);
463 m_owner->GetEventHandler()->ProcessEvent(event);
464 }
465
466 // ========================================================================
467 // wxWindowCocoa
468 // ========================================================================
469 // normally the base classes aren't included, but wxWindow is special
470 #ifdef __WXUNIVERSAL__
471 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
472 #else
473 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
474 #endif
475
476 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
477 END_EVENT_TABLE()
478
479 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
480
481 // Constructor
482 void wxWindowCocoa::Init()
483 {
484 m_cocoaNSView = NULL;
485 m_cocoaHider = NULL;
486 m_wxCocoaScrollView = NULL;
487 m_isBeingDeleted = false;
488 m_isInPaint = false;
489 m_visibleTrackingRectManager = NULL;
490 }
491
492 // Constructor
493 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
494 const wxPoint& pos,
495 const wxSize& size,
496 long style,
497 const wxString& name)
498 {
499 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
500 return false;
501
502 // TODO: create the window
503 m_cocoaNSView = NULL;
504 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
505 [m_cocoaNSView release];
506
507 if (m_parent)
508 {
509 m_parent->AddChild(this);
510 m_parent->CocoaAddChild(this);
511 SetInitialFrameRect(pos,size);
512 }
513
514 return true;
515 }
516
517 // Destructor
518 wxWindow::~wxWindow()
519 {
520 wxAutoNSAutoreleasePool pool;
521 DestroyChildren();
522
523 // Make sure our parent (in the wxWidgets sense) is our superview
524 // before we go removing from it.
525 if(m_parent && m_parent->GetNSView()==[GetNSViewForSuperview() superview])
526 CocoaRemoveFromParent();
527 delete m_cocoaHider;
528 delete m_wxCocoaScrollView;
529 if(m_cocoaNSView)
530 SendDestroyEvent();
531 SetNSView(NULL);
532 }
533
534 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
535 {
536 // Pool here due to lack of one during wx init phase
537 wxAutoNSAutoreleasePool pool;
538
539 NSView *childView = child->GetNSViewForSuperview();
540
541 wxASSERT(childView);
542 [m_cocoaNSView addSubview: childView];
543 child->m_isShown = !m_cocoaHider;
544 }
545
546 void wxWindowCocoa::CocoaRemoveFromParent(void)
547 {
548 [GetNSViewForSuperview() removeFromSuperview];
549 }
550
551 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
552 {
553 // Clear the visible area tracking rect if we have one.
554 delete m_visibleTrackingRectManager;
555 m_visibleTrackingRectManager = NULL;
556
557 bool need_debug = cocoaNSView || m_cocoaNSView;
558 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
559 DisassociateNSView(m_cocoaNSView);
560 [cocoaNSView retain];
561 [m_cocoaNSView release];
562 m_cocoaNSView = cocoaNSView;
563 AssociateNSView(m_cocoaNSView);
564 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
565 }
566
567 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
568 {
569 return m_cocoaHider
570 ? m_cocoaHider->GetNSView()
571 : m_wxCocoaScrollView
572 ? m_wxCocoaScrollView->GetNSScrollView()
573 : m_cocoaNSView;
574 }
575
576 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
577 {
578 return m_wxCocoaScrollView
579 ? m_wxCocoaScrollView->GetNSScrollView()
580 : m_cocoaNSView;
581 }
582
583 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
584 {
585 // TODO: Handle scrolling offset
586 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
587 }
588
589 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
590 {
591 // TODO: Handle scrolling offset
592 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
593 }
594
595 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
596 {
597 // TODO: Handle scrolling offset
598 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
599 }
600
601 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
602 {
603 // TODO: Handle scrolling offset
604 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
605 }
606
607 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
608 {
609 // TODO: Handle scrolling offset
610 NSAffineTransform *transform = wxDC::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
611 return transform;
612 }
613
614 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
615 {
616 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
617 // Recursion can happen if the event loop runs from within the paint
618 // handler. For instance, if an assertion dialog is shown.
619 // FIXME: This seems less than ideal.
620 if(m_isInPaint)
621 {
622 wxLogDebug(wxT("Paint event recursion!"));
623 return false;
624 }
625 m_isInPaint = true;
626
627 // Set m_updateRegion
628 const NSRect *rects = &rect; // The bounding box of the region
629 NSInteger countRects = 1;
630 // Try replacing the larger rectangle with a list of smaller ones:
631 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
632 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
633
634 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
635 for(int i=0; i<countRects; i++)
636 {
637 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
638 }
639 m_updateRegion = wxRegion(transformedRects,countRects);
640 free(transformedRects);
641
642 wxPaintEvent event(m_windowId);
643 event.SetEventObject(this);
644 bool ret = GetEventHandler()->ProcessEvent(event);
645 m_isInPaint = false;
646 return ret;
647 }
648
649 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
650 {
651 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
652 // Mouse events happen at the NSWindow level so we need to convert
653 // into our bounds coordinates then convert to wx coordinates.
654 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
655 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
656 // FIXME: Should we be adjusting for client area origin?
657 const wxPoint &clientorigin = GetClientAreaOrigin();
658 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
659 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
660
661 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
662 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
663 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
664 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
665
666 // TODO: set timestamp?
667 event.SetEventObject(this);
668 event.SetId(GetId());
669 }
670
671 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
672 {
673 wxMouseEvent event(wxEVT_MOTION);
674 InitMouseEvent(event,theEvent);
675 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
676 return GetEventHandler()->ProcessEvent(event);
677 }
678
679 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
680 {
681 wxMouseEvent event(wxEVT_MOTION);
682 NSWindow *window = [GetNSView() window];
683 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
684 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
685
686 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
687 // FIXME: Should we be adjusting for client area origin?
688 const wxPoint &clientorigin = GetClientAreaOrigin();
689 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
690 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
691
692 // TODO: Handle shift, control, alt, meta flags
693 event.SetEventObject(this);
694 event.SetId(GetId());
695
696 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
697 GetEventHandler()->ProcessEvent(event);
698 }
699
700 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
701 {
702 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
703 {
704 m_visibleTrackingRectManager->BeginSynthesizingEvents();
705
706 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
707 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
708 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
709 // first responder, so the mouseMoved: events are effectively discarded.
710 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
711
712 wxMouseEvent event(wxEVT_ENTER_WINDOW);
713 InitMouseEvent(event,theEvent);
714 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
715 return GetEventHandler()->ProcessEvent(event);
716 }
717 else
718 return false;
719 }
720
721 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
722 {
723 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
724 {
725 m_visibleTrackingRectManager->StopSynthesizingEvents();
726
727 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
728 InitMouseEvent(event,theEvent);
729 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
730 return GetEventHandler()->ProcessEvent(event);
731 }
732 else
733 return false;
734 }
735
736 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
737 {
738 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
739 InitMouseEvent(event,theEvent);
740 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
741 return GetEventHandler()->ProcessEvent(event);
742 }
743
744 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
745 {
746 wxMouseEvent event(wxEVT_MOTION);
747 InitMouseEvent(event,theEvent);
748 event.m_leftDown = true;
749 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
750 return GetEventHandler()->ProcessEvent(event);
751 }
752
753 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
754 {
755 wxMouseEvent event(wxEVT_LEFT_UP);
756 InitMouseEvent(event,theEvent);
757 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
758 return GetEventHandler()->ProcessEvent(event);
759 }
760
761 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
762 {
763 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
764 InitMouseEvent(event,theEvent);
765 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
766 return GetEventHandler()->ProcessEvent(event);
767 }
768
769 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
770 {
771 wxMouseEvent event(wxEVT_MOTION);
772 InitMouseEvent(event,theEvent);
773 event.m_rightDown = true;
774 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
775 return GetEventHandler()->ProcessEvent(event);
776 }
777
778 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
779 {
780 wxMouseEvent event(wxEVT_RIGHT_UP);
781 InitMouseEvent(event,theEvent);
782 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
783 return GetEventHandler()->ProcessEvent(event);
784 }
785
786 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
787 {
788 return false;
789 }
790
791 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
792 {
793 return false;
794 }
795
796 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
797 {
798 return false;
799 }
800
801 void wxWindowCocoa::Cocoa_FrameChanged(void)
802 {
803 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
804 if(m_visibleTrackingRectManager != NULL)
805 m_visibleTrackingRectManager->RebuildTrackingRect();
806 wxSizeEvent event(GetSize(), m_windowId);
807 event.SetEventObject(this);
808 GetEventHandler()->ProcessEvent(event);
809 }
810
811 bool wxWindowCocoa::Cocoa_resetCursorRects()
812 {
813 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
814
815 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
816 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
817 // mouse exited event. If the control happens to share the edge with its parent window which is
818 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
819 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
820 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
821 // which willl in turn call us before exiting for the next user event.
822
823 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
824 // our mouse exited event because the already queued event will have the old tracking rect
825 // tag. The simple solution is to only rebuild our tracking rect if we need to.
826
827 if(m_visibleTrackingRectManager != NULL)
828 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
829
830 if(!m_cursor.GetNSCursor())
831 return false;
832
833 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
834
835 return true;
836 }
837
838 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
839 {
840 if(!wxWindowBase::SetCursor(cursor))
841 return false;
842 // Invalidate the cursor rects so the cursor will change
843 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
844 return true;
845 }
846
847 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
848 {
849 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
850 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
851 if(m_visibleTrackingRectManager != NULL)
852 m_visibleTrackingRectManager->BuildTrackingRect();
853 return false;
854 }
855
856 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
857 {
858 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
859 // Clear tracking rects. It is imperative this be done before the new window is set.
860 if(m_visibleTrackingRectManager != NULL)
861 m_visibleTrackingRectManager->ClearTrackingRect();
862 return false;
863 }
864
865 bool wxWindow::Close(bool force)
866 {
867 // The only reason this function exists is that it is virtual and
868 // wxTopLevelWindowCocoa will override it.
869 return wxWindowBase::Close(force);
870 }
871
872 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
873 {
874 [[oldView superview] replaceSubview:oldView with:newView];
875 }
876
877 void wxWindow::DoEnable(bool enable)
878 {
879 CocoaSetEnabled(enable);
880 }
881
882 bool wxWindow::Show(bool show)
883 {
884 wxAutoNSAutoreleasePool pool;
885 // If the window is marked as visible, then it shouldn't have a dummy view
886 // If the window is marked hidden, then it should have a dummy view
887 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
888 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
889 // Return false if there isn't a window to show or hide
890 NSView *cocoaView = GetNSViewForHiding();
891 if(!cocoaView)
892 return false;
893 if(show)
894 {
895 // If state isn't changing, return false
896 if(!m_cocoaHider)
897 return false;
898 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
899 wxASSERT(![m_cocoaHider->GetNSView() superview]);
900 delete m_cocoaHider;
901 m_cocoaHider = NULL;
902 wxASSERT([cocoaView superview]);
903 }
904 else
905 {
906 // If state isn't changing, return false
907 if(m_cocoaHider)
908 return false;
909 m_cocoaHider = new wxWindowCocoaHider(this);
910 // NOTE: replaceSubview:with will cause m_cocaNSView to be
911 // (auto)released which balances out addSubview
912 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
913 // m_coocaNSView is now only retained by us
914 wxASSERT([m_cocoaHider->GetNSView() superview]);
915 wxASSERT(![cocoaView superview]);
916 }
917 m_isShown = show;
918 return true;
919 }
920
921 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
922 {
923 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":".");
924 int currentX, currentY;
925 int currentW, currentH;
926 DoGetPosition(&currentX, &currentY);
927 DoGetSize(&currentW, &currentH);
928 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
929 x=currentX;
930 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
931 y=currentY;
932
933 AdjustForParentClientOrigin(x,y,sizeFlags);
934
935 wxSize size(wxDefaultSize);
936
937 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
938 {
939 if(sizeFlags&wxSIZE_AUTO_WIDTH)
940 {
941 size=DoGetBestSize();
942 width=size.x;
943 }
944 else
945 width=currentW;
946 }
947 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
948 {
949 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
950 {
951 if(size.x==-1)
952 size=DoGetBestSize();
953 height=size.y;
954 }
955 else
956 height=currentH;
957 }
958 DoMoveWindow(x,y,width,height);
959 }
960
961 #if wxUSE_TOOLTIPS
962
963 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
964 {
965 wxWindowBase::DoSetToolTip(tip);
966
967 if ( m_tooltip )
968 {
969 m_tooltip->SetWindow((wxWindow *)this);
970 }
971 }
972
973 #endif
974
975 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
976 {
977 wxAutoNSAutoreleasePool pool;
978 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
979
980 NSView *nsview = GetNSViewForSuperview();
981 NSView *superview = [nsview superview];
982
983 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
984
985 NSRect oldFrameRect = [nsview frame];
986 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
987 [nsview setFrame:newFrameRect];
988 // Be sure to redraw the parent to reflect the changed position
989 [superview setNeedsDisplayInRect:oldFrameRect];
990 [superview setNeedsDisplayInRect:newFrameRect];
991 }
992
993 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
994 {
995 NSView *nsview = GetNSViewForSuperview();
996 NSView *superview = [nsview superview];
997 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
998 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
999 NSRect frameRect = [nsview frame];
1000 if(size.x!=-1)
1001 frameRect.size.width = size.x;
1002 if(size.y!=-1)
1003 frameRect.size.height = size.y;
1004 frameRect.origin.x = pos.x;
1005 frameRect.origin.y = pos.y;
1006 // Tell Cocoa to change the margin between the bottom of the superview
1007 // and the bottom of the control. Keeps the control pinned to the top
1008 // of its superview so that its position in the wxWidgets coordinate
1009 // system doesn't change.
1010 if(![superview isFlipped])
1011 [nsview setAutoresizingMask: NSViewMinYMargin];
1012 // MUST set the mask before setFrame: which can generate a size event
1013 // and cause a scroller to be added!
1014 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1015 [nsview setFrame: frameRect];
1016 }
1017
1018 // Get total size
1019 void wxWindow::DoGetSize(int *w, int *h) const
1020 {
1021 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1022 if(w)
1023 *w=(int)cocoaRect.size.width;
1024 if(h)
1025 *h=(int)cocoaRect.size.height;
1026 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1027 }
1028
1029 void wxWindow::DoGetPosition(int *x, int *y) const
1030 {
1031 NSView *nsview = GetNSViewForSuperview();
1032
1033 NSRect cocoaRect = [nsview frame];
1034 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1035 if(x)
1036 *x=(int)rectWx.origin.x;
1037 if(y)
1038 *y=(int)rectWx.origin.y;
1039 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1040 }
1041
1042 WXWidget wxWindow::GetHandle() const
1043 {
1044 return m_cocoaNSView;
1045 }
1046
1047 wxWindow* wxWindow::GetWxWindow() const
1048 {
1049 return (wxWindow*) this;
1050 }
1051
1052 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1053 {
1054 [m_cocoaNSView setNeedsDisplay:YES];
1055 }
1056
1057 void wxWindow::SetFocus()
1058 {
1059 if([GetNSView() acceptsFirstResponder])
1060 [[GetNSView() window] makeFirstResponder: GetNSView()];
1061 }
1062
1063 void wxWindow::DoCaptureMouse()
1064 {
1065 // TODO
1066 sm_capturedWindow = this;
1067 }
1068
1069 void wxWindow::DoReleaseMouse()
1070 {
1071 // TODO
1072 sm_capturedWindow = NULL;
1073 }
1074
1075 void wxWindow::DoScreenToClient(int *x, int *y) const
1076 {
1077 // Point in cocoa screen coordinates:
1078 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1079 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1080 NSWindow *theWindow = [clientView window];
1081
1082 // Point in window's base coordinate system:
1083 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1084 // Point in view's bounds coordinate system
1085 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1086 // Point in wx client coordinates:
1087 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1088 if(x!=NULL)
1089 *x = theWxClientPoint.x;
1090 if(y!=NULL)
1091 *y = theWxClientPoint.y;
1092 }
1093
1094 void wxWindow::DoClientToScreen(int *x, int *y) const
1095 {
1096 // Point in wx client coordinates
1097 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1098
1099 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1100
1101 // Point in the view's bounds coordinate system
1102 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1103
1104 // Point in the window's base coordinate system
1105 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1106
1107 NSWindow *theWindow = [clientView window];
1108 // Point in Cocoa's screen coordinates
1109 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1110
1111 // Act as though this was the origin of a 0x0 rectangle
1112 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1113
1114 // Convert that rectangle to wx coordinates
1115 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1116 if(*x)
1117 *x = theWxScreenPoint.x;
1118 if(*y)
1119 *y = theWxScreenPoint.y;
1120 }
1121
1122 // Get size *available for subwindows* i.e. excluding menu bar etc.
1123 void wxWindow::DoGetClientSize(int *x, int *y) const
1124 {
1125 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1126 if(m_wxCocoaScrollView)
1127 m_wxCocoaScrollView->DoGetClientSize(x,y);
1128 else
1129 wxWindowCocoa::DoGetSize(x,y);
1130 }
1131
1132 void wxWindow::DoSetClientSize(int width, int height)
1133 {
1134 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1135 if(m_wxCocoaScrollView)
1136 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1137 CocoaSetWxWindowSize(width,height);
1138 }
1139
1140 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1141 {
1142 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1143 }
1144
1145 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1146 {
1147 // Intentional no-op.
1148 }
1149
1150 wxString wxWindow::GetLabel() const
1151 {
1152 // General Get/Set of labels is implemented in wxControlBase
1153 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1154 return wxEmptyString;
1155 }
1156
1157 int wxWindow::GetCharHeight() const
1158 {
1159 // TODO
1160 return 10;
1161 }
1162
1163 int wxWindow::GetCharWidth() const
1164 {
1165 // TODO
1166 return 5;
1167 }
1168
1169 void wxWindow::GetTextExtent(const wxString& string, int *x, int *y,
1170 int *descent, int *externalLeading, const wxFont *theFont) const
1171 {
1172 // TODO
1173 }
1174
1175 // Coordinates relative to the window
1176 void wxWindow::WarpPointer (int x_pos, int y_pos)
1177 {
1178 // TODO
1179 }
1180
1181 int wxWindow::GetScrollPos(int orient) const
1182 {
1183 // TODO
1184 return 0;
1185 }
1186
1187 // This now returns the whole range, not just the number
1188 // of positions that we can scroll.
1189 int wxWindow::GetScrollRange(int orient) const
1190 {
1191 // TODO
1192 return 0;
1193 }
1194
1195 int wxWindow::GetScrollThumb(int orient) const
1196 {
1197 // TODO
1198 return 0;
1199 }
1200
1201 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1202 {
1203 // TODO
1204 }
1205
1206 void wxWindow::CocoaCreateNSScrollView()
1207 {
1208 if(!m_wxCocoaScrollView)
1209 {
1210 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1211 }
1212 }
1213
1214 // New function that will replace some of the above.
1215 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1216 int range, bool refresh)
1217 {
1218 CocoaCreateNSScrollView();
1219 // TODO
1220 }
1221
1222 // Does a physical scroll
1223 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1224 {
1225 // TODO
1226 }
1227
1228 void wxWindow::DoSetVirtualSize( int x, int y )
1229 {
1230 wxWindowBase::DoSetVirtualSize(x,y);
1231 CocoaCreateNSScrollView();
1232 [m_cocoaNSView setFrameSize:NSMakeSize(m_virtualSize.x,m_virtualSize.y)];
1233 }
1234
1235 bool wxWindow::SetFont(const wxFont& font)
1236 {
1237 // FIXME: We may need to handle wx font inheritance.
1238 return wxWindowBase::SetFont(font);
1239 }
1240
1241 #if 0 // these are used when debugging the algorithm.
1242 static char const * const comparisonresultStrings[] =
1243 { "<"
1244 , "=="
1245 , ">"
1246 };
1247 #endif
1248
1249 class CocoaWindowCompareContext
1250 {
1251 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1252 public:
1253 CocoaWindowCompareContext(); // Not implemented
1254 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1255 {
1256 m_target = target;
1257 // Cocoa sorts subviews in-place.. make a copy
1258 m_subviews = [subviews copy];
1259 }
1260 ~CocoaWindowCompareContext()
1261 { // release the copy
1262 [m_subviews release];
1263 }
1264 NSView* target()
1265 { return m_target; }
1266 NSArray* subviews()
1267 { return m_subviews; }
1268 /* Helper function that returns the comparison based off of the original ordering */
1269 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1270 {
1271 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1272 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1273 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1274 // likely compare higher than the other view which is reasonable considering the only way that
1275 // can happen is if the subview was added after our call to subviews but before the call to
1276 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1277 // that case should never occur anyway because that would imply a multi-threaded GUI call
1278 // which is a big no-no with Cocoa.
1279
1280 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1281 NSComparisonResult result = (firstI < secondI)
1282 ? NSOrderedAscending /* -1 */
1283 : (firstI > secondI)
1284 ? NSOrderedDescending /* 1 */
1285 : NSOrderedSame /* 0 */;
1286
1287 #if 0 // Enable this if you need to debug the algorithm.
1288 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1289 #endif
1290 return result;
1291 }
1292 private:
1293 /* The subview we are trying to Raise or Lower */
1294 NSView *m_target;
1295 /* A copy of the original array of subviews */
1296 NSArray *m_subviews;
1297 };
1298
1299 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1300 * the target view is always higher than every other view. When comparing two views neither of
1301 * which is the target, it returns the correct response based on the original ordering
1302 */
1303 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1304 {
1305 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1306 // first should be ordered higher
1307 if(first==compareContext->target())
1308 return NSOrderedDescending;
1309 // second should be ordered higher
1310 if(second==compareContext->target())
1311 return NSOrderedAscending;
1312 return compareContext->CompareUsingOriginalOrdering(first,second);
1313 }
1314
1315 // Raise the window to the top of the Z order
1316 void wxWindow::Raise()
1317 {
1318 // wxAutoNSAutoreleasePool pool;
1319 NSView *nsview = GetNSViewForSuperview();
1320 NSView *superview = [nsview superview];
1321 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1322
1323 [superview sortSubviewsUsingFunction:
1324 CocoaRaiseWindowCompareFunction
1325 context: &compareContext];
1326 }
1327
1328 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1329 * the target view is always lower than every other view. When comparing two views neither of
1330 * which is the target, it returns the correct response based on the original ordering
1331 */
1332 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1333 {
1334 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1335 // first should be ordered lower
1336 if(first==compareContext->target())
1337 return NSOrderedAscending;
1338 // second should be ordered lower
1339 if(second==compareContext->target())
1340 return NSOrderedDescending;
1341 return compareContext->CompareUsingOriginalOrdering(first,second);
1342 }
1343
1344 // Lower the window to the bottom of the Z order
1345 void wxWindow::Lower()
1346 {
1347 NSView *nsview = GetNSViewForSuperview();
1348 NSView *superview = [nsview superview];
1349 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1350
1351 #if 0
1352 NSLog(@"Target:\n%@\n", nsview);
1353 NSLog(@"Before:\n%@\n", compareContext.subviews());
1354 #endif
1355 [superview sortSubviewsUsingFunction:
1356 CocoaLowerWindowCompareFunction
1357 context: &compareContext];
1358 #if 0
1359 NSLog(@"After:\n%@\n", [superview subviews]);
1360 #endif
1361 }
1362
1363 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1364 {
1365 return false;
1366 }
1367
1368 // Get the window with the focus
1369 wxWindow *wxWindowBase::DoFindFocus()
1370 {
1371 // Basically we are somewhat emulating the responder chain here except
1372 // we are only loking for the first responder in the key window or
1373 // upon failing to find one if the main window is different we look
1374 // for the first responder in the main window.
1375
1376 // Note that the firstResponder doesn't necessarily have to be an
1377 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
1378 // NULL unless it finds its argument in its hash map.
1379
1380 wxCocoaNSView *win;
1381
1382 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
1383 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
1384 if(win)
1385 return win->GetWxWindow();
1386
1387 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
1388 if(mainWindow == keyWindow)
1389 return NULL;
1390 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
1391 if(win)
1392 return win->GetWxWindow();
1393
1394 return NULL;
1395 }
1396
1397 /* static */ wxWindow *wxWindowBase::GetCapture()
1398 {
1399 // TODO
1400 return wxWindowCocoa::sm_capturedWindow;
1401 }
1402
1403 wxWindow *wxGetActiveWindow()
1404 {
1405 // TODO
1406 return NULL;
1407 }
1408
1409 wxPoint wxGetMousePosition()
1410 {
1411 // TODO
1412 return wxDefaultPosition;
1413 }
1414
1415 wxMouseState wxGetMouseState()
1416 {
1417 wxMouseState ms;
1418 // TODO
1419 return ms;
1420 }
1421
1422 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
1423 {
1424 pt = wxGetMousePosition();
1425 return NULL;
1426 }
1427
1428 // ========================================================================
1429 // wxCocoaMouseMovedEventSynthesizer
1430 // ========================================================================
1431
1432 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
1433
1434 /* This class registers one run loop observer to cover all windows registered with it.
1435 * It will register the observer when the first view is registerd and unregister the
1436 * observer when the last view is unregistered.
1437 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
1438 * is no reason it couldn't be instantiated multiple times.
1439 */
1440 class wxCocoaMouseMovedEventSynthesizer
1441 {
1442 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
1443 public:
1444 wxCocoaMouseMovedEventSynthesizer()
1445 { m_lastScreenMouseLocation = NSZeroPoint;
1446 }
1447 ~wxCocoaMouseMovedEventSynthesizer();
1448 void RegisterWxCocoaView(wxCocoaNSView *aView);
1449 void UnregisterWxCocoaView(wxCocoaNSView *aView);
1450 void SynthesizeMouseMovedEvent();
1451
1452 protected:
1453 void AddRunLoopObserver();
1454 void RemoveRunLoopObserver();
1455 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
1456 std::list<wxCocoaNSView*> m_registeredViews;
1457 NSPoint m_lastScreenMouseLocation;
1458 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
1459 };
1460
1461 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
1462 {
1463 m_registeredViews.push_back(aView);
1464 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
1465
1466 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
1467 {
1468 AddRunLoopObserver();
1469 }
1470 }
1471
1472 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
1473 {
1474 m_registeredViews.remove(aView);
1475 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
1476 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
1477 {
1478 RemoveRunLoopObserver();
1479 }
1480 }
1481
1482 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
1483 {
1484 if(!m_registeredViews.empty())
1485 {
1486 // This means failure to clean up so we report on it as a debug message.
1487 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
1488 m_registeredViews.clear();
1489 }
1490 if(m_runLoopObserver != NULL)
1491 {
1492 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
1493 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
1494 RemoveRunLoopObserver();
1495 }
1496 }
1497
1498 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
1499 {
1500 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
1501 }
1502
1503 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
1504 {
1505 CFRunLoopObserverContext observerContext =
1506 { 0
1507 , this
1508 , NULL
1509 , NULL
1510 , NULL
1511 };
1512
1513 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
1514 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
1515
1516 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
1517 // in our observer procedure being called before the run loop has decided that it is going to return control to
1518 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
1519 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
1520
1521 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
1522 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
1523 // around to it before letting the event loop do its work. This has some very odd effects on the way the
1524 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
1525 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
1526 // time the new one is built.
1527
1528 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
1529 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
1530 // the mouseExited: message to be sent properly.
1531
1532 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
1533 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
1534 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
1535 }
1536
1537 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
1538 {
1539 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
1540 m_runLoopObserver.reset();
1541 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
1542 }
1543
1544 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
1545 {
1546 NSPoint screenMouseLocation = [NSEvent mouseLocation];
1547 // Checking the last mouse location is done for a few reasons:
1548 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
1549 // telling the app the mouse moved when the user hit a key for instance.
1550 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
1551 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
1552 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
1553 // loop causing the tracking rectangles to constantly be reset.
1554 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
1555 {
1556 m_lastScreenMouseLocation = screenMouseLocation;
1557 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
1558 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
1559 {
1560 (*i)->Cocoa_synthesizeMouseMoved();
1561 }
1562 }
1563 }
1564
1565 // Singleton used for all views:
1566 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
1567
1568 // ========================================================================
1569 // wxCocoaTrackingRectManager
1570 // ========================================================================
1571
1572 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
1573 : m_window(window)
1574 {
1575 m_isTrackingRectActive = false;
1576 BuildTrackingRect();
1577 }
1578
1579 void wxCocoaTrackingRectManager::ClearTrackingRect()
1580 {
1581 if(m_isTrackingRectActive)
1582 {
1583 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
1584 m_isTrackingRectActive = false;
1585 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
1586 }
1587 // If we were doing periodic events we need to clear those too
1588 StopSynthesizingEvents();
1589 }
1590
1591 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
1592 {
1593 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
1594 }
1595
1596 void wxCocoaTrackingRectManager::BuildTrackingRect()
1597 {
1598 // Pool here due to lack of one during wx init phase
1599 wxAutoNSAutoreleasePool pool;
1600
1601 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
1602
1603 NSView *theView = m_window->GetNSView();
1604
1605 if([theView window] != nil)
1606 {
1607 NSRect visibleRect = [theView visibleRect];
1608
1609 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
1610 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
1611 m_isTrackingRectActive = true;
1612
1613 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
1614 }
1615 }
1616
1617 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
1618 {
1619 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
1620 }
1621
1622 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
1623 {
1624 if(m_isTrackingRectActive)
1625 {
1626 NSView *theView = m_window->GetNSView();
1627 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
1628 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
1629 {
1630 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
1631 return;
1632 }
1633 }
1634 RebuildTrackingRect();
1635 }
1636
1637 void wxCocoaTrackingRectManager::RebuildTrackingRect()
1638 {
1639 ClearTrackingRect();
1640 BuildTrackingRect();
1641 }
1642
1643 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
1644 {
1645 ClearTrackingRect();
1646 }
1647
1648 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
1649 {
1650 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);
1651 }
1652