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