blob: 7fae834ad29cc8d9ca90f8f7104a8bdf47604631 [file] [log] [blame]
// 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_view_controller.h"
#include "base/auto_reset.h"
#include "base/i18n/rtl.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/mac_util.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/bundle_installer.h"
#import "chrome/browser/ui/chrome_style.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/page_navigator.h"
#include "grit/generated_resources.h"
#include "skia/ext/skia_utils_mac.h"
#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/gfx/image/image_skia_util_mac.h"
using content::OpenURLParams;
using content::Referrer;
using extensions::BundleInstaller;
namespace {
// A collection of attributes (bitmask) for how to draw a cell, the expand
// marker and the text in the cell.
enum CellAttributesMask {
kBoldText = 1 << 0,
kNoExpandMarker = 1 << 1,
kUseBullet = 1 << 2,
kAutoExpandCell = 1 << 3,
kUseCustomLinkCell = 1 << 4,
kCanExpand = 1 << 5,
};
typedef NSUInteger CellAttributes;
} // namespace.
@interface ExtensionInstallViewController ()
- (BOOL)isBundleInstall;
- (BOOL)isInlineInstall;
- (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage;
- (void)onOutlineViewRowCountDidChange;
- (NSDictionary*)buildItemWithTitle:(NSString*)title
cellAttributes:(CellAttributes)cellAttributes
children:(NSArray*)children;
- (NSDictionary*)buildDetailToggleItem:(size_t)type
permissionsDetailIndex:(size_t)index;
- (NSArray*)buildWarnings:(const ExtensionInstallPrompt::Prompt&)prompt;
- (void)updateViewFrame:(NSRect)frame;
@end
@interface DetailToggleHyperlinkButtonCell : HyperlinkButtonCell {
NSUInteger permissionsDetailIndex_;
ExtensionInstallPrompt::DetailsType permissionsDetailType_;
SEL linkClickedAction_;
}
@property(assign, nonatomic) NSUInteger permissionsDetailIndex;
@property(assign, nonatomic)
ExtensionInstallPrompt::DetailsType permissionsDetailType;
@property(assign, nonatomic) SEL linkClickedAction;
@end
namespace {
// Padding above the warnings separator, we must also subtract this when hiding
// it.
const CGFloat kWarningsSeparatorPadding = 14;
// The left padding for the link cell.
const CGFloat kLinkCellPaddingLeft = 3;
// Maximum height we will adjust controls to when trying to accomodate their
// contents.
const CGFloat kMaxControlHeight = 250;
NSString* const kTitleKey = @"title";
NSString* const kChildrenKey = @"children";
NSString* const kCellAttributesKey = @"cellAttributes";
NSString* const kPermissionsDetailIndex = @"permissionsDetailIndex";
NSString* const kPermissionsDetailType = @"permissionsDetailType";
// 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 - NSHeight(currentRect);
[control setFrameSize:NSMakeSize(NSWidth(currentRect),
NSHeight(currentRect) + offset)];
*totalOffset += offset;
// Move the control vertically by the new total offset.
NSPoint origin = [control frame].origin;
origin.y -= *totalOffset;
[control setFrameOrigin:origin];
}
// Gets the desired height of |outlineView|. Simply using the view's frame
// doesn't work if an animation is pending.
CGFloat GetDesiredOutlineViewHeight(NSOutlineView* outlineView) {
CGFloat height = 0;
for (NSInteger i = 0; i < [outlineView numberOfRows]; ++i)
height += NSHeight([outlineView rectOfRow:i]);
return height;
}
void OffsetOutlineViewVerticallyToFitContent(NSOutlineView* outlineView,
CGFloat* totalOffset) {
NSScrollView* scrollView = [outlineView enclosingScrollView];
NSRect frame = [scrollView frame];
CGFloat desiredHeight = GetDesiredOutlineViewHeight(outlineView);
if (desiredHeight > kMaxControlHeight)
desiredHeight = kMaxControlHeight;
CGFloat offset = desiredHeight - NSHeight(frame);
frame.size.height += offset;
*totalOffset += offset;
// Move the control vertically by the new total offset.
frame.origin.y -= *totalOffset;
[scrollView setFrame:frame];
}
void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) {
ExtensionInstallViewController* controller =
static_cast<ExtensionInstallViewController*>(data);
[controller appendRatingStar:skiaImage];
}
void DrawBulletInFrame(NSRect frame) {
NSRect rect;
rect.size.width = std::min(NSWidth(frame), NSHeight(frame)) * 0.25;
rect.size.height = NSWidth(rect);
rect.origin.x = frame.origin.x + (NSWidth(frame) - NSWidth(rect)) / 2.0;
rect.origin.y = frame.origin.y + (NSHeight(frame) - NSHeight(rect)) / 2.0;
rect = NSIntegralRect(rect);
[[NSColor colorWithCalibratedWhite:0.0 alpha:0.42] set];
[[NSBezierPath bezierPathWithOvalInRect:rect] fill];
}
bool HasAttribute(id item, CellAttributesMask attributeMask) {
return [[item objectForKey:kCellAttributesKey] intValue] & attributeMask;
}
} // namespace
@implementation ExtensionInstallViewController
@synthesize iconView = iconView_;
@synthesize titleField = titleField_;
@synthesize itemsField = itemsField_;
@synthesize cancelButton = cancelButton_;
@synthesize okButton = okButton_;
@synthesize outlineView = outlineView_;
@synthesize warningsSeparator = warningsSeparator_;
@synthesize ratingStars = ratingStars_;
@synthesize ratingCountField = ratingCountField_;
@synthesize userCountField = userCountField_;
@synthesize storeLinkButton = storeLinkButton_;
- (id)initWithNavigator:(content::PageNavigator*)navigator
delegate:(ExtensionInstallPrompt::Delegate*)delegate
prompt:(const ExtensionInstallPrompt::Prompt&)prompt {
// 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.
NSString* nibName = nil;
if (prompt.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT) {
nibName = @"ExtensionInstallPromptBundle";
} else if (prompt.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT) {
nibName = @"ExtensionInstallPromptInline";
} else if (!prompt.ShouldShowPermissions() &&
prompt.GetOAuthIssueCount() == 0 &&
prompt.GetRetainedFileCount() == 0) {
nibName = @"ExtensionInstallPromptNoWarnings";
} else {
nibName = @"ExtensionInstallPrompt";
}
if ((self = [super initWithNibName:nibName
bundle:base::mac::FrameworkBundle()])) {
navigator_ = navigator;
delegate_ = delegate;
prompt_.reset(new ExtensionInstallPrompt::Prompt(prompt));
warnings_.reset([[self buildWarnings:prompt] retain]);
}
return self;
}
- (IBAction)storeLinkClicked:(id)sender {
GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
prompt_->extension()->id());
navigator_->OpenURL(OpenURLParams(
store_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK,
false));
delegate_->InstallUIAbort(/*user_initiated=*/true);
}
- (IBAction)cancel:(id)sender {
delegate_->InstallUIAbort(/*user_initiated=*/true);
}
- (IBAction)ok:(id)sender {
delegate_->InstallUIProceed();
}
- (void)awakeFromNib {
// Set control labels.
[titleField_ setStringValue:base::SysUTF16ToNSString(prompt_->GetHeading())];
NSRect okButtonRect;
if (prompt_->HasAcceptButtonLabel()) {
[okButton_ setTitle:base::SysUTF16ToNSString(
prompt_->GetAcceptButtonLabel())];
} else {
[okButton_ removeFromSuperview];
okButtonRect = [okButton_ frame];
okButton_ = nil;
}
[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())];
[[storeLinkButton_ cell] setUnderlineOnHover:YES];
[[storeLinkButton_ cell] setTextColor:
gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
}
// 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;
if (okButton_) {
buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:okButton_];
if (buttonDelta.width) {
[okButton_ setFrame:NSOffsetRect([okButton_ frame],
-buttonDelta.width, 0)];
[cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame],
-buttonDelta.width, 0)];
}
} else {
// Make |cancelButton_| right-aligned in the absence of |okButton_|.
NSRect cancelButtonRect = [cancelButton_ frame];
cancelButtonRect.origin.x =
NSMaxX(okButtonRect) - NSWidth(cancelButtonRect);
[cancelButton_ setFrame:cancelButtonRect];
}
buttonDelta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:cancelButton_];
if (buttonDelta.width) {
[cancelButton_ setFrame:NSOffsetRect([cancelButton_ frame],
-buttonDelta.width, 0)];
}
if ([self isBundleInstall]) {
// 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 or OAuth issues, then we have to do some special
// layout.
if (prompt_->ShouldShowPermissions() || prompt_->GetOAuthIssueCount() > 0 ||
prompt_->GetRetainedFileCount() > 0) {
NSSize spacing = [outlineView_ intercellSpacing];
spacing.width += 2;
spacing.height += 2;
[outlineView_ setIntercellSpacing:spacing];
[[[[outlineView_ tableColumns] objectAtIndex:0] dataCell] setWraps:YES];
for (id item in warnings_.get())
[self expandItemAndChildren:item];
// Adjust the outline view to fit the warnings.
OffsetOutlineViewVerticallyToFitContent(outlineView_, &totalOffset);
} 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],
[[outlineView_ enclosingScrollView] frame]);
[warningsSeparator_ setHidden:YES];
[[outlineView_ enclosingScrollView] setHidden:YES];
totalOffset -= NSHeight(hiddenRect) + kWarningsSeparatorPadding;
}
// If necessary, adjust the window size.
if (totalOffset) {
NSRect currentRect = [[self view] bounds];
currentRect.size.height += totalOffset;
[self updateViewFrame:currentRect];
}
}
- (BOOL)isBundleInstall {
return prompt_->type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
}
- (BOOL)isInlineInstall {
return prompt_->type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
}
- (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage {
NSImage* image = gfx::NSImageFromImageSkiaWithColorSpace(
*skiaImage, base::mac::GetSystemColorSpace());
NSRect frame = NSMakeRect(0, 0, skiaImage->width(), skiaImage->height());
base::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];
}
- (void)onOutlineViewRowCountDidChange {
// Force the outline view to update.
[outlineView_ reloadData];
CGFloat totalOffset = 0.0;
OffsetOutlineViewVerticallyToFitContent(outlineView_, &totalOffset);
if (totalOffset) {
NSRect currentRect = [[self view] bounds];
currentRect.size.height += totalOffset;
[self updateViewFrame:currentRect];
}
}
- (id)outlineView:(NSOutlineView*)outlineView
child:(NSInteger)index
ofItem:(id)item {
if (!item)
return [warnings_ objectAtIndex:index];
if ([item isKindOfClass:[NSDictionary class]])
return [[item objectForKey:kChildrenKey] objectAtIndex:index];
NOTREACHED();
return nil;
}
- (BOOL)outlineView:(NSOutlineView*)outlineView
isItemExpandable:(id)item {
return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
}
- (NSInteger)outlineView:(NSOutlineView*)outlineView
numberOfChildrenOfItem:(id)item {
if (!item)
return [warnings_ count];
if ([item isKindOfClass:[NSDictionary class]])
return [[item objectForKey:kChildrenKey] count];
NOTREACHED();
return 0;
}
- (id)outlineView:(NSOutlineView*)outlineView
objectValueForTableColumn:(NSTableColumn *)tableColumn
byItem:(id)item {
return [item objectForKey:kTitleKey];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView
shouldExpandItem:(id)item {
return HasAttribute(item, kCanExpand);
}
- (void)outlineViewItemDidExpand:sender {
// Call via run loop to avoid animation glitches.
[self performSelector:@selector(onOutlineViewRowCountDidChange)
withObject:nil
afterDelay:0];
}
- (void)outlineViewItemDidCollapse:sender {
// Call via run loop to avoid animation glitches.
[self performSelector:@selector(onOutlineViewRowCountDidChange)
withObject:nil
afterDelay:0];
}
- (CGFloat)outlineView:(NSOutlineView *)outlineView
heightOfRowByItem:(id)item {
// Prevent reentrancy due to the frameOfCellAtColumn:row: call below.
if (isComputingRowHeight_)
return 1;
base::AutoReset<BOOL> reset(&isComputingRowHeight_, YES);
NSCell* cell = [[[outlineView_ tableColumns] objectAtIndex:0] dataCell];
[cell setStringValue:[item objectForKey:kTitleKey]];
NSRect bounds = NSZeroRect;
NSInteger row = [outlineView_ rowForItem:item];
bounds.size.width = NSWidth([outlineView_ frameOfCellAtColumn:0 row:row]);
bounds.size.height = kMaxControlHeight;
return [cell cellSizeForBounds:bounds].height;
}
- (BOOL)outlineView:(NSOutlineView*)outlineView
shouldShowOutlineCellForItem:(id)item {
return !HasAttribute(item, kNoExpandMarker);
}
- (BOOL)outlineView:(NSOutlineView*)outlineView
shouldTrackCell:(NSCell*)cell
forTableColumn:(NSTableColumn*)tableColumn
item:(id)item {
return HasAttribute(item, kUseCustomLinkCell);
}
- (void)outlineView:(NSOutlineView*)outlineView
willDisplayCell:(id)cell
forTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
if (HasAttribute(item, kBoldText))
[cell setFont:[NSFont boldSystemFontOfSize:12.0]];
else
[cell setFont:[NSFont systemFontOfSize:12.0]];
}
- (void)outlineView:(NSOutlineView *)outlineView
willDisplayOutlineCell:(id)cell
forTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
if (HasAttribute(item, kNoExpandMarker)) {
[cell setImagePosition:NSNoImage];
return;
}
if (HasAttribute(item, kUseBullet)) {
// Replace disclosure triangles with bullet lists for leaf nodes.
[cell setImagePosition:NSNoImage];
DrawBulletInFrame([outlineView_ frameOfOutlineCellAtRow:
[outlineView_ rowForItem:item]]);
return;
}
// Reset image to default value.
[cell setImagePosition:NSImageOverlaps];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView
shouldSelectItem:(id)item {
return false;
}
- (NSCell*)outlineView:(NSOutlineView*)outlineView
dataCellForTableColumn:(NSTableColumn*)tableColumn
item:(id)item {
if (HasAttribute(item, kUseCustomLinkCell)) {
base::scoped_nsobject<DetailToggleHyperlinkButtonCell> cell(
[[DetailToggleHyperlinkButtonCell alloc] initTextCell:@""]);
[cell setTarget:self];
[cell setLinkClickedAction:@selector(onToggleDetailsLinkClicked:)];
[cell setAlignment:NSLeftTextAlignment];
[cell setUnderlineOnHover:YES];
[cell setTextColor:
gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor())];
size_t detailsIndex =
[[item objectForKey:kPermissionsDetailIndex] unsignedIntegerValue];
[cell setPermissionsDetailIndex:detailsIndex];
ExtensionInstallPrompt::DetailsType detailsType =
static_cast<ExtensionInstallPrompt::DetailsType>(
[[item objectForKey:kPermissionsDetailType] unsignedIntegerValue]);
[cell setPermissionsDetailType:detailsType];
if (prompt_->GetIsShowingDetails(detailsType, detailsIndex)) {
[cell setTitle:
l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_HIDE_DETAILS)];
} else {
[cell setTitle:
l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_SHOW_DETAILS)];
}
return cell.autorelease();
} else {
return [tableColumn dataCell];
}
}
- (void)expandItemAndChildren:(id)item {
if (HasAttribute(item, kAutoExpandCell))
[outlineView_ expandItem:item expandChildren:NO];
for (id child in [item objectForKey:kChildrenKey])
[self expandItemAndChildren:child];
}
- (void)onToggleDetailsLinkClicked:(id)sender {
size_t index = [sender permissionsDetailIndex];
ExtensionInstallPrompt::DetailsType type = [sender permissionsDetailType];
prompt_->SetIsShowingDetails(
type, index, !prompt_->GetIsShowingDetails(type, index));
warnings_.reset([[self buildWarnings:*prompt_] retain]);
[outlineView_ reloadData];
for (id item in warnings_.get())
[self expandItemAndChildren:item];
}
- (NSDictionary*)buildItemWithTitle:(NSString*)title
cellAttributes:(CellAttributes)cellAttributes
children:(NSArray*)children {
if (!children || ([children count] == 0 && cellAttributes & kUseBullet)) {
// Add a dummy child even though this is a leaf node. This will cause
// the outline view to show a disclosure triangle for this item.
// This is later overriden in willDisplayOutlineCell: to draw a bullet
// instead. (The bullet could be placed in the title instead but then
// the bullet wouldn't line up with disclosure triangles of sibling nodes.)
children = [NSArray arrayWithObject:[NSDictionary dictionary]];
} else {
cellAttributes = cellAttributes | kCanExpand;
}
return @{
kTitleKey : title,
kChildrenKey : children,
kCellAttributesKey : [NSNumber numberWithInt:cellAttributes],
kPermissionsDetailIndex : @0ul,
kPermissionsDetailType : @0ul,
};
}
- (NSDictionary*)buildDetailToggleItem:(size_t)type
permissionsDetailIndex:(size_t)index {
return @{
kTitleKey : @"",
kChildrenKey : @[ @{} ],
kCellAttributesKey : [NSNumber numberWithInt:kUseCustomLinkCell |
kNoExpandMarker],
kPermissionsDetailIndex : [NSNumber numberWithUnsignedInteger:index],
kPermissionsDetailType : [NSNumber numberWithUnsignedInteger:type],
};
}
- (NSArray*)buildWarnings:(const ExtensionInstallPrompt::Prompt&)prompt {
NSMutableArray* warnings = [NSMutableArray array];
NSString* heading = nil;
ExtensionInstallPrompt::DetailsType type =
ExtensionInstallPrompt::PERMISSIONS_DETAILS;
if (prompt.ShouldShowPermissions()) {
NSMutableArray* children = [NSMutableArray array];
if (prompt.GetPermissionCount() > 0) {
for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
[children addObject:
[self buildItemWithTitle:SysUTF16ToNSString(prompt.GetPermission(i))
cellAttributes:kUseBullet
children:nil]];
// If there are additional details, add them below this item.
if (!prompt.GetPermissionsDetails(i).empty()) {
if (prompt.GetIsShowingDetails(
ExtensionInstallPrompt::PERMISSIONS_DETAILS, i)) {
[children addObject:
[self buildItemWithTitle:SysUTF16ToNSString(
prompt.GetPermissionsDetails(i))
cellAttributes:kNoExpandMarker
children:nil]];
}
// Add a row for the link.
[children addObject:
[self buildDetailToggleItem:type permissionsDetailIndex:i]];
}
}
heading = SysUTF16ToNSString(prompt.GetPermissionsHeading());
} else {
[children addObject:
[self buildItemWithTitle:
l10n_util::GetNSString(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS)
cellAttributes:kUseBullet
children:nil]];
heading = @"";
}
[warnings addObject:[self
buildItemWithTitle:heading
cellAttributes:kBoldText | kAutoExpandCell | kNoExpandMarker
children:children]];
}
if (prompt.GetOAuthIssueCount() > 0) {
type = ExtensionInstallPrompt::OAUTH_DETAILS;
NSMutableArray* children = [NSMutableArray array];
for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
NSMutableArray* details = [NSMutableArray array];
const IssueAdviceInfoEntry& issue = prompt.GetOAuthIssue(i);
if (!issue.details.empty() && prompt.GetIsShowingDetails(type, i)) {
for (size_t j = 0; j < issue.details.size(); ++j) {
[details addObject:
[self buildItemWithTitle:SysUTF16ToNSString(issue.details[j])
cellAttributes:kNoExpandMarker
children:nil]];
}
}
[children addObject:
[self buildItemWithTitle:SysUTF16ToNSString(issue.description)
cellAttributes:kUseBullet | kAutoExpandCell
children:details]];
if (!issue.details.empty()) {
// Add a row for the link.
[children addObject:
[self buildDetailToggleItem:type permissionsDetailIndex:i]];
}
}
[warnings addObject:
[self buildItemWithTitle:SysUTF16ToNSString(prompt.GetOAuthHeading())
cellAttributes:kBoldText | kAutoExpandCell| kNoExpandMarker
children:children]];
}
if (prompt.GetRetainedFileCount() > 0) {
type = ExtensionInstallPrompt::RETAINED_FILES_DETAILS;
NSMutableArray* children = [NSMutableArray array];
if (prompt.GetIsShowingDetails(type, 0)) {
for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) {
[children addObject:
[self buildItemWithTitle:SysUTF16ToNSString(
prompt.GetRetainedFile(i))
cellAttributes:kUseBullet
children:nil]];
}
}
[warnings addObject:
[self buildItemWithTitle:SysUTF16ToNSString(
prompt.GetRetainedFilesHeadingWithCount())
cellAttributes:kBoldText | kAutoExpandCell | kNoExpandMarker
children:children]];
// Add a row for the link.
[warnings addObject:
[self buildDetailToggleItem:type permissionsDetailIndex:0]];
}
return warnings;
}
- (void)updateViewFrame:(NSRect)frame {
NSWindow* window = [[self view] window];
[window setFrame:[window frameRectForContentRect:frame] display:YES];
[[self view] setFrame:frame];
}
@end
@implementation DetailToggleHyperlinkButtonCell
@synthesize permissionsDetailIndex = permissionsDetailIndex_;
@synthesize permissionsDetailType = permissionsDetailType_;
@synthesize linkClickedAction = linkClickedAction_;
+ (BOOL)prefersTrackingUntilMouseUp {
return YES;
}
- (NSRect)drawingRectForBounds:(NSRect)rect {
NSRect rectInset = NSMakeRect(rect.origin.x + kLinkCellPaddingLeft,
rect.origin.y,
rect.size.width - kLinkCellPaddingLeft,
rect.size.height);
return [super drawingRectForBounds:rectInset];
}
- (NSUInteger)hitTestForEvent:(NSEvent*)event
inRect:(NSRect)cellFrame
ofView:(NSView*)controlView {
NSUInteger hitTestResult =
[super hitTestForEvent:event inRect:cellFrame ofView:controlView];
if ((hitTestResult & NSCellHitContentArea) != 0)
hitTestResult |= NSCellHitTrackableArea;
return hitTestResult;
}
- (void)handleLinkClicked {
[NSApp sendAction:linkClickedAction_ to:[self target] from:self];
}
- (BOOL)trackMouse:(NSEvent*)event
inRect:(NSRect)cellFrame
ofView:(NSView*)controlView
untilMouseUp:(BOOL)flag {
BOOL result = YES;
NSUInteger hitTestResult =
[self hitTestForEvent:event inRect:cellFrame ofView:controlView];
if ((hitTestResult & NSCellHitContentArea) != 0) {
result = [super trackMouse:event
inRect:cellFrame
ofView:controlView
untilMouseUp:flag];
event = [NSApp currentEvent];
hitTestResult =
[self hitTestForEvent:event inRect:cellFrame ofView:controlView];
if ((hitTestResult & NSCellHitContentArea) != 0)
[self handleLinkClicked];
}
return result;
}
- (NSArray*)accessibilityActionNames {
return [[super accessibilityActionNames]
arrayByAddingObject:NSAccessibilityPressAction];
}
- (void)accessibilityPerformAction:(NSString*)action {
if ([action isEqualToString:NSAccessibilityPressAction])
[self handleLinkClicked];
else
[super accessibilityPerformAction:action];
}
@end