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 |