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