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.
CreateSingleColumnLayout(views::View * view)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.
CreateDoubleColumnLayout(views::View * view)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
CreateLink(const base::string16 & link_text,views::LinkListener * listener)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
BackgroundColorHoverButton(views::ButtonListener * listener,const base::string16 & text,const gfx::ImageSkia & normal_icon,const gfx::ImageSkia & hover_icon)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
~BackgroundColorHoverButton()134 BackgroundColorHoverButton::~BackgroundColorHoverButton() {
135 }
136
OnMouseEntered(const ui::MouseEvent & event)137 void BackgroundColorHoverButton::OnMouseEntered(const ui::MouseEvent& event) {
138 views::TextButton::OnMouseEntered(event);
139 OnHighlightStateChanged();
140 }
141
OnMouseExited(const ui::MouseEvent & event)142 void BackgroundColorHoverButton::OnMouseExited(const ui::MouseEvent& event) {
143 views::TextButton::OnMouseExited(event);
144 OnHighlightStateChanged();
145 }
146
OnHighlightStateChanged()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:
EditableProfilePhoto(views::ButtonListener * listener,const gfx::Image & icon,bool is_editing_allowed)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
change_photo_button()205 views::TextButton* change_photo_button() {
206 return change_photo_button_;
207 }
208
209 private:
210 // views::View:
OnMouseEntered(const ui::MouseEvent & event)211 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
212 if (change_photo_button_)
213 change_photo_button_->SetVisible(true);
214 }
215
OnMouseExited(const ui::MouseEvent & event)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:
EditableProfileName(views::TextfieldController * controller,const base::string16 & text,bool is_editing_allowed)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
profile_name_textfield()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.
ShowReadOnlyView()265 void ShowReadOnlyView() {
266 if (profile_name_textfield_)
267 profile_name_textfield_->SetVisible(false);
268 }
269
270 private:
271 // views::ButtonListener:
ButtonPressed(views::Button * sender,const ui::Event & event)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:
OnKeyReleased(const ui::KeyEvent & event)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:
Layout()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
ShowBubble(views::View * anchor_view,views::BubbleBorder::Arrow arrow,views::BubbleBorder::BubbleAlignment border_alignment,const gfx::Rect & anchor_rect,Browser * browser)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
IsShowing()332 bool ProfileChooserView::IsShowing() {
333 return profile_bubble_ != NULL;
334 }
335
336 // static
Hide()337 void ProfileChooserView::Hide() {
338 if (IsShowing())
339 profile_bubble_->GetWidget()->Close();
340 }
341
ProfileChooserView(views::View * anchor_view,views::BubbleBorder::Arrow arrow,const gfx::Rect & anchor_rect,Browser * browser)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
~ProfileChooserView()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
ResetView()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
Init()388 void ProfileChooserView::Init() {
389 ShowView(PROFILE_CHOOSER_VIEW, avatar_menu_.get());
390 }
391
OnAvatarMenuChanged(AvatarMenu * avatar_menu)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
OnRefreshTokenAvailable(const std::string & account_id)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
OnRefreshTokenRevoked(const std::string & account_id)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
ShowView(BubbleViewMode view_to_display,AvatarMenu * avatar_menu)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
WindowClosing()508 void ProfileChooserView::WindowClosing() {
509 DCHECK_EQ(profile_bubble_, this);
510 profile_bubble_ = NULL;
511 }
512
ButtonPressed(views::Button * sender,const ui::Event & event)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
OnMenuButtonClicked(views::View * source,const gfx::Point & point)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
LinkClicked(views::Link * sender,int event_flags)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
HandleKeyEvent(views::Textfield * sender,const ui::KeyEvent & key_event)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
CreateCurrentProfileView(const AvatarMenu::Item & avatar_item,bool is_guest)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
CreateCurrentProfileEditableView(const AvatarMenu::Item & avatar_item)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
CreateGuestProfileView()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
CreateOtherProfilesView(const Indexes & avatars_to_show)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
CreateOptionsView(bool is_guest_view)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
CreateCurrentProfileAccountsView(const AvatarMenu::Item & avatar_item)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
CreateAccountButton(views::GridLayout * layout,const std::string & account,bool is_primary_account)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