| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #import "chrome/browser/ui/cocoa/web_intent_bubble_controller.h" | |
| 6 | |
| 7 #include "base/memory/scoped_nsobject.h" | |
| 8 #include "base/sys_string_conversions.h" | |
| 9 #include "chrome/browser/ui/browser_list.h" | |
| 10 #import "chrome/browser/ui/cocoa/hyperlink_button_cell.h" | |
| 11 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
| 12 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
| 13 #include "chrome/browser/ui/cocoa/web_intent_picker_cocoa.h" | |
| 14 #include "chrome/browser/ui/intents/web_intent_picker_delegate.h" | |
| 15 #include "chrome/browser/ui/intents/web_intent_picker_model.h" | |
| 16 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 17 #include "content/public/browser/web_contents_view.h" | |
| 18 #include "content/public/browser/web_contents.h" | |
| 19 #include "grit/generated_resources.h" | |
| 20 #include "grit/locale_settings.h" | |
| 21 #include "grit/theme_resources.h" | |
| 22 #include "grit/ui_resources.h" | |
| 23 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | |
| 24 #include "ui/base/l10n/l10n_util.h" | |
| 25 #include "ui/base/l10n/l10n_util_mac.h" | |
| 26 #include "ui/base/resource/resource_bundle.h" | |
| 27 #include "ui/gfx/image/image.h" | |
| 28 | |
| 29 using content::OpenURLParams; | |
| 30 using content::Referrer; | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // The width of the window, in view coordinates. The height will be | |
| 35 // determined by the content. | |
| 36 const CGFloat kWindowWidth = 380; | |
| 37 | |
| 38 // The width of a service button, in view coordinates. | |
| 39 const CGFloat kServiceButtonWidth = 300; | |
| 40 | |
| 41 // Padding along on the X-axis between the window frame and content. | |
| 42 const CGFloat kFramePadding = 10; | |
| 43 | |
| 44 // Spacing in between sections. | |
| 45 const CGFloat kVerticalSpacing = 10; | |
| 46 | |
| 47 // Square size of the image. | |
| 48 const CGFloat kImageSize = 32; | |
| 49 | |
| 50 // Spacing between the image and the text. | |
| 51 const CGFloat kImageSpacing = 10; | |
| 52 | |
| 53 // Spacing between icons. | |
| 54 const CGFloat kImagePadding = 6; | |
| 55 | |
| 56 // Width of the text fields. | |
| 57 const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + | |
| 58 kFramePadding * 2); | |
| 59 | |
| 60 } // namespace | |
| 61 | |
| 62 // This simple NSView subclass is used as the single subview of the page info | |
| 63 // bubble's window's contentView. Drawing is flipped so that layout of the | |
| 64 // sections is easier. Apple recommends flipping the coordinate origin when | |
| 65 // doing a lot of text layout because it's more natural. | |
| 66 @interface WebIntentsContentView : NSView | |
| 67 @end | |
| 68 @implementation WebIntentsContentView | |
| 69 - (BOOL)isFlipped { | |
| 70 return YES; | |
| 71 } | |
| 72 @end | |
| 73 | |
| 74 @implementation WebIntentBubbleController; | |
| 75 | |
| 76 - (id)initWithPicker:(WebIntentPickerCocoa*)picker | |
| 77 parentWindow:(NSWindow*)parent | |
| 78 anchoredAt:(NSPoint)point { | |
| 79 // Use an arbitrary height because it will reflect the size of the content. | |
| 80 NSRect contentRect = NSMakeRect(0, 0, kWindowWidth, kVerticalSpacing); | |
| 81 // Create an empty window into which content is placed. | |
| 82 scoped_nsobject<InfoBubbleWindow> window( | |
| 83 [[InfoBubbleWindow alloc] initWithContentRect:contentRect | |
| 84 styleMask:NSBorderlessWindowMask | |
| 85 backing:NSBackingStoreBuffered | |
| 86 defer:NO]); | |
| 87 if ((self = [super initWithWindow:window.get() | |
| 88 parentWindow:parent | |
| 89 anchoredAt:point])) { | |
| 90 picker_ = picker; | |
| 91 | |
| 92 [[self bubble] setArrowLocation:info_bubble::kTopLeft]; | |
| 93 [self performLayoutWithModel:NULL]; | |
| 94 [self showWindow:nil]; | |
| 95 picker_->set_controller(self); | |
| 96 } | |
| 97 | |
| 98 return self; | |
| 99 } | |
| 100 | |
| 101 - (void)setInlineDispositionTabContents:(TabContentsWrapper*)wrapper { | |
| 102 contents_ = wrapper; | |
| 103 } | |
| 104 | |
| 105 // We need to watch for window closing so we can notify up via |picker_|. | |
| 106 - (void)windowWillClose:(NSNotification*)notification { | |
| 107 if (picker_) { | |
| 108 WebIntentPickerCocoa* temp = picker_; | |
| 109 picker_ = NULL; // Abandon picker, we are done with it. | |
| 110 temp->OnCancelled(); | |
| 111 } | |
| 112 [super windowWillClose:notification]; | |
| 113 } | |
| 114 | |
| 115 // Pop up a new tab with the Chrome Web Store. | |
| 116 - (IBAction)showChromeWebStore:(id)sender { | |
| 117 GURL url(l10n_util::GetStringUTF8(IDS_WEBSTORE_URL)); | |
| 118 Browser* browser = BrowserList::GetLastActive(); | |
| 119 OpenURLParams params( | |
| 120 url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, | |
| 121 false); | |
| 122 browser->OpenURL(params); | |
| 123 } | |
| 124 | |
| 125 // A picker button has been pressed - invoke corresponding service. | |
| 126 - (IBAction)invokeService:(id)sender { | |
| 127 if (picker_) | |
| 128 picker_->OnServiceChosen([sender tag]); | |
| 129 } | |
| 130 | |
| 131 // Sets proprties on the given |field| to act as the title or description labels | |
| 132 // in the bubble. | |
| 133 - (void)configureTextFieldAsLabel:(NSTextField*)field { | |
| 134 [field setEditable:NO]; | |
| 135 [field setSelectable:YES]; | |
| 136 [field setDrawsBackground:NO]; | |
| 137 [field setBezeled:NO]; | |
| 138 } | |
| 139 | |
| 140 // Adds a link to the Chrome Web Store, to obtain further intent handlers. | |
| 141 // Returns the y position delta for the next offset. | |
| 142 - (CGFloat)addCwsButtonToSubviews:(NSMutableArray*)subviews | |
| 143 atOffset:(CGFloat)offset { | |
| 144 NSRect frame = NSMakeRect(kFramePadding, offset, 100, 10); | |
| 145 scoped_nsobject<NSButton> button([[NSButton alloc] initWithFrame:frame]); | |
| 146 NSString* string = | |
| 147 l10n_util::GetNSStringWithFixup(IDS_FIND_MORE_INTENT_HANDLER_MESSAGE); | |
| 148 scoped_nsobject<HyperlinkButtonCell> cell( | |
| 149 [[HyperlinkButtonCell alloc] initTextCell:string]); | |
| 150 [cell setControlSize:NSSmallControlSize]; | |
| 151 [button setCell:cell.get()]; | |
| 152 [button setButtonType:NSMomentaryPushInButton]; | |
| 153 [button setBezelStyle:NSRegularSquareBezelStyle]; | |
| 154 [button setTarget:self]; | |
| 155 [button setAction:@selector(showChromeWebStore:)]; | |
| 156 [subviews addObject:button.get()]; | |
| 157 | |
| 158 // Call size-to-fit to fixup for the localized string. | |
| 159 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()]; | |
| 160 | |
| 161 return NSHeight([button frame]); | |
| 162 } | |
| 163 | |
| 164 // Adds a header (icon and explanatory text) to picker bubble. | |
| 165 // Returns the y position delta for the next offset. | |
| 166 - (CGFloat)addHeaderToSubviews:(NSMutableArray*)subviews | |
| 167 atOffset:(CGFloat)offset { | |
| 168 NSRect imageFrame = NSMakeRect(kFramePadding, offset, kImageSize, kImageSize); | |
| 169 | |
| 170 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 171 NSImage* nsImage = rb.GetNativeImageNamed(IDR_PAGEINFO_INFO); | |
| 172 | |
| 173 scoped_nsobject<NSImageView> imageView( | |
| 174 [[NSImageView alloc] initWithFrame:imageFrame]); | |
| 175 [imageView setImage:nsImage]; | |
| 176 [imageView setImageFrameStyle:NSImageFrameNone]; | |
| 177 | |
| 178 NSRect textFrame = NSMakeRect(kFramePadding + kImageSize + kImageSpacing, | |
| 179 offset, kTextWidth, 1); | |
| 180 scoped_nsobject<NSTextField> textField( | |
| 181 [[NSTextField alloc] initWithFrame:textFrame]); | |
| 182 [self configureTextFieldAsLabel:textField.get()]; | |
| 183 | |
| 184 NSString* nsString = | |
| 185 l10n_util::GetNSStringWithFixup(IDS_CHOOSE_INTENT_HANDLER_MESSAGE); | |
| 186 [textField setStringValue:nsString]; | |
| 187 textFrame.size.height += | |
| 188 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: | |
| 189 textField]; | |
| 190 | |
| 191 // Adjust view height to fit elements, center-align elements. | |
| 192 CGFloat maxHeight = std::max(imageFrame.size.height,textFrame.size.height); | |
| 193 if (maxHeight > textFrame.size.height) | |
| 194 textFrame.origin.y += (maxHeight - textFrame.size.height) / 2; | |
| 195 else | |
| 196 imageFrame.origin.y += maxHeight / 2; | |
| 197 [textField setFrame:textFrame]; | |
| 198 [imageView setFrame:imageFrame]; | |
| 199 | |
| 200 [subviews addObject:textField.get()]; | |
| 201 [subviews addObject:imageView.get()]; | |
| 202 return NSHeight([imageView frame]); | |
| 203 } | |
| 204 | |
| 205 - (CGFloat)addInlineHtmlToSubviews:(NSMutableArray*)subviews | |
| 206 atOffset:(CGFloat)offset { | |
| 207 if (!contents_) | |
| 208 return 0; | |
| 209 | |
| 210 // Determine a good size for the inline disposition window. | |
| 211 gfx::Size size = WebIntentPicker::GetDefaultInlineDispositionSize( | |
| 212 contents_->web_contents()); | |
| 213 NSRect frame = NSMakeRect(kFramePadding, offset, size.width(), size.height()); | |
| 214 | |
| 215 [contents_->web_contents()->GetNativeView() setFrame:frame]; | |
| 216 [subviews addObject:contents_->web_contents()->GetNativeView()]; | |
| 217 | |
| 218 return NSHeight(frame); | |
| 219 } | |
| 220 | |
| 221 // Add a single button for a specific service | |
| 222 -(CGFloat)addServiceButton:(NSString*)title | |
| 223 withImage:(NSImage*)image | |
| 224 index:(NSUInteger)index | |
| 225 toSubviews:(NSMutableArray*)subviews | |
| 226 atOffset:(CGFloat)offset { | |
| 227 NSRect frame = NSMakeRect(kFramePadding, offset, kServiceButtonWidth, 45); | |
| 228 scoped_nsobject<NSButton> button([[NSButton alloc] initWithFrame:frame]); | |
| 229 | |
| 230 if (image) { | |
| 231 [button setImage:image]; | |
| 232 [button setImagePosition:NSImageLeft]; | |
| 233 } | |
| 234 [button setButtonType:NSMomentaryPushInButton]; | |
| 235 [button setBezelStyle:NSRegularSquareBezelStyle]; | |
| 236 [button setTarget:self]; | |
| 237 [button setTitle:title]; | |
| 238 [button setTag:index]; | |
| 239 [button setAction:@selector(invokeService:)]; | |
| 240 [subviews addObject:button.get()]; | |
| 241 | |
| 242 // Call size-to-fit to fixup size. | |
| 243 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()]; | |
| 244 | |
| 245 // But make sure we're limited to a fixed size. | |
| 246 frame = [button frame]; | |
| 247 frame.size.width = kServiceButtonWidth; | |
| 248 [button setFrame:frame]; | |
| 249 | |
| 250 return NSHeight([button frame]); | |
| 251 } | |
| 252 | |
| 253 // Layout the contents of the picker bubble. | |
| 254 - (void)performLayoutWithModel:(WebIntentPickerModel*)model { | |
| 255 // |offset| is the Y position that should be drawn at next. | |
| 256 CGFloat offset = kFramePadding + info_bubble::kBubbleArrowHeight; | |
| 257 | |
| 258 // Keep the new subviews in an array that gets replaced at the end. | |
| 259 NSMutableArray* subviews = [NSMutableArray array]; | |
| 260 | |
| 261 if (contents_) { | |
| 262 offset += [self addInlineHtmlToSubviews:subviews atOffset:offset]; | |
| 263 } else { | |
| 264 offset += [self addHeaderToSubviews:subviews atOffset:offset]; | |
| 265 if (model) { | |
| 266 for (NSUInteger i = 0; i < model->GetInstalledServiceCount(); ++i) { | |
| 267 const WebIntentPickerModel::InstalledService& service = | |
| 268 model->GetInstalledServiceAt(i); | |
| 269 offset += [self addServiceButton:base::SysUTF16ToNSString(service.title) | |
| 270 withImage:service.favicon.ToNSImage() | |
| 271 index:i | |
| 272 toSubviews:subviews | |
| 273 atOffset:offset]; | |
| 274 } | |
| 275 } | |
| 276 offset += [self addCwsButtonToSubviews:subviews atOffset:offset]; | |
| 277 } | |
| 278 | |
| 279 // Add the bottom padding. | |
| 280 offset += kVerticalSpacing; | |
| 281 | |
| 282 // Create the dummy view that uses flipped coordinates. | |
| 283 NSRect contentFrame = NSMakeRect(0, 0, kWindowWidth, offset); | |
| 284 scoped_nsobject<WebIntentsContentView> contentView( | |
| 285 [[WebIntentsContentView alloc] initWithFrame:contentFrame]); | |
| 286 [contentView setSubviews:subviews]; | |
| 287 [contentView setAutoresizingMask:NSViewMinYMargin]; | |
| 288 | |
| 289 // Adjust frame to fit all elements. | |
| 290 NSRect windowFrame = NSMakeRect(0, 0, kWindowWidth, offset); | |
| 291 windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size | |
| 292 toView:nil]; | |
| 293 // Adjust the origin by the difference in height. | |
| 294 windowFrame.origin = [[self window] frame].origin; | |
| 295 windowFrame.origin.y -= NSHeight(windowFrame) - | |
| 296 NSHeight([[self window] frame]); | |
| 297 | |
| 298 [[self window] setFrame:windowFrame display:YES animate:YES]; | |
| 299 | |
| 300 // Replace the window's content. | |
| 301 [[[self window] contentView] setSubviews: | |
| 302 [NSArray arrayWithObject:contentView]]; | |
| 303 } | |
| 304 | |
| 305 @end // WebIntentBubbleController | |
| OLD | NEW |