profile_chooser_view.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright 2014 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. 4 5#include "chrome/browser/ui/views/profiles/profile_chooser_view.h" 6 7#include "base/prefs/pref_service.h" 8#include "base/strings/utf_string_conversions.h" 9#include "chrome/browser/browser_process.h" 10#include "chrome/browser/first_run/first_run.h" 11#include "chrome/browser/lifetime/application_lifetime.h" 12#include "chrome/browser/prefs/incognito_mode_prefs.h" 13#include "chrome/browser/profiles/profile_avatar_icon_util.h" 14#include "chrome/browser/profiles/profile_info_cache.h" 15#include "chrome/browser/profiles/profile_manager.h" 16#include "chrome/browser/profiles/profile_metrics.h" 17#include "chrome/browser/profiles/profile_window.h" 18#include "chrome/browser/profiles/profiles_state.h" 19#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 20#include "chrome/browser/signin/signin_header_helper.h" 21#include "chrome/browser/signin/signin_manager_factory.h" 22#include "chrome/browser/signin/signin_promo.h" 23#include "chrome/browser/ui/browser.h" 24#include "chrome/browser/ui/browser_commands.h" 25#include "chrome/browser/ui/browser_dialogs.h" 26#include "chrome/browser/ui/chrome_pages.h" 27#include "chrome/browser/ui/singleton_tabs.h" 28#include "chrome/browser/ui/views/profiles/user_manager_view.h" 29#include "chrome/browser/ui/webui/signin/login_ui_service.h" 30#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" 31#include "chrome/common/pref_names.h" 32#include "chrome/common/url_constants.h" 33#include "components/signin/core/browser/mutable_profile_oauth2_token_service.h" 34#include "components/signin/core/browser/profile_oauth2_token_service.h" 35#include "components/signin/core/browser/signin_error_controller.h" 36#include "components/signin/core/browser/signin_manager.h" 37#include "components/signin/core/common/profile_management_switches.h" 38#include "grit/chromium_strings.h" 39#include "grit/generated_resources.h" 40#include "grit/theme_resources.h" 41#include "third_party/skia/include/core/SkColor.h" 42#include "ui/base/l10n/l10n_util.h" 43#include "ui/base/resource/resource_bundle.h" 44#include "ui/gfx/canvas.h" 45#include "ui/gfx/image/image.h" 46#include "ui/gfx/image/image_skia.h" 47#include "ui/gfx/path.h" 48#include "ui/gfx/skia_util.h" 49#include "ui/gfx/text_elider.h" 50#include "ui/native_theme/native_theme.h" 51#include "ui/views/controls/button/blue_button.h" 52#include "ui/views/controls/button/image_button.h" 53#include "ui/views/controls/button/label_button.h" 54#include "ui/views/controls/button/menu_button.h" 55#include "ui/views/controls/label.h" 56#include "ui/views/controls/link.h" 57#include "ui/views/controls/separator.h" 58#include "ui/views/controls/styled_label.h" 59#include "ui/views/controls/textfield/textfield.h" 60#include "ui/views/controls/webview/webview.h" 61#include "ui/views/layout/grid_layout.h" 62#include "ui/views/layout/layout_constants.h" 63#include "ui/views/widget/widget.h" 64 65namespace { 66 67// Helpers -------------------------------------------------------------------- 68 69const int kFixedMenuWidth = 250; 70const int kButtonHeight = 32; 71const int kFixedGaiaViewHeight = 400; 72const int kFixedGaiaViewWidth = 360; 73const int kFixedAccountRemovalViewWidth = 280; 74const int kFixedSwitchUserViewWidth = 280; 75const int kLargeImageSide = 88; 76 77// The maximum number of times to show the welcome tutorial for an upgrade user. 78const int kUpgradeWelcomeTutorialShowMax = 1; 79 80// Creates a GridLayout with a single column. This ensures that all the child 81// views added get auto-expanded to fill the full width of the bubble. 82views::GridLayout* CreateSingleColumnLayout(views::View* view, int width) { 83 views::GridLayout* layout = new views::GridLayout(view); 84 view->SetLayoutManager(layout); 85 86 views::ColumnSet* columns = layout->AddColumnSet(0); 87 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 88 views::GridLayout::FIXED, width, width); 89 return layout; 90} 91 92views::Link* CreateLink(const base::string16& link_text, 93 views::LinkListener* listener) { 94 views::Link* link_button = new views::Link(link_text); 95 link_button->SetHorizontalAlignment(gfx::ALIGN_LEFT); 96 link_button->SetUnderline(false); 97 link_button->set_listener(listener); 98 return link_button; 99} 100 101gfx::ImageSkia CreateSquarePlaceholderImage(int size) { 102 SkBitmap bitmap; 103 bitmap.allocPixels(SkImageInfo::MakeA8(size, size)); 104 bitmap.eraseARGB(0, 0, 0, 0); 105 return gfx::ImageSkia::CreateFrom1xBitmap(bitmap); 106} 107 108bool HasAuthError(Profile* profile) { 109 const SigninErrorController* error = 110 profiles::GetSigninErrorController(profile); 111 return error && error->HasError(); 112} 113 114std::string GetAuthErrorAccountId(Profile* profile) { 115 const SigninErrorController* error = 116 profiles::GetSigninErrorController(profile); 117 if (!error) 118 return std::string(); 119 120 return error->error_account_id(); 121} 122 123std::string GetAuthErrorUsername(Profile* profile) { 124 const SigninErrorController* error = 125 profiles::GetSigninErrorController(profile); 126 if (!error) 127 return std::string(); 128 129 return error->error_username(); 130} 131 132// BackgroundColorHoverButton ------------------------------------------------- 133 134// A custom button that allows for setting a background color when hovered over. 135class BackgroundColorHoverButton : public views::LabelButton { 136 public: 137 BackgroundColorHoverButton(views::ButtonListener* listener, 138 const base::string16& text, 139 const gfx::ImageSkia& icon) 140 : views::LabelButton(listener, text) { 141 SetBorder(views::Border::CreateEmptyBorder( 142 0, views::kButtonHEdgeMarginNew, 0, views::kButtonHEdgeMarginNew)); 143 SetMinSize(gfx::Size(0, 144 kButtonHeight + views::kRelatedControlVerticalSpacing)); 145 SetImage(STATE_NORMAL, icon); 146 } 147 148 virtual ~BackgroundColorHoverButton() {} 149 150 private: 151 // views::LabelButton: 152 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 153 if ((state() == STATE_PRESSED) || 154 (state() == STATE_HOVERED) || 155 HasFocus()) { 156 canvas->DrawColor(GetNativeTheme()->GetSystemColor( 157 ui::NativeTheme::kColorId_ButtonHoverBackgroundColor)); 158 } 159 LabelButton::OnPaint(canvas); 160 } 161 162 DISALLOW_COPY_AND_ASSIGN(BackgroundColorHoverButton); 163}; 164 165// SizedContainer ------------------------------------------------- 166 167// A simple container view that takes an explicit preferred size. 168class SizedContainer : public views::View { 169 public: 170 explicit SizedContainer(const gfx::Size& preferred_size) 171 : preferred_size_(preferred_size) {} 172 173 virtual gfx::Size GetPreferredSize() const OVERRIDE { 174 return preferred_size_; 175 } 176 177 private: 178 gfx::Size preferred_size_; 179}; 180 181} // namespace 182 183// RightAlignedIconLabelButton ------------------------------------------------- 184 185// A custom LabelButton that has a centered text and right aligned icon. 186class RightAlignedIconLabelButton : public views::LabelButton { 187 public: 188 RightAlignedIconLabelButton(views::ButtonListener* listener, 189 const base::string16& text) 190 : views::LabelButton(listener, text) { 191 } 192 193 protected: 194 virtual void Layout() OVERRIDE { 195 // This layout trick keeps the text left-aligned and the icon right-aligned. 196 SetHorizontalAlignment(gfx::ALIGN_RIGHT); 197 views::LabelButton::Layout(); 198 label()->SetHorizontalAlignment(gfx::ALIGN_CENTER); 199 } 200 201 DISALLOW_COPY_AND_ASSIGN(RightAlignedIconLabelButton); 202}; 203 204// EditableProfilePhoto ------------------------------------------------- 205 206// A custom Image control that shows a "change" button when moused over. 207class EditableProfilePhoto : public views::ImageView { 208 public: 209 EditableProfilePhoto(views::ButtonListener* listener, 210 const gfx::Image& icon, 211 bool is_editing_allowed, 212 const gfx::Rect& bounds) 213 : views::ImageView(), 214 change_photo_button_(NULL) { 215 gfx::Image image = profiles::GetSizedAvatarIcon( 216 icon, true, kLargeImageSide, kLargeImageSide); 217 SetImage(image.ToImageSkia()); 218 SetBoundsRect(bounds); 219 220 // Calculate the circular mask that will be used to display the photo. 221 circular_mask_.addCircle(SkIntToScalar(bounds.width() / 2), 222 SkIntToScalar(bounds.height() / 2), 223 SkIntToScalar(bounds.width() / 2)); 224 225 if (!is_editing_allowed) 226 return; 227 228 set_notify_enter_exit_on_child(true); 229 230 // Button overlay that appears when hovering over the image. 231 change_photo_button_ = new views::LabelButton(listener, base::string16()); 232 change_photo_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 233 change_photo_button_->SetBorder(views::Border::NullBorder()); 234 235 const SkColor kBackgroundColor = SkColorSetARGB(65, 255, 255, 255); 236 change_photo_button_->set_background( 237 views::Background::CreateSolidBackground(kBackgroundColor)); 238 change_photo_button_->SetImage(views::LabelButton::STATE_NORMAL, 239 *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 240 IDR_ICON_PROFILES_EDIT_CAMERA)); 241 242 change_photo_button_->SetSize(bounds.size()); 243 change_photo_button_->SetVisible(false); 244 AddChildView(change_photo_button_); 245 } 246 247 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 248 // Display the profile picture as a circle. 249 canvas->ClipPath(circular_mask_, true); 250 views::ImageView::OnPaint(canvas); 251 } 252 253 virtual void PaintChildren(gfx::Canvas* canvas, 254 const views::CullSet& cull_set) OVERRIDE { 255 // Display any children (the "change photo" overlay) as a circle. 256 canvas->ClipPath(circular_mask_, true); 257 View::PaintChildren(canvas, cull_set); 258 } 259 260 views::LabelButton* change_photo_button() { return change_photo_button_; } 261 262 private: 263 // views::View: 264 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE { 265 if (change_photo_button_) 266 change_photo_button_->SetVisible(true); 267 } 268 269 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE { 270 if (change_photo_button_) 271 change_photo_button_->SetVisible(false); 272 } 273 274 gfx::Path circular_mask_; 275 276 // Button that is shown when hovering over the image view. Can be NULL if 277 // the photo isn't allowed to be edited (e.g. for guest profiles). 278 views::LabelButton* change_photo_button_; 279 280 DISALLOW_COPY_AND_ASSIGN(EditableProfilePhoto); 281}; 282 283// EditableProfileName ------------------------------------------------- 284 285// A custom text control that turns into a textfield for editing when clicked. 286class EditableProfileName : public RightAlignedIconLabelButton, 287 public views::ButtonListener { 288 public: 289 EditableProfileName(views::TextfieldController* controller, 290 const base::string16& text, 291 bool is_editing_allowed) 292 : RightAlignedIconLabelButton(this, text), 293 profile_name_textfield_(NULL) { 294 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 295 const gfx::FontList& medium_font_list = 296 rb->GetFontList(ui::ResourceBundle::MediumFont); 297 SetFontList(medium_font_list); 298 SetHorizontalAlignment(gfx::ALIGN_CENTER); 299 300 if (!is_editing_allowed) { 301 SetBorder(views::Border::CreateEmptyBorder(2, 0, 2, 0)); 302 return; 303 } 304 305 // Show an "edit" pencil icon when hovering over. In the default state, 306 // we need to create an empty placeholder of the correct size, so that 307 // the text doesn't jump around when the hovered icon appears. 308 gfx::ImageSkia hover_image = 309 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_HOVER); 310 SetImage(STATE_NORMAL, CreateSquarePlaceholderImage(hover_image.width())); 311 SetImage(STATE_HOVERED, hover_image); 312 SetImage(STATE_PRESSED, 313 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_EDIT_PRESSED)); 314 // To center the text, we need to offest it by the width of the icon we 315 // are adding and its padding. We need to also add a small top/bottom 316 // padding to account for the textfield's border. 317 const int kIconTextLabelButtonSpacing = 5; 318 SetBorder(views::Border::CreateEmptyBorder( 319 2, hover_image.width() + kIconTextLabelButtonSpacing, 2, 0)); 320 321 // Textfield that overlaps the button. 322 profile_name_textfield_ = new views::Textfield(); 323 profile_name_textfield_->set_controller(controller); 324 profile_name_textfield_->SetFontList(medium_font_list); 325 profile_name_textfield_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 326 327 profile_name_textfield_->SetVisible(false); 328 AddChildView(profile_name_textfield_); 329 } 330 331 views::Textfield* profile_name_textfield() { 332 return profile_name_textfield_; 333 } 334 335 // Hide the editable textfield to show the profile name button instead. 336 void ShowReadOnlyView() { 337 if (profile_name_textfield_) 338 profile_name_textfield_->SetVisible(false); 339 } 340 341 private: 342 // views::ButtonListener: 343 virtual void ButtonPressed(views::Button* sender, 344 const ui::Event& event) OVERRIDE { 345 if (profile_name_textfield_) { 346 profile_name_textfield_->SetVisible(true); 347 profile_name_textfield_->SetText(GetText()); 348 profile_name_textfield_->SelectAll(false); 349 profile_name_textfield_->RequestFocus(); 350 } 351 } 352 353 // views::LabelButton: 354 virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE { 355 // Override CustomButton's implementation, which presses the button when 356 // you press space and clicks it when you release space, as the space can be 357 // part of the new profile name typed in the textfield. 358 return false; 359 } 360 361 virtual void Layout() OVERRIDE { 362 if (profile_name_textfield_) 363 profile_name_textfield_->SetBounds(0, 0, width(), height()); 364 RightAlignedIconLabelButton::Layout(); 365 } 366 367 // Textfield that is shown when editing the profile name. Can be NULL if 368 // the profile name isn't allowed to be edited (e.g. for guest profiles). 369 views::Textfield* profile_name_textfield_; 370 371 DISALLOW_COPY_AND_ASSIGN(EditableProfileName); 372}; 373 374// A title card with one back button right aligned and one label center aligned. 375class TitleCard : public views::View { 376 public: 377 TitleCard(const base::string16& message, views::ButtonListener* listener, 378 views::ImageButton** back_button) { 379 back_button_ = new views::ImageButton(listener); 380 back_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT, 381 views::ImageButton::ALIGN_MIDDLE); 382 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 383 back_button_->SetImage(views::ImageButton::STATE_NORMAL, 384 rb->GetImageSkiaNamed(IDR_BACK)); 385 back_button_->SetImage(views::ImageButton::STATE_HOVERED, 386 rb->GetImageSkiaNamed(IDR_BACK_H)); 387 back_button_->SetImage(views::ImageButton::STATE_PRESSED, 388 rb->GetImageSkiaNamed(IDR_BACK_P)); 389 back_button_->SetImage(views::ImageButton::STATE_DISABLED, 390 rb->GetImageSkiaNamed(IDR_BACK_D)); 391 *back_button = back_button_; 392 393 title_label_ = new views::Label(message); 394 title_label_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 395 const gfx::FontList& medium_font_list = 396 rb->GetFontList(ui::ResourceBundle::MediumFont); 397 title_label_->SetFontList(medium_font_list); 398 399 AddChildView(back_button_); 400 AddChildView(title_label_); 401 } 402 403 // Creates a new view that has the |title_card| with padding at the top, an 404 // edge-to-edge separator below, and the specified |view| at the bottom. 405 static views::View* AddPaddedTitleCard(views::View* view, 406 TitleCard* title_card, 407 int width) { 408 views::View* titled_view = new views::View(); 409 views::GridLayout* layout = new views::GridLayout(titled_view); 410 titled_view->SetLayoutManager(layout); 411 412 // Column set 0 is a single column layout with horizontal padding at left 413 // and right, and column set 1 is a single column layout with no padding. 414 views::ColumnSet* columns = layout->AddColumnSet(0); 415 columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew); 416 int available_width = width - 2 * views::kButtonHEdgeMarginNew; 417 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 418 views::GridLayout::FIXED, available_width, available_width); 419 columns->AddPaddingColumn(1, views::kButtonHEdgeMarginNew); 420 layout->AddColumnSet(1)->AddColumn(views::GridLayout::FILL, 421 views::GridLayout::FILL, 0,views::GridLayout::FIXED, width, width); 422 423 layout->StartRowWithPadding(1, 0, 0, views::kButtonVEdgeMarginNew); 424 layout->AddView(title_card); 425 layout->StartRowWithPadding(1, 1, 0, views::kRelatedControlVerticalSpacing); 426 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 427 428 layout->StartRow(1, 1); 429 layout->AddView(view); 430 431 return titled_view; 432 } 433 434 private: 435 virtual void Layout() OVERRIDE{ 436 back_button_->SetBounds( 437 0, 0, back_button_->GetPreferredSize().width(), height()); 438 title_label_->SetBoundsRect(GetContentsBounds()); 439 } 440 441 virtual gfx::Size GetPreferredSize() const OVERRIDE{ 442 int height = std::max(title_label_->GetPreferredSize().height(), 443 back_button_->GetPreferredSize().height()); 444 return gfx::Size(width(), height); 445 } 446 447 views::ImageButton* back_button_; 448 views::Label* title_label_; 449 450 DISALLOW_COPY_AND_ASSIGN(TitleCard); 451}; 452 453// ProfileChooserView --------------------------------------------------------- 454 455// static 456ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL; 457bool ProfileChooserView::close_on_deactivate_for_testing_ = true; 458 459// static 460void ProfileChooserView::ShowBubble( 461 profiles::BubbleViewMode view_mode, 462 const signin::ManageAccountsParams& manage_accounts_params, 463 views::View* anchor_view, 464 views::BubbleBorder::Arrow arrow, 465 views::BubbleBorder::BubbleAlignment border_alignment, 466 Browser* browser) { 467 if (IsShowing()) 468 return; 469 470 profile_bubble_ = new ProfileChooserView(anchor_view, arrow, browser, 471 view_mode, manage_accounts_params.service_type); 472 views::BubbleDelegateView::CreateBubble(profile_bubble_); 473 profile_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_); 474 profile_bubble_->SetAlignment(border_alignment); 475 profile_bubble_->GetWidget()->Show(); 476 profile_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE); 477} 478 479// static 480bool ProfileChooserView::IsShowing() { 481 return profile_bubble_ != NULL; 482} 483 484// static 485void ProfileChooserView::Hide() { 486 if (IsShowing()) 487 profile_bubble_->GetWidget()->Close(); 488} 489 490ProfileChooserView::ProfileChooserView(views::View* anchor_view, 491 views::BubbleBorder::Arrow arrow, 492 Browser* browser, 493 profiles::BubbleViewMode view_mode, 494 signin::GAIAServiceType service_type) 495 : BubbleDelegateView(anchor_view, arrow), 496 browser_(browser), 497 view_mode_(view_mode), 498 tutorial_mode_(profiles::TUTORIAL_MODE_NONE), 499 gaia_service_type_(service_type) { 500 // Reset the default margins inherited from the BubbleDelegateView. 501 // Add a small top/bottom inset so that the bubble's rounded corners show up. 502 set_margins(gfx::Insets(1, 0, 1, 0)); 503 set_background(views::Background::CreateSolidBackground( 504 GetNativeTheme()->GetSystemColor( 505 ui::NativeTheme::kColorId_DialogBackground))); 506 ResetView(); 507 508 avatar_menu_.reset(new AvatarMenu( 509 &g_browser_process->profile_manager()->GetProfileInfoCache(), 510 this, 511 browser_)); 512 avatar_menu_->RebuildMenu(); 513 514 ProfileOAuth2TokenService* oauth2_token_service = 515 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile()); 516 if (oauth2_token_service) 517 oauth2_token_service->AddObserver(this); 518} 519 520ProfileChooserView::~ProfileChooserView() { 521 ProfileOAuth2TokenService* oauth2_token_service = 522 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile()); 523 if (oauth2_token_service) 524 oauth2_token_service->RemoveObserver(this); 525} 526 527void ProfileChooserView::ResetView() { 528 open_other_profile_indexes_map_.clear(); 529 delete_account_button_map_.clear(); 530 reauth_account_button_map_.clear(); 531 manage_accounts_link_ = NULL; 532 signin_current_profile_link_ = NULL; 533 auth_error_email_button_ = NULL; 534 current_profile_photo_ = NULL; 535 current_profile_name_ = NULL; 536 users_button_ = NULL; 537 go_incognito_button_ = NULL; 538 lock_button_ = NULL; 539 add_account_link_ = NULL; 540 gaia_signin_cancel_button_ = NULL; 541 remove_account_button_ = NULL; 542 account_removal_cancel_button_ = NULL; 543 add_person_button_ = NULL; 544 disconnect_button_ = NULL; 545 switch_user_cancel_button_ = NULL; 546 tutorial_sync_settings_ok_button_ = NULL; 547 tutorial_sync_settings_link_ = NULL; 548 tutorial_see_whats_new_button_ = NULL; 549 tutorial_not_you_link_ = NULL; 550 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE; 551} 552 553void ProfileChooserView::Init() { 554 // If view mode is PROFILE_CHOOSER but there is an auth error, force 555 // ACCOUNT_MANAGEMENT mode. 556 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER && 557 HasAuthError(browser_->profile()) && 558 switches::IsEnableAccountConsistency() && 559 avatar_menu_->GetItemAt(avatar_menu_->GetActiveProfileIndex()). 560 signed_in) { 561 view_mode_ = profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT; 562 } 563 564 ShowView(view_mode_, avatar_menu_.get()); 565} 566 567void ProfileChooserView::OnAvatarMenuChanged( 568 AvatarMenu* avatar_menu) { 569 // Do not refresh the avatar menu if the user is on a signin related view. 570 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN || 571 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT || 572 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) { 573 return; 574 } 575 576 // Refresh the view with the new menu. We can't just update the local copy 577 // as this may have been triggered by a sign out action, in which case 578 // the view is being destroyed. 579 ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu); 580} 581 582void ProfileChooserView::OnRefreshTokenAvailable( 583 const std::string& account_id) { 584 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT || 585 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN || 586 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT || 587 view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH) { 588 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN) 589 tutorial_mode_ = profiles::TUTORIAL_MODE_CONFIRM_SIGNIN; 590 // The account management UI is only available through the 591 // --enable-account-consistency flag. 592 ShowView(switches::IsEnableAccountConsistency() ? 593 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT : 594 profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get()); 595 } 596} 597 598void ProfileChooserView::OnRefreshTokenRevoked(const std::string& account_id) { 599 // Refresh the account management view when an account is removed from the 600 // profile. 601 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) 602 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get()); 603} 604 605void ProfileChooserView::ShowView(profiles::BubbleViewMode view_to_display, 606 AvatarMenu* avatar_menu) { 607 // The account management view should only be displayed if the active profile 608 // is signed in. 609 const AvatarMenu::Item& active_item = avatar_menu->GetItemAt( 610 avatar_menu->GetActiveProfileIndex()); 611 if (view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) { 612 DCHECK(switches::IsEnableAccountConsistency()); 613 DCHECK(active_item.signed_in); 614 } 615 616 if (browser_->profile()->IsSupervised() && 617 (view_to_display == profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT || 618 view_to_display == profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL)) { 619 LOG(WARNING) << "Supervised user attempted to add/remove account"; 620 return; 621 } 622 623 // Records the last tutorial mode. 624 profiles::TutorialMode last_tutorial_mode = tutorial_mode_; 625 ResetView(); 626 RemoveAllChildViews(true); 627 view_mode_ = view_to_display; 628 629 views::GridLayout* layout; 630 views::View* sub_view; 631 switch (view_mode_) { 632 case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN: 633 case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT: 634 case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH: 635 layout = CreateSingleColumnLayout(this, kFixedGaiaViewWidth); 636 sub_view = CreateGaiaSigninView(); 637 break; 638 case profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL: 639 layout = CreateSingleColumnLayout(this, kFixedAccountRemovalViewWidth); 640 sub_view = CreateAccountRemovalView(); 641 break; 642 case profiles::BUBBLE_VIEW_MODE_SWITCH_USER: 643 layout = CreateSingleColumnLayout(this, kFixedSwitchUserViewWidth); 644 sub_view = CreateSwitchUserView(active_item); 645 break; 646 default: 647 layout = CreateSingleColumnLayout(this, kFixedMenuWidth); 648 sub_view = CreateProfileChooserView(avatar_menu, last_tutorial_mode); 649 } 650 layout->StartRow(1, 0); 651 layout->AddView(sub_view); 652 Layout(); 653 if (GetBubbleFrameView()) 654 SizeToContents(); 655} 656 657void ProfileChooserView::WindowClosing() { 658 DCHECK_EQ(profile_bubble_, this); 659 profile_bubble_ = NULL; 660 661 if (tutorial_mode_ == profiles::TUTORIAL_MODE_CONFIRM_SIGNIN) { 662 LoginUIServiceFactory::GetForProfile(browser_->profile())-> 663 SyncConfirmationUIClosed(false /* configure_sync_first */); 664 } 665} 666 667void ProfileChooserView::ButtonPressed(views::Button* sender, 668 const ui::Event& event) { 669 // Disable button after clicking so that it doesn't get clicked twice and 670 // start a second action... which can crash Chrome. But don't disable if it 671 // has no parent (like in tests) because that will also crash. 672 if (sender->parent()) 673 sender->SetEnabled(false); 674 675 if (sender == users_button_) { 676 profiles::ShowUserManagerMaybeWithTutorial(browser_->profile()); 677 // If this is a guest session, also close all the guest browser windows. 678 if (browser_->profile()->IsGuestSession()) 679 profiles::CloseGuestProfileWindows(); 680 } else if (sender == go_incognito_button_) { 681 DCHECK(ShouldShowGoIncognito()); 682 chrome::NewIncognitoWindow(browser_); 683 } else if (sender == lock_button_) { 684 profiles::LockProfile(browser_->profile()); 685 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_LOCK); 686 } else if (sender == auth_error_email_button_) { 687 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get()); 688 } else if (sender == tutorial_sync_settings_ok_button_) { 689 LoginUIServiceFactory::GetForProfile(browser_->profile())-> 690 SyncConfirmationUIClosed(false /* configure_sync_first */); 691 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE; 692 ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get()); 693 } else if (sender == tutorial_see_whats_new_button_) { 694 chrome::ShowUserManagerWithTutorial( 695 profiles::USER_MANAGER_TUTORIAL_OVERVIEW); 696 } else if (sender == remove_account_button_) { 697 RemoveAccount(); 698 } else if (sender == account_removal_cancel_button_) { 699 account_id_to_remove_.clear(); 700 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get()); 701 } else if (sender == gaia_signin_cancel_button_) { 702 std::string primary_account = 703 SigninManagerFactory::GetForProfile(browser_->profile())-> 704 GetAuthenticatedUsername(); 705 // The account management view is only available with the 706 // --enable-account-consistency flag. 707 bool account_management_available = !primary_account.empty() && 708 switches::IsEnableAccountConsistency(); 709 ShowView(account_management_available ? 710 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT : 711 profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get()); 712 } else if (current_profile_photo_ && 713 sender == current_profile_photo_->change_photo_button()) { 714 avatar_menu_->EditProfile(avatar_menu_->GetActiveProfileIndex()); 715 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_IMAGE); 716 } else if (sender == signin_current_profile_link_) { 717 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN, avatar_menu_.get()); 718 } else if (sender == add_person_button_) { 719 profiles::ShowUserManagerMaybeWithTutorial(browser_->profile()); 720 } else if (sender == disconnect_button_) { 721 chrome::ShowSettings(browser_); 722 } else if (sender == switch_user_cancel_button_) { 723 ShowView(profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER, avatar_menu_.get()); 724 } else { 725 // Either one of the "other profiles", or one of the profile accounts 726 // buttons was pressed. 727 ButtonIndexes::const_iterator profile_match = 728 open_other_profile_indexes_map_.find(sender); 729 if (profile_match != open_other_profile_indexes_map_.end()) { 730 avatar_menu_->SwitchToProfile( 731 profile_match->second, 732 ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW, 733 ProfileMetrics::SWITCH_PROFILE_ICON); 734 } else { 735 // This was a profile accounts button. 736 AccountButtonIndexes::const_iterator account_match = 737 delete_account_button_map_.find(sender); 738 if (account_match != delete_account_button_map_.end()) { 739 account_id_to_remove_ = account_match->second; 740 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL, 741 avatar_menu_.get()); 742 } else { 743 account_match = reauth_account_button_map_.find(sender); 744 DCHECK(account_match != reauth_account_button_map_.end()); 745 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH, avatar_menu_.get()); 746 } 747 } 748 } 749} 750 751void ProfileChooserView::RemoveAccount() { 752 DCHECK(!account_id_to_remove_.empty()); 753 MutableProfileOAuth2TokenService* oauth2_token_service = 754 ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile( 755 browser_->profile()); 756 if (oauth2_token_service) { 757 oauth2_token_service->RevokeCredentials(account_id_to_remove_); 758 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_REMOVE_ACCT); 759 } 760 account_id_to_remove_.clear(); 761 762 ShowView(profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, avatar_menu_.get()); 763} 764 765void ProfileChooserView::LinkClicked(views::Link* sender, int event_flags) { 766 if (sender == manage_accounts_link_) { 767 // This link can either mean show/hide the account management view, 768 // depending on which view it is displayed. ShowView() will DCHECK if 769 // the account management view is displayed for non signed-in users. 770 ShowView( 771 view_mode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT ? 772 profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER : 773 profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT, 774 avatar_menu_.get()); 775 } else if (sender == add_account_link_) { 776 ShowView(profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT, avatar_menu_.get()); 777 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_ADD_ACCT); 778 } else if (sender == tutorial_sync_settings_link_) { 779 LoginUIServiceFactory::GetForProfile(browser_->profile())-> 780 SyncConfirmationUIClosed(true /* configure_sync_first */); 781 tutorial_mode_ = profiles::TUTORIAL_MODE_NONE; 782 } else { 783 DCHECK(sender == tutorial_not_you_link_); 784 ShowView(profiles::BUBBLE_VIEW_MODE_SWITCH_USER, avatar_menu_.get()); 785 } 786} 787 788void ProfileChooserView::StyledLabelLinkClicked( 789 const gfx::Range& range, int event_flags) { 790 chrome::ShowSettings(browser_); 791} 792 793bool ProfileChooserView::HandleKeyEvent(views::Textfield* sender, 794 const ui::KeyEvent& key_event) { 795 views::Textfield* name_textfield = 796 current_profile_name_->profile_name_textfield(); 797 DCHECK(sender == name_textfield); 798 799 if (key_event.key_code() == ui::VKEY_RETURN || 800 key_event.key_code() == ui::VKEY_TAB) { 801 // Pressing Tab/Enter commits the new profile name, unless it's empty. 802 base::string16 new_profile_name = name_textfield->text(); 803 if (new_profile_name.empty()) 804 return true; 805 806 const AvatarMenu::Item& active_item = avatar_menu_->GetItemAt( 807 avatar_menu_->GetActiveProfileIndex()); 808 Profile* profile = g_browser_process->profile_manager()->GetProfile( 809 active_item.profile_path); 810 DCHECK(profile); 811 812 if (profile->IsSupervised()) 813 return true; 814 815 profiles::UpdateProfileName(profile, new_profile_name); 816 PostActionPerformed(ProfileMetrics::PROFILE_DESKTOP_MENU_EDIT_NAME); 817 current_profile_name_->ShowReadOnlyView(); 818 return true; 819 } 820 return false; 821} 822 823views::View* ProfileChooserView::CreateProfileChooserView( 824 AvatarMenu* avatar_menu, 825 profiles::TutorialMode last_tutorial_mode) { 826 // TODO(guohui, noms): the view should be customized based on whether new 827 // profile management preview is enabled or not. 828 829 views::View* view = new views::View(); 830 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth); 831 // Separate items into active and alternatives. 832 Indexes other_profiles; 833 views::View* tutorial_view = NULL; 834 views::View* current_profile_view = NULL; 835 views::View* current_profile_accounts = NULL; 836 views::View* option_buttons_view = NULL; 837 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) { 838 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i); 839 if (item.active) { 840 option_buttons_view = CreateOptionsView( 841 switches::IsNewProfileManagement() && item.signed_in); 842 current_profile_view = CreateCurrentProfileView(item, false); 843 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) { 844 switch (last_tutorial_mode) { 845 case profiles::TUTORIAL_MODE_NONE: 846 case profiles::TUTORIAL_MODE_WELCOME_UPGRADE: 847 tutorial_view = CreateWelcomeUpgradeTutorialViewIfNeeded( 848 last_tutorial_mode == profiles::TUTORIAL_MODE_WELCOME_UPGRADE, 849 item); 850 break; 851 case profiles::TUTORIAL_MODE_CONFIRM_SIGNIN: 852 tutorial_view = CreateSigninConfirmationView(); 853 break; 854 case profiles::TUTORIAL_MODE_SHOW_ERROR: 855 // TODO(guohui): not implemented yet. 856 NOTREACHED(); 857 } 858 } else { 859 current_profile_accounts = CreateCurrentProfileAccountsView(item); 860 } 861 } else { 862 other_profiles.push_back(i); 863 } 864 } 865 866 if (tutorial_view) { 867 // TODO(mlerman): update UMA stats for the new tutorial. 868 layout->StartRow(1, 0); 869 layout->AddView(tutorial_view); 870 } 871 872 if (!current_profile_view) { 873 // Guest windows don't have an active profile. 874 current_profile_view = CreateGuestProfileView(); 875 option_buttons_view = CreateOptionsView(false); 876 } 877 878 layout->StartRow(1, 0); 879 layout->AddView(current_profile_view); 880 881 if (view_mode_ != profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) { 882 DCHECK(current_profile_accounts); 883 layout->StartRow(0, 0); 884 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 885 layout->StartRow(1, 0); 886 layout->AddView(current_profile_accounts); 887 } 888 889 if (browser_->profile()->IsSupervised()) { 890 layout->StartRow(0, 0); 891 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 892 layout->StartRow(1, 0); 893 layout->AddView(CreateSupervisedUserDisclaimerView()); 894 } 895 896 if (view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) { 897 layout->StartRow(1, 0); 898 if (switches::IsFastUserSwitching()) 899 layout->AddView(CreateOtherProfilesView(other_profiles)); 900 } 901 902 layout->StartRow(0, 0); 903 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 904 905 if (option_buttons_view) { 906 layout->StartRow(0, 0); 907 layout->AddView(option_buttons_view); 908 } 909 910 return view; 911} 912 913views::View* ProfileChooserView::CreateTutorialView( 914 profiles::TutorialMode tutorial_mode, 915 const base::string16& title_text, 916 const base::string16& content_text, 917 const base::string16& link_text, 918 const base::string16& button_text, 919 views::Link** link, 920 views::LabelButton** button) { 921 tutorial_mode_ = tutorial_mode; 922 923 views::View* view = new views::View(); 924 view->set_background(views::Background::CreateSolidBackground( 925 profiles::kAvatarTutorialBackgroundColor)); 926 views::GridLayout* layout = CreateSingleColumnLayout(view, 927 kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew); 928 layout->SetInsets(views::kButtonVEdgeMarginNew, 929 views::kButtonHEdgeMarginNew, 930 views::kButtonVEdgeMarginNew, 931 views::kButtonHEdgeMarginNew); 932 933 // Adds title. 934 views::Label* title_label = new views::Label(title_text); 935 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 936 title_label->SetAutoColorReadabilityEnabled(false); 937 title_label->SetEnabledColor(SK_ColorWHITE); 938 title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 939 ui::ResourceBundle::MediumFont)); 940 layout->StartRow(1, 0); 941 layout->AddView(title_label); 942 943 // Adds body content. 944 views::Label* content_label = new views::Label(content_text); 945 content_label->SetMultiLine(true); 946 content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 947 content_label->SetAutoColorReadabilityEnabled(false); 948 content_label->SetEnabledColor(profiles::kAvatarTutorialContentTextColor); 949 layout->StartRowWithPadding(1, 0, 0, views::kRelatedControlVerticalSpacing); 950 layout->AddView(content_label); 951 952 // Adds links and buttons. 953 views::ColumnSet* button_columns = layout->AddColumnSet(1); 954 button_columns->AddColumn(views::GridLayout::LEADING, 955 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); 956 button_columns->AddPaddingColumn( 957 1, views::kUnrelatedControlHorizontalSpacing); 958 button_columns->AddColumn(views::GridLayout::TRAILING, 959 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0); 960 961 *link = CreateLink(link_text, this); 962 (*link)->SetHorizontalAlignment(gfx::ALIGN_LEFT); 963 (*link)->SetAutoColorReadabilityEnabled(false); 964 (*link)->SetEnabledColor(SK_ColorWHITE); 965 layout->StartRowWithPadding(1, 1, 0, views::kUnrelatedControlVerticalSpacing); 966 layout->AddView(*link); 967 968 *button = new views::LabelButton(this, button_text); 969 (*button)->SetHorizontalAlignment(gfx::ALIGN_CENTER); 970 (*button)->SetStyle(views::Button::STYLE_BUTTON); 971 layout->AddView(*button); 972 973 return view; 974} 975 976views::View* ProfileChooserView::CreateCurrentProfileView( 977 const AvatarMenu::Item& avatar_item, 978 bool is_guest) { 979 views::View* view = new views::View(); 980 int column_width = kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew; 981 views::GridLayout* layout = CreateSingleColumnLayout(view, column_width); 982 layout->SetInsets(views::kButtonVEdgeMarginNew, 983 views::kButtonHEdgeMarginNew, 984 views::kUnrelatedControlVerticalSpacing, 985 views::kButtonHEdgeMarginNew); 986 987 // Profile icon, centered. 988 int x_offset = (column_width - kLargeImageSide) / 2; 989 current_profile_photo_ = new EditableProfilePhoto( 990 this, avatar_item.icon, !is_guest, 991 gfx::Rect(x_offset, 0, kLargeImageSide, kLargeImageSide)); 992 SizedContainer* profile_icon_container = 993 new SizedContainer(gfx::Size(column_width, kLargeImageSide)); 994 profile_icon_container->AddChildView(current_profile_photo_); 995 996 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 997 if (browser_->profile()->IsSupervised()) { 998 views::ImageView* supervised_icon = new views::ImageView(); 999 supervised_icon->SetImage( 1000 rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_SUPERVISED)); 1001 gfx::Size preferred_size = supervised_icon->GetPreferredSize(); 1002 gfx::Rect parent_bounds = current_profile_photo_->bounds(); 1003 supervised_icon->SetBounds( 1004 parent_bounds.right() - preferred_size.width(), 1005 parent_bounds.bottom() - preferred_size.height(), 1006 preferred_size.width(), 1007 preferred_size.height()); 1008 profile_icon_container->AddChildView(supervised_icon); 1009 } 1010 1011 layout->StartRow(1, 0); 1012 layout->AddView(profile_icon_container); 1013 1014 // Profile name, centered. 1015 bool editing_allowed = !is_guest && !browser_->profile()->IsSupervised(); 1016 current_profile_name_ = new EditableProfileName( 1017 this, 1018 profiles::GetAvatarNameForProfile(browser_->profile()->GetPath()), 1019 editing_allowed); 1020 layout->StartRowWithPadding(1, 0, 0, 1021 views::kRelatedControlSmallVerticalSpacing); 1022 layout->StartRow(1, 0); 1023 layout->AddView(current_profile_name_); 1024 1025 if (is_guest) 1026 return view; 1027 1028 // The available links depend on the type of profile that is active. 1029 if (avatar_item.signed_in) { 1030 layout->StartRow(1, 0); 1031 if (switches::IsEnableAccountConsistency()) { 1032 base::string16 link_title = l10n_util::GetStringUTF16( 1033 view_mode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER ? 1034 IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON : 1035 IDS_PROFILES_PROFILE_HIDE_MANAGE_ACCOUNTS_BUTTON); 1036 manage_accounts_link_ = CreateLink(link_title, this); 1037 manage_accounts_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 1038 layout->AddView(manage_accounts_link_); 1039 } else { 1040 // Badge the email address if there's an authentication error. 1041 if (HasAuthError(browser_->profile())) { 1042 const gfx::ImageSkia warning_image = *rb->GetImageNamed( 1043 IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia(); 1044 auth_error_email_button_ = 1045 new RightAlignedIconLabelButton(this, avatar_item.sync_state); 1046 auth_error_email_button_->SetElideBehavior(gfx::ELIDE_EMAIL); 1047 auth_error_email_button_->SetBorder(views::Border::NullBorder()); 1048 auth_error_email_button_->SetImage( 1049 views::LabelButton::STATE_NORMAL, warning_image); 1050 auth_error_email_button_->SetTextColor( 1051 views::LabelButton::STATE_NORMAL, 1052 views::Link::GetDefaultEnabledColor()); 1053 layout->AddView(auth_error_email_button_); 1054 } else { 1055 views::Label* email_label = new views::Label(avatar_item.sync_state); 1056 email_label->SetElideBehavior(gfx::ELIDE_EMAIL); 1057 layout->AddView(email_label); 1058 } 1059 } 1060 } else { 1061 SigninManagerBase* signin_manager = SigninManagerFactory::GetForProfile( 1062 browser_->profile()->GetOriginalProfile()); 1063 if (signin_manager->IsSigninAllowed()) { 1064 views::Label* promo = new views::Label( 1065 l10n_util::GetStringUTF16(IDS_PROFILES_SIGNIN_PROMO)); 1066 promo->SetMultiLine(true); 1067 promo->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1068 layout->StartRowWithPadding(1, 0, 0, 1069 views::kRelatedControlSmallVerticalSpacing); 1070 layout->StartRow(1, 0); 1071 layout->AddView(promo); 1072 1073 signin_current_profile_link_ = new views::BlueButton( 1074 this, l10n_util::GetStringFUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL, 1075 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME))); 1076 layout->StartRowWithPadding(1, 0, 0, 1077 views::kRelatedControlVerticalSpacing); 1078 layout->StartRow(1, 0); 1079 layout->AddView(signin_current_profile_link_); 1080 } 1081 } 1082 1083 return view; 1084} 1085 1086views::View* ProfileChooserView::CreateGuestProfileView() { 1087 gfx::Image guest_icon = 1088 ui::ResourceBundle::GetSharedInstance().GetImageNamed( 1089 profiles::GetPlaceholderAvatarIconResourceID()); 1090 AvatarMenu::Item guest_avatar_item(0, 0, guest_icon); 1091 guest_avatar_item.active = true; 1092 guest_avatar_item.name = l10n_util::GetStringUTF16( 1093 IDS_PROFILES_GUEST_PROFILE_NAME); 1094 guest_avatar_item.signed_in = false; 1095 1096 return CreateCurrentProfileView(guest_avatar_item, true); 1097} 1098 1099views::View* ProfileChooserView::CreateOtherProfilesView( 1100 const Indexes& avatars_to_show) { 1101 views::View* view = new views::View(); 1102 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth); 1103 1104 int num_avatars_to_show = avatars_to_show.size(); 1105 for (int i = 0; i < num_avatars_to_show; ++i) { 1106 const size_t index = avatars_to_show[i]; 1107 const AvatarMenu::Item& item = avatar_menu_->GetItemAt(index); 1108 const int kSmallImageSide = 32; 1109 1110 gfx::Image image = profiles::GetSizedAvatarIcon( 1111 item.icon, true, kSmallImageSide, kSmallImageSide); 1112 1113 views::LabelButton* button = new BackgroundColorHoverButton( 1114 this, 1115 item.name, 1116 *image.ToImageSkia()); 1117 open_other_profile_indexes_map_[button] = index; 1118 1119 layout->StartRow(1, 0); 1120 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 1121 layout->StartRow(1, 0); 1122 layout->AddView(button); 1123 } 1124 1125 return view; 1126} 1127 1128views::View* ProfileChooserView::CreateOptionsView(bool enable_lock) { 1129 views::View* view = new views::View(); 1130 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth); 1131 1132 base::string16 text = browser_->profile()->IsGuestSession() ? 1133 l10n_util::GetStringUTF16(IDS_PROFILES_EXIT_GUEST) : 1134 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_USERS_BUTTON); 1135 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1136 users_button_ = new BackgroundColorHoverButton( 1137 this, 1138 text, 1139 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR)); 1140 layout->StartRow(1, 0); 1141 layout->AddView(users_button_); 1142 1143 if (ShouldShowGoIncognito()) { 1144 layout->StartRow(1, 0); 1145 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 1146 1147 // TODO(noms): Use the correct incognito icon when it's available. 1148 go_incognito_button_ = new BackgroundColorHoverButton( 1149 this, 1150 l10n_util::GetStringUTF16(IDS_PROFILES_GO_INCOGNITO_BUTTON), 1151 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR)); 1152 layout->StartRow(1, 0); 1153 layout->AddView(go_incognito_button_); 1154 } 1155 1156 if (enable_lock) { 1157 layout->StartRow(1, 0); 1158 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 1159 1160 lock_button_ = new BackgroundColorHoverButton( 1161 this, 1162 l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON), 1163 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_LOCK)); 1164 layout->StartRow(1, 0); 1165 layout->AddView(lock_button_); 1166 } 1167 return view; 1168} 1169 1170views::View* ProfileChooserView::CreateSupervisedUserDisclaimerView() { 1171 views::View* view = new views::View(); 1172 views::GridLayout* layout = CreateSingleColumnLayout( 1173 view, kFixedMenuWidth - 2 * views::kButtonHEdgeMarginNew); 1174 layout->SetInsets(views::kRelatedControlVerticalSpacing, 1175 views::kButtonHEdgeMarginNew, 1176 views::kRelatedControlVerticalSpacing, 1177 views::kButtonHEdgeMarginNew); 1178 views::Label* disclaimer = new views::Label( 1179 avatar_menu_->GetSupervisedUserInformation()); 1180 disclaimer->SetMultiLine(true); 1181 disclaimer->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1182 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1183 disclaimer->SetFontList(rb->GetFontList(ui::ResourceBundle::SmallFont)); 1184 layout->StartRow(1, 0); 1185 layout->AddView(disclaimer); 1186 1187 return view; 1188} 1189 1190views::View* ProfileChooserView::CreateCurrentProfileAccountsView( 1191 const AvatarMenu::Item& avatar_item) { 1192 DCHECK(avatar_item.signed_in); 1193 views::View* view = new views::View(); 1194 view->set_background(views::Background::CreateSolidBackground( 1195 profiles::kAvatarBubbleAccountsBackgroundColor)); 1196 views::GridLayout* layout = CreateSingleColumnLayout(view, kFixedMenuWidth); 1197 1198 Profile* profile = browser_->profile(); 1199 std::string primary_account = 1200 SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedUsername(); 1201 DCHECK(!primary_account.empty()); 1202 std::vector<std::string>accounts = 1203 profiles::GetSecondaryAccountsForProfile(profile, primary_account); 1204 1205 // Get state of authentication error, if any. 1206 std::string error_account_id = GetAuthErrorAccountId(profile); 1207 1208 // The primary account should always be listed first. 1209 // TODO(rogerta): we still need to further differentiate the primary account 1210 // from the others in the UI, so more work is likely required here: 1211 // crbug.com/311124. 1212 CreateAccountButton(layout, primary_account, true, 1213 error_account_id == primary_account, kFixedMenuWidth); 1214 for (size_t i = 0; i < accounts.size(); ++i) 1215 CreateAccountButton(layout, accounts[i], false, 1216 error_account_id == accounts[i], kFixedMenuWidth); 1217 1218 if (!profile->IsSupervised()) { 1219 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 1220 1221 add_account_link_ = CreateLink(l10n_util::GetStringFUTF16( 1222 IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, avatar_item.name), this); 1223 add_account_link_->SetBorder(views::Border::CreateEmptyBorder( 1224 0, views::kButtonVEdgeMarginNew, 1225 views::kRelatedControlVerticalSpacing, 0)); 1226 layout->StartRow(1, 0); 1227 layout->AddView(add_account_link_); 1228 } 1229 1230 return view; 1231} 1232 1233void ProfileChooserView::CreateAccountButton(views::GridLayout* layout, 1234 const std::string& account, 1235 bool is_primary_account, 1236 bool reauth_required, 1237 int width) { 1238 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1239 const gfx::ImageSkia* delete_default_image = 1240 rb->GetImageNamed(IDR_CLOSE_1).ToImageSkia(); 1241 const int kDeleteButtonWidth = delete_default_image->width(); 1242 const gfx::ImageSkia warning_default_image = reauth_required ? 1243 *rb->GetImageNamed(IDR_ICON_PROFILES_ACCOUNT_BUTTON_ERROR).ToImageSkia() : 1244 gfx::ImageSkia(); 1245 const int kWarningButtonWidth = reauth_required ? 1246 warning_default_image.width() + views::kRelatedButtonHSpacing : 0; 1247 int available_width = width - 2 * views::kButtonHEdgeMarginNew 1248 - kDeleteButtonWidth - kWarningButtonWidth; 1249 views::LabelButton* email_button = new BackgroundColorHoverButton( 1250 reauth_required ? this : NULL, 1251 base::UTF8ToUTF16(account), 1252 warning_default_image); 1253 email_button->SetElideBehavior(gfx::ELIDE_EMAIL); 1254 email_button->SetMinSize(gfx::Size(0, kButtonHeight)); 1255 email_button->SetMaxSize(gfx::Size(available_width, kButtonHeight)); 1256 layout->StartRow(1, 0); 1257 layout->AddView(email_button); 1258 1259 if (reauth_required) 1260 reauth_account_button_map_[email_button] = account; 1261 1262 // Delete button. 1263 if (!browser_->profile()->IsSupervised()) { 1264 views::ImageButton* delete_button = new views::ImageButton(this); 1265 delete_button->SetImageAlignment(views::ImageButton::ALIGN_RIGHT, 1266 views::ImageButton::ALIGN_MIDDLE); 1267 delete_button->SetImage(views::ImageButton::STATE_NORMAL, 1268 delete_default_image); 1269 delete_button->SetImage(views::ImageButton::STATE_HOVERED, 1270 rb->GetImageSkiaNamed(IDR_CLOSE_1_H)); 1271 delete_button->SetImage(views::ImageButton::STATE_PRESSED, 1272 rb->GetImageSkiaNamed(IDR_CLOSE_1_P)); 1273 delete_button->SetBounds( 1274 width - views::kButtonHEdgeMarginNew - kDeleteButtonWidth, 1275 0, kDeleteButtonWidth, kButtonHeight); 1276 1277 email_button->set_notify_enter_exit_on_child(true); 1278 email_button->AddChildView(delete_button); 1279 1280 // Save the original email address, as the button text could be elided. 1281 delete_account_button_map_[delete_button] = account; 1282 } 1283} 1284 1285views::View* ProfileChooserView::CreateGaiaSigninView() { 1286 GURL url; 1287 int message_id; 1288 1289 switch (view_mode_) { 1290 case profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN: 1291 url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_SIGN_IN, 1292 false /* auto_close */, 1293 true /* is_constrained */); 1294 message_id = IDS_PROFILES_GAIA_SIGNIN_TITLE; 1295 break; 1296 case profiles::BUBBLE_VIEW_MODE_GAIA_ADD_ACCOUNT: 1297 url = signin::GetPromoURL(signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT, 1298 false /* auto_close */, 1299 true /* is_constrained */); 1300 message_id = IDS_PROFILES_GAIA_ADD_ACCOUNT_TITLE; 1301 break; 1302 case profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH: { 1303 DCHECK(HasAuthError(browser_->profile())); 1304 url = signin::GetReauthURL(browser_->profile(), 1305 GetAuthErrorUsername(browser_->profile())); 1306 message_id = IDS_PROFILES_GAIA_REAUTH_TITLE; 1307 break; 1308 } 1309 default: 1310 NOTREACHED() << "Called with invalid mode=" << view_mode_; 1311 return NULL; 1312 } 1313 1314 // Adds Gaia signin webview 1315 Profile* profile = browser_->profile(); 1316 views::WebView* web_view = new views::WebView(profile); 1317 web_view->LoadInitialURL(url); 1318 web_view->SetPreferredSize( 1319 gfx::Size(kFixedGaiaViewWidth, kFixedGaiaViewHeight)); 1320 1321 TitleCard* title_card = new TitleCard(l10n_util::GetStringUTF16(message_id), 1322 this, 1323 &gaia_signin_cancel_button_); 1324 return TitleCard::AddPaddedTitleCard( 1325 web_view, title_card, kFixedGaiaViewWidth); 1326} 1327 1328views::View* ProfileChooserView::CreateAccountRemovalView() { 1329 views::View* view = new views::View(); 1330 views::GridLayout* layout = CreateSingleColumnLayout( 1331 view, kFixedAccountRemovalViewWidth - 2 * views::kButtonHEdgeMarginNew); 1332 layout->SetInsets(0, 1333 views::kButtonHEdgeMarginNew, 1334 views::kButtonVEdgeMarginNew, 1335 views::kButtonHEdgeMarginNew); 1336 1337 const std::string& primary_account = SigninManagerFactory::GetForProfile( 1338 browser_->profile())->GetAuthenticatedUsername(); 1339 bool is_primary_account = primary_account == account_id_to_remove_; 1340 1341 // Adds main text. 1342 layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing); 1343 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1344 const gfx::FontList& small_font_list = 1345 rb->GetFontList(ui::ResourceBundle::SmallFont); 1346 1347 if (is_primary_account) { 1348 std::vector<size_t> offsets; 1349 const base::string16 settings_text = 1350 l10n_util::GetStringUTF16(IDS_PROFILES_SETTINGS_LINK); 1351 const base::string16 primary_account_removal_text = 1352 l10n_util::GetStringFUTF16(IDS_PROFILES_PRIMARY_ACCOUNT_REMOVAL_TEXT, 1353 base::UTF8ToUTF16(account_id_to_remove_), settings_text, &offsets); 1354 views::StyledLabel* primary_account_removal_label = 1355 new views::StyledLabel(primary_account_removal_text, this); 1356 primary_account_removal_label->AddStyleRange( 1357 gfx::Range(offsets[1], offsets[1] + settings_text.size()), 1358 views::StyledLabel::RangeStyleInfo::CreateForLink()); 1359 primary_account_removal_label->SetBaseFontList(small_font_list); 1360 layout->AddView(primary_account_removal_label); 1361 } else { 1362 views::Label* content_label = new views::Label( 1363 l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TEXT)); 1364 content_label->SetMultiLine(true); 1365 content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1366 content_label->SetFontList(small_font_list); 1367 layout->AddView(content_label); 1368 } 1369 1370 // Adds button. 1371 if (!is_primary_account) { 1372 remove_account_button_ = new views::BlueButton( 1373 this, l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_BUTTON)); 1374 remove_account_button_->SetHorizontalAlignment( 1375 gfx::ALIGN_CENTER); 1376 layout->StartRowWithPadding( 1377 1, 0, 0, views::kUnrelatedControlVerticalSpacing); 1378 layout->AddView(remove_account_button_); 1379 } else { 1380 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 1381 } 1382 1383 TitleCard* title_card = new TitleCard( 1384 l10n_util::GetStringUTF16(IDS_PROFILES_ACCOUNT_REMOVAL_TITLE), 1385 this, &account_removal_cancel_button_); 1386 return TitleCard::AddPaddedTitleCard(view, title_card, 1387 kFixedAccountRemovalViewWidth); 1388} 1389 1390views::View* ProfileChooserView::CreateWelcomeUpgradeTutorialViewIfNeeded( 1391 bool tutorial_shown, const AvatarMenu::Item& avatar_item){ 1392 if (first_run::IsChromeFirstRun()) 1393 return NULL; 1394 1395 Profile* profile = browser_->profile(); 1396 if (!avatar_item.signed_in) { 1397 profile->GetPrefs()->SetInteger( 1398 prefs::kProfileAvatarTutorialShown, kUpgradeWelcomeTutorialShowMax + 1); 1399 return NULL; 1400 } 1401 1402 const int show_count = profile->GetPrefs()->GetInteger( 1403 prefs::kProfileAvatarTutorialShown); 1404 // Do not show the tutorial if user has dismissed it. 1405 if (show_count > kUpgradeWelcomeTutorialShowMax) 1406 return NULL; 1407 1408 if (!tutorial_shown) { 1409 if (show_count == kUpgradeWelcomeTutorialShowMax) 1410 return NULL; 1411 profile->GetPrefs()->SetInteger( 1412 prefs::kProfileAvatarTutorialShown, show_count + 1); 1413 } 1414 1415 return CreateTutorialView( 1416 profiles::TUTORIAL_MODE_WELCOME_UPGRADE, 1417 l10n_util::GetStringFUTF16( 1418 IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_TITLE, avatar_item.name), 1419 l10n_util::GetStringUTF16( 1420 IDS_PROFILES_WELCOME_UPGRADE_TUTORIAL_CONTENT_TEXT), 1421 l10n_util::GetStringFUTF16( 1422 IDS_PROFILES_NOT_YOU, avatar_item.name), 1423 l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_WHATS_NEW_BUTTON), 1424 &tutorial_not_you_link_, 1425 &tutorial_see_whats_new_button_); 1426} 1427 1428views::View* ProfileChooserView::CreateSigninConfirmationView(){ 1429 return CreateTutorialView( 1430 profiles::TUTORIAL_MODE_CONFIRM_SIGNIN, 1431 l10n_util::GetStringUTF16(IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_TITLE), 1432 l10n_util::GetStringUTF16( 1433 IDS_PROFILES_CONFIRM_SIGNIN_TUTORIAL_CONTENT_TEXT), 1434 l10n_util::GetStringUTF16(IDS_PROFILES_SYNC_SETTINGS_LINK), 1435 l10n_util::GetStringUTF16(IDS_PROFILES_TUTORIAL_OK_BUTTON), 1436 &tutorial_sync_settings_link_, 1437 &tutorial_sync_settings_ok_button_); 1438} 1439 1440views::View* ProfileChooserView::CreateSwitchUserView( 1441 const AvatarMenu::Item& avatar_item) { 1442 views::View* view = new views::View(); 1443 views::GridLayout* layout = CreateSingleColumnLayout( 1444 view, kFixedSwitchUserViewWidth); 1445 views::ColumnSet* columns = layout->AddColumnSet(1); 1446 columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 1447 int label_width = 1448 kFixedSwitchUserViewWidth - 2 * views::kButtonHEdgeMarginNew; 1449 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 1450 views::GridLayout::FIXED, label_width, label_width); 1451 columns->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); 1452 1453 // Adds main text. 1454 layout->StartRowWithPadding(1, 1, 0, views::kUnrelatedControlVerticalSpacing); 1455 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 1456 const gfx::FontList& small_font_list = 1457 rb->GetFontList(ui::ResourceBundle::SmallFont); 1458 views::Label* content_label = new views::Label( 1459 l10n_util::GetStringFUTF16( 1460 IDS_PROFILES_NOT_YOU_CONTENT_TEXT, avatar_item.name)); 1461 content_label->SetMultiLine(true); 1462 content_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1463 content_label->SetFontList(small_font_list); 1464 layout->AddView(content_label); 1465 1466 // Adds "Add person" button. 1467 layout->StartRowWithPadding(1, 0, 0, views::kUnrelatedControlVerticalSpacing); 1468 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 1469 1470 add_person_button_ = new BackgroundColorHoverButton( 1471 this, 1472 l10n_util::GetStringUTF16(IDS_PROFILES_ADD_PERSON_BUTTON), 1473 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR)); 1474 layout->StartRow(1, 0); 1475 layout->AddView(add_person_button_); 1476 1477 // Adds "Disconnect your Google Account" button. 1478 layout->StartRow(1, 0); 1479 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 1480 1481 disconnect_button_ = new BackgroundColorHoverButton( 1482 this, 1483 l10n_util::GetStringUTF16(IDS_PROFILES_DISCONNECT_BUTTON), 1484 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_MENU_AVATAR)); 1485 layout->StartRow(1, 0); 1486 layout->AddView(disconnect_button_); 1487 1488 TitleCard* title_card = new TitleCard( 1489 l10n_util::GetStringFUTF16(IDS_PROFILES_NOT_YOU, avatar_item.name), 1490 this, &switch_user_cancel_button_); 1491 return TitleCard::AddPaddedTitleCard(view, title_card, 1492 kFixedSwitchUserViewWidth); 1493} 1494 1495bool ProfileChooserView::ShouldShowGoIncognito() const { 1496 bool incognito_available = 1497 IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) != 1498 IncognitoModePrefs::DISABLED; 1499 return incognito_available && !browser_->profile()->IsGuestSession(); 1500} 1501 1502void ProfileChooserView::PostActionPerformed( 1503 ProfileMetrics::ProfileDesktopMenu action_performed) { 1504 ProfileMetrics::LogProfileDesktopMenu(action_performed, gaia_service_type_); 1505 gaia_service_type_ = signin::GAIA_SERVICE_TYPE_NONE; 1506} 1507