Chromium Code Reviews| 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 @implementation OmniboxPopupCellData |
| 285 self = [super init]; | |
| 286 if (self) { | |
| 287 [self setImagePosition:NSImageLeft]; | |
| 288 [self setBordered:NO]; | |
| 289 [self setButtonType:NSRadioButton]; | |
| 290 | 298 |
| 291 // Without this highlighting messes up white areas of images. | 299 - (instancetype)initWithMatch:(const AutocompleteMatch&)match |
| 292 [self setHighlightsBy:NSNoCellMask]; | 300 image:(NSImage*)image { |
| 301 if ((self = [super init])) { | |
| 302 image_.reset([image retain]); | |
| 293 | 303 |
| 294 const base::string16& raw_separator = | 304 base::string16 raw_separator = |
| 295 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); | 305 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); |
| 296 separator_.reset( | 306 separator_.reset( |
| 297 [CreateAttributedString(raw_separator, DimTextColor()) retain]); | 307 [CreateAttributedString(raw_separator, DimTextColor()) retain]); |
| 308 | |
| 309 isContentsRTL_ = | |
| 310 (base::i18n::RIGHT_TO_LEFT == | |
| 311 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | |
| 312 matchType_ = match.type; | |
| 313 | |
| 314 // Prefix may not have any characters with strong directionality, and may | |
| 315 // take the UI directionality. But prefix needs to appear in continuation | |
| 316 // of the contents so we force the directionality. | |
| 317 NSTextAlignment textAlignment = | |
| 318 isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment; | |
| 319 prefix_.reset( | |
| 320 [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo( | |
| 321 kACMatchPropertyContentsPrefix)), | |
| 322 ContentTextColor(), textAlignment) retain]); | |
| 323 | |
| 324 contents_.reset([CreateClassifiedAttributedString( | |
| 325 match.contents, ContentTextColor(), match.contents_class) retain]); | |
| 326 | |
| 327 if (match.answer) { | |
| 328 base::scoped_nsobject<NSMutableAttributedString> answerString( | |
| 329 [[NSMutableAttributedString alloc] init]); | |
| 330 DCHECK(!match.answer->second_line().text_fields().empty()); | |
| 331 for (const SuggestionAnswer::TextField& textField : | |
| 332 match.answer->second_line().text_fields()) { | |
| 333 [answerString | |
| 334 appendAttributedString:CreateAnswerString(textField.text(), | |
| 335 textField.type())]; | |
| 336 } | |
| 337 const base::string16 space(base::ASCIIToUTF16(" ")); | |
| 338 // const base::char16 space(' '); | |
| 339 const SuggestionAnswer::TextField* textField = | |
| 340 match.answer->second_line().additional_text(); | |
| 341 if (textField) { | |
| 342 [answerString | |
| 343 appendAttributedString:CreateAnswerString(space + textField->text(), | |
| 344 textField->type())]; | |
| 345 } | |
| 346 textField = match.answer->second_line().status_text(); | |
| 347 if (textField) { | |
| 348 [answerString | |
| 349 appendAttributedString:CreateAnswerString(space + textField->text(), | |
| 350 textField->type())]; | |
| 351 } | |
| 352 description_.reset(answerString.release()); | |
| 353 } else if (match.description.empty()) { | |
| 354 description_.reset(); | |
| 355 } else { | |
| 356 description_.reset([CreateClassifiedAttributedString( | |
| 357 match.description, DimTextColor(), match.description_class) retain]); | |
| 358 } | |
| 298 } | 359 } |
| 299 return self; | 360 return self; |
| 300 } | 361 } |
| 301 | 362 |
| 363 - (id)copyWithZone:(NSZone*)zone { | |
|
groby-ooo-7-16
2015/06/05 01:28:47
Why does the cellData need copyWithZone?
dschuyler
2015/06/09 01:30:46
copyWithZone is called by setObjectValue.
Maybe I
| |
| 364 OmniboxPopupCellData* copy = [[OmniboxPopupCellData alloc] init]; | |
| 365 copy->contents_.reset([contents_ retain]); | |
| 366 copy->separator_.reset([separator_ retain]); | |
| 367 copy->description_.reset([description_ retain]); | |
| 368 copy->prefix_.reset([prefix_ retain]); | |
| 369 copy->image_.reset([image_ retain]); | |
| 370 copy->answerImage_.reset([answerImage_ retain]); | |
| 371 copy->maxMatchContentsWidth_ = maxMatchContentsWidth_; | |
| 372 copy->contentsOffset_ = contentsOffset_; | |
| 373 copy->isContentsRTL_ = isContentsRTL_; | |
| 374 copy->matchType_ = matchType_; | |
| 375 return copy; | |
| 376 } | |
| 377 | |
| 378 - (void)setContents:(NSAttributedString*)contents { | |
| 379 contents_.reset([contents retain]); | |
| 380 } | |
| 381 | |
| 382 - (void)setImage:(NSImage*)image { | |
| 383 image_.reset([image retain]); | |
| 384 } | |
| 385 | |
| 302 - (void)setAnswerImage:(NSImage*)image { | 386 - (void)setAnswerImage:(NSImage*)image { |
| 303 answerImage_.reset([image retain]); | 387 answerImage_.reset([image retain]); |
| 304 } | 388 } |
| 305 | 389 |
| 306 - (void)setMatch:(const AutocompleteMatch&)match { | |
| 307 match_ = match; | |
| 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 } | |
| 344 | |
| 345 - (NSAttributedString*)description { | 390 - (NSAttributedString*)description { |
| 346 return description_; | 391 return description_; |
| 347 } | 392 } |
| 348 | 393 |
| 349 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { | 394 - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { |
| 350 maxMatchContentsWidth_ = maxMatchContentsWidth; | 395 maxMatchContentsWidth_ = maxMatchContentsWidth; |
| 351 } | 396 } |
| 352 | 397 |
| 353 - (void)setContentsOffset:(CGFloat)contentsOffset { | 398 - (void)setContentsOffset:(CGFloat)contentsOffset { |
| 354 contentsOffset_ = contentsOffset; | 399 contentsOffset_ = contentsOffset; |
| 355 } | 400 } |
| 356 | 401 |
| 357 // The default NSButtonCell drawing leaves the image flush left and | 402 - (CGFloat)getMatchContentsWidth { |
| 358 // the title next to the image. This spaces things out to line up | 403 return [contents_ size].width; |
| 359 // with the star button and autocomplete field. | 404 } |
| 360 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { | 405 |
| 361 if ([self state] == NSOnState || [self isHighlighted]) { | 406 - (CGFloat)rowHeight { |
| 362 if ([self state] == NSOnState) | 407 return kImageSize + kCellHeightAdjust; |
| 363 [SelectedBackgroundColor() set]; | 408 } |
| 364 else | 409 |
| 365 [HoveredBackgroundColor() set]; | 410 - (void)drawMatchWithFrame:(NSRect)cellFrame |
|
groby-ooo-7-16
2015/06/05 01:28:47
Draw routines really belong on the cell, not the d
dschuyler
2015/06/09 01:30:46
Done.
| |
| 366 NSBezierPath* path = | 411 inCell:(OmniboxPopupCell*)cell |
| 367 [NSBezierPath bezierPathWithRoundedRect:cellFrame | 412 inView:(NSView*)controlView { |
| 368 xRadius:kCellRoundingRadius | 413 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
| 369 yRadius:kCellRoundingRadius]; | 414 CGFloat contentsWidth = [self getMatchContentsWidth]; |
| 370 [path fill]; | 415 CGFloat separatorWidth = [separator_ size].width; |
| 371 } | 416 CGFloat descriptionWidth = description_ ? [description_ size].width : 0; |
| 417 int contentsMaxWidth, descriptionMaxWidth; | |
| 418 OmniboxPopupModel::ComputeMatchMaxWidths( | |
| 419 ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth), | |
| 420 ceilf(remainingWidth), !AutocompleteMatch::IsSearchType(matchType_), | |
| 421 &contentsMaxWidth, &descriptionMaxWidth); | |
| 372 | 422 |
| 373 // Put the image centered vertically but in a fixed column. | 423 // Put the image centered vertically but in a fixed column. |
| 374 NSImage* image = [self image]; | 424 if (image_) { |
| 375 if (image) { | |
| 376 NSRect imageRect = cellFrame; | 425 NSRect imageRect = cellFrame; |
| 377 imageRect.size = [image size]; | 426 imageRect.size = [image_ size]; |
| 378 imageRect.origin.y += | 427 imageRect.origin.y += |
| 379 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); | 428 std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); |
| 380 imageRect.origin.x += kImageXOffset; | 429 imageRect.origin.x += kImageXOffset; |
| 381 [image drawInRect:FlipIfRTL(imageRect, cellFrame) | 430 [image_ drawInRect:FlipIfRTL(imageRect, cellFrame) |
| 382 fromRect:NSZeroRect // Entire image | 431 fromRect:NSZeroRect |
| 383 operation:NSCompositeSourceOver | 432 operation:NSCompositeSourceOver |
| 384 fraction:1.0 | 433 fraction:1.0 |
| 385 respectFlipped:YES | 434 respectFlipped:YES |
| 386 hints:nil]; | 435 hints:nil]; |
| 387 } | 436 } |
| 388 | 437 |
| 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; | 438 CGFloat offset = kTextStartOffset; |
| 410 if (match_.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { | 439 if (matchType_ == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { |
| 411 // Infinite suggestions are rendered with a prefix (usually ellipsis), which | 440 // Infinite suggestions are rendered with a prefix (usually ellipsis), which |
| 412 // appear vertically stacked. | 441 // appear vertically stacked. |
| 413 offset += [self drawMatchPrefixWithFrame:cellFrame | 442 offset += [self drawMatchPrefixWithFrame:cellFrame |
| 443 inCell:cell | |
| 414 inView:controlView | 444 inView:controlView |
| 415 withContentsMaxWidth:&contentsMaxWidth]; | 445 withContentsMaxWidth:&contentsMaxWidth]; |
| 416 } | 446 } |
| 417 offset += [self drawMatchPart:contents | 447 offset += [cell drawMatchPart:contents_ |
| 418 withFrame:cellFrame | 448 withFrame:cellFrame |
| 419 atOffset:offset | 449 atOffset:offset |
| 420 withMaxWidth:contentsMaxWidth | 450 withMaxWidth:contentsMaxWidth |
| 421 inView:controlView]; | 451 inView:controlView]; |
| 422 | 452 |
| 423 if (descriptionMaxWidth != 0) { | 453 if (descriptionMaxWidth != 0) { |
| 424 offset += [self drawMatchPart:separator_ | 454 offset += [cell drawMatchPart:separator_ |
| 425 withFrame:cellFrame | 455 withFrame:cellFrame |
| 426 atOffset:offset | 456 atOffset:offset |
| 427 withMaxWidth:separatorWidth | 457 withMaxWidth:separatorWidth |
| 428 inView:controlView]; | 458 inView:controlView]; |
| 429 if (answerImage_) { | 459 if (answerImage_) { |
| 430 NSRect imageRect = NSMakeRect(offset, NSMinY(cellFrame), | 460 NSRect imageRect = NSMakeRect(offset, NSMinY(cellFrame), |
| 431 NSHeight(cellFrame), NSHeight(cellFrame)); | 461 NSHeight(cellFrame), NSHeight(cellFrame)); |
| 432 [answerImage_ drawInRect:FlipIfRTL(imageRect, cellFrame) | 462 [answerImage_ drawInRect:FlipIfRTL(imageRect, cellFrame) |
| 433 fromRect:NSZeroRect | 463 fromRect:NSZeroRect |
| 434 operation:NSCompositeSourceOver | 464 operation:NSCompositeSourceOver |
| 435 fraction:1.0 | 465 fraction:1.0 |
| 436 respectFlipped:YES | 466 respectFlipped:YES |
| 437 hints:nil]; | 467 hints:nil]; |
| 438 offset += NSWidth(imageRect); | 468 offset += NSWidth(imageRect); |
| 439 } | 469 } |
| 440 offset += [self drawMatchPart:description_ | 470 offset += [cell drawMatchPart:description_ |
| 441 withFrame:cellFrame | 471 withFrame:cellFrame |
| 442 atOffset:offset | 472 atOffset:offset |
| 443 withMaxWidth:descriptionMaxWidth | 473 withMaxWidth:descriptionMaxWidth |
| 444 inView:controlView]; | 474 inView:controlView]; |
| 445 } | 475 } |
| 446 } | 476 } |
| 447 | 477 |
| 448 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame | 478 - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame |
| 479 inCell:(OmniboxPopupCell*)cell | |
| 449 inView:(NSView*)controlView | 480 inView:(NSView*)controlView |
| 450 withContentsMaxWidth:(int*)contentsMaxWidth { | 481 withContentsMaxWidth:(int*)contentsMaxWidth { |
| 451 CGFloat offset = 0.0f; | 482 CGFloat offset = 0.0f; |
| 452 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); | 483 CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
| 453 bool isRTL = base::i18n::IsRTL(); | |
| 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; | 484 CGFloat prefixWidth = [prefix_ size].width; |
| 465 | 485 |
| 466 CGFloat prefixOffset = 0.0f; | 486 CGFloat prefixOffset = 0.0f; |
| 467 if (isRTL != isContentsRTL) { | 487 if (base::i18n::IsRTL() != isContentsRTL_) { |
| 468 // The contents is rendered between the contents offset extending towards | 488 // The contents is rendered between the contents offset extending towards |
| 469 // the start edge, while prefix is rendered in opposite direction. Ideally | 489 // the start edge, while prefix is rendered in opposite direction. Ideally |
| 470 // the prefix should be rendered at |contentsOffset_|. If that is not | 490 // the prefix should be rendered at |contentsOffset_|. If that is not |
| 471 // sufficient to render the widest suggestion, we increase it to | 491 // sufficient to render the widest suggestion, we increase it to |
| 472 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to | 492 // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to |
| 473 // accommodate that, we reduce the offset so that the prefix gets rendered. | 493 // accommodate that, we reduce the offset so that the prefix gets rendered. |
| 474 prefixOffset = std::min( | 494 prefixOffset = std::min( |
| 475 remainingWidth - prefixWidth, std::max(contentsOffset_, | 495 remainingWidth - prefixWidth, std::max(contentsOffset_, |
| 476 maxMatchContentsWidth_)); | 496 maxMatchContentsWidth_)); |
| 477 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); | 497 offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); |
| 478 } else { // The direction of contents is same as UI direction. | 498 } else { // The direction of contents is same as UI direction. |
| 479 // Ideally the offset should be |contentsOffset_|. If the max total width | 499 // Ideally the offset should be |contentsOffset_|. If the max total width |
| 480 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the | 500 // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the |
| 481 // |remainingWidth|, then we shift the offset to the left , so that all | 501 // |remainingWidth|, then we shift the offset to the left , so that all |
| 482 // postfix suggestions are visible. | 502 // postfix suggestions are visible. |
| 483 // We have to render the prefix, so offset has to be at least |prefixWidth|. | 503 // We have to render the prefix, so offset has to be at least |prefixWidth|. |
| 484 offset = std::max(prefixWidth, | 504 offset = std::max( |
| 505 prefixWidth, | |
| 485 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); | 506 std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); |
| 486 prefixOffset = offset - prefixWidth; | 507 prefixOffset = offset - prefixWidth; |
| 487 } | 508 } |
| 488 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), | 509 *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), |
| 489 *contentsMaxWidth); | 510 *contentsMaxWidth); |
| 490 [self drawMatchPart:prefix_ | 511 [cell drawMatchPart:prefix_ |
| 491 withFrame:cellFrame | 512 withFrame:cellFrame |
| 492 atOffset:prefixOffset + kTextStartOffset | 513 atOffset:prefixOffset + kTextStartOffset |
| 493 withMaxWidth:prefixWidth | 514 withMaxWidth:prefixWidth |
| 494 inView:controlView]; | 515 inView:controlView]; |
| 495 return offset; | 516 return offset; |
| 496 } | 517 } |
| 497 | 518 |
| 498 - (CGFloat)drawMatchPart:(NSAttributedString*)as | 519 @end |
| 520 | |
| 521 @implementation OmniboxPopupCell | |
| 522 | |
| 523 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { | |
| 524 if ([self state] == NSOnState || [self isHighlighted]) { | |
| 525 if ([self state] == NSOnState) | |
| 526 [SelectedBackgroundColor() set]; | |
| 527 else | |
| 528 [HoveredBackgroundColor() set]; | |
| 529 NSBezierPath* path = | |
| 530 [NSBezierPath bezierPathWithRoundedRect:cellFrame | |
| 531 xRadius:kCellRoundingRadius | |
| 532 yRadius:kCellRoundingRadius]; | |
| 533 [path fill]; | |
| 534 } | |
| 535 | |
| 536 [[self objectValue] drawMatchWithFrame:cellFrame | |
| 537 inCell:self | |
| 538 inView:controlView]; | |
| 539 } | |
| 540 | |
| 541 - (CGFloat)drawMatchPart:(NSAttributedString*)attributedString | |
| 499 withFrame:(NSRect)cellFrame | 542 withFrame:(NSRect)cellFrame |
| 500 atOffset:(CGFloat)offset | 543 atOffset:(CGFloat)offset |
| 501 withMaxWidth:(int)maxWidth | 544 withMaxWidth:(int)maxWidth |
| 502 inView:(NSView*)controlView { | 545 inView:(NSView*)controlView { |
| 503 if (offset > NSWidth(cellFrame)) | 546 if (offset > NSWidth(cellFrame)) |
| 504 return 0.0f; | 547 return 0.0f; |
| 505 NSRect renderRect = ShiftRect(cellFrame, offset); | 548 NSRect renderRect = ShiftRect(cellFrame, offset); |
| 506 renderRect.size.width = | 549 renderRect.size.width = |
| 507 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); | 550 std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); |
| 508 if (renderRect.size.width != 0) { | 551 NSRect textRect = |
| 509 [self drawTitle:as | 552 [attributedString boundingRectWithSize:renderRect.size options:nil]; |
| 510 withFrame:FlipIfRTL(renderRect, cellFrame) | 553 renderRect.origin.y += |
| 511 inView:controlView]; | 554 std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0); |
| 512 } | 555 if (NSWidth(renderRect) > 0.0) |
| 556 [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)]; | |
| 513 return NSWidth(renderRect); | 557 return NSWidth(renderRect); |
| 514 } | 558 } |
| 515 | 559 |
| 516 - (CGFloat)getMatchContentsWidth { | |
| 517 NSAttributedString* contents = [self attributedTitle]; | |
| 518 return contents ? [contents size].width : 0; | |
| 519 } | |
| 520 | |
| 521 | |
| 522 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { | 560 + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { |
| 523 const base::string16& inputText = base::UTF8ToUTF16( | 561 const base::string16& inputText = base::UTF8ToUTF16( |
| 524 match.GetAdditionalInfo(kACMatchPropertyInputText)); | 562 match.GetAdditionalInfo(kACMatchPropertyInputText)); |
| 525 int contentsStartIndex = 0; | 563 int contentsStartIndex = 0; |
| 526 base::StringToInt( | 564 base::StringToInt( |
| 527 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), | 565 match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex), |
| 528 &contentsStartIndex); | 566 &contentsStartIndex); |
| 529 // Ignore invalid state. | 567 // Ignore invalid state. |
| 530 if (!StartsWith(match.fill_into_edit, inputText, true) | 568 if (!StartsWith(match.fill_into_edit, inputText, true) |
| 531 || !EndsWith(match.fill_into_edit, match.contents, true) | 569 || !EndsWith(match.fill_into_edit, match.contents, true) |
| 532 || ((size_t)contentsStartIndex >= inputText.length())) { | 570 || ((size_t)contentsStartIndex >= inputText.length())) { |
| 533 return 0; | 571 return 0; |
| 534 } | 572 } |
| 535 bool isRTL = base::i18n::IsRTL(); | 573 bool isRTL = base::i18n::IsRTL(); |
| 536 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == | 574 bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == |
| 537 base::i18n::GetFirstStrongCharacterDirection(match.contents)); | 575 base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
| 538 | 576 |
| 539 // Color does not matter. | 577 // Color does not matter. |
| 540 NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); | 578 NSAttributedString* attributedString = |
| 541 base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] | 579 CreateAttributedString(inputText, DimTextColor()); |
| 542 initWithAttributedString:as]); | 580 base::scoped_nsobject<NSTextStorage> textStorage( |
| 581 [[NSTextStorage alloc] initWithAttributedString:attributedString]); | |
| 543 base::scoped_nsobject<NSLayoutManager> layoutManager( | 582 base::scoped_nsobject<NSLayoutManager> layoutManager( |
| 544 [[NSLayoutManager alloc] init]); | 583 [[NSLayoutManager alloc] init]); |
| 545 base::scoped_nsobject<NSTextContainer> textContainer( | 584 base::scoped_nsobject<NSTextContainer> textContainer( |
| 546 [[NSTextContainer alloc] init]); | 585 [[NSTextContainer alloc] init]); |
| 547 [layoutManager addTextContainer:textContainer]; | 586 [layoutManager addTextContainer:textContainer]; |
| 548 [textStorage addLayoutManager:layoutManager]; | 587 [textStorage addLayoutManager:layoutManager]; |
| 549 | 588 |
| 550 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); | 589 NSUInteger charIndex = static_cast<NSUInteger>(contentsStartIndex); |
| 551 NSUInteger glyphIndex = | 590 NSUInteger glyphIndex = |
| 552 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; | 591 [layoutManager glyphIndexForCharacterAtIndex:charIndex]; |
| 553 | 592 |
| 554 // This offset is computed from the left edge of the glyph always from the | 593 // 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. | 594 // left edge of the string, irrespective of the directionality of UI or text. |
| 556 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 595 CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
| 557 | 596 |
| 558 CGFloat inputWidth = [as size].width; | 597 CGFloat inputWidth = [attributedString size].width; |
| 559 | 598 |
| 560 // The offset obtained above may need to be corrected because the left-most | 599 // 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 | 600 // 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. | 601 // subtract it from the offset of the glyph we obtained above. |
| 563 CGFloat minOffset = glyphOffset; | 602 CGFloat minOffset = glyphOffset; |
| 564 | 603 |
| 565 // If content is RTL, we are interested in the right-edge of the glyph. | 604 // If content is RTL, we are interested in the right-edge of the glyph. |
| 566 // Unfortunately the bounding rect computation methods from NSLayoutManager or | 605 // Unfortunately the bounding rect computation methods from NSLayoutManager or |
| 567 // NSFont don't work correctly with bidirectional text. So we compute the | 606 // 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 | 607 // glyph width by finding the closest glyph offset to the right of the glyph |
| 569 // we are looking for. | 608 // we are looking for. |
| 570 CGFloat glyphWidth = inputWidth; | 609 CGFloat glyphWidth = inputWidth; |
| 571 | 610 |
| 572 for (NSUInteger i = 0; i < [as length]; i++) { | 611 for (NSUInteger i = 0; i < [attributedString length]; i++) { |
| 573 if (i == charIndex) continue; | 612 if (i == charIndex) continue; |
| 574 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; | 613 glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; |
| 575 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; | 614 CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
| 576 minOffset = std::min(minOffset, offset); | 615 minOffset = std::min(minOffset, offset); |
| 577 if (offset > glyphOffset) | 616 if (offset > glyphOffset) |
| 578 glyphWidth = std::min(glyphWidth, offset - glyphOffset); | 617 glyphWidth = std::min(glyphWidth, offset - glyphOffset); |
| 579 } | 618 } |
| 580 glyphOffset -= minOffset; | 619 glyphOffset -= minOffset; |
| 581 if (glyphWidth == 0) | 620 if (glyphWidth == 0) |
| 582 glyphWidth = inputWidth - glyphOffset; | 621 glyphWidth = inputWidth - glyphOffset; |
| 583 if (isContentsRTL) | 622 if (isContentsRTL) |
| 584 glyphOffset += glyphWidth; | 623 glyphOffset += glyphWidth; |
| 585 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; | 624 return isRTL ? (inputWidth - glyphOffset) : glyphOffset; |
| 586 } | 625 } |
| 587 | 626 |
| 588 @end | 627 @end |
| OLD | NEW |