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 32 ProfileChooserView* ProfileChooserView::profile_bubble_ = NULL; 33 bool ProfileChooserView::close_on_deactivate_ = true; 34 35 // static 36 void 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 55 bool ProfileChooserView::IsShowing() { 56 return profile_bubble_ != NULL; 57 } 58 59 // static 60 void ProfileChooserView::Hide() { 61 if (IsShowing()) 62 profile_bubble_->GetWidget()->Close(); 63 } 64 65 ProfileChooserView::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 82 ProfileChooserView::~ProfileChooserView() { 83 } 84 85 void ProfileChooserView::Init() { 86 // Build the menu for the first time. 87 OnAvatarMenuModelChanged(avatar_menu_model_.get()); 88 } 89 90 void ProfileChooserView::WindowClosing() { 91 DCHECK_EQ(profile_bubble_, this); 92 profile_bubble_ = NULL; 93 } 94 95 bool ProfileChooserView::OnMousePressed(const ui::MouseEvent& event) { 96 return true; // Won't get "released" event if we don't return "true" here. 97 } 98 99 void 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 111 void 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 129 void 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 187 views::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 201 views::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 247 views::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 278 views::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 302 views::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