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.
5 #import "chrome/browser/ui/cocoa/extensions/extension_install_view_controller.h"
7 #include "base/auto_reset.h"
8 #include "base/i18n/rtl.h"
9 #include "base/mac/bundle_locations.h"
10 #include "base/mac/mac_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/bundle_installer.h"
15 #import "chrome/browser/ui/chrome_style.h"
16 #include "chrome/common/extensions/extension_constants.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "content/public/browser/page_navigator.h"
19 #include "extensions/common/extension.h"
20 #include "extensions/common/extension_urls.h"
21 #include "skia/ext/skia_utils_mac.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/l10n/l10n_util_mac.h"
26 #include "ui/gfx/image/image_skia_util_mac.h"
28 using content::OpenURLParams;
29 using content::Referrer;
30 using extensions::BundleInstaller;
34 // A collection of attributes (bitmask) for how to draw a cell, the expand
35 // marker and the text in the cell.
36 enum CellAttributesMask {
38 kNoExpandMarker = 1 << 1,
40 kAutoExpandCell = 1 << 3,
41 kUseCustomLinkCell = 1 << 4,
45 typedef NSUInteger CellAttributes;
49 @interface ExtensionInstallViewController ()
50 - (BOOL)isBundleInstall;
51 - (BOOL)hasWebstoreData;
52 - (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage;
53 - (void)onOutlineViewRowCountDidChange;
54 - (NSDictionary*)buildItemWithTitle:(NSString*)title
55 cellAttributes:(CellAttributes)cellAttributes
56 children:(NSArray*)children;
57 - (NSDictionary*)buildDetailToggleItem:(size_t)type
58 permissionsDetailIndex:(size_t)index;
59 - (NSArray*)buildWarnings:(const ExtensionInstallPrompt::Prompt&)prompt;
60 // Adds permissions of |type| from |prompt| to |children| and returns the
61 // the appropriate permissions header. If no permissions are found, NULL is
64 appendPermissionsForPrompt:(const ExtensionInstallPrompt::Prompt&)prompt
65 withType:(ExtensionInstallPrompt::PermissionsType)type
66 children:(NSMutableArray*)children;
67 - (void)updateViewFrame:(NSRect)frame;
70 @interface DetailToggleHyperlinkButtonCell : HyperlinkButtonCell {
71 NSUInteger permissionsDetailIndex_;
72 ExtensionInstallPrompt::DetailsType permissionsDetailType_;
73 SEL linkClickedAction_;
76 @property(assign, nonatomic) NSUInteger permissionsDetailIndex;
77 @property(assign, nonatomic)
78 ExtensionInstallPrompt::DetailsType permissionsDetailType;
79 @property(assign, nonatomic) SEL linkClickedAction;
85 // Padding above the warnings separator, we must also subtract this when hiding
87 const CGFloat kWarningsSeparatorPadding = 14;
89 // The left padding for the link cell.
90 const CGFloat kLinkCellPaddingLeft = 3;
92 // Maximum height we will adjust controls to when trying to accomodate their
94 const CGFloat kMaxControlHeight = 250;
96 NSString* const kTitleKey = @"title";
97 NSString* const kChildrenKey = @"children";
98 NSString* const kCellAttributesKey = @"cellAttributes";
99 NSString* const kPermissionsDetailIndex = @"permissionsDetailIndex";
100 NSString* const kPermissionsDetailType = @"permissionsDetailType";
102 // Adjust the |control|'s height so that its content is not clipped.
103 // This also adds the change in height to the |totalOffset| and shifts the
104 // control down by that amount.
105 void OffsetControlVerticallyToFitContent(NSControl* control,
106 CGFloat* totalOffset) {
107 // Adjust the control's height so that its content is not clipped.
108 NSRect currentRect = [control frame];
109 NSRect fitRect = currentRect;
110 fitRect.size.height = kMaxControlHeight;
111 CGFloat desiredHeight = [[control cell] cellSizeForBounds:fitRect].height;
112 CGFloat offset = desiredHeight - NSHeight(currentRect);
114 [control setFrameSize:NSMakeSize(NSWidth(currentRect),
115 NSHeight(currentRect) + offset)];
117 *totalOffset += offset;
119 // Move the control vertically by the new total offset.
120 NSPoint origin = [control frame].origin;
121 origin.y -= *totalOffset;
122 [control setFrameOrigin:origin];
125 // Gets the desired height of |outlineView|. Simply using the view's frame
126 // doesn't work if an animation is pending.
127 CGFloat GetDesiredOutlineViewHeight(NSOutlineView* outlineView) {
129 for (NSInteger i = 0; i < [outlineView numberOfRows]; ++i)
130 height += NSHeight([outlineView rectOfRow:i]);
134 void OffsetOutlineViewVerticallyToFitContent(NSOutlineView* outlineView,
135 CGFloat* totalOffset) {
136 NSScrollView* scrollView = [outlineView enclosingScrollView];
137 NSRect frame = [scrollView frame];
138 CGFloat desiredHeight = GetDesiredOutlineViewHeight(outlineView);
139 if (desiredHeight > kMaxControlHeight)
140 desiredHeight = kMaxControlHeight;
141 CGFloat offset = desiredHeight - NSHeight(frame);
142 frame.size.height += offset;
144 *totalOffset += offset;
146 // Move the control vertically by the new total offset.
147 frame.origin.y -= *totalOffset;
148 [scrollView setFrame:frame];
151 void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) {
152 ExtensionInstallViewController* controller =
153 static_cast<ExtensionInstallViewController*>(data);
154 [controller appendRatingStar:skiaImage];
157 void DrawBulletInFrame(NSRect frame) {
159 rect.size.width = std::min(NSWidth(frame), NSHeight(frame)) * 0.25;
160 rect.size.height = NSWidth(rect);
161 rect.origin.x = frame.origin.x + (NSWidth(frame) - NSWidth(rect)) / 2.0;
162 rect.origin.y = frame.origin.y + (NSHeight(frame) - NSHeight(rect)) / 2.0;
163 rect = NSIntegralRect(rect);
165 [[NSColor colorWithCalibratedWhite:0.0 alpha:0.42] set];
166 [[NSBezierPath bezierPathWithOvalInRect:rect] fill];
169 bool HasAttribute(id item, CellAttributesMask attributeMask) {
170 return [[item objectForKey:kCellAttributesKey] intValue] & attributeMask;
175 @implementation ExtensionInstallViewController
177 @synthesize iconView = iconView_;
178 @synthesize titleField = titleField_;
179 @synthesize itemsField = itemsField_;
180 @synthesize cancelButton = cancelButton_;
181 @synthesize okButton = okButton_;
182 @synthesize outlineView = outlineView_;
183 @synthesize warningsSeparator = warningsSeparator_;
184 @synthesize ratingStars = ratingStars_;
185 @synthesize ratingCountField = ratingCountField_;
186 @synthesize userCountField = userCountField_;
187 @synthesize storeLinkButton = storeLinkButton_;
189 - (id)initWithNavigator:(content::PageNavigator*)navigator
190 delegate:(ExtensionInstallPrompt::Delegate*)delegate
191 prompt:(scoped_refptr<ExtensionInstallPrompt::Prompt>)prompt {
192 // We use a different XIB in the case of bundle installs, installs with
193 // webstore data, or no permission warnings. These are laid out nicely for
194 // the data they display.
195 NSString* nibName = nil;
196 if (prompt->type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT) {
197 nibName = @"ExtensionInstallPromptBundle";
198 } else if (prompt->has_webstore_data()) {
199 nibName = @"ExtensionInstallPromptWebstoreData";
200 } else if (!prompt->ShouldShowPermissions() &&
201 prompt->GetRetainedFileCount() == 0) {
202 nibName = @"ExtensionInstallPromptNoWarnings";
204 nibName = @"ExtensionInstallPrompt";
207 if ((self = [super initWithNibName:nibName
208 bundle:base::mac::FrameworkBundle()])) {
209 navigator_ = navigator;
210 delegate_ = delegate;
212 warnings_.reset([[self buildWarnings:*prompt] retain]);
217 - (IBAction)storeLinkClicked:(id)sender {
218 GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
219 prompt_->extension()->id());
220 navigator_->OpenURL(OpenURLParams(
221 store_url, Referrer(), NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK,
224 delegate_->InstallUIAbort(/*user_initiated=*/true);
227 - (IBAction)cancel:(id)sender {
228 delegate_->InstallUIAbort(/*user_initiated=*/true);
231 - (IBAction)ok:(id)sender {
232 delegate_->InstallUIProceed();
235 - (void)awakeFromNib {
236 // Set control labels.
237 [titleField_ setStringValue:base::SysUTF16ToNSString(prompt_->GetHeading())];
239 if (prompt_->HasAcceptButtonLabel()) {
240 [okButton_ setTitle:base::SysUTF16ToNSString(
241 prompt_->GetAcceptButtonLabel())];
243 [okButton_ removeFromSuperview];
244 okButtonRect = [okButton_ frame];
247 [cancelButton_ setTitle:prompt_->HasAbortButtonLabel() ?
248 base::SysUTF16ToNSString(prompt_->GetAbortButtonLabel()) :
249 l10n_util::GetNSString(IDS_CANCEL)];
250 if ([self hasWebstoreData]) {
251 prompt_->AppendRatingStars(AppendRatingStarsShim, self);
252 [ratingCountField_ setStringValue:base::SysUTF16ToNSString(
253 prompt_->GetRatingCount())];
254 [userCountField_ setStringValue:base::SysUTF16ToNSString(
255 prompt_->GetUserCount())];
256 [[storeLinkButton_ cell] setUnderlineOnHover:YES];
257 [[storeLinkButton_ cell] setTextColor:
258 gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
261 // The bundle install dialog has no icon.
262 if (![self isBundleInstall])
263 [iconView_ setImage:prompt_->icon().ToNSImage()];
265 // The dialog is laid out in the NIB exactly how we want it assuming that
266 // each label fits on one line. However, for each label, we want to allow
267 // wrapping onto multiple lines. So we accumulate an offset by measuring how
268 // big each label wants to be, and comparing it to how big it actually is.
269 // Then we shift each label down and resize by the appropriate amount, then
270 // finally resize the window.
271 CGFloat totalOffset = 0.0;
273 OffsetControlVerticallyToFitContent(titleField_, &totalOffset);
275 // Resize |okButton_| and |cancelButton_| to fit the button labels, but keep
276 // them right-aligned.
279 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_];
280 if (buttonDelta.width) {
281 [okButton_ setFrame:NSOffsetRect([okButton_ frame],
282 -buttonDelta.width, 0)];
283 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame],
284 -buttonDelta.width, 0)];
287 // Make |cancelButton_| right-aligned in the absence of |okButton_|.
288 NSRect cancelButtonRect = [cancelButton_ frame];
289 cancelButtonRect.origin.x =
290 NSMaxX(okButtonRect) - NSWidth(cancelButtonRect);
291 [cancelButton_ setFrame:cancelButtonRect];
293 buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_];
294 if (buttonDelta.width) {
295 [cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame],
296 -buttonDelta.width, 0)];
299 if ([self isBundleInstall]) {
300 // We display the list of extension names as a simple text string, seperated
302 BundleInstaller::ItemList items = prompt_->bundle()->GetItemsWithState(
303 BundleInstaller::Item::STATE_PENDING);
305 NSMutableString* joinedItems = [NSMutableString string];
306 for (size_t i = 0; i < items.size(); ++i) {
308 [joinedItems appendString:@"\n"];
309 [joinedItems appendString:base::SysUTF16ToNSString(
310 items[i].GetNameForDisplay())];
312 [itemsField_ setStringValue:joinedItems];
314 // Adjust the controls to fit the list of extensions.
315 OffsetControlVerticallyToFitContent(itemsField_, &totalOffset);
318 // If there are any warnings or retained files, then we have to do
319 // some special layout.
320 if (prompt_->ShouldShowPermissions() || prompt_->GetRetainedFileCount() > 0) {
321 NSSize spacing = [outlineView_ intercellSpacing];
324 [outlineView_ setIntercellSpacing:spacing];
325 [[[[outlineView_ tableColumns] objectAtIndex:0] dataCell] setWraps:YES];
326 for (id item in warnings_.get())
327 [self expandItemAndChildren:item];
329 // Adjust the outline view to fit the warnings.
330 OffsetOutlineViewVerticallyToFitContent(outlineView_, &totalOffset);
331 } else if ([self hasWebstoreData] || [self isBundleInstall]) {
332 // Installs with webstore data and bundle installs that don't have a
333 // permissions section need to hide controls related to that and shrink the
334 // window by the space they take up.
335 NSRect hiddenRect = NSUnionRect([warningsSeparator_ frame],
336 [[outlineView_ enclosingScrollView] frame]);
337 [warningsSeparator_ setHidden:YES];
338 [[outlineView_ enclosingScrollView] setHidden:YES];
339 totalOffset -= NSHeight(hiddenRect) + kWarningsSeparatorPadding;
342 // If necessary, adjust the window size.
344 NSRect currentRect = [[self view] bounds];
345 currentRect.size.height += totalOffset;
346 [self updateViewFrame:currentRect];
350 - (BOOL)isBundleInstall {
351 return prompt_->type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
354 - (BOOL)hasWebstoreData {
355 return prompt_->has_webstore_data();
358 - (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage {
359 NSImage* image = gfx::NSImageFromImageSkiaWithColorSpace(
360 *skiaImage, base::mac::GetSystemColorSpace());
361 NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height());
362 base::scoped_nsobject<NSImageView> view(
363 [[NSImageView alloc] initWithFrame:frame]);
364 [view setImage:image];
366 // Add this star after all the other ones
367 CGFloat maxStarRight = 0;
368 if ([[ratingStars_ subviews] count]) {
369 maxStarRight = NSMaxX([[[ratingStars_ subviews] lastObject] frame]);
371 NSRect starBounds = NSMakeRect(maxStarRight, 0,
372 skiaImage->width(), skiaImage->height());
373 [view setFrame:starBounds];
374 [ratingStars_ addSubview:view];
377 - (void)onOutlineViewRowCountDidChange {
378 // Force the outline view to update.
379 [outlineView_ reloadData];
381 CGFloat totalOffset = 0.0;
382 OffsetOutlineViewVerticallyToFitContent(outlineView_, &totalOffset);
384 NSRect currentRect = [[self view] bounds];
385 currentRect.size.height += totalOffset;
386 [self updateViewFrame:currentRect];
390 - (id)outlineView:(NSOutlineView*)outlineView
391 child:(NSInteger)index
394 return [warnings_ objectAtIndex:index];
395 if ([item isKindOfClass:[NSDictionary class]])
396 return [[item objectForKey:kChildrenKey] objectAtIndex:index];
401 - (BOOL)outlineView:(NSOutlineView*)outlineView
402 isItemExpandable:(id)item {
403 return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
406 - (NSInteger)outlineView:(NSOutlineView*)outlineView
407 numberOfChildrenOfItem:(id)item {
409 return [warnings_ count];
411 if ([item isKindOfClass:[NSDictionary class]])
412 return [[item objectForKey:kChildrenKey] count];
418 - (id)outlineView:(NSOutlineView*)outlineView
419 objectValueForTableColumn:(NSTableColumn *)tableColumn
421 return [item objectForKey:kTitleKey];
424 - (BOOL)outlineView:(NSOutlineView *)outlineView
425 shouldExpandItem:(id)item {
426 return HasAttribute(item, kCanExpand);
429 - (void)outlineViewItemDidExpand:sender {
430 // Call via run loop to avoid animation glitches.
431 [self performSelector:@selector(onOutlineViewRowCountDidChange)
436 - (void)outlineViewItemDidCollapse:sender {
437 // Call via run loop to avoid animation glitches.
438 [self performSelector:@selector(onOutlineViewRowCountDidChange)
443 - (CGFloat)outlineView:(NSOutlineView *)outlineView
444 heightOfRowByItem:(id)item {
445 // Prevent reentrancy due to the frameOfCellAtColumn:row: call below.
446 if (isComputingRowHeight_)
448 base::AutoReset<BOOL> reset(&isComputingRowHeight_, YES);
450 NSCell* cell = [[[outlineView_ tableColumns] objectAtIndex:0] dataCell];
451 [cell setStringValue:[item objectForKey:kTitleKey]];
452 NSRect bounds = NSZeroRect;
453 NSInteger row = [outlineView_ rowForItem:item];
454 bounds.size.width = NSWidth([outlineView_ frameOfCellAtColumn:0 row:row]);
455 bounds.size.height = kMaxControlHeight;
457 return [cell cellSizeForBounds:bounds].height;
460 - (BOOL)outlineView:(NSOutlineView*)outlineView
461 shouldShowOutlineCellForItem:(id)item {
462 return !HasAttribute(item, kNoExpandMarker);
465 - (BOOL)outlineView:(NSOutlineView*)outlineView
466 shouldTrackCell:(NSCell*)cell
467 forTableColumn:(NSTableColumn*)tableColumn
469 return HasAttribute(item, kUseCustomLinkCell);
472 - (void)outlineView:(NSOutlineView*)outlineView
473 willDisplayCell:(id)cell
474 forTableColumn:(NSTableColumn *)tableColumn
476 if (HasAttribute(item, kBoldText))
477 [cell setFont:[NSFont boldSystemFontOfSize:12.0]];
479 [cell setFont:[NSFont systemFontOfSize:12.0]];
482 - (void)outlineView:(NSOutlineView *)outlineView
483 willDisplayOutlineCell:(id)cell
484 forTableColumn:(NSTableColumn *)tableColumn
486 if (HasAttribute(item, kNoExpandMarker)) {
487 [cell setImagePosition:NSNoImage];
491 if (HasAttribute(item, kUseBullet)) {
492 // Replace disclosure triangles with bullet lists for leaf nodes.
493 [cell setImagePosition:NSNoImage];
494 DrawBulletInFrame([outlineView_ frameOfOutlineCellAtRow:
495 [outlineView_ rowForItem:item]]);
499 // Reset image to default value.
500 [cell setImagePosition:NSImageOverlaps];
503 - (BOOL)outlineView:(NSOutlineView *)outlineView
504 shouldSelectItem:(id)item {
508 - (NSCell*)outlineView:(NSOutlineView*)outlineView
509 dataCellForTableColumn:(NSTableColumn*)tableColumn
511 if (HasAttribute(item, kUseCustomLinkCell)) {
512 base::scoped_nsobject<DetailToggleHyperlinkButtonCell> cell(
513 [[DetailToggleHyperlinkButtonCell alloc] initTextCell:@""]);
514 [cell setTarget:self];
515 [cell setLinkClickedAction:@selector(onToggleDetailsLinkClicked:)];
516 [cell setAlignment:NSLeftTextAlignment];
517 [cell setUnderlineOnHover:YES];
519 gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
521 size_t detailsIndex =
522 [[item objectForKey:kPermissionsDetailIndex] unsignedIntegerValue];
523 [cell setPermissionsDetailIndex:detailsIndex];
525 ExtensionInstallPrompt::DetailsType detailsType =
526 static_cast<ExtensionInstallPrompt::DetailsType>(
527 [[item objectForKey:kPermissionsDetailType] unsignedIntegerValue]);
528 [cell setPermissionsDetailType:detailsType];
530 if (prompt_->GetIsShowingDetails(detailsType, detailsIndex)) {
532 l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_HIDE_DETAILS)];
535 l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_SHOW_DETAILS)];
538 return cell.autorelease();
540 return [tableColumn dataCell];
544 - (void)expandItemAndChildren:(id)item {
545 if (HasAttribute(item, kAutoExpandCell))
546 [outlineView_ expandItem:item expandChildren:NO];
548 for (id child in [item objectForKey:kChildrenKey])
549 [self expandItemAndChildren:child];
552 - (void)onToggleDetailsLinkClicked:(id)sender {
553 size_t index = [sender permissionsDetailIndex];
554 ExtensionInstallPrompt::DetailsType type = [sender permissionsDetailType];
555 prompt_->SetIsShowingDetails(
556 type, index, !prompt_->GetIsShowingDetails(type, index));
558 warnings_.reset([[self buildWarnings:*prompt_] retain]);
559 [outlineView_ reloadData];
561 for (id item in warnings_.get())
562 [self expandItemAndChildren:item];
565 - (NSDictionary*)buildItemWithTitle:(NSString*)title
566 cellAttributes:(CellAttributes)cellAttributes
567 children:(NSArray*)children {
568 if (!children || ([children count] == 0 && cellAttributes & kUseBullet)) {
569 // Add a dummy child even though this is a leaf node. This will cause
570 // the outline view to show a disclosure triangle for this item.
571 // This is later overriden in willDisplayOutlineCell: to draw a bullet
572 // instead. (The bullet could be placed in the title instead but then
573 // the bullet wouldn't line up with disclosure triangles of sibling nodes.)
574 children = [NSArray arrayWithObject:[NSDictionary dictionary]];
576 cellAttributes = cellAttributes | kCanExpand;
581 kChildrenKey : children,
582 kCellAttributesKey : [NSNumber numberWithInt:cellAttributes],
583 kPermissionsDetailIndex : @0ul,
584 kPermissionsDetailType : @0ul,
588 - (NSDictionary*)buildDetailToggleItem:(size_t)type
589 permissionsDetailIndex:(size_t)index {
592 kChildrenKey : @[ @{} ],
593 kCellAttributesKey : [NSNumber numberWithInt:kUseCustomLinkCell |
595 kPermissionsDetailIndex : [NSNumber numberWithUnsignedInteger:index],
596 kPermissionsDetailType : [NSNumber numberWithUnsignedInteger:type],
600 - (NSArray*)buildWarnings:(const ExtensionInstallPrompt::Prompt&)prompt {
601 NSMutableArray* warnings = [NSMutableArray array];
602 NSString* heading = nil;
603 NSString* withheldHeading = nil;
605 ExtensionInstallPrompt::DetailsType type =
606 ExtensionInstallPrompt::PERMISSIONS_DETAILS;
607 bool hasPermissions = prompt.GetPermissionCount(
608 ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS);
609 CellAttributes warningCellAttributes =
610 kBoldText | kAutoExpandCell | kNoExpandMarker;
611 if (prompt.ShouldShowPermissions()) {
612 NSMutableArray* children = [NSMutableArray array];
613 NSMutableArray* withheldChildren = [NSMutableArray array];
616 [self appendPermissionsForPrompt:prompt
617 withType:ExtensionInstallPrompt::PermissionsType
618 ::REGULAR_PERMISSIONS
621 [self appendPermissionsForPrompt:prompt
622 withType:ExtensionInstallPrompt::PermissionsType
623 ::WITHHELD_PERMISSIONS
624 children:withheldChildren];
626 if (!hasPermissions) {
628 [self buildItemWithTitle:
629 l10n_util::GetNSString(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS)
630 cellAttributes:kUseBullet
636 [warnings addObject:[self buildItemWithTitle:heading
637 cellAttributes:warningCellAttributes
641 // Add withheld permissions to the prompt if they exist.
642 if (withheldHeading) {
643 [warnings addObject:[self buildItemWithTitle:withheldHeading
644 cellAttributes:warningCellAttributes
645 children:withheldChildren]];
649 if (prompt.GetRetainedFileCount() > 0) {
650 type = ExtensionInstallPrompt::RETAINED_FILES_DETAILS;
652 NSMutableArray* children = [NSMutableArray array];
654 if (prompt.GetIsShowingDetails(type, 0)) {
655 for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) {
657 [self buildItemWithTitle:SysUTF16ToNSString(
658 prompt.GetRetainedFile(i))
659 cellAttributes:kUseBullet
665 addObject:[self buildItemWithTitle:SysUTF16ToNSString(
666 prompt.GetRetainedFilesHeading())
667 cellAttributes:warningCellAttributes
670 // Add a row for the link.
672 [self buildDetailToggleItem:type permissionsDetailIndex:0]];
679 appendPermissionsForPrompt:(const ExtensionInstallPrompt::Prompt&)prompt
680 withType:(ExtensionInstallPrompt::PermissionsType)type
681 children:(NSMutableArray*)children {
682 size_t permissionsCount = prompt.GetPermissionCount(type);
683 if (permissionsCount == 0)
686 for (size_t i = 0; i < permissionsCount; ++i) {
687 NSDictionary* item = [self
688 buildItemWithTitle:SysUTF16ToNSString(prompt.GetPermission(i, type))
689 cellAttributes:kUseBullet
691 [children addObject:item];
693 // If there are additional details, add them below this item.
694 if (!prompt.GetPermissionsDetails(i, type).empty()) {
695 if (prompt.GetIsShowingDetails(
696 ExtensionInstallPrompt::PERMISSIONS_DETAILS, i)) {
698 [self buildItemWithTitle:SysUTF16ToNSString(
699 prompt.GetPermissionsDetails(i, type))
700 cellAttributes:kNoExpandMarker
702 [children addObject:item];
705 // Add a row for the link.
707 [self buildDetailToggleItem:type permissionsDetailIndex:i]];
711 return SysUTF16ToNSString(prompt.GetPermissionsHeading(type));
714 - (void)updateViewFrame:(NSRect)frame {
715 NSWindow* window = [[self view] window];
716 [window setFrame:[window frameRectForContentRect:frame] display:YES];
717 [[self view] setFrame:frame];
723 @implementation DetailToggleHyperlinkButtonCell
725 @synthesize permissionsDetailIndex = permissionsDetailIndex_;
726 @synthesize permissionsDetailType = permissionsDetailType_;
727 @synthesize linkClickedAction = linkClickedAction_;
729 + (BOOL)prefersTrackingUntilMouseUp {
733 - (NSRect)drawingRectForBounds:(NSRect)rect {
734 NSRect rectInset = NSMakeRect(rect.origin.x + kLinkCellPaddingLeft,
736 rect.size.width - kLinkCellPaddingLeft,
738 return [super drawingRectForBounds:rectInset];
741 - (NSUInteger)hitTestForEvent:(NSEvent*)event
742 inRect:(NSRect)cellFrame
743 ofView:(NSView*)controlView {
744 NSUInteger hitTestResult =
745 [super hitTestForEvent:event inRect:cellFrame ofView:controlView];
746 if ((hitTestResult & NSCellHitContentArea) != 0)
747 hitTestResult |= NSCellHitTrackableArea;
748 return hitTestResult;
751 - (void)handleLinkClicked {
752 [NSApp sendAction:linkClickedAction_ to:[self target] from:self];
755 - (BOOL)trackMouse:(NSEvent*)event
756 inRect:(NSRect)cellFrame
757 ofView:(NSView*)controlView
758 untilMouseUp:(BOOL)flag {
760 NSUInteger hitTestResult =
761 [self hitTestForEvent:event inRect:cellFrame ofView:controlView];
762 if ((hitTestResult & NSCellHitContentArea) != 0) {
763 result = [super trackMouse:event
767 event = [NSApp currentEvent];
769 [self hitTestForEvent:event inRect:cellFrame ofView:controlView];
770 if ((hitTestResult & NSCellHitContentArea) != 0)
771 [self handleLinkClicked];
776 - (NSArray*)accessibilityActionNames {
777 return [[super accessibilityActionNames]
778 arrayByAddingObject:NSAccessibilityPressAction];
781 - (void)accessibilityPerformAction:(NSString*)action {
782 if ([action isEqualToString:NSAccessibilityPressAction])
783 [self handleLinkClicked];
785 [super accessibilityPerformAction:action];