OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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/mac/mac_util.h" | 7 #include "base/mac/mac_util.h" |
| 8 #include "base/memory/scoped_nsobject.h" |
8 #include "base/string_util.h" | 9 #include "base/string_util.h" |
9 #include "base/sys_string_conversions.h" | 10 #include "base/sys_string_conversions.h" |
10 #include "base/utf_string_conversions.h" | 11 #include "base/utf_string_conversions.h" |
11 #include "chrome/browser/extensions/extension_install_dialog.h" | 12 #include "chrome/browser/extensions/extension_install_dialog.h" |
12 #include "chrome/browser/ui/browser.h" | 13 #include "chrome/browser/ui/browser.h" |
13 #include "chrome/browser/ui/browser_list.h" | 14 #include "chrome/browser/ui/browser_list.h" |
14 #include "chrome/browser/ui/browser_window.h" | 15 #include "chrome/browser/ui/browser_window.h" |
15 #include "chrome/common/extensions/extension.h" | 16 #include "chrome/common/extensions/extension.h" |
16 #include "grit/generated_resources.h" | 17 #include "grit/generated_resources.h" |
17 #include "skia/ext/skia_utils_mac.h" | 18 #include "skia/ext/skia_utils_mac.h" |
| 19 #import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" |
18 #include "ui/base/l10n/l10n_util.h" | 20 #include "ui/base/l10n/l10n_util.h" |
19 #include "ui/base/l10n/l10n_util_mac.h" | 21 #include "ui/base/l10n/l10n_util_mac.h" |
20 | 22 |
| 23 @interface ExtensionInstallDialogController () |
| 24 - (bool)isInlineInstall; |
| 25 - (void)appendRatingStar:(const SkBitmap*)skiaImage; |
| 26 @end |
| 27 |
21 namespace { | 28 namespace { |
22 | 29 |
| 30 // Padding above the warnings separator, we must also subtract this when hiding |
| 31 // it. |
| 32 const CGFloat kWarningsSeparatorPadding = 14; |
| 33 |
23 // Maximum height we will adjust controls to when trying to accomodate their | 34 // Maximum height we will adjust controls to when trying to accomodate their |
24 // contents. | 35 // contents. |
25 const CGFloat kMaxControlHeight = 400; | 36 const CGFloat kMaxControlHeight = 400; |
26 | 37 |
27 // Adjust a control's height so that its content its not clipped. Returns the | 38 // Adjust a control's height so that its content its not clipped. Returns the |
28 // amount the control's height had to be adjusted. | 39 // amount the control's height had to be adjusted. |
29 CGFloat AdjustControlHeightToFitContent(NSControl* control) { | 40 CGFloat AdjustControlHeightToFitContent(NSControl* control) { |
30 NSRect currentRect = [control frame]; | 41 NSRect currentRect = [control frame]; |
31 NSRect fitRect = currentRect; | 42 NSRect fitRect = currentRect; |
32 fitRect.size.height = kMaxControlHeight; | 43 fitRect.size.height = kMaxControlHeight; |
33 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height; | 44 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height; |
34 CGFloat offset = desiredHeight - currentRect.size.height; | 45 CGFloat offset = desiredHeight - currentRect.size.height; |
35 | 46 |
36 [control setFrameSize:NSMakeSize(currentRect.size.width, | 47 [control setFrameSize:NSMakeSize(currentRect.size.width, |
37 currentRect.size.height + offset)]; | 48 currentRect.size.height + offset)]; |
38 return offset; | 49 return offset; |
39 } | 50 } |
40 | 51 |
41 // Moves the control vertically by the specified amount. | 52 // Moves the control vertically by the specified amount. |
42 void OffsetControlVertically(NSControl* control, CGFloat amount) { | 53 void OffsetControlVertically(NSControl* control, CGFloat amount) { |
43 NSPoint origin = [control frame].origin; | 54 NSPoint origin = [control frame].origin; |
44 origin.y += amount; | 55 origin.y += amount; |
45 [control setFrameOrigin:origin]; | 56 [control setFrameOrigin:origin]; |
46 } | 57 } |
47 | 58 |
| 59 void AppendRatingStarsShim(const SkBitmap* skiaImage, void* data) { |
| 60 ExtensionInstallDialogController* controller = |
| 61 static_cast<ExtensionInstallDialogController*>(data); |
| 62 [controller appendRatingStar:skiaImage]; |
| 63 } |
| 64 |
48 } | 65 } |
49 | 66 |
50 @implementation ExtensionInstallDialogController | 67 @implementation ExtensionInstallDialogController |
51 | 68 |
52 @synthesize iconView = iconView_; | 69 @synthesize iconView = iconView_; |
53 @synthesize titleField = titleField_; | 70 @synthesize titleField = titleField_; |
54 @synthesize subtitleField = subtitleField_; | 71 @synthesize subtitleField = subtitleField_; |
55 @synthesize warningsField = warningsField_; | 72 @synthesize warningsField = warningsField_; |
56 @synthesize warningsBox= warningsBox_; | |
57 @synthesize cancelButton = cancelButton_; | 73 @synthesize cancelButton = cancelButton_; |
58 @synthesize okButton = okButton_; | 74 @synthesize okButton = okButton_; |
| 75 @synthesize warningsSeparator = warningsSeparator_; |
| 76 @synthesize ratingStars = ratingStars_; |
| 77 @synthesize ratingCountField = ratingCountField_; |
| 78 @synthesize userCountField = userCountField_; |
59 | 79 |
60 - (id)initWithParentWindow:(NSWindow*)window | 80 - (id)initWithParentWindow:(NSWindow*)window |
61 profile:(Profile*)profile | 81 profile:(Profile*)profile |
62 extension:(const Extension*)extension | 82 extension:(const Extension*)extension |
63 delegate:(ExtensionInstallUI::Delegate*)delegate | 83 delegate:(ExtensionInstallUI::Delegate*)delegate |
64 icon:(SkBitmap*)icon | 84 icon:(SkBitmap*)icon |
65 prompt:(const ExtensionInstallUI::Prompt&)prompt { | 85 prompt:(const ExtensionInstallUI::Prompt&)prompt { |
66 NSString* nibpath = nil; | 86 NSString* nibpath = nil; |
67 | 87 |
68 // We use a different XIB in the case of no permission warnings, that is a | 88 // We use a different XIB in the case of inline installs or no permission |
69 // little bit more nicely laid out. | 89 // warnings, that respectively show webstore ratings data and are a more |
70 if (prompt.GetPermissionCount() == 0) { | 90 // nicely laid out. |
| 91 if (prompt.type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT) { |
| 92 nibpath = [base::mac::MainAppBundle() |
| 93 pathForResource:@"ExtensionInstallPromptInline" |
| 94 ofType:@"nib"]; |
| 95 } else if (prompt.GetPermissionCount() == 0) { |
71 nibpath = [base::mac::MainAppBundle() | 96 nibpath = [base::mac::MainAppBundle() |
72 pathForResource:@"ExtensionInstallPromptNoWarnings" | 97 pathForResource:@"ExtensionInstallPromptNoWarnings" |
73 ofType:@"nib"]; | 98 ofType:@"nib"]; |
74 } else { | 99 } else { |
75 nibpath = [base::mac::MainAppBundle() | 100 nibpath = [base::mac::MainAppBundle() |
76 pathForResource:@"ExtensionInstallPrompt" | 101 pathForResource:@"ExtensionInstallPrompt" |
77 ofType:@"nib"]; | 102 ofType:@"nib"]; |
78 } | 103 } |
79 | 104 |
80 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { | 105 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { |
81 parentWindow_ = window; | 106 parentWindow_ = window; |
82 profile_ = profile; | 107 profile_ = profile; |
83 icon_ = *icon; | 108 icon_ = *icon; |
84 delegate_ = delegate; | 109 delegate_ = delegate; |
85 | 110 extension_ = extension; |
86 title_.reset([base::SysUTF16ToNSString( | 111 prompt_.reset(new ExtensionInstallUI::Prompt(prompt)); |
87 prompt.GetHeading(extension->name())) retain]); | |
88 subtitle_.reset([base::SysUTF16ToNSString( | |
89 prompt.GetPermissionsHeader()) retain]); | |
90 button_.reset([base::SysUTF16ToNSString( | |
91 prompt.GetAcceptButtonLabel()) retain]); | |
92 NSString* cancel_button_label = prompt.HasAbortButtonLabel() ? | |
93 base::SysUTF16ToNSString(prompt.GetAbortButtonLabel()) : | |
94 l10n_util::GetNSString(IDS_CANCEL); | |
95 cancel_button_.reset([cancel_button_label retain]); | |
96 | |
97 // We display the permission warnings as a simple text string, separated by | |
98 // newlines. | |
99 if (prompt.GetPermissionCount()) { | |
100 string16 joined_warnings; | |
101 for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) { | |
102 if (i > 0) | |
103 joined_warnings += UTF8ToUTF16("\n\n"); | |
104 | |
105 joined_warnings += prompt.GetPermission(i); | |
106 } | |
107 | |
108 warnings_.reset( | |
109 [base::SysUTF16ToNSString(joined_warnings) retain]); | |
110 } | |
111 } | 112 } |
112 return self; | 113 return self; |
113 } | 114 } |
114 | 115 |
115 - (void)runAsModalSheet { | 116 - (void)runAsModalSheet { |
116 [NSApp beginSheet:[self window] | 117 [NSApp beginSheet:[self window] |
117 modalForWindow:parentWindow_ | 118 modalForWindow:parentWindow_ |
118 modalDelegate:self | 119 modalDelegate:self |
119 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) | 120 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) |
120 contextInfo:nil]; | 121 contextInfo:nil]; |
121 } | 122 } |
122 | 123 |
123 - (IBAction)cancel:(id)sender { | 124 - (IBAction)storeLinkClicked:(id)sender { |
124 delegate_->InstallUIAbort(true); | 125 GURL store_url( |
| 126 extension_urls::GetWebstoreItemDetailURLPrefix() + extension_->id()); |
| 127 BrowserList::GetLastActiveWithProfile(profile_)-> |
| 128 OpenURL(store_url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); |
| 129 |
| 130 delegate_->InstallUIAbort(/*user_initiated=*/true); |
125 [NSApp endSheet:[self window]]; | 131 [NSApp endSheet:[self window]]; |
126 } | 132 } |
127 | 133 |
| 134 - (IBAction)cancel:(id)sender { |
| 135 delegate_->InstallUIAbort(/*user_initiated=*/true); |
| 136 [NSApp endSheet:[self window]]; |
| 137 } |
| 138 |
128 - (IBAction)ok:(id)sender { | 139 - (IBAction)ok:(id)sender { |
129 delegate_->InstallUIProceed(); | 140 delegate_->InstallUIProceed(); |
130 [NSApp endSheet:[self window]]; | 141 [NSApp endSheet:[self window]]; |
131 } | 142 } |
132 | 143 |
133 - (void)awakeFromNib { | 144 - (void)awakeFromNib { |
134 [titleField_ setStringValue:title_.get()]; | 145 // Make sure we're the window's delegate as set in the nib. |
135 [subtitleField_ setStringValue:subtitle_.get()]; | 146 DCHECK_EQ(self, static_cast<ExtensionInstallDialogController*>( |
136 [okButton_ setTitle:button_.get()]; | 147 [[self window] delegate])); |
137 [cancelButton_ setTitle:cancel_button_.get()]; | 148 |
| 149 // Set control labels. |
| 150 [titleField_ setStringValue:base::SysUTF16ToNSString( |
| 151 prompt_->GetHeading(extension_->name()))]; |
| 152 [okButton_ setTitle:base::SysUTF16ToNSString( |
| 153 prompt_->GetAcceptButtonLabel())]; |
| 154 [cancelButton_ setTitle:prompt_->HasAbortButtonLabel() ? |
| 155 base::SysUTF16ToNSString(prompt_->GetAbortButtonLabel()) : |
| 156 l10n_util::GetNSString(IDS_CANCEL)]; |
| 157 if ([self isInlineInstall]) { |
| 158 prompt_->AppendRatingStars(AppendRatingStarsShim, self); |
| 159 [ratingCountField_ setStringValue:base::SysUTF16ToNSString( |
| 160 prompt_->GetRatingCount())]; |
| 161 [userCountField_ setStringValue:base::SysUTF16ToNSString( |
| 162 prompt_->GetUserCount())]; |
| 163 } |
138 | 164 |
139 NSImage* image = gfx::SkBitmapToNSImage(icon_); | 165 NSImage* image = gfx::SkBitmapToNSImage(icon_); |
140 [iconView_ setImage:image]; | 166 [iconView_ setImage:image]; |
141 | 167 |
142 // Reisze |titleField_| to fit title | 168 // Resize |titleField_| to fit the title. |
143 CGFloat originalTitleWidth = [titleField_ frame].size.width; | 169 CGFloat originalTitleWidth = [titleField_ frame].size.width; |
144 [titleField_ sizeToFit]; | 170 [titleField_ sizeToFit]; |
145 CGFloat newTitleWidth = [titleField_ frame].size.width; | 171 CGFloat newTitleWidth = [titleField_ frame].size.width; |
146 if (newTitleWidth > originalTitleWidth) { | 172 if (newTitleWidth > originalTitleWidth) { |
147 NSRect frame = [[self window] frame]; | 173 NSRect frame = [[self window] frame]; |
148 frame.size.width += newTitleWidth - originalTitleWidth; | 174 frame.size.width += newTitleWidth - originalTitleWidth; |
149 [[self window] setFrame:frame display:NO]; | 175 [[self window] setFrame:frame display:NO]; |
150 } | 176 } |
151 | 177 |
152 // Make sure we're the window's delegate as set in the nib. | 178 // Resize |okButton_| and |cancelButton_| to fit the button labels, but keep |
153 DCHECK_EQ(self, static_cast<ExtensionInstallDialogController*>( | 179 // them right-aligned. |
154 [[self window] delegate])); | 180 NSSize buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_]; |
| 181 if (buttonDelta.width) { |
| 182 [okButton_ setFrame:NSOffsetRect([okButton_ frame], -buttonDelta.width, 0)]; |
| 183 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame], |
| 184 -buttonDelta.width, 0)]; |
| 185 } |
| 186 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_]; |
| 187 if (buttonDelta.width) { |
| 188 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame], |
| 189 -buttonDelta.width, 0)]; |
| 190 } |
155 | 191 |
| 192 CGFloat totalOffset = 0.0; |
156 // If there are any warnings, then we have to do some special layout. | 193 // If there are any warnings, then we have to do some special layout. |
157 if ([warnings_.get() length] > 0) { | 194 if (prompt_->GetPermissionCount() > 0) { |
158 [warningsField_ setStringValue:warnings_.get()]; | 195 [subtitleField_ setStringValue:base::SysUTF16ToNSString( |
| 196 prompt_->GetPermissionsHeader())]; |
| 197 |
| 198 // We display the permission warnings as a simple text string, separated by |
| 199 // newlines. |
| 200 NSMutableString* joinedWarnings = [NSMutableString string]; |
| 201 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) { |
| 202 if (i > 0) |
| 203 [joinedWarnings appendString:@"\n"]; |
| 204 [joinedWarnings appendString:base::SysUTF16ToNSString( |
| 205 prompt_->GetPermission(i))]; |
| 206 } |
| 207 [warningsField_ setStringValue:joinedWarnings]; |
159 | 208 |
160 // The dialog is laid out in the NIB exactly how we want it assuming that | 209 // The dialog is laid out in the NIB exactly how we want it assuming that |
161 // each label fits on one line. However, for each label, we want to allow | 210 // each label fits on one line. However, for each label, we want to allow |
162 // wrapping onto multiple lines. So we accumulate an offset by measuring how | 211 // wrapping onto multiple lines. So we accumulate an offset by measuring how |
163 // big each label wants to be, and comparing it to how bit it actually is. | 212 // big each label wants to be, and comparing it to how big it actually is. |
164 // Then we shift each label down and resize by the appropriate amount, then | 213 // Then we shift each label down and resize by the appropriate amount, then |
165 // finally resize the window. | 214 // finally resize the window. |
166 CGFloat totalOffset = 0.0; | |
167 | 215 |
168 // Text fields. | 216 // Additionally, in the store version of the dialog the icon extends past |
169 totalOffset += AdjustControlHeightToFitContent(titleField_); | 217 // the one-line version of the permission field. Therefore when increasing |
170 OffsetControlVertically(titleField_, -totalOffset); | 218 // the window size for multi-line permissions we don't have to add the full |
| 219 // offset, only the part that extends past the icon. |
| 220 CGFloat warningsGrowthSlack = 0; |
| 221 if ([warningsField_ frame].origin.y > [iconView_ frame].origin.y) { |
| 222 warningsGrowthSlack = |
| 223 [warningsField_ frame].origin.y - [iconView_ frame].origin.y; |
| 224 } |
171 | 225 |
172 totalOffset += AdjustControlHeightToFitContent(subtitleField_); | 226 totalOffset += AdjustControlHeightToFitContent(subtitleField_); |
173 OffsetControlVertically(subtitleField_, -totalOffset); | 227 OffsetControlVertically(subtitleField_, -totalOffset); |
174 | 228 |
175 CGFloat warningsOffset = AdjustControlHeightToFitContent(warningsField_); | 229 totalOffset += AdjustControlHeightToFitContent(warningsField_); |
176 OffsetControlVertically(warningsField_, -warningsOffset); | 230 OffsetControlVertically(warningsField_, -totalOffset); |
177 totalOffset += warningsOffset; | 231 totalOffset = MAX(totalOffset - warningsGrowthSlack, 0); |
| 232 } else if ([self isInlineInstall]) { |
| 233 // Inline installs that don't have a permissions section need to hide |
| 234 // controls related to that and shrink the window by the space they take |
| 235 // up. |
| 236 NSRect hiddenRect = NSUnionRect([warningsSeparator_ frame], |
| 237 [subtitleField_ frame]); |
| 238 hiddenRect = NSUnionRect(hiddenRect, [warningsField_ frame]); |
| 239 [warningsSeparator_ setHidden:YES]; |
| 240 [subtitleField_ setHidden:YES]; |
| 241 [warningsField_ setHidden:YES]; |
| 242 totalOffset -= hiddenRect.size.height + kWarningsSeparatorPadding; |
| 243 } |
178 | 244 |
179 NSRect warningsBoxRect = [warningsBox_ frame]; | 245 // If necessary, adjust the window size. |
180 warningsBoxRect.origin.y -= totalOffset; | 246 if (totalOffset) { |
181 warningsBoxRect.size.height += warningsOffset; | |
182 [warningsBox_ setFrame:warningsBoxRect]; | |
183 | |
184 // buttons are positioned automatically in the XIB. | |
185 | |
186 // Finally, adjust the window size. | |
187 NSRect currentRect = [[self window] frame]; | 247 NSRect currentRect = [[self window] frame]; |
188 [[self window] setFrame:NSMakeRect(currentRect.origin.x, | 248 [[self window] setFrame:NSMakeRect(currentRect.origin.x, |
189 currentRect.origin.y - totalOffset, | 249 currentRect.origin.y - totalOffset, |
190 currentRect.size.width, | 250 currentRect.size.width, |
191 currentRect.size.height + totalOffset) | 251 currentRect.size.height + totalOffset) |
192 display:NO]; | 252 display:NO]; |
193 } | 253 } |
194 } | 254 } |
195 | 255 |
196 - (void)didEndSheet:(NSWindow*)sheet | 256 - (void)didEndSheet:(NSWindow*)sheet |
197 returnCode:(int)returnCode | 257 returnCode:(int)returnCode |
198 contextInfo:(void*)contextInfo { | 258 contextInfo:(void*)contextInfo { |
199 [sheet close]; | 259 [sheet close]; |
200 } | 260 } |
201 | 261 |
202 - (void)windowWillClose:(NSNotification*)notification { | 262 - (void)windowWillClose:(NSNotification*)notification { |
203 [self autorelease]; | 263 [self autorelease]; |
204 } | 264 } |
205 | 265 |
| 266 - (bool)isInlineInstall { |
| 267 return prompt_->type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT; |
| 268 } |
| 269 |
| 270 - (void)appendRatingStar:(const SkBitmap*)skiaImage { |
| 271 NSImage* image = gfx::SkBitmapToNSImageWithColorSpace( |
| 272 *skiaImage, base::mac::GetSystemColorSpace()); |
| 273 NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height()); |
| 274 scoped_nsobject<NSImageView> view([[NSImageView alloc] initWithFrame:frame]); |
| 275 [view setImage:image]; |
| 276 |
| 277 // Add this star after all the other ones |
| 278 CGFloat maxStarRight = 0; |
| 279 if ([[ratingStars_ subviews] count]) { |
| 280 maxStarRight = NSMaxX([[[ratingStars_ subviews] lastObject] frame]); |
| 281 } |
| 282 NSRect starBounds = NSMakeRect(maxStarRight, 0, |
| 283 skiaImage->width(), skiaImage->height()); |
| 284 [view setFrame:starBounds]; |
| 285 [ratingStars_ addSubview:view]; |
| 286 } |
| 287 |
206 @end // ExtensionInstallDialogController | 288 @end // ExtensionInstallDialogController |
207 | 289 |
208 void ShowExtensionInstallDialog( | 290 void ShowExtensionInstallDialog( |
209 Profile* profile, | 291 Profile* profile, |
210 ExtensionInstallUI::Delegate* delegate, | 292 ExtensionInstallUI::Delegate* delegate, |
211 const Extension* extension, | 293 const Extension* extension, |
212 SkBitmap* icon, | 294 SkBitmap* icon, |
213 const ExtensionInstallUI::Prompt& prompt) { | 295 const ExtensionInstallUI::Prompt& prompt) { |
214 Browser* browser = BrowserList::GetLastActiveWithProfile(profile); | 296 Browser* browser = BrowserList::GetLastActiveWithProfile(profile); |
215 if (!browser) { | 297 if (!browser) { |
(...skipping 11 matching lines...) Expand all Loading... |
227 | 309 |
228 ExtensionInstallDialogController* controller = | 310 ExtensionInstallDialogController* controller = |
229 [[ExtensionInstallDialogController alloc] | 311 [[ExtensionInstallDialogController alloc] |
230 initWithParentWindow:native_window | 312 initWithParentWindow:native_window |
231 profile:profile | 313 profile:profile |
232 extension:extension | 314 extension:extension |
233 delegate:delegate | 315 delegate:delegate |
234 icon:icon | 316 icon:icon |
235 prompt:prompt]; | 317 prompt:prompt]; |
236 | 318 |
| 319 // TODO(mihaip): Switch this to be tab-modal (http://crbug.com/95455) |
237 [controller runAsModalSheet]; | 320 [controller runAsModalSheet]; |
238 } | 321 } |
OLD | NEW |