Permalink
Browse files

Merge pull request twitter-archive#57 from joshaber/spell-checking

Spell checking
  • Loading branch information...
atebits committed Aug 15, 2011
2 parents 74e0a4e + 4a7e716 commit 95bd784db77f3366c0ea25dacdb85ba73ac11763
Showing with 108 additions and 0 deletions.
  1. +1 −0 lib/UIKit/TUITextRenderer+Event.h
  2. +8 −0 lib/UIKit/TUITextView.h
  3. +99 −0 lib/UIKit/TUITextView.m
@@ -19,6 +19,7 @@
@interface TUITextRenderer (Event)
- (CFIndex)stringIndexForPoint:(CGPoint)p;
- (CFIndex)stringIndexForEvent:(NSEvent *)event;
- (void)resetSelection;
- (CGRect)rectForCurrentSelection;
View
@@ -35,6 +35,12 @@
TUIColor *textColor;
TUITextAlignment textAlignment;
BOOL editable;
BOOL spellCheckingEnabled;
NSInteger lastCheckToken;
NSArray *lastCheckResults;
NSTextCheckingResult *selectedTextCheckingResult;
BOOL autocorrectionEnabled;
TUIEdgeInsets contentInset;
@@ -62,6 +68,8 @@
@property (nonatomic, assign) NSRange selectedRange;
@property (nonatomic, assign, getter=isEditable) BOOL editable;
@property (nonatomic, assign, getter=isSpellCheckingEnabled) BOOL spellCheckingEnabled;
@property (nonatomic, assign, getter=isAutocorrectionEnabled) BOOL autocorrectionEnabled;
@property (nonatomic, copy) TUIViewDrawRect drawFrame;
View
@@ -17,6 +17,15 @@
#import "TUIKit.h"
#import "TUITextView.h"
#import "TUITextViewEditor.h"
#import "TUITextRenderer+Event.h"
@interface TUITextView ()
- (void)_checkSpelling;
- (void)_replaceMisspelledWord:(NSMenuItem *)menuItem;
@property (nonatomic, retain) NSArray *lastCheckResults;
@property (nonatomic, retain) NSTextCheckingResult *selectedTextCheckingResult;
@end
@implementation TUITextView
@@ -28,6 +37,10 @@ @implementation TUITextView
@synthesize editable;
@synthesize contentInset;
@synthesize placeholder;
@synthesize spellCheckingEnabled;
@synthesize lastCheckResults;
@synthesize selectedTextCheckingResult;
@synthesize autocorrectionEnabled;
- (void)_updateDefaultAttributes
{
@@ -76,6 +89,8 @@ - (void)dealloc
[font release];
[textColor release];
[placeholder release];
[lastCheckResults release];
[selectedTextCheckingResult release];
[super dealloc];
}
@@ -259,6 +274,90 @@ - (void)_textDidChange
{
if(_textViewFlags.delegateTextViewDidChange)
[delegate textViewDidChange:self];
if(spellCheckingEnabled) {
[self _checkSpelling];
}
}
- (void)_checkSpelling
{
lastCheckToken = [[NSSpellChecker sharedSpellChecker] requestCheckingOfString:self.text range:NSMakeRange(0, [self.text length]) types:NSTextCheckingTypeSpelling options:nil inSpellDocumentWithTag:0 completionHandler:^(NSInteger sequenceNumber, NSArray *results, NSOrthography *orthography, NSInteger wordCount) {
// This needs to happen on the main thread so that the user doesn't enter more text while we're changing the attributed string.
dispatch_async(dispatch_get_main_queue(), ^{
// we only care about the most recent results, ignore anything older
if(sequenceNumber != lastCheckToken) return;
[[renderer backingStore] beginEditing];
NSRange wholeStringRange = NSMakeRange(0, [self.text length]);
[[renderer backingStore] removeAttribute:(id)kCTUnderlineColorAttributeName range:wholeStringRange];
[[renderer backingStore] removeAttribute:(id)kCTUnderlineStyleAttributeName range:wholeStringRange];
NSRange selectionRange = [self selectedRange];
for(NSTextCheckingResult *result in results) {
// Don't spell check the word they're typing, otherwise we're constantly marking it as misspelled and that's lame.
BOOL isActiveWord = (result.range.location + result.range.length == selectionRange.location) && selectionRange.length == 0;
if(isActiveWord) continue;
[[renderer backingStore] addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[TUIColor redColor].CGColor range:result.range];
[[renderer backingStore] addAttribute:(id)kCTUnderlineStyleAttributeName value:[NSNumber numberWithInteger:kCTUnderlineStyleThick | kCTUnderlinePatternDot] range:result.range];
}
[[renderer backingStore] endEditing];
[renderer reset]; // make sure we reset so that the renderer uses our new attributes
[self setNeedsDisplay];
self.lastCheckResults = results;
});
}];
}
- (NSMenu *)menuForEvent:(NSEvent *)event
{
CFIndex stringIndex = [renderer stringIndexForEvent:event];
for(NSTextCheckingResult *result in lastCheckResults) {
if(stringIndex >= result.range.location && stringIndex <= result.range.location + result.range.length) {
self.selectedTextCheckingResult = result;
break;
}
}
if(selectedTextCheckingResult == nil) return nil;
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
NSArray *guesses = [[NSSpellChecker sharedSpellChecker] guessesForWordRange:selectedTextCheckingResult.range inString:[self text] language:nil inSpellDocumentWithTag:0];
if(guesses.count > 0) {
for(NSString *guess in guesses) {
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:guess action:@selector(_replaceMisspelledWord:) keyEquivalent:@""];
[menuItem setTarget:self];
[menuItem setRepresentedObject:guess];
[menu addItem:menuItem];
[menuItem release];
}
} else {
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"No guesses" action:NULL keyEquivalent:@""];
[menu addItem:menuItem];
[menuItem release];
}
return [menu autorelease];
}
- (void)_replaceMisspelledWord:(NSMenuItem *)menuItem
{
NSString *replacement = [menuItem representedObject];
[[renderer backingStore] beginEditing];
[[renderer backingStore] removeAttribute:(id)kCTUnderlineColorAttributeName range:selectedTextCheckingResult.range];
[[renderer backingStore] removeAttribute:(id)kCTUnderlineStyleAttributeName range:selectedTextCheckingResult.range];
[[renderer backingStore] replaceCharactersInRange:self.selectedTextCheckingResult.range withString:replacement];
[[renderer backingStore] endEditing];
[renderer reset];
[self _textDidChange];
self.selectedTextCheckingResult = nil;
}
- (NSRange)selectedRange

0 comments on commit 95bd784

Please sign in to comment.