| Index: chrome/browser/ui/cocoa/browser/profile_chooser_controller.mm
|
| diff --git a/chrome/browser/ui/cocoa/browser/profile_chooser_controller.mm b/chrome/browser/ui/cocoa/browser/profile_chooser_controller.mm
|
| index b751ecb459f80b4a3e5b1e20c1cfc68e11bf82a6..739f3a69bf05e4bfa5dcafc70c719ef9e13c2e6d 100644
|
| --- a/chrome/browser/ui/cocoa/browser/profile_chooser_controller.mm
|
| +++ b/chrome/browser/ui/cocoa/browser/profile_chooser_controller.mm
|
| @@ -7,13 +7,20 @@
|
| #import "chrome/browser/ui/cocoa/browser/profile_chooser_controller.h"
|
|
|
| #include "base/mac/bundle_locations.h"
|
| +#include "base/stl_util.h"
|
| #include "base/strings/sys_string_conversions.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| #include "chrome/browser/browser_process.h"
|
| #include "chrome/browser/profiles/avatar_menu.h"
|
| +#include "chrome/browser/profiles/avatar_menu_observer.h"
|
| #include "chrome/browser/profiles/profile_info_cache.h"
|
| #include "chrome/browser/profiles/profile_info_util.h"
|
| #include "chrome/browser/profiles/profile_manager.h"
|
| #include "chrome/browser/profiles/profile_window.h"
|
| +#include "chrome/browser/signin/profile_oauth2_token_service.h"
|
| +#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
|
| +#include "chrome/browser/signin/signin_manager.h"
|
| +#include "chrome/browser/signin/signin_manager_factory.h"
|
| #include "chrome/browser/signin/signin_promo.h"
|
| #include "chrome/browser/ui/browser.h"
|
| #include "chrome/browser/ui/browser_dialogs.h"
|
| @@ -23,17 +30,22 @@
|
| #import "chrome/browser/ui/cocoa/info_bubble_window.h"
|
| #include "chrome/browser/ui/singleton_tabs.h"
|
| #include "chrome/common/url_constants.h"
|
| +#include "content/public/browser/web_contents.h"
|
| +#include "content/public/browser/web_contents_view.h"
|
| #include "grit/chromium_strings.h"
|
| #include "grit/generated_resources.h"
|
| #include "grit/theme_resources.h"
|
| +#include "google_apis/gaia/oauth2_token_service.h"
|
| #include "skia/ext/skia_utils_mac.h"
|
| #import "ui/base/cocoa/cocoa_event_utils.h"
|
| +#import "ui/base/cocoa/controls/blue_label_button.h"
|
| #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
|
| #include "ui/base/cocoa/window_size_constants.h"
|
| #include "ui/base/l10n/l10n_util.h"
|
| #include "ui/base/l10n/l10n_util_mac.h"
|
| #include "ui/base/resource/resource_bundle.h"
|
| #include "ui/gfx/image/image.h"
|
| +#include "ui/gfx/text_elider.h"
|
|
|
| namespace {
|
|
|
| @@ -49,6 +61,10 @@ const CGFloat kHorizontalSpacing = 20.0;
|
| const CGFloat kTitleFontSize = 15.0;
|
| const CGFloat kTextFontSize = 12.0;
|
|
|
| +// Minimum size for embedded sign in pages as defined in Gaia.
|
| +const CGFloat kMinGaiaViewWidth = 320;
|
| +const CGFloat kMinGaiaViewHeight = 440;
|
| +
|
| gfx::Image CreateProfileImage(const gfx::Image& icon, int imageSize) {
|
| return profiles::GetSizedAvatarIconWithBorder(
|
| icon, true /* image is a square */,
|
| @@ -56,22 +72,82 @@ gfx::Image CreateProfileImage(const gfx::Image& icon, int imageSize) {
|
| imageSize + profiles::kAvatarIconPadding);
|
| }
|
|
|
| -// Should only be called before the window is shown, as that sets the window
|
| -// position.
|
| +// Updates the window size and position.
|
| void SetWindowSize(NSWindow* window, NSSize size) {
|
| NSRect frame = [window frame];
|
| + frame.origin.x += frame.size.width - size.width;
|
| + frame.origin.y += frame.size.height - size.height;
|
| frame.size = size;
|
| [window setFrame:frame display:YES];
|
| }
|
|
|
| +NSString* ElideEmail(const std::string& email, CGFloat width) {
|
| + base::string16 elidedEmail = gfx::ElideEmail(
|
| + base::UTF8ToUTF16(email),
|
| + ui::ResourceBundle::GetSharedInstance().GetFontList(
|
| + ui::ResourceBundle::BaseFont),
|
| + width);
|
| + return base::SysUTF16ToNSString(elidedEmail);
|
| +}
|
| +
|
| } // namespace
|
|
|
| +// Class that listens to changes to the OAuth2Tokens for the active profile, or
|
| +// changes to the avatar menu model.
|
| +// TODO(noms): The avatar menu model changes can only be triggered by modifying
|
| +// the name or avatar for the active profile, but this is not currently
|
| +// implemented.
|
| +class ActiveProfileObserverBridge : public AvatarMenuObserver,
|
| + public OAuth2TokenService::Observer {
|
| + public:
|
| + explicit ActiveProfileObserverBridge(ProfileChooserController* controller)
|
| + : controller_(controller) {
|
| + }
|
| +
|
| + virtual ~ActiveProfileObserverBridge() {
|
| + }
|
| +
|
| + // OAuth2TokenService::Observer:
|
| + virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE {
|
| + // Tokens can only be added by adding an account through the inline flow,
|
| + // which is started from the account management view. Refresh it to show the
|
| + // update.
|
| + BubbleViewMode viewMode = [controller_ viewMode];
|
| + if (viewMode == ACCOUNT_MANAGEMENT_VIEW ||
|
| + viewMode == GAIA_SIGNIN_VIEW ||
|
| + viewMode == GAIA_ADD_ACCOUNT_VIEW) {
|
| + [controller_ initMenuContentsWithView:ACCOUNT_MANAGEMENT_VIEW];
|
| + }
|
| + }
|
| +
|
| + virtual void OnRefreshTokenRevoked(const std::string& account_id) OVERRIDE {
|
| + // Tokens can only be removed from the account management view. Refresh it
|
| + // to show the update.
|
| + if ([controller_ viewMode] == ACCOUNT_MANAGEMENT_VIEW)
|
| + [controller_ initMenuContentsWithView:ACCOUNT_MANAGEMENT_VIEW];
|
| + }
|
| +
|
| + // AvatarMenuObserver:
|
| + virtual void OnAvatarMenuChanged(AvatarMenu* avatar_menu) OVERRIDE {
|
| + // While the bubble is open, the avatar menu can only change from the
|
| + // profile chooser view by modifying the current profile's photo or name.
|
| + [controller_ initMenuContentsWithView:PROFILE_CHOOSER_VIEW];
|
| + }
|
| +
|
| + private:
|
| + ProfileChooserController* controller_; // Weak; owns this.
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ActiveProfileObserverBridge);
|
| +};
|
| +
|
| @interface ProfileChooserController (Private)
|
| // Creates the main profile card for the profile |item| at the top of
|
| -// the bubble. |isGuestView| is used to control which links/buttons are
|
| -// displayed.
|
| -- (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item
|
| - isGuest:(BOOL)isGuestView;
|
| +// the bubble.
|
| +- (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item;
|
| +
|
| +// Creates the possible links for the main profile card with profile |item|.
|
| +- (NSView*)createCurrentProfileLinksForItem:(const AvatarMenu::Item&)item
|
| + withXOffset:(CGFloat)xOffset;
|
|
|
| // Creates a main profile card for the guest user.
|
| - (NSView*)createGuestProfileView;
|
| @@ -80,10 +156,17 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| // switcher in the middle of the bubble.
|
| - (NSButton*)createOtherProfileView:(int)itemIndex;
|
|
|
| -// Creates the Guest / Add person / View all persons buttons. |isGuestView| is
|
| -// used to determine the text and functionality of the Guest button.
|
| -- (NSView*)createOptionsViewWithRect:(NSRect)rect
|
| - isGuestView:(BOOL)isGuestView;
|
| +// Creates the Guest / Add person / View all persons buttons.
|
| +- (NSView*)createOptionsViewWithRect:(NSRect)rect;
|
| +
|
| +// Creates the account management view for the active profile.
|
| +- (NSView*)createCurrentProfileAccountsView:(NSRect)rect;
|
| +
|
| +// Creates the list of accounts for the active profile.
|
| +- (NSView*)createAccountsListWithRect:(NSRect)rect;
|
| +
|
| +// Creates the Gaia sign-in/add account view.
|
| +- (NSView*)createGaiaEmbeddedView;
|
|
|
| // Creates a generic button with text given by |textResourceId|, an icon
|
| // given by |imageResourceId| and with |action|.
|
| @@ -98,11 +181,20 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| frameOrigin:(NSPoint)frameOrigin
|
| action:(SEL)action;
|
|
|
| -// Creates all the subviews of the avatar bubble.
|
| -- (void)initMenuContents;
|
| +// Creates an email account button with |title|. If |canBeDeleted| is YES, then
|
| +// the button is clickable and has a remove icon.
|
| +- (NSButton*)makeAccountButtonWithRect:(NSRect)rect
|
| + title:(const std::string&)title
|
| + canBeDeleted:(BOOL)canBeDeleted;
|
| +
|
| +- (void)dealloc;
|
| +
|
| @end
|
|
|
| @implementation ProfileChooserController
|
| +- (BubbleViewMode) viewMode {
|
| + return viewMode_;
|
| +}
|
|
|
| - (IBAction)addNewProfile:(id)sender {
|
| profiles::CreateAndSwitchToNewProfile(
|
| @@ -138,7 +230,7 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| }
|
|
|
| - (IBAction)showAccountManagement:(id)sender {
|
| - NOTIMPLEMENTED();
|
| + [self initMenuContentsWithView:ACCOUNT_MANAGEMENT_VIEW];
|
| }
|
|
|
| - (IBAction)lockProfile:(id)sender {
|
| @@ -146,101 +238,155 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| }
|
|
|
| - (IBAction)showSigninPage:(id)sender {
|
| - GURL page = signin::GetPromoURL(signin::SOURCE_MENU, false);
|
| - chrome::ShowSingletonTab(browser_, page);
|
| + [self initMenuContentsWithView:GAIA_SIGNIN_VIEW];
|
| }
|
|
|
| -- (id)initWithBrowser:(Browser*)browser anchoredAt:(NSPoint)point {
|
| - browser_ = browser;
|
| - // TODO(noms): Add an observer when profile name editing is implemented.
|
| - avatarMenu_.reset(new AvatarMenu(
|
| - &g_browser_process->profile_manager()->GetProfileInfoCache(),
|
| - NULL,
|
| - browser));
|
| - avatarMenu_->RebuildMenu();
|
| +- (IBAction)addAccount:(id)sender {
|
| + [self initMenuContentsWithView:GAIA_ADD_ACCOUNT_VIEW];
|
| +}
|
|
|
| +- (IBAction)removeAccount:(id)sender {
|
| + DCHECK(!isGuestSession_);
|
| + DCHECK_GE([sender tag], 0); // Should not be called for the primary account.
|
| + DCHECK(ContainsKey(currentProfileAccounts_, [sender tag]));
|
| + std::string account = currentProfileAccounts_[[sender tag]];
|
| + ProfileOAuth2TokenServiceFactory::GetForProfile(
|
| + browser_->profile())->RevokeCredentials(account);
|
| +}
|
| +
|
| +- (id)initWithBrowser:(Browser*)browser anchoredAt:(NSPoint)point {
|
| base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
|
| initWithContentRect:ui::kWindowSizeDeterminedLater
|
| styleMask:NSBorderlessWindowMask
|
| backing:NSBackingStoreBuffered
|
| defer:NO]);
|
| +
|
| if ((self = [super initWithWindow:window
|
| parentWindow:browser->window()->GetNativeWindow()
|
| anchoredAt:point])) {
|
| + browser_ = browser;
|
| + viewMode_ = PROFILE_CHOOSER_VIEW;
|
| + observer_.reset(new ActiveProfileObserverBridge(self));
|
| +
|
| + avatarMenu_.reset(new AvatarMenu(
|
| + &g_browser_process->profile_manager()->GetProfileInfoCache(),
|
| + observer_.get(),
|
| + browser_));
|
| + avatarMenu_->RebuildMenu();
|
| +
|
| + // Guest profiles do not have a token service.
|
| + isGuestSession_ = browser_->profile()->IsGuestSession();
|
| + if (!isGuestSession_) {
|
| + ProfileOAuth2TokenService* oauth2TokenService =
|
| + ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
|
| + DCHECK(oauth2TokenService);
|
| + oauth2TokenService->AddObserver(observer_.get());
|
| + }
|
| +
|
| [[self bubble] setArrowLocation:info_bubble::kTopRight];
|
| - [self initMenuContents];
|
| + [self initMenuContentsWithView:viewMode_];
|
| }
|
| +
|
| return self;
|
| }
|
|
|
| -- (void)initMenuContents {
|
| +- (void)dealloc {
|
| + if (!isGuestSession_) {
|
| + ProfileOAuth2TokenService* oauth2TokenService =
|
| + ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile());
|
| + DCHECK(oauth2TokenService);
|
| + oauth2TokenService->RemoveObserver(observer_.get());
|
| + }
|
| + [super dealloc];
|
| +}
|
| +
|
| +- (void)initMenuContentsWithView:(BubbleViewMode)viewToDisplay {
|
| + viewMode_ = viewToDisplay;
|
| NSView* contentView = [[self window] contentView];
|
| + [contentView setSubviews:[NSArray array]];
|
| +
|
| + if (viewMode_ == GAIA_SIGNIN_VIEW || viewMode_ == GAIA_ADD_ACCOUNT_VIEW) {
|
| + [contentView addSubview:[self createGaiaEmbeddedView]];
|
| + SetWindowSize([self window],
|
| + NSMakeSize(kMinGaiaViewWidth, kMinGaiaViewHeight));
|
| + return;
|
| + }
|
|
|
| - // Separate items into active and other profiles.
|
| NSView* currentProfileView = nil;
|
| base::scoped_nsobject<NSMutableArray> otherProfiles(
|
| [[NSMutableArray alloc] init]);
|
| - BOOL isGuestView = YES;
|
|
|
| + // Separate items into active and other profiles.
|
| for (size_t i = 0; i < avatarMenu_->GetNumberOfItems(); ++i) {
|
| const AvatarMenu::Item& item = avatarMenu_->GetItemAt(i);
|
| if (item.active) {
|
| - currentProfileView = [self createCurrentProfileView:item isGuest:NO];
|
| - // The avatar menu only contains non-guest profiles, so an active profile
|
| - // implies this is not a guest session browser.
|
| - isGuestView = NO;
|
| + currentProfileView = [self createCurrentProfileView:item];
|
| } else {
|
| [otherProfiles addObject:[self createOtherProfileView:i]];
|
| }
|
| }
|
| if (!currentProfileView) // Guest windows don't have an active profile.
|
| currentProfileView = [self createGuestProfileView];
|
| - CGFloat updatedMenuWidth =
|
| +
|
| + CGFloat contentsWidth =
|
| std::max(kMinMenuWidth, NSWidth([currentProfileView frame]));
|
| + CGFloat contentsWidthWithSpacing = contentsWidth + 2 * kHorizontalSpacing;
|
|
|
| // |yOffset| is the next position at which to draw in |contentView|
|
| // coordinates.
|
| CGFloat yOffset = kVerticalSpacing;
|
| + NSRect viewRect = NSMakeRect(kHorizontalSpacing, yOffset, contentsWidth, 0);
|
|
|
| // Guest / Add Person / View All Persons buttons.
|
| - NSRect viewRect = NSMakeRect(kHorizontalSpacing, yOffset,
|
| - updatedMenuWidth, 0);
|
| - NSView* optionsView = [self createOptionsViewWithRect:viewRect
|
| - isGuestView:isGuestView];
|
| + NSView* optionsView = [self createOptionsViewWithRect:viewRect];
|
| [contentView addSubview:optionsView];
|
| yOffset = NSMaxY([optionsView frame]) + kVerticalSpacing;
|
|
|
| - NSBox* separator =
|
| - [self separatorWithFrame:NSMakeRect(0, yOffset, updatedMenuWidth, 0)];
|
| + NSBox* separator = [self separatorWithFrame:NSMakeRect(
|
| + 0, yOffset, contentsWidthWithSpacing, 0)];
|
| [contentView addSubview:separator];
|
| yOffset = NSMaxY([separator frame]) + kVerticalSpacing;
|
|
|
| - // Other profiles switcher.
|
| - for (NSView *otherProfileView in otherProfiles.get()) {
|
| - [otherProfileView setFrameOrigin:NSMakePoint(kHorizontalSpacing, yOffset)];
|
| - [contentView addSubview:otherProfileView];
|
| - yOffset = NSMaxY([otherProfileView frame]) + kSmallVerticalSpacing;
|
| - }
|
| + if (viewToDisplay == PROFILE_CHOOSER_VIEW) {
|
| + // Other profiles switcher.
|
| + for (NSView *otherProfileView in otherProfiles.get()) {
|
| + [otherProfileView setFrameOrigin:NSMakePoint(kHorizontalSpacing,
|
| + yOffset)];
|
| + [contentView addSubview:otherProfileView];
|
| + yOffset = NSMaxY([otherProfileView frame]) + kSmallVerticalSpacing;
|
| + }
|
|
|
| - // If we displayed other profiles, ensure the spacing between the last item
|
| - // and the active profile card is the same as the spacing between the active
|
| - // profile card and the bottom of the bubble.
|
| - if ([otherProfiles.get() count] > 0)
|
| - yOffset += kSmallVerticalSpacing;
|
| + // If we displayed other profiles, ensure the spacing between the last item
|
| + // and the active profile card is the same as the spacing between the active
|
| + // profile card and the bottom of the bubble.
|
| + if ([otherProfiles.get() count] > 0)
|
| + yOffset += kSmallVerticalSpacing;
|
| + } else if (viewToDisplay == ACCOUNT_MANAGEMENT_VIEW) {
|
| + // Reuse the same view area as for the option buttons.
|
| + viewRect.origin.y = yOffset;
|
| + NSView* currentProfileAccountsView =
|
| + [self createCurrentProfileAccountsView:viewRect];
|
| + [contentView addSubview:currentProfileAccountsView];
|
| + yOffset = NSMaxY([currentProfileAccountsView frame]) + kVerticalSpacing;
|
| +
|
| + NSBox* accountsSeparator = [self separatorWithFrame:NSMakeRect(
|
| + 0, yOffset, contentsWidthWithSpacing, 0)];
|
| + [contentView addSubview:accountsSeparator];
|
| + yOffset = NSMaxY([accountsSeparator frame]) + kVerticalSpacing;
|
| + }
|
|
|
| // Active profile card.
|
| if (currentProfileView) {
|
| - // Don't need to size this view, as it was done at its creation.
|
| - [currentProfileView setFrameOrigin:NSMakePoint(0, yOffset)];
|
| + [currentProfileView setFrameOrigin:NSMakePoint(kHorizontalSpacing,
|
| + yOffset)];
|
| [contentView addSubview:currentProfileView];
|
| yOffset = NSMaxY([currentProfileView frame]) + kVerticalSpacing;
|
| }
|
|
|
| - SetWindowSize([self window], NSMakeSize(updatedMenuWidth, yOffset));
|
| + SetWindowSize([self window], NSMakeSize(contentsWidthWithSpacing, yOffset));
|
| }
|
|
|
| -- (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item
|
| - isGuest:(BOOL)isGuestView {
|
| +- (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item {
|
| base::scoped_nsobject<NSView> container([[NSView alloc]
|
| initWithFrame:NSZeroRect]);
|
|
|
| @@ -249,21 +395,48 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| initWithFrame:NSMakeRect(0, 0, kLargeImageSide, kLargeImageSide)]);
|
| [iconView setImage:CreateProfileImage(
|
| item.icon, kLargeImageSide).ToNSImage()];
|
| - // Position the image correctly so that the width of the container can be
|
| - // used to correctly resize the bubble if needed.
|
| - [iconView setFrameOrigin:NSMakePoint(kHorizontalSpacing, 0)];
|
| + [iconView setFrameOrigin:NSMakePoint(0, 0)];
|
| [container addSubview:iconView];
|
|
|
| CGFloat xOffset = NSMaxX([iconView frame]) + kHorizontalSpacing;
|
| + CGFloat yOffset = kVerticalSpacing;
|
| + CGFloat maxXLinksContainer = 0;
|
| + if (!isGuestSession_ && viewMode_ == PROFILE_CHOOSER_VIEW) {
|
| + NSView* linksContainer =
|
| + [self createCurrentProfileLinksForItem:item withXOffset:xOffset];
|
| + [container addSubview:linksContainer];
|
| + yOffset = NSMaxY([linksContainer frame]);
|
| + maxXLinksContainer = NSMaxX([linksContainer frame]);
|
| + }
|
| +
|
| + // Profile name.
|
| + base::scoped_nsobject<NSTextField> profileName([[NSTextField alloc]
|
| + initWithFrame:NSZeroRect]);
|
| + [profileName setStringValue:base::SysUTF16ToNSString(item.name)];
|
| + [profileName setFont:[NSFont labelFontOfSize:kTitleFontSize]];
|
| + [profileName setEditable:NO];
|
| + [profileName setDrawsBackground:NO];
|
| + [profileName setBezeled:NO];
|
| + [profileName setFrameOrigin:NSMakePoint(xOffset, yOffset)];
|
| + [profileName sizeToFit];
|
| + [container addSubview:profileName];
|
| +
|
| + CGFloat maxX = std::max(maxXLinksContainer,
|
| + NSMaxX([profileName frame]));
|
| +
|
| + [container setFrameSize:NSMakeSize(maxX, NSHeight([iconView frame]))];
|
| + return container.autorelease();
|
| +}
|
| +
|
| +- (NSView*)createCurrentProfileLinksForItem:(const AvatarMenu::Item&)item
|
| + withXOffset:(CGFloat)xOffset {
|
| + base::scoped_nsobject<NSView> container([[NSView alloc]
|
| + initWithFrame:NSZeroRect]);
|
| + CGFloat maxX = 0; // Ensure the container is wide enough for all the links.
|
| CGFloat yOffset;
|
| - // Since the bubble hasn't been sized yet, keep track of the widest
|
| - // link (or profile name), to make sure everything fits.
|
| - CGFloat maxXOfLinksColumn = 0;
|
|
|
| // The available links depend on the type of profile that is active.
|
| - if (isGuestView) {
|
| - yOffset = kVerticalSpacing;
|
| - } else if (item.signed_in) {
|
| + if (item.signed_in) {
|
| yOffset = 0;
|
| // We need to display 2 links instead of 1, so make the padding in between
|
| // the links even smaller to fit.
|
| @@ -282,7 +455,7 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| action:@selector(lockProfile:)];
|
| yOffset = NSMaxY([signOutLink frame]) + kLinkSpacing;
|
|
|
| - maxXOfLinksColumn = std::max(NSMaxX([manageAccountsLink frame]),
|
| + maxX = std::max(NSMaxX([manageAccountsLink frame]),
|
| NSMaxX([signOutLink frame]));
|
| [container addSubview:manageAccountsLink];
|
| [container addSubview:signOutLink];
|
| @@ -295,26 +468,12 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| frameOrigin:NSMakePoint(xOffset, yOffset)
|
| action:@selector(showSigninPage:)];
|
| yOffset = NSMaxY([signInLink frame]) + kSmallVerticalSpacing;
|
| - maxXOfLinksColumn = NSMaxX([signInLink frame]);
|
| + maxX = NSMaxX([signInLink frame]);
|
| +
|
| [container addSubview:signInLink];
|
| }
|
|
|
| - // Profile name.
|
| - base::scoped_nsobject<NSTextField> profileName([[NSTextField alloc]
|
| - initWithFrame:NSZeroRect]);
|
| - [profileName setStringValue:base::SysUTF16ToNSString(item.name)];
|
| - [profileName setFont:[NSFont labelFontOfSize:kTitleFontSize]];
|
| - [profileName setEditable:NO];
|
| - [profileName setDrawsBackground:NO];
|
| - [profileName setBezeled:NO];
|
| - [profileName setFrameOrigin:NSMakePoint(xOffset, yOffset)];
|
| - [profileName sizeToFit];
|
| - maxXOfLinksColumn = std::max(maxXOfLinksColumn, NSMaxX([profileName frame]));
|
| -
|
| - [container addSubview:profileName];
|
| -
|
| - [container setFrameSize:NSMakeSize(maxXOfLinksColumn + kHorizontalSpacing,
|
| - NSHeight([iconView frame]))];
|
| + [container setFrameSize:NSMakeSize(maxX, yOffset)];
|
| return container.autorelease();
|
| }
|
|
|
| @@ -329,7 +488,7 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| guestItem.name = base::SysNSStringToUTF16(
|
| l10n_util::GetNSString(IDS_PROFILES_GUEST_PROFILE_NAME));
|
|
|
| - return [self createCurrentProfileView:guestItem isGuest:YES];
|
| + return [self createCurrentProfileView:guestItem];
|
| }
|
|
|
| - (NSButton*)createOtherProfileView:(int)itemIndex {
|
| @@ -352,8 +511,7 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| return profileButton.autorelease();
|
| }
|
|
|
| -- (NSView*)createOptionsViewWithRect:(NSRect)rect
|
| - isGuestView:(BOOL)isGuestView {
|
| +- (NSView*)createOptionsViewWithRect:(NSRect)rect {
|
| CGFloat yOffset = 0;
|
| base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
|
|
|
| @@ -371,10 +529,10 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| action:@selector(addNewProfile:)];
|
| yOffset = NSMaxY([addUserButton frame]) + kSmallVerticalSpacing;
|
|
|
| - int guestButtonText = isGuestView ? IDS_PROFILES_EXIT_GUEST_BUTTON :
|
| - IDS_PROFILES_GUEST_BUTTON;
|
| - SEL guestButtonAction = isGuestView ? @selector(exitGuestProfile:) :
|
| - @selector(switchToGuestProfile:);
|
| + int guestButtonText = isGuestSession_ ? IDS_PROFILES_EXIT_GUEST_BUTTON :
|
| + IDS_PROFILES_GUEST_BUTTON;
|
| + SEL guestButtonAction = isGuestSession_ ? @selector(exitGuestProfile:) :
|
| + @selector(switchToGuestProfile:);
|
| NSButton* guestButton =
|
| [self buttonWithRect:NSMakeRect(0, yOffset, 0, 0)
|
| textResourceId:guestButtonText
|
| @@ -387,6 +545,100 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| return container.autorelease();
|
| }
|
|
|
| +- (NSView*)createCurrentProfileAccountsView:(NSRect)rect {
|
| + const CGFloat kBlueButtonHeight = 30;
|
| + const CGFloat kAccountButtonHeight = 15;
|
| +
|
| + const AvatarMenu::Item& item =
|
| + avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex());
|
| + DCHECK(item.signed_in);
|
| +
|
| + base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
|
| +
|
| + NSRect viewRect = NSMakeRect(0, 0, rect.size.width, kBlueButtonHeight);
|
| + base::scoped_nsobject<NSButton> addAccountsButton([[BlueLabelButton alloc]
|
| + initWithFrame:viewRect]);
|
| + [addAccountsButton setTitle:l10n_util::GetNSStringFWithFixup(
|
| + IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, item.name)];
|
| + [addAccountsButton setTarget:self];
|
| + [addAccountsButton setAction:@selector(addAccount:)];
|
| + [container addSubview:addAccountsButton];
|
| +
|
| + // Update the height of the email account buttons. This is needed so that the
|
| + // all the buttons span the entire width of the bubble.
|
| + viewRect.origin.y = NSMaxY([addAccountsButton frame]) + kVerticalSpacing;
|
| + viewRect.size.height = kAccountButtonHeight;
|
| +
|
| + NSView* accountEmails = [self createAccountsListWithRect:viewRect];
|
| + [container addSubview:accountEmails];
|
| + [container setFrameSize:NSMakeSize(
|
| + NSWidth([container frame]), NSMaxY([accountEmails frame]))];
|
| + return container.autorelease();
|
| +}
|
| +
|
| +- (NSView*)createAccountsListWithRect:(NSRect)rect {
|
| + base::scoped_nsobject<NSView> container([[NSView alloc] initWithFrame:rect]);
|
| + currentProfileAccounts_.clear();
|
| +
|
| + // The primary account should always be listed first. However, the vector
|
| + // returned by ProfileOAuth2TokenService::GetAccounts() will contain the
|
| + // primary account too. Ignore it when it appears later.
|
| + // TODO(rogerta): we still need to further differentiate the primary account
|
| + // from the others, so more work is likely required here: crbug.com/311124.
|
| + Profile* profile = browser_->profile();
|
| +
|
| + // TODO(noms): This code is duplicated by the views implementation. See
|
| + // crbug.com/331805.
|
| + std::string primaryAccount =
|
| + SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedUsername();
|
| + DCHECK(!primaryAccount.empty());
|
| + std::vector<std::string> accounts =
|
| + ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->GetAccounts();
|
| + DCHECK_EQ(1, std::count_if(accounts.begin(), accounts.end(),
|
| + std::bind1st(std::equal_to<std::string>(),
|
| + primaryAccount)));
|
| + rect.origin.y = 0;
|
| + for (size_t i = 0; i < accounts.size(); ++i) {
|
| + // Save the original email address, as the button text could be elided.
|
| + currentProfileAccounts_[i] = accounts[i];
|
| + if (primaryAccount != accounts[i]) {
|
| + NSButton* accountButton = [self makeAccountButtonWithRect:rect
|
| + title:accounts[i]
|
| + canBeDeleted:true];
|
| + [accountButton setTag:i];
|
| + [container addSubview:accountButton];
|
| + rect.origin.y = NSMaxY([accountButton frame]) + kSmallVerticalSpacing;
|
| + }
|
| + }
|
| +
|
| + // Add the primary account button. It doesn't need a tag, as it cannot be
|
| + // removed.
|
| + NSButton* accountButton = [self makeAccountButtonWithRect:rect
|
| + title:primaryAccount
|
| + canBeDeleted:false];
|
| + [container addSubview:accountButton];
|
| + [container setFrameSize:NSMakeSize(NSWidth([container frame]),
|
| + NSMaxY([accountButton frame]))];
|
| + return container.autorelease();
|
| +}
|
| +
|
| +- (NSView*) createGaiaEmbeddedView {
|
| + signin::Source source = (viewMode_ == GAIA_SIGNIN_VIEW) ?
|
| + signin::SOURCE_AVATAR_BUBBLE_SIGN_IN :
|
| + signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT;
|
| +
|
| + webContents_.reset(content::WebContents::Create(
|
| + content::WebContents::CreateParams(browser_->profile())));
|
| + webContents_->GetController().LoadURL(
|
| + signin::GetPromoURL(source, false),
|
| + content::Referrer(),
|
| + content::PAGE_TRANSITION_AUTO_TOPLEVEL,
|
| + std::string());
|
| + NSView* webview = webContents_->GetView()->GetNativeView();
|
| + [webview setFrameSize:NSMakeSize(kMinGaiaViewWidth, kMinGaiaViewHeight)];
|
| + return webview;
|
| +}
|
| +
|
| - (NSButton*)buttonWithRect:(NSRect)rect
|
| textResourceId:(int)textResourceId
|
| imageResourceId:(int)imageResourceId
|
| @@ -426,5 +678,24 @@ void SetWindowSize(NSWindow* window, NSSize size) {
|
| return link.autorelease();
|
| }
|
|
|
| +- (NSButton*)makeAccountButtonWithRect:(NSRect)rect
|
| + title:(const std::string&)title
|
| + canBeDeleted:(BOOL)canBeDeleted {
|
| + base::scoped_nsobject<NSButton> button([[NSButton alloc] initWithFrame:rect]);
|
| + [button setTitle:ElideEmail(title, rect.size.width)];
|
| + [button setAlignment:NSLeftTextAlignment];
|
| + [button setBordered:NO];
|
| +
|
| + if (canBeDeleted) {
|
| + [button setImage:ui::ResourceBundle::GetSharedInstance().
|
| + GetNativeImageNamed(IDR_CLOSE_1).ToNSImage()];
|
| + [button setImagePosition:NSImageRight];
|
| + [button setTarget:self];
|
| + [button setAction:@selector(removeAccount:)];
|
| + }
|
| +
|
| + return button.autorelease();
|
| +}
|
| +
|
| @end
|
|
|
|
|