1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/osx/cocoa/textctrl.mm
3// Purpose:     wxTextCtrl
4// Author:      Stefan Csomor
5// Modified by: Ryan Norton (MLTE GetLineLength and GetLineText)
6// Created:     1998-01-01
7// Copyright:   (c) Stefan Csomor
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#if wxUSE_TEXTCTRL
14
15#include "wx/textctrl.h"
16
17#ifndef WX_PRECOMP
18    #include "wx/intl.h"
19    #include "wx/app.h"
20    #include "wx/utils.h"
21    #include "wx/dc.h"
22    #include "wx/button.h"
23    #include "wx/menu.h"
24    #include "wx/settings.h"
25    #include "wx/msgdlg.h"
26    #include "wx/toplevel.h"
27#endif
28
29#ifdef __DARWIN__
30    #include <sys/types.h>
31    #include <sys/stat.h>
32#else
33    #include <stat.h>
34#endif
35
36#if wxUSE_STD_IOSTREAM
37    #if wxUSE_IOSTREAMH
38        #include <fstream.h>
39    #else
40        #include <fstream>
41    #endif
42#endif
43
44#include "wx/filefn.h"
45#include "wx/sysopt.h"
46#include "wx/thread.h"
47#include "wx/textcompleter.h"
48
49#include "wx/osx/private.h"
50#include "wx/osx/cocoa/private/textimpl.h"
51
52@interface NSView(EditableView)
53- (BOOL)isEditable;
54- (void)setEditable:(BOOL)flag;
55- (BOOL)isSelectable;
56- (void)setSelectable:(BOOL)flag;
57@end
58
59// An object of this class is created before the text is modified
60// programmatically and destroyed as soon as this is done. It does several
61// things, like ensuring that the control is editable to allow setting its text
62// at all and eating any unwanted focus loss events from textDidEndEditing:
63// which don't really correspond to focus change.
64class wxMacEditHelper
65{
66public :
67    wxMacEditHelper( NSView* textView )
68    {
69        m_viewPreviouslyEdited = ms_viewCurrentlyEdited;
70        ms_viewCurrentlyEdited =
71        m_textView = textView;
72        m_formerEditable = YES;
73        if ( textView )
74        {
75            m_formerEditable = [textView isEditable];
76            m_formerSelectable = [textView isSelectable];
77            [textView setEditable:YES];
78        }
79    }
80
81    ~wxMacEditHelper()
82    {
83        if ( m_textView )
84        {
85            [m_textView setEditable:m_formerEditable];
86            [m_textView setSelectable:m_formerSelectable];
87        }
88
89        ms_viewCurrentlyEdited = m_viewPreviouslyEdited;
90    }
91
92    // Returns true if this view is the one currently being changed by the
93    // program.
94    static bool IsCurrentlyEditedView(NSView* v)
95    {
96        return v == ms_viewCurrentlyEdited;
97    }
98
99    // Returns true if this editor is the one currently being modified.
100    static bool IsCurrentEditor(wxNSTextFieldEditor* e)
101    {
102        return e == [(NSTextField*)ms_viewCurrentlyEdited currentEditor];
103    }
104
105protected :
106    BOOL m_formerEditable ;
107    BOOL m_formerSelectable;
108    NSView* m_textView;
109
110    // The original value of ms_viewCurrentlyEdited when this object was
111    // created.
112    NSView* m_viewPreviouslyEdited;
113
114    static NSView* ms_viewCurrentlyEdited;
115} ;
116
117NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil;
118
119// a minimal NSFormatter that just avoids getting too long entries
120@interface wxMaximumLengthFormatter : NSFormatter
121{
122    int maxLength;
123    wxTextEntry* field;
124}
125
126@end
127
128@implementation wxMaximumLengthFormatter
129
130- (id)init
131{
132    self = [super init];
133    maxLength = 0;
134    return self;
135}
136
137- (void) setMaxLength:(int) maxlen
138{
139    maxLength = maxlen;
140}
141
142- (NSString *)stringForObjectValue:(id)anObject
143{
144    if(![anObject isKindOfClass:[NSString class]])
145        return nil;
146    return [NSString stringWithString:anObject];
147}
148
149- (BOOL)getObjectValue:(id *)obj forString:(NSString *)string errorDescription:(NSString  **)error
150{
151    *obj = [NSString stringWithString:string];
152    return YES;
153}
154
155- (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
156              originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error
157{
158    int len = [*partialStringPtr length];
159    if ( maxLength > 0 && len > maxLength )
160    {
161        field->SendMaxLenEvent();
162        return NO;
163    }
164    return YES;
165}
166
167- (void) setTextEntry:(wxTextEntry*) tf
168{
169    field = tf;
170}
171
172@end
173
174@implementation wxNSSecureTextField
175
176+ (void)initialize
177{
178    static BOOL initialized = NO;
179    if (!initialized)
180    {
181        initialized = YES;
182        wxOSXCocoaClassAddWXMethods( self );
183    }
184}
185
186- (void)controlTextDidChange:(NSNotification *)aNotification
187{
188    wxUnusedVar(aNotification);
189    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
190    if ( impl )
191        impl->controlTextDidChange();
192}
193
194- (void)controlTextDidEndEditing:(NSNotification *)aNotification
195{
196    wxUnusedVar(aNotification);
197    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
198    if ( impl )
199        impl->DoNotifyFocusLost();
200}
201
202- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector
203{
204    wxUnusedVar(textView);
205
206    BOOL handled = NO;
207
208    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( control );
209    if ( impl  )
210    {
211        wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer();
212        if ( wxpeer )
213        {
214            if (commandSelector == @selector(insertNewline:))
215            {
216                if ( wxpeer->GetWindowStyle() & wxTE_PROCESS_ENTER )
217                {
218                    wxCommandEvent event(wxEVT_TEXT_ENTER, wxpeer->GetId());
219                    event.SetEventObject( wxpeer );
220                    wxTextWidgetImpl* impl = (wxNSTextFieldControl * ) wxWidgetImpl::FindFromWXWidget( self );
221                    wxTextEntry * const entry = impl->GetTextEntry();
222                    event.SetString( entry->GetValue() );
223                    handled = wxpeer->HandleWindowEvent( event );
224                }
225                else
226                {
227                    wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(wxpeer), wxTopLevelWindow);
228                    if ( tlw && tlw->GetDefaultItem() )
229                    {
230                        wxButton *def = wxDynamicCast(tlw->GetDefaultItem(), wxButton);
231                        if ( def && def->IsEnabled() )
232                        {
233                            wxCommandEvent event(wxEVT_BUTTON, def->GetId() );
234                            event.SetEventObject(def);
235                            def->Command(event);
236                            handled = YES;
237                        }
238                     }
239                }
240            }
241        }
242    }
243
244    return handled;
245}
246
247@end
248
249@interface wxNSTextScrollView : NSScrollView
250{
251}
252@end
253
254@implementation wxNSTextScrollView
255
256+ (void)initialize
257{
258    static BOOL initialized = NO;
259    if (!initialized)
260    {
261        initialized = YES;
262        wxOSXCocoaClassAddWXMethods( self );
263    }
264}
265
266@end
267
268@implementation wxNSTextFieldEditor
269
270- (void) keyDown:(NSEvent*) event
271{
272    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( (WXWidget) [self delegate] );
273    lastKeyDownEvent = event;
274    if ( impl == NULL || !impl->DoHandleKeyEvent(event) )
275        [super keyDown:event];
276    lastKeyDownEvent = nil;
277}
278
279- (void) keyUp:(NSEvent*) event
280{
281    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( (WXWidget) [self delegate] );
282    if ( impl == NULL || !impl->DoHandleKeyEvent(event) )
283        [super keyUp:event];
284}
285
286- (void) flagsChanged:(NSEvent*) event
287{
288    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( (WXWidget) [self delegate] );
289    if ( impl == NULL || !impl->DoHandleKeyEvent(event) )
290        [super flagsChanged:event];
291}
292
293- (BOOL) performKeyEquivalent:(NSEvent*) event
294{
295    BOOL retval = [super performKeyEquivalent:event];
296    return retval;
297}
298
299- (void) insertText:(id) str
300{
301    // We should never generate char events for the text being inserted
302    // programmatically.
303    if ( !wxMacEditHelper::IsCurrentEditor(self) )
304    {
305        NSString *text = [str isKindOfClass:[NSAttributedString class]] ? [str string] : str;
306        wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( (WXWidget) [self delegate] );
307        if ( impl && lastKeyDownEvent && impl->DoHandleCharEvent(lastKeyDownEvent, text) )
308            return;
309    }
310
311    [super insertText:str];
312}
313
314- (BOOL) resignFirstResponder
315{
316    return [super resignFirstResponder];
317}
318
319- (BOOL) becomeFirstResponder
320{
321    // we need the stored text field, as at this point the delegate is not yet set
322    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( (WXWidget) textField );
323
324    BOOL r = [super becomeFirstResponder];
325    if ( impl != NULL && r )
326        impl->DoNotifyFocusSet();
327
328    return r;
329}
330
331- (void) setTextField:(NSTextField*) field
332{
333    textField = field;
334}
335
336
337@end
338
339@implementation wxNSTextView
340
341+ (void)initialize
342{
343    static BOOL initialized = NO;
344    if (!initialized)
345    {
346        initialized = YES;
347        wxOSXCocoaClassAddWXMethods( self );
348    }
349}
350
351- (void)textDidChange:(NSNotification *)aNotification
352{
353    wxUnusedVar(aNotification);
354    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
355    if ( impl )
356        impl->controlTextDidChange();
357}
358
359- (void) setEnabled:(BOOL) flag
360{
361    // from Technical Q&A QA1461
362    if (flag) {
363        [self setTextColor: [NSColor controlTextColor]];
364
365    } else {
366        [self setTextColor: [NSColor disabledControlTextColor]];
367    }
368
369    [self setSelectable: flag];
370    [self setEditable: flag];
371}
372
373- (BOOL) isEnabled
374{
375    return [self isEditable];
376}
377
378- (void)textDidEndEditing:(NSNotification *)aNotification
379{
380    wxUnusedVar(aNotification);
381
382    if ( wxMacEditHelper::IsCurrentlyEditedView(self) )
383    {
384        // This notification is generated as the result of calling our own
385        // wxTextCtrl method (e.g. WriteText()) and doesn't correspond to any
386        // real focus loss event so skip generating it.
387        return;
388    }
389
390    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
391    if ( impl )
392        impl->DoNotifyFocusLost();
393}
394
395@end
396
397@implementation wxNSTextField
398
399+ (void)initialize
400{
401    static BOOL initialized = NO;
402    if (!initialized)
403    {
404        initialized = YES;
405        wxOSXCocoaClassAddWXMethods( self );
406    }
407}
408
409- (id) initWithFrame:(NSRect) frame
410{
411    self = [super initWithFrame:frame];
412    fieldEditor = nil;
413    return self;
414}
415
416- (void) dealloc
417{
418    [fieldEditor release];
419    [super dealloc];
420}
421
422- (void) setFieldEditor:(wxNSTextFieldEditor*) editor
423{
424    if ( editor != fieldEditor )
425    {
426        [editor retain];
427        [fieldEditor release];
428        fieldEditor = editor;
429    }
430}
431
432- (wxNSTextFieldEditor*) fieldEditor
433{
434    return fieldEditor;
435}
436
437- (void) setEnabled:(BOOL) flag
438{
439    [super setEnabled: flag];
440
441    if (![self drawsBackground]) {
442        // Static text is drawn incorrectly when disabled.
443        // For an explanation, see
444        // http://www.cocoabuilder.com/archive/message/cocoa/2006/7/21/168028
445        if (flag) {
446            [self setTextColor: [NSColor controlTextColor]];
447        } else {
448            [self setTextColor: [NSColor secondarySelectedControlColor]];
449        }
450    }
451}
452
453- (void)controlTextDidChange:(NSNotification *)aNotification
454{
455    wxUnusedVar(aNotification);
456    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
457    if ( impl )
458        impl->controlTextDidChange();
459}
460
461- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
462 forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger*)index
463{
464    NSMutableArray* matches = NULL;
465
466    wxTextWidgetImpl* impl = (wxNSTextFieldControl * ) wxWidgetImpl::FindFromWXWidget( self );
467    wxTextEntry * const entry = impl->GetTextEntry();
468    wxTextCompleter * const completer = entry->OSXGetCompleter();
469    if ( completer )
470    {
471        const wxString prefix = entry->GetValue();
472        if ( completer->Start(prefix) )
473        {
474            const wxString
475                wordStart = wxCFStringRef::AsString(
476                              [[textView string] substringWithRange:charRange]
477                            );
478
479            matches = [NSMutableArray array];
480            for ( ;; )
481            {
482                const wxString s = completer->GetNext();
483                if ( s.empty() )
484                    break;
485
486                // Normally the completer should return only the strings
487                // starting with the prefix, but there could be exceptions
488                // and, for compatibility with MSW which simply ignores all
489                // entries that don't match the current text control contents,
490                // we ignore them as well. Besides, our own wxTextCompleterFixed
491                // doesn't respect this rule and, moreover, we need to extract
492                // just the rest of the string anyhow.
493                wxString completion;
494                if ( s.StartsWith(prefix, &completion) )
495                {
496                    // We discarded the entire prefix above but actually we
497                    // should include the part of it that consists of the
498                    // beginning of the current word, otherwise it would be
499                    // lost when completion is accepted as OS X supposes that
500                    // our matches do start with the "partial word range"
501                    // passed to us.
502                    const wxCFStringRef fullWord(wordStart + completion);
503                    [matches addObject: fullWord.AsNSString()];
504                }
505            }
506        }
507    }
508
509    return matches;
510}
511
512- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector
513{
514    wxUnusedVar(textView);
515    wxUnusedVar(control);
516
517    BOOL handled = NO;
518
519    // send back key events wx' common code knows how to handle
520
521    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
522    if ( impl  )
523    {
524        wxWindow* wxpeer = (wxWindow*) impl->GetWXPeer();
525        if ( wxpeer )
526        {
527            if (commandSelector == @selector(insertNewline:))
528            {
529                [textView insertNewlineIgnoringFieldEditor:self];
530                handled = YES;
531            }
532            else if ( commandSelector == @selector(insertTab:))
533            {
534                [textView insertTabIgnoringFieldEditor:self];
535                handled = YES;
536            }
537            else if ( commandSelector == @selector(insertBacktab:))
538            {
539                [textView insertTabIgnoringFieldEditor:self];
540                handled = YES;
541            }
542        }
543    }
544
545    return handled;
546}
547
548- (void)controlTextDidEndEditing:(NSNotification *)aNotification
549{
550    wxUnusedVar(aNotification);
551    wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self );
552    if ( impl )
553    {
554        wxNSTextFieldControl* timpl = dynamic_cast<wxNSTextFieldControl*>(impl);
555        if ( timpl )
556            timpl->UpdateInternalSelectionFromEditor(fieldEditor);
557        impl->DoNotifyFocusLost();
558    }
559}
560@end
561
562// wxNSTextViewControl
563
564wxNSTextViewControl::wxNSTextViewControl( wxTextCtrl *wxPeer, WXWidget w, long style )
565    : wxWidgetCocoaImpl(wxPeer, w),
566      wxTextWidgetImpl(wxPeer)
567{
568    wxNSTextScrollView* sv = (wxNSTextScrollView*) w;
569    m_scrollView = sv;
570
571    const bool hasHScroll = (style & wxHSCROLL) != 0;
572
573    [m_scrollView setHasVerticalScroller:YES];
574    [m_scrollView setHasHorizontalScroller:hasHScroll];
575    NSSize contentSize = [m_scrollView contentSize];
576    NSRect viewFrame = NSMakeRect(
577            0, 0,
578            hasHScroll ? FLT_MAX : contentSize.width, contentSize.height
579        );
580
581    wxNSTextView* const tv = [[wxNSTextView alloc] initWithFrame: viewFrame];
582    m_textView = tv;
583    [tv setVerticallyResizable:YES];
584    [tv setHorizontallyResizable:hasHScroll];
585    [tv setAutoresizingMask:NSViewWidthSizable];
586
587    if ( hasHScroll )
588    {
589        [[tv textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
590        [[tv textContainer] setWidthTracksTextView:NO];
591    }
592
593    if ( style & wxTE_RIGHT)
594    {
595        [tv setAlignment:NSRightTextAlignment];
596    }
597    else if ( style & wxTE_CENTRE)
598    {
599        [tv setAlignment:NSCenterTextAlignment];
600    }
601
602    if ( !wxPeer->HasFlag(wxTE_RICH | wxTE_RICH2) )
603    {
604        [tv setRichText:NO];
605    }
606
607    [m_scrollView setDocumentView: tv];
608
609    [tv setDelegate: tv];
610
611    InstallEventHandler(tv);
612}
613
614wxNSTextViewControl::~wxNSTextViewControl()
615{
616    if (m_textView)
617        [m_textView setDelegate: nil];
618}
619
620bool wxNSTextViewControl::CanFocus() const
621{
622    // since this doesn't work (return false), we hardcode
623    // if (m_textView)
624    //    return [m_textView canBecomeKeyView];
625    return true;
626}
627
628wxString wxNSTextViewControl::GetStringValue() const
629{
630    if (m_textView)
631    {
632        wxString result = wxCFStringRef::AsString([m_textView string], m_wxPeer->GetFont().GetEncoding());
633        wxMacConvertNewlines13To10( &result ) ;
634        return result;
635    }
636    return wxEmptyString;
637}
638void wxNSTextViewControl::SetStringValue( const wxString &str)
639{
640    wxString st = str;
641    wxMacConvertNewlines10To13( &st );
642    wxMacEditHelper helper(m_textView);
643
644    if (m_textView)
645        [m_textView setString: wxCFStringRef( st , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
646}
647
648void wxNSTextViewControl::Copy()
649{
650    if (m_textView)
651        [m_textView copy:nil];
652
653}
654
655void wxNSTextViewControl::Cut()
656{
657    if (m_textView)
658        [m_textView cut:nil];
659}
660
661void wxNSTextViewControl::Paste()
662{
663    if (m_textView)
664        [m_textView paste:nil];
665}
666
667bool wxNSTextViewControl::CanPaste() const
668{
669    return true;
670}
671
672void wxNSTextViewControl::SetEditable(bool editable)
673{
674    if (m_textView)
675        [m_textView setEditable: editable];
676}
677
678void wxNSTextViewControl::GetSelection( long* from, long* to) const
679{
680    if (m_textView)
681    {
682        NSRange range = [m_textView selectedRange];
683        *from = range.location;
684        *to = range.location + range.length;
685    }
686}
687
688void wxNSTextViewControl::SetSelection( long from , long to )
689{
690    long textLength = [[m_textView string] length];
691    if ((from == -1) && (to == -1))
692    {
693        from = 0 ;
694        to = textLength ;
695    }
696    else
697    {
698        from = wxMin(textLength,wxMax(from,0)) ;
699        if ( to == -1 )
700            to = textLength;
701        else
702            to = wxMax(0,wxMin(textLength,to)) ;
703    }
704
705    NSRange selrange = NSMakeRange(from, to-from);
706    [m_textView setSelectedRange:selrange];
707    [m_textView scrollRangeToVisible:selrange];
708}
709
710void wxNSTextViewControl::WriteText(const wxString& str)
711{
712    wxString st = str;
713    wxMacConvertNewlines10To13( &st );
714    wxMacEditHelper helper(m_textView);
715    NSEvent* formerEvent = m_lastKeyDownEvent;
716    m_lastKeyDownEvent = nil;
717    [m_textView insertText:wxCFStringRef( st , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
718    m_lastKeyDownEvent = formerEvent;
719}
720
721void wxNSTextViewControl::SetFont( const wxFont & font , const wxColour& WXUNUSED(foreground) , long WXUNUSED(windowStyle), bool WXUNUSED(ignoreBlack) )
722{
723    if ([m_textView respondsToSelector:@selector(setFont:)])
724        [m_textView setFont: font.OSXGetNSFont()];
725}
726
727bool wxNSTextViewControl::GetStyle(long position, wxTextAttr& style)
728{
729    if (m_textView && position >=0)
730    {
731        NSFont* font = NULL;
732        NSColor* bgcolor = NULL;
733        NSColor* fgcolor = NULL;
734        // NOTE: It appears that other platforms accept GetStyle with the position == length
735        // but that NSTextStorage does not accept length as a valid position.
736        // Therefore we return the default control style in that case.
737        if (position < (long) [[m_textView string] length])
738        {
739            NSTextStorage* storage = [m_textView textStorage];
740            font = [[storage attribute:NSFontAttributeName atIndex:position effectiveRange:NULL] autorelease];
741            bgcolor = [[storage attribute:NSBackgroundColorAttributeName atIndex:position effectiveRange:NULL] autorelease];
742            fgcolor = [[storage attribute:NSForegroundColorAttributeName atIndex:position effectiveRange:NULL] autorelease];
743        }
744        else
745        {
746            NSDictionary* attrs = [m_textView typingAttributes];
747            font = [[attrs objectForKey:NSFontAttributeName] autorelease];
748            bgcolor = [[attrs objectForKey:NSBackgroundColorAttributeName] autorelease];
749            fgcolor = [[attrs objectForKey:NSForegroundColorAttributeName] autorelease];
750        }
751
752        if (font)
753            style.SetFont(wxFont(font));
754
755        if (bgcolor)
756            style.SetBackgroundColour(wxColour(bgcolor));
757
758        if (fgcolor)
759            style.SetTextColour(wxColour(fgcolor));
760        return true;
761    }
762
763    return false;
764}
765
766void wxNSTextViewControl::SetStyle(long start,
767                                long end,
768                                const wxTextAttr& style)
769{
770    if ( !m_textView )
771        return;
772
773    if ( start == -1 && end == -1 )
774    {
775        NSMutableDictionary* const
776            attrs = [NSMutableDictionary dictionaryWithCapacity:3];
777        if ( style.HasFont() )
778            [attrs setValue:style.GetFont().OSXGetNSFont() forKey:NSFontAttributeName];
779        if ( style.HasBackgroundColour() )
780            [attrs setValue:style.GetBackgroundColour().OSXGetNSColor() forKey:NSBackgroundColorAttributeName];
781        if ( style.HasTextColour() )
782            [attrs setValue:style.GetTextColour().OSXGetNSColor() forKey:NSForegroundColorAttributeName];
783
784        [m_textView setTypingAttributes:attrs];
785    }
786    else // Set the attributes just for this range.
787    {
788        NSRange range = NSMakeRange(start, end-start);
789
790        NSTextStorage* storage = [m_textView textStorage];
791        if ( style.HasFont() )
792            [storage addAttribute:NSFontAttributeName value:style.GetFont().OSXGetNSFont() range:range];
793
794        if ( style.HasBackgroundColour() )
795            [storage addAttribute:NSBackgroundColorAttributeName value:style.GetBackgroundColour().OSXGetNSColor() range:range];
796
797        if ( style.HasTextColour() )
798            [storage addAttribute:NSForegroundColorAttributeName value:style.GetTextColour().OSXGetNSColor() range:range];
799    }
800}
801
802void wxNSTextViewControl::CheckSpelling(bool check)
803{
804    if (m_textView)
805        [m_textView setContinuousSpellCheckingEnabled: check];
806}
807
808wxSize wxNSTextViewControl::GetBestSize() const
809{
810    if (m_textView && [m_textView layoutManager])
811    {
812        NSRect rect = [[m_textView layoutManager] usedRectForTextContainer: [m_textView textContainer]];
813        return wxSize((int)(rect.size.width + [m_textView textContainerInset].width),
814                      (int)(rect.size.height + [m_textView textContainerInset].height));
815    }
816    return wxSize(0,0);
817}
818
819// wxNSTextFieldControl
820
821wxNSTextFieldControl::wxNSTextFieldControl( wxTextCtrl *text, WXWidget w )
822    : wxWidgetCocoaImpl(text, w),
823      wxTextWidgetImpl(text)
824{
825    Init(w);
826}
827
828wxNSTextFieldControl::wxNSTextFieldControl(wxWindow *wxPeer,
829                                           wxTextEntry *entry,
830                                           WXWidget w)
831    : wxWidgetCocoaImpl(wxPeer, w),
832      wxTextWidgetImpl(entry)
833{
834    Init(w);
835}
836
837void wxNSTextFieldControl::Init(WXWidget w)
838{
839    NSTextField wxOSX_10_6_AND_LATER(<NSTextFieldDelegate>) *tf = (NSTextField wxOSX_10_6_AND_LATER(<NSTextFieldDelegate>)*) w;
840    m_textField = tf;
841    [m_textField setDelegate: tf];
842    m_selStart = m_selEnd = 0;
843    m_hasEditor = [w isKindOfClass:[NSTextField class]];
844}
845
846wxNSTextFieldControl::~wxNSTextFieldControl()
847{
848    if (m_textField)
849        [m_textField setDelegate: nil];
850}
851
852wxString wxNSTextFieldControl::GetStringValue() const
853{
854    return wxCFStringRef::AsString([m_textField stringValue], m_wxPeer->GetFont().GetEncoding());
855}
856
857void wxNSTextFieldControl::SetStringValue( const wxString &str)
858{
859    wxMacEditHelper helper(m_textField);
860    [m_textField setStringValue: wxCFStringRef( str , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
861}
862
863void wxNSTextFieldControl::SetMaxLength(unsigned long len)
864{
865    wxMaximumLengthFormatter* formatter = [[[wxMaximumLengthFormatter alloc] init] autorelease];
866    [formatter setMaxLength:len];
867    [formatter setTextEntry:GetTextEntry()];
868    [m_textField setFormatter:formatter];
869}
870
871void wxNSTextFieldControl::Copy()
872{
873    NSText* editor = [m_textField currentEditor];
874    if ( editor )
875    {
876        [editor copy:nil];
877    }
878}
879
880void wxNSTextFieldControl::Cut()
881{
882    NSText* editor = [m_textField currentEditor];
883    if ( editor )
884    {
885        [editor cut:nil];
886    }
887}
888
889void wxNSTextFieldControl::Paste()
890{
891    NSText* editor = [m_textField currentEditor];
892    if ( editor )
893    {
894        [editor paste:nil];
895    }
896}
897
898bool wxNSTextFieldControl::CanPaste() const
899{
900    return true;
901}
902
903void wxNSTextFieldControl::SetEditable(bool editable)
904{
905    [m_textField setEditable:editable];
906}
907
908void wxNSTextFieldControl::GetSelection( long* from, long* to) const
909{
910    NSText* editor = [m_textField currentEditor];
911    if ( editor )
912    {
913        NSRange range = [editor selectedRange];
914        *from = range.location;
915        *to = range.location + range.length;
916    }
917    else
918    {
919        *from = m_selStart;
920        *to = m_selEnd;
921    }
922}
923
924void wxNSTextFieldControl::SetSelection( long from , long to )
925{
926    long textLength = [[m_textField stringValue] length];
927    if ((from == -1) && (to == -1))
928    {
929        from = 0 ;
930        to = textLength ;
931    }
932    else
933    {
934        from = wxMin(textLength,wxMax(from,0)) ;
935        if ( to == -1 )
936            to = textLength;
937        else
938            to = wxMax(0,wxMin(textLength,to)) ;
939    }
940
941    NSText* editor = [m_textField currentEditor];
942    if ( editor )
943    {
944        [editor setSelectedRange:NSMakeRange(from, to-from)];
945    }
946
947    // the editor might still be in existence, but we might be already passed our 'focus lost' storage
948    // of the selection, so make sure we copy this
949    m_selStart = from;
950    m_selEnd = to;
951}
952
953void wxNSTextFieldControl::WriteText(const wxString& str)
954{
955    NSEvent* formerEvent = m_lastKeyDownEvent;
956    m_lastKeyDownEvent = nil;
957    NSText* editor = [m_textField currentEditor];
958    if ( editor )
959    {
960        wxMacEditHelper helper(m_textField);
961        BOOL hasUndo = [editor respondsToSelector:@selector(setAllowsUndo:)];
962        if ( hasUndo )
963            [(NSTextView*)editor setAllowsUndo:NO];
964        [editor insertText:wxCFStringRef( str , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
965        if ( hasUndo )
966            [(NSTextView*)editor setAllowsUndo:YES];
967    }
968    else
969    {
970        wxString val = GetStringValue() ;
971        long start , end ;
972        GetSelection( &start , &end ) ;
973        val.Remove( start , end - start ) ;
974        val.insert( start , str ) ;
975        SetStringValue( val ) ;
976        SetSelection( start + str.length() , start + str.length() ) ;
977    }
978    m_lastKeyDownEvent = formerEvent;
979}
980
981void wxNSTextFieldControl::controlAction(WXWidget WXUNUSED(slf),
982    void* WXUNUSED(_cmd), void *WXUNUSED(sender))
983{
984    wxWindow* wxpeer = (wxWindow*) GetWXPeer();
985    if ( wxpeer && (wxpeer->GetWindowStyle() & wxTE_PROCESS_ENTER) )
986    {
987        wxCommandEvent event(wxEVT_TEXT_ENTER, wxpeer->GetId());
988        event.SetEventObject( wxpeer );
989        event.SetString( GetTextEntry()->GetValue() );
990        wxpeer->HandleWindowEvent( event );
991    }
992}
993
994void wxNSTextFieldControl::SetInternalSelection( long from , long to )
995{
996    m_selStart = from;
997    m_selEnd = to;
998}
999
1000void wxNSTextFieldControl::UpdateInternalSelectionFromEditor( wxNSTextFieldEditor* fieldEditor )
1001{
1002    if ( fieldEditor )
1003    {
1004        NSRange range = [fieldEditor selectedRange];
1005        SetInternalSelection(range.location, range.location + range.length);
1006    }
1007}
1008
1009// as becoming first responder on a window - triggers a resign on the same control, we have to avoid
1010// the resign notification writing back native selection values before we can set our own
1011
1012static WXWidget s_widgetBecomingFirstResponder = nil;
1013
1014bool wxNSTextFieldControl::becomeFirstResponder(WXWidget slf, void *_cmd)
1015{
1016    s_widgetBecomingFirstResponder = slf;
1017    bool retval = wxWidgetCocoaImpl::becomeFirstResponder(slf, _cmd);
1018    s_widgetBecomingFirstResponder = nil;
1019    if ( retval )
1020    {
1021        NSText* editor = [m_textField currentEditor];
1022        if ( editor )
1023        {
1024            long textLength = [[m_textField stringValue] length];
1025            m_selStart = wxMin(textLength,wxMax(m_selStart,0)) ;
1026            m_selEnd = wxMax(0,wxMin(textLength,m_selEnd)) ;
1027
1028            [editor setSelectedRange:NSMakeRange(m_selStart, m_selEnd-m_selStart)];
1029        }
1030    }
1031    return retval;
1032}
1033
1034bool wxNSTextFieldControl::resignFirstResponder(WXWidget slf, void *_cmd)
1035{
1036    if ( slf != s_widgetBecomingFirstResponder )
1037    {
1038        NSText* editor = [m_textField currentEditor];
1039        if ( editor )
1040        {
1041            NSRange range = [editor selectedRange];
1042            m_selStart = range.location;
1043            m_selEnd = range.location + range.length;
1044        }
1045    }
1046    return wxWidgetCocoaImpl::resignFirstResponder(slf, _cmd);
1047}
1048
1049bool wxNSTextFieldControl::SetHint(const wxString& hint)
1050{
1051    wxCFStringRef hintstring(hint);
1052    [[m_textField cell] setPlaceholderString:hintstring.AsNSString()];
1053    return true;
1054}
1055
1056//
1057//
1058//
1059
1060wxWidgetImplType* wxWidgetImpl::CreateTextControl( wxTextCtrl* wxpeer,
1061                                    wxWindowMac* WXUNUSED(parent),
1062                                    wxWindowID WXUNUSED(id),
1063                                    const wxString& WXUNUSED(str),
1064                                    const wxPoint& pos,
1065                                    const wxSize& size,
1066                                    long style,
1067                                    long WXUNUSED(extraStyle))
1068{
1069    NSRect r = wxOSXGetFrameForControl( wxpeer, pos , size ) ;
1070    wxWidgetCocoaImpl* c = NULL;
1071
1072    if ( style & wxTE_MULTILINE )
1073    {
1074        wxNSTextScrollView* v = nil;
1075        v = [[wxNSTextScrollView alloc] initWithFrame:r];
1076        c = new wxNSTextViewControl( wxpeer, v, style );
1077        c->SetNeedsFocusRect( true );
1078    }
1079    else
1080    {
1081        NSTextField* v = nil;
1082        if ( style & wxTE_PASSWORD )
1083            v = [[wxNSSecureTextField alloc] initWithFrame:r];
1084        else
1085            v = [[wxNSTextField alloc] initWithFrame:r];
1086
1087        if ( style & wxTE_RIGHT)
1088        {
1089            [v setAlignment:NSRightTextAlignment];
1090        }
1091        else if ( style & wxTE_CENTRE)
1092        {
1093            [v setAlignment:NSCenterTextAlignment];
1094        }
1095
1096        NSTextFieldCell* cell = [v cell];
1097        [cell setScrollable:YES];
1098        // TODO: Remove if we definitely are sure, it's not needed
1099        // as setting scrolling to yes, should turn off any wrapping
1100        // [cell setLineBreakMode:NSLineBreakByClipping];
1101
1102        c = new wxNSTextFieldControl( wxpeer, wxpeer, v );
1103
1104        if ( (style & wxNO_BORDER) || (style & wxSIMPLE_BORDER) )
1105        {
1106            // under 10.7 the textcontrol can draw its own focus
1107            // even if no border is shown, on previous systems
1108            // we have to emulate this
1109            [v setBezeled:NO];
1110            [v setBordered:NO];
1111            if ( UMAGetSystemVersion() < 0x1070 )
1112                c->SetNeedsFocusRect( true );
1113        }
1114        else
1115        {
1116            // use native border
1117            c->SetNeedsFrame(false);
1118        }
1119    }
1120
1121    return c;
1122}
1123
1124
1125#endif // wxUSE_TEXTCTRL
1126