1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/cocoa/window.mm
3 // Purpose: wxWindowCocoa
4 // Author: David Elliott
8 // Copyright: (c) 2002 David Elliott
9 // Licence: wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
12 #include "wx/wxprec.h"
16 #include "wx/window.h"
21 #include "wx/tooltip.h"
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"
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>
40 // Turn this on to paint green over the dummy views for debugging
41 #undef WXCOCOA_FILL_DUMMY_VIEW
43 #ifdef WXCOCOA_FILL_DUMMY_VIEW
44 #import <AppKit/NSBezierPath.h>
45 #endif //def WXCOCOA_FILL_DUMMY_VIEW
47 // STL list used by wxCocoaMouseMovedEventSynthesizer
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.
56 #if defined(NSINTEGER_DEFINED)
57 typedef NSComparisonResult CocoaWindowCompareFunctionResult;
59 typedef int CocoaWindowCompareFunctionResult;
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;
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)
73 wxCHECK_MSG(nsview, pointBounds, wxT("Need to have a Cocoa view to do translation"));
74 if([nsview isFlipped])
76 NSRect ourBounds = [nsview bounds];
79 , ourBounds.size.height - pointBounds.y
83 NSRect CocoaTransformNSViewBoundsToWx(NSView *nsview, NSRect rectBounds)
85 wxCHECK_MSG(nsview, rectBounds, wxT("Need to have a Cocoa view to do translation"));
86 if([nsview isFlipped])
88 NSRect ourBounds = [nsview bounds];
91 , ourBounds.size.height - (rectBounds.origin.y + rectBounds.size.height)
92 , rectBounds.size.width
93 , rectBounds.size.height
97 NSPoint CocoaTransformNSViewWxToBounds(NSView *nsview, NSPoint pointWx)
99 wxCHECK_MSG(nsview, pointWx, wxT("Need to have a Cocoa view to do translation"));
100 if([nsview isFlipped])
102 NSRect ourBounds = [nsview bounds];
105 , ourBounds.size.height - pointWx.y
109 NSRect CocoaTransformNSViewWxToBounds(NSView *nsview, NSRect rectWx)
111 wxCHECK_MSG(nsview, rectWx, wxT("Need to have a Cocoa view to do translation"));
112 if([nsview isFlipped])
114 NSRect ourBounds = [nsview bounds];
117 , ourBounds.size.height - (rectWx.origin.y + rectWx.size.height)
123 // ============================================================================
124 // Screen coordinate helpers
125 // ============================================================================
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.
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.
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.
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.
146 namespace { // file namespace
148 class wxCocoaPrivateScreenCoordinateTransformer
150 DECLARE_NO_COPY_CLASS(wxCocoaPrivateScreenCoordinateTransformer)
152 wxCocoaPrivateScreenCoordinateTransformer();
153 ~wxCocoaPrivateScreenCoordinateTransformer();
154 wxPoint OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame);
155 NSPoint OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible);
158 NSScreen *m_screenZero;
159 NSRect m_screenZeroFrame;
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
165 inline wxCocoaPrivateScreenCoordinateTransformer::wxCocoaPrivateScreenCoordinateTransformer()
167 NSArray *screens = [NSScreen screens];
172 if(screens != nil && [screens count] > 0)
173 m_screenZero = [[screens objectAtIndex:0] retain];
177 if(m_screenZero != nil)
178 m_screenZeroFrame = [m_screenZero frame];
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);
189 inline wxCocoaPrivateScreenCoordinateTransformer::~wxCocoaPrivateScreenCoordinateTransformer()
191 [m_screenZero release];
195 inline wxPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
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.
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;
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.
212 theWxOrigin.y = (m_screenZeroFrame.origin.y + m_screenZeroFrame.size.height) - (windowFrame.origin.y + windowFrame.size.height);
217 inline NSPoint wxCocoaPrivateScreenCoordinateTransformer::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
219 NSPoint theCocoaOrigin;
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)
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))
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)
236 if(y >= 0 && y < visibleOrigin.y)
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;
249 return theCocoaOrigin;
254 wxPoint wxWindowCocoa::OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(NSRect windowFrame)
256 wxCocoaPrivateScreenCoordinateTransformer transformer;
257 return transformer.OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(windowFrame);
260 NSPoint wxWindowCocoa::OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(wxCoord x, wxCoord y, wxCoord width, wxCoord height, bool keepOriginVisible)
262 wxCocoaPrivateScreenCoordinateTransformer transformer;
263 return transformer.OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x,y,width,height,keepOriginVisible);
266 // ========================================================================
267 // wxWindowCocoaHider
268 // ========================================================================
269 class wxWindowCocoaHider: protected wxCocoaNSView
271 DECLARE_NO_COPY_CLASS(wxWindowCocoaHider)
273 wxWindowCocoaHider(wxWindow *owner);
274 virtual ~wxWindowCocoaHider();
275 inline WX_NSView GetNSView() { return m_dummyNSView; }
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
285 wxWindowCocoaHider();
288 // ========================================================================
289 // wxWindowCocoaScrollView
290 // ========================================================================
291 class wxWindowCocoaScrollView: protected wxCocoaNSView
293 DECLARE_NO_COPY_CLASS(wxWindowCocoaScrollView)
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;
301 void Unencapsulate();
303 wxWindowCocoa *m_owner;
304 WX_NSScrollView m_cocoaNSScrollView;
305 virtual void Cocoa_FrameChanged(void);
306 virtual void Cocoa_synthesizeMouseMoved(void) {}
308 wxWindowCocoaScrollView();
311 // ========================================================================
313 // ========================================================================
314 @interface wxDummyNSView : NSView
315 - (NSView *)hitTest:(NSPoint)aPoint;
317 WX_DECLARE_GET_OBJC_CLASS(wxDummyNSView,NSView)
319 @implementation wxDummyNSView : NSView
320 - (NSView *)hitTest:(NSPoint)aPoint
326 WX_IMPLEMENT_GET_OBJC_CLASS(wxDummyNSView,NSView)
328 // ========================================================================
329 // wxWindowCocoaHider
330 // ========================================================================
331 wxWindowCocoaHider::wxWindowCocoaHider(wxWindow *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);
342 wxWindowCocoaHider::~wxWindowCocoaHider()
344 DisassociateNSView(m_dummyNSView);
345 [m_dummyNSView release];
348 void wxWindowCocoaHider::Cocoa_FrameChanged(void)
350 // Keep the real window in synch with the dummy
351 wxASSERT(m_dummyNSView);
352 [m_owner->GetNSViewForHiding() setFrame:[m_dummyNSView frame]];
356 #ifdef WXCOCOA_FILL_DUMMY_VIEW
357 bool wxWindowCocoaHider::Cocoa_drawRect(const NSRect& rect)
359 NSBezierPath *bezpath = [NSBezierPath bezierPathWithRect:rect];
360 [[NSColor greenColor] set];
365 #endif //def WXCOCOA_FILL_DUMMY_VIEW
367 // ========================================================================
368 // wxFlippedNSClipView
369 // ========================================================================
370 @interface wxFlippedNSClipView : NSClipView
373 WX_DECLARE_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
375 @implementation wxFlippedNSClipView : NSClipView
382 WX_IMPLEMENT_GET_OBJC_CLASS(wxFlippedNSClipView,NSClipView)
384 // ========================================================================
385 // wxWindowCocoaScrollView
386 // ========================================================================
387 wxWindowCocoaScrollView::wxWindowCocoaScrollView(wxWindow *owner)
390 wxAutoNSAutoreleasePool pool;
392 wxASSERT(owner->GetNSView());
393 m_cocoaNSScrollView = [[NSScrollView alloc]
394 initWithFrame:[owner->GetNSView() frame]];
395 AssociateNSView(m_cocoaNSScrollView);
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];
404 [m_cocoaNSScrollView setBackgroundColor: [NSColor windowBackgroundColor]];
405 [m_cocoaNSScrollView setHasHorizontalScroller: YES];
406 [m_cocoaNSScrollView setHasVerticalScroller: YES];
410 void wxWindowCocoaScrollView::Encapsulate()
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
424 void wxWindowCocoaScrollView::Unencapsulate()
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];
432 wxWindowCocoaScrollView::~wxWindowCocoaScrollView()
434 DisassociateNSView(m_cocoaNSScrollView);
435 [m_cocoaNSScrollView release];
438 void wxWindowCocoaScrollView::ClientSizeToSize(int &width, int &height)
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;
449 void wxWindowCocoaScrollView::DoGetClientSize(int *x, int *y) const
451 NSSize nssize = [m_cocoaNSScrollView contentSize];
453 *x = (int)nssize.width;
455 *y = (int)nssize.height;
458 void wxWindowCocoaScrollView::Cocoa_FrameChanged(void)
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);
466 // ========================================================================
468 // ========================================================================
469 // normally the base classes aren't included, but wxWindow is special
470 #ifdef __WXUNIVERSAL__
471 IMPLEMENT_ABSTRACT_CLASS(wxWindowCocoa, wxWindowBase)
473 IMPLEMENT_DYNAMIC_CLASS(wxWindow, wxWindowBase)
476 BEGIN_EVENT_TABLE(wxWindowCocoa, wxWindowBase)
479 wxWindow *wxWindowCocoa::sm_capturedWindow = NULL;
482 void wxWindowCocoa::Init()
484 m_cocoaNSView = NULL;
486 m_wxCocoaScrollView = NULL;
487 m_isBeingDeleted = false;
489 m_visibleTrackingRectManager = NULL;
493 bool wxWindow::Create(wxWindow *parent, wxWindowID winid,
497 const wxString& name)
499 if(!CreateBase(parent,winid,pos,size,style,wxDefaultValidator,name))
502 // TODO: create the window
503 m_cocoaNSView = NULL;
504 SetNSView([[WX_GET_OBJC_CLASS(WXNSView) alloc] initWithFrame: MakeDefaultNSRect(size)]);
505 [m_cocoaNSView release];
509 m_parent->AddChild(this);
510 m_parent->CocoaAddChild(this);
511 SetInitialFrameRect(pos,size);
518 wxWindow::~wxWindow()
520 wxAutoNSAutoreleasePool pool;
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();
528 delete m_wxCocoaScrollView;
534 void wxWindowCocoa::CocoaAddChild(wxWindowCocoa *child)
536 // Pool here due to lack of one during wx init phase
537 wxAutoNSAutoreleasePool pool;
539 NSView *childView = child->GetNSViewForSuperview();
542 [m_cocoaNSView addSubview: childView];
545 void wxWindowCocoa::CocoaRemoveFromParent(void)
547 [GetNSViewForSuperview() removeFromSuperview];
550 void wxWindowCocoa::SetNSView(WX_NSView cocoaNSView)
552 // Clear the visible area tracking rect if we have one.
553 delete m_visibleTrackingRectManager;
554 m_visibleTrackingRectManager = NULL;
556 bool need_debug = cocoaNSView || m_cocoaNSView;
557 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [m_cocoaNSView=%p retainCount]=%d"),this,m_cocoaNSView,[m_cocoaNSView retainCount]);
558 DisassociateNSView(m_cocoaNSView);
559 [cocoaNSView retain];
560 [m_cocoaNSView release];
561 m_cocoaNSView = cocoaNSView;
562 AssociateNSView(m_cocoaNSView);
563 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxWindowCocoa=%p::SetNSView [cocoaNSView=%p retainCount]=%d"),this,cocoaNSView,[cocoaNSView retainCount]);
566 WX_NSView wxWindowCocoa::GetNSViewForSuperview() const
569 ? m_cocoaHider->GetNSView()
570 : m_wxCocoaScrollView
571 ? m_wxCocoaScrollView->GetNSScrollView()
575 WX_NSView wxWindowCocoa::GetNSViewForHiding() const
577 return m_wxCocoaScrollView
578 ? m_wxCocoaScrollView->GetNSScrollView()
582 NSPoint wxWindowCocoa::CocoaTransformBoundsToWx(NSPoint pointBounds)
584 // TODO: Handle scrolling offset
585 return CocoaTransformNSViewBoundsToWx(GetNSView(), pointBounds);
588 NSRect wxWindowCocoa::CocoaTransformBoundsToWx(NSRect rectBounds)
590 // TODO: Handle scrolling offset
591 return CocoaTransformNSViewBoundsToWx(GetNSView(), rectBounds);
594 NSPoint wxWindowCocoa::CocoaTransformWxToBounds(NSPoint pointWx)
596 // TODO: Handle scrolling offset
597 return CocoaTransformNSViewWxToBounds(GetNSView(), pointWx);
600 NSRect wxWindowCocoa::CocoaTransformWxToBounds(NSRect rectWx)
602 // TODO: Handle scrolling offset
603 return CocoaTransformNSViewWxToBounds(GetNSView(), rectWx);
606 WX_NSAffineTransform wxWindowCocoa::CocoaGetWxToBoundsTransform()
608 // TODO: Handle scrolling offset
609 NSAffineTransform *transform = wxDC::CocoaGetWxToBoundsTransform([GetNSView() isFlipped], [GetNSView() bounds].size.height);
613 bool wxWindowCocoa::Cocoa_drawRect(const NSRect &rect)
615 wxLogTrace(wxTRACE_COCOA,wxT("Cocoa_drawRect"));
616 // Recursion can happen if the event loop runs from within the paint
617 // handler. For instance, if an assertion dialog is shown.
618 // FIXME: This seems less than ideal.
621 wxLogDebug(wxT("Paint event recursion!"));
626 // Set m_updateRegion
627 const NSRect *rects = ▭ // The bounding box of the region
628 NSInteger countRects = 1;
629 // Try replacing the larger rectangle with a list of smaller ones:
630 if ([GetNSView() respondsToSelector:@selector(getRectsBeingDrawn:count:)])
631 [GetNSView() getRectsBeingDrawn:&rects count:&countRects];
633 NSRect *transformedRects = (NSRect*)malloc(sizeof(NSRect)*countRects);
634 for(int i=0; i<countRects; i++)
636 transformedRects[i] = CocoaTransformBoundsToWx(rects[i]);
638 m_updateRegion = wxRegion(transformedRects,countRects);
639 free(transformedRects);
641 wxPaintEvent event(m_windowId);
642 event.SetEventObject(this);
643 bool ret = GetEventHandler()->ProcessEvent(event);
648 void wxWindowCocoa::InitMouseEvent(wxMouseEvent& event, WX_NSEvent cocoaEvent)
650 wxASSERT_MSG([m_cocoaNSView window]==[cocoaEvent window],wxT("Mouse event for different NSWindow"));
651 // Mouse events happen at the NSWindow level so we need to convert
652 // into our bounds coordinates then convert to wx coordinates.
653 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:[(NSEvent*)cocoaEvent locationInWindow] fromView:nil];
654 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
655 // FIXME: Should we be adjusting for client area origin?
656 const wxPoint &clientorigin = GetClientAreaOrigin();
657 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
658 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
660 event.m_shiftDown = [cocoaEvent modifierFlags] & NSShiftKeyMask;
661 event.m_controlDown = [cocoaEvent modifierFlags] & NSControlKeyMask;
662 event.m_altDown = [cocoaEvent modifierFlags] & NSAlternateKeyMask;
663 event.m_metaDown = [cocoaEvent modifierFlags] & NSCommandKeyMask;
665 // TODO: set timestamp?
666 event.SetEventObject(this);
667 event.SetId(GetId());
670 bool wxWindowCocoa::Cocoa_mouseMoved(WX_NSEvent theEvent)
672 wxMouseEvent event(wxEVT_MOTION);
673 InitMouseEvent(event,theEvent);
674 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_mouseMoved @%d,%d"),this,event.m_x,event.m_y);
675 return GetEventHandler()->ProcessEvent(event);
678 void wxWindowCocoa::Cocoa_synthesizeMouseMoved()
680 wxMouseEvent event(wxEVT_MOTION);
681 NSWindow *window = [GetNSView() window];
682 NSPoint locationInWindow = [window mouseLocationOutsideOfEventStream];
683 NSPoint cocoaPoint = [m_cocoaNSView convertPoint:locationInWindow fromView:nil];
685 NSPoint pointWx = CocoaTransformBoundsToWx(cocoaPoint);
686 // FIXME: Should we be adjusting for client area origin?
687 const wxPoint &clientorigin = GetClientAreaOrigin();
688 event.m_x = (wxCoord)pointWx.x - clientorigin.x;
689 event.m_y = (wxCoord)pointWx.y - clientorigin.y;
691 // TODO: Handle shift, control, alt, meta flags
692 event.SetEventObject(this);
693 event.SetId(GetId());
695 wxLogTrace(wxTRACE_COCOA,wxT("wxwin=%p Synthesized Mouse Moved @%d,%d"),this,event.m_x,event.m_y);
696 GetEventHandler()->ProcessEvent(event);
699 bool wxWindowCocoa::Cocoa_mouseEntered(WX_NSEvent theEvent)
701 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
703 m_visibleTrackingRectManager->BeginSynthesizingEvents();
705 // Although we synthesize the mouse moved events we don't poll for them but rather send them only when
706 // some other event comes in. That other event is (guess what) mouse moved events that will be sent
707 // to the NSWindow which will forward them on to the first responder. We are not likely to be the
708 // first responder, so the mouseMoved: events are effectively discarded.
709 [[GetNSView() window] setAcceptsMouseMovedEvents:YES];
711 wxMouseEvent event(wxEVT_ENTER_WINDOW);
712 InitMouseEvent(event,theEvent);
713 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Entered TR#%d @%d,%d"),this,[theEvent trackingNumber], event.m_x,event.m_y);
714 return GetEventHandler()->ProcessEvent(event);
720 bool wxWindowCocoa::Cocoa_mouseExited(WX_NSEvent theEvent)
722 if(m_visibleTrackingRectManager != NULL && m_visibleTrackingRectManager->IsOwnerOfEvent(theEvent))
724 m_visibleTrackingRectManager->StopSynthesizingEvents();
726 wxMouseEvent event(wxEVT_LEAVE_WINDOW);
727 InitMouseEvent(event,theEvent);
728 wxLogTrace(wxTRACE_COCOA_TrackingRect,wxT("wxwin=%p Mouse Exited TR#%d @%d,%d"),this,[theEvent trackingNumber],event.m_x,event.m_y);
729 return GetEventHandler()->ProcessEvent(event);
735 bool wxWindowCocoa::Cocoa_mouseDown(WX_NSEvent theEvent)
737 wxMouseEvent event([theEvent clickCount]<2?wxEVT_LEFT_DOWN:wxEVT_LEFT_DCLICK);
738 InitMouseEvent(event,theEvent);
739 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
740 return GetEventHandler()->ProcessEvent(event);
743 bool wxWindowCocoa::Cocoa_mouseDragged(WX_NSEvent theEvent)
745 wxMouseEvent event(wxEVT_MOTION);
746 InitMouseEvent(event,theEvent);
747 event.m_leftDown = true;
748 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
749 return GetEventHandler()->ProcessEvent(event);
752 bool wxWindowCocoa::Cocoa_mouseUp(WX_NSEvent theEvent)
754 wxMouseEvent event(wxEVT_LEFT_UP);
755 InitMouseEvent(event,theEvent);
756 wxLogTrace(wxTRACE_COCOA,wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
757 return GetEventHandler()->ProcessEvent(event);
760 bool wxWindowCocoa::Cocoa_rightMouseDown(WX_NSEvent theEvent)
762 wxMouseEvent event([theEvent clickCount]<2?wxEVT_RIGHT_DOWN:wxEVT_RIGHT_DCLICK);
763 InitMouseEvent(event,theEvent);
764 wxLogDebug(wxT("Mouse Down @%d,%d num clicks=%d"),event.m_x,event.m_y,[theEvent clickCount]);
765 return GetEventHandler()->ProcessEvent(event);
768 bool wxWindowCocoa::Cocoa_rightMouseDragged(WX_NSEvent theEvent)
770 wxMouseEvent event(wxEVT_MOTION);
771 InitMouseEvent(event,theEvent);
772 event.m_rightDown = true;
773 wxLogDebug(wxT("Mouse Drag @%d,%d"),event.m_x,event.m_y);
774 return GetEventHandler()->ProcessEvent(event);
777 bool wxWindowCocoa::Cocoa_rightMouseUp(WX_NSEvent theEvent)
779 wxMouseEvent event(wxEVT_RIGHT_UP);
780 InitMouseEvent(event,theEvent);
781 wxLogDebug(wxT("Mouse Up @%d,%d"),event.m_x,event.m_y);
782 return GetEventHandler()->ProcessEvent(event);
785 bool wxWindowCocoa::Cocoa_otherMouseDown(WX_NSEvent theEvent)
790 bool wxWindowCocoa::Cocoa_otherMouseDragged(WX_NSEvent theEvent)
795 bool wxWindowCocoa::Cocoa_otherMouseUp(WX_NSEvent theEvent)
800 void wxWindowCocoa::Cocoa_FrameChanged(void)
802 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_FrameChanged"),this);
803 if(m_visibleTrackingRectManager != NULL)
804 m_visibleTrackingRectManager->RebuildTrackingRect();
805 wxSizeEvent event(GetSize(), m_windowId);
806 event.SetEventObject(this);
807 GetEventHandler()->ProcessEvent(event);
810 bool wxWindowCocoa::Cocoa_resetCursorRects()
812 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::Cocoa_resetCursorRects"),this);
814 // When we are called there may be a queued tracking rect event (mouse entered or exited) and
815 // we won't know it. A specific example is wxGenericHyperlinkCtrl changing the cursor from its
816 // mouse exited event. If the control happens to share the edge with its parent window which is
817 // also tracking mouse events then Cocoa receives two mouse exited events from the window server.
818 // The first one will cause wxGenericHyperlinkCtrl to call wxWindow::SetCursor which will
819 // invaildate the cursor rect causing Cocoa to schedule cursor rect reset with the run loop
820 // which willl in turn call us before exiting for the next user event.
822 // If we are the parent window then rebuilding our tracking rectangle will cause us to miss
823 // our mouse exited event because the already queued event will have the old tracking rect
824 // tag. The simple solution is to only rebuild our tracking rect if we need to.
826 if(m_visibleTrackingRectManager != NULL)
827 m_visibleTrackingRectManager->RebuildTrackingRectIfNeeded();
829 if(!m_cursor.GetNSCursor())
832 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
837 bool wxWindowCocoa::SetCursor(const wxCursor &cursor)
839 if(!wxWindowBase::SetCursor(cursor))
842 // Set up the cursor rect so that invalidateCursorRectsForView: will destroy it.
843 // If we don't do this then Cocoa thinks (rightly) that we don't have any cursor
844 // rects and thus won't ever call resetCursorRects.
845 [GetNSView() addCursorRect: [GetNSView() visibleRect] cursor: m_cursor.GetNSCursor()];
847 // Invalidate the cursor rects so the cursor will change
848 // Note that it is not enough to remove the old one (if any) and add the new one.
849 // For the rects to work properly, Cocoa itself must call resetCursorRects.
850 [[GetNSView() window] invalidateCursorRectsForView:GetNSView()];
854 bool wxWindowCocoa::Cocoa_viewDidMoveToWindow()
856 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewDidMoveToWindow"),this);
857 // Set up new tracking rects. I am reasonably sure the new window must be set before doing this.
858 if(m_visibleTrackingRectManager != NULL)
859 m_visibleTrackingRectManager->BuildTrackingRect();
863 bool wxWindowCocoa::Cocoa_viewWillMoveToWindow(WX_NSWindow newWindow)
865 wxLogTrace(wxTRACE_COCOA,wxT("wxWindow=%p::viewWillMoveToWindow:%p"),this, newWindow);
866 // Clear tracking rects. It is imperative this be done before the new window is set.
867 if(m_visibleTrackingRectManager != NULL)
868 m_visibleTrackingRectManager->ClearTrackingRect();
872 bool wxWindow::Close(bool force)
874 // The only reason this function exists is that it is virtual and
875 // wxTopLevelWindowCocoa will override it.
876 return wxWindowBase::Close(force);
879 void wxWindow::CocoaReplaceView(WX_NSView oldView, WX_NSView newView)
881 [[oldView superview] replaceSubview:oldView with:newView];
884 void wxWindow::DoEnable(bool enable)
886 CocoaSetEnabled(enable);
889 bool wxWindow::Show(bool show)
891 wxAutoNSAutoreleasePool pool;
892 // If the window is marked as visible, then it shouldn't have a dummy view
893 // If the window is marked hidden, then it should have a dummy view
894 // wxSpinCtrl (generic) abuses m_isShown, don't use it for any logic
895 // wxASSERT_MSG( (m_isShown && !m_dummyNSView) || (!m_isShown && m_dummyNSView),wxT("wxWindow: m_isShown does not agree with m_dummyNSView"));
896 // Return false if there isn't a window to show or hide
897 NSView *cocoaView = GetNSViewForHiding();
902 // If state isn't changing, return false
905 CocoaReplaceView(m_cocoaHider->GetNSView(), cocoaView);
906 wxASSERT(![m_cocoaHider->GetNSView() superview]);
909 wxASSERT([cocoaView superview]);
913 // If state isn't changing, return false
916 m_cocoaHider = new wxWindowCocoaHider(this);
917 // NOTE: replaceSubview:with will cause m_cocaNSView to be
918 // (auto)released which balances out addSubview
919 CocoaReplaceView(cocoaView, m_cocoaHider->GetNSView());
920 // m_coocaNSView is now only retained by us
921 wxASSERT([m_cocoaHider->GetNSView() superview]);
922 wxASSERT(![cocoaView superview]);
928 void wxWindowCocoa::DoSetSize(int x, int y, int width, int height, int sizeFlags)
930 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":".");
931 int currentX, currentY;
932 int currentW, currentH;
933 DoGetPosition(¤tX, ¤tY);
934 DoGetSize(¤tW, ¤tH);
935 if((x==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
937 if((y==-1) && !(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
940 AdjustForParentClientOrigin(x,y,sizeFlags);
942 wxSize size(wxDefaultSize);
944 if((width==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
946 if(sizeFlags&wxSIZE_AUTO_WIDTH)
948 size=DoGetBestSize();
954 if((height==-1)&&!(sizeFlags&wxSIZE_ALLOW_MINUS_ONE))
956 if(sizeFlags&wxSIZE_AUTO_HEIGHT)
959 size=DoGetBestSize();
965 DoMoveWindow(x,y,width,height);
970 void wxWindowCocoa::DoSetToolTip( wxToolTip *tip )
972 wxWindowBase::DoSetToolTip(tip);
976 m_tooltip->SetWindow((wxWindow *)this);
982 void wxWindowCocoa::DoMoveWindow(int x, int y, int width, int height)
984 wxAutoNSAutoreleasePool pool;
985 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoMoveWindow(%d,%d,%d,%d)"),this,x,y,width,height);
987 NSView *nsview = GetNSViewForSuperview();
988 NSView *superview = [nsview superview];
990 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
992 NSRect oldFrameRect = [nsview frame];
993 NSRect newFrameRect = GetParent()->CocoaTransformWxToBounds(NSMakeRect(x,y,width,height));
994 [nsview setFrame:newFrameRect];
995 // Be sure to redraw the parent to reflect the changed position
996 [superview setNeedsDisplayInRect:oldFrameRect];
997 [superview setNeedsDisplayInRect:newFrameRect];
1000 void wxWindowCocoa::SetInitialFrameRect(const wxPoint& pos, const wxSize& size)
1002 NSView *nsview = GetNSViewForSuperview();
1003 NSView *superview = [nsview superview];
1004 wxCHECK_RET(superview,wxT("NSView does not have a superview"));
1005 wxCHECK_RET(GetParent(), wxT("Window can only be placed correctly when it has a parent"));
1006 NSRect frameRect = [nsview frame];
1008 frameRect.size.width = size.x;
1010 frameRect.size.height = size.y;
1011 frameRect.origin.x = pos.x;
1012 frameRect.origin.y = pos.y;
1013 // Tell Cocoa to change the margin between the bottom of the superview
1014 // and the bottom of the control. Keeps the control pinned to the top
1015 // of its superview so that its position in the wxWidgets coordinate
1016 // system doesn't change.
1017 if(![superview isFlipped])
1018 [nsview setAutoresizingMask: NSViewMinYMargin];
1019 // MUST set the mask before setFrame: which can generate a size event
1020 // and cause a scroller to be added!
1021 frameRect = GetParent()->CocoaTransformWxToBounds(frameRect);
1022 [nsview setFrame: frameRect];
1026 void wxWindow::DoGetSize(int *w, int *h) const
1028 NSRect cocoaRect = [GetNSViewForSuperview() frame];
1030 *w=(int)cocoaRect.size.width;
1032 *h=(int)cocoaRect.size.height;
1033 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetSize = (%d,%d)"),this,(int)cocoaRect.size.width,(int)cocoaRect.size.height);
1036 void wxWindow::DoGetPosition(int *x, int *y) const
1038 NSView *nsview = GetNSViewForSuperview();
1040 NSRect cocoaRect = [nsview frame];
1041 NSRect rectWx = GetParent()->CocoaTransformBoundsToWx(cocoaRect);
1043 *x=(int)rectWx.origin.x;
1045 *y=(int)rectWx.origin.y;
1046 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("wxWindow=%p::DoGetPosition = (%d,%d)"),this,(int)cocoaRect.origin.x,(int)cocoaRect.origin.y);
1049 WXWidget wxWindow::GetHandle() const
1051 return m_cocoaNSView;
1054 wxWindow* wxWindow::GetWxWindow() const
1056 return (wxWindow*) this;
1059 void wxWindow::Refresh(bool eraseBack, const wxRect *rect)
1061 [m_cocoaNSView setNeedsDisplay:YES];
1064 void wxWindow::SetFocus()
1066 if([GetNSView() acceptsFirstResponder])
1067 [[GetNSView() window] makeFirstResponder: GetNSView()];
1070 void wxWindow::DoCaptureMouse()
1073 sm_capturedWindow = this;
1076 void wxWindow::DoReleaseMouse()
1079 sm_capturedWindow = NULL;
1082 void wxWindow::DoScreenToClient(int *x, int *y) const
1084 // Point in cocoa screen coordinates:
1085 NSPoint cocoaScreenPoint = OriginInCocoaScreenCoordinatesForRectInWxDisplayCoordinates(x!=NULL?*x:0, y!=NULL?*y:0, 0, 0, false);
1086 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1087 NSWindow *theWindow = [clientView window];
1089 // Point in window's base coordinate system:
1090 NSPoint windowPoint = [theWindow convertScreenToBase:cocoaScreenPoint];
1091 // Point in view's bounds coordinate system
1092 NSPoint boundsPoint = [clientView convertPoint:windowPoint fromView:nil];
1093 // Point in wx client coordinates:
1094 NSPoint theWxClientPoint = CocoaTransformNSViewBoundsToWx(clientView, boundsPoint);
1096 *x = theWxClientPoint.x;
1098 *y = theWxClientPoint.y;
1101 void wxWindow::DoClientToScreen(int *x, int *y) const
1103 // Point in wx client coordinates
1104 NSPoint theWxClientPoint = NSMakePoint(x!=NULL?*x:0, y!=NULL?*y:0);
1106 NSView *clientView = const_cast<wxWindow*>(this)->GetNSView();
1108 // Point in the view's bounds coordinate system
1109 NSPoint boundsPoint = CocoaTransformNSViewWxToBounds(clientView, theWxClientPoint);
1111 // Point in the window's base coordinate system
1112 NSPoint windowPoint = [clientView convertPoint:boundsPoint toView:nil];
1114 NSWindow *theWindow = [clientView window];
1115 // Point in Cocoa's screen coordinates
1116 NSPoint screenPoint = [theWindow convertBaseToScreen:windowPoint];
1118 // Act as though this was the origin of a 0x0 rectangle
1119 NSRect screenPointRect = NSMakeRect(screenPoint.x, screenPoint.y, 0, 0);
1121 // Convert that rectangle to wx coordinates
1122 wxPoint theWxScreenPoint = OriginInWxDisplayCoordinatesForRectInCocoaScreenCoordinates(screenPointRect);
1124 *x = theWxScreenPoint.x;
1126 *y = theWxScreenPoint.y;
1129 // Get size *available for subwindows* i.e. excluding menu bar etc.
1130 void wxWindow::DoGetClientSize(int *x, int *y) const
1132 wxLogTrace(wxTRACE_COCOA,wxT("DoGetClientSize:"));
1133 if(m_wxCocoaScrollView)
1134 m_wxCocoaScrollView->DoGetClientSize(x,y);
1136 wxWindowCocoa::DoGetSize(x,y);
1139 void wxWindow::DoSetClientSize(int width, int height)
1141 wxLogTrace(wxTRACE_COCOA_Window_Size,wxT("DoSetClientSize=(%d,%d)"),width,height);
1142 if(m_wxCocoaScrollView)
1143 m_wxCocoaScrollView->ClientSizeToSize(width,height);
1144 CocoaSetWxWindowSize(width,height);
1147 void wxWindow::CocoaSetWxWindowSize(int width, int height)
1149 wxWindowCocoa::DoSetSize(wxDefaultCoord,wxDefaultCoord,width,height,wxSIZE_USE_EXISTING);
1152 void wxWindow::SetLabel(const wxString& WXUNUSED(label))
1154 // Intentional no-op.
1157 wxString wxWindow::GetLabel() const
1159 // General Get/Set of labels is implemented in wxControlBase
1160 wxLogDebug(wxT("wxWindow::GetLabel: Should be overridden if needed."));
1161 return wxEmptyString;
1164 int wxWindow::GetCharHeight() const
1170 int wxWindow::GetCharWidth() const
1176 void wxWindow::GetTextExtent(const wxString& string, int *outX, int *outY,
1177 int *outDescent, int *outExternalLeading, const wxFont *inFont) const
1179 // FIXME: This obviously ignores the window's font (if any) along with any size
1180 // transformations. However, it's better than nothing.
1181 // We don't create a wxClientDC because we don't want to accidently be able to use
1184 return tmpdc.GetTextExtent(string, outX, outY, outDescent, outExternalLeading, inFont);
1187 // Coordinates relative to the window
1188 void wxWindow::WarpPointer (int x_pos, int y_pos)
1193 int wxWindow::GetScrollPos(int orient) const
1199 // This now returns the whole range, not just the number
1200 // of positions that we can scroll.
1201 int wxWindow::GetScrollRange(int orient) const
1207 int wxWindow::GetScrollThumb(int orient) const
1213 void wxWindow::SetScrollPos(int orient, int pos, bool refresh)
1218 void wxWindow::CocoaCreateNSScrollView()
1220 if(!m_wxCocoaScrollView)
1222 m_wxCocoaScrollView = new wxWindowCocoaScrollView(this);
1226 // New function that will replace some of the above.
1227 void wxWindow::SetScrollbar(int orient, int pos, int thumbVisible,
1228 int range, bool refresh)
1230 CocoaCreateNSScrollView();
1234 // Does a physical scroll
1235 void wxWindow::ScrollWindow(int dx, int dy, const wxRect *rect)
1240 static inline int _DoFixupDistance(int vDistance, int cDistance)
1242 // If the virtual distance is wxDefaultCoord, set it to the client distance
1243 // This definitely has to be done or else we literally get views with a -1 size component!
1244 if(vDistance == wxDefaultCoord)
1245 vDistance = cDistance;
1246 // NOTE: Since cDistance should always be >= 0 and since wxDefaultCoord is -1, the above
1247 // test is more or less useless because it gets covered by the next one. However, just in
1248 // case anyone decides that the next test is not correct, I want them to be aware that
1249 // the above test would still be needed.
1251 // I am not entirely sure about this next one but I believe it makes sense because
1252 // otherwise the virtual view (which is the m_cocoaNSView that got wrapped by the scrolling
1253 // machinery) can be smaller than the NSClipView (the client area) which
1254 // means that, for instance, mouse clicks inside the client area as wx sees it but outside
1255 // the virtual area as wx sees it won't be seen by the m_cocoaNSView.
1256 // We make the assumption that if a virtual distance is less than the client distance that
1257 // the real view must already be or will soon be positioned at coordinate 0 within the
1258 // NSClipView that represents the client area. This way, when we increase the distance to
1259 // be the client distance, the real view will exactly fit in the clip view.
1260 else if(vDistance < cDistance)
1261 vDistance = cDistance;
1265 void wxWindow::DoSetVirtualSize( int x, int y )
1267 // Call wxWindowBase method which will set m_virtualSize to the appropriate value,
1268 // possibly not what the caller passed in. For example, the current implementation
1269 // clamps the width and height to within the min/max virtual ranges.
1270 // wxDefaultCoord is passed through unchanged which means we need to handle it ourselves
1271 // which we do by using the _DoFixupDistance helper method.
1272 wxWindowBase::DoSetVirtualSize(x,y);
1273 // Create the scroll view if it hasn't been already.
1274 CocoaCreateNSScrollView();
1275 // Now use fixed-up distances when setting the frame size
1276 wxSize clientSize = GetClientSize();
1277 [m_cocoaNSView setFrameSize:NSMakeSize(_DoFixupDistance(m_virtualSize.x, clientSize.x), _DoFixupDistance(m_virtualSize.y, clientSize.y))];
1280 bool wxWindow::SetFont(const wxFont& font)
1282 // FIXME: We may need to handle wx font inheritance.
1283 return wxWindowBase::SetFont(font);
1286 #if 0 // these are used when debugging the algorithm.
1287 static char const * const comparisonresultStrings[] =
1294 class CocoaWindowCompareContext
1296 DECLARE_NO_COPY_CLASS(CocoaWindowCompareContext)
1298 CocoaWindowCompareContext(); // Not implemented
1299 CocoaWindowCompareContext(NSView *target, NSArray *subviews)
1302 // Cocoa sorts subviews in-place.. make a copy
1303 m_subviews = [subviews copy];
1305 ~CocoaWindowCompareContext()
1306 { // release the copy
1307 [m_subviews release];
1310 { return m_target; }
1312 { return m_subviews; }
1313 /* Helper function that returns the comparison based off of the original ordering */
1314 CocoaWindowCompareFunctionResult CompareUsingOriginalOrdering(id first, id second)
1316 NSUInteger firstI = [m_subviews indexOfObjectIdenticalTo:first];
1317 NSUInteger secondI = [m_subviews indexOfObjectIdenticalTo:second];
1318 // NOTE: If either firstI or secondI is NSNotFound then it will be NSIntegerMax and thus will
1319 // likely compare higher than the other view which is reasonable considering the only way that
1320 // can happen is if the subview was added after our call to subviews but before the call to
1321 // sortSubviewsUsingFunction:context:. Thus we don't bother checking. Particularly because
1322 // that case should never occur anyway because that would imply a multi-threaded GUI call
1323 // which is a big no-no with Cocoa.
1325 // Subviews are ordered from back to front meaning one that is already lower will have an lower index.
1326 NSComparisonResult result = (firstI < secondI)
1327 ? NSOrderedAscending /* -1 */
1328 : (firstI > secondI)
1329 ? NSOrderedDescending /* 1 */
1330 : NSOrderedSame /* 0 */;
1332 #if 0 // Enable this if you need to debug the algorithm.
1333 NSLog(@"%@ [%d] %s %@ [%d]\n", first, firstI, comparisonresultStrings[result+1], second, secondI);
1338 /* The subview we are trying to Raise or Lower */
1340 /* A copy of the original array of subviews */
1341 NSArray *m_subviews;
1344 /* Causes Cocoa to raise the target view to the top of the Z-Order by telling the sort function that
1345 * the target view is always higher than every other view. When comparing two views neither of
1346 * which is the target, it returns the correct response based on the original ordering
1348 static CocoaWindowCompareFunctionResult CocoaRaiseWindowCompareFunction(id first, id second, void *ctx)
1350 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1351 // first should be ordered higher
1352 if(first==compareContext->target())
1353 return NSOrderedDescending;
1354 // second should be ordered higher
1355 if(second==compareContext->target())
1356 return NSOrderedAscending;
1357 return compareContext->CompareUsingOriginalOrdering(first,second);
1360 // Raise the window to the top of the Z order
1361 void wxWindow::Raise()
1363 // wxAutoNSAutoreleasePool pool;
1364 NSView *nsview = GetNSViewForSuperview();
1365 NSView *superview = [nsview superview];
1366 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1368 [superview sortSubviewsUsingFunction:
1369 CocoaRaiseWindowCompareFunction
1370 context: &compareContext];
1373 /* Causes Cocoa to lower the target view to the bottom of the Z-Order by telling the sort function that
1374 * the target view is always lower than every other view. When comparing two views neither of
1375 * which is the target, it returns the correct response based on the original ordering
1377 static CocoaWindowCompareFunctionResult CocoaLowerWindowCompareFunction(id first, id second, void *ctx)
1379 CocoaWindowCompareContext *compareContext = (CocoaWindowCompareContext*)ctx;
1380 // first should be ordered lower
1381 if(first==compareContext->target())
1382 return NSOrderedAscending;
1383 // second should be ordered lower
1384 if(second==compareContext->target())
1385 return NSOrderedDescending;
1386 return compareContext->CompareUsingOriginalOrdering(first,second);
1389 // Lower the window to the bottom of the Z order
1390 void wxWindow::Lower()
1392 NSView *nsview = GetNSViewForSuperview();
1393 NSView *superview = [nsview superview];
1394 CocoaWindowCompareContext compareContext(nsview, [superview subviews]);
1397 NSLog(@"Target:\n%@\n", nsview);
1398 NSLog(@"Before:\n%@\n", compareContext.subviews());
1400 [superview sortSubviewsUsingFunction:
1401 CocoaLowerWindowCompareFunction
1402 context: &compareContext];
1404 NSLog(@"After:\n%@\n", [superview subviews]);
1408 bool wxWindow::DoPopupMenu(wxMenu *menu, int x, int y)
1413 // Get the window with the focus
1414 wxWindow *wxWindowBase::DoFindFocus()
1416 // Basically we are somewhat emulating the responder chain here except
1417 // we are only loking for the first responder in the key window or
1418 // upon failing to find one if the main window is different we look
1419 // for the first responder in the main window.
1421 // Note that the firstResponder doesn't necessarily have to be an
1422 // NSView but wxCocoaNSView::GetFromCocoa() will simply return
1423 // NULL unless it finds its argument in its hash map.
1427 NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
1428 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([keyWindow firstResponder]));
1430 return win->GetWxWindow();
1432 NSWindow *mainWindow = [[NSApplication sharedApplication] keyWindow];
1433 if(mainWindow == keyWindow)
1435 win = wxCocoaNSView::GetFromCocoa(static_cast<NSView*>([mainWindow firstResponder]));
1437 return win->GetWxWindow();
1442 /* static */ wxWindow *wxWindowBase::GetCapture()
1445 return wxWindowCocoa::sm_capturedWindow;
1448 wxWindow *wxGetActiveWindow()
1454 wxPoint wxGetMousePosition()
1457 return wxDefaultPosition;
1460 wxMouseState wxGetMouseState()
1467 wxWindow* wxFindWindowAtPointer(wxPoint& pt)
1469 pt = wxGetMousePosition();
1473 // ========================================================================
1474 // wxCocoaMouseMovedEventSynthesizer
1475 // ========================================================================
1477 #define wxTRACE_COCOA_MouseMovedSynthesizer wxT("COCOA_MouseMovedSynthesizer")
1479 /* This class registers one run loop observer to cover all windows registered with it.
1480 * It will register the observer when the first view is registerd and unregister the
1481 * observer when the last view is unregistered.
1482 * It is instantiated as a static s_mouseMovedSynthesizer in this file although there
1483 * is no reason it couldn't be instantiated multiple times.
1485 class wxCocoaMouseMovedEventSynthesizer
1487 DECLARE_NO_COPY_CLASS(wxCocoaMouseMovedEventSynthesizer)
1489 wxCocoaMouseMovedEventSynthesizer()
1490 { m_lastScreenMouseLocation = NSZeroPoint;
1492 ~wxCocoaMouseMovedEventSynthesizer();
1493 void RegisterWxCocoaView(wxCocoaNSView *aView);
1494 void UnregisterWxCocoaView(wxCocoaNSView *aView);
1495 void SynthesizeMouseMovedEvent();
1498 void AddRunLoopObserver();
1499 void RemoveRunLoopObserver();
1500 wxCFRef<CFRunLoopObserverRef> m_runLoopObserver;
1501 std::list<wxCocoaNSView*> m_registeredViews;
1502 NSPoint m_lastScreenMouseLocation;
1503 static void SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
1506 void wxCocoaMouseMovedEventSynthesizer::RegisterWxCocoaView(wxCocoaNSView *aView)
1508 m_registeredViews.push_back(aView);
1509 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Registered wxCocoaNSView=%p"), aView);
1511 if(!m_registeredViews.empty() && m_runLoopObserver == NULL)
1513 AddRunLoopObserver();
1517 void wxCocoaMouseMovedEventSynthesizer::UnregisterWxCocoaView(wxCocoaNSView *aView)
1519 m_registeredViews.remove(aView);
1520 wxLogTrace(wxTRACE_COCOA_MouseMovedSynthesizer, wxT("Unregistered wxCocoaNSView=%p"), aView);
1521 if(m_registeredViews.empty() && m_runLoopObserver != NULL)
1523 RemoveRunLoopObserver();
1527 wxCocoaMouseMovedEventSynthesizer::~wxCocoaMouseMovedEventSynthesizer()
1529 if(!m_registeredViews.empty())
1531 // This means failure to clean up so we report on it as a debug message.
1532 wxLogDebug(wxT("There are still %d wxCocoaNSView registered to receive mouse moved events at static destruction time"), m_registeredViews.size());
1533 m_registeredViews.clear();
1535 if(m_runLoopObserver != NULL)
1537 // This should not occur unless m_registeredViews was not empty since the last object unregistered should have done this.
1538 wxLogDebug(wxT("Removing run loop observer during static destruction time."));
1539 RemoveRunLoopObserver();
1543 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
1545 reinterpret_cast<wxCocoaMouseMovedEventSynthesizer*>(info)->SynthesizeMouseMovedEvent();
1548 void wxCocoaMouseMovedEventSynthesizer::AddRunLoopObserver()
1550 CFRunLoopObserverContext observerContext =
1558 // The kCFRunLoopExit observation point is used such that we hook the run loop after it has already decided that
1559 // it is going to exit which is generally for the purpose of letting the event loop process the next Cocoa event.
1561 // Executing our procedure within the run loop (e.g. kCFRunLoopBeforeWaiting which was used before) results
1562 // in our observer procedure being called before the run loop has decided that it is going to return control to
1563 // the Cocoa event loop. One major problem is uncovered by the wxGenericHyperlinkCtrl (consider this to be "user
1564 // code") which changes the window's cursor and thus causes the cursor rectangle's to be invalidated.
1566 // Cocoa implements this invalidation using a delayed notification scheme whereby the resetCursorRects method
1567 // won't be called until the CFRunLoop gets around to it. If the CFRunLoop has not yet exited then it will get
1568 // around to it before letting the event loop do its work. This has some very odd effects on the way the
1569 // newly created tracking rects function. In particular, we will often miss the mouseExited: message if the
1570 // user flicks the mouse quickly enough such that the mouse is already outside of the tracking rect by the
1571 // time the new one is built.
1573 // Observing from the kCFRunLoopExit point gives Cocoa's event loop an opportunity to chew some events before it cedes
1574 // control back to the CFRunLoop, thus causing the delayed notifications to fire at an appropriate time and
1575 // the mouseExited: message to be sent properly.
1577 m_runLoopObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopExit, TRUE, 0, SynthesizeMouseMovedEvent, &observerContext));
1578 CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
1579 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Added tracking rect run loop observer"));
1582 void wxCocoaMouseMovedEventSynthesizer::RemoveRunLoopObserver()
1584 CFRunLoopRemoveObserver([[NSRunLoop currentRunLoop] getCFRunLoop], m_runLoopObserver, kCFRunLoopCommonModes);
1585 m_runLoopObserver.reset();
1586 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Removed tracking rect run loop observer"));
1589 void wxCocoaMouseMovedEventSynthesizer::SynthesizeMouseMovedEvent()
1591 NSPoint screenMouseLocation = [NSEvent mouseLocation];
1592 // Checking the last mouse location is done for a few reasons:
1593 // 1. We are observing every iteration of the event loop so we'd be sending out a lot of extraneous events
1594 // telling the app the mouse moved when the user hit a key for instance.
1595 // 2. When handling the mouse moved event, user code can do something to the view which will cause Cocoa to
1596 // call resetCursorRects. Cocoa does this by using a delayed notification which means the event loop gets
1597 // pumped once which would mean that if we didn't check the mouse location we'd get into a never-ending
1598 // loop causing the tracking rectangles to constantly be reset.
1599 if(screenMouseLocation.x != m_lastScreenMouseLocation.x || screenMouseLocation.y != m_lastScreenMouseLocation.y)
1601 m_lastScreenMouseLocation = screenMouseLocation;
1602 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Synthesizing mouse moved at screen (%f,%f)"), screenMouseLocation.x, screenMouseLocation.y);
1603 for(std::list<wxCocoaNSView*>::iterator i = m_registeredViews.begin(); i != m_registeredViews.end(); ++i)
1605 (*i)->Cocoa_synthesizeMouseMoved();
1610 // Singleton used for all views:
1611 static wxCocoaMouseMovedEventSynthesizer s_mouseMovedSynthesizer;
1613 // ========================================================================
1614 // wxCocoaTrackingRectManager
1615 // ========================================================================
1617 wxCocoaTrackingRectManager::wxCocoaTrackingRectManager(wxWindow *window)
1620 m_isTrackingRectActive = false;
1621 BuildTrackingRect();
1624 void wxCocoaTrackingRectManager::ClearTrackingRect()
1626 if(m_isTrackingRectActive)
1628 [m_window->GetNSView() removeTrackingRect:m_trackingRectTag];
1629 m_isTrackingRectActive = false;
1630 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Removed tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
1632 // If we were doing periodic events we need to clear those too
1633 StopSynthesizingEvents();
1636 void wxCocoaTrackingRectManager::StopSynthesizingEvents()
1638 s_mouseMovedSynthesizer.UnregisterWxCocoaView(m_window);
1641 void wxCocoaTrackingRectManager::BuildTrackingRect()
1643 // Pool here due to lack of one during wx init phase
1644 wxAutoNSAutoreleasePool pool;
1646 wxASSERT_MSG(!m_isTrackingRectActive, wxT("Tracking rect was not cleared"));
1648 NSView *theView = m_window->GetNSView();
1650 if([theView window] != nil)
1652 NSRect visibleRect = [theView visibleRect];
1654 m_trackingRectTag = [theView addTrackingRect:visibleRect owner:theView userData:NULL assumeInside:NO];
1655 m_trackingRectInWindowCoordinates = [theView convertRect:visibleRect toView:nil];
1656 m_isTrackingRectActive = true;
1658 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("%s@%p: Added tracking rect #%d"), m_window->GetClassInfo()->GetClassName(), m_window, m_trackingRectTag);
1662 void wxCocoaTrackingRectManager::BeginSynthesizingEvents()
1664 s_mouseMovedSynthesizer.RegisterWxCocoaView(m_window);
1667 void wxCocoaTrackingRectManager::RebuildTrackingRectIfNeeded()
1669 if(m_isTrackingRectActive)
1671 NSView *theView = m_window->GetNSView();
1672 NSRect currentRect = [theView convertRect:[theView visibleRect] toView:nil];
1673 if(NSEqualRects(m_trackingRectInWindowCoordinates,currentRect))
1675 wxLogTrace(wxTRACE_COCOA_TrackingRect, wxT("Ignored request to rebuild TR#%d"), m_trackingRectTag);
1679 RebuildTrackingRect();
1682 void wxCocoaTrackingRectManager::RebuildTrackingRect()
1684 ClearTrackingRect();
1685 BuildTrackingRect();
1688 wxCocoaTrackingRectManager::~wxCocoaTrackingRectManager()
1690 ClearTrackingRect();
1693 bool wxCocoaTrackingRectManager::IsOwnerOfEvent(NSEvent *anEvent)
1695 return m_isTrackingRectActive && (m_trackingRectTag == [anEvent trackingNumber]);