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 <vector>
8
9#include "chrome/browser/browser_process.h"
10#include "chrome/browser/profiles/avatar_menu_model.h"
11#include "chrome/browser/profiles/profile_info_util.h"
12#include "chrome/browser/profiles/profile_manager.h"
13#include "chrome/browser/ui/singleton_tabs.h"
14#include "chrome/common/url_constants.h"
15#include "grit/generated_resources.h"
16#include "ui/base/l10n/l10n_util.h"
17#include "ui/base/resource/resource_bundle.h"
18#include "ui/gfx/image/image.h"
19#include "ui/gfx/image/image_skia.h"
20#include "ui/views/controls/button/label_button.h"
21#include "ui/views/controls/image_view.h"
22#include "ui/views/controls/label.h"
23#include "ui/views/controls/link.h"
24#include "ui/views/controls/separator.h"
25#include "ui/views/layout/box_layout.h"
26#include "ui/views/layout/grid_layout.h"
27#include "ui/views/layout/layout_constants.h"
28#include "ui/views/widget/widget.h"
29
30
31// static
32ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL;
33bool ProfileChooserView::close_on_deactivate_ = true;
34
35// static
36void ProfileChooserView::ShowBubble(
37    views::View* anchor_view,
38    views::BubbleBorder::Arrow arrow,
39    views::BubbleBorder::BubbleAlignment border_alignment,
40    const gfx::Rect& anchor_rect,
41    Browser* browser) {
42  if (IsShowing())
43    // TODO(bcwhite): handle case where we should show on different window
44    return;
45
46  profile_bubble_ = new ProfileChooserView(
47      anchor_view, arrow, anchor_rect, browser);
48  views::BubbleDelegateView::CreateBubble(profile_bubble_);
49  profile_bubble_->set_close_on_deactivate(close_on_deactivate_);
50  profile_bubble_->SetAlignment(border_alignment);
51  profile_bubble_->GetWidget()->Show();
52}
53
54// static
55bool ProfileChooserView::IsShowing() {
56  return profile_bubble_ != NULL;
57}
58
59// static
60void ProfileChooserView::Hide() {
61  if (IsShowing())
62    profile_bubble_->GetWidget()->Close();
63}
64
65ProfileChooserView::ProfileChooserView(
66    views::View* anchor_view,
67    views::BubbleBorder::Arrow arrow,
68    const gfx::Rect& anchor_rect,
69    Browser* browser)
70    : BubbleDelegateView(anchor_view, arrow),
71      browser_(browser),
72      current_profile_view_(NULL),
73      guest_button_view_(NULL),
74      users_button_view_(NULL),
75      other_profiles_view_(NULL),
76      signout_current_profile_view_(NULL) {
77  avatar_menu_model_.reset(new AvatarMenuModel(
78      &g_browser_process->profile_manager()->GetProfileInfoCache(),
79      this, browser_));
80}
81
82ProfileChooserView::~ProfileChooserView() {
83}
84
85void ProfileChooserView::Init() {
86  // Build the menu for the first time.
87  OnAvatarMenuModelChanged(avatar_menu_model_.get());
88}
89
90void ProfileChooserView::WindowClosing() {
91  DCHECK_EQ(profile_bubble_, this);
92  profile_bubble_ = NULL;
93}
94
95bool ProfileChooserView::OnMousePressed(const ui::MouseEvent& event) {
96  return true;  // Won't get "released" event if we don't return "true" here.
97}
98
99void ProfileChooserView::OnMouseReleased(const ui::MouseEvent& event) {
100  views::View* sender = GetEventHandlerForPoint(event.location());
101  ViewIndexes::const_iterator match;
102
103  match = open_other_profile_indexes_map_.find(sender);
104  if (match != open_other_profile_indexes_map_.end()) {
105    avatar_menu_model_->SwitchToProfile(
106        match->second,
107        ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW);
108  }
109}
110
111void ProfileChooserView::ButtonPressed(views::Button* sender,
112                                       const ui::Event& event) {
113  // Disable button after clicking so that it doesn't get clicked twice and
114  // start a second action... which can crash Chrome.  But don't disable if it
115  // has no parent (like in tests) because that will also crash.
116  if (sender->parent())
117    sender->SetEnabled(false);
118
119  if (sender == guest_button_view_) {
120    avatar_menu_model_->SwitchToGuestProfileWindow(browser_);
121  } else if (sender == users_button_view_) {
122    chrome::ShowSingletonTab(browser_, GURL(chrome::kChromeUIUserManagerURL));
123  } else {
124    DCHECK_EQ(sender, signout_current_profile_view_);
125    avatar_menu_model_->BeginSignOut();
126  }
127}
128
129void ProfileChooserView::OnAvatarMenuModelChanged(
130    AvatarMenuModel* avatar_menu_model) {
131  // Unset all our child view references and call RemoveAllChildViews() which
132  // will actually delete them.
133  current_profile_view_ = NULL;
134  guest_button_view_ = NULL;
135  users_button_view_ = NULL;
136  open_other_profile_indexes_map_.clear();
137  other_profiles_view_ = NULL;
138  signout_current_profile_view_ = NULL;
139  RemoveAllChildViews(true);
140
141  views::GridLayout* layout = new views::GridLayout(this);
142  SetLayoutManager(layout);
143
144  views::ColumnSet* columns = layout->AddColumnSet(0);
145  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
146                     views::GridLayout::USE_PREF, 0, 0);
147
148  // Seprate items into active and alternatives.
149  Indexes other_profiles;
150  for (size_t i = 0; i < avatar_menu_model->GetNumberOfItems(); ++i) {
151    const AvatarMenuModel::Item& item = avatar_menu_model->GetItemAt(i);
152    if (item.active) {
153      DCHECK(!current_profile_view_);
154      current_profile_view_ = CreateCurrentProfileView(i);
155    } else {
156      other_profiles.push_back(i);
157    }
158  }
159  DCHECK(current_profile_view_);
160
161  layout->StartRow(1, 0);
162  layout->AddView(current_profile_view_);
163  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
164
165  layout->StartRow(0, 0);
166  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
167
168  other_profiles_view_ = CreateOtherProfilesView(other_profiles);
169  layout->StartRow(1, 0);
170  layout->AddView(other_profiles_view_);
171
172  layout->StartRow(0, 0);
173  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
174
175  views::View* option_buttons_view = CreateOptionsView();
176  option_buttons_view->SetSize(current_profile_view_->GetPreferredSize());
177  layout->StartRow(0, 0);
178  layout->AddView(option_buttons_view);
179
180  layout->StartRow(0, 0);
181  layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
182
183  // If the bubble has already been shown then resize and reposition the bubble.
184  Layout();
185}
186
187views::View* ProfileChooserView::CreateProfileImageView(const gfx::Image& icon,
188                                                        int side) {
189  views::ImageView* view = new views::ImageView();
190
191  gfx::Image image = profiles::GetSizedAvatarIconWithBorder(
192      icon, true,
193      side + profiles::kAvatarIconBorder,
194      side + profiles::kAvatarIconBorder);
195  // TODO(bcwhite): image alterations
196  view->SetImage(image.ToImageSkia());
197
198  return view;
199}
200
201views::View* ProfileChooserView::CreateProfileCardView(size_t avatar_to_show) {
202  views::View* view = new views::View();
203
204  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
205  const AvatarMenuModel::Item& avatar_item =
206      avatar_menu_model_->GetItemAt(avatar_to_show);
207
208  const int kLargeImageSide = 64;
209  views::View* photo_image =
210      CreateProfileImageView(avatar_item.icon, kLargeImageSide);
211  view->SetBoundsRect(photo_image->bounds());
212
213  views::Label* name_label =
214      new views::Label(avatar_item.name,
215                       rb.GetFont(ui::ResourceBundle::MediumFont));
216  name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
217
218  views::Label* email_label = new views::Label(avatar_item.sync_state);
219  if (avatar_item.signed_in)
220    email_label->SetElideBehavior(views::Label::ELIDE_AS_EMAIL);
221  email_label->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
222  email_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
223
224  views::GridLayout* layout = new views::GridLayout(view);
225  view->SetLayoutManager(layout);
226
227  views::ColumnSet* columns = layout->AddColumnSet(0);
228  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
229                     views::GridLayout::USE_PREF, 0, 0);
230  columns->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
231  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::TRAILING, 1,
232                     views::GridLayout::USE_PREF, 0, 0);
233
234  layout->StartRow(1, 0);
235  layout->AddView(photo_image, 1, 3);
236  layout->AddView(name_label);
237  layout->StartRow(1, 0);
238  layout->SkipColumns(1);
239  layout->AddView(email_label, 1, 1,
240                  views::GridLayout::LEADING, views::GridLayout::LEADING);
241  layout->StartRow(1, 0);
242  layout->SkipColumns(1);
243
244  return view;
245}
246
247views::View* ProfileChooserView::CreateCurrentProfileView(
248    size_t avatar_to_show) {
249  views::View* view = new views::View();
250
251  views::View* card_view = CreateProfileCardView(avatar_to_show);
252
253  views::LabelButton* signout_button = new views::LabelButton(
254      this,
255      l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON));
256  signout_button->SetStyle(views::Button::STYLE_BUTTON);
257  DCHECK(!signout_current_profile_view_);
258  signout_current_profile_view_ = signout_button;
259
260  views::GridLayout* layout = new views::GridLayout(view);
261  view->SetLayoutManager(layout);
262
263  views::ColumnSet* columns = layout->AddColumnSet(0);
264  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
265                     views::GridLayout::USE_PREF, 0, 0);
266  columns->AddPaddingColumn(0, 30);
267  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1,
268                     views::GridLayout::USE_PREF, 0, 0);
269  columns->AddPaddingColumn(0, 10);
270
271  layout->StartRow(1, 0);
272  layout->AddView(card_view, 1, 1);
273  layout->AddView(signout_button);
274
275  return view;
276}
277
278views::View* ProfileChooserView::CreateOtherProfilesView(
279    const Indexes& avatars_to_show) {
280  views::View* view = new views::View();
281
282  views::BoxLayout* layout = new views::BoxLayout(
283      views::BoxLayout::kHorizontal, 0,
284      views::kRelatedControlSmallVerticalSpacing,
285      views::kRelatedButtonHSpacing);
286  view->SetLayoutManager(layout);
287
288  const int kSmallImageSide = 32;
289  for (Indexes::const_iterator iter = avatars_to_show.begin();
290       iter != avatars_to_show.end();
291       ++iter) {
292    const size_t index = *iter;
293    views::View* image = CreateProfileImageView(
294        avatar_menu_model_->GetItemAt(index).icon, kSmallImageSide);
295    open_other_profile_indexes_map_[image] = index;
296    view->AddChildView(image);
297  }
298
299  return view;
300}
301
302views::View* ProfileChooserView::CreateOptionsView() {
303  users_button_view_ = new views::LabelButton(
304      this,
305      l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_USERS_BUTTON));
306  users_button_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
307  users_button_view_->set_tag(IDS_PROFILES_PROFILE_USERS_BUTTON);
308
309 guest_button_view_ = new views::LabelButton(
310      this,
311      l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_GUEST_BUTTON));
312  guest_button_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
313  guest_button_view_->set_tag(IDS_PROFILES_PROFILE_GUEST_BUTTON);
314
315  views::View* view = new views::View();
316  views::GridLayout* layout = new views::GridLayout(view);
317  view->SetLayoutManager(layout);
318
319  views::ColumnSet* columns = layout->AddColumnSet(0);
320  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
321                     views::GridLayout::USE_PREF, 0, 0);
322  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 0,
323                     views::GridLayout::USE_PREF, 0, 0);
324  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
325                     views::GridLayout::USE_PREF, 0, 0);
326  columns->LinkColumnSizes(0, 2, -1);
327
328  const int kButtonHeight = 40;
329  layout->StartRow(0, 0);
330  layout->AddView(users_button_view_, 1, 1,
331                  views::GridLayout::FILL, views::GridLayout::FILL,
332                  0, kButtonHeight);
333  layout->AddView(new views::Separator(views::Separator::VERTICAL));
334  layout->AddView(guest_button_view_, 1, 1,
335                  views::GridLayout::FILL, views::GridLayout::FILL,
336                  0, kButtonHeight);
337
338  return view;
339}
340