]> git.saurik.com Git - wxWidgets.git/blobdiff - src/osx/cocoa/window.mm
Since wxDialog::Show(false) only calls wxWindow::Show, we must explicitly hide the...
[wxWidgets.git] / src / osx / cocoa / window.mm
index 274eb120992e17021bf206e762ef2dd3fd918a7a..a2aea90d23c464a829abe7167a13cd2301428b27 100644 (file)
     #include "wx/dcclient.h"
     #include "wx/nonownedwnd.h"
     #include "wx/log.h"
+    #include "wx/textctrl.h"
 #endif
 
 #ifdef __WXMAC__
     #include "wx/osx/private.h"
 #endif
 
-#include "wx/hashmap.h"
+#include "wx/evtloop.h"
 
 #if wxUSE_CARET
     #include "wx/caret.h"
     #include "wx/dnd.h"
 #endif
 
-#include <objc/objc-runtime.h>
-
-namespace
-{
-
-// stop animation of this window if one is in progress
-void StopAnimation(wxWindow *win);
-
-} // anonymous namespace
+#if wxUSE_TOOLTIPS
+    #include "wx/tooltip.h"
+#endif
 
+#include <objc/objc-runtime.h>
 
 // Get the window with the focus
 
@@ -49,7 +45,7 @@ NSView* GetViewFromResponder( NSResponder* responder )
     NSView* view = nil;
     if ( [responder isKindOfClass:[NSTextView class]] )
     {
-        NSView* delegate =  [(NSTextView*)responder delegate];
+        NSView* delegate = (NSView*) [(NSTextView*)responder delegate];
         if ( [delegate isKindOfClass:[NSTextField class] ] )
             view = delegate;
         else
@@ -91,11 +87,20 @@ NSRect wxOSXGetFrameForControl( wxWindowMac* window , const wxPoint& pos , const
 @interface wxNSView : NSView
 {
     NSTrackingRectTag rectTag;
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+    NSTrackingArea* _trackingArea;
+#endif
 }
 
 // the tracking tag is needed to track mouse enter / exit events
 - (void) setTrackingTag: (NSTrackingRectTag)tag;
 - (NSTrackingRectTag) trackingTag;
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+// under 10.5 we can also track mouse moved events on non-focused windows if
+// we use the new NSTrackingArea APIs. 
+- (void) updateTrackingArea;
+- (NSTrackingArea*) trackingArea;
+#endif
 @end // wxNSView
 
 @interface NSView(PossibleMethods)
@@ -302,7 +307,7 @@ void wxWidgetCocoaImpl::SetupKeyEvent(wxKeyEvent &wxevent , NSEvent * nsEvent, N
     wxString chars;
     if ( eventType != NSFlagsChanged )
     {
-        NSString* nschars = (wxevent.GetEventType() != wxEVT_CHAR) ? [nsEvent charactersIgnoringModifiers] : [nsEvent characters];
+        NSString* nschars = [nsEvent charactersIgnoringModifiers];
         if ( charString )
         {
             // if charString is set, it did not come from key up / key down
@@ -375,6 +380,14 @@ void wxWidgetCocoaImpl::SetupKeyEvent(wxKeyEvent &wxevent , NSEvent * nsEvent, N
 UInt32 g_lastButton = 0 ;
 bool g_lastButtonWasFakeRight = false ;
 
+// better scroll wheel support 
+// see http://lists.apple.com/archives/cocoa-dev/2007/Feb/msg00050.html
+
+@interface NSEvent (DeviceDelta)
+- (float)deviceDeltaX;
+- (float)deviceDeltaY;
+@end
+
 void wxWidgetCocoaImpl::SetupMouseEvent( wxMouseEvent &wxevent , NSEvent * nsEvent )
 {
     int eventType = [nsEvent type];
@@ -508,19 +521,43 @@ void wxWidgetCocoaImpl::SetupMouseEvent( wxMouseEvent &wxevent , NSEvent * nsEve
 
      case NSScrollWheel :
         {
+            float deltaX = 0.0;
+            float deltaY = 0.0;
+
             wxevent.SetEventType( wxEVT_MOUSEWHEEL ) ;
+
+            // see http://developer.apple.com/qa/qa2005/qa1453.html
+            // for more details on why we have to look for the exact type
+            
+            const EventRef cEvent = (EventRef) [nsEvent eventRef];
+            bool isMouseScrollEvent = false;
+            if ( cEvent )
+                isMouseScrollEvent = ::GetEventKind(cEvent) == kEventMouseScroll;
+                
+            if ( isMouseScrollEvent )
+            {
+                deltaX = [nsEvent deviceDeltaX];
+                deltaY = [nsEvent deviceDeltaY];
+            }
+            else
+            {
+                deltaX = ([nsEvent deltaX] * 10);
+                deltaY = ([nsEvent deltaY] * 10);
+            }
+            
             wxevent.m_wheelDelta = 10;
             wxevent.m_linesPerAction = 1;
-
-            if ( fabs([nsEvent deltaX]) > fabs([nsEvent deltaY]) )
+                
+            if ( fabs(deltaX) > fabs(deltaY) )
             {
                 wxevent.m_wheelAxis = 1;
-                wxevent.m_wheelRotation = (int)([nsEvent deltaX] * 10);
+                wxevent.m_wheelRotation = (int)deltaX;
             }
             else
             {
-                wxevent.m_wheelRotation = (int)([nsEvent deltaY] * 10);
+                wxevent.m_wheelRotation = (int)deltaY;
             }
+
         }
         break ;
 
@@ -571,6 +608,28 @@ void wxWidgetCocoaImpl::SetupMouseEvent( wxMouseEvent &wxevent , NSEvent * nsEve
     return rectTag;
 }
 
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+- (void) updateTrackingArea
+{
+    if (_trackingArea)
+    {
+        [self removeTrackingArea: _trackingArea];
+        [_trackingArea release];
+    }
+    
+    NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveAlways;
+        
+    NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect: [self bounds] options: options owner: self userInfo: nil];
+    [self addTrackingArea: area];
+
+    _trackingArea = area;
+}
+
+- (NSTrackingArea*) trackingArea
+{
+    return _trackingArea;
+}
+#endif
 @end // wxNSView
 
 //
@@ -629,6 +688,13 @@ void wxOSX_mouseEvent(NSView* self, SEL _cmd, NSEvent *event)
     impl->mouseEvent(event, self, _cmd);
 }
 
+BOOL wxOSX_acceptsFirstMouse(NSView* self, SEL _cmd, NSEvent *event)
+{
+    // This is needed to support click through, otherwise the first click on a window
+    // will not do anything unless it is the active window already.
+    return YES;
+}
+
 void wxOSX_keyEvent(NSView* self, SEL _cmd, NSEvent *event)
 {
     wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
@@ -818,7 +884,7 @@ unsigned int wxWidgetCocoaImpl::draggingUpdated(void* s, WXWidget WXUNUSED(slf),
         result = wxDragCopy;
     else if ( sourceDragMask & NSDragOperationMove )
         result = wxDragMove;
-
+    
     PasteboardRef pboardRef;
     PasteboardCreate((CFStringRef)[pboard name], &pboardRef);
     target->SetCurrentDragPasteboard(pboardRef);
@@ -1062,6 +1128,18 @@ void wxWidgetCocoaImpl::controlDoubleAction( WXWidget WXUNUSED(slf), void *WXUNU
 {
 }
 
+void wxWidgetCocoaImpl::controlTextDidChange()
+{
+    wxWindow* wxpeer = (wxWindow*)GetWXPeer();
+    if ( wxpeer ) 
+    {
+        wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, wxpeer->GetId());
+        event.SetEventObject( wxpeer );
+        event.SetString( static_cast<wxTextCtrl*>(wxpeer)->GetValue() );
+        wxpeer->HandleWindowEvent( event );
+    }
+}
+
 //
 
 #if OBJC_API_VERSION >= 2
@@ -1097,6 +1175,8 @@ void wxOSXCocoaClassAddWXMethods(Class c)
     wxOSX_CLASS_ADD_METHOD(c, @selector(mouseDragged:), (IMP) wxOSX_mouseEvent, "v@:@" )
     wxOSX_CLASS_ADD_METHOD(c, @selector(rightMouseDragged:), (IMP) wxOSX_mouseEvent, "v@:@" )
     wxOSX_CLASS_ADD_METHOD(c, @selector(otherMouseDragged:), (IMP) wxOSX_mouseEvent, "v@:@" )
+    
+    wxOSX_CLASS_ADD_METHOD(c, @selector(acceptsFirstMouse:), (IMP) wxOSX_acceptsFirstMouse, "v@:@" )
 
     wxOSX_CLASS_ADD_METHOD(c, @selector(scrollWheel:), (IMP) wxOSX_mouseEvent, "v@:@" )
     wxOSX_CLASS_ADD_METHOD(c, @selector(mouseEntered:), (IMP) wxOSX_mouseEvent, "v@:@" )
@@ -1180,8 +1260,6 @@ void wxWidgetCocoaImpl::Init()
 
 wxWidgetCocoaImpl::~wxWidgetCocoaImpl()
 {
-    StopAnimation(m_wxPeer);
-
     RemoveAssociations( this );
 
     if ( !IsRootControl() )
@@ -1209,72 +1287,40 @@ void wxWidgetCocoaImpl::SetVisibility( bool visible )
 // window animation stuff
 // ----------------------------------------------------------------------------
 
-namespace
-{
-
-WX_DECLARE_VOIDPTR_HASH_MAP(NSViewAnimation *, wxNSViewAnimations);
-
-// all currently active animations
-//
-// this is MT-safe because windows can only be animated from the main
-// thread anyhow
-wxNSViewAnimations gs_activeAnimations;
-
-void StopAnimation(wxWindow *win)
-{
-    wxNSViewAnimations::iterator it = gs_activeAnimations.find(win);
-    if ( it != gs_activeAnimations.end() )
-    {
-        [it->second stopAnimation];
-    }
-}
-
-} // anonymous namespace
-
-// define a delegate used to detect the end of the window animation
-@interface wxNSAnimationDelegate : NSObject
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
-                                   <NSAnimationDelegate>
-#endif
+// define a delegate used to refresh the window during animation
+@interface wxNSAnimationDelegate : NSObject wxOSX_10_6_AND_LATER(<NSAnimationDelegate>)
 {
-    // can't use wxRect here as it has a user-defined ctor and so can't be used
-    // as an Objective-C field
-    struct
-    {
-        int x, y, w, h;
-    } m_origRect;
     wxWindow *m_win;
-    bool m_show;
+    bool m_isDone;
 }
 
-- (id)initWithWindow:(wxWindow *)win show:(bool)show;
+- (id)init:(wxWindow *)win;
+
+- (bool)isDone;
 
 // NSAnimationDelegate methods
+- (void)animationDidEnd:(NSAnimation*)animation;
 - (void)animation:(NSAnimation*)animation
         didReachProgressMark:(NSAnimationProgress)progress;
-- (void)animationDidStop:(NSAnimation *)animation;
-- (void)animationDidEnd:(NSAnimation *)animation;
-
-// private helpers
-- (void)finishAnimation:(NSAnimation *)animation;
 @end
 
 @implementation wxNSAnimationDelegate
 
-- (id)initWithWindow:(wxWindow *)win show:(bool)show
+- (id)init:(wxWindow *)win
 {
     [super init];
 
     m_win = win;
-    m_show = show;
-    if ( !show )
-    {
-        m_win->GetPosition(&m_origRect.x, &m_origRect.y);
-        m_win->GetSize(&m_origRect.w, &m_origRect.h);
-    }
+    m_isDone = false;
+
     return self;
 }
 
+- (bool)isDone
+{
+    return m_isDone;
+}
+
 - (void)animation:(NSAnimation*)animation
         didReachProgressMark:(NSAnimationProgress)progress
 {
@@ -1284,49 +1330,9 @@ void StopAnimation(wxWindow *win)
     m_win->SendSizeEvent();
 }
 
-- (void)animationDidStop:(NSAnimation *)animation
-{
-    [self finishAnimation:animation];
-}
-
-- (void)animationDidEnd:(NSAnimation *)animation
-{
-    [self finishAnimation:animation];
-}
-
-- (void)finishAnimation:(NSAnimation *)animation
+- (void)animationDidEnd:(NSAnimation*)animation
 {
-    if ( m_show )
-    {
-        // the window expects to be sent a size event when it is shown normally
-        // and so it should also get one when it is shown with effect
-        m_win->SendSizeEvent();
-    }
-    else // window was being hidden
-    {
-        // NSViewAnimation is smart enough to hide the NSView itself but we
-        // also need to ensure that it's considered to be hidden at wx level
-        m_win->Hide();
-
-        // and we also need to restore its original size which was changed by
-        // the animation
-        m_win->SetSize(m_origRect.x, m_origRect.y, m_origRect.w, m_origRect.h);
-    }
-
-    wxNSViewAnimations::iterator it = gs_activeAnimations.find(m_win);
-    wxASSERT_MSG( it != gs_activeAnimations.end() && it->second == animation,
-                  "corrupted active animations list?" );
-
-    gs_activeAnimations.erase(it);
-
-    // we don't dare to release it immediately as we're called from its code
-    // but schedule the animation itself for deletion soon
-    [animation autorelease];
-
-    // ensure that this delegate is definitely not needed any more before
-    // destroying it
-    [animation setDelegate:nil];
-    [self release];
+    m_isDone = true;
 }
 
 @end
@@ -1338,13 +1344,6 @@ wxWidgetCocoaImpl::ShowViewOrWindowWithEffect(wxWindow *win,
                                               wxShowEffect effect,
                                               unsigned timeout)
 {
-    // first of all, check if this window is not being already animated and
-    // cancel the previous animation if it is as performing more than one
-    // animation on the same window at the same time results in some really
-    // unexpected effects
-    StopAnimation(win);
-
-
     // create the dictionary describing the animation to perform on this view
     NSObject * const
         viewOrWin = static_cast<NSObject *>(win->OSXGetViewOrWindow());
@@ -1353,10 +1352,11 @@ wxWidgetCocoaImpl::ShowViewOrWindowWithEffect(wxWindow *win,
     [dict setObject:viewOrWin forKey:NSViewAnimationTargetKey];
 
     // determine the start and end rectangles assuming we're hiding the window
+    const wxRect rectOrig = win->GetRect();
     wxRect rectStart,
            rectEnd;
     rectStart =
-    rectEnd = win->GetRect();
+    rectEnd = rectOrig;
 
     if ( show )
     {
@@ -1431,7 +1431,7 @@ wxWidgetCocoaImpl::ShowViewOrWindowWithEffect(wxWindow *win,
         // animation to be visible at all (but don't restore it at its full
         // rectangle as it shouldn't appear immediately)
         win->SetSize(rectStart);
-        win->Show(true);
+        win->Show();
     }
 
     NSView * const parentView = [viewOrWin isKindOfClass:[NSView class]]
@@ -1446,13 +1446,9 @@ wxWidgetCocoaImpl::ShowViewOrWindowWithEffect(wxWindow *win,
           forKey:NSViewAnimationEndFrameKey];
 
     // create an animation using the values in the above dictionary
-    //
-    // notice that it will be released when it is removed from
-    // gs_activeAnimations
     NSViewAnimation * const
         anim = [[NSViewAnimation alloc]
                 initWithViewAnimations:[NSArray arrayWithObject:dict]];
-    gs_activeAnimations[win] = anim;
 
     if ( !timeout )
     {
@@ -1472,9 +1468,47 @@ wxWidgetCocoaImpl::ShowViewOrWindowWithEffect(wxWindow *win,
     for ( float f = 1./NUM_LAYOUTS; f < 1.; f += 1./NUM_LAYOUTS )
         [anim addProgressMark:f];
 
-    [anim setDelegate:[[wxNSAnimationDelegate alloc] initWithWindow:win show:show]];
+    wxNSAnimationDelegate * const
+        animDelegate = [[wxNSAnimationDelegate alloc] init:win];
+    [anim setDelegate:animDelegate];
     [anim startAnimation];
 
+    // Cocoa is capable of doing animation asynchronously or even from separate
+    // thread but wx API doesn't provide any way to be notified about the
+    // animation end and without this we really must ensure that the window has
+    // the expected (i.e. the same as if a simple Show() had been used) size
+    // when we return, so block here until the animation finishes
+    //
+    // notice that because the default animation mode is NSAnimationBlocking,
+    // no user input events ought to be processed from here
+    {
+        wxEventLoopGuarantor ensureEventLoopExistence;
+        wxEventLoopBase * const loop = wxEventLoopBase::GetActive();
+        while ( ![animDelegate isDone] )
+            loop->Dispatch();
+    }
+
+    if ( !show )
+    {
+        // NSViewAnimation is smart enough to hide the NSView being animated at
+        // the end but we also must ensure that it's hidden for wx too
+        win->Hide();
+
+        // and we must also restore its size because it isn't expected to
+        // change just because the window was hidden
+        win->SetSize(rectOrig);
+    }
+    else
+    {
+        // refresh it once again after the end to ensure that everything is in
+        // place
+        win->SendSizeEvent();
+    }
+
+    [anim setDelegate:nil];
+    [animDelegate release];
+    [anim release];
+
     return true;
 }
 
@@ -1531,13 +1565,19 @@ void wxWidgetCocoaImpl::Move(int x, int y, int width, int height)
     [m_osxView setFrame:r];
     [[m_osxView superview] setNeedsDisplayInRect:r];
 
+    wxNSView* wxview = (wxNSView*)m_osxView;
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+    if ([wxview respondsToSelector:@selector(updateTrackingArea)] )
+        [wxview updateTrackingArea]; 
+#else
     if ([m_osxView respondsToSelector:@selector(trackingTag)] )
     {
-        if ( [(wxNSView*)m_osxView trackingTag] )
-            [m_osxView removeTrackingRect: [(wxNSView*)m_osxView trackingTag]];
+        if ( [wxview trackingTag] )
+            [wxview removeTrackingRect: [wxview trackingTag]];
 
-        [(wxNSView*)m_osxView setTrackingTag: [m_osxView addTrackingRect: [m_osxView bounds] owner: m_osxView userData: nil assumeInside: NO]];
+        [wxview setTrackingTag: [wxview addTrackingRect: [m_osxView bounds] owner: wxview userData: nil assumeInside: NO]];
     }
+#endif
 }
 
 void wxWidgetCocoaImpl::GetPosition( int &x, int &y ) const
@@ -1735,6 +1775,7 @@ void wxWidgetCocoaImpl::SetBitmap( const wxBitmap& bitmap )
     if (  [m_osxView respondsToSelector:@selector(setImage:)] )
     {
         [m_osxView setImage:bitmap.GetNSImage()];
+        [m_osxView setNeedsDisplay:YES];
     }
 }
 
@@ -1853,6 +1894,18 @@ void wxWidgetCocoaImpl::SetFont(wxFont const& font, wxColour const&, long, bool)
         [m_osxView setFont: font.OSXGetNSFont()];
 }
 
+void wxWidgetCocoaImpl::SetToolTip(wxToolTip* tooltip)
+{
+    if (tooltip)
+    {
+        wxCFStringRef cf( tooltip->GetTip() , m_wxPeer->GetFont().GetEncoding() );
+        [m_osxView setToolTip: cf.AsNSString()];
+    }
+    else 
+        [m_osxView setToolTip: nil];
+
+}
+
 void wxWidgetCocoaImpl::InstallEventHandler( WXWidget control )
 {
     WXWidget c =  control ? control : (WXWidget) m_osxView;