X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/c6d9001128e007380f9c9ea4505538207b2d8524..0d1cd87409608d690e17cfd1404e81b148ec4de5:/src/osx/cocoa/window.mm diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 3decd9fc80..9bbcb2146b 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -4,7 +4,7 @@ // Author: Stefan Csomor // Modified by: // Created: 2008-06-20 -// RCS-ID: $Id: window.mm 48805 2007-09-19 14:52:25Z SC $ +// RCS-ID: $Id$ // Copyright: (c) Stefan Csomor // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -13,9 +13,10 @@ #ifndef WX_PRECOMP #include "wx/dcclient.h" - #include "wx/nonownedwnd.h" + #include "wx/frame.h" #include "wx/log.h" #include "wx/textctrl.h" + #include "wx/combobox.h" #endif #ifdef __WXMAC__ @@ -70,7 +71,7 @@ NSView* GetFocusedViewInWindow( NSWindow* keyWindow ) WXWidget wxWidgetImpl::FindFocus() { - return GetFocusedViewInWindow( [[NSApplication sharedApplication] keyWindow] ); + return GetFocusedViewInWindow( [NSApp keyWindow] ); } NSRect wxOSXGetFrameForControl( wxWindowMac* window , const wxPoint& pos , const wxSize &size , bool adjustForOrigin ) @@ -131,6 +132,7 @@ NSRect wxOSXGetFrameForControl( wxWindowMac* window , const wxPoint& pos , const - (void)setAction:(SEL)aSelector; - (void)setDoubleAction:(SEL)aSelector; - (void)setBackgroundColor:(NSColor*)aColor; +- (void)setOpaque:(BOOL)opaque; - (void)setTextColor:(NSColor *)color; - (void)setImagePosition:(NSCellImagePosition)aPosition; @end @@ -385,22 +387,36 @@ bool g_lastButtonWasFakeRight = false ; // see http://lists.apple.com/archives/cocoa-dev/2007/Feb/msg00050.html @interface NSEvent (DeviceDelta) -- (float)deviceDeltaX; -- (float)deviceDeltaY; +- (CGFloat)deviceDeltaX; +- (CGFloat)deviceDeltaY; @end void wxWidgetCocoaImpl::SetupMouseEvent( wxMouseEvent &wxevent , NSEvent * nsEvent ) { int eventType = [nsEvent type]; UInt32 modifiers = [nsEvent modifierFlags] ; - wxPoint screenMouseLocation = wxFromNSPoint( NULL, [nsEvent locationInWindow]); + + NSPoint locationInWindow = [nsEvent locationInWindow]; + + // adjust coordinates for the window of the target view + if ( [nsEvent window] != [m_osxView window] ) + { + if ( [nsEvent window] != nil ) + locationInWindow = [[nsEvent window] convertBaseToScreen:locationInWindow]; + + if ( [m_osxView window] != nil ) + locationInWindow = [[m_osxView window] convertScreenToBase:locationInWindow]; + } + + NSPoint locationInView = [m_osxView convertPoint:locationInWindow fromView:nil]; + wxPoint locationInViewWX = wxFromNSPoint( m_osxView, locationInView ); // these parameters are not given for all events UInt32 button = [nsEvent buttonNumber]; UInt32 clickCount = 0; - wxevent.m_x = screenMouseLocation.x; - wxevent.m_y = screenMouseLocation.y; + wxevent.m_x = locationInViewWX.x; + wxevent.m_y = locationInViewWX.y; wxevent.m_shiftDown = modifiers & NSShiftKeyMask; wxevent.m_controlDown = modifiers & NSControlKeyMask; wxevent.m_altDown = modifiers & NSAlternateKeyMask; @@ -689,7 +705,7 @@ void wxOSX_mouseEvent(NSView* self, SEL _cmd, NSEvent *event) impl->mouseEvent(event, self, _cmd); } -BOOL wxOSX_acceptsFirstMouse(NSView* self, SEL _cmd, NSEvent *event) +BOOL wxOSX_acceptsFirstMouse(NSView* WXUNUSED(self), SEL WXUNUSED(_cmd), NSEvent *WXUNUSED(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. @@ -768,12 +784,34 @@ BOOL wxOSX_isFlipped(NSView* self, SEL _cmd) return impl->isFlipped(self, _cmd) ? YES:NO; } +typedef void (*wxOSX_DrawRectHandlerPtr)(NSView* self, SEL _cmd, NSRect rect); + void wxOSX_drawRect(NSView* self, SEL _cmd, NSRect rect) { wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); if (impl == NULL) return; +#ifdef wxUSE_THREADS + // OS X starts a NSUIHeartBeatThread for animating the default button in a + // dialog. This causes a drawRect of the active dialog from outside the + // main UI thread. This causes an occasional crash since the wx drawing + // objects (like wxPen) are not thread safe. + // + // Notice that NSUIHeartBeatThread seems to be undocumented and doing + // [NSWindow setAllowsConcurrentViewDrawing:NO] does not affect it. + if ( !wxThread::IsMain() ) + { + // just call the superclass handler, we don't need any custom wx drawing + // here and it seems to work fine: + wxOSX_DrawRectHandlerPtr + superimpl = (wxOSX_DrawRectHandlerPtr) + [[self superclass] instanceMethodForSelector:_cmd]; + superimpl(self, _cmd, rect); + return; + } +#endif // wxUSE_THREADS + return impl->drawRect(&rect, self, _cmd); } @@ -944,14 +982,13 @@ typedef void (*wxOSX_EventHandlerPtr)(NSView* self, SEL _cmd, NSEvent *event); typedef BOOL (*wxOSX_PerformKeyEventHandlerPtr)(NSView* self, SEL _cmd, NSEvent *event); typedef BOOL (*wxOSX_FocusHandlerPtr)(NSView* self, SEL _cmd); typedef BOOL (*wxOSX_ResetCursorRectsHandlerPtr)(NSView* self, SEL _cmd); -typedef void (*wxOSX_DrawRectHandlerPtr)(NSView* self, SEL _cmd, NSRect rect); void wxWidgetCocoaImpl::mouseEvent(WX_NSEvent event, WXWidget slf, void *_cmd) { if ( !DoHandleMouseEvent(event) ) { // for plain NSView mouse events would propagate to parents otherwise - if (!m_wxPeer->MacIsUserPane()) + if (!IsUserPane()) { wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd]; superimpl(slf, (SEL)_cmd, event); @@ -962,7 +999,15 @@ void wxWidgetCocoaImpl::mouseEvent(WX_NSEvent event, WXWidget slf, void *_cmd) void wxWidgetCocoaImpl::keyEvent(WX_NSEvent event, WXWidget slf, void *_cmd) { if ( [event type] == NSKeyDown ) + { + // there are key equivalents that are not command-combos and therefore not handled by cocoa automatically, + // therefore we call the menubar directly here, exit if the menu is handling the shortcut + if ( [[[NSApplication sharedApplication] mainMenu] performKeyEquivalent:event] ) + return; + m_lastKeyDownEvent = event; + } + if ( GetFocusedViewInWindow([slf window]) != slf || m_hasEditor || !DoHandleKeyEvent(event) ) { wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd]; @@ -983,13 +1028,41 @@ void wxWidgetCocoaImpl::insertText(NSString* text, WXWidget slf, void *_cmd) bool wxWidgetCocoaImpl::performKeyEquivalent(WX_NSEvent event, WXWidget slf, void *_cmd) { - wxOSX_PerformKeyEventHandlerPtr superimpl = (wxOSX_PerformKeyEventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd]; - return superimpl(slf, (SEL)_cmd, event); + bool handled = false; + + wxKeyEvent wxevent(wxEVT_KEY_DOWN); + SetupKeyEvent( wxevent, event ); + + // because performKeyEquivalent is going up the entire view hierarchy, we don't have to + // walk up the ancestors ourselves but let cocoa do it + + int command = m_wxPeer->GetAcceleratorTable()->GetCommand( wxevent ); + if (command != -1) + { + wxEvtHandler * const handler = m_wxPeer->GetEventHandler(); + + wxCommandEvent command_event( wxEVT_COMMAND_MENU_SELECTED, command ); + handled = handler->ProcessEvent( command_event ); + + if ( !handled ) + { + // accelerators can also be used with buttons, try them too + command_event.SetEventType(wxEVT_COMMAND_BUTTON_CLICKED); + handled = handler->ProcessEvent( command_event ); + } + } + + if ( !handled ) + { + wxOSX_PerformKeyEventHandlerPtr superimpl = (wxOSX_PerformKeyEventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd]; + return superimpl(slf, (SEL)_cmd, event); + } + return YES; } bool wxWidgetCocoaImpl::acceptsFirstResponder(WXWidget slf, void *_cmd) { - if ( m_wxPeer->MacIsUserPane() ) + if ( IsUserPane() ) return m_wxPeer->AcceptsFocus(); else { @@ -1023,6 +1096,15 @@ bool wxWidgetCocoaImpl::resignFirstResponder(WXWidget slf, void *_cmd) // is resigning NSView* otherView = FindFocus(); wxWidgetImpl* otherWindow = FindFromWXWidget(otherView); + + // It doesn't make sense to notify about the loss of focus if we're not + // really losing it and the window which has just gained focus is the same + // one as this window itself. Of course, this should never happen in the + // first place but somehow it does in wxGrid code and without this check we + // enter into an infinite recursion, see #12267. + if ( otherWindow == this ) + return r; + // NSTextViews have an editor as true responder, therefore the might get the // resign notification if their editor takes over, don't trigger any event then if ( r && !m_hasEditor) @@ -1061,13 +1143,46 @@ bool wxWidgetCocoaImpl::isFlipped(WXWidget WXUNUSED(slf), void *WXUNUSED(_cmd)) void wxWidgetCocoaImpl::drawRect(void* rect, WXWidget slf, void *WXUNUSED(_cmd)) { + // preparing the update region + + wxRegion updateRgn; + const NSRect *rects; + NSInteger count; + + [slf getRectsBeingDrawn:&rects count:&count]; + for ( int i = 0 ; i < count ; ++i ) + { + updateRgn.Union(wxFromNSRect(slf, rects[i])); + } + + wxWindow* wxpeer = GetWXPeer(); + + if ( wxpeer->MacGetLeftBorderSize() != 0 || wxpeer->MacGetTopBorderSize() != 0 ) + { + // as this update region is in native window locals we must adapt it to wx window local + updateRgn.Offset( wxpeer->MacGetLeftBorderSize() , wxpeer->MacGetTopBorderSize() ); + } + + if ( wxpeer->MacGetTopLevelWindow()->GetWindowStyle() & wxFRAME_SHAPED ) + { + int xoffset = 0, yoffset = 0; + wxRegion rgn = wxpeer->MacGetTopLevelWindow()->GetShape(); + wxpeer->MacRootWindowToWindow( &xoffset, &yoffset ); + rgn.Offset( xoffset, yoffset ); + updateRgn.Intersect(rgn); + } + + wxpeer->GetUpdateRegion() = updateRgn; + + // setting up the drawing context + CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState( context ); - + #if OSX_DEBUG_DRAWING CGContextBeginPath( context ); CGContextMoveToPoint(context, 0, 0); - NSRect bounds = [self bounds]; + NSRect bounds = [slf bounds]; CGContextAddLineToPoint(context, 10, 0); CGContextMoveToPoint(context, 0, 0); CGContextAddLineToPoint(context, 0, 10); @@ -1078,29 +1193,16 @@ void wxWidgetCocoaImpl::drawRect(void* rect, WXWidget slf, void *WXUNUSED(_cmd)) CGContextClosePath( context ); CGContextStrokePath(context); #endif - + if ( !m_isFlipped ) { CGContextTranslateCTM( context, 0, [m_osxView bounds].size.height ); CGContextScaleCTM( context, 1, -1 ); } - - wxRegion updateRgn; - const NSRect *rects; - NSInteger count; - - [slf getRectsBeingDrawn:&rects count:&count]; - for ( int i = 0 ; i < count ; ++i ) - { - updateRgn.Union(wxFromNSRect(slf, rects[i]) ); - } - - wxWindow* wxpeer = GetWXPeer(); - wxpeer->GetUpdateRegion() = updateRgn; + wxpeer->MacSetCGContextRef( context ); bool handled = wxpeer->MacDoRedraw( 0 ); - CGContextRestoreGState( context ); CGContextSaveGState( context ); @@ -1113,6 +1215,12 @@ void wxWidgetCocoaImpl::drawRect(void* rect, WXWidget slf, void *WXUNUSED(_cmd)) CGContextRestoreGState( context ); CGContextSaveGState( context ); } + // as we called restore above, we have to flip again if necessary + if ( !m_isFlipped ) + { + CGContextTranslateCTM( context, 0, [m_osxView bounds].size.height ); + CGContextScaleCTM( context, 1, -1 ); + } wxpeer->MacPaintChildrenBorders(); wxpeer->MacSetCGContextRef( NULL ); CGContextRestoreGState( context ); @@ -1134,10 +1242,17 @@ void wxWidgetCocoaImpl::controlTextDidChange() wxWindow* wxpeer = (wxWindow*)GetWXPeer(); if ( wxpeer ) { - wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, wxpeer->GetId()); - event.SetEventObject( wxpeer ); - event.SetString( static_cast(wxpeer)->GetValue() ); - wxpeer->HandleWindowEvent( event ); + // since native rtti doesn't have to be enabled and wx' rtti is not aware of the mixin wxTextEntry, workaround is needed + wxTextCtrl *tc = wxDynamicCast( wxpeer , wxTextCtrl ); + wxComboBox *cb = wxDynamicCast( wxpeer , wxComboBox ); + if ( tc ) + tc->SendTextUpdatedEventIfAllowed(); + else if ( cb ) + cb->SendTextUpdatedEventIfAllowed(); + else + { + wxFAIL_MSG("Unexpected class for controlTextDidChange event"); + } } } @@ -1151,7 +1266,7 @@ void wxWidgetCocoaImpl::controlTextDidChange() #else #define wxOSX_CLASS_ADD_METHOD( c, s, i, t ) \ - { s, t, i }, + { s, (char*) t, i }, #endif @@ -1230,8 +1345,8 @@ void wxOSXCocoaClassAddWXMethods(Class c) IMPLEMENT_DYNAMIC_CLASS( wxWidgetCocoaImpl , wxWidgetImpl ) -wxWidgetCocoaImpl::wxWidgetCocoaImpl( wxWindowMac* peer , WXWidget w, bool isRootControl ) : - wxWidgetImpl( peer, isRootControl ) +wxWidgetCocoaImpl::wxWidgetCocoaImpl( wxWindowMac* peer , WXWidget w, bool isRootControl, bool isUserPane ) : + wxWidgetImpl( peer, isRootControl, isUserPane ) { Init(); m_osxView = w; @@ -1333,6 +1448,7 @@ void wxWidgetCocoaImpl::SetVisibility( bool visible ) - (void)animationDidEnd:(NSAnimation*)animation { + wxUnusedVar(animation); m_isDone = true; } @@ -1586,6 +1702,20 @@ void wxWidgetCocoaImpl::GetPosition( int &x, int &y ) const wxRect r = wxFromNSRect( [m_osxView superview], [m_osxView frame] ); x = r.GetLeft(); y = r.GetTop(); + + // under Cocoa we might have a contentView in the wxParent to which we have to + // adjust the coordinates + wxWindowMac* parent = GetWXPeer()->GetParent(); + if (parent && [m_osxView superview] != parent->GetHandle() ) + { + int cx = 0,cy = 0,cw = 0,ch = 0; + if ( parent->GetPeer() ) + { + parent->GetPeer()->GetContentArea(cx, cy, cw, ch); + x += cx; + y += cy; + } + } } void wxWidgetCocoaImpl::GetSize( int &width, int &height ) const @@ -1681,6 +1811,18 @@ void wxWidgetCocoaImpl::SetBackgroundColour( const wxColour &col ) } } +bool wxWidgetCocoaImpl::SetBackgroundStyle( wxBackgroundStyle style ) +{ + BOOL opaque = ( style == wxBG_STYLE_PAINT ); + + if ( [m_osxView respondsToSelector:@selector(setOpaque:) ] ) + { + [m_osxView setOpaque: opaque]; + } + + return true ; +} + void wxWidgetCocoaImpl::SetLabel( const wxString& title, wxFontEncoding encoding ) { if ( [m_osxView respondsToSelector:@selector(setTitle:) ] ) @@ -1742,7 +1884,7 @@ void wxWidgetCocoaImpl::SetMaximum( wxInt32 v ) wxInt32 wxWidgetCocoaImpl::GetMinimum() const { - if ( [m_osxView respondsToSelector:@selector(getMinValue:)] ) + if ( [m_osxView respondsToSelector:@selector(minValue)] ) { return (int)[m_osxView minValue]; } @@ -1751,7 +1893,7 @@ wxInt32 wxWidgetCocoaImpl::GetMinimum() const wxInt32 wxWidgetCocoaImpl::GetMaximum() const { - if ( [m_osxView respondsToSelector:@selector(getMaxValue:)] ) + if ( [m_osxView respondsToSelector:@selector(maxValue)] ) { return (int)[m_osxView maxValue]; } @@ -1834,15 +1976,23 @@ void wxWidgetCocoaImpl::GetBestRect( wxRect *r ) const bool wxWidgetCocoaImpl::IsEnabled() const { - if ( [m_osxView respondsToSelector:@selector(isEnabled) ] ) - return [m_osxView isEnabled]; + NSView* targetView = m_osxView; + if ( [m_osxView isKindOfClass:[NSScrollView class] ] ) + targetView = [(NSScrollView*) m_osxView documentView]; + + if ( [targetView respondsToSelector:@selector(isEnabled) ] ) + return [targetView isEnabled]; return true; } void wxWidgetCocoaImpl::Enable( bool enable ) { - if ( [m_osxView respondsToSelector:@selector(setEnabled:) ] ) - [m_osxView setEnabled:enable]; + NSView* targetView = m_osxView; + if ( [m_osxView isKindOfClass:[NSScrollView class] ] ) + targetView = [(NSScrollView*) m_osxView documentView]; + + if ( [targetView respondsToSelector:@selector(setEnabled:) ] ) + [targetView setEnabled:enable]; } void wxWidgetCocoaImpl::PulseGauge() @@ -1945,15 +2095,28 @@ bool wxWidgetCocoaImpl::DoHandleKeyEvent(NSEvent *event) // this will fire higher level events, like insertText, to help // us handle EVT_CHAR, etc. - if ( m_wxPeer->MacIsUserPane() && [event type] == NSKeyDown) + if ( IsUserPane() && [event type] == NSKeyDown) { if ( !result ) { - if ( [m_osxView isKindOfClass:[NSScrollView class] ] ) - [[(NSScrollView*)m_osxView documentView] interpretKeyEvents:[NSArray arrayWithObject:event]]; + if ( wxevent.GetKeyCode() < WXK_SPACE || wxevent.GetKeyCode() == WXK_DELETE || wxevent.GetKeyCode() >= WXK_START ) + { + // eventually we could setup a doCommandBySelector catcher and retransform this into the wx key chars + wxKeyEvent wxevent2(wxevent) ; + wxevent2.SetEventType(wxEVT_CHAR); + result = GetWXPeer()->OSXHandleKeyEvent(wxevent2); + } else - [m_osxView interpretKeyEvents:[NSArray arrayWithObject:event]]; - result = true; + { + if ( !wxevent.CmdDown() ) + { + if ( [m_osxView isKindOfClass:[NSScrollView class] ] ) + [[(NSScrollView*)m_osxView documentView] interpretKeyEvents:[NSArray arrayWithObject:event]]; + else + [m_osxView interpretKeyEvents:[NSArray arrayWithObject:event]]; + result = true; + } + } } } @@ -1962,13 +2125,8 @@ bool wxWidgetCocoaImpl::DoHandleKeyEvent(NSEvent *event) bool wxWidgetCocoaImpl::DoHandleMouseEvent(NSEvent *event) { - NSPoint clickLocation; - clickLocation = [m_osxView convertPoint:[event locationInWindow] fromView:nil]; - wxPoint pt = wxFromNSPoint( m_osxView, clickLocation ); wxMouseEvent wxevent(wxEVT_LEFT_DOWN); SetupMouseEvent(wxevent , event) ; - wxevent.m_x = pt.x; - wxevent.m_y = pt.y; return GetWXPeer()->HandleWindowEvent(wxevent); } @@ -2017,13 +2175,16 @@ void wxWidgetCocoaImpl::DoNotifyFocusEvent(bool receivedFocus, wxWidgetImpl* oth void wxWidgetCocoaImpl::SetCursor(const wxCursor& cursor) { - NSPoint location = [NSEvent mouseLocation]; - location = [[m_osxView window] convertScreenToBase:location]; - NSPoint locationInView = [m_osxView convertPoint:location fromView:nil]; - - if( NSMouseInRect(locationInView, [m_osxView bounds], YES) ) + if ( !wxIsBusy() ) { - [(NSCursor*)cursor.GetHCURSOR() set]; + NSPoint location = [NSEvent mouseLocation]; + location = [[m_osxView window] convertScreenToBase:location]; + NSPoint locationInView = [m_osxView convertPoint:location fromView:nil]; + + if( NSMouseInRect(locationInView, [m_osxView bounds], YES) ) + { + [(NSCursor*)cursor.GetHCURSOR() set]; + } } [[m_osxView window] invalidateCursorRectsForView:m_osxView]; } @@ -2058,16 +2219,31 @@ wxWidgetImpl* wxWidgetImpl::CreateUserPane( wxWindowMac* wxpeer, wxWindowMac* WX [v registerForDraggedTypes:[NSArray arrayWithObjects: NSStringPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSPDFPboardType, nil]]; - wxWidgetCocoaImpl* c = new wxWidgetCocoaImpl( wxpeer, v ); + wxWidgetCocoaImpl* c = new wxWidgetCocoaImpl( wxpeer, v, false, true ); return c; } wxWidgetImpl* wxWidgetImpl::CreateContentView( wxNonOwnedWindow* now ) { NSWindow* tlw = now->GetWXWindow(); - wxNSView* v = [[wxNSView alloc] initWithFrame:[[tlw contentView] frame]]; - wxWidgetCocoaImpl* c = new wxWidgetCocoaImpl( now, v, true ); - c->InstallEventHandler(); - [tlw setContentView:v]; + + wxWidgetCocoaImpl* c = NULL; + if ( now->IsNativeWindowWrapper() ) + { + NSView* cv = [tlw contentView]; + c = new wxWidgetCocoaImpl( now, cv, true ); + // increase ref count, because the impl destructor will decrement it again + CFRetain(cv); + if ( !now->IsShown() ) + [cv setHidden:NO]; + + } + else + { + wxNSView* v = [[wxNSView alloc] initWithFrame:[[tlw contentView] frame]]; + c = new wxWidgetCocoaImpl( now, v, true ); + c->InstallEventHandler(); + [tlw setContentView:v]; + } return c; }