OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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 "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h" | 5 #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cmath> | 8 #include <cmath> |
9 | 9 |
10 #include "base/i18n/rtl.h" | 10 #include "base/i18n/rtl.h" |
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
148 value:DimTextColor() | 148 value:DimTextColor() |
149 range:range]; | 149 range:range]; |
150 } | 150 } |
151 } | 151 } |
152 | 152 |
153 return as; | 153 return as; |
154 } | 154 } |
155 | 155 |
156 } // namespace | 156 } // namespace |
157 | 157 |
158 @implementation OmniboxPopupCell | 158 @implementation OmniboxPopupCellData |
159 | 159 |
160 - (id)init { | 160 - (id)initWithMatch:(const AutocompleteMatch&)match { |
161 self = [super init]; | 161 self = [super init]; |
162 if (self) { | 162 if (!self) |
163 [self setImagePosition:NSImageLeft]; | 163 return self; |
164 [self setBordered:NO]; | |
165 [self setButtonType:NSRadioButton]; | |
166 | 164 |
167 // Without this highlighting messes up white areas of images. | 165 const base::string16& raw_separator = |
168 [self setHighlightsBy:NSNoCellMask]; | 166 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); |
167 separator_.reset( | |
168 [CreateAttributedString(raw_separator, DimTextColor()) retain]); | |
169 | 169 |
170 const base::string16& raw_separator = | 170 isContentsRTL_ = |
171 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); | 171 (base::i18n::RIGHT_TO_LEFT == |
172 separator_.reset( | 172 base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
173 [CreateAttributedString(raw_separator, DimTextColor()) retain]); | 173 matchType_ = match.type; |
174 | |
175 // Prefix may not have any characters with strong directionality, and may take | |
176 // the UI directionality. But prefix needs to appear in continuation of the | |
177 // contents so we force the directionality. | |
178 NSTextAlignment textAlignment = | |
179 isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment; | |
180 prefix_.reset( | |
181 [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo( | |
182 kACMatchPropertyContentsPrefix)), | |
183 ContentTextColor(), textAlignment) retain]); | |
184 | |
185 contents_.reset([CreateClassifiedAttributedString( | |
186 match.contents, ContentTextColor(), match.contents_class) retain]); | |
187 | |
188 if (match.answer) { | |
189 base::string16 answerString; | |
190 DCHECK(!match.answer->second_line().text_fields().empty()); | |
191 for (const SuggestionAnswer::TextField& textField : | |
192 match.answer->second_line().text_fields()) | |
193 answerString += textField.text(); | |
194 const base::char16 space(' '); | |
195 const SuggestionAnswer::TextField* textField = | |
196 match.answer->second_line().additional_text(); | |
197 if (textField) | |
198 answerString += space + textField->text(); | |
199 textField = match.answer->second_line().status_text(); | |
200 if (textField) | |
201 answerString += space + textField->text(); | |
202 description_.reset([CreateClassifiedAttributedString( | |
203 answerString, DimTextColor(), match.description_class) retain]); | |
204 } else if (match.description.empty()) { | |
205 description_.reset(); | |
206 } else { | |
207 description_.reset([CreateClassifiedAttributedString( | |
208 match.description, DimTextColor(), match.description_class) retain]); | |
174 } | 209 } |
175 return self; | 210 return self; |
176 } | 211 } |
177 | 212 |
178 - (void)setMatch:(const AutocompleteMatch&)match { | 213 - (void)setContents:(NSAttributedString*)contents { |
179 match_ = match; | 214 contents_.reset([contents retain]); |
180 NSAttributedString *contents = CreateClassifiedAttributedString( | 215 } |
181 match_.contents, ContentTextColor(), match_.contents_class); | |
182 [self setAttributedTitle:contents]; | |
183 | 216 |
184 if (match_.answer) { | 217 - (void)setImage:(NSImage*)image { |
185 base::string16 answerString; | 218 image_.reset([image retain]); |
186 DCHECK(!match_.answer->second_line().text_fields().empty()); | 219 } |
187 for (const SuggestionAnswer::TextField& textField : | 220 |
188 match_.answer->second_line().text_fields()) | 221 - (void)setRect:(NSRect)rect { |
189 answerString += textField.text(); | 222 rect_ = rect; |
190 const base::char16 space(' '); | 223 } |
191 const SuggestionAnswer::TextField* textField = | 224 |
192 match_.answer->second_line().additional_text(); | 225 - (NSRect)rect { |
193 if (textField) | 226 return rect_; |
194 answerString += space + textField->text(); | |
195 textField = match_.answer->second_line().status_text(); | |
196 if (textField) | |
197 answerString += space + textField->text(); | |
198 description_.reset([CreateClassifiedAttributedString( | |
199 answerString, DimTextColor(), match_.description_class) retain]); | |
200 } else if (match_.description.empty()) { | |
201 description_.reset(); | |
202 } else { | |
203 description_.reset([CreateClassifiedAttributedString( | |
204 match_.description, DimTextColor(), match_.description_class) retain]); | |
205 } | |
206 } | 227 } |
207 | 228 |
208 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { | 229 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { |
209 maxMatchContentsWidth_ = maxMatchContentsWidth; | 230 maxMatchContentsWidth_ = maxMatchContentsWidth; |
210 } | 231 } |
211 | 232 |
212 - (void)setContentsOffset:(CGFloat)contentsOffset { | 233 - (void)setContentsOffset:(CGFloat)contentsOffset { |
213 contentsOffset_ = contentsOffset; | 234 contentsOffset_ = contentsOffset; |
214 } | 235 } |
215 | 236 |
216 // The default NSButtonCell drawing leaves the image flush left and | 237 - (CGFloat)getMatchContentsWidth { |
217 // the title next to the image. This spaces things out to line up | 238 return contents_ ? [contents_ size].width : 0; |
218 // with the star button and autocomplete field. | 239 } |
219 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { | 240 |
220 if ([self state] == NSOnState || [self isHighlighted]) { | 241 - (id)copyWithZone:(NSZone*)zone { |
221 if ([self state] == NSOnState) | 242 // Note: Before changing this code, please check with groby, shess, or |
222 [SelectedBackgroundColor() set]; | 243 // dschuyler about why it's written with swaps and local data. |
223 else | 244 base::scoped_nsobject<NSAttributedString> contents; |
224 [HoveredBackgroundColor() set]; | 245 base::scoped_nsobject<NSAttributedString> separator; |
225 NSBezierPath* path = | 246 base::scoped_nsobject<NSAttributedString> description; |
226 [NSBezierPath bezierPathWithRoundedRect:cellFrame | 247 base::scoped_nsobject<NSAttributedString> prefix; |
227 xRadius:kCellRoundingRadius | 248 base::scoped_nsobject<NSImage> image; |
228 yRadius:kCellRoundingRadius]; | 249 contents_.swap(contents); |
229 [path fill]; | 250 separator_.swap(separator); |
230 } | 251 description_.swap(description); |
252 prefix_.swap(prefix); | |
253 image_.swap(image); | |
254 | |
255 OmniboxPopupCellData* copy = [[OmniboxPopupCellData alloc] init]; | |
256 copy->contents_.reset(contents); | |
257 copy->separator_.reset(separator); | |
258 copy->description_.reset(description); | |
259 copy->prefix_.reset(prefix); | |
260 copy->image_.reset(image); | |
261 copy->maxMatchContentsWidth_ = maxMatchContentsWidth_; | |
262 copy->contentsOffset_ = contentsOffset_; | |
263 copy->isContentsRTL_ = isContentsRTL_; | |
264 copy->matchType_ = matchType_; | |
265 copy->rect_ = rect_; | |
266 | |
267 contents_.swap(contents); | |
268 separator_.swap(separator); | |
269 description_.swap(description); | |
270 prefix_.swap(prefix); | |
271 image_.swap(image); | |
272 return copy; | |
273 } | |
274 | |
275 - (void)drawMatchWithFrame:(NSRect)cellFrame | |
276 inCell:(OmniboxPopupCell*)cell | |
277 inView:(NSView*)controlView { | |
278 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | |
279 CGFloat contentsWidth = [self getMatchContentsWidth]; | |
280 CGFloat separatorWidth = [separator_ size].width; | |
281 CGFloat descriptionWidth = description_ ? [description_ size].width : 0; | |
282 int contentsMaxWidth, descriptionMaxWidth; | |
283 OmniboxPopupModel::ComputeMatchMaxWidths( | |
284 ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth), | |
285 ceilf(remainingWidth), !AutocompleteMatch::IsSearchType(matchType_), | |
286 &contentsMaxWidth, &descriptionMaxWidth); | |
231 | 287 |
232 // Put the image centered vertically but in a fixed column. | 288 // Put the image centered vertically but in a fixed column. |
233 NSImage* image = [self image]; | 289 NSImage* image = image_; |
shrike
2015/05/14 18:02:39
Why do you have to create a local variable instead
dschuyler
2015/05/14 22:28:20
It was done that was previously. I'm not sure why
Scott Hess - ex-Googler
2015/05/15 00:09:38
I suspect [self image] was using the cell's image
| |
234 if (image) { | 290 if (image) { |
235 NSRect imageRect = cellFrame; | 291 NSRect imageRect = cellFrame; |
236 imageRect.size = [image size]; | 292 imageRect.size = [image size]; |
237 imageRect.origin.y += | 293 imageRect.origin.y += |
238 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); | 294 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); |
239 imageRect.origin.x += kImageXOffset; | 295 imageRect.origin.x += kImageXOffset; |
240 [image drawInRect:FlipIfRTL(imageRect, cellFrame) | 296 [image drawInRect:FlipIfRTL(imageRect, cellFrame) |
241 fromRect:NSZeroRect // Entire image | 297 fromRect:NSZeroRect |
242 operation:NSCompositeSourceOver | 298 operation:NSCompositeSourceOver |
243 fraction:1.0 | 299 fraction:1.0 |
244 respectFlipped:YES | 300 respectFlipped:YES |
245 hints:nil]; | 301 hints:nil]; |
246 } | 302 } |
247 | 303 |
248 [self drawMatchWithFrame:cellFrame inView:controlView]; | |
249 } | |
250 | |
251 - (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
252 NSAttributedString* contents = [self attributedTitle]; | |
253 | |
254 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | |
255 CGFloat contentsWidth = [self getMatchContentsWidth]; | |
256 CGFloat separatorWidth = [separator_ size].width; | |
257 CGFloat descriptionWidth = description_.get() ? [description_ size].width : 0; | |
258 int contentsMaxWidth, descriptionMaxWidth; | |
259 OmniboxPopupModel::ComputeMatchMaxWidths( | |
260 ceilf(contentsWidth), | |
261 ceilf(separatorWidth), | |
262 ceilf(descriptionWidth), | |
263 ceilf(remainingWidth), | |
264 !AutocompleteMatch::IsSearchType(match_.type), | |
265 &contentsMaxWidth, | |
266 &descriptionMaxWidth); | |
267 | |
268 CGFloat offset = kTextStartOffset; | 304 CGFloat offset = kTextStartOffset; |
269 if (match_.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { | 305 if (matchType_ == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { |
270 // Infinite suggestions are rendered with a prefix (usually ellipsis), which | 306 // Infinite suggestions are rendered with a prefix (usually ellipsis), which |
271 // appear vertically stacked. | 307 // appear vertically stacked. |
272 offset += [self drawMatchPrefixWithFrame:cellFrame | 308 offset += [self drawMatchPrefixWithFrame:cellFrame |
309 inCell:cell | |
273 inView:controlView | 310 inView:controlView |
274 withContentsMaxWidth:&contentsMaxWidth]; | 311 withContentsMaxWidth:&contentsMaxWidth]; |
275 } | 312 } |
276 offset += [self drawMatchPart:contents | 313 offset += [cell drawMatchPart:contents_ |
277 withFrame:cellFrame | 314 withFrame:cellFrame |
278 atOffset:offset | 315 atOffset:offset |
279 withMaxWidth:contentsMaxWidth | 316 withMaxWidth:contentsMaxWidth |
280 inView:controlView]; | 317 inView:controlView]; |
281 | 318 |
282 if (descriptionMaxWidth != 0) { | 319 if (descriptionMaxWidth != 0) { |
283 offset += [self drawMatchPart:separator_ | 320 offset += [cell drawMatchPart:separator_ |
284 withFrame:cellFrame | 321 withFrame:cellFrame |
285 atOffset:offset | 322 atOffset:offset |
286 withMaxWidth:separatorWidth | 323 withMaxWidth:separatorWidth |
287 inView:controlView]; | 324 inView:controlView]; |
288 offset += [self drawMatchPart:description_ | 325 offset += [cell drawMatchPart:description_ |
289 withFrame:cellFrame | 326 withFrame:cellFrame |
290 atOffset:offset | 327 atOffset:offset |
291 withMaxWidth:descriptionMaxWidth | 328 withMaxWidth:descriptionMaxWidth |
292 inView:controlView]; | 329 inView:controlView]; |
293 } | 330 } |
294 } | 331 } |
295 | 332 |
296 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame | 333 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame |
334 inCell:(OmniboxPopupCell*)cell | |
297 inView:(NSView*)controlView | 335 inView:(NSView*)controlView |
298 withContentsMaxWidth:(int*)contentsMaxWidth { | 336 withContentsMaxWidth:(int*)contentsMaxWidth { |
299 CGFloat offset = 0.0f; | 337 CGFloat offset = 0.0f; |
300 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | 338 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
301 bool isRTL = base::i18n::IsRTL(); | 339 bool isRTL = base::i18n::IsRTL(); |
302 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | |
303 base::i18n::GetFirstStrongCharacterDirection(match_.contents)); | |
304 // Prefix may not have any characters with strong directionality, and may take | |
305 // the UI directionality. But prefix needs to appear in continuation of the | |
306 // contents so we force the directionality. | |
307 NSTextAlignment textAlignment = isContentsRTL ? | |
308 NSRightTextAlignment : NSLeftTextAlignment; | |
309 prefix_.reset([CreateAttributedString(base::UTF8ToUTF16( | |
310 match_.GetAdditionalInfo(kACMatchPropertyContentsPrefix)), | |
311 ContentTextColor(), textAlignment) retain]); | |
312 CGFloat prefixWidth = [prefix_ size].width; | 340 CGFloat prefixWidth = [prefix_ size].width; |
313 | 341 |
314 CGFloat prefixOffset = 0.0f; | 342 CGFloat prefixOffset = 0.0f; |
315 if (isRTL != isContentsRTL) { | 343 if (isRTL != isContentsRTL_) { |
316 // The contents is rendered between the contents offset extending towards | 344 // The contents is rendered between the contents offset extending towards |
317 // the start edge, while prefix is rendered in opposite direction. Ideally | 345 // the start edge, while prefix is rendered in opposite direction. Ideally |
318 // the prefix should be rendered at |contentsOffset_|. If that is not | 346 // the prefix should be rendered at |contentsOffset_|. If that is not |
319 // sufficient to render the widest suggestion, we increase it to | 347 // sufficient to render the widest suggestion, we increase it to |
320 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to | 348 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to |
321 // accommodate that, we reduce the offset so that the prefix gets rendered. | 349 // accommodate that, we reduce the offset so that the prefix gets rendered. |
322 prefixOffset = std::min( | 350 prefixOffset = std::min( |
323 remainingWidth - prefixWidth, std::max(contentsOffset_, | 351 remainingWidth - prefixWidth, std::max(contentsOffset_, |
324 maxMatchContentsWidth_)); | 352 maxMatchContentsWidth_)); |
325 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); | 353 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); |
326 } else { // The direction of contents is same as UI direction. | 354 } else { // The direction of contents is same as UI direction. |
327 // Ideally the offset should be |contentsOffset_|. If the max total width | 355 // Ideally the offset should be |contentsOffset_|. If the max total width |
328 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the | 356 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the |
329 // |remainingWidth|, then we shift the offset to the left , so that all | 357 // |remainingWidth|, then we shift the offset to the left , so that all |
330 // postfix suggestions are visible. | 358 // postfix suggestions are visible. |
331 // We have to render the prefix, so offset has to be at least |prefixWidth|. | 359 // We have to render the prefix, so offset has to be at least |prefixWidth|. |
332 offset = std::max(prefixWidth, | 360 offset = std::max( |
361 prefixWidth, | |
333 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); | 362 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); |
334 prefixOffset = offset - prefixWidth; | 363 prefixOffset = offset - prefixWidth; |
335 } | 364 } |
336 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), | 365 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), |
337 *contentsMaxWidth); | 366 *contentsMaxWidth); |
338 [self drawMatchPart:prefix_ | 367 [cell drawMatchPart:prefix_ |
339 withFrame:cellFrame | 368 withFrame:cellFrame |
340 atOffset:prefixOffset + kTextStartOffset | 369 atOffset:prefixOffset + kTextStartOffset |
341 withMaxWidth:prefixWidth | 370 withMaxWidth:prefixWidth |
342 inView:controlView]; | 371 inView:controlView]; |
343 return offset; | 372 return offset; |
344 } | 373 } |
345 | 374 |
346 - (CGFloat)drawMatchPart:(NSAttributedString*)as | 375 @end |
376 | |
377 @implementation OmniboxPopupCell | |
378 | |
379 - (void)setCellData:(OmniboxPopupCellData*)cellData { | |
380 cellData_.reset([cellData retain]); | |
381 } | |
382 | |
383 - (id)copyWithZone:(NSZone*)zone { | |
384 // Note: Before changing this code, please check with groby, shess, or | |
385 // dschuyler about why it's written with swaps and local data. | |
386 base::scoped_nsobject<OmniboxPopupCellData> cellData(nil); | |
387 cellData_.swap(cellData); | |
388 OmniboxPopupCell* copy = [super copyWithZone:zone]; | |
389 copy->cellData_ = cellData; | |
390 cellData_.swap(cellData); | |
391 return copy; | |
392 } | |
393 | |
394 // The default NSButtonCell drawing leaves the image flush left and | |
395 // the title next to the image. This spaces things out to line up | |
396 // with the star button and autocomplete field. | |
397 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
398 if ([self state] == NSOnState || [self isHighlighted]) { | |
399 if ([self state] == NSOnState) | |
400 [SelectedBackgroundColor() set]; | |
401 else | |
402 [HoveredBackgroundColor() set]; | |
403 NSBezierPath* path = | |
404 [NSBezierPath bezierPathWithRoundedRect:cellFrame | |
405 xRadius:kCellRoundingRadius | |
406 yRadius:kCellRoundingRadius]; | |
407 [path fill]; | |
408 } | |
409 | |
410 [cellData_ drawMatchWithFrame:cellFrame inCell:self inView:controlView]; | |
411 } | |
412 | |
413 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString | |
347 withFrame:(NSRect)cellFrame | 414 withFrame:(NSRect)cellFrame |
348 atOffset:(CGFloat)offset | 415 atOffset:(CGFloat)offset |
349 withMaxWidth:(int)maxWidth | 416 withMaxWidth:(int)maxWidth |
350 inView:(NSView*)controlView { | 417 inView:(NSView*)controlView { |
351 if (offset > NSWidth(cellFrame)) | 418 if (offset > NSWidth(cellFrame)) |
352 return 0.0f; | 419 return 0.0f; |
353 NSRect renderRect = ShiftRect(cellFrame, offset); | 420 NSRect renderRect = ShiftRect(cellFrame, offset); |
354 renderRect.size.width = | 421 renderRect.size.width = |
355 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); | 422 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); |
356 if (renderRect.size.width != 0) { | 423 NSRect textRect = |
357 [self drawTitle:as | 424 [attributedString boundingRectWithSize:renderRect.size options:nil]; |
358 withFrame:FlipIfRTL(renderRect, cellFrame) | 425 renderRect.origin.y += |
359 inView:controlView]; | 426 std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0); |
360 } | 427 if (renderRect.size.width != 0) |
428 [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)]; | |
361 return NSWidth(renderRect); | 429 return NSWidth(renderRect); |
362 } | 430 } |
363 | 431 |
364 - (CGFloat)getMatchContentsWidth { | |
365 NSAttributedString* contents = [self attributedTitle]; | |
366 return contents ? [contents size].width : 0; | |
367 } | |
368 | |
369 | |
370 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { | 432 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { |
371 const base::string16& inputText = base::UTF8ToUTF16( | 433 const base::string16& inputText = base::UTF8ToUTF16( |
372 match.GetAdditionalInfo(kACMatchPropertyInputText)); | 434 match.GetAdditionalInfo(kACMatchPropertyInputText)); |
373 int contentsStartIndex = 0; | 435 int contentsStartIndex = 0; |
374 base::StringToInt( | 436 base::StringToInt( |
375 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), | 437 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), |
376 &contentsStartIndex); | 438 &contentsStartIndex); |
377 // Ignore invalid state. | 439 // Ignore invalid state. |
378 if (!StartsWith(match.fill_into_edit, inputText, true) | 440 if (!StartsWith(match.fill_into_edit, inputText, true) |
379 || !EndsWith(match.fill_into_edit, match.contents, true) | 441 || !EndsWith(match.fill_into_edit, match.contents, true) |
380 || ((size_t)contentsStartIndex >= inputText.length())) { | 442 || ((size_t)contentsStartIndex >= inputText.length())) { |
381 return 0; | 443 return 0; |
382 } | 444 } |
383 bool isRTL = base::i18n::IsRTL(); | 445 bool isRTL = base::i18n::IsRTL(); |
384 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | 446 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == |
385 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | 447 base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
386 | 448 |
387 // Color does not matter. | 449 // Color does not matter. |
388 NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); | 450 NSAttributedString* attributedString = |
389 base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] | 451 CreateAttributedString(inputText, DimTextColor()); |
390 initWithAttributedString:as]); | 452 base::scoped_nsobject<NSTextStorage> textStorage( |
453 [[NSTextStorage alloc] initWithAttributedString:attributedString]); | |
391 base::scoped_nsobject<NSLayoutManager> layoutManager( | 454 base::scoped_nsobject<NSLayoutManager> layoutManager( |
392 [[NSLayoutManager alloc] init]); | 455 [[NSLayoutManager alloc] init]); |
393 base::scoped_nsobject<NSTextContainer> textContainer( | 456 base::scoped_nsobject<NSTextContainer> textContainer( |
394 [[NSTextContainer alloc] init]); | 457 [[NSTextContainer alloc] init]); |
395 [layoutManager addTextContainer:textContainer]; | 458 [layoutManager addTextContainer:textContainer]; |
396 [textStorage addLayoutManager:layoutManager]; | 459 [textStorage addLayoutManager:layoutManager]; |
397 | 460 |
398 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); | 461 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); |
399 NSUInteger glyphIndex = | 462 NSUInteger glyphIndex = |
400 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; | 463 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; |
401 | 464 |
402 // This offset is computed from the left edge of the glyph always from the | 465 // This offset is computed from the left edge of the glyph always from the |
403 // left edge of the string, irrespective of the directionality of UI or text. | 466 // left edge of the string, irrespective of the directionality of UI or text. |
404 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 467 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
405 | 468 |
406 CGFloat inputWidth = [as size].width; | 469 CGFloat inputWidth = [attributedString size].width; |
407 | 470 |
408 // The offset obtained above may need to be corrected because the left-most | 471 // The offset obtained above may need to be corrected because the left-most |
409 // glyph may not have 0 offset. So we find the offset of left-most glyph, and | 472 // glyph may not have 0 offset. So we find the offset of left-most glyph, and |
410 // subtract it from the offset of the glyph we obtained above. | 473 // subtract it from the offset of the glyph we obtained above. |
411 CGFloat minOffset = glyphOffset; | 474 CGFloat minOffset = glyphOffset; |
412 | 475 |
413 // If content is RTL, we are interested in the right-edge of the glyph. | 476 // If content is RTL, we are interested in the right-edge of the glyph. |
414 // Unfortunately the bounding rect computation methods from NSLayoutManager or | 477 // Unfortunately the bounding rect computation methods from NSLayoutManager or |
415 // NSFont don't work correctly with bidirectional text. So we compute the | 478 // NSFont don't work correctly with bidirectional text. So we compute the |
416 // glyph width by finding the closest glyph offset to the right of the glyph | 479 // glyph width by finding the closest glyph offset to the right of the glyph |
417 // we are looking for. | 480 // we are looking for. |
418 CGFloat glyphWidth = inputWidth; | 481 CGFloat glyphWidth = inputWidth; |
419 | 482 |
420 for (NSUInteger i = 0; i < [as length]; i++) { | 483 for (NSUInteger i = 0; i < [attributedString length]; i++) { |
421 if (i == charIndex) continue; | 484 if (i == charIndex) continue; |
422 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; | 485 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; |
423 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 486 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
424 minOffset = std::min(minOffset, offset); | 487 minOffset = std::min(minOffset, offset); |
425 if (offset > glyphOffset) | 488 if (offset > glyphOffset) |
426 glyphWidth = std::min(glyphWidth, offset - glyphOffset); | 489 glyphWidth = std::min(glyphWidth, offset - glyphOffset); |
427 } | 490 } |
428 glyphOffset -= minOffset; | 491 glyphOffset -= minOffset; |
429 if (glyphWidth == 0) | 492 if (glyphWidth == 0) |
430 glyphWidth = inputWidth - glyphOffset; | 493 glyphWidth = inputWidth - glyphOffset; |
431 if (isContentsRTL) | 494 if (isContentsRTL) |
432 glyphOffset += glyphWidth; | 495 glyphOffset += glyphWidth; |
433 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; | 496 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; |
434 } | 497 } |
435 | 498 |
436 @end | 499 @end |
OLD | NEW |