PageRenderTime 29ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Three20UI/Sources/TTStyledTextLabel.m

https://github.com/GetMoPix/three20
Objective C | 538 lines | 320 code | 113 blank | 105 comment | 59 complexity | 63426602e9f7460ba292e702ef190ce6 MD5 | raw file
  1. //
  2. // Copyright 2009-2011 Facebook
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "Three20UI/TTStyledTextLabel.h"
  17. // UI
  18. #import "Three20UI/TTNavigator.h"
  19. #import "Three20UI/TTTableView.h"
  20. #import "Three20UI/UIViewAdditions.h"
  21. // Style
  22. #import "Three20Style/TTGlobalStyle.h"
  23. #import "Three20Style/TTStyledText.h"
  24. #import "Three20Style/TTStyledNode.h"
  25. #import "Three20Style/TTStyleSheet.h"
  26. #import "Three20Style/TTStyledElement.h"
  27. #import "Three20Style/TTStyledLinkNode.h"
  28. #import "Three20Style/TTStyledButtonNode.h"
  29. #import "Three20Style/TTStyledTextNode.h"
  30. // - Styled frames
  31. #import "Three20Style/TTStyledInlineFrame.h"
  32. #import "Three20Style/TTStyledTextFrame.h"
  33. // Core
  34. #import "Three20Core/TTCorePreprocessorMacros.h"
  35. #import "Three20Core/TTDebug.h"
  36. static const CGFloat kCancelHighlightThreshold = 4.0f;
  37. ///////////////////////////////////////////////////////////////////////////////////////////////////
  38. ///////////////////////////////////////////////////////////////////////////////////////////////////
  39. ///////////////////////////////////////////////////////////////////////////////////////////////////
  40. @implementation TTStyledTextLabel
  41. @synthesize text = _text;
  42. @synthesize textColor = _textColor;
  43. @synthesize highlightedTextColor = _highlightedTextColor;
  44. @synthesize font = _font;
  45. @synthesize textAlignment = _textAlignment;
  46. @synthesize contentInset = _contentInset;
  47. @synthesize highlighted = _highlighted;
  48. @synthesize highlightedNode = _highlightedNode;
  49. ///////////////////////////////////////////////////////////////////////////////////////////////////
  50. - (id)initWithFrame:(CGRect)frame {
  51. self = [super initWithFrame:frame];
  52. if (self) {
  53. _textAlignment = UITextAlignmentLeft;
  54. _contentInset = UIEdgeInsetsZero;
  55. self.font = TTSTYLEVAR(font);
  56. self.backgroundColor = TTSTYLEVAR(backgroundColor);
  57. self.contentMode = UIViewContentModeRedraw;
  58. }
  59. return self;
  60. }
  61. ///////////////////////////////////////////////////////////////////////////////////////////////////
  62. - (void)dealloc {
  63. _text.delegate = nil;
  64. TT_RELEASE_SAFELY(_text);
  65. TT_RELEASE_SAFELY(_font);
  66. TT_RELEASE_SAFELY(_textColor);
  67. TT_RELEASE_SAFELY(_highlightedTextColor);
  68. TT_RELEASE_SAFELY(_highlightedNode);
  69. TT_RELEASE_SAFELY(_highlightedFrame);
  70. TT_RELEASE_SAFELY(_accessibilityElements);
  71. [super dealloc];
  72. }
  73. ///////////////////////////////////////////////////////////////////////////////////////////////////
  74. ///////////////////////////////////////////////////////////////////////////////////////////////////
  75. #pragma mark -
  76. #pragma mark Private
  77. ///////////////////////////////////////////////////////////////////////////////////////////////////
  78. /**
  79. * UITableView looks for this function and crashes if it is not found when you select a cell
  80. */
  81. - (BOOL)isHighlighted {
  82. return _highlighted;
  83. }
  84. ///////////////////////////////////////////////////////////////////////////////////////////////////
  85. - (void)setStyle:(TTStyle*)style forFrame:(TTStyledBoxFrame*)frame {
  86. if ([frame isKindOfClass:[TTStyledInlineFrame class]]) {
  87. TTStyledInlineFrame* inlineFrame = (TTStyledInlineFrame*)frame;
  88. while (inlineFrame.inlinePreviousFrame) {
  89. inlineFrame = inlineFrame.inlinePreviousFrame;
  90. }
  91. while (inlineFrame) {
  92. inlineFrame.style = style;
  93. inlineFrame = inlineFrame.inlineNextFrame;
  94. }
  95. } else {
  96. frame.style = style;
  97. }
  98. }
  99. ///////////////////////////////////////////////////////////////////////////////////////////////////
  100. - (void)setHighlightedFrame:(TTStyledBoxFrame*)frame{
  101. if (frame != _highlightedFrame) {
  102. TTTableView* tableView = (TTTableView*)[self ancestorOrSelfWithClass:[TTTableView class]];
  103. TTStyledBoxFrame* affectFrame = frame ? frame : _highlightedFrame;
  104. NSString* className = affectFrame.element.className;
  105. if (!className && [affectFrame.element isKindOfClass:[TTStyledLinkNode class]]) {
  106. className = @"linkText:";
  107. }
  108. if (className && [className rangeOfString:@":"].location != NSNotFound) {
  109. if (frame) {
  110. TTStyle* style = [TTSTYLESHEET styleWithSelector:className
  111. forState:UIControlStateHighlighted];
  112. [self setStyle:style forFrame:frame];
  113. [_highlightedFrame release];
  114. _highlightedFrame = [frame retain];
  115. [_highlightedNode release];
  116. _highlightedNode = [frame.element retain];
  117. tableView.highlightedLabel = self;
  118. } else {
  119. TTStyle* style = [TTSTYLESHEET styleWithSelector:className forState:UIControlStateNormal];
  120. [self setStyle:style forFrame:_highlightedFrame];
  121. TT_RELEASE_SAFELY(_highlightedFrame);
  122. TT_RELEASE_SAFELY(_highlightedNode);
  123. tableView.highlightedLabel = nil;
  124. }
  125. [self setNeedsDisplay];
  126. }
  127. }
  128. }
  129. ///////////////////////////////////////////////////////////////////////////////////////////////////
  130. - (NSString*)combineTextFromFrame:(TTStyledTextFrame*)fromFrame
  131. toFrame:(TTStyledTextFrame*)toFrame {
  132. NSMutableArray* strings = [NSMutableArray array];
  133. for (TTStyledTextFrame* frame = fromFrame; frame && frame != toFrame;
  134. frame = (TTStyledTextFrame*)frame.nextFrame) {
  135. [strings addObject:frame.text];
  136. }
  137. return [strings componentsJoinedByString:@""];
  138. }
  139. ///////////////////////////////////////////////////////////////////////////////////////////////////
  140. - (void)addAccessibilityElementFromFrame:(TTStyledTextFrame*)fromFrame
  141. toFrame:(TTStyledTextFrame*)toFrame withEdges:(UIEdgeInsets)edges {
  142. CGRect rect = CGRectMake(edges.left, edges.top,
  143. edges.right-edges.left, edges.bottom-edges.top);
  144. UIAccessibilityElement* acc = [[[UIAccessibilityElement alloc]
  145. initWithAccessibilityContainer:self] autorelease];
  146. acc.accessibilityFrame = CGRectOffset(rect, self.screenViewX, self.screenViewY);
  147. acc.accessibilityTraits = UIAccessibilityTraitStaticText;
  148. if (fromFrame == toFrame) {
  149. acc.accessibilityLabel = fromFrame.text;
  150. } else {
  151. acc.accessibilityLabel = [self combineTextFromFrame:fromFrame toFrame:toFrame];
  152. }
  153. [_accessibilityElements addObject:acc];
  154. }
  155. ///////////////////////////////////////////////////////////////////////////////////////////////////
  156. - (UIEdgeInsets)edgesForRect:(CGRect)rect {
  157. return UIEdgeInsetsMake(rect.origin.y, rect.origin.x,
  158. rect.origin.y+rect.size.height,
  159. rect.origin.x+rect.size.width);
  160. }
  161. ///////////////////////////////////////////////////////////////////////////////////////////////////
  162. - (void)addAccessibilityElementsForNode:(TTStyledNode*)node {
  163. if ([node isKindOfClass:[TTStyledLinkNode class]]) {
  164. UIAccessibilityElement* acc = [[[UIAccessibilityElement alloc]
  165. initWithAccessibilityContainer:self] autorelease];
  166. TTStyledFrame* frame = [_text getFrameForNode:node];
  167. acc.accessibilityFrame = CGRectOffset(frame.bounds, self.screenViewX, self.screenViewY);
  168. acc.accessibilityTraits = UIAccessibilityTraitLink;
  169. acc.accessibilityLabel = [node outerText];
  170. [_accessibilityElements addObject:acc];
  171. } else if ([node isKindOfClass:[TTStyledTextNode class]]) {
  172. TTStyledTextFrame* startFrame = (TTStyledTextFrame*)[_text getFrameForNode:node];
  173. UIEdgeInsets edges = [self edgesForRect:startFrame.bounds];
  174. TTStyledTextFrame* frame = (TTStyledTextFrame*)startFrame.nextFrame;
  175. for (;
  176. [frame isKindOfClass:[TTStyledTextFrame class]];
  177. frame = (TTStyledTextFrame*)frame.nextFrame) {
  178. if (frame.bounds.origin.x < edges.left) {
  179. [self addAccessibilityElementFromFrame:startFrame toFrame:frame withEdges:edges];
  180. edges = [self edgesForRect:frame.bounds];
  181. startFrame = frame;
  182. } else {
  183. if (frame.bounds.origin.x+frame.bounds.size.width > edges.right) {
  184. edges.right = frame.bounds.origin.x+frame.bounds.size.width;
  185. }
  186. if (frame.bounds.origin.y+frame.bounds.size.height > edges.bottom) {
  187. edges.bottom = frame.bounds.origin.y+frame.bounds.size.height;
  188. }
  189. }
  190. }
  191. if (frame != startFrame) {
  192. [self addAccessibilityElementFromFrame:startFrame toFrame:frame withEdges:edges];
  193. }
  194. } else if ([node isKindOfClass:[TTStyledElement class]]) {
  195. TTStyledElement* element = (TTStyledElement*)node;
  196. for (TTStyledNode* child = element.firstChild; child; child = child.nextSibling) {
  197. [self addAccessibilityElementsForNode:child];
  198. }
  199. }
  200. }
  201. ///////////////////////////////////////////////////////////////////////////////////////////////////
  202. - (NSMutableArray*)accessibilityElements {
  203. if (!_accessibilityElements) {
  204. _accessibilityElements = [[NSMutableArray alloc] init];
  205. [self addAccessibilityElementsForNode:_text.rootNode];
  206. }
  207. return _accessibilityElements;
  208. }
  209. ///////////////////////////////////////////////////////////////////////////////////////////////////
  210. //////////////////////////////////////////////////////////////////////////////////////////////////
  211. #pragma mark -
  212. #pragma mark UIResponder
  213. /*
  214. ///////////////////////////////////////////////////////////////////////////////////////////////////
  215. - (BOOL)canBecomeFirstResponder {
  216. return YES;
  217. }
  218. ///////////////////////////////////////////////////////////////////////////////////////////////////
  219. - (BOOL)becomeFirstResponder {
  220. BOOL became = [super becomeFirstResponder];
  221. UIMenuController* menu = [UIMenuController sharedMenuController];
  222. [menu setTargetRect:self.frame inView:self.superview];
  223. [menu setMenuVisible:YES animated:YES];
  224. self.highlighted = YES;
  225. return became;
  226. }
  227. ///////////////////////////////////////////////////////////////////////////////////////////////////
  228. - (BOOL)resignFirstResponder {
  229. self.highlighted = NO;
  230. BOOL resigned = [super resignFirstResponder];
  231. [[UIMenuController sharedMenuController] setMenuVisible:NO];
  232. return resigned;
  233. }
  234. */
  235. ///////////////////////////////////////////////////////////////////////////////////////////////////
  236. - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  237. UITouch* touch = [touches anyObject];
  238. CGPoint point = [touch locationInView:self];
  239. point.x -= _contentInset.left;
  240. point.y -= _contentInset.top;
  241. TTStyledBoxFrame* frame = [_text hitTest:point];
  242. if (frame) {
  243. [self setHighlightedFrame:frame];
  244. }
  245. //[self performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.5];
  246. [super touchesBegan:touches withEvent:event];
  247. }
  248. ///////////////////////////////////////////////////////////////////////////////////////////////////
  249. - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
  250. [super touchesMoved:touches withEvent:event];
  251. }
  252. ///////////////////////////////////////////////////////////////////////////////////////////////////
  253. - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
  254. TTTableView* tableView = (TTTableView*)[self ancestorOrSelfWithClass:[TTTableView class]];
  255. if (!tableView) {
  256. if (_highlightedNode) {
  257. // This is a dirty hack to decouple the UI from Style. TTOpenURL was originally within
  258. // the node implementation. One potential fix would be to provide some protocol for these
  259. // nodes to converse with.
  260. if ([_highlightedNode isKindOfClass:[TTStyledLinkNode class]]) {
  261. TTOpenURL([(TTStyledLinkNode*)_highlightedNode URL]);
  262. } else if ([_highlightedNode isKindOfClass:[TTStyledButtonNode class]]) {
  263. TTOpenURL([(TTStyledButtonNode*)_highlightedNode URL]);
  264. } else {
  265. [_highlightedNode performDefaultAction];
  266. }
  267. [self setHighlightedFrame:nil];
  268. }
  269. }
  270. // We definitely don't want to call this if the label is inside a TTTableView, because
  271. // it winds up calling touchesEnded on the table twice, triggering the link twice
  272. [super touchesEnded:touches withEvent:event];
  273. }
  274. ///////////////////////////////////////////////////////////////////////////////////////////////////
  275. - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
  276. [super touchesCancelled:touches withEvent:event];
  277. }
  278. ///////////////////////////////////////////////////////////////////////////////////////////////////
  279. ///////////////////////////////////////////////////////////////////////////////////////////////////
  280. #pragma mark -
  281. #pragma mark UIView
  282. ///////////////////////////////////////////////////////////////////////////////////////////////////
  283. - (void)drawRect:(CGRect)rect {
  284. if (_highlighted) {
  285. [self.highlightedTextColor setFill];
  286. } else {
  287. [self.textColor setFill];
  288. }
  289. CGPoint origin = CGPointMake(rect.origin.x + _contentInset.left,
  290. rect.origin.y + _contentInset.top);
  291. [_text drawAtPoint:origin highlighted:_highlighted];
  292. }
  293. ///////////////////////////////////////////////////////////////////////////////////////////////////
  294. - (void)layoutSubviews {
  295. [super layoutSubviews];
  296. CGFloat newWidth = self.width - (_contentInset.left + _contentInset.right);
  297. if (newWidth != _text.width) {
  298. // Remove the highlighted node+frame when resizing the text
  299. self.highlightedNode = nil;
  300. }
  301. _text.width = newWidth;
  302. }
  303. ///////////////////////////////////////////////////////////////////////////////////////////////////
  304. - (CGSize)sizeThatFits:(CGSize)size {
  305. [self layoutIfNeeded];
  306. return CGSizeMake(_text.width + (_contentInset.left + _contentInset.right),
  307. _text.height+ (_contentInset.top + _contentInset.bottom));
  308. }
  309. ///////////////////////////////////////////////////////////////////////////////////////////////////
  310. //////////////////////////////////////////////////////////////////////////////////////////////////
  311. #pragma mark -
  312. #pragma mark UIAccessibilityContainer
  313. ///////////////////////////////////////////////////////////////////////////////////////////////////
  314. - (id)accessibilityElementAtIndex:(NSInteger)elementIndex {
  315. return [[self accessibilityElements] objectAtIndex:elementIndex];
  316. }
  317. ///////////////////////////////////////////////////////////////////////////////////////////////////
  318. - (NSInteger)accessibilityElementCount {
  319. return [self accessibilityElements].count;
  320. }
  321. ///////////////////////////////////////////////////////////////////////////////////////////////////
  322. - (NSInteger)indexOfAccessibilityElement:(id)element {
  323. return [[self accessibilityElements] indexOfObject:element];
  324. }
  325. ///////////////////////////////////////////////////////////////////////////////////////////////////
  326. //////////////////////////////////////////////////////////////////////////////////////////////////
  327. #pragma mark -
  328. #pragma mark UIResponderStandardEditActions
  329. ///////////////////////////////////////////////////////////////////////////////////////////////////
  330. - (void)copy:(id)sender {
  331. NSString* text = _text.rootNode.outerText;
  332. UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
  333. [pasteboard setValue:text forPasteboardType:@"public.utf8-plain-text"];
  334. }
  335. ///////////////////////////////////////////////////////////////////////////////////////////////////
  336. ///////////////////////////////////////////////////////////////////////////////////////////////////
  337. #pragma mark -
  338. #pragma mark TTStyledTextDelegate
  339. ///////////////////////////////////////////////////////////////////////////////////////////////////
  340. - (void)styledTextNeedsDisplay:(TTStyledText*)text {
  341. [self setNeedsDisplay];
  342. }
  343. ///////////////////////////////////////////////////////////////////////////////////////////////////
  344. ///////////////////////////////////////////////////////////////////////////////////////////////////
  345. #pragma mark -
  346. #pragma mark Public
  347. ///////////////////////////////////////////////////////////////////////////////////////////////////
  348. - (void)setText:(TTStyledText*)text {
  349. if (text != _text) {
  350. _text.delegate = nil;
  351. [_text release];
  352. TT_RELEASE_SAFELY(_accessibilityElements);
  353. _text = [text retain];
  354. _text.delegate = self;
  355. _text.font = _font;
  356. _text.textAlignment = _textAlignment;
  357. [self setNeedsLayout];
  358. [self setNeedsDisplay];
  359. }
  360. }
  361. ///////////////////////////////////////////////////////////////////////////////////////////////////
  362. - (NSString*)html {
  363. return [_text description];
  364. }
  365. ///////////////////////////////////////////////////////////////////////////////////////////////////
  366. - (void)setHtml:(NSString*)html {
  367. self.text = [TTStyledText textFromXHTML:html];
  368. }
  369. ///////////////////////////////////////////////////////////////////////////////////////////////////
  370. - (void)setFont:(UIFont*)font {
  371. if (font != _font) {
  372. [_font release];
  373. _font = [font retain];
  374. _text.font = _font;
  375. [self setNeedsLayout];
  376. }
  377. }
  378. ///////////////////////////////////////////////////////////////////////////////////////////////////
  379. - (void)setTextAlignment:(UITextAlignment)textAlignment {
  380. if (textAlignment != _textAlignment) {
  381. _textAlignment = textAlignment;
  382. _text.textAlignment = _textAlignment;
  383. [self setNeedsLayout];
  384. }
  385. }
  386. ///////////////////////////////////////////////////////////////////////////////////////////////////
  387. - (UIColor*)textColor {
  388. if (!_textColor) {
  389. _textColor = [TTSTYLEVAR(textColor) retain];
  390. }
  391. return _textColor;
  392. }
  393. ///////////////////////////////////////////////////////////////////////////////////////////////////
  394. - (void)setTextColor:(UIColor*)textColor {
  395. if (textColor != _textColor) {
  396. [_textColor release];
  397. _textColor = [textColor retain];
  398. [self setNeedsDisplay];
  399. }
  400. }
  401. ///////////////////////////////////////////////////////////////////////////////////////////////////
  402. - (UIColor*)highlightedTextColor {
  403. if (!_highlightedTextColor) {
  404. _highlightedTextColor = [TTSTYLEVAR(highlightedTextColor) retain];
  405. }
  406. return _highlightedTextColor;
  407. }
  408. ///////////////////////////////////////////////////////////////////////////////////////////////////
  409. - (void)setHighlightedNode:(TTStyledElement*)node {
  410. if (node != _highlightedNode) {
  411. if (!node) {
  412. [self setHighlightedFrame:nil];
  413. } else {
  414. [_highlightedNode release];
  415. _highlightedNode = [node retain];
  416. }
  417. }
  418. }
  419. @end