Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(335)

Side by Side Diff: chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm

Issue 7835039: Implement the inline extensions/apps install UI for Cocoa. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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"
18 #include "ui/base/l10n/l10n_util.h" 19 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/l10n/l10n_util_mac.h" 20 #include "ui/base/l10n/l10n_util_mac.h"
20 21
22 @interface ExtensionInstallDialogController ()
23 - (bool)isInlineInstall;
24 - (void)appendRatingStar:(const SkBitmap*)skiaImage;
25 @end
26
21 namespace { 27 namespace {
22 28
29 // Padding above the warnings separator, we must also subtract this when hiding
30 // it.
31 const CGFloat kWarningsSeparatorPadding = 14;
32
23 // Maximum height we will adjust controls to when trying to accomodate their 33 // Maximum height we will adjust controls to when trying to accomodate their
24 // contents. 34 // contents.
25 const CGFloat kMaxControlHeight = 400; 35 const CGFloat kMaxControlHeight = 400;
26 36
27 // Adjust a control's height so that its content its not clipped. Returns the 37 // Adjust a control's height so that its content its not clipped. Returns the
28 // amount the control's height had to be adjusted. 38 // amount the control's height had to be adjusted.
29 CGFloat AdjustControlHeightToFitContent(NSControl* control) { 39 CGFloat AdjustControlHeightToFitContent(NSControl* control) {
30 NSRect currentRect = [control frame]; 40 NSRect currentRect = [control frame];
31 NSRect fitRect = currentRect; 41 NSRect fitRect = currentRect;
32 fitRect.size.height = kMaxControlHeight; 42 fitRect.size.height = kMaxControlHeight;
33 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height; 43 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height;
34 CGFloat offset = desiredHeight - currentRect.size.height; 44 CGFloat offset = desiredHeight - currentRect.size.height;
35 45
36 [control setFrameSize:NSMakeSize(currentRect.size.width, 46 [control setFrameSize:NSMakeSize(currentRect.size.width,
37 currentRect.size.height + offset)]; 47 currentRect.size.height + offset)];
38 return offset; 48 return offset;
39 } 49 }
40 50
41 // Moves the control vertically by the specified amount. 51 // Moves the control vertically by the specified amount.
42 void OffsetControlVertically(NSControl* control, CGFloat amount) { 52 void OffsetControlVertically(NSControl* control, CGFloat amount) {
43 NSPoint origin = [control frame].origin; 53 NSPoint origin = [control frame].origin;
44 origin.y += amount; 54 origin.y += amount;
45 [control setFrameOrigin:origin]; 55 [control setFrameOrigin:origin];
46 } 56 }
47 57
58 void AppendRatingStarsShim(const SkBitmap* skiaImage,
59 ExtensionInstallDialogController* controller) {
60 [controller appendRatingStar:skiaImage];
61 }
62
48 } 63 }
49 64
50 @implementation ExtensionInstallDialogController 65 @implementation ExtensionInstallDialogController
51 66
52 @synthesize iconView = iconView_; 67 @synthesize iconView = iconView_;
53 @synthesize titleField = titleField_; 68 @synthesize titleField = titleField_;
54 @synthesize subtitleField = subtitleField_; 69 @synthesize subtitleField = subtitleField_;
55 @synthesize warningsField = warningsField_; 70 @synthesize warningsField = warningsField_;
56 @synthesize warningsBox= warningsBox_;
57 @synthesize cancelButton = cancelButton_; 71 @synthesize cancelButton = cancelButton_;
58 @synthesize okButton = okButton_; 72 @synthesize okButton = okButton_;
73 @synthesize warningsSeparator = warningsSeparator_;
74 @synthesize ratingStars = ratingStars_;
75 @synthesize ratingCountField = ratingCountField_;
76 @synthesize userCountField = userCountField_;
59 77
60 - (id)initWithParentWindow:(NSWindow*)window 78 - (id)initWithParentWindow:(NSWindow*)window
61 profile:(Profile*)profile 79 profile:(Profile*)profile
62 extension:(const Extension*)extension 80 extension:(const Extension*)extension
63 delegate:(ExtensionInstallUI::Delegate*)delegate 81 delegate:(ExtensionInstallUI::Delegate*)delegate
64 icon:(SkBitmap*)icon 82 icon:(SkBitmap*)icon
65 prompt:(const ExtensionInstallUI::Prompt&)prompt { 83 prompt:(const ExtensionInstallUI::Prompt&)prompt {
84 prompt_.reset(new ExtensionInstallUI::Prompt(prompt));
Nico 2011/09/05 22:59:17 You shouldn't write member variables before "self
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Moved initialization of prompt_ to be after the [s
66 NSString* nibpath = nil; 85 NSString* nibpath = nil;
67 86
68 // We use a different XIB in the case of no permission warnings, that is a 87 // We use a different XIB in the case of inline installs or no permission
69 // little bit more nicely laid out. 88 // warnings, that respectively show webstore ratings data and are a more
70 if (prompt.GetPermissionCount() == 0) { 89 // nicely laid out.
90 if ([self isInlineInstall]) {
91 nibpath = [base::mac::MainAppBundle()
92 pathForResource:@"ExtensionInstallPromptInline"
93 ofType:@"nib"];
94 } else if (prompt.GetPermissionCount() == 0) {
71 nibpath = [base::mac::MainAppBundle() 95 nibpath = [base::mac::MainAppBundle()
72 pathForResource:@"ExtensionInstallPromptNoWarnings" 96 pathForResource:@"ExtensionInstallPromptNoWarnings"
73 ofType:@"nib"]; 97 ofType:@"nib"];
74 } else { 98 } else {
75 nibpath = [base::mac::MainAppBundle() 99 nibpath = [base::mac::MainAppBundle()
76 pathForResource:@"ExtensionInstallPrompt" 100 pathForResource:@"ExtensionInstallPrompt"
77 ofType:@"nib"]; 101 ofType:@"nib"];
78 } 102 }
79 103
80 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { 104 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
81 parentWindow_ = window; 105 parentWindow_ = window;
82 profile_ = profile; 106 profile_ = profile;
83 icon_ = *icon; 107 icon_ = *icon;
84 delegate_ = delegate; 108 delegate_ = delegate;
85 109 extension_ = extension;
86 title_.reset([base::SysUTF16ToNSString(
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 } 110 }
112 return self; 111 return self;
113 } 112 }
114 113
115 - (void)runAsModalSheet { 114 - (void)runAsModalSheet {
116 [NSApp beginSheet:[self window] 115 [NSApp beginSheet:[self window]
117 modalForWindow:parentWindow_ 116 modalForWindow:parentWindow_
118 modalDelegate:self 117 modalDelegate:self
119 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:) 118 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
120 contextInfo:nil]; 119 contextInfo:nil];
121 } 120 }
122 121
122 - (IBAction)storeLinkClicked:(id)sender {
123 GURL store_url(
124 extension_urls::GetWebstoreItemDetailURLPrefix() + extension_->id());
125 BrowserList::GetLastActiveWithProfile(profile_)->
126 OpenURL(store_url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
127
128 delegate_->InstallUIAbort(true);
Nico 2011/09/05 22:59:17 nit: Make it more clear what "true" means here, ei
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Done.
129 [NSApp endSheet:[self window]];
Nico 2011/09/05 22:59:17 Window-modal sheets are not cool. Have you thought
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Tab-modal dialogs sound like a good idea for a fol
Nico 2011/09/06 01:04:00 Yes. Thanks. (Maybe file a bug and add the bug #
Mihai Parparita -not on Chrome 2011/09/06 01:25:59 Filed http://crbug.com/95455
130 }
131
123 - (IBAction)cancel:(id)sender { 132 - (IBAction)cancel:(id)sender {
124 delegate_->InstallUIAbort(true); 133 delegate_->InstallUIAbort(true);
125 [NSApp endSheet:[self window]]; 134 [NSApp endSheet:[self window]];
126 } 135 }
127 136
128 - (IBAction)ok:(id)sender { 137 - (IBAction)ok:(id)sender {
129 delegate_->InstallUIProceed(); 138 delegate_->InstallUIProceed();
130 [NSApp endSheet:[self window]]; 139 [NSApp endSheet:[self window]];
131 } 140 }
132 141
133 - (void)awakeFromNib { 142 - (void)awakeFromNib {
134 [titleField_ setStringValue:title_.get()]; 143 // Make sure we're the window's delegate as set in the nib.
135 [subtitleField_ setStringValue:subtitle_.get()]; 144 DCHECK_EQ(self, static_cast<ExtensionInstallDialogController*>(
136 [okButton_ setTitle:button_.get()]; 145 [[self window] delegate]));
137 [cancelButton_ setTitle:cancel_button_.get()]; 146
147 // Set control labels
Nico 2011/09/05 22:59:17 nit: trailing period.
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Done.
148 [titleField_ setStringValue:base::SysUTF16ToNSString(
149 prompt_->GetHeading(extension_->name()))];
150 [okButton_ setTitle:base::SysUTF16ToNSString(
151 prompt_->GetAcceptButtonLabel())];
152 [cancelButton_ setTitle:prompt_->HasAbortButtonLabel() ?
153 base::SysUTF16ToNSString(prompt_->GetAbortButtonLabel()) :
154 l10n_util::GetNSString(IDS_CANCEL)];
Nico 2011/09/05 22:59:17 Do you need to resize the controls to make them ad
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 It looks like there's no autosizing going on (e.g.
Nico 2011/09/06 01:04:00 This is sometimes doable all in the xib file witho
Mihai Parparita -not on Chrome 2011/09/06 01:25:59 Yeah, I looked into that, but since I set the butt
155 if ([self isInlineInstall]) {
156 prompt_->AppendRatingStars(
157 reinterpret_cast<ExtensionInstallUI::Prompt::StarAppender>(
158 AppendRatingStarsShim),
Nico 2011/09/05 22:59:17 You need this cast because AppendRatingStarsShim d
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Done.
159 self);
160 [ratingCountField_ setStringValue:base::SysUTF16ToNSString(
161 prompt_->GetRatingCount())];
162 [userCountField_ setStringValue:base::SysUTF16ToNSString(
163 prompt_->GetUserCount())];
164 }
138 165
139 NSImage* image = gfx::SkBitmapToNSImage(icon_); 166 NSImage* image = gfx::SkBitmapToNSImage(icon_);
140 [iconView_ setImage:image]; 167 [iconView_ setImage:image];
141 168
142 // Reisze |titleField_| to fit title 169 // Resize |titleField_| to fit title
143 CGFloat originalTitleWidth = [titleField_ frame].size.width; 170 CGFloat originalTitleWidth = [titleField_ frame].size.width;
144 [titleField_ sizeToFit]; 171 [titleField_ sizeToFit];
145 CGFloat newTitleWidth = [titleField_ frame].size.width; 172 CGFloat newTitleWidth = [titleField_ frame].size.width;
146 if (newTitleWidth > originalTitleWidth) { 173 if (newTitleWidth > originalTitleWidth) {
147 NSRect frame = [[self window] frame]; 174 NSRect frame = [[self window] frame];
148 frame.size.width += newTitleWidth - originalTitleWidth; 175 frame.size.width += newTitleWidth - originalTitleWidth;
149 [[self window] setFrame:frame display:NO]; 176 [[self window] setFrame:frame display:NO];
150 } 177 }
151 178
152 // Make sure we're the window's delegate as set in the nib. 179 CGFloat totalOffset = 0.0;
153 DCHECK_EQ(self, static_cast<ExtensionInstallDialogController*>( 180 // If there are any warnings, then we have to do some special layout.
154 [[self window] delegate])); 181 if (prompt_->GetPermissionCount() > 0) {
182 [subtitleField_ setStringValue:base::SysUTF16ToNSString(
183 prompt_->GetPermissionsHeader())];
155 184
156 // If there are any warnings, then we have to do some special layout. 185 // We display the permission warnings as a simple text string, separated by
157 if ([warnings_.get() length] > 0) { 186 // newlines.
158 [warningsField_ setStringValue:warnings_.get()]; 187 string16 joined_warnings;
Nico 2011/09/05 22:59:17 nit: variables objc methods have camelCaps names (
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Leftover from past version of the file, fixed.
188 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) {
189 if (i > 0)
190 joined_warnings += UTF8ToUTF16("\n");
Nico 2011/09/05 22:59:17 nit: You could go from utf8 to nsstring directly (
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Done.
191
192 joined_warnings += prompt_->GetPermission(i);
193 }
194 [warningsField_ setStringValue:base::SysUTF16ToNSString(joined_warnings)];
159 195
160 // The dialog is laid out in the NIB exactly how we want it assuming that 196 // 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 197 // 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 198 // 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. 199 // 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 200 // Then we shift each label down and resize by the appropriate amount, then
165 // finally resize the window. 201 // finally resize the window.
166 CGFloat totalOffset = 0.0;
167 202
168 // Text fields. 203 // Additionally, in the store version of the dialog the icon extends past
169 totalOffset += AdjustControlHeightToFitContent(titleField_); 204 // the one-line version of the permission field. Therefore when increasing
170 OffsetControlVertically(titleField_, -totalOffset); 205 // the window size for multi-line permissions we don't have to add the full
206 //
Nico 2011/09/05 22:59:17 you accidentally the rest of the comment
Nico 2011/09/06 01:04:00 still missing
Mihai Parparita -not on Chrome 2011/09/06 01:25:59 Done.
207 CGFloat warningsGrowthSlack = 0;
208 if ([warningsField_ frame].origin.y > [iconView_ frame].origin.y) {
209 warningsGrowthSlack =
210 [warningsField_ frame].origin.y - [iconView_ frame].origin.y;
211 }
171 212
172 totalOffset += AdjustControlHeightToFitContent(subtitleField_); 213 totalOffset += AdjustControlHeightToFitContent(subtitleField_);
173 OffsetControlVertically(subtitleField_, -totalOffset); 214 OffsetControlVertically(subtitleField_, -totalOffset);
174 215
175 CGFloat warningsOffset = AdjustControlHeightToFitContent(warningsField_); 216 totalOffset += AdjustControlHeightToFitContent(warningsField_);
176 OffsetControlVertically(warningsField_, -warningsOffset); 217 OffsetControlVertically(warningsField_, -totalOffset);
177 totalOffset += warningsOffset; 218 totalOffset = MAX(totalOffset - warningsGrowthSlack, 0);
219 } else if ([self isInlineInstall]) {
220 // Inline installs that don't have a permissions section need to hide
221 // controls related to that and shrink the window by the space they take
222 // up.
223 NSRect hiddenRect = NSUnionRect([warningsSeparator_ frame],
224 [subtitleField_ frame]);
225 hiddenRect = NSUnionRect(hiddenRect, [warningsField_ frame]);
226 [warningsSeparator_ setHidden:YES];
227 [subtitleField_ setHidden:YES];
228 [warningsField_ setHidden:YES];
229 totalOffset -= hiddenRect.size.height + kWarningsSeparatorPadding;
230 }
178 231
179 NSRect warningsBoxRect = [warningsBox_ frame]; 232 // If necessary, adjust the window size.
180 warningsBoxRect.origin.y -= totalOffset; 233 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]; 234 NSRect currentRect = [[self window] frame];
188 [[self window] setFrame:NSMakeRect(currentRect.origin.x, 235 [[self window] setFrame:NSMakeRect(currentRect.origin.x,
189 currentRect.origin.y - totalOffset, 236 currentRect.origin.y - totalOffset,
190 currentRect.size.width, 237 currentRect.size.width,
191 currentRect.size.height + totalOffset) 238 currentRect.size.height + totalOffset)
192 display:NO]; 239 display:NO];
193 } 240 }
241
242 // Focus the cancel button, otherwise the store link gets the default focus
243 // for inline installs.
244 [[self window] makeFirstResponder:cancelButton_];
Nico 2011/09/05 22:59:17 I think you can set the first responder in the nib
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 I selected the window in Interface Builder and the
194 } 245 }
195 246
196 - (void)didEndSheet:(NSWindow*)sheet 247 - (void)didEndSheet:(NSWindow*)sheet
197 returnCode:(int)returnCode 248 returnCode:(int)returnCode
198 contextInfo:(void*)contextInfo { 249 contextInfo:(void*)contextInfo {
199 [sheet close]; 250 [sheet close];
200 } 251 }
201 252
202 - (void)windowWillClose:(NSNotification*)notification { 253 - (void)windowWillClose:(NSNotification*)notification {
203 [self autorelease]; 254 [self autorelease];
204 } 255 }
205 256
257 - (bool)isInlineInstall {
258 return prompt_->type() == ExtensionInstallUI::INLINE_INSTALL_PROMPT;
259 }
260
261 - (void)appendRatingStar:(const SkBitmap*)skiaImage {
262 NSImage* image = gfx::SkBitmapToNSImageWithColorSpace(
263 *skiaImage, base::mac::GetSystemColorSpace());
264 NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height());
265 NSImageView* view = [[[NSImageView alloc] initWithFrame:frame] autorelease];
Nico 2011/09/05 22:59:17 nit: I think we generally prefer scoped_nsobject o
Mihai Parparita -not on Chrome 2011/09/06 00:54:11 Done.
266 [view setImage:image];
267
268 // Add this star after all the other ones
269 CGFloat maxStarRight = 0;
270 if ([[ratingStars_ subviews] count]) {
271 maxStarRight = NSMaxX([[[ratingStars_ subviews] lastObject] frame]);
272 }
273 NSRect starBounds = NSMakeRect(maxStarRight, 0,
274 skiaImage->width(), skiaImage->height());
275 [view setFrame:starBounds];
276 [ratingStars_ addSubview:view];
277 }
278
206 @end // ExtensionInstallDialogController 279 @end // ExtensionInstallDialogController
207 280
208 void ShowExtensionInstallDialog( 281 void ShowExtensionInstallDialog(
209 Profile* profile, 282 Profile* profile,
210 ExtensionInstallUI::Delegate* delegate, 283 ExtensionInstallUI::Delegate* delegate,
211 const Extension* extension, 284 const Extension* extension,
212 SkBitmap* icon, 285 SkBitmap* icon,
213 const ExtensionInstallUI::Prompt& prompt) { 286 const ExtensionInstallUI::Prompt& prompt) {
214 Browser* browser = BrowserList::GetLastActiveWithProfile(profile); 287 Browser* browser = BrowserList::GetLastActiveWithProfile(profile);
215 if (!browser) { 288 if (!browser) {
(...skipping 13 matching lines...) Expand all
229 [[ExtensionInstallDialogController alloc] 302 [[ExtensionInstallDialogController alloc]
230 initWithParentWindow:native_window 303 initWithParentWindow:native_window
231 profile:profile 304 profile:profile
232 extension:extension 305 extension:extension
233 delegate:delegate 306 delegate:delegate
234 icon:icon 307 icon:icon
235 prompt:prompt]; 308 prompt:prompt];
236 309
237 [controller runAsModalSheet]; 310 [controller runAsModalSheet];
238 } 311 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698