Chromium Code Reviews| Index: chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm b/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm |
| index a13a2d94bc3400e29ee3687f714763b4f6a4b92a..0b62afe7a0735838da371661c7b9795333a444b9 100644 |
| --- a/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm |
| +++ b/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm |
| @@ -4,6 +4,7 @@ |
| #import "chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.h" |
| +#include "base/auto_reset.h" |
| #include "base/i18n/rtl.h" |
| #include "base/mac/bundle_locations.h" |
| #include "base/mac/mac_util.h" |
| @@ -29,6 +30,7 @@ using extensions::BundleInstaller; |
| - (BOOL)isBundleInstall; |
| - (BOOL)isInlineInstall; |
| - (void)appendRatingStar:(const gfx::ImageSkia*)skiaImage; |
| +- (void)onOutlineViewRowCountDidChange; |
| @end |
| namespace { |
| @@ -41,6 +43,11 @@ const CGFloat kWarningsSeparatorPadding = 14; |
| // contents. |
| const CGFloat kMaxControlHeight = 400; |
| +NSString* const kTitleKey = @"title"; |
| +NSString* const kIsGroupItemKey = @"isGroupItem"; |
| +NSString* const kChildrenKey = @"children"; |
| +NSString* const kCanExpandKey = @"canExpand"; |
| + |
| // 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. |
| @@ -64,12 +71,104 @@ void OffsetControlVerticallyToFitContent(NSControl* control, |
| [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 += [outlineView rectOfRow:i].size.height; |
|
Robert Sesek
2012/08/06 21:31:48
nit: NSHeight
sail
2012/08/09 02:00:29
Done.
|
| + return height; |
| +} |
| + |
| +void OffsetOutlineViewVerticallyToFitContent(NSOutlineView* outlineView, |
| + CGFloat* totalOffset) { |
| + NSScrollView* scrollView = [outlineView enclosingScrollView]; |
| + NSRect frame = [scrollView frame]; |
| + CGFloat desiredHeight = GetDesiredOutlineViewHeight(outlineView); |
| + CGFloat offset = desiredHeight - frame.size.height; |
|
Robert Sesek
2012/08/06 21:31:48
nit: NSHeight
sail
2012/08/09 02:00:29
Done.
|
| + 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) { |
| ExtensionInstallDialogController* controller = |
| static_cast<ExtensionInstallDialogController*>(data); |
| [controller appendRatingStar:skiaImage]; |
| } |
| +NSDictionary* BuildItem(NSString* title, BOOL isGroupItem, NSArray* children) { |
|
Robert Sesek
2012/08/06 21:31:48
I think this may be easier to read inline as an Ob
sail
2012/08/09 02:00:29
Done.
|
| + BOOL canExpand = YES; |
| + if (!children) { |
| + // 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]]; |
| + canExpand = false; |
|
Robert Sesek
2012/08/06 21:31:48
NO
sail
2012/08/09 02:00:29
Done.
|
| + } |
| + |
| + return [NSDictionary dictionaryWithObjectsAndKeys: |
| + title, kTitleKey, |
| + [NSNumber numberWithBool:isGroupItem], kIsGroupItemKey, |
| + children, kChildrenKey, |
| + [NSNumber numberWithBool:canExpand], kCanExpandKey, |
| + nil]; |
| +} |
| + |
| +NSDictionary* BuildIssue(const IssueAdviceInfoEntry& issue) { |
| + if (issue.details.empty()) |
| + return BuildItem(SysUTF16ToNSString(issue.description), NO, nil); |
| + |
| + NSMutableArray* details = [NSMutableArray array]; |
| + for (size_t j = 0; j < issue.details.size(); ++j) { |
| + [details addObject:BuildItem( |
| + SysUTF16ToNSString(issue.details[j]), NO, nil)]; |
| + } |
| + return BuildItem(SysUTF16ToNSString(issue.description), NO, details); |
| +} |
| + |
| +NSArray* BuildWarnings(const ExtensionInstallPrompt::Prompt& prompt) { |
| + NSMutableArray* warnings = [NSMutableArray array]; |
| + |
| + if (prompt.GetPermissionCount() > 0) { |
| + NSMutableArray* children = [NSMutableArray array]; |
| + for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) { |
| + [children addObject:BuildItem( |
| + SysUTF16ToNSString(prompt.GetPermission(i)), NO, nil)]; |
| + } |
| + [warnings addObject:BuildItem( |
| + SysUTF16ToNSString(prompt.GetPermissionsHeading()), YES, children)]; |
| + } |
| + |
| + if (prompt.GetOAuthIssueCount() > 0) { |
| + NSMutableArray* children = [NSMutableArray array]; |
| + for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) |
| + [children addObject:BuildIssue(prompt.GetOAuthIssue(i))]; |
| + [warnings addObject:BuildItem( |
| + SysUTF16ToNSString(prompt.GetOAuthHeading()), YES, children)]; |
| + } |
| + |
| + return warnings; |
| +} |
| + |
| +void DrawBulletInFrame(NSRect frame) { |
| + NSRect rect; |
| + rect.size.width = std::min(frame.size.width, frame.size.height) * 0.38; |
|
Robert Sesek
2012/08/06 21:31:48
Use NSWidth/Height in here
sail
2012/08/09 02:00:29
Done.
|
| + rect.size.height = rect.size.width; |
| + rect.origin.x = frame.origin.x + (frame.size.width - rect.size.width) / 2.0; |
| + rect.origin.y = frame.origin.y + (frame.size.height - rect.size.height) / 2.0; |
| + rect = NSIntegralRect(rect); |
| + |
| + [[NSColor colorWithCalibratedWhite:0.0 alpha:0.42] set]; |
| + [[NSBezierPath bezierPathWithOvalInRect:rect] fill]; |
| +} |
| + |
| } |
| @implementation ExtensionInstallDialogController |
| @@ -77,10 +176,9 @@ void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) { |
| @synthesize iconView = iconView_; |
| @synthesize titleField = titleField_; |
| @synthesize itemsField = itemsField_; |
| -@synthesize subtitleField = subtitleField_; |
| -@synthesize warningsField = warningsField_; |
| @synthesize cancelButton = cancelButton_; |
| @synthesize okButton = okButton_; |
| +@synthesize outlineView = outlineView_; |
| @synthesize warningsSeparator = warningsSeparator_; |
| @synthesize ratingStars = ratingStars_; |
| @synthesize ratingCountField = ratingCountField_; |
| @@ -91,6 +189,7 @@ void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) { |
| delegate:(ExtensionInstallPrompt::Delegate*)delegate |
| prompt:(const ExtensionInstallPrompt::Prompt&)prompt { |
| NSString* nibpath = nil; |
| + warnings_.reset([BuildWarnings(prompt) retain]); |
| // 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 |
| @@ -103,7 +202,8 @@ void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) { |
| nibpath = [base::mac::FrameworkBundle() |
| pathForResource:@"ExtensionInstallPromptInline" |
| ofType:@"nib"]; |
| - } else if (prompt.GetPermissionCount() == 0) { |
| + } else if (prompt.GetPermissionCount() == 0 && |
| + prompt.GetOAuthIssueCount() == 0) { |
| nibpath = [base::mac::FrameworkBundle() |
| pathForResource:@"ExtensionInstallPromptNoWarnings" |
| ofType:@"nib"]; |
| @@ -200,9 +300,6 @@ void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) { |
| } |
| 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( |
| @@ -221,48 +318,28 @@ void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) { |
| 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))]; |
| + // If there are any warnings or OAuth issues, then we have to do some special |
| + // layout. |
| + if (prompt_->GetPermissionCount() > 0 || prompt_->GetOAuthIssueCount() > 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()) { |
| + if ([[item objectForKey:kIsGroupItemKey] boolValue]) |
| + [outlineView_ expandItem:item expandChildren:NO]; |
| } |
| - [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); |
| + // 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], |
| - [subtitleField_ frame]); |
| - hiddenRect = NSUnionRect(hiddenRect, [warningsField_ frame]); |
| + [[outlineView_ enclosingScrollView] frame]); |
| [warningsSeparator_ setHidden:YES]; |
| - [subtitleField_ setHidden:YES]; |
| - [warningsField_ setHidden:YES]; |
| + [[outlineView_ enclosingScrollView] setHidden:YES]; |
| totalOffset -= hiddenRect.size.height + kWarningsSeparatorPadding; |
| } |
| @@ -313,6 +390,123 @@ void AppendRatingStarsShim(const gfx::ImageSkia* skiaImage, void* data) { |
| [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 window] frame]; |
| + [[self window] setFrame:NSMakeRect(currentRect.origin.x, |
|
Robert Sesek
2012/08/06 21:31:48
Why not just mutate currentRect and pass that?
sail
2012/08/09 02:00:29
Done.
Good idea.
|
| + currentRect.origin.y - totalOffset, |
| + currentRect.size.width, |
| + currentRect.size.height + totalOffset) |
| + display:YES]; |
| + } |
| +} |
| + |
| +- (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 [[item objectForKey:kCanExpandKey] boolValue]; |
| +} |
| + |
| +- (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; |
| + 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 = [outlineView_ frameOfCellAtColumn:0 |
| + row:row].size.width; |
|
Robert Sesek
2012/08/06 21:31:48
NSWidth
sail
2012/08/09 02:00:29
Done.
|
| + bounds.size.height = kMaxControlHeight; |
| + |
| + return [cell cellSizeForBounds:bounds].height; |
| +} |
| + |
| +- (BOOL)outlineView:(NSOutlineView*)outlineView |
| + shouldShowOutlineCellForItem:(id)item { |
| + // The top most group header items are always expanded so hide their |
| + // disclosure trianggles. |
| + return ![[item objectForKey:kIsGroupItemKey] boolValue]; |
| +} |
| + |
| +- (void)outlineView:(NSOutlineView*)outlineView |
| + willDisplayCell:(id)cell |
| + forTableColumn:(NSTableColumn *)tableColumn |
| + item:(id)item { |
| + if ([[item objectForKey:kIsGroupItemKey] boolValue]) |
| + [cell setFont:[NSFont boldSystemFontOfSize:11.0]]; |
| + else |
| + [cell setFont:[NSFont systemFontOfSize:11.0]]; |
| +} |
| + |
| +- (void)outlineView:(NSOutlineView *)outlineView |
| + willDisplayOutlineCell:(id)cell |
| + forTableColumn:(NSTableColumn *)tableColumn |
| + item:(id)item { |
| + // Replace disclosure triangles with bullet lists for leaf nodes. |
| + if (![[item objectForKey:kCanExpandKey] boolValue]) { |
| + [cell setImagePosition:NSNoImage]; |
| + DrawBulletInFrame([outlineView_ frameOfOutlineCellAtRow: |
| + [outlineView_ rowForItem:item]]); |
| + } else { |
| + // Reset image to default value. |
| + [cell setImagePosition:NSImageOverlaps]; |
| + } |
| +} |
| + |
| @end // ExtensionInstallDialogController |
| void ShowExtensionInstallDialogImpl( |