| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/chrome/browser/ui/util/label_link_controller.h" | 5 #import "ios/chrome/browser/ui/util/label_link_controller.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/ios/ios_util.h" | 10 #include "base/ios/ios_util.h" |
| 11 #include "base/ios/weak_nsobject.h" | |
| 12 #include "base/logging.h" | 11 #include "base/logging.h" |
| 13 #include "base/mac/foundation_util.h" | 12 #include "base/mac/foundation_util.h" |
| 14 #include "base/mac/scoped_block.h" | |
| 15 #import "base/mac/scoped_nsobject.h" | |
| 16 #import "base/strings/sys_string_conversions.h" | 13 #import "base/strings/sys_string_conversions.h" |
| 17 #include "ios/chrome/browser/ui/ui_util.h" | 14 #include "ios/chrome/browser/ui/ui_util.h" |
| 18 #import "ios/chrome/browser/ui/util/label_observer.h" | 15 #import "ios/chrome/browser/ui/util/label_observer.h" |
| 19 #import "ios/chrome/browser/ui/util/text_region_mapper.h" | 16 #import "ios/chrome/browser/ui/util/text_region_mapper.h" |
| 20 #import "ios/chrome/browser/ui/util/transparent_link_button.h" | 17 #import "ios/chrome/browser/ui/util/transparent_link_button.h" |
| 21 #import "net/base/mac/url_conversions.h" | 18 #import "net/base/mac/url_conversions.h" |
| 22 #include "url/gurl.h" | 19 #include "url/gurl.h" |
| 23 | 20 |
| 21 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 22 #error "This file requires ARC support." |
| 23 #endif |
| 24 |
| 24 #pragma mark - LinkLayout | 25 #pragma mark - LinkLayout |
| 25 | 26 |
| 26 // Object encapsulating the range of a link and the frames corresponding with | 27 // Object encapsulating the range of a link and the frames corresponding with |
| 27 // that range. | 28 // that range. |
| 28 @interface LinkLayout : NSObject { | 29 @interface LinkLayout : NSObject |
| 29 // Backing objects for properties of same name. | |
| 30 base::scoped_nsobject<NSArray> _frames; | |
| 31 } | |
| 32 | 30 |
| 33 // Designated initializer. | 31 // Designated initializer. |
| 34 - (instancetype)initWithRange:(NSRange)range NS_DESIGNATED_INITIALIZER; | 32 - (instancetype)initWithRange:(NSRange)range NS_DESIGNATED_INITIALIZER; |
| 35 - (instancetype)init NS_UNAVAILABLE; | 33 - (instancetype)init NS_UNAVAILABLE; |
| 36 | 34 |
| 37 // The range passed on initialization. | 35 // The range passed on initialization. |
| 38 @property(nonatomic, readonly) NSRange range; | 36 @property(nonatomic, readonly) NSRange range; |
| 39 | 37 |
| 40 // The frames calculated for |_range|. | 38 // The frames calculated for |_range|. |
| 41 @property(nonatomic, retain) NSArray* frames; | 39 @property(nonatomic, strong) NSArray* frames; |
| 42 | 40 |
| 43 @end | 41 @end |
| 44 | 42 |
| 45 @implementation LinkLayout | 43 @implementation LinkLayout |
| 46 | 44 |
| 47 @synthesize range = _range; | 45 @synthesize range = _range; |
| 46 @synthesize frames = _frames; |
| 48 | 47 |
| 49 - (instancetype)initWithRange:(NSRange)range { | 48 - (instancetype)initWithRange:(NSRange)range { |
| 50 if ((self = [super init])) { | 49 if ((self = [super init])) { |
| 51 DCHECK_NE(range.location, static_cast<NSUInteger>(NSNotFound)); | 50 DCHECK_NE(range.location, static_cast<NSUInteger>(NSNotFound)); |
| 52 DCHECK_NE(range.length, 0U); | 51 DCHECK_NE(range.length, 0U); |
| 53 _range = range; | 52 _range = range; |
| 54 } | 53 } |
| 55 return self; | 54 return self; |
| 56 } | 55 } |
| 57 | 56 |
| 58 #pragma mark - Accessors | |
| 59 | |
| 60 - (void)setFrames:(NSArray*)frames { | |
| 61 _frames.reset([frames retain]); | |
| 62 } | |
| 63 | |
| 64 - (NSArray*)frames { | |
| 65 return _frames.get(); | |
| 66 } | |
| 67 | |
| 68 @end | 57 @end |
| 69 | 58 |
| 70 #pragma mark - LabelLinkController | 59 #pragma mark - LabelLinkController |
| 71 | 60 |
| 72 @interface LabelLinkController () | 61 @interface LabelLinkController () |
| 73 // Private property exposed publically in testing interface. | 62 // Private property exposed publically in testing interface. |
| 74 @property(nonatomic, assign) Class textMapperClass; | 63 @property(nonatomic, unsafe_unretained) Class textMapperClass; |
| 75 | 64 |
| 76 // The original attributed text set on the label. This may be different from | 65 // The original attributed text set on the label. This may be different from |
| 77 // the label's |attributedText| property, as additional style attributes may be | 66 // the label's |attributedText| property, as additional style attributes may be |
| 78 // introduced for links. | 67 // introduced for links. |
| 79 @property(nonatomic, readonly) NSAttributedString* originalLabelText; | 68 @property(nonatomic, strong, readonly) NSAttributedString* originalLabelText; |
| 80 | 69 |
| 81 // The array of TransparentLinkButtons inserted above the label. | 70 // The array of TransparentLinkButtons inserted above the label. |
| 82 @property(nonatomic, readonly) NSArray* linkButtons; | 71 @property(nonatomic, strong, readonly) NSMutableArray* linkButtons; |
| 83 | 72 |
| 84 // Adds LabelObserverActions to the LabelObserver corresponding to |_label|. | 73 // Adds LabelObserverActions to the LabelObserver corresponding to |_label|. |
| 85 - (void)addLabelObserverActions; | 74 - (void)addLabelObserverActions; |
| 86 | 75 |
| 87 // Clears all defined links and any data associated with them. Update the | 76 // Clears all defined links and any data associated with them. Update the |
| 88 // original attributed text from the controlled label. | 77 // original attributed text from the controlled label. |
| 89 - (void)reset; | 78 - (void)reset; |
| 90 | 79 |
| 91 // Handle a change to the label that changes the positioning of glyphs but not | 80 // Handle a change to the label that changes the positioning of glyphs but not |
| 92 // any styling of those glyphs. Forces a recomputation of the tap regions, and | 81 // any styling of those glyphs. Forces a recomputation of the tap regions, and |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 125 // are cleared. | 114 // are cleared. |
| 126 // If there are tap buttons, but they are not subviews of |_label|'s superview | 115 // If there are tap buttons, but they are not subviews of |_label|'s superview |
| 127 // (if _label's superview has changed since the buttons were created), then | 116 // (if _label's superview has changed since the buttons were created), then |
| 128 // the tap buttons are migrated into the new superview. | 117 // the tap buttons are migrated into the new superview. |
| 129 - (void)updateTapButtons; | 118 - (void)updateTapButtons; |
| 130 | 119 |
| 131 @end | 120 @end |
| 132 | 121 |
| 133 @implementation LabelLinkController { | 122 @implementation LabelLinkController { |
| 134 // Ivars immutable for the lifetime of the object. | 123 // Ivars immutable for the lifetime of the object. |
| 135 base::mac::ScopedBlock<ProceduralBlockWithURL> _action; | 124 ProceduralBlockWithURL _action; |
| 136 base::scoped_nsobject<UILabel> _label; | 125 UILabel* _label; |
| 137 base::scoped_nsobject<UITapGestureRecognizer> _linkTapRecognizer; | 126 UITapGestureRecognizer* _linkTapRecognizer; |
| 138 | |
| 139 // Ivas backing properties. | |
| 140 base::scoped_nsobject<UIColor> _linkColor; | |
| 141 base::scoped_nsobject<UIFont> _linkFont; | |
| 142 | 127 |
| 143 // Ivars that reset when label text changes. | 128 // Ivars that reset when label text changes. |
| 144 base::scoped_nsobject<NSMutableDictionary> _layoutsForURLs; | 129 NSMutableDictionary* _layoutsForURLs; |
| 145 base::scoped_nsobject<NSAttributedString> _originalLabelText; | |
| 146 CGRect _lastLabelFrame; | 130 CGRect _lastLabelFrame; |
| 147 | 131 |
| 148 // Ivars that reset when text or bounds change. | 132 // Ivars that reset when text or bounds change. |
| 149 base::scoped_nsprotocol<id<TextRegionMapper>> _textMapper; | 133 id<TextRegionMapper> _textMapper; |
| 150 | 134 |
| 151 // Internal tracking. | 135 // Internal tracking. |
| 152 BOOL _justUpdatedStyles; | 136 BOOL _justUpdatedStyles; |
| 153 base::scoped_nsobject<NSMutableArray> _linkButtons; | 137 LabelObserver* _labelObserver; |
| 154 base::scoped_nsobject<LabelObserver> _labelObserver; | |
| 155 } | 138 } |
| 156 | 139 |
| 157 @synthesize showTapAreas = _showTapAreas; | 140 @synthesize showTapAreas = _showTapAreas; |
| 158 @synthesize textMapperClass = _textMapperClass; | 141 @synthesize textMapperClass = _textMapperClass; |
| 159 @synthesize linkUnderlineStyle = _linkUnderlineStyle; | 142 @synthesize linkUnderlineStyle = _linkUnderlineStyle; |
| 143 @synthesize linkButtons = _linkButtons; |
| 144 @synthesize originalLabelText = _originalLabelText; |
| 145 @synthesize linkFont = _linkFont; |
| 146 @synthesize linkColor = _linkColor; |
| 160 | 147 |
| 161 - (instancetype)initWithLabel:(UILabel*)label | 148 - (instancetype)initWithLabel:(UILabel*)label |
| 162 action:(ProceduralBlockWithURL)action { | 149 action:(ProceduralBlockWithURL)action { |
| 163 if ((self = [super init])) { | 150 if ((self = [super init])) { |
| 164 DCHECK(label); | 151 DCHECK(label); |
| 165 _label.reset([label retain]); | 152 _label = label; |
| 166 _action.reset(action, base::scoped_policy::RETAIN); | 153 _action = [action copy]; |
| 167 _linkUnderlineStyle = NSUnderlineStyleNone; | 154 _linkUnderlineStyle = NSUnderlineStyleNone; |
| 168 [self reset]; | 155 [self reset]; |
| 169 | 156 |
| 170 _labelObserver.reset([[LabelObserver observerForLabel:_label] retain]); | 157 _labelObserver = [LabelObserver observerForLabel:_label]; |
| 171 [_labelObserver startObserving]; | 158 [_labelObserver startObserving]; |
| 172 [self addLabelObserverActions]; | 159 [self addLabelObserverActions]; |
| 173 | 160 |
| 174 self.textMapperClass = [CoreTextRegionMapper class]; | 161 self.textMapperClass = [CoreTextRegionMapper class]; |
| 175 _linkButtons.reset([[NSMutableArray alloc] init]); | 162 _linkButtons = [[NSMutableArray alloc] init]; |
| 176 } | 163 } |
| 177 return self; | 164 return self; |
| 178 } | 165 } |
| 179 | 166 |
| 180 - (NSAttributedString*)originalLabelText { | |
| 181 return _originalLabelText.get(); | |
| 182 } | |
| 183 | |
| 184 - (NSArray*)linkButtons { | |
| 185 return _linkButtons.get(); | |
| 186 } | |
| 187 | |
| 188 - (void)addLabelObserverActions { | 167 - (void)addLabelObserverActions { |
| 189 base::WeakNSObject<LabelLinkController> weakSelf(self); | 168 __weak LabelLinkController* weakSelf = self; |
| 190 [_labelObserver addStyleChangedAction:^(UILabel* label) { | 169 [_labelObserver addStyleChangedAction:^(UILabel* label) { |
| 191 // One of the style properties has been changed, which will silently | 170 // One of the style properties has been changed, which will silently |
| 192 // update the label's attributedText. | 171 // update the label's attributedText. |
| 193 if (!weakSelf) | 172 if (!weakSelf) |
| 194 return; | 173 return; |
| 195 base::scoped_nsobject<LabelLinkController> strongSelf([weakSelf retain]); | 174 LabelLinkController* strongSelf = weakSelf; |
| 196 [strongSelf labelStyleInvalidated]; | 175 [strongSelf labelStyleInvalidated]; |
| 197 }]; | 176 }]; |
| 198 [_labelObserver addTextChangedAction:^(UILabel* label) { | 177 [_labelObserver addTextChangedAction:^(UILabel* label) { |
| 199 if (!weakSelf) | 178 if (!weakSelf) |
| 200 return; | 179 return; |
| 201 base::scoped_nsobject<LabelLinkController> strongSelf([weakSelf retain]); | 180 LabelLinkController* strongSelf = weakSelf; |
| 202 NSString* originalText = [[strongSelf originalLabelText] string]; | 181 NSString* originalText = [[strongSelf originalLabelText] string]; |
| 203 if ([label.text isEqualToString:originalText]) { | 182 if ([label.text isEqualToString:originalText]) { |
| 204 // The actual text of the label didn't change, so this was a change to | 183 // The actual text of the label didn't change, so this was a change to |
| 205 // the string attributes only. | 184 // the string attributes only. |
| 206 [strongSelf labelStyleInvalidated]; | 185 [strongSelf labelStyleInvalidated]; |
| 207 } else { | 186 } else { |
| 208 // The label text has changed, so start everything from scratch. | 187 // The label text has changed, so start everything from scratch. |
| 209 [strongSelf reset]; | 188 [strongSelf reset]; |
| 210 } | 189 } |
| 211 }]; | 190 }]; |
| 212 [_labelObserver addLayoutChangedAction:^(UILabel* label) { | 191 [_labelObserver addLayoutChangedAction:^(UILabel* label) { |
| 213 if (!weakSelf) | 192 if (!weakSelf) |
| 214 return; | 193 return; |
| 215 base::scoped_nsobject<LabelLinkController> strongSelf([weakSelf retain]); | 194 LabelLinkController* strongSelf = weakSelf; |
| 216 [strongSelf labelLayoutInvalidated]; | 195 [strongSelf labelLayoutInvalidated]; |
| 217 NSArray* linkButtons = [strongSelf linkButtons]; | 196 NSArray* linkButtons = [strongSelf linkButtons]; |
| 218 // If this layout change corresponds to |label|'s moving to a new superview, | 197 // If this layout change corresponds to |label|'s moving to a new superview, |
| 219 // update the tap buttons so that they are inserted above |label| in the new | 198 // update the tap buttons so that they are inserted above |label| in the new |
| 220 // hierarchy. | 199 // hierarchy. |
| 221 if (linkButtons.count && label.superview != [linkButtons[0] superview]) | 200 if (linkButtons.count && label.superview != [linkButtons[0] superview]) |
| 222 [strongSelf updateTapButtons]; | 201 [strongSelf updateTapButtons]; |
| 223 }]; | 202 }]; |
| 224 } | 203 } |
| 225 | 204 |
| 226 - (void)dealloc { | 205 - (void)dealloc { |
| 227 [self clearTapButtons]; | 206 [self clearTapButtons]; |
| 228 [_labelObserver stopObserving]; | 207 [_labelObserver stopObserving]; |
| 229 [super dealloc]; | |
| 230 } | 208 } |
| 231 | 209 |
| 232 - (void)addLinkWithRange:(NSRange)range url:(GURL)url { | 210 - (void)addLinkWithRange:(NSRange)range url:(GURL)url { |
| 233 DCHECK(url.is_valid()); | 211 DCHECK(url.is_valid()); |
| 234 if (!_layoutsForURLs) | 212 if (!_layoutsForURLs) |
| 235 _layoutsForURLs.reset([[NSMutableDictionary alloc] init]); | 213 _layoutsForURLs = [[NSMutableDictionary alloc] init]; |
| 236 NSURL* key = net::NSURLWithGURL(url); | 214 NSURL* key = net::NSURLWithGURL(url); |
| 237 base::scoped_nsobject<LinkLayout> layout( | 215 LinkLayout* layout = [[LinkLayout alloc] initWithRange:range]; |
| 238 [[LinkLayout alloc] initWithRange:range]); | |
| 239 [_layoutsForURLs setObject:layout forKey:key]; | 216 [_layoutsForURLs setObject:layout forKey:key]; |
| 240 [self updateStyles]; | 217 [self updateStyles]; |
| 241 } | 218 } |
| 242 | 219 |
| 243 - (UIColor*)linkColor { | |
| 244 return _linkColor.get(); | |
| 245 } | |
| 246 | |
| 247 - (void)setLinkColor:(UIColor*)linkColor { | 220 - (void)setLinkColor:(UIColor*)linkColor { |
| 248 _linkColor.reset([linkColor copy]); | 221 _linkColor = [linkColor copy]; |
| 249 [self updateStyles]; | 222 [self updateStyles]; |
| 250 } | 223 } |
| 251 | 224 |
| 252 - (void)setLinkUnderlineStyle:(NSUnderlineStyle)underlineStyle { | 225 - (void)setLinkUnderlineStyle:(NSUnderlineStyle)underlineStyle { |
| 253 _linkUnderlineStyle = underlineStyle; | 226 _linkUnderlineStyle = underlineStyle; |
| 254 [self updateStyles]; | 227 [self updateStyles]; |
| 255 } | 228 } |
| 256 | 229 |
| 257 - (UIFont*)linkFont { | |
| 258 return _linkFont.get(); | |
| 259 } | |
| 260 | |
| 261 - (void)setLinkFont:(UIFont*)linkFont { | 230 - (void)setLinkFont:(UIFont*)linkFont { |
| 262 _linkFont.reset([linkFont retain]); | 231 _linkFont = linkFont; |
| 263 [self updateStyles]; | 232 [self updateStyles]; |
| 264 } | 233 } |
| 265 | 234 |
| 266 - (void)setShowTapAreas:(BOOL)showTapAreas { | 235 - (void)setShowTapAreas:(BOOL)showTapAreas { |
| 267 #ifndef NDEBUG | 236 #ifndef NDEBUG |
| 268 for (TransparentLinkButton* button in _linkButtons.get()) { | 237 for (TransparentLinkButton* button in _linkButtons) { |
| 269 button.debug = showTapAreas; | 238 button.debug = showTapAreas; |
| 270 } | 239 } |
| 271 #endif // NDEBUG | 240 #endif // NDEBUG |
| 272 _showTapAreas = showTapAreas; | 241 _showTapAreas = showTapAreas; |
| 273 } | 242 } |
| 274 | 243 |
| 275 #pragma mark - internal methods | 244 #pragma mark - internal methods |
| 276 | 245 |
| 277 - (void)reset { | 246 - (void)reset { |
| 278 _originalLabelText.reset([[_label attributedText] copy]); | 247 _originalLabelText = [[_label attributedText] copy]; |
| 279 _textMapper.reset(); | 248 _textMapper = nil; |
| 280 _lastLabelFrame = CGRectZero; | 249 _lastLabelFrame = CGRectZero; |
| 281 _layoutsForURLs.reset(); | 250 _layoutsForURLs = nil; |
| 282 } | 251 } |
| 283 | 252 |
| 284 - (void)labelLayoutInvalidated { | 253 - (void)labelLayoutInvalidated { |
| 285 _textMapper.reset(); | 254 _textMapper = nil; |
| 286 [self updateTapRects]; | 255 [self updateTapRects]; |
| 287 } | 256 } |
| 288 | 257 |
| 289 - (void)labelStyleInvalidated { | 258 - (void)labelStyleInvalidated { |
| 290 // If the style invalidation was triggered by this class updating link styles, | 259 // If the style invalidation was triggered by this class updating link styles, |
| 291 // then the original label text is still correct, but the tap rects still need | 260 // then the original label text is still correct, but the tap rects still need |
| 292 // to be updated. Otherwise, update the original label text, and then update | 261 // to be updated. Otherwise, update the original label text, and then update |
| 293 // styles. This will set |_justUpdatedStyles| and trigger another call to | 262 // styles. This will set |_justUpdatedStyles| and trigger another call to |
| 294 // this method via KVO. | 263 // this method via KVO. |
| 295 if (_justUpdatedStyles) { | 264 if (_justUpdatedStyles) { |
| 296 // TODO(crbug.com/664648): Remove _justUpdatedStyles due to bug that | 265 // TODO(crbug.com/664648): Remove _justUpdatedStyles due to bug that |
| 297 // prevents proper style updates after successive label format changes. | 266 // prevents proper style updates after successive label format changes. |
| 298 _justUpdatedStyles = NO; | 267 _justUpdatedStyles = NO; |
| 299 } else if (![_originalLabelText isEqual:[_label attributedText]]) { | 268 } else if (![_originalLabelText isEqual:[_label attributedText]]) { |
| 300 _originalLabelText.reset([[_label attributedText] copy]); | 269 _originalLabelText = [[_label attributedText] copy]; |
| 301 [self updateStyles]; | 270 [self updateStyles]; |
| 302 } | 271 } |
| 303 _lastLabelFrame = CGRectZero; | 272 _lastLabelFrame = CGRectZero; |
| 304 [self labelLayoutInvalidated]; | 273 [self labelLayoutInvalidated]; |
| 305 } | 274 } |
| 306 | 275 |
| 307 - (void)updateStyles { | 276 - (void)updateStyles { |
| 308 if (![_layoutsForURLs count]) | 277 if (![_layoutsForURLs count]) |
| 309 return; | 278 return; |
| 310 | 279 |
| 311 __block base::scoped_nsobject<NSMutableAttributedString> labelText( | 280 __block NSMutableAttributedString* labelText = |
| 312 [_originalLabelText mutableCopy]); | 281 [_originalLabelText mutableCopy]; |
| 313 [_layoutsForURLs enumerateKeysAndObjectsUsingBlock:^( | 282 [_layoutsForURLs enumerateKeysAndObjectsUsingBlock:^( |
| 314 NSURL* key, LinkLayout* layout, BOOL* stop) { | 283 NSURL* key, LinkLayout* layout, BOOL* stop) { |
| 315 if (_linkColor) { | 284 if (_linkColor) { |
| 316 [labelText addAttribute:NSForegroundColorAttributeName | 285 [labelText addAttribute:NSForegroundColorAttributeName |
| 317 value:_linkColor | 286 value:_linkColor |
| 318 range:layout.range]; | 287 range:layout.range]; |
| 319 } | 288 } |
| 320 if (_linkUnderlineStyle != NSUnderlineStyleNone) { | 289 if (_linkUnderlineStyle != NSUnderlineStyleNone) { |
| 321 [labelText addAttribute:NSUnderlineStyleAttributeName | 290 [labelText addAttribute:NSUnderlineStyleAttributeName |
| 322 value:@(_linkUnderlineStyle) | 291 value:@(_linkUnderlineStyle) |
| 323 range:layout.range]; | 292 range:layout.range]; |
| 324 } | 293 } |
| 325 if (_linkFont) { | 294 if (_linkFont) { |
| 326 [labelText addAttribute:NSFontAttributeName | 295 [labelText addAttribute:NSFontAttributeName |
| 327 value:_linkFont | 296 value:_linkFont |
| 328 range:layout.range]; | 297 range:layout.range]; |
| 329 } | 298 } |
| 330 }]; | 299 }]; |
| 331 _justUpdatedStyles = YES; | 300 _justUpdatedStyles = YES; |
| 332 [_label setAttributedText:labelText]; | 301 [_label setAttributedText:labelText]; |
| 333 _textMapper.reset(); | 302 _textMapper = nil; |
| 334 } | 303 } |
| 335 | 304 |
| 336 - (void)updateTapRects { | 305 - (void)updateTapRects { |
| 337 // Don't update if the label hasn't changed size or position. | 306 // Don't update if the label hasn't changed size or position. |
| 338 if (CGRectEqualToRect([_label frame], _lastLabelFrame)) | 307 if (CGRectEqualToRect([_label frame], _lastLabelFrame)) |
| 339 return; | 308 return; |
| 340 // Don't update if there are no links. | 309 // Don't update if there are no links. |
| 341 if (![_layoutsForURLs count]) | 310 if (![_layoutsForURLs count]) |
| 342 return; | 311 return; |
| 343 | 312 |
| 344 _lastLabelFrame = [_label frame]; | 313 _lastLabelFrame = [_label frame]; |
| 345 [self clearTapButtons]; | 314 [self clearTapButtons]; |
| 346 | 315 |
| 347 // If the label bounds are zero in either dimension, no rects are possible. | 316 // If the label bounds are zero in either dimension, no rects are possible. |
| 348 if (0.0 == _lastLabelFrame.size.width || 0.0 == _lastLabelFrame.size.height) | 317 if (0.0 == _lastLabelFrame.size.width || 0.0 == _lastLabelFrame.size.height) |
| 349 return; | 318 return; |
| 350 | 319 |
| 351 if (!_textMapper) | 320 if (!_textMapper) |
| 352 [self resetTextMapper]; | 321 [self resetTextMapper]; |
| 353 | 322 |
| 354 for (LinkLayout* layout in [_layoutsForURLs allValues]) { | 323 for (LinkLayout* layout in [_layoutsForURLs allValues]) { |
| 355 base::scoped_nsobject<NSMutableArray> frames([[NSMutableArray alloc] init]); | 324 NSMutableArray* frames = [[NSMutableArray alloc] init]; |
| 356 NSArray* rects = [_textMapper rectsForRange:layout.range]; | 325 NSArray* rects = [_textMapper rectsForRange:layout.range]; |
| 357 for (NSUInteger rectIdx = 0; rectIdx < [rects count]; ++rectIdx) { | 326 for (NSUInteger rectIdx = 0; rectIdx < [rects count]; ++rectIdx) { |
| 358 CGRect frame = [rects[rectIdx] CGRectValue]; | 327 CGRect frame = [rects[rectIdx] CGRectValue]; |
| 359 frame = [[_label superview] convertRect:frame fromView:_label]; | 328 frame = [[_label superview] convertRect:frame fromView:_label]; |
| 360 [frames addObject:[NSValue valueWithCGRect:frame]]; | 329 [frames addObject:[NSValue valueWithCGRect:frame]]; |
| 361 } | 330 } |
| 362 layout.frames = frames; | 331 layout.frames = frames; |
| 363 } | 332 } |
| 364 [self updateTapButtons]; | 333 [self updateTapButtons]; |
| 365 } | 334 } |
| 366 | 335 |
| 367 - (void)resetTextMapper { | 336 - (void)resetTextMapper { |
| 368 DCHECK([self.textMapperClass conformsToProtocol:@protocol(TextRegionMapper)]); | 337 DCHECK([self.textMapperClass conformsToProtocol:@protocol(TextRegionMapper)]); |
| 369 _textMapper.reset([[self.textMapperClass alloc] | 338 _textMapper = [[self.textMapperClass alloc] |
| 370 initWithAttributedString:[_label attributedText] | 339 initWithAttributedString:[_label attributedText] |
| 371 bounds:[_label bounds]]); | 340 bounds:[_label bounds]]; |
| 372 } | 341 } |
| 373 | 342 |
| 374 - (void)clearTapButtons { | 343 - (void)clearTapButtons { |
| 375 for (TransparentLinkButton* button in _linkButtons.get()) { | 344 for (TransparentLinkButton* button in _linkButtons) { |
| 376 [button removeFromSuperview]; | 345 [button removeFromSuperview]; |
| 377 } | 346 } |
| 378 [_linkButtons removeAllObjects]; | 347 [_linkButtons removeAllObjects]; |
| 379 } | 348 } |
| 380 | 349 |
| 381 - (void)updateTapButtons { | 350 - (void)updateTapButtons { |
| 382 // If the label has no superview, clear any existing buttons. | 351 // If the label has no superview, clear any existing buttons. |
| 383 if (![_label superview]) { | 352 if (![_label superview]) { |
| 384 [self clearTapButtons]; | 353 [self clearTapButtons]; |
| 385 return; | 354 return; |
| 386 } else if ([_linkButtons count]) { | 355 } else if ([_linkButtons count]) { |
| 387 // If the buttons are currently in some view other than the label's | 356 // If the buttons are currently in some view other than the label's |
| 388 // superview, repatriate them. | 357 // superview, repatriate them. |
| 389 if (base::mac::ObjCCast<TransparentLinkButton>(_linkButtons[0]).superview != | 358 if (base::mac::ObjCCast<TransparentLinkButton>(_linkButtons[0]).superview != |
| 390 [_label superview]) { | 359 [_label superview]) { |
| 391 for (TransparentLinkButton* button in _linkButtons.get()) { | 360 for (TransparentLinkButton* button in _linkButtons) { |
| 392 CGRect newFrame = | 361 CGRect newFrame = |
| 393 [[_label superview] convertRect:button.frame fromView:button]; | 362 [[_label superview] convertRect:button.frame fromView:button]; |
| 394 [[_label superview] insertSubview:button aboveSubview:_label]; | 363 [[_label superview] insertSubview:button aboveSubview:_label]; |
| 395 button.frame = newFrame; | 364 button.frame = newFrame; |
| 396 } | 365 } |
| 397 } | 366 } |
| 398 } | 367 } |
| 399 // If there are no buttons, make some and put them in the label's superview. | 368 // If there are no buttons, make some and put them in the label's superview. |
| 400 if (![_linkButtons count] && _layoutsForURLs) { | 369 if (![_linkButtons count] && _layoutsForURLs) { |
| 401 [_layoutsForURLs enumerateKeysAndObjectsUsingBlock:^( | 370 [_layoutsForURLs enumerateKeysAndObjectsUsingBlock:^( |
| (...skipping 17 matching lines...) Expand all Loading... |
| 419 } | 388 } |
| 420 }]; | 389 }]; |
| 421 } | 390 } |
| 422 } | 391 } |
| 423 | 392 |
| 424 #pragma mark - Tap Handlers | 393 #pragma mark - Tap Handlers |
| 425 | 394 |
| 426 - (void)linkButtonTapped:(id)sender { | 395 - (void)linkButtonTapped:(id)sender { |
| 427 TransparentLinkButton* button = | 396 TransparentLinkButton* button = |
| 428 base::mac::ObjCCast<TransparentLinkButton>(sender); | 397 base::mac::ObjCCast<TransparentLinkButton>(sender); |
| 429 _action.get()(button.URL); | 398 _action(button.URL); |
| 430 } | 399 } |
| 431 | 400 |
| 432 #pragma mark - Test facilitators | 401 #pragma mark - Test facilitators |
| 433 | 402 |
| 434 - (NSArray*)tapRectsForURL:(GURL)url { | 403 - (NSArray*)tapRectsForURL:(GURL)url { |
| 435 NSURL* key = net::NSURLWithGURL(url); | 404 NSURL* key = net::NSURLWithGURL(url); |
| 436 LinkLayout* layout = [_layoutsForURLs objectForKey:key]; | 405 LinkLayout* layout = [_layoutsForURLs objectForKey:key]; |
| 437 return layout.frames; | 406 return layout.frames; |
| 438 } | 407 } |
| 439 | 408 |
| 440 - (void)tapLabelAtPoint:(CGPoint)point { | 409 - (void)tapLabelAtPoint:(CGPoint)point { |
| 441 [_layoutsForURLs enumerateKeysAndObjectsUsingBlock:^( | 410 [_layoutsForURLs enumerateKeysAndObjectsUsingBlock:^( |
| 442 NSURL* key, LinkLayout* layout, BOOL* stop) { | 411 NSURL* key, LinkLayout* layout, BOOL* stop) { |
| 443 for (NSValue* frameValue in layout.frames) { | 412 for (NSValue* frameValue in layout.frames) { |
| 444 CGRect frame = [frameValue CGRectValue]; | 413 CGRect frame = [frameValue CGRectValue]; |
| 445 if (CGRectContainsPoint(frame, point)) { | 414 if (CGRectContainsPoint(frame, point)) { |
| 446 _action.get()(net::GURLWithNSURL(key)); | 415 _action(net::GURLWithNSURL(key)); |
| 447 *stop = YES; | 416 *stop = YES; |
| 448 break; | 417 break; |
| 449 } | 418 } |
| 450 } | 419 } |
| 451 }]; | 420 }]; |
| 452 } | 421 } |
| 453 | 422 |
| 454 @end | 423 @end |
| OLD | NEW |