OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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/web_intent_bubble_controller.h" | 5 #import "chrome/browser/ui/cocoa/web_intent_bubble_controller.h" |
6 | 6 |
7 #include "base/memory/scoped_nsobject.h" | 7 #include "base/memory/scoped_nsobject.h" |
8 #include "base/sys_string_conversions.h" | |
8 #include "chrome/browser/ui/browser_list.h" | 9 #include "chrome/browser/ui/browser_list.h" |
9 #import "chrome/browser/ui/cocoa/hyperlink_button_cell.h" | 10 #import "chrome/browser/ui/cocoa/hyperlink_button_cell.h" |
10 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | 11 #import "chrome/browser/ui/cocoa/info_bubble_view.h" |
11 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | 12 #import "chrome/browser/ui/cocoa/info_bubble_window.h" |
12 #include "chrome/browser/ui/cocoa/web_intent_picker_cocoa.h" | 13 #include "chrome/browser/ui/cocoa/web_intent_picker_cocoa.h" |
13 #include "chrome/browser/ui/intents/web_intent_picker_delegate.h" | 14 #include "chrome/browser/ui/intents/web_intent_picker_delegate.h" |
15 #include "chrome/browser/ui/intents/web_intent_picker_model.h" | |
14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | 16 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
15 #include "content/public/browser/web_contents_view.h" | 17 #include "content/public/browser/web_contents_view.h" |
16 #include "content/public/browser/web_contents.h" | 18 #include "content/public/browser/web_contents.h" |
17 #include "grit/generated_resources.h" | 19 #include "grit/generated_resources.h" |
18 #include "grit/locale_settings.h" | 20 #include "grit/locale_settings.h" |
19 #include "grit/theme_resources.h" | 21 #include "grit/theme_resources.h" |
20 #include "grit/ui_resources.h" | 22 #include "grit/ui_resources.h" |
21 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | 23 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
22 #include "ui/base/l10n/l10n_util.h" | 24 #include "ui/base/l10n/l10n_util.h" |
23 #include "ui/base/l10n/l10n_util_mac.h" | 25 #include "ui/base/l10n/l10n_util_mac.h" |
24 #include "ui/base/resource/resource_bundle.h" | 26 #include "ui/base/resource/resource_bundle.h" |
25 #include "ui/gfx/image/image.h" | 27 #include "ui/gfx/image/image.h" |
26 | 28 |
27 using content::OpenURLParams; | 29 using content::OpenURLParams; |
28 using content::Referrer; | 30 using content::Referrer; |
29 | 31 |
30 namespace { | 32 namespace { |
31 | 33 |
32 // The width of the window, in view coordinates. The height will be | 34 // The width of the window, in view coordinates. The height will be |
33 // determined by the content. | 35 // determined by the content. |
34 const CGFloat kWindowWidth = 380; | 36 const CGFloat kWindowWidth = 380; |
35 | 37 |
38 // The width of a service button, in view coordinates. | |
39 const CGFloat kServiceButtonWidth = 300; | |
Nico
2012/02/03 23:27:07
Does this localize?
groby-ooo-7-16
2012/02/06 22:30:19
No, but neither do the strings in that UI for now.
| |
40 | |
36 // Padding along on the X-axis between the window frame and content. | 41 // Padding along on the X-axis between the window frame and content. |
37 const CGFloat kFramePadding = 10; | 42 const CGFloat kFramePadding = 10; |
38 | 43 |
39 // Spacing in between sections. | 44 // Spacing in between sections. |
40 const CGFloat kVerticalSpacing = 10; | 45 const CGFloat kVerticalSpacing = 10; |
41 | 46 |
42 // Square size of the image. | 47 // Square size of the image. |
43 const CGFloat kImageSize = 32; | 48 const CGFloat kImageSize = 32; |
44 | 49 |
45 // Spacing between the image and the text. | 50 // Spacing between the image and the text. |
46 const CGFloat kImageSpacing = 10; | 51 const CGFloat kImageSpacing = 10; |
47 | 52 |
48 // Spacing between icons. | 53 // Spacing between icons. |
49 const CGFloat kImagePadding = 6; | 54 const CGFloat kImagePadding = 6; |
50 | 55 |
51 // Width of the text fields. | 56 // Width of the text fields. |
52 const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + | 57 const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + |
53 kFramePadding * 2); | 58 kFramePadding * 2); |
54 | 59 |
55 } // namespace | 60 } // namespace |
56 | 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 | |
57 @implementation WebIntentBubbleController; | 74 @implementation WebIntentBubbleController; |
58 | 75 |
59 - (id)initWithPicker:(WebIntentPickerCocoa*)picker | 76 - (id)initWithPicker:(WebIntentPickerCocoa*)picker |
60 parentWindow:(NSWindow*)parent | 77 parentWindow:(NSWindow*)parent |
61 anchoredAt:(NSPoint)point { | 78 anchoredAt:(NSPoint)point { |
62 // Use an arbitrary height because it will reflect the size of the content. | 79 // Use an arbitrary height because it will reflect the size of the content. |
63 NSRect contentRect = NSMakeRect(0, 0, kWindowWidth, kVerticalSpacing); | 80 NSRect contentRect = NSMakeRect(0, 0, kWindowWidth, kVerticalSpacing); |
64 // Create an empty window into which content is placed. | 81 // Create an empty window into which content is placed. |
65 scoped_nsobject<InfoBubbleWindow> window( | 82 scoped_nsobject<InfoBubbleWindow> window( |
66 [[InfoBubbleWindow alloc] initWithContentRect:contentRect | 83 [[InfoBubbleWindow alloc] initWithContentRect:contentRect |
67 styleMask:NSBorderlessWindowMask | 84 styleMask:NSBorderlessWindowMask |
68 backing:NSBackingStoreBuffered | 85 backing:NSBackingStoreBuffered |
69 defer:NO]); | 86 defer:NO]); |
70 if ((self = [super initWithWindow:window.get() | 87 if ((self = [super initWithWindow:window.get() |
71 parentWindow:parent | 88 parentWindow:parent |
72 anchoredAt:point])) { | 89 anchoredAt:point])) { |
73 picker_ = picker; | 90 picker_ = picker; |
74 iconImages_.reset([[NSPointerArray alloc] initWithOptions: | |
75 NSPointerFunctionsStrongMemory | | |
76 NSPointerFunctionsObjectPersonality]); | |
77 | 91 |
78 [[self bubble] setArrowLocation:info_bubble::kTopLeft]; | 92 [[self bubble] setArrowLocation:info_bubble::kTopLeft]; |
79 [self performLayout]; | 93 [self performLayoutWithModel:NULL]; |
80 [self showWindow:nil]; | 94 [self showWindow:nil]; |
81 picker_->set_controller(self); | 95 picker_->set_controller(self); |
82 } | 96 } |
83 | 97 |
84 return self; | 98 return self; |
85 } | 99 } |
86 | 100 |
87 - (void)setServiceURLs:(NSArray*)urls { | |
88 serviceURLs_.reset([urls retain]); | |
89 | |
90 if ([iconImages_ count] < [serviceURLs_ count]) | |
91 [iconImages_ setCount:[serviceURLs_ count]]; | |
92 | |
93 [self performLayout]; | |
94 } | |
95 | |
96 - (void)setInlineDispositionTabContents:(TabContentsWrapper*)wrapper { | 101 - (void)setInlineDispositionTabContents:(TabContentsWrapper*)wrapper { |
97 contents_ = wrapper; | 102 contents_ = wrapper; |
98 [self performLayout]; | |
99 } | |
100 | |
101 - (void)replaceImageAtIndex:(size_t)index withImage:(NSImage*)image { | |
102 if ([iconImages_ count] <= index) | |
103 [iconImages_ setCount:index + 1]; | |
104 | |
105 [iconImages_ replacePointerAtIndex:index withPointer:image]; | |
106 [self performLayout]; | |
107 } | 103 } |
108 | 104 |
109 // We need to watch for window closing so we can notify up via |picker_|. | 105 // We need to watch for window closing so we can notify up via |picker_|. |
110 - (void)windowWillClose:(NSNotification*)notification { | 106 - (void)windowWillClose:(NSNotification*)notification { |
111 if (picker_) { | 107 if (picker_) { |
112 WebIntentPickerCocoa* temp = picker_; | 108 WebIntentPickerCocoa* temp = picker_; |
113 picker_ = NULL; // Abandon picker, we are done with it. | 109 picker_ = NULL; // Abandon picker, we are done with it. |
114 temp->OnCancelled(); | 110 temp->OnCancelled(); |
115 } | 111 } |
116 [super windowWillClose:notification]; | 112 [super windowWillClose:notification]; |
117 } | 113 } |
118 | 114 |
119 // Pop up a new tab with the Chrome Web Store. | 115 // Pop up a new tab with the Chrome Web Store. |
120 - (IBAction)showChromeWebStore:(id)sender { | 116 - (IBAction)showChromeWebStore:(id)sender { |
121 GURL url(l10n_util::GetStringUTF8(IDS_WEBSTORE_URL)); | 117 GURL url(l10n_util::GetStringUTF8(IDS_WEBSTORE_URL)); |
122 Browser* browser = BrowserList::GetLastActive(); | 118 Browser* browser = BrowserList::GetLastActive(); |
123 OpenURLParams params( | 119 OpenURLParams params( |
124 url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, | 120 url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, |
125 false); | 121 false); |
126 browser->OpenURL(params); | 122 browser->OpenURL(params); |
127 } | 123 } |
128 | 124 |
129 // A picker button has been pressed - invoke corresponding service. | 125 // A picker button has been pressed - invoke corresponding service. |
130 - (IBAction)invokeService:(id)sender { | 126 - (IBAction)invokeService:(id)sender { |
131 if (picker_) { | 127 if (picker_) { |
132 WebIntentPickerCocoa* temp = picker_; | 128 WebIntentPickerCocoa* temp = picker_; |
133 picker_ = NULL; // Abandon picker, we are done with it. | 129 picker_ = NULL; // Abandon picker, we are done with it. |
134 temp->OnServiceChosen([[sender selectedCell] tag]); | 130 temp->OnServiceChosen([sender tag]); |
135 } | 131 } |
136 } | 132 } |
137 | 133 |
138 // Sets proprties on the given |field| to act as the title or description labels | 134 // Sets proprties on the given |field| to act as the title or description labels |
139 // in the bubble. | 135 // in the bubble. |
140 - (void)configureTextFieldAsLabel:(NSTextField*)field { | 136 - (void)configureTextFieldAsLabel:(NSTextField*)field { |
141 [field setEditable:NO]; | 137 [field setEditable:NO]; |
142 [field setSelectable:YES]; | 138 [field setSelectable:YES]; |
143 [field setDrawsBackground:NO]; | 139 [field setDrawsBackground:NO]; |
144 [field setBezeled:NO]; | 140 [field setBezeled:NO]; |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
202 else | 198 else |
203 imageFrame.origin.y += maxHeight / 2; | 199 imageFrame.origin.y += maxHeight / 2; |
204 [textField setFrame:textFrame]; | 200 [textField setFrame:textFrame]; |
205 [imageView setFrame:imageFrame]; | 201 [imageView setFrame:imageFrame]; |
206 | 202 |
207 [subviews addObject:textField.get()]; | 203 [subviews addObject:textField.get()]; |
208 [subviews addObject:imageView.get()]; | 204 [subviews addObject:imageView.get()]; |
209 return NSHeight([imageView frame]); | 205 return NSHeight([imageView frame]); |
210 } | 206 } |
211 | 207 |
212 // Add all service icons to picker UI. | |
213 // Returns the y position delta for the next offset. | |
214 - (CGFloat)addIcons:(NSPointerArray*)icons | |
215 toSubviews:(NSMutableArray*)subviews | |
216 atOffset:(CGFloat)offset { | |
217 CGFloat matrixOffset = kFramePadding + kImageSize + kImagePadding; | |
218 CGFloat matrixWidth = kWindowWidth - matrixOffset - kFramePadding; | |
219 | |
220 CGFloat iconWidth = kImageSize + kImagePadding; | |
221 NSInteger iconsPerRow = matrixWidth / iconWidth; | |
222 NSInteger numRows = ([icons count] / iconsPerRow) + 1; | |
223 | |
224 NSRect frame = NSMakeRect(matrixOffset, offset, matrixWidth, | |
225 (kImageSize + kImagePadding) * numRows); | |
226 | |
227 scoped_nsobject<NSMatrix> matrix( | |
228 [[NSMatrix alloc] initWithFrame:frame | |
229 mode:NSHighlightModeMatrix | |
230 cellClass:[NSImageCell class] | |
231 numberOfRows:numRows | |
232 numberOfColumns:iconsPerRow]); | |
233 | |
234 [matrix setCellSize:NSMakeSize(kImageSize,kImageSize)]; | |
235 [matrix setIntercellSpacing:NSMakeSize(kImagePadding,kImagePadding)]; | |
236 | |
237 for (NSUInteger i = 0; i < [iconImages_ count]; ++i) { | |
238 scoped_nsobject<NSButtonCell> cell([[NSButtonCell alloc] init]); | |
239 NSImage* image = static_cast<NSImage*>([iconImages_ pointerAtIndex:i]); | |
240 if (!image) | |
241 continue; | |
242 | |
243 // Set cell styles so it acts as image button. | |
244 [cell setBordered:NO]; | |
245 [cell setImage:image]; | |
246 [cell setImagePosition:NSImageOnly]; | |
247 [cell setButtonType:NSMomentaryChangeButton]; | |
248 [cell setTarget:self]; | |
249 [cell setAction:@selector(invokeService:)]; | |
250 [cell setTag:i]; | |
251 [cell setEnabled:YES]; | |
252 | |
253 [matrix putCell:cell atRow:(i / iconsPerRow) column:(i % iconsPerRow)]; | |
254 if (serviceURLs_ && i < [serviceURLs_ count]) | |
255 [matrix setToolTip:[serviceURLs_ objectAtIndex:i] forCell:cell]; | |
256 } | |
257 | |
258 [subviews addObject:matrix]; | |
259 return NSHeight([matrix frame]); | |
260 } | |
261 | |
262 - (CGFloat)addInlineHtmlToSubviews:(NSMutableArray*)subviews | 208 - (CGFloat)addInlineHtmlToSubviews:(NSMutableArray*)subviews |
263 atOffset:(CGFloat)offset { | 209 atOffset:(CGFloat)offset { |
264 if (!contents_) | 210 if (!contents_) |
265 return 0; | 211 return 0; |
266 | 212 |
267 // Determine a good size for the inline disposition window. | 213 // Determine a good size for the inline disposition window. |
268 gfx::Size tab_size = contents_->web_contents()->GetView()->GetContainerSize(); | 214 gfx::Size tab_size = contents_->web_contents()->GetView()->GetContainerSize(); |
269 CGFloat width = std::max(CGFloat(tab_size.width()/2.0), kWindowWidth); | 215 CGFloat width = std::max(CGFloat(tab_size.width()/2.0), kWindowWidth); |
270 CGFloat height = std::max(CGFloat(tab_size.height()/2.0), kWindowWidth); | 216 CGFloat height = std::max(CGFloat(tab_size.height()/2.0), kWindowWidth); |
271 NSRect frame = NSMakeRect(kFramePadding, offset, width, height); | 217 NSRect frame = NSMakeRect(kFramePadding, offset, width, height); |
272 | 218 |
273 [contents_->web_contents()->GetNativeView() setFrame:frame]; | 219 [contents_->web_contents()->GetNativeView() setFrame:frame]; |
274 [subviews addObject:contents_->web_contents()->GetNativeView()]; | 220 [subviews addObject:contents_->web_contents()->GetNativeView()]; |
275 | 221 |
276 return NSHeight(frame); | 222 return NSHeight(frame); |
277 } | 223 } |
278 | 224 |
225 // Add a single button for a specific service | |
226 -(CGFloat)addServiceButton:(NSString*)title | |
227 withImage:(NSImage*)image | |
228 index:(NSUInteger)index | |
229 toSubviews:(NSMutableArray*)subviews | |
230 atOffset:(CGFloat)offset { | |
Nico
2012/02/03 23:27:07
yOffset:? Makes this sound less like an index too.
groby-ooo-7-16
2012/02/06 22:30:19
It would be inconsistent with all other bubble con
| |
231 NSRect frame = NSMakeRect(kFramePadding, offset, kServiceButtonWidth, 45); | |
232 scoped_nsobject<NSButton> button([[NSButton alloc] initWithFrame:frame]); | |
233 | |
234 if (image) { | |
235 [button setImage:image]; | |
236 [button setImagePosition:NSImageLeft]; | |
237 } | |
238 [button setButtonType:NSMomentaryPushInButton]; | |
239 [button setBezelStyle:NSRegularSquareBezelStyle]; | |
240 [button setTarget:self]; | |
241 [button setTitle:title]; | |
242 [button setTag:index]; | |
243 [button setAction:@selector(invokeService:)]; | |
244 [subviews addObject:button.get()]; | |
245 | |
246 // Call size-to-fit to fixup size. | |
247 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button.get()]; | |
248 | |
249 // But make sure we're limited to a fixed size. | |
250 frame = [button frame]; | |
251 frame.size.width = kServiceButtonWidth; | |
252 [button setFrame:frame]; | |
253 | |
254 return NSHeight([button frame]); | |
255 } | |
256 | |
279 // Layout the contents of the picker bubble. | 257 // Layout the contents of the picker bubble. |
280 - (void)performLayout { | 258 - (void)performLayoutWithModel:(WebIntentPickerModel*)model { |
281 // |offset| is the Y position that should be drawn at next. | 259 // |offset| is the Y position that should be drawn at next. |
282 CGFloat offset = kFramePadding + info_bubble::kBubbleArrowHeight; | 260 CGFloat offset = kFramePadding + info_bubble::kBubbleArrowHeight; |
283 | 261 |
284 // Keep the new subviews in an array that gets replaced at the end. | 262 // Keep the new subviews in an array that gets replaced at the end. |
285 NSMutableArray* subviews = [NSMutableArray array]; | 263 NSMutableArray* subviews = [NSMutableArray array]; |
286 | 264 |
287 if (contents_) { | 265 if (contents_) { |
288 offset += [self addInlineHtmlToSubviews:subviews atOffset:offset]; | 266 offset += [self addInlineHtmlToSubviews:subviews atOffset:offset]; |
289 } else { | 267 } else { |
268 offset += [self addHeaderToSubviews:subviews atOffset:offset]; | |
269 if (model) { | |
270 for (NSUInteger i = 0; i < model->GetItemCount(); ++i) { | |
271 const WebIntentPickerModel::Item& item = model->GetItemAt(i); | |
272 offset += [self addServiceButton:base::SysUTF16ToNSString(item.title) | |
273 withImage:item.favicon.ToNSImage() | |
274 index:i | |
275 toSubviews:subviews | |
276 atOffset:offset]; | |
277 } | |
278 } | |
290 offset += [self addCwsButtonToSubviews:subviews atOffset:offset]; | 279 offset += [self addCwsButtonToSubviews:subviews atOffset:offset]; |
291 offset += [self addIcons:iconImages_ toSubviews:subviews atOffset:offset]; | |
292 offset += [self addHeaderToSubviews:subviews atOffset:offset]; | |
293 } | 280 } |
294 | 281 |
295 // Add the bottom padding. | 282 // Add the bottom padding. |
296 offset += kVerticalSpacing; | 283 offset += kVerticalSpacing; |
297 | 284 |
285 // Create the dummy view that uses flipped coordinates. | |
Nico
2012/02/03 23:27:07
nit: add 1 indent to comment
groby-ooo-7-16
2012/02/06 22:30:19
Done.
| |
286 NSRect contentFrame = NSMakeRect(0, 0, kWindowWidth, offset); | |
287 scoped_nsobject<WebIntentsContentView> contentView( | |
288 [[WebIntentsContentView alloc] initWithFrame:contentFrame]); | |
289 [contentView setSubviews:subviews]; | |
290 [contentView setAutoresizingMask:NSViewMinYMargin]; | |
291 | |
298 // Adjust frame to fit all elements. | 292 // Adjust frame to fit all elements. |
299 NSRect windowFrame = NSMakeRect(0, 0, kWindowWidth, offset); | 293 NSRect windowFrame = NSMakeRect(0, 0, kWindowWidth, offset); |
300 windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size | 294 windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size |
301 toView:nil]; | 295 toView:nil]; |
302 // Adjust the origin by the difference in height. | 296 // Adjust the origin by the difference in height. |
303 windowFrame.origin = [[self window] frame].origin; | 297 windowFrame.origin = [[self window] frame].origin; |
304 windowFrame.origin.y -= NSHeight(windowFrame) - | 298 windowFrame.origin.y -= NSHeight(windowFrame) - |
305 NSHeight([[self window] frame]); | 299 NSHeight([[self window] frame]); |
306 | 300 |
307 [[self window] setFrame:windowFrame display:YES animate:YES]; | 301 [[self window] setFrame:windowFrame display:YES animate:YES]; |
308 | 302 |
309 // Replace the window's content. | 303 // Replace the window's content. |
310 [[[self window] contentView] setSubviews:subviews]; | 304 [[[self window] contentView] setSubviews: |
305 [NSArray arrayWithObject:contentView]]; | |
311 } | 306 } |
312 | 307 |
313 @end // WebIntentBubbleController | 308 @end // WebIntentBubbleController |
OLD | NEW |