diff options
author | groby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 21:39:25 +0000 |
---|---|---|
committer | groby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 21:39:25 +0000 |
commit | 89457c907830db941455f4838ce4a6a3e3a968a7 (patch) | |
tree | c96ac5bbbc7efd72a6cf062240e106e73820202a /chrome | |
parent | 7242ab5738122da6cc21aa469a418de9bce00b48 (diff) | |
download | chromium_src-89457c907830db941455f4838ce4a6a3e3a968a7.zip chromium_src-89457c907830db941455f4838ce4a6a3e3a968a7.tar.gz chromium_src-89457c907830db941455f4838ce4a6a3e3a968a7.tar.bz2 |
[WebIntents] intent suggestions for OSX
BUG=113476
TEST=none
Review URL: http://codereview.chromium.org/9700083
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127796 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
4 files changed, 230 insertions, 15 deletions
diff --git a/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm b/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm index d7a6cbd..a92862c 100644 --- a/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm +++ b/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm @@ -149,7 +149,8 @@ void WebIntentPickerCocoa::OnFaviconChanged(WebIntentPickerModel* model, void WebIntentPickerCocoa::OnExtensionIconChanged( WebIntentPickerModel* model, const string16& extension_id) { - // TODO(binji): implement. + // We don't handle individual icon changes - just redo the whole model. + PerformLayout(); } void WebIntentPickerCocoa::OnInlineDisposition(WebIntentPickerModel* model, diff --git a/chrome/browser/ui/cocoa/web_intent_sheet_controller.h b/chrome/browser/ui/cocoa/web_intent_sheet_controller.h index 5820f08..04678f5 100644 --- a/chrome/browser/ui/cocoa/web_intent_sheet_controller.h +++ b/chrome/browser/ui/cocoa/web_intent_sheet_controller.h @@ -25,6 +25,9 @@ class WebIntentPickerModel; // Inline disposition tab contents. Weak reference. TabContentsWrapper* contents_; + + // The intent picker data to be rendered. Weak reference. + WebIntentPickerModel* model_; } // Initialize the constrained dialog, and connect to picker. diff --git a/chrome/browser/ui/cocoa/web_intent_sheet_controller.mm b/chrome/browser/ui/cocoa/web_intent_sheet_controller.mm index 29547b4..bb66962 100644 --- a/chrome/browser/ui/cocoa/web_intent_sheet_controller.mm +++ b/chrome/browser/ui/cocoa/web_intent_sheet_controller.mm @@ -6,6 +6,7 @@ #include "base/memory/scoped_nsobject.h" #include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" #include "chrome/browser/ui/browser_list.h" #import "chrome/browser/ui/cocoa/hover_close_button.h" #import "chrome/browser/ui/cocoa/hyperlink_button_cell.h" @@ -18,6 +19,7 @@ #include "content/public/browser/web_contents_view.h" #include "content/public/browser/web_contents.h" #include "grit/generated_resources.h" +#include "grit/google_chrome_strings.h" #include "grit/locale_settings.h" #include "grit/theme_resources.h" #include "grit/ui_resources.h" @@ -34,7 +36,7 @@ namespace { // The width of the window, in view coordinates. The height will be // determined by the content. -const CGFloat kWindowWidth = 380; +const CGFloat kWindowWidth = 400; // The width of a service button, in view coordinates. const CGFloat kServiceButtonWidth = 300; @@ -144,6 +146,24 @@ const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + picker_->OnServiceChosen([sender tag]); } +- (IBAction)openExtensionLink:(id)sender { + const WebIntentPickerModel::SuggestedExtension& extension = + model_->GetSuggestedExtensionAt([sender tag]); + + GURL extension_url(extension_urls::GetWebstoreItemDetailURLPrefix() + + UTF16ToUTF8(extension.id)); + Browser* browser = BrowserList::GetLastActive(); + browser::NavigateParams params(browser, + extension_url, + content::PAGE_TRANSITION_AUTO_BOOKMARK); + params.disposition = NEW_FOREGROUND_TAB; + browser::Navigate(¶ms); +} + +- (IBAction)installExtension:(id)sender { + // TODO(groby):one-click install +} + // Sets proprties on the given |field| to act as the title or description labels // in the bubble. - (void)configureTextFieldAsLabel:(NSTextField*)field { @@ -153,20 +173,140 @@ const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + [field setBezeled:NO]; } +- (CGFloat)addStackedView:(NSView*)view + toSubviews:(NSMutableArray*)subviews + atOffset:(CGFloat)offset { + if (view == nil) + return 0.0; + + NSPoint frameOrigin = [view frame].origin; + frameOrigin.y = offset; + [view setFrameOrigin:frameOrigin]; + [subviews addObject:view]; + + return NSHeight([view frame]); +} + +- (NSButton*)newHyperlinkButton:(NSString*)title withFrame:(NSRect)frame { + NSButton* button = [[NSButton alloc] initWithFrame:frame]; + scoped_nsobject<HyperlinkButtonCell> cell( + [[HyperlinkButtonCell alloc] initTextCell:title]); + [cell setControlSize:NSSmallControlSize]; + [button setCell:cell.get()]; + [button setButtonType:NSMomentaryPushInButton]; + [button setBezelStyle:NSRegularSquareBezelStyle]; + + return button; +} + +- (NSView*)newViewForSuggestedExtension: + (const WebIntentPickerModel::SuggestedExtension*)extension + withIndex:(size_t)index { + const CGFloat kMaxHeight = 34.0; + const CGFloat kTitleX = 20.0; + const CGFloat kTitleWidth = 120.0; + const CGFloat kStarX = 150.0; + const CGFloat kMinAddButtonHeight = 24.0; + const CGFloat kAddButtonX = 245; + const CGFloat kAddButtonWidth = 128.0; + + NSMutableArray* subviews = [NSMutableArray array]; + + // Add the extension icon. + NSImage* icon = extension->icon.ToNSImage(); + NSRect imageFrame = NSZeroRect; + + scoped_nsobject<NSImageView> imageView( + [[NSImageView alloc] initWithFrame:imageFrame]); + [imageView setImage:icon]; + [imageView setImageFrameStyle:NSImageFrameNone]; + + imageFrame.size = [icon size]; + imageFrame.size.height = std::min(NSHeight(imageFrame), kMaxHeight); + imageFrame.origin.y += (kMaxHeight - NSHeight(imageFrame)) / 2.0; + [imageView setFrame:imageFrame]; + [subviews addObject:imageView.get()]; + + // Add the extension title. + NSRect frame = NSMakeRect(kTitleX, 0, kTitleWidth, 0.0); + + NSString* string = base::SysUTF16ToNSString(extension->title); + scoped_nsobject<NSButton> button( + [self newHyperlinkButton:string withFrame:frame]); + [button setTarget:self]; + [button setAction:@selector(openExtensionLink:)]; + [button setTag:index]; + frame.size.height = std::min([[button cell] cellSize].height, kMaxHeight); + frame.origin.y += (kMaxHeight - NSHeight(frame)) / 2.0; + [button setFrame:frame]; + [subviews addObject:button.get()]; + + // Add the star rating + [self addStarWidgetWithRating:extension->average_rating + toSubviews:subviews + atPoint:NSMakePoint(kStarX,0)]; + + // Add an "add to chromium" button. + frame = NSMakeRect(kAddButtonX, 0, kAddButtonWidth, 0); + scoped_nsobject<NSButton> addButton([[NSButton alloc] initWithFrame:frame]); + [addButton setAlignment:NSLeftTextAlignment]; + [addButton setButtonType:NSMomentaryPushInButton]; + [addButton setBezelStyle:NSRegularSquareBezelStyle]; + string = l10n_util::GetNSStringWithFixup(IDS_INTENT_PICKER_INSTALL_EXTENSION); + [addButton setTitle:string]; + frame.size.height = std::min(kMinAddButtonHeight, kMaxHeight); + frame.origin.y += (kMaxHeight - NSHeight(frame)) / 2.0; + [addButton setFrame:frame]; + + [addButton setTarget:self]; + [addButton setAction:@selector(invokeService:)]; + [addButton setTag:index]; + [subviews addObject:addButton.get()]; + + // Build the main view + NSRect contentFrame = NSMakeRect(0, 0, kWindowWidth, kMaxHeight); + NSView* view = [[NSView alloc] initWithFrame:contentFrame]; + + [view setSubviews: subviews]; + return view; +} + +- (NSView*)newViewForSuggestions:(WebIntentPickerModel*)model { + const CGFloat kYMargin = 16.0; + size_t count = model->GetSuggestedExtensionCount(); + if (count == 0) + return nil; + + NSMutableArray* subviews = [NSMutableArray array]; + + CGFloat offset = kYMargin; + for (size_t i = count; i > 0; --i) { + const WebIntentPickerModel::SuggestedExtension& ext = + model->GetSuggestedExtensionAt(i - 1); + scoped_nsobject<NSView> suggestView( + [self newViewForSuggestedExtension:&ext withIndex:i - 1]); + offset += [self addStackedView:suggestView.get() + toSubviews:subviews + atOffset:offset]; + } + offset += kYMargin; + + NSRect contentFrame = NSMakeRect(kFramePadding, 0, kWindowWidth, offset); + NSView* view = [[NSView alloc] initWithFrame:contentFrame]; + [view setSubviews: subviews]; + + return view; +} + // Adds a link to the Chrome Web Store, to obtain further intent handlers. // Returns the y position delta for the next offset. - (CGFloat)addCwsButtonToSubviews:(NSMutableArray*)subviews atOffset:(CGFloat)offset { NSRect frame = NSMakeRect(kFramePadding, offset, 100, 10); - scoped_nsobject<NSButton> button([[NSButton alloc] initWithFrame:frame]); NSString* string = l10n_util::GetNSStringWithFixup(IDS_FIND_MORE_INTENT_HANDLER_MESSAGE); - scoped_nsobject<HyperlinkButtonCell> cell( - [[HyperlinkButtonCell alloc] initTextCell:string]); - [cell setControlSize:NSSmallControlSize]; - [button setCell:cell.get()]; - [button setButtonType:NSMomentaryPushInButton]; - [button setBezelStyle:NSRegularSquareBezelStyle]; + scoped_nsobject<NSButton> button( + [self newHyperlinkButton:string withFrame:frame]); [button setTarget:self]; [button setAction:@selector(showChromeWebStore:)]; [subviews addObject:button.get()]; @@ -204,18 +344,19 @@ const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: textField]; - NSRect buttonFrame = NSMakeRect(kFramePadding+kImageSize+kTextWidth, - offset, kCloseButtonSize, kCloseButtonSize); + NSRect buttonFrame = NSMakeRect( + kFramePadding + kImageSize + kTextWidth, offset, + kCloseButtonSize, kCloseButtonSize); scoped_nsobject<HoverCloseButton> closeButton( [[HoverCloseButton alloc] initWithFrame:buttonFrame]); [closeButton setTarget:self]; [closeButton setAction:@selector(cancelSelection:)]; // Adjust view height to fit elements, center-align elements. - CGFloat maxHeight = std::max(buttonFrame.size.height, - std::max(imageFrame.size.height,textFrame.size.height)); - textFrame.origin.y += (maxHeight - textFrame.size.height) / 2; - imageFrame.origin.y += (maxHeight - imageFrame.size.height) / 2; + CGFloat maxHeight = std::max(NSHeight(buttonFrame), + std::max(NSHeight(imageFrame), NSHeight(textFrame))); + textFrame.origin.y += (maxHeight - NSHeight(textFrame)) / 2; + imageFrame.origin.y += (maxHeight - NSHeight(imageFrame)) / 2; [textField setFrame:textFrame]; [imageView setFrame:imageFrame]; @@ -278,8 +419,34 @@ const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + return NSHeight([button frame]); } +- (NSPoint)addStarWidgetWithRating:(CGFloat)rating + toSubviews:(NSMutableArray*)subviews + atPoint:(NSPoint)position { + const int kStarSpacing = 1; // Spacing between stars in pixels. + const CGFloat kStarSize = 16.0; // Size of the star in pixels. + + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + NSRect imageFrame = NSMakeRect(position.x, position.y, kStarSize, kStarSize); + + for (int i = 0; i < 5; ++i) { + NSImage* nsImage = rb.GetNativeImageNamed( + WebIntentPicker::GetNthStarImageIdFromCWSRating(rating, i)); + + scoped_nsobject<NSImageView> imageView( + [[NSImageView alloc] initWithFrame:imageFrame]); + [imageView setImage:nsImage]; + [imageView setImageFrameStyle:NSImageFrameNone]; + [imageView setFrame:imageFrame]; + [subviews addObject:imageView.get()]; + imageFrame.origin.x += kStarSize + kStarSpacing; + } + position.x = imageFrame.origin.x; + return position; +} + // Layout the contents of the picker bubble. - (void)performLayoutWithModel:(WebIntentPickerModel*)model { + model_ = model; // |offset| is the Y position that should be drawn at next. CGFloat offset = kFramePadding; @@ -300,6 +467,12 @@ const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + toSubviews:subviews atOffset:offset]; } + + scoped_nsobject<NSView> suggestionView( + [self newViewForSuggestions:model]); + offset += [self addStackedView:suggestionView.get() + toSubviews:subviews + atOffset:offset]; } offset += [self addCwsButtonToSubviews:subviews atOffset:offset]; } diff --git a/chrome/browser/ui/cocoa/web_intent_sheet_controller_unittest.mm b/chrome/browser/ui/cocoa/web_intent_sheet_controller_unittest.mm index 8e0a13b..c173462 100644 --- a/chrome/browser/ui/cocoa/web_intent_sheet_controller_unittest.mm +++ b/chrome/browser/ui/cocoa/web_intent_sheet_controller_unittest.mm @@ -193,3 +193,41 @@ TEST_F(WebIntentPickerSheetControllerTest, DontCancelAfterServiceInvokation) { ignore_result(picker_.release()); // Closing |picker_| will destruct it. } + +TEST_F(WebIntentPickerSheetControllerTest, SuggestionView) { + CreateBubble(); + + WebIntentPickerModel model; + + model.AddSuggestedExtension(string16(), string16(), 2.5); + [controller_ performLayoutWithModel:&model]; + + // Get subviews. + NSArray* flip_views = [[window_ contentView] subviews]; + NSArray* main_views = [[flip_views objectAtIndex:0] subviews]; + + // 4th object should be the suggestion view. + ASSERT_TRUE([main_views count] > 3); + ASSERT_TRUE([[main_views objectAtIndex:3] isKindOfClass:[NSView class]]); + NSView* suggest_view = [main_views objectAtIndex:3]; + + // There is exactly one subview, which contains the suggested item. + ASSERT_EQ(1U, [[suggest_view subviews] count]); + ASSERT_TRUE([[[suggest_view subviews] objectAtIndex:0] + isKindOfClass:[NSView class]]); + NSView* item_view = [[suggest_view subviews] objectAtIndex:0]; + + // 8 subobject - Icon, title, star rating (5 objects), add button. + ASSERT_EQ(8U, [[item_view subviews] count]); + + // Verify title button is hooked up properly + ASSERT_TRUE([[[item_view subviews] objectAtIndex:1] + isKindOfClass:[NSButton class]]); + NSButton* title_button = [[item_view subviews] objectAtIndex:1]; + CheckButton(title_button, @selector(openExtensionLink:)); + + ASSERT_TRUE([[[item_view subviews] objectAtIndex:7] + isKindOfClass:[NSButton class]]); + NSButton* add_button = [[item_view subviews] objectAtIndex:7]; + CheckButton(add_button, @selector(invokeService:)); +} |