1 // Copyright 2013 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/profile_chooser_view.h" 6 7 #include "base/command_line.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "chrome/browser/browser_process.h" 10 #include "chrome/browser/profiles/profile_info_util.h" 11 #include "chrome/browser/profiles/profile_manager.h" 12 #include "chrome/browser/profiles/profile_window.h" 13 #include "chrome/browser/profiles/profiles_state.h" 14 #include "chrome/browser/signin/profile_oauth2_token_service.h" 15 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 16 #include "chrome/browser/signin/signin_manager.h" 17 #include "chrome/browser/signin/signin_manager_factory.h" 18 #include "chrome/browser/signin/signin_promo.h" 19 #include "chrome/browser/ui/browser.h" 20 #include "chrome/browser/ui/browser_dialogs.h" 21 #include "chrome/browser/ui/singleton_tabs.h" 22 #include "chrome/browser/ui/views/user_manager_view.h" 23 #include "chrome/common/chrome_switches.h" 24 #include "chrome/common/url_constants.h" 25 #include "grit/chromium_strings.h" 26 #include "grit/generated_resources.h" 27 #include "grit/theme_resources.h" 28 #include "third_party/skia/include/core/SkColor.h" 29 #include "ui/base/l10n/l10n_util.h" 30 #include "ui/base/resource/resource_bundle.h" 31 #include "ui/gfx/image/image.h" 32 #include "ui/gfx/image/image_skia.h" 33 #include "ui/gfx/text_elider.h" 34 #include "ui/views/controls/button/blue_button.h" 35 #include "ui/views/controls/button/menu_button.h" 36 #include "ui/views/controls/label.h" 37 #include "ui/views/controls/link.h" 38 #include "ui/views/controls/separator.h" 39 #include "ui/views/controls/textfield/textfield.h" 40 #include "ui/views/controls/webview/webview.h" 41 #include "ui/views/layout/grid_layout.h" 42 #include "ui/views/layout/layout_constants.h" 43 #include "ui/views/widget/widget.h" 44 45 #if defined(USE_AURA) 46 #include "ui/native_theme/native_theme_aura.h" 47 #endif 48 49 namespace { 50 51 // Helpers -------------------------------------------------------------------- 52 53 const int kMinMenuWidth = 250; 54 const int kButtonHeight = 29; 55 56 // Creates a GridLayout with a single column. This ensures that all the child 57 // views added get auto-expanded to fill the full width of the bubble. 58 views::GridLayout* CreateSingleColumnLayout(views::View* view) { 59 views::GridLayout* layout = new views::GridLayout(view); 60 view->SetLayoutManager(layout); 61 62 views::ColumnSet* columns = layout->AddColumnSet(0); 63 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, 64 views::GridLayout::USE_PREF, 0, 0); 65 return layout; 66 } 67 68 // Creates a GridLayout with two columns. 69 views::GridLayout* CreateDoubleColumnLayout(views::View* view) { 70 views::GridLayout* layout = new views::GridLayout(view); 71 view->SetLayoutManager(layout); 72 73 views::ColumnSet* columns = layout->AddColumnSet(0); 74 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 75 views::GridLayout::USE_PREF, 0, 0); 76 columns->AddPaddingColumn(0, views::kUnrelatedControlLargeHorizontalSpacing); 77 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0, 78 views::GridLayout::USE_PREF, 0, 0); 79 return layout; 80 } 81 82 views::Link* CreateLink(const base::string16& link_text, 83 views::LinkListener* listener) { 84 views::Link* link_button = new views::Link(link_text); 85 link_button->SetHorizontalAlignment(gfx::ALIGN_LEFT); 86 link_button->SetUnderline(false); 87 link_button->set_listener(listener); 88 return link_button; 89 } 90 91 92 // BackgroundColorHoverButton ------------------------------------------------- 93 94 // A custom button that allows for setting a background color when hovered over. 95 class BackgroundColorHoverButton : public views::TextButton { 96 public: 97 BackgroundColorHoverButton(views::ButtonListener* listener, 98 const base::string16& text, 99 const gfx::ImageSkia& normal_icon, 100 const gfx::ImageSkia& hover_icon); 101 virtual ~BackgroundColorHoverButton(); 102 103 private: 104 // views::TextButton: 105 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; 106 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; 107 108 void OnHighlightStateChanged(); 109 110 DISALLOW_COPY_AND_ASSIGN(BackgroundColorHoverButton); 111 }; 112 113 BackgroundColorHoverButton::BackgroundColorHoverButton( 114 views::ButtonListener* listener, 115 const base::string16& text, 116 const gfx::ImageSkia& normal_icon, 117 const gfx::ImageSkia& hover_icon) 118 : views::TextButton(listener, text) { 119 scoped_ptr<views::TextButtonBorder> text_button_border( 120 new views::TextButtonBorder()); 121 text_button_border->SetInsets(gfx::Insets(0, views::kButtonHEdgeMarginNew, 122 0, views::kButtonHEdgeMarginNew)); 123 set_border(text_button_border.release()); 124 set_min_height(kButtonHeight); 125 set_icon_text_spacing(views::kItemLabelSpacing); 126 SetIcon(normal_icon); 127 SetHoverIcon(hover_icon); 128 SetPushedIcon(hover_icon); 129 SetHoverColor(GetNativeTheme()->GetSystemColor( 130 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor)); 131 OnHighlightStateChanged(); 132 } 133 134 BackgroundColorHoverButton::~BackgroundColorHoverButton() { 135 } 136 137 void BackgroundColorHoverButton::OnMouseEntered(const ui::MouseEvent& event) { 138 views::TextButton::OnMouseEntered(event); 139 OnHighlightStateChanged(); 140 } 141 142 void BackgroundColorHoverButton::OnMouseExited(const ui::MouseEvent& event) { 143 views::TextButton::OnMouseExited(event); 144 OnHighlightStateChanged(); 145 } 146 147 void BackgroundColorHoverButton::OnHighlightStateChanged() { 148 bool is_highlighted = (state() == views::TextButton::STATE_PRESSED) || 149 (state() == views::TextButton::STATE_HOVERED) || HasFocus(); 150 set_background(views::Background::CreateSolidBackground( 151 GetNativeTheme()->GetSystemColor(is_highlighted ? 152 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor : 153 ui::NativeTheme::kColorId_MenuBackgroundColor))); 154 SchedulePaint(); 155 } 156 157 } // namespace 158 159 160 // EditableProfilePhoto ------------------------------------------------- 161 162 // A custom Image control that shows a "change" button when moused over. 163 class EditableProfilePhoto : public views::ImageView { 164 public: 165 EditableProfilePhoto(views::ButtonListener* listener, 166 const gfx::Image& icon, 167 bool is_editing_allowed) 168 : views::ImageView(), 169 change_photo_button_(NULL) { 170 const int kLargeImageSide = 64; 171 const SkColor kBackgroundColor = SkColorSetARGB(125, 0, 0, 0); 172 const int kOverlayHeight = 20; 173 174 gfx::Image image = profiles::GetSizedAvatarIconWithBorder( 175 icon, true, 176 kLargeImageSide + profiles::kAvatarIconPadding, 177 kLargeImageSide + profiles::kAvatarIconPadding); 178 SetImage(image.ToImageSkia()); 179 180 if (!is_editing_allowed) 181 return; 182 183 set_notify_enter_exit_on_child(true); 184 185 // Button overlay that appears when hovering over the image. 186 change_photo_button_ = new views::TextButton(listener, 187 l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_CHANGE_PHOTO_BUTTON)); 188 change_photo_button_->set_alignment(views::TextButton::ALIGN_CENTER); 189 change_photo_button_->set_border(NULL); 190 change_photo_button_->SetEnabledColor(SK_ColorWHITE); 191 change_photo_button_->SetHoverColor(SK_ColorWHITE); 192 193 change_photo_button_->set_background( 194 views::Background::CreateSolidBackground(kBackgroundColor)); 195 // Need to take in account the border padding on the avatar. 196 change_photo_button_->SetBounds( 197 profiles::kAvatarIconPadding, 198 kLargeImageSide - kOverlayHeight, 199 kLargeImageSide - profiles::kAvatarIconPadding, 200 kOverlayHeight); 201 change_photo_button_->SetVisible(false); 202 AddChildView(change_photo_button_); 203 } 204 205 views::TextButton* change_photo_button() { 206 return change_photo_button_; 207 } 208 209 private: 210 // views::View: 211 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE { 212 if (change_photo_button_) 213 change_photo_button_->SetVisible(true); 214 } 215 216 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE { 217 if (change_photo_button_) 218 change_photo_button_->SetVisible(false); 219 } 220 221 // Button that is shown when hovering over the image view. Can be NULL if 222 // the photo isn't allowed to be edited (e.g. for guest profiles). 223 views::TextButton* change_photo_button_; 224 225 DISALLOW_COPY_AND_ASSIGN(EditableProfilePhoto); 226 }; 227 228 229 // EditableProfileName ------------------------------------------------- 230 231 // A custom text control that turns into a textfield for editing when clicked. 232 class EditableProfileName : public views::TextButton, 233 public views::ButtonListener { 234 public: 235 EditableProfileName(views::TextfieldController* controller, 236 const base::string16& text, 237 bool is_editing_allowed) 238 : views::TextButton(this, text), 239 profile_name_textfield_(NULL) { 240 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 241 gfx::Font medium_font = rb->GetFont(ui::ResourceBundle::MediumFont); 242 SetFont(medium_font); 243 set_border(NULL); 244 245 if (!is_editing_allowed) 246 return; 247 248 SetIcon(*rb->GetImageSkiaNamed(IDR_INFOBAR_AUTOFILL)); 249 set_icon_placement(views::TextButton::ICON_ON_RIGHT); 250 251 // Textfield that overlaps the button. 252 profile_name_textfield_ = new views::Textfield(); 253 profile_name_textfield_->SetController(controller); 254 profile_name_textfield_->SetFont(medium_font); 255 profile_name_textfield_->SetVisible(false); 256 AddChildView(profile_name_textfield_); 257 } 258 259 views::Textfield* profile_name_textfield() { 260 return profile_name_textfield_; 261 } 262 263 // Hide the editable textfield and show the button displaying the profile 264 // name instead. 265 void ShowReadOnlyView() { 266 if (profile_name_textfield_) 267 profile_name_textfield_->SetVisible(false); 268 } 269 270 private: 271 // views::ButtonListener: 272 virtual void ButtonPressed(views::Button* sender, 273 const ui::Event& event) OVERRIDE { 274 if (profile_name_textfield_) { 275 profile_name_textfield_->SetVisible(true); 276 profile_name_textfield_->SetText(text()); 277 profile_name_textfield_->SelectAll(false); 278 profile_name_textfield_->RequestFocus(); 279 } 280 } 281 282 // views::CustomButton: 283 virtual bool OnKeyReleased(const ui::KeyEvent& event) OVERRIDE { 284 // Override CustomButton's implementation, which presses the button when 285 // you press space and clicks it when you release space, as the space can be 286 // part of the new profile name typed in the textfield. 287 return false; 288 } 289 290 // views::View: 291 virtual void Layout() OVERRIDE { 292 if (profile_name_textfield_) 293 profile_name_textfield_->SetBounds(0, 0, width(), height()); 294 views::View::Layout(); 295 } 296 297 // Button that is shown when hovering over the image view. Can be NULL if 298 // the profile name isn't allowed to be edited (e.g. for guest profiles). 299 views::Textfield* profile_name_textfield_; 300 301 DISALLOW_COPY_AND_ASSIGN(EditableProfileName); 302 }; 303 304 305 // ProfileChooserView --------------------------------------------------------- 306 307 // static 308 ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL; 309 bool ProfileChooserView::close_on_deactivate_for_testing_ = true; 310 311 // static 312 void ProfileChooserView::ShowBubble( 313 views::View* anchor_view, 314 views::BubbleBorder::Arrow arrow, 315 views::BubbleBorder::BubbleAlignment border_alignment, 316 const gfx::Rect& anchor_rect, 317 Browser* browser) { 318 if (IsShowing()) 319 // TODO(bcwhite): handle case where we should show on different window 320 return; 321 322 profile_bubble_ = new ProfileChooserView( 323 anchor_view, arrow, anchor_rect, browser); 324 views::BubbleDelegateView::CreateBubble(profile_bubble_); 325 profile_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_); 326 profile_bubble_->SetAlignment(border_alignment); 327 profile_bubble_->GetWidget()->Show(); 328 profile_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE); 329 } 330 331 // static 332 bool ProfileChooserView::IsShowing() { 333 return profile_bubble_ != NULL; 334 } 335 336 // static 337 void ProfileChooserView::Hide() { 338 if (IsShowing()) 339 profile_bubble_->GetWidget()->Close(); 340 } 341 342 ProfileChooserView::ProfileChooserView(views::View* anchor_view, 343 views::BubbleBorder::Arrow arrow, 344 const gfx::Rect& anchor_rect, 345 Browser* browser) 346 : BubbleDelegateView(anchor_view, arrow), 347 browser_(browser), 348 view_mode_(PROFILE_CHOOSER_VIEW) { 349 // Reset the default margins inherited from the BubbleDelegateView. 350 set_margins(gfx::Insets()); 351 352 ResetView(); 353 354 avatar_menu_.reset(new AvatarMenu( 355 &g_browser_process->profile_manager()->GetProfileInfoCache(), 356 this, 357 browser_)); 358 avatar_menu_->RebuildMenu(); 359 360 ProfileOAuth2TokenService* oauth2_token_service = 361 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile()); 362 if (oauth2_token_service) 363 oauth2_token_service->AddObserver(this); 364 } 365 366 ProfileChooserView::~ProfileChooserView() { 367 ProfileOAuth2TokenService* oauth2_token_service = 368 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile()); 369 if (oauth2_token_service) 370 oauth2_token_service->RemoveObserver(this); 371 } 372 373 void ProfileChooserView::ResetView() { 374 manage_accounts_link_ = NULL; 375 signout_current_profile_link_ = NULL; 376 signin_current_profile_link_ = NULL; 377 guest_button_ = NULL; 378 end_guest_button_ = NULL; 379 users_button_ = NULL; 380 add_user_button_ = NULL; 381 add_account_button_ = NULL; 382 current_profile_photo_ = NULL; 383 current_profile_name_ = NULL; 384 open_other_profile_indexes_map_.clear(); 385 current_profile_accounts_map_.clear(); 386 } 387 388 void ProfileChooserView::Init() { 389 ShowView(PROFILE_CHOOSER_VIEW, avatar_menu_.get()); 390 } 391 392 void ProfileChooserView::OnAvatarMenuChanged( 393 AvatarMenu* avatar_menu) { 394 // Refresh the view with the new menu. We can't just update the local copy 395 // as this may have been triggered by a sign out action, in which case 396 // the view is being destroyed. 397 ShowView(PROFILE_CHOOSER_VIEW, avatar_menu); 398 } 399 400 void ProfileChooserView::OnRefreshTokenAvailable( 401 const std::string& account_id) { 402 // Refresh the account management view when a new account is added to the 403 // profile. 404 if (view_mode_ == ACCOUNT_MANAGEMENT_VIEW || 405 view_mode_ == GAIA_SIGNIN_VIEW || 406 view_mode_ == GAIA_ADD_ACCOUNT_VIEW) { 407 ShowView(ACCOUNT_MANAGEMENT_VIEW, avatar_menu_.get()); 408 } 409 } 410 411 void ProfileChooserView::OnRefreshTokenRevoked(const std::string& account_id) { 412 // Refresh the account management view when an account is removed from the 413 // profile. 414 if (view_mode_ == ACCOUNT_MANAGEMENT_VIEW) 415 ShowView(ACCOUNT_MANAGEMENT_VIEW, avatar_menu_.get()); 416 } 417 418 void ProfileChooserView::ShowView(BubbleViewMode view_to_display, 419 AvatarMenu* avatar_menu) { 420 // The account management view should only be displayed if the active profile 421 // is signed in. 422 if (view_to_display == ACCOUNT_MANAGEMENT_VIEW) { 423 const AvatarMenu::Item& active_item = avatar_menu->GetItemAt( 424 avatar_menu->GetActiveProfileIndex()); 425 DCHECK(active_item.signed_in); 426 } 427 428 ResetView(); 429 RemoveAllChildViews(true); 430 view_mode_ = view_to_display; 431 432 views::GridLayout* layout = CreateSingleColumnLayout(this); 433 layout->set_minimum_size(gfx::Size(kMinMenuWidth, 0)); 434 435 if (view_to_display == GAIA_SIGNIN_VIEW || 436 view_to_display == GAIA_ADD_ACCOUNT_VIEW) { 437 // Minimum size for embedded sign in pages as defined in Gaia. 438 const int kMinGaiaViewWidth = 320; 439 const int kMinGaiaViewHeight = 440; 440 Profile* profile = browser_->profile(); 441 views::WebView* web_view = new views::WebView(profile); 442 signin::Source source = (view_to_display == GAIA_SIGNIN_VIEW) ? 443 signin::SOURCE_AVATAR_BUBBLE_SIGN_IN : 444 signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT; 445 GURL url(signin::GetPromoURL( 446 source, false /* auto_close */, true /* is_constrained */)); 447 web_view->LoadInitialURL(url); 448 layout->StartRow(1, 0); 449 layout->AddView(web_view); 450 layout->set_minimum_size( 451 gfx::Size(kMinGaiaViewWidth, kMinGaiaViewHeight)); 452 Layout(); 453 if (GetBubbleFrameView()) 454 SizeToContents(); 455 return; 456 } 457 458 // Separate items into active and alternatives. 459 Indexes other_profiles; 460 bool is_guest_view = true; 461 views::View* current_profile_view = NULL; 462 views::View* current_profile_accounts = NULL; 463 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) { 464 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i); 465 if (item.active) { 466 if (view_to_display == PROFILE_CHOOSER_VIEW) { 467 current_profile_view = CreateCurrentProfileView(item, false); 468 } else { 469 current_profile_view = CreateCurrentProfileEditableView(item); 470 current_profile_accounts = CreateCurrentProfileAccountsView(item); 471 } 472 is_guest_view = false; 473 } else { 474 other_profiles.push_back(i); 475 } 476 } 477 478 if (!current_profile_view) // Guest windows don't have an active profile. 479 current_profile_view = CreateGuestProfileView(); 480 481 layout->StartRow(1, 0); 482 layout->AddView(current_profile_view); 483 484 if (view_to_display == PROFILE_CHOOSER_VIEW) { 485 layout->StartRow(1, 0); 486 layout->AddView(CreateOtherProfilesView(other_profiles)); 487 } else { 488 DCHECK(current_profile_accounts); 489 layout->StartRow(0, 0); 490 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 491 layout->StartRow(1, 0); 492 layout->AddView(current_profile_accounts); 493 } 494 495 layout->StartRow(0, 0); 496 layout->AddView(new views::Separator(views::Separator::HORIZONTAL)); 497 498 // Action buttons. 499 views::View* option_buttons_view = CreateOptionsView(is_guest_view); 500 layout->StartRow(0, 0); 501 layout->AddView(option_buttons_view); 502 503 Layout(); 504 if (GetBubbleFrameView()) 505 SizeToContents(); 506 } 507 508 void ProfileChooserView::WindowClosing() { 509 DCHECK_EQ(profile_bubble_, this); 510 profile_bubble_ = NULL; 511 } 512 513 void ProfileChooserView::ButtonPressed(views::Button* sender, 514 const ui::Event& event) { 515 // Disable button after clicking so that it doesn't get clicked twice and 516 // start a second action... which can crash Chrome. But don't disable if it 517 // has no parent (like in tests) because that will also crash. 518 if (sender->parent()) 519 sender->SetEnabled(false); 520 521 if (sender == guest_button_) { 522 profiles::SwitchToGuestProfile(browser_->host_desktop_type(), 523 profiles::ProfileSwitchingDoneCallback()); 524 } else if (sender == end_guest_button_) { 525 profiles::CloseGuestProfileWindows(); 526 } else if (sender == users_button_) { 527 // Only non-guest users appear in the User Manager. 528 base::FilePath profile_path; 529 if (!end_guest_button_) { 530 size_t active_index = avatar_menu_->GetActiveProfileIndex(); 531 profile_path = avatar_menu_->GetItemAt(active_index).profile_path; 532 } 533 chrome::ShowUserManager(profile_path); 534 } else if (sender == add_user_button_) { 535 profiles::CreateAndSwitchToNewProfile( 536 browser_->host_desktop_type(), 537 profiles::ProfileSwitchingDoneCallback()); 538 } else if (sender == add_account_button_) { 539 ShowView(GAIA_ADD_ACCOUNT_VIEW, avatar_menu_.get()); 540 } else if (sender == current_profile_photo_->change_photo_button()) { 541 avatar_menu_->EditProfile(avatar_menu_->GetActiveProfileIndex()); 542 } else { 543 // One of the "other profiles" buttons was pressed. 544 ButtonIndexes::const_iterator match = 545 open_other_profile_indexes_map_.find(sender); 546 DCHECK(match != open_other_profile_indexes_map_.end()); 547 avatar_menu_->SwitchToProfile( 548 match->second, 549 ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW); 550 } 551 } 552 553 void ProfileChooserView::OnMenuButtonClicked(views::View* source, 554 const gfx::Point& point) { 555 AccountButtonIndexes::const_iterator match = 556 current_profile_accounts_map_.find(source); 557 DCHECK(match != current_profile_accounts_map_.end()); 558 559 ProfileOAuth2TokenService* oauth2_token_service = 560 ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile()); 561 if (oauth2_token_service) 562 oauth2_token_service->RevokeCredentials(match->second); 563 } 564 565 void ProfileChooserView::LinkClicked(views::Link* sender, int event_flags) { 566 if (sender == manage_accounts_link_) { 567 // ShowView() will DCHECK if this view is displayed for non signed-in users. 568 ShowView(ACCOUNT_MANAGEMENT_VIEW, avatar_menu_.get()); 569 } else if (sender == signout_current_profile_link_) { 570 profiles::LockProfile(browser_->profile()); 571 } else { 572 DCHECK(sender == signin_current_profile_link_); 573 if (CommandLine::ForCurrentProcess()->HasSwitch( 574 switches::kEnableInlineSignin)) { 575 ShowView(GAIA_SIGNIN_VIEW, avatar_menu_.get()); 576 } else { 577 GURL page = signin::GetPromoURL(signin::SOURCE_MENU, false); 578 chrome::ShowSingletonTab(browser_, page); 579 } 580 } 581 } 582 583 bool ProfileChooserView::HandleKeyEvent(views::Textfield* sender, 584 const ui::KeyEvent& key_event) { 585 views::Textfield* name_textfield = 586 current_profile_name_->profile_name_textfield(); 587 DCHECK(sender == name_textfield); 588 589 if (key_event.key_code() == ui::VKEY_RETURN || 590 key_event.key_code() == ui::VKEY_TAB) { 591 // Pressing Tab/Enter commits the new profile name, unless it's empty. 592 base::string16 new_profile_name = name_textfield->text(); 593 if (new_profile_name.empty()) 594 return true; 595 596 const AvatarMenu::Item& active_item = avatar_menu_->GetItemAt( 597 avatar_menu_->GetActiveProfileIndex()); 598 Profile* profile = g_browser_process->profile_manager()->GetProfile( 599 active_item.profile_path); 600 DCHECK(profile); 601 602 if (profile->IsManaged()) 603 return true; 604 605 profiles::UpdateProfileName(profile, new_profile_name); 606 current_profile_name_->ShowReadOnlyView(); 607 return true; 608 } 609 return false; 610 } 611 612 views::View* ProfileChooserView::CreateCurrentProfileView( 613 const AvatarMenu::Item& avatar_item, 614 bool is_guest) { 615 views::View* view = new views::View(); 616 views::GridLayout* layout = CreateDoubleColumnLayout(view); 617 layout->SetInsets(views::kButtonVEdgeMarginNew, 618 views::kButtonHEdgeMarginNew, 619 views::kButtonVEdgeMarginNew, 620 views::kButtonHEdgeMarginNew); 621 622 current_profile_photo_ = 623 new EditableProfilePhoto(this, avatar_item.icon, !is_guest); 624 view->SetBoundsRect(current_profile_photo_->bounds()); 625 current_profile_name_ = 626 new EditableProfileName(this, avatar_item.name, !is_guest); 627 628 layout->StartRow(1, 0); 629 layout->AddView(current_profile_photo_, 1, 3); 630 layout->AddView(current_profile_name_); 631 632 if (is_guest) { 633 layout->StartRow(1, 0); 634 layout->SkipColumns(1); 635 layout->StartRow(1, 0); 636 layout->SkipColumns(1); 637 } else if (avatar_item.signed_in) { 638 manage_accounts_link_ = CreateLink( 639 l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_MANAGE_ACCOUNTS_BUTTON), 640 this); 641 signout_current_profile_link_ = CreateLink( 642 l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON), this); 643 layout->StartRow(1, 0); 644 layout->SkipColumns(1); 645 layout->AddView(signout_current_profile_link_); 646 layout->StartRow(1, 0); 647 layout->SkipColumns(1); 648 layout->AddView(manage_accounts_link_); 649 } else { 650 signin_current_profile_link_ = CreateLink( 651 l10n_util::GetStringFUTF16( 652 IDS_SYNC_START_SYNC_BUTTON_LABEL, 653 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)), 654 this); 655 layout->StartRow(1, 0); 656 layout->SkipColumns(1); 657 layout->AddView(signin_current_profile_link_); 658 layout->StartRow(1, 0); 659 layout->SkipColumns(1); 660 } 661 662 return view; 663 } 664 665 views::View* ProfileChooserView::CreateCurrentProfileEditableView( 666 const AvatarMenu::Item& avatar_item) { 667 DCHECK(avatar_item.signed_in); 668 views::View* view = new views::View(); 669 views::GridLayout* layout = CreateDoubleColumnLayout(view); 670 layout->SetInsets(views::kButtonVEdgeMarginNew, 671 views::kButtonHEdgeMarginNew, 672 views::kButtonVEdgeMarginNew, 673 views::kButtonHEdgeMarginNew); 674 675 current_profile_photo_ = 676 new EditableProfilePhoto(this, avatar_item.icon, true); 677 view->SetBoundsRect(current_profile_photo_->bounds()); 678 current_profile_name_ = 679 new EditableProfileName(this, avatar_item.name, true); 680 681 layout->StartRow(1, 0); 682 layout->AddView(current_profile_photo_, 1, 3); 683 layout->AddView(current_profile_name_); 684 685 layout->StartRow(1, 0); 686 layout->SkipColumns(1); 687 688 layout->StartRow(1, 0); 689 layout->SkipColumns(1); 690 return view; 691 } 692 693 views::View* ProfileChooserView::CreateGuestProfileView() { 694 gfx::Image guest_icon = 695 ui::ResourceBundle::GetSharedInstance().GetImageNamed(IDR_LOGIN_GUEST); 696 AvatarMenu::Item guest_avatar_item(0, 0, guest_icon); 697 guest_avatar_item.active = true; 698 guest_avatar_item.name = l10n_util::GetStringUTF16( 699 IDS_PROFILES_GUEST_PROFILE_NAME); 700 guest_avatar_item.signed_in = false; 701 702 return CreateCurrentProfileView(guest_avatar_item, true); 703 } 704 705 views::View* ProfileChooserView::CreateOtherProfilesView( 706 const Indexes& avatars_to_show) { 707 views::View* view = new views::View(); 708 views::GridLayout* layout = CreateSingleColumnLayout(view); 709 layout->SetInsets(0, views::kButtonHEdgeMarginNew, 710 views::kButtonVEdgeMarginNew, views::kButtonHEdgeMarginNew); 711 int num_avatars_to_show = avatars_to_show.size(); 712 for (int i = 0; i < num_avatars_to_show; ++i) { 713 const size_t index = avatars_to_show[i]; 714 const AvatarMenu::Item& item = avatar_menu_->GetItemAt(index); 715 const int kSmallImageSide = 32; 716 717 gfx::Image image = profiles::GetSizedAvatarIconWithBorder( 718 item.icon, true, 719 kSmallImageSide + profiles::kAvatarIconPadding, 720 kSmallImageSide + profiles::kAvatarIconPadding); 721 722 views::TextButton* button = new views::TextButton(this, item.name); 723 open_other_profile_indexes_map_[button] = index; 724 button->SetIcon(*image.ToImageSkia()); 725 button->set_icon_text_spacing(views::kItemLabelSpacing); 726 button->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( 727 ui::ResourceBundle::MediumFont)); 728 button->set_border(NULL); 729 730 layout->StartRow(1, 0); 731 layout->AddView(button); 732 733 // The last avatar in the list does not need any bottom padding. 734 if (i < num_avatars_to_show - 1) 735 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 736 } 737 738 return view; 739 } 740 741 views::View* ProfileChooserView::CreateOptionsView(bool is_guest_view) { 742 views::View* view = new views::View(); 743 views::GridLayout* layout = CreateSingleColumnLayout(view); 744 // The horizontal padding will be set by each button individually, so that 745 // in the hovered state the button spans the entire parent view. 746 layout->SetInsets(views::kRelatedControlVerticalSpacing, 0, 747 views::kRelatedControlVerticalSpacing, 0); 748 749 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 750 751 layout->StartRow(1, 0); 752 if (is_guest_view) { 753 end_guest_button_ = new BackgroundColorHoverButton( 754 this, 755 l10n_util::GetStringUTF16(IDS_PROFILES_EXIT_GUEST_BUTTON), 756 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_BROWSE_GUEST), 757 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_BROWSE_GUEST_WHITE)); 758 layout->AddView(end_guest_button_); 759 } else { 760 guest_button_ = new BackgroundColorHoverButton( 761 this, 762 l10n_util::GetStringUTF16(IDS_PROFILES_GUEST_BUTTON), 763 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_BROWSE_GUEST), 764 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_BROWSE_GUEST_WHITE)); 765 layout->AddView(guest_button_); 766 } 767 768 add_user_button_ = new BackgroundColorHoverButton( 769 this, 770 l10n_util::GetStringUTF16(IDS_PROFILES_ADD_PERSON_BUTTON), 771 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_ADD_USER), 772 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_ADD_USER_WHITE)); 773 layout->StartRow(1, 0); 774 layout->AddView(add_user_button_); 775 776 users_button_ = new BackgroundColorHoverButton( 777 this, 778 l10n_util::GetStringUTF16(IDS_PROFILES_ALL_PEOPLE_BUTTON), 779 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_ADD_USER), 780 *rb->GetImageSkiaNamed(IDR_ICON_PROFILES_ADD_USER_WHITE)); 781 layout->StartRow(1, 0); 782 layout->AddView(users_button_); 783 784 return view; 785 } 786 787 views::View* ProfileChooserView::CreateCurrentProfileAccountsView( 788 const AvatarMenu::Item& avatar_item) { 789 DCHECK(avatar_item.signed_in); 790 views::View* view = new views::View(); 791 views::GridLayout* layout = CreateSingleColumnLayout(view); 792 layout->SetInsets(views::kButtonVEdgeMarginNew, 793 views::kButtonHEdgeMarginNew, 794 views::kButtonVEdgeMarginNew, 795 views::kButtonHEdgeMarginNew); 796 797 Profile* profile = browser_->profile(); 798 std::string primary_account = 799 SigninManagerFactory::GetForProfile(profile)->GetAuthenticatedUsername(); 800 DCHECK(!primary_account.empty()); 801 std::vector<std::string> accounts( 802 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->GetAccounts()); 803 DCHECK_EQ(1, std::count_if(accounts.begin(), accounts.end(), 804 std::bind1st(std::equal_to<std::string>(), 805 primary_account))); 806 807 // The primary account should always be listed first. However, the vector 808 // returned by ProfileOAuth2TokenService::GetAccounts() will contain the 809 // primary account too. Ignore it when it appears later. 810 // TODO(rogerta): we still need to further differentiate the primary account 811 // from the others, so more work is likely required here: crbug.com/311124. 812 CreateAccountButton(layout, primary_account, true); 813 for (size_t i = 0; i < accounts.size(); ++i) { 814 if (primary_account != accounts[i]) 815 CreateAccountButton(layout, accounts[i], false); 816 } 817 818 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 819 820 add_account_button_ = new views::BlueButton( 821 this, 822 l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_ADD_ACCOUNT_BUTTON, 823 avatar_item.name)); 824 layout->StartRow(1, 0); 825 layout->AddView(add_account_button_); 826 return view; 827 } 828 829 void ProfileChooserView::CreateAccountButton(views::GridLayout* layout, 830 const std::string& account, 831 bool is_primary_account) { 832 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 833 // Use a MenuButtonListener and not a regular ButtonListener to be 834 // able to distinguish between the unnamed "other profile" buttons and the 835 // unnamed "multiple accounts" buttons. 836 views::MenuButton* email_button = new views::MenuButton( 837 NULL, 838 gfx::ElideEmail(UTF8ToUTF16(account), 839 rb->GetFontList(ui::ResourceBundle::BaseFont), 840 width()), 841 is_primary_account ? NULL : this, // Cannot delete the primary account. 842 !is_primary_account); 843 email_button->SetFont(rb->GetFont(ui::ResourceBundle::BaseFont)); 844 email_button->set_border(views::Border::CreateEmptyBorder(0, 0, 0, 0)); 845 if (!is_primary_account) { 846 email_button->set_menu_marker( 847 rb->GetImageNamed(IDR_CLOSE_1).ToImageSkia()); 848 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 849 } 850 layout->StartRow(1, 0); 851 layout->AddView(email_button); 852 853 // Save the original email address, as the button text could be elided. 854 current_profile_accounts_map_[email_button] = account; 855 } 856