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" |
11 #include "base/mac/scoped_nsobject.h" | 11 #include "base/mac/scoped_nsobject.h" |
12 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" |
13 #include "base/strings/string_util.h" | 13 #include "base/strings/string_util.h" |
14 #include "base/strings/sys_string_conversions.h" | 14 #include "base/strings/sys_string_conversions.h" |
15 #include "base/strings/utf_string_conversions.h" | 15 #include "base/strings/utf_string_conversions.h" |
16 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h" | 16 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h" |
17 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h" | 17 #include "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h" |
18 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" | 18 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" |
19 #include "chrome/grit/generated_resources.h" | 19 #include "chrome/grit/generated_resources.h" |
20 #include "components/omnibox/suggestion_answer.h" | 20 #include "components/omnibox/suggestion_answer.h" |
21 #include "skia/ext/skia_utils_mac.h" | 21 #include "skia/ext/skia_utils_mac.h" |
22 #include "ui/base/l10n/l10n_util.h" | 22 #include "ui/base/l10n/l10n_util.h" |
23 #include "ui/gfx/font.h" | 23 #include "ui/gfx/font.h" |
24 | 24 |
25 namespace { | 25 namespace { |
26 | 26 |
27 // How much to adjust the cell sizing up from the default determined | |
28 // by the font. | |
29 const CGFloat kCellHeightAdjust = 6.0; | |
30 | |
31 // How large the icon should be when displayed. | |
32 const CGFloat kImageSize = 19.0; | |
33 | |
27 // How far to offset image column from the left. | 34 // How far to offset image column from the left. |
28 const CGFloat kImageXOffset = 5.0; | 35 const CGFloat kImageXOffset = 5.0; |
29 | 36 |
30 // How far to offset the text column from the left. | 37 // How far to offset the text column from the left. |
31 const CGFloat kTextStartOffset = 28.0; | 38 const CGFloat kTextStartOffset = 28.0; |
32 | 39 |
33 // Rounding radius of selection and hover background on popup items. | 40 // Rounding radius of selection and hover background on popup items. |
34 const CGFloat kCellRoundingRadius = 2.0; | 41 const CGFloat kCellRoundingRadius = 2.0; |
35 | 42 |
36 // Flips the given |rect| in context of the given |frame|. | 43 // Flips the given |rect| in context of the given |frame|. |
(...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
272 value:DimTextColor() | 279 value:DimTextColor() |
273 range:range]; | 280 range:range]; |
274 } | 281 } |
275 } | 282 } |
276 | 283 |
277 return attributedString; | 284 return attributedString; |
278 } | 285 } |
279 | 286 |
280 } // namespace | 287 } // namespace |
281 | 288 |
282 @implementation OmniboxPopupCell | 289 @interface OmniboxPopupCell () |
290 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString | |
291 withFrame:(NSRect)cellFrame | |
292 atOffset:(CGFloat)offset | |
293 withMaxWidth:(int)maxWidth | |
294 inView:(NSView*)controlView; | |
295 @end | |
283 | 296 |
284 - (id)init { | 297 @interface OmniboxPopupCellData () |
285 self = [super init]; | 298 - (NSAttributedString*)contents; |
286 if (self) { | 299 - (NSAttributedString*)description; |
287 [self setImagePosition:NSImageLeft]; | 300 - (NSAttributedString*)prefix; |
288 [self setBordered:NO]; | 301 - (NSImage*)image; |
289 [self setButtonType:NSRadioButton]; | 302 - (NSImage*)answerImage; |
303 - (CGFloat)maxMatchContentsWidth; | |
304 - (CGFloat)contentsOffset; | |
305 - (bool)isContentsRTL; | |
306 - (AutocompleteMatch::Type)matchType; | |
307 @end | |
290 | 308 |
291 // Without this highlighting messes up white areas of images. | 309 @implementation OmniboxPopupCellData |
292 [self setHighlightsBy:NSNoCellMask]; | |
293 | 310 |
294 const base::string16& raw_separator = | 311 - (instancetype)initWithMatch:(const AutocompleteMatch&)match |
295 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); | 312 image:(NSImage*)image { |
296 separator_.reset( | 313 if ((self = [super init])) { |
297 [CreateAttributedString(raw_separator, DimTextColor()) retain]); | 314 image_.reset([image retain]); |
315 | |
316 isContentsRTL_ = | |
317 (base::i18n::RIGHT_TO_LEFT == | |
318 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | |
319 matchType_ = match.type; | |
320 | |
321 // Prefix may not have any characters with strong directionality, and may | |
322 // take the UI directionality. But prefix needs to appear in continuation | |
323 // of the contents so we force the directionality. | |
324 NSTextAlignment textAlignment = | |
325 isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment; | |
326 prefix_.reset( | |
327 [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo( | |
328 kACMatchPropertyContentsPrefix)), | |
329 ContentTextColor(), textAlignment) retain]); | |
330 | |
331 contents_.reset([CreateClassifiedAttributedString( | |
332 match.contents, ContentTextColor(), match.contents_class) retain]); | |
333 | |
334 if (match.answer) { | |
335 base::scoped_nsobject<NSMutableAttributedString> answerString( | |
336 [[NSMutableAttributedString alloc] init]); | |
337 DCHECK(!match.answer->second_line().text_fields().empty()); | |
338 for (const SuggestionAnswer::TextField& textField : | |
339 match.answer->second_line().text_fields()) { | |
340 [answerString | |
341 appendAttributedString:CreateAnswerString(textField.text(), | |
342 textField.type())]; | |
343 } | |
344 const base::string16 space(base::ASCIIToUTF16(" ")); | |
345 // const base::char16 space(' '); | |
346 const SuggestionAnswer::TextField* textField = | |
347 match.answer->second_line().additional_text(); | |
348 if (textField) { | |
349 [answerString | |
350 appendAttributedString:CreateAnswerString(space + textField->text(), | |
351 textField->type())]; | |
352 } | |
353 textField = match.answer->second_line().status_text(); | |
354 if (textField) { | |
355 [answerString | |
356 appendAttributedString:CreateAnswerString(space + textField->text(), | |
357 textField->type())]; | |
358 } | |
359 description_.reset(answerString.release()); | |
360 } else if (match.description.empty()) { | |
361 description_.reset(); | |
362 } else { | |
363 description_.reset([CreateClassifiedAttributedString( | |
364 match.description, DimTextColor(), match.description_class) retain]); | |
365 } | |
298 } | 366 } |
299 return self; | 367 return self; |
300 } | 368 } |
301 | 369 |
370 - (instancetype)copyWithZone:(NSZone*)zone { | |
371 OmniboxPopupCellData* copy = [[OmniboxPopupCellData alloc] init]; | |
groby-ooo-7-16
2015/06/11 01:22:17
Since we seem to retain all the members - iow, thi
Scott Hess - ex-Googler
2015/06/11 21:52:02
Yeah, as long as it's returning a shallow copy, sh
dschuyler
2015/06/11 22:34:22
That seems to work nicely.
Done.
dschuyler
2015/06/11 22:34:22
Done.
| |
372 copy->contents_.reset([contents_ retain]); | |
373 copy->description_.reset([description_ retain]); | |
374 copy->prefix_.reset([prefix_ retain]); | |
375 copy->image_.reset([image_ retain]); | |
376 copy->answerImage_.reset([answerImage_ retain]); | |
377 copy->maxMatchContentsWidth_ = maxMatchContentsWidth_; | |
378 copy->contentsOffset_ = contentsOffset_; | |
379 copy->isContentsRTL_ = isContentsRTL_; | |
380 copy->matchType_ = matchType_; | |
381 return copy; | |
382 } | |
383 | |
384 - (void)setContents:(NSAttributedString*)contents { | |
385 contents_.reset([contents retain]); | |
386 } | |
387 | |
388 - (void)setImage:(NSImage*)image { | |
389 image_.reset([image retain]); | |
390 } | |
391 | |
302 - (void)setAnswerImage:(NSImage*)image { | 392 - (void)setAnswerImage:(NSImage*)image { |
303 answerImage_.reset([image retain]); | 393 answerImage_.reset([image retain]); |
304 } | 394 } |
305 | 395 |
306 - (void)setMatch:(const AutocompleteMatch&)match { | 396 - (NSAttributedString*)contents { |
307 match_ = match; | 397 return contents_; |
308 NSAttributedString *contents = CreateClassifiedAttributedString( | |
309 match_.contents, ContentTextColor(), match_.contents_class); | |
310 [self setAttributedTitle:contents]; | |
311 [self setAnswerImage:nil]; | |
312 if (match_.answer) { | |
313 base::scoped_nsobject<NSMutableAttributedString> answerString( | |
314 [[NSMutableAttributedString alloc] init]); | |
315 DCHECK(!match_.answer->second_line().text_fields().empty()); | |
316 for (const SuggestionAnswer::TextField& textField : | |
317 match_.answer->second_line().text_fields()) { | |
318 NSAttributedString* as = | |
319 CreateAnswerString(textField.text(), textField.type()); | |
320 [answerString appendAttributedString:as]; | |
321 } | |
322 const base::char16 space(' '); | |
323 const SuggestionAnswer::TextField* textField = | |
324 match_.answer->second_line().additional_text(); | |
325 if (textField) { | |
326 [answerString | |
327 appendAttributedString:CreateAnswerString(space + textField->text(), | |
328 textField->type())]; | |
329 } | |
330 textField = match_.answer->second_line().status_text(); | |
331 if (textField) { | |
332 [answerString | |
333 appendAttributedString:CreateAnswerString(space + textField->text(), | |
334 textField->type())]; | |
335 } | |
336 description_.reset(answerString.release()); | |
337 } else if (match_.description.empty()) { | |
338 description_.reset(); | |
339 } else { | |
340 description_.reset([CreateClassifiedAttributedString( | |
341 match_.description, DimTextColor(), match_.description_class) retain]); | |
342 } | |
343 } | 398 } |
344 | 399 |
345 - (NSAttributedString*)description { | 400 - (NSAttributedString*)description { |
346 return description_; | 401 return description_; |
347 } | 402 } |
348 | 403 |
404 - (NSAttributedString*)prefix { | |
405 return prefix_; | |
406 } | |
407 | |
408 - (NSImage*)image { | |
409 return image_; | |
410 } | |
411 | |
412 - (NSImage*)answerImage { | |
413 return answerImage_; | |
414 } | |
415 | |
416 - (CGFloat)maxMatchContentsWidth { | |
417 return maxMatchContentsWidth_; | |
418 } | |
419 | |
420 - (CGFloat)contentsOffset { | |
421 return contentsOffset_; | |
422 } | |
423 | |
424 - (bool)isContentsRTL { | |
425 return isContentsRTL_; | |
426 } | |
427 | |
428 - (AutocompleteMatch::Type)matchType { | |
429 return matchType_; | |
430 } | |
431 | |
349 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { | 432 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { |
350 maxMatchContentsWidth_ = maxMatchContentsWidth; | 433 maxMatchContentsWidth_ = maxMatchContentsWidth; |
351 } | 434 } |
352 | 435 |
353 - (void)setContentsOffset:(CGFloat)contentsOffset { | 436 - (void)setContentsOffset:(CGFloat)contentsOffset { |
354 contentsOffset_ = contentsOffset; | 437 contentsOffset_ = contentsOffset; |
355 } | 438 } |
356 | 439 |
357 // The default NSButtonCell drawing leaves the image flush left and | 440 - (CGFloat)getMatchContentsWidth { |
358 // the title next to the image. This spaces things out to line up | 441 return [contents_ size].width; |
359 // with the star button and autocomplete field. | 442 } |
360 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { | 443 |
444 - (CGFloat)rowHeight { | |
445 return kImageSize + kCellHeightAdjust; | |
446 } | |
447 | |
448 @end | |
449 | |
450 @implementation OmniboxPopupCell | |
451 | |
452 - (instancetype)init { | |
453 if ((self = [super init])) { | |
454 base::string16 raw_separator = | |
455 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); | |
456 separator_.reset( | |
457 [CreateAttributedString(raw_separator, DimTextColor()) retain]); | |
458 } | |
459 return self; | |
460 } | |
461 | |
462 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
361 if ([self state] == NSOnState || [self isHighlighted]) { | 463 if ([self state] == NSOnState || [self isHighlighted]) { |
362 if ([self state] == NSOnState) | 464 if ([self state] == NSOnState) |
363 [SelectedBackgroundColor() set]; | 465 [SelectedBackgroundColor() set]; |
364 else | 466 else |
365 [HoveredBackgroundColor() set]; | 467 [HoveredBackgroundColor() set]; |
366 NSBezierPath* path = | 468 NSBezierPath* path = |
367 [NSBezierPath bezierPathWithRoundedRect:cellFrame | 469 [NSBezierPath bezierPathWithRoundedRect:cellFrame |
368 xRadius:kCellRoundingRadius | 470 xRadius:kCellRoundingRadius |
369 yRadius:kCellRoundingRadius]; | 471 yRadius:kCellRoundingRadius]; |
370 [path fill]; | 472 [path fill]; |
371 } | 473 } |
372 | 474 |
475 [self drawMatchWithFrame:cellFrame | |
476 withCellData:[self objectValue] | |
477 inView:controlView]; | |
478 } | |
479 | |
480 - (void)drawMatchWithFrame:(NSRect)cellFrame | |
481 withCellData:(OmniboxPopupCellData*)cellData | |
482 inView:(NSView*)controlView { | |
483 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | |
484 CGFloat contentsWidth = [cellData getMatchContentsWidth]; | |
485 CGFloat separatorWidth = [separator_ size].width; | |
486 CGFloat descriptionWidth = | |
487 [cellData description] ? [[cellData description] size].width : 0; | |
488 int contentsMaxWidth, descriptionMaxWidth; | |
489 OmniboxPopupModel::ComputeMatchMaxWidths( | |
490 ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth), | |
491 ceilf(remainingWidth), | |
492 !AutocompleteMatch::IsSearchType([cellData matchType]), &contentsMaxWidth, | |
493 &descriptionMaxWidth); | |
494 | |
373 // Put the image centered vertically but in a fixed column. | 495 // Put the image centered vertically but in a fixed column. |
374 NSImage* image = [self image]; | 496 if ([cellData image]) { |
375 if (image) { | |
376 NSRect imageRect = cellFrame; | 497 NSRect imageRect = cellFrame; |
377 imageRect.size = [image size]; | 498 imageRect.size = [[cellData image] size]; |
378 imageRect.origin.y += | 499 imageRect.origin.y += |
379 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); | 500 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); |
380 imageRect.origin.x += kImageXOffset; | 501 imageRect.origin.x += kImageXOffset; |
381 [image drawInRect:FlipIfRTL(imageRect, cellFrame) | 502 [[cellData image] drawInRect:FlipIfRTL(imageRect, cellFrame) |
382 fromRect:NSZeroRect // Entire image | 503 fromRect:NSZeroRect |
383 operation:NSCompositeSourceOver | 504 operation:NSCompositeSourceOver |
384 fraction:1.0 | 505 fraction:1.0 |
385 respectFlipped:YES | 506 respectFlipped:YES |
386 hints:nil]; | 507 hints:nil]; |
387 } | 508 } |
388 | 509 |
389 [self drawMatchWithFrame:cellFrame inView:controlView]; | |
390 } | |
391 | |
392 - (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
393 NSAttributedString* contents = [self attributedTitle]; | |
394 | |
395 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | |
396 CGFloat contentsWidth = [self getMatchContentsWidth]; | |
397 CGFloat separatorWidth = [separator_ size].width; | |
398 CGFloat descriptionWidth = description_.get() ? [description_ size].width : 0; | |
399 int contentsMaxWidth, descriptionMaxWidth; | |
400 OmniboxPopupModel::ComputeMatchMaxWidths( | |
401 ceilf(contentsWidth), | |
402 ceilf(separatorWidth), | |
403 ceilf(descriptionWidth), | |
404 ceilf(remainingWidth), | |
405 !AutocompleteMatch::IsSearchType(match_.type), | |
406 &contentsMaxWidth, | |
407 &descriptionMaxWidth); | |
408 | |
409 CGFloat offset = kTextStartOffset; | 510 CGFloat offset = kTextStartOffset; |
410 if (match_.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { | 511 if ([cellData matchType] == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { |
411 // Infinite suggestions are rendered with a prefix (usually ellipsis), which | 512 // Infinite suggestions are rendered with a prefix (usually ellipsis), which |
412 // appear vertically stacked. | 513 // appear vertically stacked. |
413 offset += [self drawMatchPrefixWithFrame:cellFrame | 514 offset += [self drawMatchPrefixWithFrame:cellFrame |
515 withCellData:cellData | |
414 inView:controlView | 516 inView:controlView |
415 withContentsMaxWidth:&contentsMaxWidth]; | 517 withContentsMaxWidth:&contentsMaxWidth]; |
416 } | 518 } |
417 offset += [self drawMatchPart:contents | 519 offset += [self drawMatchPart:[cellData contents] |
418 withFrame:cellFrame | 520 withFrame:cellFrame |
419 atOffset:offset | 521 atOffset:offset |
420 withMaxWidth:contentsMaxWidth | 522 withMaxWidth:contentsMaxWidth |
421 inView:controlView]; | 523 inView:controlView]; |
422 | 524 |
423 if (descriptionMaxWidth != 0) { | 525 if (descriptionMaxWidth != 0) { |
424 offset += [self drawMatchPart:separator_ | 526 offset += [self drawMatchPart:separator_ |
425 withFrame:cellFrame | 527 withFrame:cellFrame |
426 atOffset:offset | 528 atOffset:offset |
427 withMaxWidth:separatorWidth | 529 withMaxWidth:separatorWidth |
428 inView:controlView]; | 530 inView:controlView]; |
429 if (answerImage_) { | 531 if ([cellData answerImage]) { |
430 NSRect imageRect = NSMakeRect(offset, NSMinY(cellFrame), | 532 NSRect imageRect = NSMakeRect(offset, NSMinY(cellFrame), |
431 NSHeight(cellFrame), NSHeight(cellFrame)); | 533 NSHeight(cellFrame), NSHeight(cellFrame)); |
432 [answerImage_ drawInRect:FlipIfRTL(imageRect, cellFrame) | 534 [[cellData answerImage] drawInRect:FlipIfRTL(imageRect, cellFrame) |
433 fromRect:NSZeroRect | 535 fromRect:NSZeroRect |
434 operation:NSCompositeSourceOver | 536 operation:NSCompositeSourceOver |
435 fraction:1.0 | 537 fraction:1.0 |
436 respectFlipped:YES | 538 respectFlipped:YES |
437 hints:nil]; | 539 hints:nil]; |
438 offset += NSWidth(imageRect); | 540 offset += NSWidth(imageRect); |
439 } | 541 } |
440 offset += [self drawMatchPart:description_ | 542 offset += [self drawMatchPart:[cellData description] |
441 withFrame:cellFrame | 543 withFrame:cellFrame |
442 atOffset:offset | 544 atOffset:offset |
443 withMaxWidth:descriptionMaxWidth | 545 withMaxWidth:descriptionMaxWidth |
444 inView:controlView]; | 546 inView:controlView]; |
445 } | 547 } |
446 } | 548 } |
447 | 549 |
448 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame | 550 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame |
551 withCellData:(OmniboxPopupCellData*)cellData | |
449 inView:(NSView*)controlView | 552 inView:(NSView*)controlView |
450 withContentsMaxWidth:(int*)contentsMaxWidth { | 553 withContentsMaxWidth:(int*)contentsMaxWidth { |
451 CGFloat offset = 0.0f; | 554 CGFloat offset = 0.0f; |
452 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | 555 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
453 bool isRTL = base::i18n::IsRTL(); | 556 CGFloat prefixWidth = [[cellData prefix] size].width; |
454 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | |
455 base::i18n::GetFirstStrongCharacterDirection(match_.contents)); | |
456 // Prefix may not have any characters with strong directionality, and may take | |
457 // the UI directionality. But prefix needs to appear in continuation of the | |
458 // contents so we force the directionality. | |
459 NSTextAlignment textAlignment = isContentsRTL ? | |
460 NSRightTextAlignment : NSLeftTextAlignment; | |
461 prefix_.reset([CreateAttributedString(base::UTF8ToUTF16( | |
462 match_.GetAdditionalInfo(kACMatchPropertyContentsPrefix)), | |
463 ContentTextColor(), textAlignment) retain]); | |
464 CGFloat prefixWidth = [prefix_ size].width; | |
465 | 557 |
466 CGFloat prefixOffset = 0.0f; | 558 CGFloat prefixOffset = 0.0f; |
467 if (isRTL != isContentsRTL) { | 559 if (base::i18n::IsRTL() != [cellData isContentsRTL]) { |
468 // The contents is rendered between the contents offset extending towards | 560 // The contents is rendered between the contents offset extending towards |
469 // the start edge, while prefix is rendered in opposite direction. Ideally | 561 // the start edge, while prefix is rendered in opposite direction. Ideally |
470 // the prefix should be rendered at |contentsOffset_|. If that is not | 562 // the prefix should be rendered at |contentsOffset_|. If that is not |
471 // sufficient to render the widest suggestion, we increase it to | 563 // sufficient to render the widest suggestion, we increase it to |
472 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to | 564 // |maxMatchContentsWidth|. If |remainingWidth| is not sufficient to |
473 // accommodate that, we reduce the offset so that the prefix gets rendered. | 565 // accommodate that, we reduce the offset so that the prefix gets rendered. |
474 prefixOffset = std::min( | 566 prefixOffset = std::min( |
475 remainingWidth - prefixWidth, std::max(contentsOffset_, | 567 remainingWidth - prefixWidth, |
476 maxMatchContentsWidth_)); | 568 std::max([cellData contentsOffset], [cellData maxMatchContentsWidth])); |
477 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); | 569 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); |
478 } else { // The direction of contents is same as UI direction. | 570 } else { // The direction of contents is same as UI direction. |
479 // Ideally the offset should be |contentsOffset_|. If the max total width | 571 // Ideally the offset should be |contentsOffset_|. If the max total width |
480 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the | 572 // (|prefixWidth| + |maxMatchContentsWidth|) from offset will exceed the |
481 // |remainingWidth|, then we shift the offset to the left , so that all | 573 // |remainingWidth|, then we shift the offset to the left , so that all |
482 // postfix suggestions are visible. | 574 // postfix suggestions are visible. |
483 // We have to render the prefix, so offset has to be at least |prefixWidth|. | 575 // We have to render the prefix, so offset has to be at least |prefixWidth|. |
484 offset = std::max(prefixWidth, | 576 offset = std::max( |
485 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); | 577 prefixWidth, std::min(remainingWidth - [cellData maxMatchContentsWidth], |
578 [cellData contentsOffset])); | |
486 prefixOffset = offset - prefixWidth; | 579 prefixOffset = offset - prefixWidth; |
487 } | 580 } |
488 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), | 581 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), |
489 *contentsMaxWidth); | 582 *contentsMaxWidth); |
490 [self drawMatchPart:prefix_ | 583 [self drawMatchPart:[cellData prefix] |
491 withFrame:cellFrame | 584 withFrame:cellFrame |
492 atOffset:prefixOffset + kTextStartOffset | 585 atOffset:prefixOffset + kTextStartOffset |
493 withMaxWidth:prefixWidth | 586 withMaxWidth:prefixWidth |
494 inView:controlView]; | 587 inView:controlView]; |
495 return offset; | 588 return offset; |
496 } | 589 } |
497 | 590 |
498 - (CGFloat)drawMatchPart:(NSAttributedString*)as | 591 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString |
499 withFrame:(NSRect)cellFrame | 592 withFrame:(NSRect)cellFrame |
500 atOffset:(CGFloat)offset | 593 atOffset:(CGFloat)offset |
501 withMaxWidth:(int)maxWidth | 594 withMaxWidth:(int)maxWidth |
502 inView:(NSView*)controlView { | 595 inView:(NSView*)controlView { |
503 if (offset > NSWidth(cellFrame)) | 596 if (offset > NSWidth(cellFrame)) |
504 return 0.0f; | 597 return 0.0f; |
505 NSRect renderRect = ShiftRect(cellFrame, offset); | 598 NSRect renderRect = ShiftRect(cellFrame, offset); |
506 renderRect.size.width = | 599 renderRect.size.width = |
507 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); | 600 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); |
508 if (renderRect.size.width != 0) { | 601 NSRect textRect = |
509 [self drawTitle:as | 602 [attributedString boundingRectWithSize:renderRect.size options:nil]; |
510 withFrame:FlipIfRTL(renderRect, cellFrame) | 603 renderRect.origin.y += |
511 inView:controlView]; | 604 std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0); |
512 } | 605 if (NSWidth(renderRect) > 0.0) |
606 [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)]; | |
513 return NSWidth(renderRect); | 607 return NSWidth(renderRect); |
514 } | 608 } |
515 | 609 |
516 - (CGFloat)getMatchContentsWidth { | |
517 NSAttributedString* contents = [self attributedTitle]; | |
518 return contents ? [contents size].width : 0; | |
519 } | |
520 | |
521 | |
522 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { | 610 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { |
523 const base::string16& inputText = base::UTF8ToUTF16( | 611 const base::string16& inputText = base::UTF8ToUTF16( |
524 match.GetAdditionalInfo(kACMatchPropertyInputText)); | 612 match.GetAdditionalInfo(kACMatchPropertyInputText)); |
525 int contentsStartIndex = 0; | 613 int contentsStartIndex = 0; |
526 base::StringToInt( | 614 base::StringToInt( |
527 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), | 615 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), |
528 &contentsStartIndex); | 616 &contentsStartIndex); |
529 // Ignore invalid state. | 617 // Ignore invalid state. |
530 if (!StartsWith(match.fill_into_edit, inputText, true) | 618 if (!StartsWith(match.fill_into_edit, inputText, true) |
531 || !EndsWith(match.fill_into_edit, match.contents, true) | 619 || !EndsWith(match.fill_into_edit, match.contents, true) |
532 || ((size_t)contentsStartIndex >= inputText.length())) { | 620 || ((size_t)contentsStartIndex >= inputText.length())) { |
533 return 0; | 621 return 0; |
534 } | 622 } |
535 bool isRTL = base::i18n::IsRTL(); | 623 bool isRTL = base::i18n::IsRTL(); |
536 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | 624 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == |
537 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | 625 base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
538 | 626 |
539 // Color does not matter. | 627 // Color does not matter. |
540 NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); | 628 NSAttributedString* attributedString = |
541 base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] | 629 CreateAttributedString(inputText, DimTextColor()); |
542 initWithAttributedString:as]); | 630 base::scoped_nsobject<NSTextStorage> textStorage( |
631 [[NSTextStorage alloc] initWithAttributedString:attributedString]); | |
543 base::scoped_nsobject<NSLayoutManager> layoutManager( | 632 base::scoped_nsobject<NSLayoutManager> layoutManager( |
544 [[NSLayoutManager alloc] init]); | 633 [[NSLayoutManager alloc] init]); |
545 base::scoped_nsobject<NSTextContainer> textContainer( | 634 base::scoped_nsobject<NSTextContainer> textContainer( |
546 [[NSTextContainer alloc] init]); | 635 [[NSTextContainer alloc] init]); |
547 [layoutManager addTextContainer:textContainer]; | 636 [layoutManager addTextContainer:textContainer]; |
548 [textStorage addLayoutManager:layoutManager]; | 637 [textStorage addLayoutManager:layoutManager]; |
549 | 638 |
550 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); | 639 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); |
551 NSUInteger glyphIndex = | 640 NSUInteger glyphIndex = |
552 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; | 641 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; |
553 | 642 |
554 // This offset is computed from the left edge of the glyph always from the | 643 // This offset is computed from the left edge of the glyph always from the |
555 // left edge of the string, irrespective of the directionality of UI or text. | 644 // left edge of the string, irrespective of the directionality of UI or text. |
556 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 645 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
557 | 646 |
558 CGFloat inputWidth = [as size].width; | 647 CGFloat inputWidth = [attributedString size].width; |
559 | 648 |
560 // The offset obtained above may need to be corrected because the left-most | 649 // The offset obtained above may need to be corrected because the left-most |
561 // glyph may not have 0 offset. So we find the offset of left-most glyph, and | 650 // glyph may not have 0 offset. So we find the offset of left-most glyph, and |
562 // subtract it from the offset of the glyph we obtained above. | 651 // subtract it from the offset of the glyph we obtained above. |
563 CGFloat minOffset = glyphOffset; | 652 CGFloat minOffset = glyphOffset; |
564 | 653 |
565 // If content is RTL, we are interested in the right-edge of the glyph. | 654 // If content is RTL, we are interested in the right-edge of the glyph. |
566 // Unfortunately the bounding rect computation methods from NSLayoutManager or | 655 // Unfortunately the bounding rect computation methods from NSLayoutManager or |
567 // NSFont don't work correctly with bidirectional text. So we compute the | 656 // NSFont don't work correctly with bidirectional text. So we compute the |
568 // glyph width by finding the closest glyph offset to the right of the glyph | 657 // glyph width by finding the closest glyph offset to the right of the glyph |
569 // we are looking for. | 658 // we are looking for. |
570 CGFloat glyphWidth = inputWidth; | 659 CGFloat glyphWidth = inputWidth; |
571 | 660 |
572 for (NSUInteger i = 0; i < [as length]; i++) { | 661 for (NSUInteger i = 0; i < [attributedString length]; i++) { |
573 if (i == charIndex) continue; | 662 if (i == charIndex) continue; |
574 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; | 663 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; |
575 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 664 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
576 minOffset = std::min(minOffset, offset); | 665 minOffset = std::min(minOffset, offset); |
577 if (offset > glyphOffset) | 666 if (offset > glyphOffset) |
578 glyphWidth = std::min(glyphWidth, offset - glyphOffset); | 667 glyphWidth = std::min(glyphWidth, offset - glyphOffset); |
579 } | 668 } |
580 glyphOffset -= minOffset; | 669 glyphOffset -= minOffset; |
581 if (glyphWidth == 0) | 670 if (glyphWidth == 0) |
582 glyphWidth = inputWidth - glyphOffset; | 671 glyphWidth = inputWidth - glyphOffset; |
583 if (isContentsRTL) | 672 if (isContentsRTL) |
584 glyphOffset += glyphWidth; | 673 glyphOffset += glyphWidth; |
585 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; | 674 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; |
586 } | 675 } |
587 | 676 |
588 @end | 677 @end |
OLD | NEW |