diff --git a/samples/TTCatalog/Classes/RootViewController.m b/samples/TTCatalog/Classes/RootViewController.m
index 1f99733eba..4545b74ac1 100644
--- a/samples/TTCatalog/Classes/RootViewController.m
+++ b/samples/TTCatalog/Classes/RootViewController.m
@@ -111,4 +111,14 @@ - (void)willNavigateToObject:(id)object inView:(NSString*)viewType
viewController.title = field.text;
}
+- (BOOL)shouldLoadExternalURL:(NSURL*)url {
+ NSString* message = [NSString stringWithFormat:@"You touched a link to %@", url];
+ UIAlertView* alertView = [[[UIAlertView alloc] initWithTitle:@"Link"
+ message:message delegate:nil cancelButtonTitle:NSLocalizedString(@"Ok", @"")
+ otherButtonTitles:nil] autorelease];
+ [alertView show];
+
+ return NO;
+}
+
@end
diff --git a/samples/TTCatalog/Classes/StyledTextTableTestController.m b/samples/TTCatalog/Classes/StyledTextTableTestController.m
index e8b26a923b..a2865f10f6 100644
--- a/samples/TTCatalog/Classes/StyledTextTableTestController.m
+++ b/samples/TTCatalog/Classes/StyledTextTableTestController.m
@@ -26,6 +26,7 @@ - (void)loadView {
@"This is a whole bunch of text made from characters and followed by this url http://bit.ly/1234",
@"Here we have a url http://www.h0tlinkz.com followed by another http://www.internets.com",
@"http://www.cnn.com is a url and the words you are now reading are the text that follows",
+ @"Here is text that has absolutely no styles. Move along now. Nothing to see here. Goodbye now.",
// @"Let's test out some line breaks.\n\nOh yeah.",
// @"This is a message with a long url in it http://www.foo.com/abra/cadabra/abrabra/dabarababa",
nil];
@@ -34,7 +35,7 @@ - (void)loadView {
TTListDataSource* dataSource = [[[TTListDataSource alloc] init] autorelease];
for (int i = 0; i < 50; ++i) {
NSString* string = [strings objectAtIndex:i % strings.count];
- TTStyledText* text = [TTStyledText textFromURLString:string];
+ TTStyledText* text = [TTStyledText textWithURLs:string];
// Add a bold prefix to the text
NSString* title = [NSString stringWithFormat:@"Row %d: ", i+1];
diff --git a/samples/TTCatalog/Classes/StyledTextTestController.m b/samples/TTCatalog/Classes/StyledTextTestController.m
index b6368770e5..dddaccfdee 100644
--- a/samples/TTCatalog/Classes/StyledTextTestController.m
+++ b/samples/TTCatalog/Classes/StyledTextTestController.m
@@ -8,16 +8,28 @@ @implementation StyledTextTestController
- (void)loadView {
[super loadView];
- NSString* kSampleText = @"This is a test of http://foo.com styled text. This test will \
-be more interesting when I implement the HTML parser. See the 'Styled Labels in Table' test \
-for another example of styled text. Gratuitous URL alert: http://www.foo.com";
+ NSString* kText = @"This is a test of styled labels. Styled labels support \
+bold text and italic text. They also support \
+hyperlinks and inline images \
+. You can also embed a URL inline and it will be turned into \
+a link, like the following URL: http://www.foo.com";
- TTStyledLabel* label = [[[TTStyledLabel alloc] initWithFrame:
+ TTStyledLabel* label1 = [[[TTStyledLabel alloc] initWithFrame:
CGRectInset(self.view.bounds, 10, 10)] autorelease];
- label.font = [UIFont systemFontOfSize:18];
- label.text = [TTStyledText textFromURLString:kSampleText];
-
- [self.view addSubview:label];
+ label1.font = [UIFont systemFontOfSize:17];
+ label1.text = [TTStyledText textFromXHTML:kText];
+ [label1 sizeToFit];
+ [self.view addSubview:label1];
+
+ TTStyledLabel* label2 = [[[TTStyledLabel alloc] initWithFrame:
+ CGRectInset(self.view.bounds, 10, 10)] autorelease];
+ label2.font = [UIFont systemFontOfSize:12];
+ label2.text = [TTStyledText textFromXHTML:kText];
+ label2.textColor = [UIColor grayColor];
+ [label2 sizeToFit];
+ label2.top = label1.bottom + 20;
+ [self.view addSubview:label2];
}
@end
+
\ No newline at end of file
diff --git a/samples/TTCatalog/TTCatalog.xcodeproj/project.pbxproj b/samples/TTCatalog/TTCatalog.xcodeproj/project.pbxproj
index 581da40ad2..0beac15ed7 100755
--- a/samples/TTCatalog/TTCatalog.xcodeproj/project.pbxproj
+++ b/samples/TTCatalog/TTCatalog.xcodeproj/project.pbxproj
@@ -15,6 +15,7 @@
2899E5600DE3E45000AC0155 /* RootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2899E55F0DE3E45000AC0155 /* RootViewController.xib */; };
28AD73600D9D9599002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD735F0D9D9599002E5188 /* MainWindow.xib */; };
28C286E10D94DF7D0034E888 /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C286E00D94DF7D0034E888 /* RootViewController.m */; };
+ BE3188A10F822E2C00E3067D /* smiley.png in Resources */ = {isa = PBXBuildFile; fileRef = BE3188A00F822E2C00E3067D /* smiley.png */; };
BE5F25920EBA5F0400FD59A6 /* PhotoTest2Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = BE5F25910EBA5F0400FD59A6 /* PhotoTest2Controller.m */; };
BE69B7590F62874900C02928 /* TableTestController.m in Sources */ = {isa = PBXBuildFile; fileRef = BE69B7580F62874900C02928 /* TableTestController.m */; };
BE6E46EC0F4578BA001CE9B4 /* TabBarTestController.m in Sources */ = {isa = PBXBuildFile; fileRef = BE6E46EB0F4578BA001CE9B4 /* TabBarTestController.m */; };
@@ -70,6 +71,7 @@
28C286E00D94DF7D0034E888 /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = ""; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BE3188A00F822E2C00E3067D /* smiley.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = smiley.png; sourceTree = ""; };
BE5F25900EBA5F0400FD59A6 /* PhotoTest2Controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoTest2Controller.h; sourceTree = ""; };
BE5F25910EBA5F0400FD59A6 /* PhotoTest2Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoTest2Controller.m; sourceTree = ""; };
BE69B7570F62874900C02928 /* TableTestController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableTestController.h; sourceTree = ""; };
@@ -207,6 +209,7 @@
BE80E39C0EAF103200743358 /* DefaultAlbum.png */,
BE6E4DBF0F46A352001CE9B4 /* tableIcon.png */,
BECB1CC10F46AE9600AE5B52 /* person.jpg */,
+ BE3188A00F822E2C00E3067D /* smiley.png */,
8D1107310486CEB800E47090 /* Info.plist */,
);
name = Resources;
@@ -304,6 +307,7 @@
BE6E4DC00F46A352001CE9B4 /* tableIcon.png in Resources */,
BECB1CC20F46AE9600AE5B52 /* person.jpg in Resources */,
BEDCFBB40F4FFF820060B7D1 /* Three20.bundle in Resources */,
+ BE3188A10F822E2C00E3067D /* smiley.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/samples/TTCatalog/smiley.png b/samples/TTCatalog/smiley.png
new file mode 100644
index 0000000000..86d17949c2
Binary files /dev/null and b/samples/TTCatalog/smiley.png differ
diff --git a/src/TTNavigationCenter.m b/src/TTNavigationCenter.m
index b6eb37d20d..94a271b47e 100644
--- a/src/TTNavigationCenter.m
+++ b/src/TTNavigationCenter.m
@@ -440,7 +440,10 @@ - (TTViewController*)displayURL:(NSString*)url withState:(NSDictionary*)state
animated:(BOOL)animated {
NSURL* u = [NSURL URLWithString:url];
if ([_urlSchemes indexOfObject:u.scheme] == NSNotFound) {
- [[UIApplication sharedApplication] openURL:u];
+ if (![_delegate respondsToSelector:@selector(shouldLoadExternalURL:)]
+ || [_delegate shouldLoadExternalURL:u]) {
+ [[UIApplication sharedApplication] openURL:u];
+ }
} else if (_viewLoaders) {
id object = [self locateObject:u];
NSString* viewType = object && u.query ? u.query : u.host;
diff --git a/src/TTStyledLabel.m b/src/TTStyledLabel.m
index b29931b55d..88168c74b5 100644
--- a/src/TTStyledLabel.m
+++ b/src/TTStyledLabel.m
@@ -77,9 +77,13 @@ - (void)layoutSubviews {
}
- (CGSize)sizeThatFits:(CGSize)size {
+ _text.width = size.width;
return CGSizeMake(_text.width, _text.height);
}
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// UIResponder
+
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch* touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
@@ -97,8 +101,7 @@ - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
if (_highlightedNode) {
- // XXXjoe Still deciding whether to do this, or use a delegate
- // [[TTNavigationCenter defaultCenter] displayURL:_highlightedNode.url];
+ [[TTNavigationCenter defaultCenter] displayURL:_highlightedNode.url];
self.highlightedNode = nil;
@@ -164,6 +167,7 @@ - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
if (self = [super initWithFrame:frame style:style]) {
_highlightedLabel = nil;
_highlightStartPoint = CGPointZero;
+ _highlightTimer = nil;
self.delaysContentTouches = NO;
}
return self;
@@ -171,15 +175,28 @@ - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
- (void)dealloc {
[_highlightedLabel release];
+ [_highlightTimer invalidate];
[super dealloc];
}
+- (void)delayedTouchesEnded:(NSTimer*)timer {
+ _highlightTimer = nil;
+
+ self.highlightedLabel = nil;
+
+ NSString* url = timer.userInfo;
+ [[TTNavigationCenter defaultCenter] displayURL:url];
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////
-// UIView
+// UIResponder
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[super touchesBegan:touches withEvent:event];
+ [_highlightTimer invalidate];
+ _highlightTimer = nil;
+
if (_highlightedLabel) {
UITouch* touch = [touches anyObject];
_highlightStartPoint = [touch locationInView:self];
@@ -204,8 +221,11 @@ - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
if (_highlightedLabel) {
+ NSString* url = _highlightedLabel.highlightedNode.url;
_highlightedLabel.highlightedNode = nil;
- self.highlightedLabel = nil;
+
+ _highlightTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self
+ selector:@selector(delayedTouchesEnded:) userInfo:url repeats:NO];
} else {
[super touchesEnded:touches withEvent:event];
}
diff --git a/src/TTStyledText.m b/src/TTStyledText.m
index 4164ba8cb3..a3b7680e37 100644
--- a/src/TTStyledText.m
+++ b/src/TTStyledText.m
@@ -1,88 +1,35 @@
#import "Three20/TTStyledText.h"
#import "Three20/TTStyledTextNode.h"
+#import "Three20/TTStyledTextParser.h"
#import "Three20/TTAppearance.h"
//////////////////////////////////////////////////////////////////////////////////////////////////
@implementation TTStyledText
-@synthesize rootNode = _rootNode, font = _font, width = _width, height = _height,
- lastLineWidth = _lastLineWidth;
+@synthesize rootNode = _rootNode, font = _font, width = _width, height = _height;
//////////////////////////////////////////////////////////////////////////////////////////////////
// class public
-+ (TTStyledText*)textFromHTMLString:(NSString*)string {
- // XXXjoe XHTML parser yet to be implemented
- return nil;
++ (TTStyledText*)textFromXHTML:(NSString*)source {
+ TTStyledTextParser* parser = [[[TTStyledTextParser alloc] init] autorelease];
+ [parser parseXHTML:source];
+ if (parser.rootNode) {
+ return [[[TTStyledText alloc] initWithNode:parser.rootNode] autorelease];
+ } else {
+ return nil;
+ }
}
-+ (TTStyledText*)textFromURLString:(NSString*)string {
- TTStyledTextNode* rootNode = nil;
- TTStyledTextNode* lastNode = nil;
-
- NSInteger index = 0;
- while (index < string.length) {
- NSRange searchRange = NSMakeRange(index, string.length - index);
- NSRange startRange = [string rangeOfString:@"http://" options:NSCaseInsensitiveSearch
- range:searchRange];
- if (startRange.location == NSNotFound) {
- NSString* text = [string substringWithRange:searchRange];
- TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:text] autorelease];
- if (lastNode) {
- lastNode.nextNode = node;
- } else {
- rootNode = node;
- }
- lastNode = node;
- break;
- } else {
- NSRange beforeRange = NSMakeRange(searchRange.location,
- startRange.location - searchRange.location);
- if (beforeRange.length) {
- NSString* text = [string substringWithRange:beforeRange];
-
- TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:text] autorelease];
- if (lastNode) {
- lastNode.nextNode = node;
- } else {
- rootNode = node;
- }
- lastNode = node;
- }
-
- NSRange searchRange = NSMakeRange(startRange.location, string.length - startRange.location);
- NSRange endRange = [string rangeOfString:@" " options:NSCaseInsensitiveSearch
- range:searchRange];
- if (endRange.location == NSNotFound) {
- NSString* url = [string substringWithRange:searchRange];
- TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] initWithText:url] autorelease];
- node.url = url;
- if (lastNode) {
- lastNode.nextNode = node;
- } else {
- rootNode = node;
- }
- lastNode = node;
- break;
- } else {
- NSRange urlRange = NSMakeRange(startRange.location,
- endRange.location - startRange.location);
- NSString* url = [string substringWithRange:urlRange];
- TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] initWithText:url] autorelease];
- node.url = url;
- if (lastNode) {
- lastNode.nextNode = node;
- } else {
- rootNode = node;
- }
- lastNode = node;
- index = endRange.location;
- }
- }
++ (TTStyledText*)textWithURLs:(NSString*)source {
+ TTStyledTextParser* parser = [[[TTStyledTextParser alloc] init] autorelease];
+ [parser parseURLs:source];
+ if (parser.rootNode) {
+ return [[[TTStyledText alloc] initWithNode:parser.rootNode] autorelease];
+ } else {
+ return nil;
}
-
- return [[[TTStyledText alloc] initWithNode:rootNode] autorelease];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -93,91 +40,163 @@ - (UIFont*)defaultFont {
}
- (UIFont*)boldVersionOfFont:(UIFont*)font {
- // XXXjoe Construct the family name + bold and use that instead of this
return [UIFont boldSystemFontOfSize:font.pointSize];
}
+- (UIFont*)italicVersionOfFont:(UIFont*)font {
+ return [UIFont italicSystemFontOfSize:font.pointSize];
+}
+
- (TTStyledTextFrame*)addFrameForText:(NSString*)text node:(TTStyledTextNode*)node
after:(TTStyledTextFrame*)lastFrame {
- TTStyledTextFrame* frame = [[TTStyledTextFrame alloc] initWithText:text node:node];
+ TTStyledTextFrame* frame = [[[TTStyledTextFrame alloc] initWithText:text node:node] autorelease];
if (lastFrame) {
lastFrame.nextFrame = frame;
} else {
_rootFrame = [frame retain];
}
- [frame release];
return frame;
}
- (void)layoutFrames {
UIFont* baseFont = _font ? _font : [self defaultFont];
- UIFont* boldFont = [self boldVersionOfFont:baseFont];
- CGSize spaceSize = [@" " sizeWithFont:baseFont];
- NSCharacterSet* whitespace = [NSCharacterSet whitespaceCharacterSet];
+ UIFont* boldFont = nil;
+ UIFont* italicFont = nil;
+ NSCharacterSet* whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
- _lineHeight = spaceSize.height;
- _height = _lineHeight;
- _lastLineWidth = 0;
-
TTStyledTextFrame* lastFrame = nil;
+ CGFloat lineWidth = 0;
+ CGFloat height = 0;
TTStyledTextNode* node = _rootNode;
while (node) {
- if ([node isKindOfClass:[TTStyledTextNode class]]) {
- TTStyledTextNode* textNode = (TTStyledTextNode*)node;
- NSString* text = textNode.text;
+ if ([node isKindOfClass:[TTStyledImageNode class]]) {
+ UIImage* image = [(TTStyledImageNode*)node image];
+
+ if (lineWidth + image.size.width > _width) {
+ // The image will be placed on the next line, so create a new frame for
+ // the current line and mark it with a line break
+ lastFrame.lineBreak = YES;
+ lineWidth = 0;
+ }
+
+ lastFrame = [self addFrameForText:nil node:node after:lastFrame];
+ lastFrame.width = image.size.width;
+ lastFrame.height = baseFont.ascender;
+
+ if (!lineWidth) {
+ height += lastFrame.height;
+ }
+ lineWidth += image.size.width;
+ } else {
+ NSString* text = node.text;
+ NSUInteger length = text.length;
- UIFont* font = [node isKindOfClass:[TTStyledLinkNode class]]
- || [node isKindOfClass:[TTStyledBoldNode class]] ? boldFont : baseFont;
-
+ // Figure out which font to use for the node
+ UIFont* font = baseFont;
+ if ([node isKindOfClass:[TTStyledLinkNode class]]
+ || [node isKindOfClass:[TTStyledBoldNode class]]) {
+ if (!boldFont) {
+ boldFont = [self boldVersionOfFont:baseFont];
+ }
+ font = boldFont;
+ } else if ([node isKindOfClass:[TTStyledItalicNode class]]) {
+ if (!italicFont) {
+ italicFont = [self italicVersionOfFont:baseFont];
+ }
+ font = italicFont;
+ }
+
+ if (!node.nextNode && node == _rootNode) {
+ // This is this is the only node, so measure it all at once and move on
+ CGSize textSize = [text sizeWithFont:font
+ constrainedToSize:CGSizeMake(_width, CGFLOAT_MAX)
+ lineBreakMode:UILineBreakModeWordWrap];
+ lastFrame = [self addFrameForText:text node:node after:lastFrame];
+ lastFrame.width = textSize.width;
+ lastFrame.height = textSize.height;
+ lastFrame.font = font;
+ height += textSize.height;
+ break;
+ }
+
NSInteger index = 0;
NSInteger lineStartIndex = 0;
CGFloat frameWidth = 0;
- while (index < text.length) {
- NSRange searchRange = NSMakeRange(index, text.length - index);
+
+ while (index < length) {
+ // Search for the next whitespace character
+ NSRange searchRange = NSMakeRange(index, length - index);
NSRange spaceRange = [text rangeOfCharacterFromSet:whitespace options:0 range:searchRange];
-
- NSRange wordRange;
- if (spaceRange.location != NSNotFound) {
- wordRange = NSMakeRange(searchRange.location,
- (spaceRange.location+1) - searchRange.location);
- } else {
- wordRange = NSMakeRange(searchRange.location, text.length - searchRange.location);
- }
-
+
+ // Get the word prior to the whitespace
+ NSRange wordRange = spaceRange.location != NSNotFound
+ ? NSMakeRange(searchRange.location, (spaceRange.location+1) - searchRange.location)
+ : NSMakeRange(searchRange.location, length - searchRange.location);
NSString* word = [text substringWithRange:wordRange];
- CGSize wordSize = [word sizeWithFont:font];
-
- if (_lastLineWidth + wordSize.width > _width) {
- if (wordSize.width > _width) {
- // XXXjoe Split word into multiple frames here
- }
- NSRange lineRange = NSMakeRange(lineStartIndex, index - lineStartIndex);
- if (lineRange.length) {
- NSString* line = [text substringWithRange:lineRange];
- lastFrame = [self addFrameForText:line node:node after:lastFrame];
- lastFrame.width = frameWidth;
+ // Measure the word and check to see if it fits on the current line
+ CGSize wordSize = [word sizeWithFont:font];
+ if (lineWidth + wordSize.width > _width) {
+ if (0 && wordSize.width > _width) {
+ // The word is larger than an entire line, so we need to split it across lines
+ // XXXjoe TODO
+ } else {
+ // The word will be placed on the next line, so create a new frame for
+ // the current line and mark it with a line break
+ NSRange lineRange = NSMakeRange(lineStartIndex, index - lineStartIndex);
+ if (lineRange.length) {
+ NSString* line = [text substringWithRange:lineRange];
+ lastFrame = [self addFrameForText:line node:node after:lastFrame];
+ lastFrame.width = frameWidth;
+ lastFrame.height = wordSize.height;
+ lastFrame.font = font;
+ }
+
+ lastFrame.lineBreak = YES;
+ lineStartIndex = lineRange.location + lineRange.length;
frameWidth = 0;
+ lineWidth = 0;
}
-
- lastFrame.lineBreak = YES;
- lineStartIndex = lineRange.location + lineRange.length;
-
- _lastLineWidth = 0;
- _height += _lineHeight;
}
- _lastLineWidth += wordSize.width;
+ if (!lineWidth) {
+ // We are at the start of a new line
+ if (node.nextNode) {
+ // Count the height of the new line
+ height += wordSize.height;
+ } else {
+ // This is the last node, so we don't need to keep measuring every word. We
+ // can just measure all remaining text and create a new frame for all of it.
+ NSString* lines = [text substringWithRange:searchRange];
+ CGSize linesSize = [lines sizeWithFont:font
+ constrainedToSize:CGSizeMake(_width, CGFLOAT_MAX)
+ lineBreakMode:UILineBreakModeWordWrap];
+
+ lastFrame = [self addFrameForText:lines node:node after:lastFrame];
+ lastFrame.width = linesSize.width;
+ lastFrame.height = linesSize.height;
+ lastFrame.font = font;
+ height += linesSize.height;
+ break;
+ }
+ }
+
frameWidth += wordSize.width;
+ lineWidth += wordSize.width;
index = wordRange.location + wordRange.length;
- if (index >= text.length) {
- NSRange lineRange = NSMakeRange(lineStartIndex,
- (wordRange.location + wordRange.length) - lineStartIndex);
- NSString* line = [text substringWithRange:lineRange];
+ if (index >= length) {
+ // The current word was at the very end of the string
+ NSRange lineRange = NSMakeRange(lineStartIndex, (wordRange.location + wordRange.length)
+ - lineStartIndex);
+ NSString* line = !lineWidth ? word : [text substringWithRange:lineRange];
+ TTLOG(@"LINE '%@'", line);
+
lastFrame = [self addFrameForText:line node:node after:lastFrame];
lastFrame.width = frameWidth;
+ lastFrame.height = wordSize.height;
+ lastFrame.font = font;
frameWidth = 0;
}
}
@@ -186,8 +205,7 @@ - (void)layoutFrames {
node = node.nextNode;
}
- _height = ceil(_height);
- _lastLineWidth = ceil(_lastLineWidth);
+ _height = ceil(height);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -200,7 +218,6 @@ - (id)initWithNode:(TTStyledTextNode*)rootNode {
_font = nil;
_width = 0;
_height = 0;
- _lastLineWidth = 0;
}
return self;
}
@@ -259,17 +276,14 @@ - (void)drawAtPoint:(CGPoint)point {
- (void)drawAtPoint:(CGPoint)point highlighted:(BOOL)highlighted {
CGContextRef context = UIGraphicsGetCurrentContext();
- UIFont* baseFont = _font ? _font : [self defaultFont];
- UIFont* boldFont = [self boldVersionOfFont:baseFont];
-
CGPoint origin = point;
TTStyledTextFrame* frame = self.rootFrame;
while (frame) {
+ CGRect frameRect = CGRectMake(origin.x, origin.y, frame.width, frame.height);
if ([frame.node isKindOfClass:[TTStyledLinkNode class]]) {
TTStyledLinkNode* linkNode = (TTStyledLinkNode*)frame.node;
if (linkNode.highlighted) {
- CGRect frameRect = CGRectMake(origin.x, origin.y, frame.width, _lineHeight);
UIColor* fill[] = {[UIColor colorWithWhite:0 alpha:0.3]};
[[TTAppearance appearance] draw:TTStyleFill rect:frameRect fill:fill fillCount:1
stroke:nil radius:3];
@@ -280,21 +294,29 @@ - (void)drawAtPoint:(CGPoint)point highlighted:(BOOL)highlighted {
[[TTAppearance appearance].linkTextColor setFill];
}
- [frame.text drawAtPoint:origin withFont:boldFont];
+ [frame.text drawInRect:frameRect withFont:frame.font];
if (!highlighted) {
CGContextRestoreGState(context);
}
} else if ([frame.node isKindOfClass:[TTStyledBoldNode class]]) {
- [frame.text drawAtPoint:origin withFont:boldFont];
+ [frame.text drawInRect:frameRect withFont:frame.font];
+ } else if ([frame.node isKindOfClass:[TTStyledItalicNode class]]) {
+ [frame.text drawInRect:frameRect withFont:frame.font];
+ } else if ([frame.node isKindOfClass:[TTStyledImageNode class]]) {
+ TTStyledImageNode* imageNode = (TTStyledImageNode*)frame.node;
+ UIImage* image = imageNode.image;
+ CGRect imageRect = CGRectMake(origin.x, (origin.y+frame.height)-image.size.height,
+ image.size.width, image.size.height);
+ [imageNode.image drawInRect:imageRect];
} else {
- [frame.text drawAtPoint:origin withFont:baseFont];
+ [frame.text drawInRect:frameRect withFont:frame.font];
}
origin.x += frame.width;
if (frame.lineBreak) {
origin.x = 0;
- origin.y += _lineHeight;
+ origin.y += frame.height;
}
frame = frame.nextFrame;
@@ -305,7 +327,7 @@ - (TTStyledTextFrame*)hitTest:(CGPoint)point {
CGPoint origin = CGPointMake(0, 0);
TTStyledTextFrame* frame = self.rootFrame;
while (frame) {
- CGRect rect = CGRectMake(origin.x, origin.y, frame.width, _lineHeight);
+ CGRect rect = CGRectMake(origin.x, origin.y, frame.width, frame.height);
if (CGRectContainsPoint(rect, point)) {
return frame;
}
@@ -313,7 +335,7 @@ - (TTStyledTextFrame*)hitTest:(CGPoint)point {
origin.x += frame.width;
if (frame.lineBreak) {
origin.x = 0;
- origin.y += _lineHeight;
+ origin.y += frame.height;
}
frame = frame.nextFrame;
@@ -327,8 +349,8 @@ - (TTStyledTextFrame*)hitTest:(CGPoint)point {
@implementation TTStyledTextFrame
-@synthesize node = _node, text = _text, nextFrame = _nextFrame, width = _width,
- lineBreak = _lineBreak;
+@synthesize node = _node, nextFrame = _nextFrame, text = _text, font = _font,
+ width = _width, height = _height, lineBreak = _lineBreak;
///////////////////////////////////////////////////////////////////////////////////////////////////
// NSObject
@@ -336,17 +358,21 @@ @implementation TTStyledTextFrame
- (id)initWithText:(NSString*)text node:(TTStyledTextNode*)node {
if (self = [super init]) {
_text = [text retain];
- _node = [node retain];
_nextFrame = nil;
+ _node = [node retain];
+ _font = nil;
+ _width = 0;
+ _height = 0;
_lineBreak = NO;
}
return self;
}
- (void)dealloc {
- [_text release];
[_node release];
[_nextFrame release];
+ [_text release];
+ [_font release];
[super dealloc];
}
diff --git a/src/TTStyledTextNode.m b/src/TTStyledTextNode.m
index 9a5451c817..5db35a32bc 100644
--- a/src/TTStyledTextNode.m
+++ b/src/TTStyledTextNode.m
@@ -1,4 +1,6 @@
#import "Three20/TTStyledTextNode.h"
+#import "Three20/TTURLRequest.h"
+#import "Three20/TTURLResponse.h"
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -57,6 +59,20 @@ - (NSString*)description {
@end
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation TTStyledItalicNode
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// NSObject
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"/%@/", _text];
+}
+
+@end
+
//////////////////////////////////////////////////////////////////////////////////////////////////
@implementation TTStyledLinkNode
@@ -84,3 +100,47 @@ - (NSString*)description {
}
@end
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation TTStyledImageNode
+
+@synthesize url = _url, image = _image;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// NSObject
+
+- (id)init {
+ if (self = [super init]) {
+ _url = nil;
+ _image = nil;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_url release];
+ [_image release];
+ [super dealloc];
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"(%@)", _url];
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// public
+
+- (UIImage*)image {
+ if (!_image && _url) {
+ TTURLRequest* request = [TTURLRequest requestWithURL:_url delegate:nil];
+ TTURLImageResponse* response = [[[TTURLImageResponse alloc] init] autorelease];
+ request.response = response;
+ if ([request send]) {
+ _image = [response.image retain];
+ }
+ }
+ return _image;
+}
+
+@end
diff --git a/src/TTStyledTextParser.m b/src/TTStyledTextParser.m
new file mode 100644
index 0000000000..5b4a53541a
--- /dev/null
+++ b/src/TTStyledTextParser.m
@@ -0,0 +1,168 @@
+#import "Three20/TTStyledTextParser.h"
+#import "Three20/TTStyledTextNode.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation TTStyledTextParser
+
+@synthesize rootNode = _rootNode;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// private
+
+- (void)addNode:(TTStyledTextNode*)node {
+ if (!_rootNode) {
+ _rootNode = [node retain];
+ } else {
+ _lastNode.nextNode = node;
+ }
+ _lastNode = node;
+}
+
+- (void)flushCharacters {
+ if (_chars.length) {
+ if (_openNode) {
+ _openNode.text = _chars;
+ _openNode = nil;
+ } else if (1) {
+ [self parseURLs:_chars];
+ } else {
+ TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:_chars] autorelease];
+ [self addNode:node];
+ }
+ }
+
+ [_chars release];
+ _chars = nil;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// NSObject
+
+- (id)init {
+ if (self = [super init]) {
+ _rootNode = nil;
+ _openNode = nil;
+ _lastNode = nil;
+ _chars = nil;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_rootNode release];
+ [_chars release];
+ [super dealloc];
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// NSXMLParserDelegate
+
+- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
+ namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
+ attributes:(NSDictionary *)attributeDict {
+ [self flushCharacters];
+
+ NSString* tag = [elementName lowercaseString];
+ if ([tag isEqualToString:@"b"]) {
+ TTStyledBoldNode* node = [[[TTStyledBoldNode alloc] init] autorelease];
+ [self addNode:node];
+ if (_openNode) {
+ // XXXjoe Merge styles
+ } else {
+ _openNode = node;
+ }
+ } else if ([tag isEqualToString:@"i"]) {
+ TTStyledItalicNode* node = [[[TTStyledItalicNode alloc] init] autorelease];
+ [self addNode:node];
+ if (_openNode) {
+ // XXXjoe Merge styles
+ } else {
+ _openNode = node;
+ }
+ } else if ([tag isEqualToString:@"a"]) {
+ TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] init] autorelease];
+ node.url = [attributeDict objectForKey:@"href"];
+
+ [self addNode:node];
+ if (_openNode) {
+ // XXXjoe Merge styles
+ } else {
+ _openNode = node;
+ }
+ } else if ([tag isEqualToString:@"img"]) {
+ TTStyledImageNode* node = [[[TTStyledImageNode alloc] init] autorelease];
+ node.url = [attributeDict objectForKey:@"src"];
+ [self addNode:node];
+ }
+}
+
+- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
+ if (!_chars) {
+ _chars = [string mutableCopy];
+ } else {
+ [_chars appendString:string];
+ }
+}
+
+- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
+ namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
+ [self flushCharacters];
+ _openNode = nil;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// public
+
+- (void)parseXHTML:(NSString*)html {
+ NSString* document = [NSString stringWithFormat:@"%@", html];
+ NSData* data = [document dataUsingEncoding:html.fastestEncoding];
+ NSXMLParser* parser = [[[NSXMLParser alloc] initWithData:data] autorelease];
+ parser.delegate = self;
+ [parser parse];
+}
+
+- (void)parseURLs:(NSString*)string {
+ NSInteger index = 0;
+ while (index < string.length) {
+ NSRange searchRange = NSMakeRange(index, string.length - index);
+ NSRange startRange = [string rangeOfString:@"http://" options:NSCaseInsensitiveSearch
+ range:searchRange];
+ if (startRange.location == NSNotFound) {
+ NSString* text = [string substringWithRange:searchRange];
+ TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:text] autorelease];
+ [self addNode:node];
+ break;
+ } else {
+ NSRange beforeRange = NSMakeRange(searchRange.location,
+ startRange.location - searchRange.location);
+ if (beforeRange.length) {
+ NSString* text = [string substringWithRange:beforeRange];
+
+ TTStyledTextNode* node = [[[TTStyledTextNode alloc] initWithText:text] autorelease];
+ [self addNode:node];
+ }
+
+ NSRange searchRange = NSMakeRange(startRange.location, string.length - startRange.location);
+ NSRange endRange = [string rangeOfString:@" " options:NSCaseInsensitiveSearch
+ range:searchRange];
+ if (endRange.location == NSNotFound) {
+ NSString* url = [string substringWithRange:searchRange];
+ TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] initWithText:url] autorelease];
+ node.url = url;
+ [self addNode:node];
+ break;
+ } else {
+ NSRange urlRange = NSMakeRange(startRange.location,
+ endRange.location - startRange.location);
+ NSString* url = [string substringWithRange:urlRange];
+ TTStyledLinkNode* node = [[[TTStyledLinkNode alloc] initWithText:url] autorelease];
+ node.url = url;
+ [self addNode:node];
+ index = endRange.location;
+ }
+ }
+ }
+}
+
+@end
diff --git a/src/TTTableFieldCell.m b/src/TTTableFieldCell.m
index 811660b8b3..b4a01aa6e3 100644
--- a/src/TTTableFieldCell.m
+++ b/src/TTTableFieldCell.m
@@ -228,18 +228,6 @@ - (void)layoutSubviews {
_label.frame = CGRectInset(self.contentView.bounds, kHPadding, kVPadding);
}
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// UITableViewCell
-
-//- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
-// if (_label.highlightedNode) {
-// self.selectionStyle = UITableViewCellSelectionStyleNone;
-// } else {
-// self.selectionStyle = UITableViewCellSelectionStyleBlue;
-// [super setSelected:selected animated:animated];
-// }
-//}
-
///////////////////////////////////////////////////////////////////////////////////////////////////
// TTTableViewCell
diff --git a/src/TTTableViewController.m b/src/TTTableViewController.m
index 976d48f09a..f227eae954 100644
--- a/src/TTTableViewController.m
+++ b/src/TTTableViewController.m
@@ -213,7 +213,9 @@ - (void)updateTableDelegate {
- (void)reloadTableData {
[self updateTableDelegate];
+ //NSDate* date = [NSDate date];
[_tableView reloadData];
+ //NSLog(@"TABLE LAYOUT %fs", [date timeIntervalSinceNow]);
}
- (void)refreshingHideAnimationStopped {
diff --git a/src/TTTableViewDataSource.m b/src/TTTableViewDataSource.m
index 6c6d2d1f79..14c6d24871 100644
--- a/src/TTTableViewDataSource.m
+++ b/src/TTTableViewDataSource.m
@@ -2,6 +2,7 @@
#import "Three20/TTTableField.h"
#import "Three20/TTTableFieldCell.h"
#import "Three20/TTURLCache.h"
+#import
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,13 +35,18 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger
- (UITableViewCell*)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
id object = [self tableView:tableView objectForRowAtIndexPath:indexPath];
+
Class cellClass = [self tableView:tableView cellClassForObject:object];
+ const char* className = class_getName(cellClass);
+ NSString* identifier = [[NSString alloc] initWithBytesNoCopy:(char*)className
+ length:strlen(className)
+ encoding:NSASCIIStringEncoding freeWhenDone:NO];
- NSString* className = NSStringFromClass(cellClass);
- UITableViewCell* cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:className];
+ UITableViewCell* cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
- cell = [[[cellClass alloc] initWithFrame:CGRectZero reuseIdentifier:className] autorelease];
+ cell = [[[cellClass alloc] initWithFrame:CGRectZero reuseIdentifier:identifier] autorelease];
}
+ [identifier release];
if ([cell isKindOfClass:[TTTableViewCell class]]) {
[(TTTableViewCell*)cell setObject:object];
diff --git a/src/TTURLRequest.m b/src/TTURLRequest.m
index db7ce1a270..e8637a978a 100644
--- a/src/TTURLRequest.m
+++ b/src/TTURLRequest.m
@@ -28,7 +28,9 @@ + (TTURLRequest*)requestWithURL:(NSString*)url delegate:(id)delegate {
if (self = [self init]) {
_url = [url retain];
- [_delegates addObject:delegate];
+ if (delegate) {
+ [_delegates addObject:delegate];
+ }
}
return self;
}
diff --git a/src/TTURLRequestQueue.m b/src/TTURLRequestQueue.m
index 447c398c40..2b32f20158 100644
--- a/src/TTURLRequestQueue.m
+++ b/src/TTURLRequestQueue.m
@@ -43,7 +43,7 @@ - (id)initForRequest:(TTURLRequest*)request queue:(TTURLRequestQueue*)queue;
- (void)addRequest:(TTURLRequest*)request;
- (void)removeRequest:(TTURLRequest*)request;
-- (void)load;
+- (void)load:(NSURL*)url;
- (BOOL)cancel:(TTURLRequest*)request;
@end
@@ -158,28 +158,6 @@ - (void)dispatchError:(NSError*)error {
}
}
-- (void)loadFromBundle:(NSURL*)url {
- NSString* path = nil;
- if (url.path.length) {
- NSString* fileName = [url.path substringFromIndex:1];
- path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil inDirectory:url.host];
- } else {
- path = [[NSBundle mainBundle] pathForResource:url.host ofType:nil];
- }
-
- NSFileManager* fm = [NSFileManager defaultManager];
- if (path && [fm fileExistsAtPath:path]) {
- NSData* data = [NSData dataWithContentsOfFile:path];
- [_queue performSelector:@selector(loader:didLoadResponse:data:) withObject:self
- withObject:nil withObject:data];
- } else {
- NSError* error = [NSError errorWithDomain:NSCocoaErrorDomain
- code:NSFileReadNoSuchFileError userInfo:nil];
- [_queue performSelector:@selector(loader:didFailLoadWithError:) withObject:self
- withObject:error];
- }
-}
-
//////////////////////////////////////////////////////////////////////////////////////////////////
// NSURLConnectionDelegate
@@ -238,7 +216,7 @@ - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)err
// If there is a network error then we will wait and retry a few times just in case
// it was just a temporary blip in connectivity
--_retriesLeft;
- [self load];
+ [self load:[NSURL URLWithString:_url]];
} else {
[_queue performSelector:@selector(loader:didFailLoadWithError:) withObject:self
withObject:error];
@@ -259,14 +237,9 @@ - (void)removeRequest:(TTURLRequest*)request {
[_requests removeObject:request];
}
-- (void)load {
+- (void)load:(NSURL*)url {
if (!_connection) {
- NSURL* url = [NSURL URLWithString:_url];
- if ([url.scheme isEqualToString:@"bundle"]) {
- [self loadFromBundle:url];
- } else {
- [self connectToURL:url];
- }
+ [self connectToURL:url];
}
}
@@ -416,7 +389,7 @@ - (void)executeLoader:(TTRequestLoader*)loader {
[_loaders removeObjectForKey:loader.cacheKey];
} else {
++_totalLoading;
- [loader load];
+ [loader load:[NSURL URLWithString:loader.url]];
}
}
@@ -479,6 +452,45 @@ - (void)loaderDidCancel:(TTRequestLoader*)loader wasLoading:(BOOL)wasLoading {
}
}
+- (void)loadFromBundle:(NSURL*)url request:(TTURLRequest*)request {
+ request.respondedFromCache = YES;
+
+ NSString* path = nil;
+ if (url.path.length) {
+ NSString* fileName = [url.path substringFromIndex:1];
+ path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil inDirectory:url.host];
+ } else {
+ path = [[NSBundle mainBundle] pathForResource:url.host ofType:nil];
+ }
+
+ NSFileManager* fm = [NSFileManager defaultManager];
+ if (path && [fm fileExistsAtPath:path]) {
+ NSData* data = [NSData dataWithContentsOfFile:path];
+ NSError* error = [request.response request:request processResponse:nil data:data];
+ if (error) {
+ for (id delegate in request.delegates) {
+ if ([delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
+ [delegate request:request didFailLoadWithError:error];
+ }
+ }
+ } else {
+ for (id delegate in request.delegates) {
+ if ([delegate respondsToSelector:@selector(requestDidFinishLoad:)]) {
+ [delegate requestDidFinishLoad:request];
+ }
+ }
+ }
+ } else {
+ NSError* error = [NSError errorWithDomain:NSCocoaErrorDomain
+ code:NSFileReadNoSuchFileError userInfo:nil];
+ for (id delegate in request.delegates) {
+ if ([delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
+ [delegate request:request didFailLoadWithError:error];
+ }
+ }
+ }
+}
+
//////////////////////////////////////////////////////////////////////////////////////////////////
- (void)setSuspended:(BOOL)isSuspended {
@@ -494,6 +506,12 @@ - (void)setSuspended:(BOOL)isSuspended {
}
- (BOOL)sendRequest:(TTURLRequest*)request {
+ NSURL* url = [NSURL URLWithString:request.url];
+ if ([url.scheme isEqualToString:@"bundle"]) {
+ [self loadFromBundle:url request:request];
+ return YES;
+ }
+
if (!request.cacheKey) {
request.cacheKey = [[TTURLCache sharedCache] keyForURL:request.url];
}
@@ -529,7 +547,7 @@ - (BOOL)sendRequest:(TTURLRequest*)request {
return NO;
}
}
-
+
// Finally, create a new loader and hit the network (unless we are suspended)
loader = [[TTRequestLoader alloc] initForRequest:request queue:self];
[_loaders setObject:loader forKey:request.cacheKey];
@@ -537,10 +555,10 @@ - (BOOL)sendRequest:(TTURLRequest*)request {
[_loaderQueue addObject:loader];
} else {
++_totalLoading;
- [loader load];
+ [loader load:url];
}
[loader release];
-
+
return NO;
}
diff --git a/src/Three20.xcodeproj/project.pbxproj b/src/Three20.xcodeproj/project.pbxproj
index 39ea285888..5ff9c4959d 100755
--- a/src/Three20.xcodeproj/project.pbxproj
+++ b/src/Three20.xcodeproj/project.pbxproj
@@ -63,6 +63,8 @@
BEE34F270F496620008C826E /* TTTextEditor.h in Headers */ = {isa = PBXBuildFile; fileRef = BEE34F260F496620008C826E /* TTTextEditor.h */; };
BEF198E00F818A1C0010D36E /* TTStyledTextNode.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF198DF0F818A1C0010D36E /* TTStyledTextNode.m */; };
BEF198E20F818A240010D36E /* TTStyledTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = BEF198E10F818A240010D36E /* TTStyledTextNode.h */; };
+ BEF19D6B0F81B8440010D36E /* TTStyledTextParser.h in Headers */ = {isa = PBXBuildFile; fileRef = BEF19D6A0F81B8440010D36E /* TTStyledTextParser.h */; };
+ BEF19D6D0F81B8530010D36E /* TTStyledTextParser.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF19D6C0F81B8530010D36E /* TTStyledTextParser.m */; };
BEF31F700F352E64000DE5D2 /* TTActivityLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF31F460F352E64000DE5D2 /* TTActivityLabel.m */; };
BEF31F710F352E64000DE5D2 /* TTErrorView.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF31F470F352E64000DE5D2 /* TTErrorView.m */; };
BEF31F720F352E64000DE5D2 /* TTGlobal.m in Sources */ = {isa = PBXBuildFile; fileRef = BEF31F480F352E64000DE5D2 /* TTGlobal.m */; };
@@ -170,6 +172,8 @@
BEE34F260F496620008C826E /* TTTextEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TTTextEditor.h; path = Three20/TTTextEditor.h; sourceTree = ""; };
BEF198DF0F818A1C0010D36E /* TTStyledTextNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTStyledTextNode.m; sourceTree = ""; };
BEF198E10F818A240010D36E /* TTStyledTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TTStyledTextNode.h; path = Three20/TTStyledTextNode.h; sourceTree = ""; };
+ BEF19D6A0F81B8440010D36E /* TTStyledTextParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TTStyledTextParser.h; path = Three20/TTStyledTextParser.h; sourceTree = ""; };
+ BEF19D6C0F81B8530010D36E /* TTStyledTextParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTStyledTextParser.m; sourceTree = ""; };
BEF31F3A0F352DF5000DE5D2 /* libThree20.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libThree20.a; sourceTree = BUILT_PRODUCTS_DIR; };
BEF31F460F352E64000DE5D2 /* TTActivityLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTActivityLabel.m; sourceTree = ""; };
BEF31F470F352E64000DE5D2 /* TTErrorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTErrorView.m; sourceTree = ""; };
@@ -274,6 +278,8 @@
BE3EDE420F80C79600A732BE /* TTStyledText.m */,
BEF198E10F818A240010D36E /* TTStyledTextNode.h */,
BEF198DF0F818A1C0010D36E /* TTStyledTextNode.m */,
+ BEF19D6A0F81B8440010D36E /* TTStyledTextParser.h */,
+ BEF19D6C0F81B8530010D36E /* TTStyledTextParser.m */,
);
name = "Styled Text";
sourceTree = "";
@@ -484,6 +490,7 @@
BEF341970F8042520027E93C /* TTStyledLabel.h in Headers */,
BE3EDE410F80C78500A732BE /* TTStyledText.h in Headers */,
BEF198E20F818A240010D36E /* TTStyledTextNode.h in Headers */,
+ BEF19D6B0F81B8440010D36E /* TTStyledTextParser.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -577,6 +584,7 @@
BEF341950F8042440027E93C /* TTStyledLabel.m in Sources */,
BE3EDE430F80C79600A732BE /* TTStyledText.m in Sources */,
BEF198E00F818A1C0010D36E /* TTStyledTextNode.m in Sources */,
+ BEF19D6D0F81B8530010D36E /* TTStyledTextParser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/src/Three20/TTNavigationCenter.h b/src/Three20/TTNavigationCenter.h
index edf921afe4..e961c322ae 100644
--- a/src/Three20/TTNavigationCenter.h
+++ b/src/Three20/TTNavigationCenter.h
@@ -98,5 +98,7 @@ typedef enum {
- (void)didNavigateToObject:(id)object inView:(NSString*)viewType
withController:(UIViewController*)viewController;
+
+- (BOOL)shouldLoadExternalURL:(NSURL*)url;
@end
diff --git a/src/Three20/TTStyledLabel.h b/src/Three20/TTStyledLabel.h
index ccc42cd257..cea0e76d96 100644
--- a/src/Three20/TTStyledLabel.h
+++ b/src/Three20/TTStyledLabel.h
@@ -60,6 +60,7 @@
@interface TTStyledTextTableView : UITableView {
TTStyledLabel* _highlightedLabel;
CGPoint _highlightStartPoint;
+ NSTimer* _highlightTimer;
}
@property(nonatomic,retain) TTStyledLabel* highlightedLabel;
diff --git a/src/Three20/TTStyledText.h b/src/Three20/TTStyledText.h
index 56389f83f8..e6891046ff 100644
--- a/src/Three20/TTStyledText.h
+++ b/src/Three20/TTStyledText.h
@@ -8,8 +8,6 @@
UIFont* _font;
CGFloat _width;
CGFloat _height;
- CGFloat _lineHeight;
- CGFloat _lastLineWidth;
}
@property(nonatomic, retain) TTStyledTextNode* rootNode;
@@ -17,21 +15,18 @@
@property(nonatomic, retain) UIFont* font;
@property(nonatomic) CGFloat width;
@property(nonatomic, readonly) CGFloat height;
-@property(nonatomic, readonly) CGFloat lastLineWidth;
/**
- * Constructs a tree of HTML nodes from a well-formatted XHTML string.
- *
- * NOT YET IMPLEMENTED.
+ * Constructs a tree of HTML nodes from a well-formed XHTML fragment.
*/
-+ (TTStyledText*)textFromHTMLString:(NSString*)string;
++ (TTStyledText*)textFromXHTML:(NSString*)source;
/**
* Constructs a tree of HTML nodes from a string containing URLs.
*
* Only URLs are parsed, not HTML markup. URLs are turned into links.
*/
-+ (TTStyledText*)textFromURLString:(NSString*)string;
++ (TTStyledText*)textWithURLs:(NSString*)source;
- (id)initWithNode:(TTStyledTextNode*)rootNode;
@@ -48,16 +43,20 @@
@interface TTStyledTextFrame : NSObject {
TTStyledTextNode* _node;
- NSString* _text;
TTStyledTextFrame* _nextFrame;
+ NSString* _text;
+ UIFont* _font;
CGFloat _width;
+ CGFloat _height;
BOOL _lineBreak;
}
@property(nonatomic, readonly) TTStyledTextNode* node;
-@property(nonatomic, readonly) NSString* text;
@property(nonatomic, retain) TTStyledTextFrame* nextFrame;
+@property(nonatomic, readonly) NSString* text;
+@property(nonatomic, retain) UIFont* font;
@property(nonatomic) CGFloat width;
+@property(nonatomic) CGFloat height;
@property(nonatomic) BOOL lineBreak;
- (id)initWithText:(NSString*)text node:(TTStyledTextNode*)node;
diff --git a/src/Three20/TTStyledTextNode.h b/src/Three20/TTStyledTextNode.h
index 4737cb41e6..67e6f0b9cd 100644
--- a/src/Three20/TTStyledTextNode.h
+++ b/src/Three20/TTStyledTextNode.h
@@ -26,6 +26,11 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
+@interface TTStyledItalicNode : TTStyledTextNode
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
@interface TTStyledLinkNode : TTStyledTextNode {
NSString* _url;
BOOL _highlighted;
@@ -35,3 +40,15 @@
@property(nonatomic,retain) NSString* url;
@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface TTStyledImageNode : TTStyledTextNode {
+ NSString* _url;
+ UIImage* _image;
+}
+
+@property(nonatomic,retain) NSString* url;
+@property(nonatomic,retain) UIImage* image;
+
+@end
diff --git a/src/Three20/TTStyledTextParser.h b/src/Three20/TTStyledTextParser.h
new file mode 100644
index 0000000000..5290df2577
--- /dev/null
+++ b/src/Three20/TTStyledTextParser.h
@@ -0,0 +1,19 @@
+#import "Three20/TTGlobal.h"
+
+@class TTStyledTextNode;
+
+@interface TTStyledTextParser : NSObject {
+ TTStyledTextNode* _rootNode;
+ TTStyledTextNode* _lastNode;
+ TTStyledTextNode* _openNode;
+ NSError* _parserError;
+ NSMutableString* _chars;
+}
+
+@property(nonatomic, retain) TTStyledTextNode* rootNode;
+
+- (void)parseXHTML:(NSString*)html;
+
+- (void)parseURLs:(NSString*)string;
+
+@end