X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/906c935a80b10d53cecf57f71ab5f3f4f1d529ec..0afeb753e0a6a3fdba290bf3612bb2f012d44d95:/src/osx/cocoa/textctrl.mm diff --git a/src/osx/cocoa/textctrl.mm b/src/osx/cocoa/textctrl.mm index b5913b841b..760257f4f4 100644 --- a/src/osx/cocoa/textctrl.mm +++ b/src/osx/cocoa/textctrl.mm @@ -57,11 +57,18 @@ - (void)setSelectable:(BOOL)flag; @end +// An object of this class is created before the text is modified +// programmatically and destroyed as soon as this is done. It does several +// things, like ensuring that the control is editable to allow setting its text +// at all and eating any unwanted focus loss events from textDidEndEditing: +// which don't really correspond to focus change. class wxMacEditHelper { public : wxMacEditHelper( NSView* textView ) { + m_viewPreviouslyEdited = ms_viewCurrentlyEdited; + ms_viewCurrentlyEdited = m_textView = textView; m_formerEditable = YES; if ( textView ) @@ -79,14 +86,76 @@ public : [m_textView setEditable:m_formerEditable]; [m_textView setSelectable:m_formerSelectable]; } + + ms_viewCurrentlyEdited = m_viewPreviouslyEdited; } + // Returns the last view we were instantiated for or NULL. + static NSView *GetCurrentlyEditedView() { return ms_viewCurrentlyEdited; } + protected : BOOL m_formerEditable ; BOOL m_formerSelectable; NSView* m_textView; + + // The original value of ms_viewCurrentlyEdited when this object was + // created. + NSView* m_viewPreviouslyEdited; + + static NSView* ms_viewCurrentlyEdited; } ; +NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; + +// a minimal NSFormatter that just avoids getting too long entries +@interface wxMaximumLengthFormatter : NSFormatter +{ + int maxLength; +} + +@end + +@implementation wxMaximumLengthFormatter + +- (id)init +{ + self = [super init]; + maxLength = 0; + return self; +} + +- (void) setMaxLength:(int) maxlen +{ + maxLength = maxlen; +} + +- (NSString *)stringForObjectValue:(id)anObject +{ + if(![anObject isKindOfClass:[NSString class]]) + return nil; + return [NSString stringWithString:anObject]; +} + +- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString **)error +{ + *obj = [NSString stringWithString:string]; + return YES; +} + +- (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr + originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error +{ + int len = [*partialStringPtr length]; + if ( maxLength > 0 && len > maxLength ) + { + // TODO wxEVT_TEXT_MAXLEN + return NO; + } + return YES; +} + +@end + @implementation wxNSSecureTextField + (void)initialize @@ -113,10 +182,47 @@ protected : wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); if ( impl ) { - impl->DoNotifyFocusEvent( false, NULL ); + NSResponder * responder = wxNonOwnedWindowCocoaImpl::GetNextFirstResponder(); + NSView* otherView = wxOSXGetViewFromResponder(responder); + + wxWidgetImpl* otherWindow = impl->FindBestFromWXWidget(otherView); + impl->DoNotifyFocusEvent( false, otherWindow ); } } +- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector +{ + wxUnusedVar(textView); + + BOOL handled = NO; + + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( control ); + if ( impl ) + { + wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer(); + if ( wxpeer ) + { + if (commandSelector == @selector(insertNewline:)) + { + wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(wxpeer), wxTopLevelWindow); + if ( tlw && tlw->GetDefaultItem() ) + { + wxButton *def = wxDynamicCast(tlw->GetDefaultItem(), wxButton); + if ( def && def->IsEnabled() ) + { + wxCommandEvent event(wxEVT_BUTTON, def->GetId() ); + event.SetEventObject(def); + def->Command(event); + handled = YES; + } + } + } + } + } + + return handled; +} + @end @interface wxNSTextScrollView : NSScrollView @@ -222,10 +328,23 @@ protected : - (void)textDidEndEditing:(NSNotification *)aNotification { wxUnusedVar(aNotification); + + if ( self == wxMacEditHelper::GetCurrentlyEditedView() ) + { + // This notification is generated as the result of calling our own + // wxTextCtrl method (e.g. WriteText()) and doesn't correspond to any + // real focus loss event so skip generating it. + return; + } + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); if ( impl ) { - impl->DoNotifyFocusEvent( false, NULL ); + NSResponder * responder = wxNonOwnedWindowCocoaImpl::GetNextFirstResponder(); + NSView* otherView = wxOSXGetViewFromResponder(responder); + + wxWidgetImpl* otherWindow = impl->FindBestFromWXWidget(otherView); + impl->DoNotifyFocusEvent( false, otherWindow ); } } @@ -296,7 +415,7 @@ protected : } - (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words - forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int*)index + forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger*)index { NSMutableArray* matches = NULL; @@ -388,7 +507,18 @@ protected : wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); if ( impl ) { - impl->DoNotifyFocusEvent( false, NULL ); + wxNSTextFieldControl* timpl = dynamic_cast(impl); + if ( fieldEditor ) + { + NSRange range = [fieldEditor selectedRange]; + timpl->SetInternalSelection(range.location, range.location + range.length); + } + + NSResponder * responder = wxNonOwnedWindowCocoaImpl::GetNextFirstResponder(); + NSView* otherView = wxOSXGetViewFromResponder(responder); + + wxWidgetImpl* otherWindow = impl->FindBestFromWXWidget(otherView); + impl->DoNotifyFocusEvent( false, otherWindow ); } } @end @@ -404,7 +534,8 @@ wxNSTextViewControl::wxNSTextViewControl( wxTextCtrl *wxPeer, WXWidget w ) [m_scrollView setHasVerticalScroller:YES]; [m_scrollView setHasHorizontalScroller:NO]; - [m_scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + // TODO Remove if no regression, this was causing automatic resizes of multi-line textfields when the tlw changed + // [m_scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; NSSize contentSize = [m_scrollView contentSize]; wxNSTextView* tv = [[wxNSTextView alloc] initWithFrame: NSMakeRect(0, 0, @@ -577,24 +708,35 @@ void wxNSTextViewControl::SetStyle(long start, long end, const wxTextAttr& style) { - if (m_textView) { + if ( !m_textView ) + return; + + if ( start == -1 && end == -1 ) + { + NSMutableDictionary* const + attrs = [NSMutableDictionary dictionaryWithCapacity:3]; + if ( style.HasFont() ) + [attrs setValue:style.GetFont().OSXGetNSFont() forKey:NSFontAttributeName]; + if ( style.HasBackgroundColour() ) + [attrs setValue:style.GetBackgroundColour().OSXGetNSColor() forKey:NSBackgroundColorAttributeName]; + if ( style.HasTextColour() ) + [attrs setValue:style.GetTextColour().OSXGetNSColor() forKey:NSForegroundColorAttributeName]; + + [m_textView setTypingAttributes:attrs]; + } + else // Set the attributes just for this range. + { NSRange range = NSMakeRange(start, end-start); - if (start == -1 && end == -1) - range = [m_textView selectedRange]; NSTextStorage* storage = [m_textView textStorage]; - - wxFont font = style.GetFont(); - if (style.HasFont() && font.IsOk()) - [storage addAttribute:NSFontAttributeName value:font.OSXGetNSFont() range:range]; - - wxColour bgcolor = style.GetBackgroundColour(); - if (style.HasBackgroundColour() && bgcolor.IsOk()) - [storage addAttribute:NSBackgroundColorAttributeName value:bgcolor.OSXGetNSColor() range:range]; - - wxColour fgcolor = style.GetTextColour(); - if (style.HasTextColour() && fgcolor.IsOk()) - [storage addAttribute:NSForegroundColorAttributeName value:fgcolor.OSXGetNSColor() range:range]; + if ( style.HasFont() ) + [storage addAttribute:NSFontAttributeName value:style.GetFont().OSXGetNSFont() range:range]; + + if ( style.HasBackgroundColour() ) + [storage addAttribute:NSBackgroundColorAttributeName value:style.GetBackgroundColour().OSXGetNSColor() range:range]; + + if ( style.HasTextColour() ) + [storage addAttribute:NSForegroundColorAttributeName value:style.GetTextColour().OSXGetNSColor() range:range]; } } @@ -659,6 +801,13 @@ void wxNSTextFieldControl::SetStringValue( const wxString &str) [m_textField setStringValue: wxCFStringRef( str , m_wxPeer->GetFont().GetEncoding() ).AsNSString()]; } +void wxNSTextFieldControl::SetMaxLength(unsigned long len) +{ + wxMaximumLengthFormatter* formatter = [[[wxMaximumLengthFormatter alloc] init] autorelease]; + [formatter setMaxLength:len]; + [m_textField setFormatter:formatter]; +} + void wxNSTextFieldControl::Copy() { NSText* editor = [m_textField currentEditor]; @@ -734,11 +883,11 @@ void wxNSTextFieldControl::SetSelection( long from , long to ) { [editor setSelectedRange:NSMakeRange(from, to-from)]; } - else - { - m_selStart = from; - m_selEnd = to; - } + + // the editor might still be in existence, but we might be already passed our 'focus lost' storage + // of the selection, so make sure we copy this + m_selStart = from; + m_selEnd = to; } void wxNSTextFieldControl::WriteText(const wxString& str) @@ -749,7 +898,12 @@ void wxNSTextFieldControl::WriteText(const wxString& str) if ( editor ) { wxMacEditHelper helper(m_textField); + BOOL hasUndo = [editor respondsToSelector:@selector(setAllowsUndo:)]; + if ( hasUndo ) + [(NSTextView*)editor setAllowsUndo:NO]; [editor insertText:wxCFStringRef( str , m_wxPeer->GetFont().GetEncoding() ).AsNSString()]; + if ( hasUndo ) + [(NSTextView*)editor setAllowsUndo:YES]; } else { @@ -770,13 +924,59 @@ void wxNSTextFieldControl::controlAction(WXWidget WXUNUSED(slf), wxWindow* wxpeer = (wxWindow*) GetWXPeer(); if ( wxpeer && (wxpeer->GetWindowStyle() & wxTE_PROCESS_ENTER) ) { - wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, wxpeer->GetId()); + wxCommandEvent event(wxEVT_TEXT_ENTER, wxpeer->GetId()); event.SetEventObject( wxpeer ); event.SetString( GetTextEntry()->GetValue() ); wxpeer->HandleWindowEvent( event ); } } +void wxNSTextFieldControl::SetInternalSelection( long from , long to ) +{ + m_selStart = from; + m_selEnd = to; +} + +// as becoming first responder on a window - triggers a resign on the same control, we have to avoid +// the resign notification writing back native selection values before we can set our own + +static WXWidget s_widgetBecomingFirstResponder = nil; + +bool wxNSTextFieldControl::becomeFirstResponder(WXWidget slf, void *_cmd) +{ + s_widgetBecomingFirstResponder = slf; + bool retval = wxWidgetCocoaImpl::becomeFirstResponder(slf, _cmd); + s_widgetBecomingFirstResponder = nil; + if ( retval ) + { + NSText* editor = [m_textField currentEditor]; + if ( editor ) + { + long textLength = [[m_textField stringValue] length]; + m_selStart = wxMin(textLength,wxMax(m_selStart,0)) ; + m_selEnd = wxMax(0,wxMin(textLength,m_selEnd)) ; + + [editor setSelectedRange:NSMakeRange(m_selStart, m_selEnd-m_selStart)]; + } + } + return retval; +} + +bool wxNSTextFieldControl::resignFirstResponder(WXWidget slf, void *_cmd) +{ + if ( slf != s_widgetBecomingFirstResponder ) + { + NSText* editor = [m_textField currentEditor]; + if ( editor ) + { + NSRange range = [editor selectedRange]; + m_selStart = range.location; + m_selEnd = range.location + range.length; + } + } + return wxWidgetCocoaImpl::resignFirstResponder(slf, _cmd); +} + bool wxNSTextFieldControl::SetHint(const wxString& hint) { wxCFStringRef hintstring(hint); @@ -800,11 +1000,12 @@ wxWidgetImplType* wxWidgetImpl::CreateTextControl( wxTextCtrl* wxpeer, NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ; wxWidgetCocoaImpl* c = NULL; - if ( style & wxTE_MULTILINE || style & wxTE_RICH || style & wxTE_RICH2 ) + if ( style & wxTE_MULTILINE ) { wxNSTextScrollView* v = nil; v = [[wxNSTextScrollView alloc] initWithFrame:r]; c = new wxNSTextViewControl( wxpeer, v ); + c->SetNeedsFocusRect( true ); } else { @@ -814,24 +1015,39 @@ wxWidgetImplType* wxWidgetImpl::CreateTextControl( wxTextCtrl* wxpeer, else v = [[wxNSTextField alloc] initWithFrame:r]; - if ( style & wxNO_BORDER ) + if ( style & wxTE_RIGHT) { - // FIXME: How can we remove the native control's border? - // setBordered is separate from the text ctrl's border. + [v setAlignment:NSRightTextAlignment]; } - + else if ( style & wxTE_CENTRE) + { + [v setAlignment:NSCenterTextAlignment]; + } + NSTextFieldCell* cell = [v cell]; [cell setScrollable:YES]; // TODO: Remove if we definitely are sure, it's not needed // as setting scrolling to yes, should turn off any wrapping // [cell setLineBreakMode:NSLineBreakByClipping]; - [v setBezeled:NO]; - [v setBordered:NO]; - c = new wxNSTextFieldControl( wxpeer, wxpeer, v ); + + if ( (style & wxNO_BORDER) || (style & wxSIMPLE_BORDER) ) + { + // under 10.7 the textcontrol can draw its own focus + // even if no border is shown, on previous systems + // we have to emulate this + [v setBezeled:NO]; + [v setBordered:NO]; + if ( UMAGetSystemVersion() < 0x1070 ) + c->SetNeedsFocusRect( true ); + } + else + { + // use native border + c->SetNeedsFrame(false); + } } - c->SetNeedsFocusRect( true ); return c; }