Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(42)

Side by Side Diff: chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm

Issue 1099403005: [AiS] changing mac omnibox suggestions form NSMatrix to NSTableView (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Merge form master Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698