Chromium Code Reviews| Index: chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm |
| diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm |
| index 67d7c2239e23dd8aa5759ec2147f5047702d38e6..4b706e64fe7bc2c621b6a5379c13fff9e1cbff01 100644 |
| --- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm |
| +++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm |
| @@ -24,6 +24,13 @@ |
| namespace { |
| +// How much to adjust the cell sizing up from the default determined |
| +// by the font. |
| +const CGFloat kCellHeightAdjust = 6.0; |
| + |
| +// How large the icon should be when displayed. |
| +const CGFloat kImageSize = 19.0; |
| + |
| // How far to offset image column from the left. |
| const CGFloat kImageXOffset = 5.0; |
| @@ -279,73 +286,149 @@ NSAttributedString* CreateClassifiedAttributedString( |
| } // namespace |
| -@implementation OmniboxPopupCell |
| - |
| -- (id)init { |
| - self = [super init]; |
| - if (self) { |
| - [self setImagePosition:NSImageLeft]; |
| - [self setBordered:NO]; |
| - [self setButtonType:NSRadioButton]; |
| +@interface OmniboxPopupCell () |
| +- (CGFloat)drawMatchPart:(NSAttributedString*)attributedString |
| + withFrame:(NSRect)cellFrame |
| + atOffset:(CGFloat)offset |
| + withMaxWidth:(int)maxWidth |
| + inView:(NSView*)controlView; |
| +@end |
| - // Without this highlighting messes up white areas of images. |
| - [self setHighlightsBy:NSNoCellMask]; |
| +@interface OmniboxPopupCellData () |
| +- (NSAttributedString*)contents; |
| +- (NSAttributedString*)description; |
| +- (NSAttributedString*)prefix; |
| +- (NSImage*)image; |
| +- (NSImage*)answerImage; |
| +- (CGFloat)maxMatchContentsWidth; |
| +- (CGFloat)contentsOffset; |
| +- (bool)isContentsRTL; |
| +- (AutocompleteMatch::Type)matchType; |
| +@end |
| - const base::string16& raw_separator = |
| - l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); |
| - separator_.reset( |
| - [CreateAttributedString(raw_separator, DimTextColor()) retain]); |
| +@implementation OmniboxPopupCellData |
| + |
| +- (instancetype)initWithMatch:(const AutocompleteMatch&)match |
| + image:(NSImage*)image { |
| + if ((self = [super init])) { |
| + image_.reset([image retain]); |
| + |
| + isContentsRTL_ = |
| + (base::i18n::RIGHT_TO_LEFT == |
| + base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
| + matchType_ = match.type; |
| + |
| + // Prefix may not have any characters with strong directionality, and may |
| + // take the UI directionality. But prefix needs to appear in continuation |
| + // of the contents so we force the directionality. |
| + NSTextAlignment textAlignment = |
| + isContentsRTL_ ? NSRightTextAlignment : NSLeftTextAlignment; |
| + prefix_.reset( |
| + [CreateAttributedString(base::UTF8ToUTF16(match.GetAdditionalInfo( |
| + kACMatchPropertyContentsPrefix)), |
| + ContentTextColor(), textAlignment) retain]); |
| + |
| + contents_.reset([CreateClassifiedAttributedString( |
| + match.contents, ContentTextColor(), match.contents_class) retain]); |
| + |
| + if (match.answer) { |
| + base::scoped_nsobject<NSMutableAttributedString> answerString( |
| + [[NSMutableAttributedString alloc] init]); |
| + DCHECK(!match.answer->second_line().text_fields().empty()); |
| + for (const SuggestionAnswer::TextField& textField : |
| + match.answer->second_line().text_fields()) { |
| + [answerString |
| + appendAttributedString:CreateAnswerString(textField.text(), |
| + textField.type())]; |
| + } |
| + const base::string16 space(base::ASCIIToUTF16(" ")); |
| + // const base::char16 space(' '); |
| + const SuggestionAnswer::TextField* textField = |
| + match.answer->second_line().additional_text(); |
| + if (textField) { |
| + [answerString |
| + appendAttributedString:CreateAnswerString(space + textField->text(), |
| + textField->type())]; |
| + } |
| + textField = match.answer->second_line().status_text(); |
| + if (textField) { |
| + [answerString |
| + appendAttributedString:CreateAnswerString(space + textField->text(), |
| + textField->type())]; |
| + } |
| + description_.reset(answerString.release()); |
| + } else if (match.description.empty()) { |
| + description_.reset(); |
| + } else { |
| + description_.reset([CreateClassifiedAttributedString( |
| + match.description, DimTextColor(), match.description_class) retain]); |
| + } |
| } |
| return self; |
| } |
| +- (instancetype)copyWithZone:(NSZone*)zone { |
| + 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.
|
| + copy->contents_.reset([contents_ retain]); |
| + copy->description_.reset([description_ retain]); |
| + copy->prefix_.reset([prefix_ retain]); |
| + copy->image_.reset([image_ retain]); |
| + copy->answerImage_.reset([answerImage_ retain]); |
| + copy->maxMatchContentsWidth_ = maxMatchContentsWidth_; |
| + copy->contentsOffset_ = contentsOffset_; |
| + copy->isContentsRTL_ = isContentsRTL_; |
| + copy->matchType_ = matchType_; |
| + return copy; |
| +} |
| + |
| +- (void)setContents:(NSAttributedString*)contents { |
| + contents_.reset([contents retain]); |
| +} |
| + |
| +- (void)setImage:(NSImage*)image { |
| + image_.reset([image retain]); |
| +} |
| + |
| - (void)setAnswerImage:(NSImage*)image { |
| answerImage_.reset([image retain]); |
| } |
| -- (void)setMatch:(const AutocompleteMatch&)match { |
| - match_ = match; |
| - NSAttributedString *contents = CreateClassifiedAttributedString( |
| - match_.contents, ContentTextColor(), match_.contents_class); |
| - [self setAttributedTitle:contents]; |
| - [self setAnswerImage:nil]; |
| - if (match_.answer) { |
| - base::scoped_nsobject<NSMutableAttributedString> answerString( |
| - [[NSMutableAttributedString alloc] init]); |
| - DCHECK(!match_.answer->second_line().text_fields().empty()); |
| - for (const SuggestionAnswer::TextField& textField : |
| - match_.answer->second_line().text_fields()) { |
| - NSAttributedString* as = |
| - CreateAnswerString(textField.text(), textField.type()); |
| - [answerString appendAttributedString:as]; |
| - } |
| - const base::char16 space(' '); |
| - const SuggestionAnswer::TextField* textField = |
| - match_.answer->second_line().additional_text(); |
| - if (textField) { |
| - [answerString |
| - appendAttributedString:CreateAnswerString(space + textField->text(), |
| - textField->type())]; |
| - } |
| - textField = match_.answer->second_line().status_text(); |
| - if (textField) { |
| - [answerString |
| - appendAttributedString:CreateAnswerString(space + textField->text(), |
| - textField->type())]; |
| - } |
| - description_.reset(answerString.release()); |
| - } else if (match_.description.empty()) { |
| - description_.reset(); |
| - } else { |
| - description_.reset([CreateClassifiedAttributedString( |
| - match_.description, DimTextColor(), match_.description_class) retain]); |
| - } |
| +- (NSAttributedString*)contents { |
| + return contents_; |
| } |
| - (NSAttributedString*)description { |
| return description_; |
| } |
| +- (NSAttributedString*)prefix { |
| + return prefix_; |
| +} |
| + |
| +- (NSImage*)image { |
| + return image_; |
| +} |
| + |
| +- (NSImage*)answerImage { |
| + return answerImage_; |
| +} |
| + |
| +- (CGFloat)maxMatchContentsWidth { |
| + return maxMatchContentsWidth_; |
| +} |
| + |
| +- (CGFloat)contentsOffset { |
| + return contentsOffset_; |
| +} |
| + |
| +- (bool)isContentsRTL { |
| + return isContentsRTL_; |
| +} |
| + |
| +- (AutocompleteMatch::Type)matchType { |
| + return matchType_; |
| +} |
| + |
| - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { |
| maxMatchContentsWidth_ = maxMatchContentsWidth; |
| } |
| @@ -354,10 +437,29 @@ NSAttributedString* CreateClassifiedAttributedString( |
| contentsOffset_ = contentsOffset; |
| } |
| -// The default NSButtonCell drawing leaves the image flush left and |
| -// the title next to the image. This spaces things out to line up |
| -// with the star button and autocomplete field. |
| -- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { |
| +- (CGFloat)getMatchContentsWidth { |
| + return [contents_ size].width; |
| +} |
| + |
| +- (CGFloat)rowHeight { |
| + return kImageSize + kCellHeightAdjust; |
| +} |
| + |
| +@end |
| + |
| +@implementation OmniboxPopupCell |
| + |
| +- (instancetype)init { |
| + if ((self = [super init])) { |
| + base::string16 raw_separator = |
| + l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); |
| + separator_.reset( |
| + [CreateAttributedString(raw_separator, DimTextColor()) retain]); |
| + } |
| + return self; |
| +} |
| + |
| +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| if ([self state] == NSOnState || [self isHighlighted]) { |
| if ([self state] == NSOnState) |
| [SelectedBackgroundColor() set]; |
| @@ -370,51 +472,51 @@ NSAttributedString* CreateClassifiedAttributedString( |
| [path fill]; |
| } |
| - // Put the image centered vertically but in a fixed column. |
| - NSImage* image = [self image]; |
| - if (image) { |
| - NSRect imageRect = cellFrame; |
| - imageRect.size = [image size]; |
| - imageRect.origin.y += |
| - std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); |
| - imageRect.origin.x += kImageXOffset; |
| - [image drawInRect:FlipIfRTL(imageRect, cellFrame) |
| - fromRect:NSZeroRect // Entire image |
| - operation:NSCompositeSourceOver |
| - fraction:1.0 |
| - respectFlipped:YES |
| - hints:nil]; |
| - } |
| - |
| - [self drawMatchWithFrame:cellFrame inView:controlView]; |
| + [self drawMatchWithFrame:cellFrame |
| + withCellData:[self objectValue] |
| + inView:controlView]; |
| } |
| -- (void)drawMatchWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| - NSAttributedString* contents = [self attributedTitle]; |
| - |
| +- (void)drawMatchWithFrame:(NSRect)cellFrame |
| + withCellData:(OmniboxPopupCellData*)cellData |
| + inView:(NSView*)controlView { |
| CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
| - CGFloat contentsWidth = [self getMatchContentsWidth]; |
| + CGFloat contentsWidth = [cellData getMatchContentsWidth]; |
| CGFloat separatorWidth = [separator_ size].width; |
| - CGFloat descriptionWidth = description_.get() ? [description_ size].width : 0; |
| + CGFloat descriptionWidth = |
| + [cellData description] ? [[cellData description] size].width : 0; |
| int contentsMaxWidth, descriptionMaxWidth; |
| OmniboxPopupModel::ComputeMatchMaxWidths( |
| - ceilf(contentsWidth), |
| - ceilf(separatorWidth), |
| - ceilf(descriptionWidth), |
| + ceilf(contentsWidth), ceilf(separatorWidth), ceilf(descriptionWidth), |
| ceilf(remainingWidth), |
| - !AutocompleteMatch::IsSearchType(match_.type), |
| - &contentsMaxWidth, |
| + !AutocompleteMatch::IsSearchType([cellData matchType]), &contentsMaxWidth, |
| &descriptionMaxWidth); |
| + // Put the image centered vertically but in a fixed column. |
| + if ([cellData image]) { |
| + NSRect imageRect = cellFrame; |
| + imageRect.size = [[cellData image] size]; |
| + imageRect.origin.y += |
| + std::floor((NSHeight(cellFrame) - NSHeight(imageRect)) / 2.0); |
| + imageRect.origin.x += kImageXOffset; |
| + [[cellData image] drawInRect:FlipIfRTL(imageRect, cellFrame) |
| + fromRect:NSZeroRect |
| + operation:NSCompositeSourceOver |
| + fraction:1.0 |
| + respectFlipped:YES |
| + hints:nil]; |
| + } |
| + |
| CGFloat offset = kTextStartOffset; |
| - if (match_.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { |
| + if ([cellData matchType] == AutocompleteMatchType::SEARCH_SUGGEST_TAIL) { |
| // Infinite suggestions are rendered with a prefix (usually ellipsis), which |
| // appear vertically stacked. |
| offset += [self drawMatchPrefixWithFrame:cellFrame |
| + withCellData:cellData |
| inView:controlView |
| withContentsMaxWidth:&contentsMaxWidth]; |
| } |
| - offset += [self drawMatchPart:contents |
| + offset += [self drawMatchPart:[cellData contents] |
| withFrame:cellFrame |
| atOffset:offset |
| withMaxWidth:contentsMaxWidth |
| @@ -426,18 +528,18 @@ NSAttributedString* CreateClassifiedAttributedString( |
| atOffset:offset |
| withMaxWidth:separatorWidth |
| inView:controlView]; |
| - if (answerImage_) { |
| + if ([cellData answerImage]) { |
| NSRect imageRect = NSMakeRect(offset, NSMinY(cellFrame), |
| NSHeight(cellFrame), NSHeight(cellFrame)); |
| - [answerImage_ drawInRect:FlipIfRTL(imageRect, cellFrame) |
| - fromRect:NSZeroRect |
| - operation:NSCompositeSourceOver |
| - fraction:1.0 |
| - respectFlipped:YES |
| - hints:nil]; |
| + [[cellData answerImage] drawInRect:FlipIfRTL(imageRect, cellFrame) |
| + fromRect:NSZeroRect |
| + operation:NSCompositeSourceOver |
| + fraction:1.0 |
| + respectFlipped:YES |
| + hints:nil]; |
| offset += NSWidth(imageRect); |
| } |
| - offset += [self drawMatchPart:description_ |
| + offset += [self drawMatchPart:[cellData description] |
| withFrame:cellFrame |
| atOffset:offset |
| withMaxWidth:descriptionMaxWidth |
| @@ -446,48 +548,39 @@ NSAttributedString* CreateClassifiedAttributedString( |
| } |
| - (CGFloat)drawMatchPrefixWithFrame:(NSRect)cellFrame |
| + withCellData:(OmniboxPopupCellData*)cellData |
| inView:(NSView*)controlView |
| withContentsMaxWidth:(int*)contentsMaxWidth { |
| CGFloat offset = 0.0f; |
| CGFloat remainingWidth = GetContentAreaWidth(cellFrame); |
| - bool isRTL = base::i18n::IsRTL(); |
| - bool isContentsRTL = (base::i18n::RIGHT_TO_LEFT == |
| - base::i18n::GetFirstStrongCharacterDirection(match_.contents)); |
| - // Prefix may not have any characters with strong directionality, and may take |
| - // the UI directionality. But prefix needs to appear in continuation of the |
| - // contents so we force the directionality. |
| - NSTextAlignment textAlignment = isContentsRTL ? |
| - NSRightTextAlignment : NSLeftTextAlignment; |
| - prefix_.reset([CreateAttributedString(base::UTF8ToUTF16( |
| - match_.GetAdditionalInfo(kACMatchPropertyContentsPrefix)), |
| - ContentTextColor(), textAlignment) retain]); |
| - CGFloat prefixWidth = [prefix_ size].width; |
| + CGFloat prefixWidth = [[cellData prefix] size].width; |
| CGFloat prefixOffset = 0.0f; |
| - if (isRTL != isContentsRTL) { |
| + if (base::i18n::IsRTL() != [cellData isContentsRTL]) { |
| // The contents is rendered between the contents offset extending towards |
| // the start edge, while prefix is rendered in opposite direction. Ideally |
| // the prefix should be rendered at |contentsOffset_|. If that is not |
| // sufficient to render the widest suggestion, we increase it to |
| - // |maxMatchContentsWidth_|. If |remainingWidth| is not sufficient to |
| + // |maxMatchContentsWidth|. If |remainingWidth| is not sufficient to |
| // accommodate that, we reduce the offset so that the prefix gets rendered. |
| prefixOffset = std::min( |
| - remainingWidth - prefixWidth, std::max(contentsOffset_, |
| - maxMatchContentsWidth_)); |
| + remainingWidth - prefixWidth, |
| + std::max([cellData contentsOffset], [cellData maxMatchContentsWidth])); |
| offset = std::max<CGFloat>(0.0, prefixOffset - *contentsMaxWidth); |
| } else { // The direction of contents is same as UI direction. |
| // Ideally the offset should be |contentsOffset_|. If the max total width |
| - // (|prefixWidth| + |maxMatchContentsWidth_|) from offset will exceed the |
| + // (|prefixWidth| + |maxMatchContentsWidth|) from offset will exceed the |
| // |remainingWidth|, then we shift the offset to the left , so that all |
| // postfix suggestions are visible. |
| // We have to render the prefix, so offset has to be at least |prefixWidth|. |
| - offset = std::max(prefixWidth, |
| - std::min(remainingWidth - maxMatchContentsWidth_, contentsOffset_)); |
| + offset = std::max( |
| + prefixWidth, std::min(remainingWidth - [cellData maxMatchContentsWidth], |
| + [cellData contentsOffset])); |
| prefixOffset = offset - prefixWidth; |
| } |
| *contentsMaxWidth = std::min((int)ceilf(remainingWidth - prefixWidth), |
| *contentsMaxWidth); |
| - [self drawMatchPart:prefix_ |
| + [self drawMatchPart:[cellData prefix] |
| withFrame:cellFrame |
| atOffset:prefixOffset + kTextStartOffset |
| withMaxWidth:prefixWidth |
| @@ -495,7 +588,7 @@ NSAttributedString* CreateClassifiedAttributedString( |
| return offset; |
| } |
| -- (CGFloat)drawMatchPart:(NSAttributedString*)as |
| +- (CGFloat)drawMatchPart:(NSAttributedString*)attributedString |
| withFrame:(NSRect)cellFrame |
| atOffset:(CGFloat)offset |
| withMaxWidth:(int)maxWidth |
| @@ -505,20 +598,15 @@ NSAttributedString* CreateClassifiedAttributedString( |
| NSRect renderRect = ShiftRect(cellFrame, offset); |
| renderRect.size.width = |
| std::min(NSWidth(renderRect), static_cast<CGFloat>(maxWidth)); |
| - if (renderRect.size.width != 0) { |
| - [self drawTitle:as |
| - withFrame:FlipIfRTL(renderRect, cellFrame) |
| - inView:controlView]; |
| - } |
| + NSRect textRect = |
| + [attributedString boundingRectWithSize:renderRect.size options:nil]; |
| + renderRect.origin.y += |
| + std::floor((NSHeight(cellFrame) - NSHeight(textRect)) / 2.0); |
| + if (NSWidth(renderRect) > 0.0) |
| + [attributedString drawInRect:FlipIfRTL(renderRect, cellFrame)]; |
| return NSWidth(renderRect); |
| } |
| -- (CGFloat)getMatchContentsWidth { |
| - NSAttributedString* contents = [self attributedTitle]; |
| - return contents ? [contents size].width : 0; |
| -} |
| - |
| - |
| + (CGFloat)computeContentsOffset:(const AutocompleteMatch&)match { |
| const base::string16& inputText = base::UTF8ToUTF16( |
| match.GetAdditionalInfo(kACMatchPropertyInputText)); |
| @@ -537,9 +625,10 @@ NSAttributedString* CreateClassifiedAttributedString( |
| base::i18n::GetFirstStrongCharacterDirection(match.contents)); |
| // Color does not matter. |
| - NSAttributedString* as = CreateAttributedString(inputText, DimTextColor()); |
| - base::scoped_nsobject<NSTextStorage> textStorage([[NSTextStorage alloc] |
| - initWithAttributedString:as]); |
| + NSAttributedString* attributedString = |
| + CreateAttributedString(inputText, DimTextColor()); |
| + base::scoped_nsobject<NSTextStorage> textStorage( |
| + [[NSTextStorage alloc] initWithAttributedString:attributedString]); |
| base::scoped_nsobject<NSLayoutManager> layoutManager( |
| [[NSLayoutManager alloc] init]); |
| base::scoped_nsobject<NSTextContainer> textContainer( |
| @@ -555,7 +644,7 @@ NSAttributedString* CreateClassifiedAttributedString( |
| // left edge of the string, irrespective of the directionality of UI or text. |
| CGFloat glyphOffset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |
| - CGFloat inputWidth = [as size].width; |
| + CGFloat inputWidth = [attributedString size].width; |
| // The offset obtained above may need to be corrected because the left-most |
| // glyph may not have 0 offset. So we find the offset of left-most glyph, and |
| @@ -569,7 +658,7 @@ NSAttributedString* CreateClassifiedAttributedString( |
| // we are looking for. |
| CGFloat glyphWidth = inputWidth; |
| - for (NSUInteger i = 0; i < [as length]; i++) { |
| + for (NSUInteger i = 0; i < [attributedString length]; i++) { |
| if (i == charIndex) continue; |
| glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:i]; |
| CGFloat offset = [layoutManager locationForGlyphAtIndex:glyphIndex].x; |