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_sheet_controller.h" | |
6 | |
7 #include "base/memory/scoped_nsobject.h" | |
8 #include "base/sys_string_conversions.h" | |
9 #include "base/utf_string_conversions.h" | |
10 #include "chrome/browser/ui/browser_list.h" | |
11 #import "chrome/browser/ui/cocoa/event_utils.h" | |
12 #import "chrome/browser/ui/cocoa/hover_close_button.h" | |
13 #import "chrome/browser/ui/cocoa/hyperlink_button_cell.h" | |
14 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
15 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
16 #include "chrome/browser/ui/cocoa/web_intent_picker_cocoa.h" | |
17 #include "chrome/browser/ui/constrained_window.h" | |
18 #include "chrome/browser/ui/constrained_window_constants.h" | |
19 #include "chrome/browser/ui/intents/web_intent_picker_delegate.h" | |
20 #include "chrome/browser/ui/intents/web_intent_picker_model.h" | |
21 #import "chrome/browser/ui/cocoa/tabs/throbber_view.h" | |
22 #include "content/public/browser/web_contents.h" | |
23 #include "content/public/browser/web_contents_view.h" | |
24 #include "grit/generated_resources.h" | |
25 #include "grit/google_chrome_strings.h" | |
26 #include "grit/locale_settings.h" | |
27 #include "grit/theme_resources.h" | |
28 #include "grit/ui_resources.h" | |
29 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | |
30 #include "ui/base/l10n/l10n_util.h" | |
31 #include "ui/base/l10n/l10n_util_mac.h" | |
32 #include "ui/base/resource/resource_bundle.h" | |
33 #include "ui/base/text/text_elider.h" | |
34 #include "ui/gfx/font.h" | |
35 #include "ui/gfx/image/image.h" | |
36 | |
37 using content::OpenURLParams; | |
38 using content::Referrer; | |
39 | |
40 @interface HyperlinkButtonCell (Private) | |
41 - (void)customizeButtonCell; | |
42 @end | |
43 | |
44 @interface CustomLinkButtonCell : HyperlinkButtonCell | |
45 @end | |
46 | |
47 namespace { | |
48 | |
49 // The width of a service button, in view coordinates. | |
50 const CGFloat kServiceButtonWidth = 300; | |
51 | |
52 // Spacing in between sections. | |
53 const CGFloat kVerticalSpacing = 18; | |
54 | |
55 // Square size of the close button. | |
56 const CGFloat kCloseButtonSize = 16; | |
57 | |
58 // Width of the text fields. | |
59 const CGFloat kTextWidth = WebIntentPicker::kWindowMinWidth - | |
60 (WebIntentPicker::kContentAreaBorder * 2.0 + kCloseButtonSize); | |
61 | |
62 // Maximum number of intents (suggested and installed) displayed. | |
63 const int kMaxIntentRows = 4; | |
64 | |
65 // Sets properties on the given |field| to act as title or description labels. | |
66 void ConfigureTextFieldAsLabel(NSTextField* field) { | |
67 [field setEditable:NO]; | |
68 [field setSelectable:YES]; | |
69 [field setDrawsBackground:NO]; | |
70 [field setBezeled:NO]; | |
71 } | |
72 | |
73 NSButton* CreateHyperlinkButton(NSString* title, const NSRect& frame) { | |
74 NSButton* button = [[NSButton alloc] initWithFrame:frame]; | |
75 scoped_nsobject<CustomLinkButtonCell> cell( | |
76 [[CustomLinkButtonCell alloc] initTextCell:title]); | |
77 [cell setControlSize:NSSmallControlSize]; | |
78 [button setCell:cell.get()]; | |
79 [button setButtonType:NSMomentaryPushInButton]; | |
80 [button setBezelStyle:NSRegularSquareBezelStyle]; | |
81 | |
82 return button; | |
83 } | |
84 | |
85 } // namespace | |
86 | |
87 | |
88 // Provide custom link format for intent picker. Removes underline attribute, | |
89 // since UX direction is "look like WebUI". | |
90 @implementation CustomLinkButtonCell | |
91 - (void)customizeButtonCell { | |
92 [super customizeButtonCell]; | |
93 [self setTextColor:[NSColor colorWithDeviceRed:0x11/255.0 | |
94 green:0x55/255.0 | |
95 blue:0xcc/255.0 | |
96 alpha:0xff/255.0]]; | |
97 } | |
98 | |
99 - (NSDictionary*)linkAttributes { | |
100 scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( | |
101 [[NSParagraphStyle defaultParagraphStyle] mutableCopy]); | |
102 [paragraphStyle setAlignment:[self alignment]]; | |
103 | |
104 return @{ | |
105 NSForegroundColorAttributeName: [self textColor], | |
106 NSFontAttributeName: [self font], | |
107 NSCursorAttributeName: [NSCursor pointingHandCursor], | |
108 NSParagraphStyleAttributeName: paragraphStyle.get() | |
109 }; | |
110 } | |
111 @end | |
112 | |
113 // This simple NSView subclass is used as the single subview of the page info | |
114 // bubble's window's contentView. Drawing is flipped so that layout of the | |
115 // sections is easier. Apple recommends flipping the coordinate origin when | |
116 // doing a lot of text layout because it's more natural. | |
117 @interface WebIntentsContentView : NSView | |
118 @end | |
119 @implementation WebIntentsContentView | |
120 - (BOOL)isFlipped { | |
121 return YES; | |
122 } | |
123 | |
124 - (void)drawRect:(NSRect)rect { | |
125 [[NSColor colorWithCalibratedWhite:1.0 alpha:1.0] set]; | |
126 NSRectFill(rect); | |
127 } | |
128 @end | |
129 | |
130 // NSImageView subclassed to allow fading the alpha value of the image to | |
131 // indicate an inactive/disabled extension. | |
132 @interface DimmableImageView : NSImageView { | |
133 @private | |
134 CGFloat alpha; | |
135 } | |
136 | |
137 - (void)setEnabled:(BOOL)enabled; | |
138 | |
139 // NSView override | |
140 - (void)drawRect:(NSRect)rect; | |
141 @end | |
142 | |
143 @implementation DimmableImageView | |
144 - (void)drawRect:(NSRect)rect { | |
145 NSImage* image = [self image]; | |
146 | |
147 NSRect sourceRect, destinationRect; | |
148 sourceRect.origin = NSZeroPoint; | |
149 sourceRect.size = [image size]; | |
150 | |
151 destinationRect.origin = NSZeroPoint; | |
152 destinationRect.size = [self frame].size; | |
153 | |
154 // If the source image is smaller than the destination, center it. | |
155 if (destinationRect.size.width > sourceRect.size.width) { | |
156 destinationRect.origin.x = | |
157 (destinationRect.size.width - sourceRect.size.width) / 2.0; | |
158 destinationRect.size.width = sourceRect.size.width; | |
159 } | |
160 if (destinationRect.size.height > sourceRect.size.height) { | |
161 destinationRect.origin.y = | |
162 (destinationRect.size.height - sourceRect.size.height) / 2.0; | |
163 destinationRect.size.height = sourceRect.size.height; | |
164 } | |
165 | |
166 [image drawInRect:destinationRect | |
167 fromRect:sourceRect | |
168 operation:NSCompositeSourceOver | |
169 fraction:alpha]; | |
170 } | |
171 | |
172 - (void)setEnabled:(BOOL)enabled { | |
173 if (enabled) | |
174 alpha = 1.0; | |
175 else | |
176 alpha = 0.5; | |
177 [self setNeedsDisplay:YES]; | |
178 } | |
179 @end | |
180 | |
181 @interface WaitingView : NSView { | |
182 @private | |
183 scoped_nsobject<NSTextField> text_; | |
184 scoped_nsobject<ThrobberView> throbber_; | |
185 } | |
186 @end | |
187 | |
188 @implementation WaitingView | |
189 - (id)init { | |
190 NSRect frame = NSMakeRect(WebIntentPicker::kContentAreaBorder, 0, | |
191 kTextWidth, 140); | |
192 | |
193 if (self = [super initWithFrame:frame]) { | |
194 const CGFloat kTopMargin = 35.0; | |
195 const CGFloat kBottomMargin = 25.0; | |
196 const CGFloat kVerticalSpacing = 18.0; | |
197 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
198 | |
199 frame.origin = NSMakePoint(WebIntentPicker::kContentAreaBorder, | |
200 kBottomMargin); | |
201 frame.size.width = kTextWidth; | |
202 text_.reset([[NSTextField alloc] initWithFrame:frame]); | |
203 ConfigureTextFieldAsLabel(text_); | |
204 [text_ setAlignment:NSCenterTextAlignment]; | |
205 [text_ setFont:[NSFont boldSystemFontOfSize:[NSFont systemFontSize]]]; | |
206 [text_ setStringValue: | |
207 l10n_util::GetNSStringWithFixup(IDS_INTENT_PICKER_WAIT_FOR_CWS)]; | |
208 frame.size.height += | |
209 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: | |
210 text_]; | |
211 [text_ setFrame:frame]; | |
212 | |
213 frame.origin.y += NSHeight(frame) + kVerticalSpacing; | |
214 | |
215 // The sprite image consists of all the animation frames put together in one | |
216 // horizontal/wide image. Each animation frame is square in shape within the | |
217 // sprite. | |
218 NSImage* iconImage = | |
219 rb.GetNativeImageNamed(IDR_SPEECH_INPUT_SPINNER).ToNSImage(); | |
220 frame.size = [iconImage size]; | |
221 frame.size.width = NSHeight(frame); | |
222 frame.origin.x = (WebIntentPicker::kWindowMinWidth - NSWidth(frame))/2.0; | |
223 throbber_.reset([ThrobberView filmstripThrobberViewWithFrame:frame | |
224 image:iconImage]); | |
225 | |
226 frame.size = NSMakeSize(WebIntentPicker::kWindowMinWidth, | |
227 NSMaxY(frame) + kTopMargin); | |
228 frame.origin = NSMakePoint(0, 0); | |
229 [self setSubviews:@[throbber_, text_]]; | |
230 [self setFrame:frame]; | |
231 } | |
232 return self; | |
233 } | |
234 @end | |
235 | |
236 // An NSView subclass to display ratings stars. | |
237 @interface RatingsView : NSView | |
238 // Mark RatingsView as disabled/enabled. | |
239 - (void)setEnabled:(BOOL)enabled; | |
240 @end | |
241 | |
242 @implementation RatingsView | |
243 - (void)setEnabled:(BOOL)enabled { | |
244 for (DimmableImageView* imageView in [self subviews]) | |
245 [imageView setEnabled:enabled]; | |
246 } | |
247 @end | |
248 | |
249 // NSView for the header of the box. | |
250 @interface HeaderView : NSView { | |
251 @private | |
252 // Used to forward button clicks. Weak reference. | |
253 scoped_nsobject<NSTextField> titleField_; | |
254 scoped_nsobject<NSTextField> subtitleField_; | |
255 scoped_nsobject<NSBox> spacer_; | |
256 } | |
257 | |
258 - (id)init; | |
259 @end | |
260 | |
261 @implementation HeaderView | |
262 - (id)init { | |
263 NSRect contentFrame = NSMakeRect(0, 0, WebIntentPicker::kWindowMinWidth, 1); | |
264 if (self = [super initWithFrame:contentFrame]) { | |
265 NSRect frame = NSMakeRect(WebIntentPicker::kContentAreaBorder, 0, | |
266 kTextWidth, 1); | |
267 | |
268 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
269 titleField_.reset([[NSTextField alloc] initWithFrame:frame]); | |
270 ConfigureTextFieldAsLabel(titleField_); | |
271 gfx::Font titleFont = rb.GetFont( | |
272 ConstrainedWindowConstants::kTitleFontStyle); | |
273 titleFont = titleFont.DeriveFont(0, gfx::Font::BOLD); | |
274 [titleField_ setFont:titleFont.GetNativeFont()]; | |
275 | |
276 frame = NSMakeRect(WebIntentPicker::kContentAreaBorder, 0, | |
277 kTextWidth, 1); | |
278 subtitleField_.reset([[NSTextField alloc] initWithFrame:frame]); | |
279 ConfigureTextFieldAsLabel(subtitleField_); | |
280 gfx::Font textFont = rb.GetFont(ConstrainedWindowConstants::kTextFontStyle); | |
281 [subtitleField_ setFont:textFont.GetNativeFont()]; | |
282 | |
283 frame = NSMakeRect(0, 0, WebIntentPicker::kWindowMinWidth, 1.0); | |
284 spacer_.reset([[NSBox alloc] initWithFrame:frame]); | |
285 [spacer_ setBoxType:NSBoxSeparator]; | |
286 [spacer_ setBorderColor:[NSColor blackColor]]; | |
287 [spacer_ setAlphaValue:0.2]; | |
288 | |
289 NSArray* subviews = @[titleField_, subtitleField_, spacer_]; | |
290 [self setSubviews:subviews]; | |
291 } | |
292 return self; | |
293 } | |
294 | |
295 - (void)setTitle:(NSString*)title { | |
296 NSRect frame = [titleField_ frame]; | |
297 [titleField_ setStringValue:title]; | |
298 frame.size.height += | |
299 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: | |
300 titleField_]; | |
301 [titleField_ setFrame:frame]; | |
302 } | |
303 | |
304 - (void)setSubtitle:(NSString*)subtitle { | |
305 if (subtitle && [subtitle length]) { | |
306 NSRect frame = [subtitleField_ frame]; | |
307 | |
308 [subtitleField_ setHidden:FALSE]; | |
309 [subtitleField_ setStringValue:subtitle]; | |
310 frame.size.height += | |
311 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: | |
312 subtitleField_]; | |
313 [subtitleField_ setFrame:frame]; | |
314 } else { | |
315 [subtitleField_ setHidden:TRUE]; | |
316 } | |
317 } | |
318 | |
319 - (void)performLayout { | |
320 CGFloat offset = kVerticalSpacing; | |
321 | |
322 NSRect frame = [spacer_ frame]; | |
323 frame.origin.y = offset; | |
324 [spacer_ setFrame:frame]; | |
325 offset += NSHeight(frame); | |
326 | |
327 offset += kVerticalSpacing; | |
328 | |
329 if (![subtitleField_ isHidden]) { | |
330 frame = [subtitleField_ frame]; | |
331 frame.origin.y = offset; | |
332 [subtitleField_ setFrame:frame]; | |
333 offset += NSHeight(frame); | |
334 } | |
335 | |
336 frame = [titleField_ frame]; | |
337 frame.origin.y = offset; | |
338 [titleField_ setFrame:frame]; | |
339 offset += NSHeight(frame); | |
340 | |
341 // No kContentAreaBorder here, since that is currently handled elsewhere. | |
342 frame = [self frame]; | |
343 frame.size.height = offset; | |
344 [self setFrame:frame]; | |
345 } | |
346 @end | |
347 | |
348 // NSView for a single row in the intents view. | |
349 @interface IntentRowView : NSView { | |
350 @private | |
351 scoped_nsobject<NSProgressIndicator> throbber_; | |
352 scoped_nsobject<NSButton> cwsButton_; | |
353 scoped_nsobject<RatingsView> ratingsWidget_; | |
354 scoped_nsobject<NSButton> installButton_; | |
355 scoped_nsobject<DimmableImageView> iconView_; | |
356 scoped_nsobject<NSTextField> label_; | |
357 } | |
358 | |
359 - (id)initWithExtension: | |
360 (const WebIntentPickerModel::SuggestedExtension*)extension | |
361 withIndex:(size_t)index | |
362 forController:(WebIntentPickerSheetController*)controller; | |
363 - (void)startThrobber; | |
364 - (void)setEnabled:(BOOL)enabled; | |
365 - (void)stopThrobber; | |
366 - (NSInteger)tag; | |
367 @end | |
368 | |
369 @implementation IntentRowView | |
370 const CGFloat kMaxHeight = 34.0; | |
371 const CGFloat kTitleX = 20.0; | |
372 const CGFloat kMinAddButtonHeight = 28.0; | |
373 const CGFloat kAddButtonX = 245; | |
374 const CGFloat kAddButtonWidth = 128.0; | |
375 | |
376 + (RatingsView*)createStarWidgetWithRating:(CGFloat)rating { | |
377 const int kStarSpacing = 1; // Spacing between stars in pixels. | |
378 const CGFloat kStarSize = 16.0; // Size of the star in pixels. | |
379 | |
380 NSMutableArray* subviews = [NSMutableArray array]; | |
381 | |
382 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
383 NSRect imageFrame = NSMakeRect(0, 0, kStarSize, kStarSize); | |
384 | |
385 for (int i = 0; i < 5; ++i) { | |
386 NSImage* nsImage = rb.GetNativeImageNamed( | |
387 WebIntentPicker::GetNthStarImageIdFromCWSRating(rating, i)).ToNSImage(); | |
388 | |
389 scoped_nsobject<DimmableImageView> imageView( | |
390 [[DimmableImageView alloc] initWithFrame:imageFrame]); | |
391 [imageView setImage:nsImage]; | |
392 [imageView setImageFrameStyle:NSImageFrameNone]; | |
393 [imageView setFrame:imageFrame]; | |
394 [imageView setEnabled:YES]; | |
395 [subviews addObject:imageView]; | |
396 imageFrame.origin.x += kStarSize + kStarSpacing; | |
397 } | |
398 | |
399 NSRect frame = NSMakeRect(0, 0, (kStarSize + kStarSpacing) * 5, kStarSize); | |
400 RatingsView* widget = [[RatingsView alloc] initWithFrame:frame]; | |
401 [widget setSubviews:subviews]; | |
402 return widget; | |
403 } | |
404 | |
405 | |
406 - (void)setIcon:(NSImage*)icon { | |
407 NSRect imageFrame = NSZeroRect; | |
408 | |
409 iconView_.reset( | |
410 [[DimmableImageView alloc] initWithFrame:imageFrame]); | |
411 [iconView_ setImage:icon]; | |
412 [iconView_ setImageFrameStyle:NSImageFrameNone]; | |
413 [iconView_ setEnabled:YES]; | |
414 | |
415 imageFrame.size = [icon size]; | |
416 imageFrame.size.height = std::min(NSHeight(imageFrame), kMaxHeight); | |
417 imageFrame.origin.y += (kMaxHeight - NSHeight(imageFrame)) / 2.0; | |
418 [iconView_ setFrame:imageFrame]; | |
419 } | |
420 | |
421 - (void)setActionButton:(NSString*)title | |
422 withSelector:(SEL)selector | |
423 forController:(WebIntentPickerSheetController*)controller { | |
424 | |
425 NSRect frame = NSMakeRect(kAddButtonX, 0, kAddButtonWidth, 0); | |
426 installButton_.reset([[NSButton alloc] initWithFrame:frame]); | |
427 [installButton_ setAlignment:NSCenterTextAlignment]; | |
428 [installButton_ setButtonType:NSMomentaryPushInButton]; | |
429 [installButton_ setBezelStyle:NSRegularSquareBezelStyle]; | |
430 [installButton_ setTitle:title]; | |
431 frame.size.height = std::min(kMinAddButtonHeight, kMaxHeight); | |
432 frame.origin.y += (kMaxHeight - NSHeight(frame)) / 2.0; | |
433 [installButton_ setFrame:frame]; | |
434 | |
435 [installButton_ setTarget:controller]; | |
436 [installButton_ setAction:selector]; | |
437 } | |
438 | |
439 - (id)init { | |
440 // Build the main view. | |
441 NSRect contentFrame = NSMakeRect( | |
442 0, 0, WebIntentPicker::kWindowMinWidth, kMaxHeight); | |
443 if (self = [super initWithFrame:contentFrame]) { | |
444 NSMutableArray* subviews = [NSMutableArray array]; | |
445 if (iconView_) [subviews addObject:iconView_]; | |
446 if (label_) [subviews addObject:label_]; | |
447 if (cwsButton_) [subviews addObject:cwsButton_]; | |
448 if (ratingsWidget_) [subviews addObject:ratingsWidget_]; | |
449 if (installButton_) [subviews addObject:installButton_]; | |
450 if (throbber_) [subviews addObject:throbber_]; | |
451 [self setSubviews:subviews]; | |
452 } | |
453 return self; | |
454 } | |
455 | |
456 - (id)initWithExtension: | |
457 (const WebIntentPickerModel::SuggestedExtension*)extension | |
458 withIndex:(size_t)index | |
459 forController:(WebIntentPickerSheetController*)controller { | |
460 // Add the extension icon. | |
461 [self setIcon:extension->icon.ToNSImage()]; | |
462 | |
463 // Add the extension title. | |
464 NSRect frame = NSMakeRect(kTitleX, 0, 0, 0); | |
465 const string16 elidedTitle = ui::ElideText( | |
466 extension->title, gfx::Font(), WebIntentPicker::kTitleLinkMaxWidth, | |
467 ui::ELIDE_AT_END); | |
468 NSString* string = base::SysUTF16ToNSString(elidedTitle); | |
469 cwsButton_.reset(CreateHyperlinkButton(string, frame)); | |
470 [cwsButton_ setAlignment:NSCenterTextAlignment]; | |
471 [cwsButton_ setTarget:controller]; | |
472 [cwsButton_ setAction:@selector(openExtensionLink:)]; | |
473 [cwsButton_ setTag:index]; | |
474 [cwsButton_ sizeToFit]; | |
475 | |
476 frame = [cwsButton_ frame]; | |
477 frame.size.height = std::min([[cwsButton_ cell] cellSize].height, | |
478 kMaxHeight); | |
479 frame.origin.y = (kMaxHeight - NSHeight(frame)) / 2.0; | |
480 [cwsButton_ setFrame:frame]; | |
481 | |
482 // Add the star rating | |
483 CGFloat offsetX = frame.origin.x + NSWidth(frame) + | |
484 WebIntentPicker::kContentAreaBorder; | |
485 ratingsWidget_.reset( | |
486 [IntentRowView | |
487 createStarWidgetWithRating:extension->average_rating]); | |
488 frame = [ratingsWidget_ frame]; | |
489 frame.origin.y += (kMaxHeight - NSHeight(frame)) / 2.0; | |
490 frame.origin.x = offsetX; | |
491 [ratingsWidget_ setFrame:frame]; | |
492 | |
493 // Add an "add to chromium" button. | |
494 string = l10n_util::GetNSStringWithFixup( | |
495 IDS_INTENT_PICKER_INSTALL_EXTENSION); | |
496 [self setActionButton:string | |
497 withSelector:@selector(installExtension:) | |
498 forController:controller]; | |
499 [installButton_ setTag:index]; | |
500 | |
501 // Keep a throbber handy. | |
502 frame = [installButton_ frame]; | |
503 frame.origin.x += (NSWidth(frame) - 16) / 2; | |
504 frame.origin.y += (NSHeight(frame) - 16) /2; | |
505 frame.size = NSMakeSize(16, 16); | |
506 throbber_.reset([[NSProgressIndicator alloc] initWithFrame:frame]); | |
507 [throbber_ setHidden:YES]; | |
508 [throbber_ setStyle:NSProgressIndicatorSpinningStyle]; | |
509 | |
510 return [self init]; | |
511 } | |
512 | |
513 - (id)initWithService: | |
514 (const WebIntentPickerModel::InstalledService*)service | |
515 withIndex:(size_t)index | |
516 forController:(WebIntentPickerSheetController*)controller { | |
517 | |
518 // Add the extension icon. | |
519 [self setIcon:service->favicon.ToNSImage()]; | |
520 | |
521 // Add the extension title. | |
522 NSRect frame = NSMakeRect( | |
523 kTitleX, 0, WebIntentPicker::kTitleLinkMaxWidth, 10); | |
524 | |
525 const string16 elidedTitle = ui::ElideText( | |
526 service->title, gfx::Font(), | |
527 WebIntentPicker::kTitleLinkMaxWidth, ui::ELIDE_AT_END); | |
528 NSString* string = base::SysUTF16ToNSString(elidedTitle); | |
529 | |
530 label_.reset([[NSTextField alloc] initWithFrame:frame]); | |
531 ConfigureTextFieldAsLabel(label_); | |
532 [label_ setStringValue:string]; | |
533 frame.size.height += | |
534 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: | |
535 label_]; | |
536 frame.origin.y = (kMaxHeight - NSHeight(frame)) / 2.0; | |
537 [label_ setFrame:frame]; | |
538 | |
539 string = l10n_util::GetNSStringWithFixup( | |
540 IDS_INTENT_PICKER_SELECT_INTENT); | |
541 [self setActionButton:string | |
542 withSelector:@selector(invokeService:) | |
543 forController:controller]; | |
544 [installButton_ setTag:index]; | |
545 return [self init]; | |
546 } | |
547 | |
548 - (NSInteger)tag { | |
549 return [installButton_ tag]; | |
550 } | |
551 | |
552 - (void)startThrobber { | |
553 [installButton_ setHidden:YES]; | |
554 [throbber_ setHidden:NO]; | |
555 [throbber_ startAnimation:self]; | |
556 [iconView_ setEnabled:YES]; | |
557 [ratingsWidget_ setEnabled:NO]; | |
558 } | |
559 | |
560 - (void)setEnabled:(BOOL) enabled { | |
561 [installButton_ setEnabled:enabled]; | |
562 [cwsButton_ setEnabled:enabled]; | |
563 [iconView_ setEnabled:enabled]; | |
564 [ratingsWidget_ setEnabled:enabled]; | |
565 } | |
566 | |
567 - (void)stopThrobber { | |
568 if (throbber_.get()) { | |
569 [throbber_ setHidden:YES]; | |
570 [throbber_ stopAnimation:self]; | |
571 } | |
572 [installButton_ setHidden:NO]; | |
573 } | |
574 | |
575 - (void)adjustButtonSize:(CGFloat)newWidth { | |
576 CGFloat increase = std::max(kAddButtonWidth, newWidth) - kAddButtonWidth; | |
577 NSRect frame = [self frame]; | |
578 frame.size.width += increase; | |
579 [self setFrame:frame]; | |
580 | |
581 frame = [installButton_ frame]; | |
582 frame.size.width += increase; | |
583 [installButton_ setFrame:frame]; | |
584 } | |
585 | |
586 @end | |
587 | |
588 @interface IntentView : NSView { | |
589 @private | |
590 // Used to forward button clicks. Weak reference. | |
591 WebIntentPickerSheetController* controller_; | |
592 } | |
593 | |
594 - (id)initWithModel:(WebIntentPickerModel*)model | |
595 forController:(WebIntentPickerSheetController*)controller; | |
596 - (void)startThrobberForRow:(NSInteger)index; | |
597 - (void)stopThrobber; | |
598 @end | |
599 | |
600 @implementation IntentView | |
601 - (id)initWithModel:(WebIntentPickerModel*)model | |
602 forController:(WebIntentPickerSheetController*)controller { | |
603 if (self = [super initWithFrame:NSZeroRect]) { | |
604 const CGFloat kYMargin = 16.0; | |
605 int availableSpots = kMaxIntentRows; | |
606 | |
607 CGFloat offset = kYMargin; | |
608 NSMutableArray* subviews = [NSMutableArray array]; | |
609 NSMutableArray* rows = [NSMutableArray array]; | |
610 | |
611 for (size_t i = 0; i < model->GetInstalledServiceCount() && availableSpots; | |
612 ++i, --availableSpots) { | |
613 const WebIntentPickerModel::InstalledService& svc = | |
614 model->GetInstalledServiceAt(i); | |
615 scoped_nsobject<NSView> suggestView( | |
616 [[IntentRowView alloc] initWithService:&svc | |
617 withIndex:i | |
618 forController:controller]); | |
619 | |
620 [rows addObject:suggestView]; | |
621 } | |
622 | |
623 // Special case for the empty view. | |
624 size_t count = model->GetSuggestedExtensionCount(); | |
625 if (count == 0 && availableSpots == kMaxIntentRows) | |
626 return nil; | |
627 | |
628 for (size_t i = count; i > 0 && availableSpots; --i, --availableSpots) { | |
629 const WebIntentPickerModel::SuggestedExtension& ext = | |
630 model->GetSuggestedExtensionAt(i - 1); | |
631 scoped_nsobject<NSView> suggestView( | |
632 [[IntentRowView alloc] initWithExtension:&ext | |
633 withIndex:i-1 | |
634 forController:controller]); | |
635 [rows addObject:suggestView]; | |
636 } | |
637 | |
638 // Determine optimal width for buttons given localized texts. | |
639 scoped_nsobject<NSButton> sizeHelper( | |
640 [[NSButton alloc] initWithFrame:NSZeroRect]); | |
641 [sizeHelper setAlignment:NSCenterTextAlignment]; | |
642 [sizeHelper setButtonType:NSMomentaryPushInButton]; | |
643 [sizeHelper setBezelStyle:NSRegularSquareBezelStyle]; | |
644 | |
645 [sizeHelper setTitle: | |
646 l10n_util::GetNSStringWithFixup(IDS_INTENT_PICKER_INSTALL_EXTENSION)]; | |
647 [sizeHelper sizeToFit]; | |
648 CGFloat buttonWidth = NSWidth([sizeHelper frame]); | |
649 | |
650 [sizeHelper setTitle: | |
651 l10n_util::GetNSStringWithFixup(IDS_INTENT_PICKER_SELECT_INTENT)]; | |
652 [sizeHelper sizeToFit]; | |
653 buttonWidth = std::max(buttonWidth, NSWidth([sizeHelper frame])); | |
654 | |
655 for (IntentRowView* row in [rows reverseObjectEnumerator]) { | |
656 [row adjustButtonSize:buttonWidth]; | |
657 offset += [self addStackedView:row | |
658 toSubviews:subviews | |
659 atOffset:offset]; | |
660 } | |
661 | |
662 [self setSubviews:subviews]; | |
663 NSRect contentFrame = NSMakeRect(WebIntentPicker::kContentAreaBorder, 0, | |
664 WebIntentPicker::kWindowMinWidth, offset); | |
665 [self setFrame:contentFrame]; | |
666 controller_ = controller; | |
667 } | |
668 return self; | |
669 } | |
670 | |
671 - (void)startThrobberForRow:(NSInteger)index { | |
672 for (IntentRowView* row in [self subviews]) { | |
673 if ([row isMemberOfClass:[IntentRowView class]]) { | |
674 [row setEnabled:NO]; | |
675 if ([row tag] == index) { | |
676 [row startThrobber]; | |
677 } | |
678 } | |
679 } | |
680 } | |
681 | |
682 - (void)stopThrobber { | |
683 for (IntentRowView* row in [self subviews]) { | |
684 if ([row isMemberOfClass:[IntentRowView class]]) { | |
685 [row stopThrobber]; | |
686 [row setEnabled:YES]; | |
687 } | |
688 } | |
689 } | |
690 | |
691 - (IBAction)installExtension:(id)sender { | |
692 [controller_ installExtension:sender]; | |
693 } | |
694 | |
695 - (CGFloat)addStackedView:(NSView*)view | |
696 toSubviews:(NSMutableArray*)subviews | |
697 atOffset:(CGFloat)offset { | |
698 if (view == nil) | |
699 return 0.0; | |
700 | |
701 NSPoint frameOrigin = [view frame].origin; | |
702 frameOrigin.y = offset; | |
703 [view setFrameOrigin:frameOrigin]; | |
704 [subviews addObject:view]; | |
705 | |
706 return NSHeight([view frame]); | |
707 } | |
708 @end | |
709 | |
710 @implementation WebIntentPickerSheetController | |
711 | |
712 - (id)initWithPicker:(WebIntentPickerCocoa*)picker { | |
713 // Use an arbitrary height because it will reflect the size of the content. | |
714 NSRect contentRect = NSMakeRect( | |
715 0, 0, WebIntentPicker::kWindowMinWidth, kVerticalSpacing); | |
716 | |
717 // |window| is retained by the ConstrainedWindowMacDelegateCustomSheet when | |
718 // the sheet is initialized. | |
719 scoped_nsobject<NSWindow> window( | |
720 [[NSWindow alloc] initWithContentRect:contentRect | |
721 styleMask:NSTitledWindowMask | |
722 backing:NSBackingStoreBuffered | |
723 defer:YES]); | |
724 if ((self = [super initWithWindow:window.get()])) { | |
725 picker_ = picker; | |
726 if (picker) | |
727 model_ = picker->model(); | |
728 | |
729 inlineDispositionTitleField_.reset([[NSTextField alloc] init]); | |
730 ConfigureTextFieldAsLabel(inlineDispositionTitleField_); | |
731 [inlineDispositionTitleField_ setFont: | |
732 [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]]; | |
733 flipView_.reset([[WebIntentsContentView alloc] init]); | |
734 [flipView_ setAutoresizingMask:NSViewMinYMargin]; | |
735 [[[self window] contentView] setSubviews:@[flipView_]]; | |
736 | |
737 [self performLayoutWithModel:model_]; | |
738 } | |
739 return self; | |
740 } | |
741 | |
742 // Handle default OSX dialog cancel mechanisms. (Cmd-.) | |
743 - (void)cancelOperation:(id)sender { | |
744 if (picker_) | |
745 picker_->OnCancelled(); | |
746 [self closeSheet]; | |
747 } | |
748 | |
749 - (void)chooseAnotherService:(id)sender { | |
750 if (picker_) | |
751 picker_->OnChooseAnotherService(); | |
752 } | |
753 | |
754 - (void)sheetDidEnd:(NSWindow*)sheet | |
755 returnCode:(int)returnCode | |
756 contextInfo:(void*)contextInfo { | |
757 if (picker_) | |
758 picker_->OnSheetDidEnd(sheet); | |
759 } | |
760 | |
761 - (void)setInlineDispositionWebContents:(content::WebContents*)webContents { | |
762 webContents_ = webContents; | |
763 } | |
764 | |
765 - (void)setInlineDispositionFrameSize:(NSSize)inlineContentSize { | |
766 DCHECK(webContents_); | |
767 | |
768 NSView* webContentView = webContents_->GetNativeView(); | |
769 | |
770 // Make sure inline content size is never shrunk. | |
771 inlineContentSize = NSMakeSize( | |
772 std::max(NSWidth([webContentView frame]), inlineContentSize.width), | |
773 std::max(NSHeight([webContentView frame]), inlineContentSize.height)); | |
774 | |
775 // Compute container size to fit all elements, including padding. | |
776 NSSize containerSize = inlineContentSize; | |
777 containerSize.height += | |
778 [webContentView frame].origin.y + WebIntentPicker::kContentAreaBorder; | |
779 containerSize.width += 2 * WebIntentPicker::kContentAreaBorder; | |
780 | |
781 // Ensure minimum container width. | |
782 containerSize.width = | |
783 std::max(CGFloat(WebIntentPicker::kWindowMinWidth), containerSize.width); | |
784 | |
785 // Resize web contents. | |
786 [webContentView setFrameSize:inlineContentSize]; | |
787 [self setContainerSize:containerSize]; | |
788 } | |
789 | |
790 - (void)setContainerSize:(NSSize)containerSize { | |
791 // Resize container views | |
792 NSRect frame = NSMakeRect(0, 0, 0, 0); | |
793 frame.size = containerSize; | |
794 [[[self window] contentView] setFrame:frame]; | |
795 [flipView_ setFrame:frame]; | |
796 | |
797 // Resize and reposition dialog window. | |
798 frame.size = [[[self window] contentView] convertSize:containerSize | |
799 toView:nil]; | |
800 frame = [[self window] frameRectForContentRect:frame]; | |
801 | |
802 // Readjust window position to keep top in place and center horizontally. | |
803 NSRect windowFrame = [[self window] frame]; | |
804 windowFrame.origin.x -= (NSWidth(frame) - NSWidth(windowFrame)) / 2.0; | |
805 windowFrame.origin.y -= (NSHeight(frame) - NSHeight(windowFrame)); | |
806 windowFrame.size = frame.size; | |
807 [[self window] setFrame:windowFrame display:YES animate:NO]; | |
808 } | |
809 | |
810 // Pop up a new tab with the Chrome Web Store. | |
811 - (IBAction)showChromeWebStore:(id)sender { | |
812 DCHECK(picker_); | |
813 picker_->OnSuggestionsLinkClicked( | |
814 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent])); | |
815 } | |
816 | |
817 // A picker button has been pressed - invoke corresponding service. | |
818 - (IBAction)invokeService:(id)sender { | |
819 DCHECK(picker_); | |
820 picker_->OnServiceChosen([sender tag]); | |
821 } | |
822 | |
823 - (IBAction)openExtensionLink:(id)sender { | |
824 DCHECK(model_); | |
825 DCHECK(picker_); | |
826 const WebIntentPickerModel::SuggestedExtension& extension = | |
827 model_->GetSuggestedExtensionAt([sender tag]); | |
828 | |
829 picker_->OnExtensionLinkClicked(extension.id, | |
830 event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent])); | |
831 } | |
832 | |
833 - (IBAction)installExtension:(id)sender { | |
834 DCHECK(model_); | |
835 DCHECK(picker_); | |
836 const WebIntentPickerModel::SuggestedExtension& extension = | |
837 model_->GetSuggestedExtensionAt([sender tag]); | |
838 if (picker_) { | |
839 [intentView_ startThrobberForRow:[sender tag]]; | |
840 [closeButton_ setEnabled:NO]; | |
841 picker_->OnExtensionInstallRequested(extension.id); | |
842 } | |
843 } | |
844 | |
845 - (CGFloat)addStackedView:(NSView*)view | |
846 toSubviews:(NSMutableArray*)subviews | |
847 atOffset:(CGFloat)offset { | |
848 if (view == nil) | |
849 return 0.0; | |
850 | |
851 NSPoint frameOrigin = [view frame].origin; | |
852 frameOrigin.y = offset; | |
853 [view setFrameOrigin:frameOrigin]; | |
854 [subviews addObject:view]; | |
855 | |
856 return NSHeight([view frame]); | |
857 } | |
858 | |
859 // Adds a link to the Chrome Web Store, to obtain further intent handlers. | |
860 // Returns the y position delta for the next offset. | |
861 - (CGFloat)addCwsButtonToSubviews:(NSMutableArray*)subviews | |
862 atOffset:(CGFloat)offset { | |
863 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
864 NSImage* iconImage = rb.GetNativeImageNamed(IDR_WEBSTORE_ICON_16).ToNSImage(); | |
865 NSRect imageFrame; | |
866 imageFrame.origin = NSMakePoint(WebIntentPicker::kContentAreaBorder, offset); | |
867 imageFrame.size = [iconImage size]; | |
868 scoped_nsobject<NSImageView> iconView( | |
869 [[NSImageView alloc] initWithFrame:imageFrame]); | |
870 [iconView setImage:iconImage]; | |
871 [iconView setImageFrameStyle:NSImageFrameNone]; | |
872 | |
873 const CGFloat kCWSIconPadding = 4.0; // Same spacing as for service options. | |
874 NSRect frame = NSMakeRect(WebIntentPicker::kContentAreaBorder + | |
875 NSWidth(imageFrame) + kCWSIconPadding, | |
876 offset, 100, 10); | |
877 NSString* string = l10n_util::GetNSStringWithFixup( | |
878 IDS_FIND_MORE_INTENT_HANDLER_MESSAGE); | |
879 scoped_nsobject<NSButton> button(CreateHyperlinkButton(string, frame)); | |
880 [button setTarget:self]; | |
881 [button setAction:@selector(showChromeWebStore:)]; | |
882 [subviews addObjectsFromArray:@[iconView, button]]; | |
883 | |
884 // Call size-to-fit to fixup for the localized string. | |
885 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button]; | |
886 | |
887 return NSHeight([button frame]); | |
888 } | |
889 | |
890 - (void)addCloseButtonToSubviews:(NSMutableArray*)subviews { | |
891 const CGFloat kButtonPadding = 4.0; // whitespace inside button frame. | |
892 if (!closeButton_.get()) { | |
893 NSRect buttonFrame = NSMakeRect( | |
894 WebIntentPicker::kContentAreaBorder + kTextWidth + kButtonPadding, | |
895 WebIntentPicker::kContentAreaBorder - kButtonPadding, | |
896 kCloseButtonSize, kCloseButtonSize); | |
897 closeButton_.reset( | |
898 [[HoverCloseButton alloc] initWithFrame:buttonFrame]); | |
899 // Anchor close button to upper right. | |
900 // (NSViewMaxYMargin since parent view is flipped.) | |
901 [closeButton_ setAutoresizingMask:NSViewMaxYMargin|NSViewMinXMargin]; | |
902 [closeButton_ setTarget:self]; | |
903 [closeButton_ setAction:@selector(cancelOperation:)]; | |
904 [[closeButton_ cell] setKeyEquivalent:@"\e"]; | |
905 } | |
906 [subviews addObject:closeButton_]; | |
907 } | |
908 | |
909 // Adds a header (icon and explanatory text) to picker bubble. | |
910 // Returns the y position delta for the next offset. | |
911 - (CGFloat)addHeaderToSubviews:(NSMutableArray*)subviews | |
912 atOffset:(CGFloat)offset { | |
913 // Create a new text field if we don't have one yet. | |
914 // TODO(groby): This should not be necessary since the controller sends this | |
915 // string. | |
916 if (!actionTextField_.get()) { | |
917 NSString* nsString = | |
918 l10n_util::GetNSStringWithFixup(IDS_CHOOSE_INTENT_HANDLER_MESSAGE); | |
919 [self setActionString:nsString]; | |
920 } | |
921 | |
922 scoped_nsobject<HeaderView> header([[HeaderView alloc] init]); | |
923 | |
924 [header setTitle:[actionTextField_ stringValue]]; | |
925 string16 labelText; | |
926 if (model_ && model_->GetInstalledServiceCount() == 0) | |
927 labelText = model_->GetSuggestionsLinkText(); | |
928 [header setSubtitle:base::SysUTF16ToNSString(labelText)]; | |
929 [header performLayout]; | |
930 | |
931 NSRect frame = [header frame]; | |
932 frame.origin.y = offset; | |
933 [header setFrame:frame]; | |
934 [subviews addObject:header]; | |
935 | |
936 return NSHeight(frame); | |
937 } | |
938 | |
939 - (CGFloat)addInlineHtmlToSubviews:(NSMutableArray*)subviews | |
940 atOffset:(CGFloat)offset { | |
941 if (!webContents_) | |
942 return 0; | |
943 | |
944 // Determine a good size for the inline disposition window. | |
945 | |
946 gfx::Size size = picker_->GetMinInlineDispositionSize(); | |
947 NSView* webContentView = webContents_->GetNativeView(); | |
948 NSRect contentFrame = NSMakeRect( | |
949 WebIntentPicker::kContentAreaBorder, | |
950 offset, | |
951 std::max(NSWidth([webContentView frame]),CGFloat(size.width())), | |
952 std::max(NSHeight([webContentView frame]),CGFloat(size.height()))); | |
953 | |
954 [webContentView setFrame:contentFrame]; | |
955 [subviews addObject:webContentView]; | |
956 | |
957 return NSHeight(contentFrame); | |
958 } | |
959 | |
960 - (CGFloat)addAnotherServiceLinkToSubviews:(NSMutableArray*)subviews | |
961 atOffset:(CGFloat)offset { | |
962 DCHECK(model_); | |
963 DCHECK(model_->IsInlineDisposition()); | |
964 GURL url = model_->inline_disposition_url(); | |
965 | |
966 const WebIntentPickerModel::InstalledService* service = | |
967 model_->GetInstalledServiceWithURL(url); | |
968 DCHECK(service); | |
969 | |
970 CGFloat originalOffset = offset; | |
971 | |
972 // Icon for current service. | |
973 scoped_nsobject<NSImageView> icon; | |
974 NSRect imageFrame = NSMakeRect(WebIntentPicker::kContentAreaBorder, offset, | |
975 0, 0); | |
976 icon.reset([[NSImageView alloc] initWithFrame:imageFrame]); | |
977 [icon setImage:service->favicon.ToNSImage()]; | |
978 [icon setImageFrameStyle:NSImageFrameNone]; | |
979 [icon setEnabled:YES]; | |
980 | |
981 imageFrame.size = [service->favicon.ToNSImage() size]; | |
982 [icon setFrame:imageFrame]; | |
983 | |
984 [subviews addObject:icon]; | |
985 | |
986 // Resize control to fit text | |
987 NSRect textFrame = | |
988 NSMakeRect(NSMaxX(imageFrame) + 4, | |
989 offset, | |
990 WebIntentPicker::kTitleLinkMaxWidth, 1); | |
991 [inlineDispositionTitleField_ setFrame:textFrame]; | |
992 [subviews addObject:inlineDispositionTitleField_]; | |
993 [GTMUILocalizerAndLayoutTweaker sizeToFitView:inlineDispositionTitleField_]; | |
994 textFrame = [inlineDispositionTitleField_ frame]; | |
995 | |
996 // Add link for "choose another service" if other suggestions are available | |
997 // or if more than one (the current) service is installed. | |
998 if (model_->show_use_another_service() && | |
999 (model_->GetInstalledServiceCount() > 1 || | |
1000 model_->GetSuggestedExtensionCount())) { | |
1001 NSRect frame = NSMakeRect( | |
1002 NSMaxX(textFrame) + WebIntentPicker::kContentAreaBorder, offset, | |
1003 1, 1); | |
1004 NSString* string = l10n_util::GetNSStringWithFixup( | |
1005 IDS_INTENT_PICKER_USE_ALTERNATE_SERVICE); | |
1006 scoped_nsobject<NSButton> button(CreateHyperlinkButton(string, frame)); | |
1007 [[button cell] setControlSize:NSRegularControlSize]; | |
1008 [[button cell] setFont: | |
1009 [NSFont controlContentFontOfSize:[NSFont systemFontSize]]]; | |
1010 [button setTarget:self]; | |
1011 [button setAction:@selector(chooseAnotherService:)]; | |
1012 [subviews addObject:button]; | |
1013 | |
1014 // Call size-to-fit to fixup for the localized string. | |
1015 [GTMUILocalizerAndLayoutTweaker sizeToFitView:button]; | |
1016 | |
1017 // Right-align the "use another service" button. | |
1018 frame = [button frame]; | |
1019 frame.origin.x = WebIntentPicker::kWindowMinWidth - NSWidth(frame) - | |
1020 2 * WebIntentPicker::kContentAreaBorder - kCloseButtonSize; | |
1021 [button setFrame:frame]; | |
1022 [button setAutoresizingMask:NSViewMinXMargin]; | |
1023 | |
1024 // And finally, make sure the link and the title are horizontally centered. | |
1025 frame = [button frame]; | |
1026 CGFloat height = std::max(NSHeight(textFrame), NSHeight(frame)); | |
1027 frame.origin.y += (height - NSHeight(frame)) / 2.0; | |
1028 frame.size.height = height; | |
1029 textFrame.origin.y += (height - NSHeight(textFrame)) / 2.0; | |
1030 textFrame.size.height = height; | |
1031 [button setFrame:frame]; | |
1032 [inlineDispositionTitleField_ setFrame:textFrame]; | |
1033 } | |
1034 | |
1035 offset += NSHeight(textFrame) + kVerticalSpacing; | |
1036 | |
1037 scoped_nsobject<NSBox> spacer; | |
1038 | |
1039 NSRect frame = NSMakeRect(0, offset, WebIntentPicker::kWindowMinWidth, 1.0); | |
1040 spacer.reset([[NSBox alloc] initWithFrame:frame]); | |
1041 [spacer setBoxType:NSBoxSeparator]; | |
1042 [spacer setAlphaValue:0.2]; | |
1043 [spacer setAutoresizingMask:NSViewWidthSizable]; | |
1044 [subviews addObject: spacer]; | |
1045 | |
1046 return offset + kVerticalSpacing - originalOffset; | |
1047 } | |
1048 | |
1049 - (NSView*)createEmptyView { | |
1050 NSRect titleFrame = NSMakeRect(WebIntentPicker::kContentAreaBorder, | |
1051 WebIntentPicker::kContentAreaBorder, | |
1052 kTextWidth, 1); | |
1053 scoped_nsobject<NSTextField> title( | |
1054 [[NSTextField alloc] initWithFrame:titleFrame]); | |
1055 ConfigureTextFieldAsLabel(title); | |
1056 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
1057 gfx::Font titleFont = rb.GetFont(ConstrainedWindowConstants::kTitleFontStyle); | |
1058 titleFont = titleFont.DeriveFont(0, gfx::Font::BOLD); | |
1059 [title setFont:titleFont.GetNativeFont()]; | |
1060 [title setStringValue: | |
1061 l10n_util::GetNSStringWithFixup(IDS_INTENT_PICKER_NO_SERVICES_TITLE)]; | |
1062 titleFrame.size.height += | |
1063 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:title]; | |
1064 | |
1065 NSRect bodyFrame = titleFrame; | |
1066 bodyFrame.origin.y += | |
1067 NSHeight(titleFrame) + WebIntentPicker::kContentAreaBorder; | |
1068 | |
1069 scoped_nsobject<NSTextField> body( | |
1070 [[NSTextField alloc] initWithFrame:bodyFrame]); | |
1071 ConfigureTextFieldAsLabel(body); | |
1072 [body setStringValue: | |
1073 l10n_util::GetNSStringWithFixup(IDS_INTENT_PICKER_NO_SERVICES)]; | |
1074 bodyFrame.size.height += | |
1075 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:body]; | |
1076 NSRect viewFrame = NSMakeRect( | |
1077 0, | |
1078 WebIntentPicker::kContentAreaBorder, | |
1079 std::max(NSWidth(bodyFrame), NSWidth(titleFrame)) + | |
1080 2 * WebIntentPicker::kContentAreaBorder, | |
1081 NSHeight(titleFrame) + NSHeight(bodyFrame) + kVerticalSpacing); | |
1082 | |
1083 titleFrame.origin.y = NSHeight(viewFrame) - NSHeight(titleFrame); | |
1084 bodyFrame.origin.y = 0; | |
1085 | |
1086 [title setFrame:titleFrame]; | |
1087 [body setFrame:bodyFrame]; | |
1088 | |
1089 NSView* view = [[NSView alloc] initWithFrame:viewFrame]; | |
1090 [view setSubviews:@[title, body]]; | |
1091 | |
1092 return view; | |
1093 } | |
1094 | |
1095 - (void)performLayoutWithModel:(WebIntentPickerModel*)model { | |
1096 model_ = model; | |
1097 | |
1098 // |offset| is the Y position that should be drawn at next. | |
1099 CGFloat offset = WebIntentPicker::kContentAreaBorder; | |
1100 | |
1101 // Keep the new subviews in an array that gets replaced at the end. | |
1102 NSMutableArray* subviews = [NSMutableArray array]; | |
1103 | |
1104 // Indicator that we have neither suggested nor installed services, | |
1105 // and we're not in the wait stage any more either. | |
1106 BOOL isEmpty = model_ && | |
1107 !model_->IsWaitingForSuggestions() && | |
1108 !model_->GetInstalledServiceCount() && | |
1109 !model_->GetSuggestedExtensionCount(); | |
1110 | |
1111 if (model_ && model_->IsWaitingForSuggestions()) { | |
1112 if (!waitingView_.get()) | |
1113 waitingView_.reset([[WaitingView alloc] init]); | |
1114 [subviews addObject:waitingView_]; | |
1115 offset += NSHeight([waitingView_ frame]); | |
1116 } else if (isEmpty) { | |
1117 scoped_nsobject<NSView> emptyView([self createEmptyView]); | |
1118 [subviews addObject:emptyView]; | |
1119 offset += NSHeight([emptyView frame]); | |
1120 } else if (webContents_) { | |
1121 offset += [self addAnotherServiceLinkToSubviews:subviews | |
1122 atOffset:offset]; | |
1123 offset += [self addInlineHtmlToSubviews:subviews atOffset:offset]; | |
1124 } else { | |
1125 offset += [self addHeaderToSubviews:subviews atOffset:offset]; | |
1126 | |
1127 if (model) { | |
1128 intentView_.reset( | |
1129 [[IntentView alloc] initWithModel:model forController:self]); | |
1130 offset += [self addStackedView:intentView_ | |
1131 toSubviews:subviews | |
1132 atOffset:offset]; | |
1133 } | |
1134 offset += [self addCwsButtonToSubviews:subviews atOffset:offset]; | |
1135 } | |
1136 | |
1137 // Add the bottom padding. | |
1138 offset += WebIntentPicker::kContentAreaBorder; | |
1139 | |
1140 // Resize to fit. | |
1141 [self setContainerSize:NSMakeSize(WebIntentPicker::kWindowMinWidth, offset)]; | |
1142 | |
1143 [self addCloseButtonToSubviews:subviews]; | |
1144 | |
1145 // Replace the window's content. | |
1146 [flipView_ setSubviews:subviews]; | |
1147 } | |
1148 | |
1149 - (void)setActionString:(NSString*)actionString { | |
1150 NSRect textFrame; | |
1151 if (!actionTextField_.get()) { | |
1152 textFrame = NSMakeRect(WebIntentPicker::kContentAreaBorder, 0, | |
1153 kTextWidth, 1); | |
1154 | |
1155 actionTextField_.reset([[NSTextField alloc] initWithFrame:textFrame]); | |
1156 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
1157 ConfigureTextFieldAsLabel(actionTextField_); | |
1158 gfx::Font titleFont = rb.GetFont( | |
1159 ConstrainedWindowConstants::kTitleFontStyle); | |
1160 titleFont = titleFont.DeriveFont(0, gfx::Font::BOLD); | |
1161 [actionTextField_ setFont:titleFont.GetNativeFont()]; | |
1162 } else { | |
1163 textFrame = [actionTextField_ frame]; | |
1164 } | |
1165 | |
1166 [actionTextField_ setStringValue:actionString]; | |
1167 textFrame.size.height += | |
1168 [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: | |
1169 actionTextField_]; | |
1170 [actionTextField_ setFrame:textFrame]; | |
1171 } | |
1172 | |
1173 - (void)setInlineDispositionTitle:(NSString*)title { | |
1174 NSFont* nsfont = [inlineDispositionTitleField_ font]; | |
1175 gfx::Font font( | |
1176 base::SysNSStringToUTF8([nsfont fontName]), [nsfont pointSize]); | |
1177 NSString* elidedTitle = base::SysUTF16ToNSString(ui::ElideText( | |
1178 base::SysNSStringToUTF16(title), | |
1179 font, WebIntentPicker::kTitleLinkMaxWidth, ui::ELIDE_AT_END)); | |
1180 [inlineDispositionTitleField_ setStringValue:elidedTitle]; | |
1181 } | |
1182 | |
1183 - (void)stopThrobber { | |
1184 [closeButton_ setEnabled:YES]; | |
1185 [intentView_ stopThrobber]; | |
1186 } | |
1187 | |
1188 - (void)closeSheet { | |
1189 [NSApp endSheet:[self window]]; | |
1190 } | |
1191 | |
1192 @end // WebIntentPickerSheetController | |
OLD | NEW |