diff options
6 files changed, 242 insertions, 28 deletions
diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h index d2f3736..7e60da9 100644 --- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h +++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h @@ -46,6 +46,8 @@ class OmniboxPopupViewMac; - (void)setMatch:(const AutocompleteMatch&)match; +- (NSAttributedString*)description; + - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth; - (void)setContentsOffset:(CGFloat)contentsOffset; diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm index 5100d12..67d7c22 100644 --- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm +++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.mm @@ -18,6 +18,7 @@ #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" #include "chrome/grit/generated_resources.h" #include "components/omnibox/suggestion_answer.h" +#include "skia/ext/skia_utils_mac.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/font.h" @@ -66,6 +67,12 @@ NSColor* ContentTextColor() { NSColor* DimTextColor() { return [NSColor darkGrayColor]; } +NSColor* PositiveTextColor() { + return gfx::SkColorToCalibratedNSColor(SkColorSetRGB(0x0b, 0x80, 0x43)); +} +NSColor* NegativeTextColor() { + return gfx::SkColorToCalibratedNSColor(SkColorSetRGB(0xc5, 0x39, 0x29)); +} NSColor* URLTextColor() { return [NSColor colorWithCalibratedRed:0.0 green:0.55 blue:0.0 alpha:1.0]; } @@ -76,11 +83,126 @@ NSFont* FieldFont() { NSFont* BoldFieldFont() { return OmniboxViewMac::GetFieldFont(gfx::Font::BOLD); } +NSFont* LargeFont() { + return OmniboxViewMac::GetLargeFont(gfx::Font::NORMAL); +} +NSFont* LargeSuperscriptFont() { + NSFont* font = OmniboxViewMac::GetLargeFont(gfx::Font::NORMAL); + // Calculate a slightly smaller font. The ratio here is somewhat arbitrary. + // Proportions from 5/9 to 5/7 all look pretty good. + CGFloat size = [font pointSize] * 5.0 / 9.0; + NSFontDescriptor* descriptor = [font fontDescriptor]; + return [NSFont fontWithDescriptor:descriptor size:size]; +} +NSFont* SmallFont() { + return OmniboxViewMac::GetSmallFont(gfx::Font::NORMAL); +} CGFloat GetContentAreaWidth(NSRect cellFrame) { return NSWidth(cellFrame) - kTextStartOffset; } +NSAttributedString* CreateAnswerString(const base::string16& text, + NSInteger style_type) { + NSDictionary* answer_style = nil; + switch (style_type) { + case SuggestionAnswer::ANSWER: + answer_style = @{ + NSForegroundColorAttributeName : ContentTextColor(), + NSFontAttributeName : LargeFont() + }; + break; + case SuggestionAnswer::HEADLINE: + answer_style = @{ + NSForegroundColorAttributeName : DimTextColor(), + NSFontAttributeName : LargeFont() + }; + break; + case SuggestionAnswer::TOP_ALIGNED: + answer_style = @{ + NSForegroundColorAttributeName : DimTextColor(), + NSFontAttributeName : LargeSuperscriptFont(), + NSSuperscriptAttributeName : @1 + }; + break; + case SuggestionAnswer::DESCRIPTION: + answer_style = @{ + NSForegroundColorAttributeName : DimTextColor(), + NSFontAttributeName : FieldFont() + }; + break; + case SuggestionAnswer::DESCRIPTION_NEGATIVE: + answer_style = @{ + NSForegroundColorAttributeName : NegativeTextColor(), + NSFontAttributeName : LargeSuperscriptFont() + }; + break; + case SuggestionAnswer::DESCRIPTION_POSITIVE: + answer_style = @{ + NSForegroundColorAttributeName : PositiveTextColor(), + NSFontAttributeName : LargeSuperscriptFont() + }; + break; + case SuggestionAnswer::MORE_INFO: + answer_style = @{ + NSForegroundColorAttributeName : DimTextColor(), + NSFontAttributeName : SmallFont() + }; + break; + case SuggestionAnswer::SUGGESTION: + answer_style = @{ + NSForegroundColorAttributeName : ContentTextColor(), + NSFontAttributeName : FieldFont() + }; + break; + case SuggestionAnswer::SUGGESTION_POSITIVE: + answer_style = @{ + NSForegroundColorAttributeName : PositiveTextColor(), + NSFontAttributeName : FieldFont() + }; + break; + case SuggestionAnswer::SUGGESTION_NEGATIVE: + answer_style = @{ + NSForegroundColorAttributeName : NegativeTextColor(), + NSFontAttributeName : FieldFont() + }; + break; + case SuggestionAnswer::SUGGESTION_LINK: + answer_style = @{ + NSForegroundColorAttributeName : URLTextColor(), + NSFontAttributeName : FieldFont() + }; + break; + case SuggestionAnswer::STATUS: + answer_style = @{ + NSForegroundColorAttributeName : DimTextColor(), + NSFontAttributeName : LargeSuperscriptFont() + }; + break; + case SuggestionAnswer::PERSONALIZED_SUGGESTION: + answer_style = @{ + NSForegroundColorAttributeName : ContentTextColor(), + NSFontAttributeName : FieldFont() + }; + break; + } + + // Start out with a string using the default style info. + base::scoped_nsobject<NSMutableAttributedString> attributed_string( + [[NSMutableAttributedString alloc] + initWithString:base::SysUTF16ToNSString(text) + attributes:answer_style]); + + base::scoped_nsobject<NSMutableParagraphStyle> style( + [[NSMutableParagraphStyle alloc] init]); + [style setLineBreakMode:NSLineBreakByTruncatingTail]; + [style setTighteningFactorForTruncation:0.0]; + [attributed_string addAttribute:NSParagraphStyleAttributeName + value:style + range:NSMakeRange(0, [attributed_string length])]; + return attributed_string.autorelease(); +} + NSMutableAttributedString* CreateAttributedString( const base::string16& text, NSColor* text_color, @@ -91,21 +213,20 @@ NSMutableAttributedString* CreateAttributedString( NSFontAttributeName : FieldFont(), NSForegroundColorAttributeName : text_color }; - NSMutableAttributedString* as = - [[[NSMutableAttributedString alloc] initWithString:s - attributes:attributes] - autorelease]; + NSMutableAttributedString* attributedString = [[ + [NSMutableAttributedString alloc] initWithString:s + attributes:attributes] autorelease]; NSMutableParagraphStyle* style = [[[NSMutableParagraphStyle alloc] init] autorelease]; [style setLineBreakMode:NSLineBreakByTruncatingTail]; [style setTighteningFactorForTruncation:0.0]; [style setAlignment:textAlignment]; - [as addAttribute:NSParagraphStyleAttributeName - value:style - range:NSMakeRange(0, [as length])]; + [attributedString addAttribute:NSParagraphStyleAttributeName + value:style + range:NSMakeRange(0, [attributedString length])]; - return as; + return attributedString; } NSMutableAttributedString* CreateAttributedString( @@ -118,8 +239,9 @@ NSAttributedString* CreateClassifiedAttributedString( const base::string16& text, NSColor* text_color, const ACMatchClassifications& classifications) { - NSMutableAttributedString* as = CreateAttributedString(text, text_color); - NSUInteger match_length = [as length]; + NSMutableAttributedString* attributedString = + CreateAttributedString(text, text_color); + NSUInteger match_length = [attributedString length]; // Mark up the runs which differ from the default. for (ACMatchClassifications::const_iterator i = classifications.begin(); @@ -136,21 +258,23 @@ NSAttributedString* CreateClassifiedAttributedString( NSMakeRange(location, std::min(length, match_length - location)); if (0 != (i->style & ACMatchClassification::MATCH)) { - [as addAttribute:NSFontAttributeName value:BoldFieldFont() range:range]; + [attributedString addAttribute:NSFontAttributeName + value:BoldFieldFont() + range:range]; } if (0 != (i->style & ACMatchClassification::URL)) { - [as addAttribute:NSForegroundColorAttributeName - value:URLTextColor() - range:range]; + [attributedString addAttribute:NSForegroundColorAttributeName + value:URLTextColor() + range:range]; } else if (0 != (i->style & ACMatchClassification::DIM)) { - [as addAttribute:NSForegroundColorAttributeName - value:DimTextColor() - range:range]; + [attributedString addAttribute:NSForegroundColorAttributeName + value:DimTextColor() + range:range]; } } - return as; + return attributedString; } } // namespace @@ -184,23 +308,32 @@ NSAttributedString* CreateClassifiedAttributedString( NSAttributedString *contents = CreateClassifiedAttributedString( match_.contents, ContentTextColor(), match_.contents_class); [self setAttributedTitle:contents]; - [self setAnswerImage:nil]; if (match_.answer) { - base::string16 answerString; + 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 += textField.text(); + 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 += space + textField->text(); + if (textField) { + [answerString + appendAttributedString:CreateAnswerString(space + textField->text(), + textField->type())]; + } textField = match_.answer->second_line().status_text(); - if (textField) - answerString += space + textField->text(); - description_.reset([CreateClassifiedAttributedString( - answerString, DimTextColor(), match_.description_class) retain]); + if (textField) { + [answerString + appendAttributedString:CreateAnswerString(space + textField->text(), + textField->type())]; + } + description_.reset(answerString.release()); } else if (match_.description.empty()) { description_.reset(); } else { @@ -209,6 +342,10 @@ NSAttributedString* CreateClassifiedAttributedString( } } +- (NSAttributedString*)description { + return description_; +} + - (void)setMaxMatchContentsWidth:(CGFloat)maxMatchContentsWidth { maxMatchContentsWidth_ = maxMatchContentsWidth; } diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell_unittest.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell_unittest.mm index e4bb64b..0367a84 100644 --- a/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell_unittest.mm +++ b/chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell_unittest.mm @@ -4,8 +4,12 @@ #import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_cell.h" +#include "base/json/json_reader.h" #include "base/mac/scoped_nsobject.h" +#include "base/values.h" #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" +#include "components/omnibox/suggestion_answer.h" +#import "testing/gtest_mac.h" namespace { @@ -44,4 +48,39 @@ TEST_F(OmniboxPopupCellTest, Title) { [button_ display]; } +TEST_F(OmniboxPopupCellTest, AnswerStyle) { + const char* weatherJson = + "{\"l\": [ {\"il\": {\"t\": [ {" + "\"t\": \"weather in pari<b>s</b>\", \"tt\": 8} ]}}, {" + "\"il\": {\"at\": {\"t\": \"Thu\",\"tt\": 12}, " + "\"i\": {\"d\": \"//ssl.gstatic.com/onebox/weather/64/cloudy.png\"," + "\"t\": 3}, \"t\": [ {\"t\": \"46\",\"tt\": 1}, {" + "\"t\": \"°F\",\"tt\": 3} ]}} ]}"; + NSString* finalString = @"46°F Thu"; + + scoped_ptr<base::Value> root(base::JSONReader::Read(weatherJson)); + ASSERT_NE(root, nullptr); + base::DictionaryValue* dictionary; + root->GetAsDictionary(&dictionary); + ASSERT_NE(dictionary, nullptr); + AutocompleteMatch match; + match.answer = SuggestionAnswer::ParseAnswer(dictionary); + EXPECT_TRUE(match.answer); + [cell_ setMatch:match]; + EXPECT_NSEQ([[cell_ description] string], finalString); + size_t length = [[cell_ description] length]; + const NSRange checkValues[] = {{0, 2}, {2, 2}, {4, 4}}; + EXPECT_EQ(length, 8UL); + NSDictionary* lastAttributes = nil; + for (const NSRange& value : checkValues) { + NSRange range; + NSDictionary* currentAttributes = + [[cell_ description] attributesAtIndex:value.location + effectiveRange:&range]; + EXPECT_TRUE(NSEqualRanges(value, range)); + EXPECT_FALSE([currentAttributes isEqualToDictionary:lastAttributes]); + lastAttributes = currentAttributes; + } +} + } // namespace diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h b/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h index ec0d2c13..385b79ed 100644 --- a/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h +++ b/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h @@ -102,6 +102,8 @@ class OmniboxViewMac : public OmniboxView, // The style parameter specifies the new style for the font, and is a // bitmask of the values: BOLD, ITALIC and UNDERLINE (see ui/gfx/font.h). static NSFont* GetFieldFont(int style); + static NSFont* GetLargeFont(int style); + static NSFont* GetSmallFont(int style); // If |resource_id| has a PDF image which can be used, return it. // Otherwise return the PNG image from the resource bundle. diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm index efa97fe..0e8f701 100644 --- a/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm +++ b/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm @@ -1021,6 +1021,22 @@ NSFont* OmniboxViewMac::GetFieldFont(int style) { .GetPrimaryFont().GetNativeFont(); } +NSFont* OmniboxViewMac::GetLargeFont(int style) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + return rb.GetFontList(ui::ResourceBundle::LargeFont) + .Derive(1, style) + .GetPrimaryFont() + .GetNativeFont(); +} + +NSFont* OmniboxViewMac::GetSmallFont(int style) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + return rb.GetFontList(ui::ResourceBundle::SmallFont) + .Derive(1, style) + .GetPrimaryFont() + .GetNativeFont(); +} + int OmniboxViewMac::GetOmniboxTextLength() const { return static_cast<int>(GetTextLength()); } diff --git a/components/omnibox/suggestion_answer.h b/components/omnibox/suggestion_answer.h index a2df1bc..98e3969 100644 --- a/components/omnibox/suggestion_answer.h +++ b/components/omnibox/suggestion_answer.h @@ -35,6 +35,24 @@ class SuggestionAnswer { typedef std::vector<TextField> TextFields; typedef std::vector<GURL> URLs; + // These values are named and numbered to match a specification at go/ais_api. + // The values are only used for answer results. + enum TextType { + ANSWER = 1, + HEADLINE = 2, + TOP_ALIGNED = 3, + DESCRIPTION = 4, + DESCRIPTION_NEGATIVE = 5, + DESCRIPTION_POSITIVE = 6, + MORE_INFO = 7, + SUGGESTION = 8, + SUGGESTION_POSITIVE = 9, + SUGGESTION_NEGATIVE = 10, + SUGGESTION_LINK = 11, + STATUS = 12, + PERSONALIZED_SUGGESTION = 13, + }; + class TextField { public: TextField(); |