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/extensions/extension_install_dialog_controller.
h" | 5 #import "chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.
h" |
6 | 6 |
| 7 #include "base/i18n/rtl.h" |
7 #include "base/mac/bundle_locations.h" | 8 #include "base/mac/bundle_locations.h" |
8 #include "base/mac/mac_util.h" | 9 #include "base/mac/mac_util.h" |
9 #include "base/memory/scoped_nsobject.h" | 10 #include "base/memory/scoped_nsobject.h" |
10 #include "base/string_util.h" | 11 #include "base/string_util.h" |
11 #include "base/sys_string_conversions.h" | 12 #include "base/sys_string_conversions.h" |
12 #include "base/utf_string_conversions.h" | 13 #include "base/utf_string_conversions.h" |
| 14 #include "chrome/browser/extensions/bundle_installer.h" |
13 #include "chrome/browser/extensions/extension_install_dialog.h" | 15 #include "chrome/browser/extensions/extension_install_dialog.h" |
14 #include "chrome/browser/ui/browser.h" | 16 #include "chrome/browser/ui/browser.h" |
15 #include "chrome/browser/ui/browser_list.h" | 17 #include "chrome/browser/ui/browser_list.h" |
16 #include "chrome/browser/ui/browser_window.h" | 18 #include "chrome/browser/ui/browser_window.h" |
17 #include "chrome/common/extensions/extension.h" | 19 #include "chrome/common/extensions/extension.h" |
18 #include "grit/generated_resources.h" | 20 #include "grit/generated_resources.h" |
19 #include "skia/ext/skia_utils_mac.h" | 21 #include "skia/ext/skia_utils_mac.h" |
20 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | 22 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
21 #include "ui/base/l10n/l10n_util.h" | 23 #include "ui/base/l10n/l10n_util.h" |
22 #include "ui/base/l10n/l10n_util_mac.h" | 24 #include "ui/base/l10n/l10n_util_mac.h" |
23 | 25 |
24 using content::OpenURLParams; | 26 using content::OpenURLParams; |
25 using content::Referrer; | 27 using content::Referrer; |
| 28 using extensions::BundleInstaller; |
26 | 29 |
27 @interface ExtensionInstallDialogController () | 30 @interface ExtensionInstallDialogController () |
28 - (bool)isInlineInstall; | 31 - (BOOL)isBundleInstall; |
| 32 - (BOOL)isInlineInstall; |
29 - (void)appendRatingStar:(const SkBitmap*)skiaImage; | 33 - (void)appendRatingStar:(const SkBitmap*)skiaImage; |
30 @end | 34 @end |
31 | 35 |
32 namespace { | 36 namespace { |
33 | 37 |
34 // Padding above the warnings separator, we must also subtract this when hiding | 38 // Padding above the warnings separator, we must also subtract this when hiding |
35 // it. | 39 // it. |
36 const CGFloat kWarningsSeparatorPadding = 14; | 40 const CGFloat kWarningsSeparatorPadding = 14; |
37 | 41 |
38 // Maximum height we will adjust controls to when trying to accomodate their | 42 // Maximum height we will adjust controls to when trying to accomodate their |
39 // contents. | 43 // contents. |
40 const CGFloat kMaxControlHeight = 400; | 44 const CGFloat kMaxControlHeight = 400; |
41 | 45 |
42 // Adjust a control's height so that its content its not clipped. Returns the | 46 // Adjust the |control|'s height so that its content is not clipped. |
43 // amount the control's height had to be adjusted. | 47 // This also adds the change in height to the |totalOffset| and shifts the |
44 CGFloat AdjustControlHeightToFitContent(NSControl* control) { | 48 // control down by that amount. |
| 49 void OffsetControlVerticallyToFitContent(NSControl* control, |
| 50 CGFloat* totalOffset) { |
| 51 // Adjust the control's height so that its content is not clipped. |
45 NSRect currentRect = [control frame]; | 52 NSRect currentRect = [control frame]; |
46 NSRect fitRect = currentRect; | 53 NSRect fitRect = currentRect; |
47 fitRect.size.height = kMaxControlHeight; | 54 fitRect.size.height = kMaxControlHeight; |
48 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height; | 55 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height; |
49 CGFloat offset = desiredHeight - currentRect.size.height; | 56 CGFloat offset = desiredHeight - currentRect.size.height; |
50 | 57 |
51 [control setFrameSize:NSMakeSize(currentRect.size.width, | 58 [control setFrameSize:NSMakeSize(currentRect.size.width, |
52 currentRect.size.height + offset)]; | 59 currentRect.size.height + offset)]; |
53 return offset; | |
54 } | |
55 | 60 |
56 // Moves the control vertically by the specified amount. | 61 *totalOffset += offset; |
57 void OffsetControlVertically(NSControl* control, CGFloat amount) { | 62 |
| 63 // Move the control vertically by the new total offset. |
58 NSPoint origin = [control frame].origin; | 64 NSPoint origin = [control frame].origin; |
59 origin.y += amount; | 65 origin.y -= *totalOffset; |
60 [control setFrameOrigin:origin]; | 66 [control setFrameOrigin:origin]; |
61 } | 67 } |
62 | 68 |
63 void AppendRatingStarsShim(const SkBitmap* skiaImage, void* data) { | 69 void AppendRatingStarsShim(const SkBitmap* skiaImage, void* data) { |
64 ExtensionInstallDialogController* controller = | 70 ExtensionInstallDialogController* controller = |
65 static_cast<ExtensionInstallDialogController*>(data); | 71 static_cast<ExtensionInstallDialogController*>(data); |
66 [controller appendRatingStar:skiaImage]; | 72 [controller appendRatingStar:skiaImage]; |
67 } | 73 } |
68 | 74 |
69 } | 75 } |
70 | 76 |
71 @implementation ExtensionInstallDialogController | 77 @implementation ExtensionInstallDialogController |
72 | 78 |
73 @synthesize iconView = iconView_; | 79 @synthesize iconView = iconView_; |
74 @synthesize titleField = titleField_; | 80 @synthesize titleField = titleField_; |
| 81 @synthesize itemsField = itemsField_; |
75 @synthesize subtitleField = subtitleField_; | 82 @synthesize subtitleField = subtitleField_; |
76 @synthesize warningsField = warningsField_; | 83 @synthesize warningsField = warningsField_; |
77 @synthesize cancelButton = cancelButton_; | 84 @synthesize cancelButton = cancelButton_; |
78 @synthesize okButton = okButton_; | 85 @synthesize okButton = okButton_; |
79 @synthesize warningsSeparator = warningsSeparator_; | 86 @synthesize warningsSeparator = warningsSeparator_; |
80 @synthesize ratingStars = ratingStars_; | 87 @synthesize ratingStars = ratingStars_; |
81 @synthesize ratingCountField = ratingCountField_; | 88 @synthesize ratingCountField = ratingCountField_; |
82 @synthesize userCountField = userCountField_; | 89 @synthesize userCountField = userCountField_; |
83 | 90 |
84 - (id)initWithParentWindow:(NSWindow*)window | 91 - (id)initWithParentWindow:(NSWindow*)window |
85 profile:(Profile*)profile | 92 profile:(Profile*)profile |
86 delegate:(ExtensionInstallUI::Delegate*)delegate | 93 delegate:(ExtensionInstallUI::Delegate*)delegate |
87 prompt:(const ExtensionInstallUI::Prompt&)prompt { | 94 prompt:(const ExtensionInstallUI::Prompt&)prompt { |
88 NSString* nibpath = nil; | 95 NSString* nibpath = nil; |
89 | 96 |
90 // We use a different XIB in the case of inline installs or no permission | 97 // We use a different XIB in the case of bundle installs, inline installs or |
91 // warnings, that respectively show webstore ratings data and are a more | 98 // no permission warnings. These are laid out nicely for the data they |
92 // nicely laid out. | 99 // display. |
93 if (prompt.type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT) { | 100 if (prompt.type() == ExtensionInstallUI::BUNDLE_INSTALL_PROMPT) { |
| 101 nibpath = [base::mac::FrameworkBundle() |
| 102 pathForResource:@"ExtensionInstallPromptBundle" |
| 103 ofType:@"nib"]; |
| 104 } else if (prompt.type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT) { |
94 nibpath = [base::mac::FrameworkBundle() | 105 nibpath = [base::mac::FrameworkBundle() |
95 pathForResource:@"ExtensionInstallPromptInline" | 106 pathForResource:@"ExtensionInstallPromptInline" |
96 ofType:@"nib"]; | 107 ofType:@"nib"]; |
97 } else if (prompt.GetPermissionCount() == 0) { | 108 } else if (prompt.GetPermissionCount() == 0) { |
98 nibpath = [base::mac::FrameworkBundle() | 109 nibpath = [base::mac::FrameworkBundle() |
99 pathForResource:@"ExtensionInstallPromptNoWarnings" | 110 pathForResource:@"ExtensionInstallPromptNoWarnings" |
100 ofType:@"nib"]; | 111 ofType:@"nib"]; |
101 } else { | 112 } else { |
102 nibpath = [base::mac::FrameworkBundle() | 113 nibpath = [base::mac::FrameworkBundle() |
103 pathForResource:@"ExtensionInstallPrompt" | 114 pathForResource:@"ExtensionInstallPrompt" |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 base::SysUTF16ToNSString(prompt_->GetAbortButtonLabel()) : | 166 base::SysUTF16ToNSString(prompt_->GetAbortButtonLabel()) : |
156 l10n_util::GetNSString(IDS_CANCEL)]; | 167 l10n_util::GetNSString(IDS_CANCEL)]; |
157 if ([self isInlineInstall]) { | 168 if ([self isInlineInstall]) { |
158 prompt_->AppendRatingStars(AppendRatingStarsShim, self); | 169 prompt_->AppendRatingStars(AppendRatingStarsShim, self); |
159 [ratingCountField_ setStringValue:base::SysUTF16ToNSString( | 170 [ratingCountField_ setStringValue:base::SysUTF16ToNSString( |
160 prompt_->GetRatingCount())]; | 171 prompt_->GetRatingCount())]; |
161 [userCountField_ setStringValue:base::SysUTF16ToNSString( | 172 [userCountField_ setStringValue:base::SysUTF16ToNSString( |
162 prompt_->GetUserCount())]; | 173 prompt_->GetUserCount())]; |
163 } | 174 } |
164 | 175 |
165 [iconView_ setImage:prompt_->icon().ToNSImage()]; | 176 // The bundle install dialog has no icon. |
| 177 if (![self isBundleInstall]) |
| 178 [iconView_ setImage:prompt_->icon().ToNSImage()]; |
166 | 179 |
167 // Resize |titleField_| to fit the title. | 180 // Resize |titleField_| to fit the title. |
168 CGFloat originalTitleWidth = [titleField_ frame].size.width; | 181 CGFloat originalTitleWidth = [titleField_ frame].size.width; |
169 [titleField_ sizeToFit]; | 182 [titleField_ sizeToFit]; |
170 CGFloat newTitleWidth = [titleField_ frame].size.width; | 183 CGFloat newTitleWidth = [titleField_ frame].size.width; |
171 if (newTitleWidth > originalTitleWidth) { | 184 if (newTitleWidth > originalTitleWidth) { |
172 NSRect frame = [[self window] frame]; | 185 NSRect frame = [[self window] frame]; |
173 frame.size.width += newTitleWidth - originalTitleWidth; | 186 frame.size.width += newTitleWidth - originalTitleWidth; |
174 [[self window] setFrame:frame display:NO]; | 187 [[self window] setFrame:frame display:NO]; |
175 } | 188 } |
176 | 189 |
177 // Resize |okButton_| and |cancelButton_| to fit the button labels, but keep | 190 // Resize |okButton_| and |cancelButton_| to fit the button labels, but keep |
178 // them right-aligned. | 191 // them right-aligned. |
179 NSSize buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_]; | 192 NSSize buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_]; |
180 if (buttonDelta.width) { | 193 if (buttonDelta.width) { |
181 [okButton_ setFrame:NSOffsetRect([okButton_ frame], -buttonDelta.width, 0)]; | 194 [okButton_ setFrame:NSOffsetRect([okButton_ frame], -buttonDelta.width, 0)]; |
182 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame], | 195 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame], |
183 -buttonDelta.width, 0)]; | 196 -buttonDelta.width, 0)]; |
184 } | 197 } |
185 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_]; | 198 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_]; |
186 if (buttonDelta.width) { | 199 if (buttonDelta.width) { |
187 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame], | 200 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame], |
188 -buttonDelta.width, 0)]; | 201 -buttonDelta.width, 0)]; |
189 } | 202 } |
190 | 203 |
| 204 // The dialog is laid out in the NIB exactly how we want it assuming that |
| 205 // each label fits on one line. However, for each label, we want to allow |
| 206 // wrapping onto multiple lines. So we accumulate an offset by measuring how |
| 207 // big each label wants to be, and comparing it to how big it actually is. |
| 208 // Then we shift each label down and resize by the appropriate amount, then |
| 209 // finally resize the window. |
191 CGFloat totalOffset = 0.0; | 210 CGFloat totalOffset = 0.0; |
| 211 |
| 212 if ([self isBundleInstall]) { |
| 213 [subtitleField_ setStringValue:base::SysUTF16ToNSString( |
| 214 prompt_->GetPermissionsHeading())]; |
| 215 |
| 216 // We display the list of extension names as a simple text string, seperated |
| 217 // by newlines. |
| 218 BundleInstaller::ItemList items = prompt_->bundle()->GetItemsWithState( |
| 219 BundleInstaller::Item::STATE_PENDING); |
| 220 |
| 221 NSMutableString* joinedItems = [NSMutableString string]; |
| 222 for (size_t i = 0; i < items.size(); ++i) { |
| 223 if (i > 0) |
| 224 [joinedItems appendString:@"\n"]; |
| 225 [joinedItems appendString:base::SysUTF16ToNSString( |
| 226 items[i].GetNameForDisplay())]; |
| 227 } |
| 228 [itemsField_ setStringValue:joinedItems]; |
| 229 |
| 230 // Adjust the controls to fit the list of extensions. |
| 231 OffsetControlVerticallyToFitContent(titleField_, &totalOffset); |
| 232 OffsetControlVerticallyToFitContent(itemsField_, &totalOffset); |
| 233 } |
| 234 |
192 // If there are any warnings, then we have to do some special layout. | 235 // If there are any warnings, then we have to do some special layout. |
193 if (prompt_->GetPermissionCount() > 0) { | 236 if (prompt_->GetPermissionCount() > 0) { |
194 [subtitleField_ setStringValue:base::SysUTF16ToNSString( | 237 [subtitleField_ setStringValue:base::SysUTF16ToNSString( |
195 prompt_->GetPermissionsHeading())]; | 238 prompt_->GetPermissionsHeading())]; |
196 | 239 |
197 // We display the permission warnings as a simple text string, separated by | 240 // We display the permission warnings as a simple text string, separated by |
198 // newlines. | 241 // newlines. |
199 NSMutableString* joinedWarnings = [NSMutableString string]; | 242 NSMutableString* joinedWarnings = [NSMutableString string]; |
200 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) { | 243 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) { |
201 if (i > 0) | 244 if (i > 0) |
202 [joinedWarnings appendString:@"\n"]; | 245 [joinedWarnings appendString:@"\n"]; |
203 [joinedWarnings appendString:base::SysUTF16ToNSString( | 246 [joinedWarnings appendString:base::SysUTF16ToNSString( |
204 prompt_->GetPermission(i))]; | 247 prompt_->GetPermission(i))]; |
205 } | 248 } |
206 [warningsField_ setStringValue:joinedWarnings]; | 249 [warningsField_ setStringValue:joinedWarnings]; |
207 | 250 |
208 // The dialog is laid out in the NIB exactly how we want it assuming that | 251 // In the store version of the dialog the icon extends past the one-line |
209 // each label fits on one line. However, for each label, we want to allow | 252 // version of the permission field. Therefore when increasing the window |
210 // wrapping onto multiple lines. So we accumulate an offset by measuring how | 253 // size for multi-line permissions we don't have to add the full offset, |
211 // big each label wants to be, and comparing it to how big it actually is. | 254 // only the part that extends past the icon. |
212 // Then we shift each label down and resize by the appropriate amount, then | |
213 // finally resize the window. | |
214 | |
215 // Additionally, in the store version of the dialog the icon extends past | |
216 // the one-line version of the permission field. Therefore when increasing | |
217 // the window size for multi-line permissions we don't have to add the full | |
218 // offset, only the part that extends past the icon. | |
219 CGFloat warningsGrowthSlack = 0; | 255 CGFloat warningsGrowthSlack = 0; |
220 if ([warningsField_ frame].origin.y > [iconView_ frame].origin.y) { | 256 if (![self isBundleInstall] && |
| 257 [warningsField_ frame].origin.y > [iconView_ frame].origin.y) { |
221 warningsGrowthSlack = | 258 warningsGrowthSlack = |
222 [warningsField_ frame].origin.y - [iconView_ frame].origin.y; | 259 [warningsField_ frame].origin.y - [iconView_ frame].origin.y; |
223 } | 260 } |
224 | 261 |
225 totalOffset += AdjustControlHeightToFitContent(subtitleField_); | 262 // Adjust the controls to fit the permission warnings. |
226 OffsetControlVertically(subtitleField_, -totalOffset); | 263 OffsetControlVerticallyToFitContent(subtitleField_, &totalOffset); |
| 264 OffsetControlVerticallyToFitContent(warningsField_, &totalOffset); |
227 | 265 |
228 totalOffset += AdjustControlHeightToFitContent(warningsField_); | |
229 OffsetControlVertically(warningsField_, -totalOffset); | |
230 totalOffset = MAX(totalOffset - warningsGrowthSlack, 0); | 266 totalOffset = MAX(totalOffset - warningsGrowthSlack, 0); |
231 } else if ([self isInlineInstall]) { | 267 } else if ([self isInlineInstall] || [self isBundleInstall]) { |
232 // Inline installs that don't have a permissions section need to hide | 268 // Inline and bundle installs that don't have a permissions section need to |
233 // controls related to that and shrink the window by the space they take | 269 // hide controls related to that and shrink the window by the space they |
234 // up. | 270 // take up. |
235 NSRect hiddenRect = NSUnionRect([warningsSeparator_ frame], | 271 NSRect hiddenRect = NSUnionRect([warningsSeparator_ frame], |
236 [subtitleField_ frame]); | 272 [subtitleField_ frame]); |
237 hiddenRect = NSUnionRect(hiddenRect, [warningsField_ frame]); | 273 hiddenRect = NSUnionRect(hiddenRect, [warningsField_ frame]); |
238 [warningsSeparator_ setHidden:YES]; | 274 [warningsSeparator_ setHidden:YES]; |
239 [subtitleField_ setHidden:YES]; | 275 [subtitleField_ setHidden:YES]; |
240 [warningsField_ setHidden:YES]; | 276 [warningsField_ setHidden:YES]; |
241 totalOffset -= hiddenRect.size.height + kWarningsSeparatorPadding; | 277 totalOffset -= hiddenRect.size.height + kWarningsSeparatorPadding; |
242 } | 278 } |
243 | 279 |
244 // If necessary, adjust the window size. | 280 // If necessary, adjust the window size. |
245 if (totalOffset) { | 281 if (totalOffset) { |
246 NSRect currentRect = [[self window] frame]; | 282 NSRect currentRect = [[self window] frame]; |
247 [[self window] setFrame:NSMakeRect(currentRect.origin.x, | 283 [[self window] setFrame:NSMakeRect(currentRect.origin.x, |
248 currentRect.origin.y - totalOffset, | 284 currentRect.origin.y - totalOffset, |
249 currentRect.size.width, | 285 currentRect.size.width, |
250 currentRect.size.height + totalOffset) | 286 currentRect.size.height + totalOffset) |
251 display:NO]; | 287 display:NO]; |
252 } | 288 } |
253 } | 289 } |
254 | 290 |
255 - (void)didEndSheet:(NSWindow*)sheet | 291 - (void)didEndSheet:(NSWindow*)sheet |
256 returnCode:(int)returnCode | 292 returnCode:(int)returnCode |
257 contextInfo:(void*)contextInfo { | 293 contextInfo:(void*)contextInfo { |
258 [sheet close]; | 294 [sheet close]; |
259 } | 295 } |
260 | 296 |
261 - (void)windowWillClose:(NSNotification*)notification { | 297 - (void)windowWillClose:(NSNotification*)notification { |
262 [self autorelease]; | 298 [self autorelease]; |
263 } | 299 } |
264 | 300 |
265 - (bool)isInlineInstall { | 301 - (BOOL)isBundleInstall { |
| 302 return prompt_->type() == ExtensionInstallUI::BUNDLE_INSTALL_PROMPT; |
| 303 } |
| 304 |
| 305 - (BOOL)isInlineInstall { |
266 return prompt_->type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT; | 306 return prompt_->type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT; |
267 } | 307 } |
268 | 308 |
269 - (void)appendRatingStar:(const SkBitmap*)skiaImage { | 309 - (void)appendRatingStar:(const SkBitmap*)skiaImage { |
270 NSImage* image = gfx::SkBitmapToNSImageWithColorSpace( | 310 NSImage* image = gfx::SkBitmapToNSImageWithColorSpace( |
271 *skiaImage, base::mac::GetSystemColorSpace()); | 311 *skiaImage, base::mac::GetSystemColorSpace()); |
272 NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height()); | 312 NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height()); |
273 scoped_nsobject<NSImageView> view([[NSImageView alloc] initWithFrame:frame]); | 313 scoped_nsobject<NSImageView> view([[NSImageView alloc] initWithFrame:frame]); |
274 [view setImage:image]; | 314 [view setImage:image]; |
275 | 315 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
307 ExtensionInstallDialogController* controller = | 347 ExtensionInstallDialogController* controller = |
308 [[ExtensionInstallDialogController alloc] | 348 [[ExtensionInstallDialogController alloc] |
309 initWithParentWindow:native_window | 349 initWithParentWindow:native_window |
310 profile:profile | 350 profile:profile |
311 delegate:delegate | 351 delegate:delegate |
312 prompt:prompt]; | 352 prompt:prompt]; |
313 | 353 |
314 // TODO(mihaip): Switch this to be tab-modal (http://crbug.com/95455) | 354 // TODO(mihaip): Switch this to be tab-modal (http://crbug.com/95455) |
315 [controller runAsModalSheet]; | 355 [controller runAsModalSheet]; |
316 } | 356 } |
OLD | NEW |