1 // Copyright (c) 2012 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/avatar_menu_bubble_view.h" 6 7 #include <algorithm> 8 9 #include "base/command_line.h" 10 #include "base/strings/string16.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "chrome/app/chrome_command_ids.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/profiles/avatar_menu.h" 15 #include "chrome/browser/profiles/profile_info_cache.h" 16 #include "chrome/browser/profiles/profile_info_util.h" 17 #include "chrome/browser/profiles/profile_manager.h" 18 #include "chrome/browser/profiles/profile_window.h" 19 #include "chrome/browser/signin/signin_manager.h" 20 #include "chrome/browser/signin/signin_manager_factory.h" 21 #include "chrome/browser/ui/browser.h" 22 #include "chrome/browser/ui/browser_commands.h" 23 #include "chrome/browser/ui/browser_list.h" 24 #include "chrome/browser/ui/browser_window.h" 25 #include "chrome/browser/ui/chrome_pages.h" 26 #include "chrome/common/chrome_switches.h" 27 #include "chrome/common/url_constants.h" 28 #include "content/public/browser/page_navigator.h" 29 #include "content/public/browser/web_contents.h" 30 #include "grit/generated_resources.h" 31 #include "grit/theme_resources.h" 32 #include "ui/base/l10n/l10n_util.h" 33 #include "ui/base/resource/resource_bundle.h" 34 #include "ui/gfx/canvas.h" 35 #include "ui/gfx/image/canvas_image_source.h" 36 #include "ui/gfx/image/image.h" 37 #include "ui/views/controls/button/custom_button.h" 38 #include "ui/views/controls/button/image_button.h" 39 #include "ui/views/controls/button/label_button.h" 40 #include "ui/views/controls/image_view.h" 41 #include "ui/views/controls/label.h" 42 #include "ui/views/controls/link.h" 43 #include "ui/views/controls/separator.h" 44 #include "ui/views/layout/grid_layout.h" 45 #include "ui/views/layout/layout_constants.h" 46 #include "ui/views/widget/widget.h" 47 48 namespace { 49 50 const int kItemHeight = 44; 51 const int kItemMarginY = 4; 52 const int kIconMarginX = 6; 53 const int kSeparatorPaddingY = 5; 54 const int kMaxItemTextWidth = 200; 55 const SkColor kHighlightColor = 0xFFE3EDF6; 56 57 inline int Round(double x) { 58 return static_cast<int>(x + 0.5); 59 } 60 61 gfx::Rect GetCenteredAndScaledRect(int src_width, int src_height, 62 int dst_x, int dst_y, 63 int dst_width, int dst_height) { 64 int scaled_width; 65 int scaled_height; 66 if (src_width > src_height) { 67 scaled_width = std::min(src_width, dst_width); 68 float scale = static_cast<float>(scaled_width) / 69 static_cast<float>(src_width); 70 scaled_height = Round(src_height * scale); 71 } else { 72 scaled_height = std::min(src_height, dst_height); 73 float scale = static_cast<float>(scaled_height) / 74 static_cast<float>(src_height); 75 scaled_width = Round(src_width * scale); 76 } 77 int x = dst_x + (dst_width - scaled_width) / 2; 78 int y = dst_y + (dst_height - scaled_height) / 2; 79 return gfx::Rect(x, y, scaled_width, scaled_height); 80 } 81 82 // BadgeImageSource ----------------------------------------------------------- 83 class BadgeImageSource: public gfx::CanvasImageSource { 84 public: 85 BadgeImageSource(const gfx::ImageSkia& icon, 86 const gfx::Size& icon_size, 87 const gfx::ImageSkia& badge); 88 89 virtual ~BadgeImageSource(); 90 91 // Overridden from CanvasImageSource: 92 virtual void Draw(gfx::Canvas* canvas) OVERRIDE; 93 94 private: 95 gfx::Size ComputeSize(const gfx::ImageSkia& icon, 96 const gfx::Size& size, 97 const gfx::ImageSkia& badge); 98 99 const gfx::ImageSkia icon_; 100 gfx::Size icon_size_; 101 const gfx::ImageSkia badge_; 102 103 DISALLOW_COPY_AND_ASSIGN(BadgeImageSource); 104 }; 105 106 BadgeImageSource::BadgeImageSource(const gfx::ImageSkia& icon, 107 const gfx::Size& icon_size, 108 const gfx::ImageSkia& badge) 109 : gfx::CanvasImageSource(ComputeSize(icon, icon_size, badge), false), 110 icon_(icon), 111 icon_size_(icon_size), 112 badge_(badge) { 113 } 114 115 BadgeImageSource::~BadgeImageSource() { 116 } 117 118 void BadgeImageSource::Draw(gfx::Canvas* canvas) { 119 canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0, 120 icon_size_.width(), icon_size_.height(), true); 121 canvas->DrawImageInt(badge_, size().width() - badge_.width(), 122 size().height() - badge_.height()); 123 } 124 125 gfx::Size BadgeImageSource::ComputeSize(const gfx::ImageSkia& icon, 126 const gfx::Size& icon_size, 127 const gfx::ImageSkia& badge) { 128 const float kBadgeOverlapRatioX = 1.0f / 5.0f; 129 int width = icon_size.width() + badge.width() * kBadgeOverlapRatioX; 130 const float kBadgeOverlapRatioY = 1.0f / 3.0f; 131 int height = icon_size.height() + badge.height() * kBadgeOverlapRatioY; 132 return gfx::Size(width, height); 133 } 134 135 // HighlightDelegate ---------------------------------------------------------- 136 137 // Delegate to callback when the highlight state of a control changes. 138 class HighlightDelegate { 139 public: 140 virtual ~HighlightDelegate() {} 141 virtual void OnHighlightStateChanged() = 0; 142 virtual void OnFocusStateChanged(bool has_focus) = 0; 143 }; 144 145 146 // EditProfileLink ------------------------------------------------------------ 147 148 // A custom Link control that forwards highlight state changes. We need to do 149 // this to make sure that the ProfileItemView looks highlighted even when 150 // the mouse is over this link. 151 class EditProfileLink : public views::Link { 152 public: 153 explicit EditProfileLink(const base::string16& title, 154 HighlightDelegate* delegate); 155 156 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; 157 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; 158 virtual void OnFocus() OVERRIDE; 159 virtual void OnBlur() OVERRIDE; 160 161 views::CustomButton::ButtonState state() { return state_; } 162 163 private: 164 HighlightDelegate* delegate_; 165 views::CustomButton::ButtonState state_; 166 }; 167 168 EditProfileLink::EditProfileLink(const base::string16& title, 169 HighlightDelegate* delegate) 170 : views::Link(title), 171 delegate_(delegate), 172 state_(views::CustomButton::STATE_NORMAL) { 173 } 174 175 void EditProfileLink::OnMouseEntered(const ui::MouseEvent& event) { 176 views::Link::OnMouseEntered(event); 177 state_ = views::CustomButton::STATE_HOVERED; 178 delegate_->OnHighlightStateChanged(); 179 } 180 181 void EditProfileLink::OnMouseExited(const ui::MouseEvent& event) { 182 views::Link::OnMouseExited(event); 183 state_ = views::CustomButton::STATE_NORMAL; 184 delegate_->OnHighlightStateChanged(); 185 } 186 187 void EditProfileLink::OnFocus() { 188 views::Link::OnFocus(); 189 delegate_->OnFocusStateChanged(true); 190 } 191 192 void EditProfileLink::OnBlur() { 193 views::Link::OnBlur(); 194 state_ = views::CustomButton::STATE_NORMAL; 195 delegate_->OnFocusStateChanged(false); 196 } 197 198 199 // ProfileImageView ----------------------------------------------------------- 200 201 // A custom image view that ignores mouse events so that the parent can receive 202 // them instead. 203 class ProfileImageView : public views::ImageView { 204 public: 205 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE; 206 }; 207 208 bool ProfileImageView::HitTestRect(const gfx::Rect& rect) const { 209 return false; 210 } 211 212 } // namespace 213 214 // ProfileItemView ------------------------------------------------------------ 215 216 // Control that shows information about a single profile. 217 class ProfileItemView : public views::CustomButton, 218 public HighlightDelegate { 219 public: 220 ProfileItemView(const AvatarMenu::Item& item, 221 AvatarMenuBubbleView* parent, 222 AvatarMenu* menu); 223 224 virtual gfx::Size GetPreferredSize() OVERRIDE; 225 virtual void Layout() OVERRIDE; 226 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; 227 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; 228 virtual void OnFocus() OVERRIDE; 229 virtual void OnBlur() OVERRIDE; 230 231 virtual void OnHighlightStateChanged() OVERRIDE; 232 virtual void OnFocusStateChanged(bool has_focus) OVERRIDE; 233 234 const AvatarMenu::Item& item() const { return item_; } 235 EditProfileLink* edit_link() { return edit_link_; } 236 237 private: 238 gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon); 239 240 bool IsHighlighted(); 241 242 AvatarMenu::Item item_; 243 AvatarMenuBubbleView* parent_; 244 AvatarMenu* menu_; 245 views::ImageView* image_view_; 246 views::Label* name_label_; 247 views::Label* sync_state_label_; 248 EditProfileLink* edit_link_; 249 250 DISALLOW_COPY_AND_ASSIGN(ProfileItemView); 251 }; 252 253 ProfileItemView::ProfileItemView(const AvatarMenu::Item& item, 254 AvatarMenuBubbleView* parent, 255 AvatarMenu* menu) 256 : views::CustomButton(parent), 257 item_(item), 258 parent_(parent), 259 menu_(menu) { 260 set_notify_enter_exit_on_child(true); 261 262 image_view_ = new ProfileImageView(); 263 gfx::ImageSkia profile_icon = *item_.icon.ToImageSkia(); 264 if (item_.active || item_.signin_required) 265 image_view_->SetImage(GetBadgedIcon(profile_icon)); 266 else 267 image_view_->SetImage(profile_icon); 268 AddChildView(image_view_); 269 270 // Add a label to show the profile name. 271 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 272 name_label_ = new views::Label(item_.name, 273 rb->GetFont(item_.active ? 274 ui::ResourceBundle::BoldFont : 275 ui::ResourceBundle::BaseFont)); 276 name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 277 AddChildView(name_label_); 278 279 // Add a label to show the sync state. 280 sync_state_label_ = new views::Label(item_.sync_state); 281 if (item_.signed_in) 282 sync_state_label_->SetElideBehavior(views::Label::ELIDE_AS_EMAIL); 283 sync_state_label_->SetFont(rb->GetFont(ui::ResourceBundle::SmallFont)); 284 sync_state_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 285 sync_state_label_->SetEnabled(false); 286 AddChildView(sync_state_label_); 287 288 // Add an edit profile link. 289 edit_link_ = new EditProfileLink( 290 l10n_util::GetStringUTF16(IDS_PROFILES_EDIT_PROFILE_LINK), this); 291 edit_link_->set_listener(parent); 292 edit_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 293 edit_link_->SetHasFocusBorder(true); 294 AddChildView(edit_link_); 295 296 OnHighlightStateChanged(); 297 } 298 299 gfx::Size ProfileItemView::GetPreferredSize() { 300 int text_width = std::max(name_label_->GetPreferredSize().width(), 301 sync_state_label_->GetPreferredSize().width()); 302 text_width = std::max(edit_link_->GetPreferredSize().width(), text_width); 303 text_width = std::min(kMaxItemTextWidth, text_width); 304 return gfx::Size(profiles::kAvatarIconWidth + kIconMarginX + text_width, 305 kItemHeight); 306 } 307 308 void ProfileItemView::Layout() { 309 // Profile icon. 310 gfx::Rect icon_rect; 311 if (item_.active) { 312 // If this is the active item then the icon is already scaled and so 313 // just use the preferred size. 314 icon_rect.set_size(image_view_->GetPreferredSize()); 315 icon_rect.set_y((height() - icon_rect.height()) / 2); 316 } else { 317 const gfx::ImageSkia& icon = image_view_->GetImage(); 318 icon_rect = GetCenteredAndScaledRect(icon.width(), icon.height(), 0, 0, 319 profiles::kAvatarIconWidth, height()); 320 } 321 image_view_->SetBoundsRect(icon_rect); 322 323 int label_x = profiles::kAvatarIconWidth + kIconMarginX; 324 int max_label_width = std::max(width() - label_x, 0); 325 gfx::Size name_size = name_label_->GetPreferredSize(); 326 name_size.set_width(std::min(name_size.width(), max_label_width)); 327 gfx::Size state_size = sync_state_label_->GetPreferredSize(); 328 state_size.set_width(std::min(state_size.width(), max_label_width)); 329 gfx::Size edit_size = edit_link_->GetPreferredSize(); 330 edit_size.set_width(std::min(edit_size.width(), max_label_width)); 331 332 const int kNameStatePaddingY = 2; 333 int labels_height = name_size.height() + kNameStatePaddingY + 334 std::max(state_size.height(), edit_size.height()); 335 int y = (height() - labels_height) / 2; 336 name_label_->SetBounds(label_x, y, name_size.width(), name_size.height()); 337 338 int bottom = y + labels_height; 339 sync_state_label_->SetBounds(label_x, bottom - state_size.height(), 340 state_size.width(), state_size.height()); 341 // The edit link overlaps the sync state label. 342 edit_link_->SetBounds(label_x, bottom - edit_size.height(), 343 edit_size.width(), edit_size.height()); 344 } 345 346 void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) { 347 views::CustomButton::OnMouseEntered(event); 348 OnHighlightStateChanged(); 349 } 350 351 void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) { 352 views::CustomButton::OnMouseExited(event); 353 OnHighlightStateChanged(); 354 } 355 356 void ProfileItemView::OnFocus() { 357 views::CustomButton::OnFocus(); 358 OnFocusStateChanged(true); 359 } 360 361 void ProfileItemView::OnBlur() { 362 views::CustomButton::OnBlur(); 363 OnFocusStateChanged(false); 364 } 365 366 void ProfileItemView::OnHighlightStateChanged() { 367 const SkColor color = IsHighlighted() ? kHighlightColor : parent_->color(); 368 set_background(views::Background::CreateSolidBackground(color)); 369 name_label_->SetBackgroundColor(color); 370 sync_state_label_->SetBackgroundColor(color); 371 edit_link_->SetBackgroundColor(color); 372 373 bool show_edit = IsHighlighted() && item_.active && 374 menu_->ShouldShowEditProfileLink(); 375 sync_state_label_->SetVisible(!show_edit); 376 edit_link_->SetVisible(show_edit); 377 SchedulePaint(); 378 } 379 380 void ProfileItemView::OnFocusStateChanged(bool has_focus) { 381 if (!has_focus && state() != views::CustomButton::STATE_DISABLED) 382 SetState(views::CustomButton::STATE_NORMAL); 383 OnHighlightStateChanged(); 384 } 385 386 // static 387 gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) { 388 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); 389 const gfx::ImageSkia* badge = NULL; 390 391 if (item_.active) 392 badge = rb->GetImageSkiaNamed(IDR_PROFILE_SELECTED); 393 else if (item_.signin_required) // TODO(bcwhite): create new icon 394 badge = rb->GetImageSkiaNamed(IDR_OMNIBOX_HTTPS_VALID); 395 else 396 NOTREACHED(); // function should only be called if one of above is true 397 398 gfx::Size icon_size = GetCenteredAndScaledRect(icon.width(), icon.height(), 399 0, 0, profiles::kAvatarIconWidth, kItemHeight).size(); 400 gfx::CanvasImageSource* source = 401 new BadgeImageSource(icon, icon_size, *badge); 402 // ImageSkia takes ownership of |source|. 403 return gfx::ImageSkia(source, source->size()); 404 } 405 406 bool ProfileItemView::IsHighlighted() { 407 return state() == views::CustomButton::STATE_PRESSED || 408 state() == views::CustomButton::STATE_HOVERED || 409 edit_link_->state() == views::CustomButton::STATE_PRESSED || 410 edit_link_->state() == views::CustomButton::STATE_HOVERED || 411 HasFocus() || 412 edit_link_->HasFocus(); 413 } 414 415 416 // ActionButtonView ----------------------------------------------------------- 417 418 // A custom view that manages the "action" buttons at the bottom of the list 419 // of profiles. 420 class ActionButtonView : public views::View { 421 public: 422 ActionButtonView(views::ButtonListener* listener, Profile* profile); 423 424 private: 425 views::LabelButton* manage_button_; 426 views::LabelButton* signout_button_; 427 428 DISALLOW_COPY_AND_ASSIGN(ActionButtonView); 429 }; 430 431 432 ActionButtonView::ActionButtonView(views::ButtonListener* listener, 433 Profile* profile) 434 : manage_button_(NULL), 435 signout_button_(NULL) { 436 std::string username; 437 SigninManagerBase* signin = 438 SigninManagerFactory::GetForProfile(profile); 439 if (signin != NULL) 440 username = signin->GetAuthenticatedUsername(); 441 442 manage_button_ = new views::LabelButton( 443 listener, l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON)); 444 manage_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 445 manage_button_->SetTooltipText( 446 l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON_TIP)); 447 manage_button_->set_tag(IDS_PROFILES_MANAGE_PROFILES_BUTTON); 448 449 signout_button_ = new views::LabelButton( 450 listener, l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON)); 451 signout_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 452 if (username.empty()) { 453 signout_button_->SetTooltipText( 454 l10n_util::GetStringUTF16( 455 IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP_UNAVAILABLE)); 456 signout_button_->SetEnabled(false); 457 } else { 458 signout_button_->SetTooltipText( 459 l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP, 460 UTF8ToUTF16(username))); 461 } 462 signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON); 463 464 views::GridLayout* layout = new views::GridLayout(this); 465 views::ColumnSet* columns = layout->AddColumnSet(0); 466 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1, 467 views::GridLayout::USE_PREF, 0, 0); 468 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1, 469 views::GridLayout::USE_PREF, 0, 0); 470 layout->StartRow(0, 0); 471 layout->AddView(signout_button_); 472 layout->AddView(manage_button_); 473 SetLayoutManager(layout); 474 } 475 476 477 // AvatarMenuBubbleView ------------------------------------------------------- 478 479 // static 480 AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL; 481 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true; 482 483 // static 484 void AvatarMenuBubbleView::ShowBubble( 485 views::View* anchor_view, 486 views::BubbleBorder::Arrow arrow, 487 views::BubbleBorder::BubbleAlignment border_alignment, 488 const gfx::Rect& anchor_rect, 489 Browser* browser) { 490 if (IsShowing()) 491 return; 492 493 DCHECK(chrome::IsCommandEnabled(browser, IDC_SHOW_AVATAR_MENU)); 494 avatar_bubble_ = new AvatarMenuBubbleView( 495 anchor_view, arrow, anchor_rect, browser); 496 views::BubbleDelegateView::CreateBubble(avatar_bubble_); 497 avatar_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_); 498 avatar_bubble_->SetBackgroundColors(); 499 avatar_bubble_->SetAlignment(border_alignment); 500 avatar_bubble_->GetWidget()->Show(); 501 } 502 503 // static 504 bool AvatarMenuBubbleView::IsShowing() { 505 return avatar_bubble_ != NULL; 506 } 507 508 // static 509 void AvatarMenuBubbleView::Hide() { 510 if (IsShowing()) 511 avatar_bubble_->GetWidget()->Close(); 512 } 513 514 AvatarMenuBubbleView::AvatarMenuBubbleView( 515 views::View* anchor_view, 516 views::BubbleBorder::Arrow arrow, 517 const gfx::Rect& anchor_rect, 518 Browser* browser) 519 : BubbleDelegateView(anchor_view, arrow), 520 anchor_rect_(anchor_rect), 521 browser_(browser), 522 separator_(NULL), 523 buttons_view_(NULL), 524 managed_user_info_(NULL), 525 separator_switch_users_(NULL), 526 expanded_(false) { 527 avatar_menu_.reset(new AvatarMenu( 528 &g_browser_process->profile_manager()->GetProfileInfoCache(), 529 this, 530 browser_)); 531 avatar_menu_->RebuildMenu(); 532 } 533 534 AvatarMenuBubbleView::~AvatarMenuBubbleView() { 535 } 536 537 gfx::Size AvatarMenuBubbleView::GetPreferredSize() { 538 const int kBubbleViewMinWidth = 175; 539 gfx::Size preferred_size(kBubbleViewMinWidth, 0); 540 for (size_t i = 0; i < item_views_.size(); ++i) { 541 gfx::Size size = item_views_[i]->GetPreferredSize(); 542 preferred_size.Enlarge(0, size.height() + kItemMarginY); 543 preferred_size.SetToMax(size); 544 } 545 546 if (buttons_view_) { 547 preferred_size.Enlarge( 548 0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height()); 549 550 gfx::Size buttons_size = buttons_view_->GetPreferredSize(); 551 preferred_size.Enlarge(0, buttons_size.height()); 552 preferred_size.SetToMax(buttons_size); 553 } 554 555 556 if (managed_user_info_) { 557 // First handle the switch profile link because it can still affect the 558 // preferred width. 559 gfx::Size size = switch_profile_link_->GetPreferredSize(); 560 preferred_size.Enlarge(0, size.height()); 561 preferred_size.SetToMax(size); 562 563 // Add the height of the two separators. 564 preferred_size.Enlarge( 565 0, 566 kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2); 567 } 568 569 const int kBubbleViewMaxWidth = 800; 570 preferred_size.SetToMin( 571 gfx::Size(kBubbleViewMaxWidth, preferred_size.height())); 572 573 // We have to do this after the final width is calculated, since the label 574 // will wrap based on the width. 575 if (managed_user_info_) { 576 int remaining_width = 577 preferred_size.width() - icon_view_->GetPreferredSize().width() - 578 views::kRelatedControlSmallHorizontalSpacing; 579 preferred_size.Enlarge( 580 0, 581 managed_user_info_->GetHeightForWidth(remaining_width) + kItemMarginY); 582 } 583 584 return preferred_size; 585 } 586 587 void AvatarMenuBubbleView::Layout() { 588 int y = 0; 589 for (size_t i = 0; i < item_views_.size(); ++i) { 590 views::CustomButton* item_view = item_views_[i]; 591 int item_height = item_view->GetPreferredSize().height(); 592 int item_width = width(); 593 item_view->SetBounds(0, y, item_width, item_height); 594 y += item_height + kItemMarginY; 595 } 596 597 int separator_height; 598 if (buttons_view_ || managed_user_info_) { 599 separator_height = separator_->GetPreferredSize().height(); 600 y += kSeparatorPaddingY; 601 separator_->SetBounds(0, y, width(), separator_height); 602 y += kSeparatorPaddingY + separator_height; 603 } 604 605 if (buttons_view_) { 606 buttons_view_->SetBounds(0, y, 607 width(), buttons_view_->GetPreferredSize().height()); 608 } else if (managed_user_info_) { 609 gfx::Size icon_size = icon_view_->GetPreferredSize(); 610 gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height()); 611 icon_view_->SetBoundsRect(icon_bounds); 612 int info_width = width() - icon_bounds.right() - 613 views::kRelatedControlSmallHorizontalSpacing; 614 int height = managed_user_info_->GetHeightForWidth(info_width); 615 managed_user_info_->SetBounds( 616 icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing, 617 y, info_width, height); 618 y += height + kItemMarginY + kSeparatorPaddingY; 619 separator_switch_users_->SetBounds(0, y, width(), separator_height); 620 y += separator_height + kSeparatorPaddingY; 621 int link_height = switch_profile_link_->GetPreferredSize().height(); 622 switch_profile_link_->SetBounds(0, y, width(), link_height); 623 } 624 } 625 626 bool AvatarMenuBubbleView::AcceleratorPressed( 627 const ui::Accelerator& accelerator) { 628 if (accelerator.key_code() != ui::VKEY_DOWN && 629 accelerator.key_code() != ui::VKEY_UP) 630 return BubbleDelegateView::AcceleratorPressed(accelerator); 631 632 if (item_views_.empty()) 633 return true; 634 635 // Find the currently focused item. Note that if there is no focused item, the 636 // code below correctly handles a |focus_index| of -1. 637 int focus_index = -1; 638 for (size_t i = 0; i < item_views_.size(); ++i) { 639 if (item_views_[i]->HasFocus()) { 640 focus_index = i; 641 break; 642 } 643 } 644 645 // Moved the focus up or down by 1. 646 if (accelerator.key_code() == ui::VKEY_DOWN) 647 focus_index = (focus_index + 1) % item_views_.size(); 648 else 649 focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1; 650 item_views_[focus_index]->RequestFocus(); 651 652 return true; 653 } 654 655 void AvatarMenuBubbleView::ButtonPressed(views::Button* sender, 656 const ui::Event& event) { 657 if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) { 658 std::string subpage = chrome::kSearchUsersSubPage; 659 chrome::ShowSettingsSubPage(browser_, subpage); 660 return; 661 } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) { 662 profiles::LockProfile(browser_->profile()); 663 return; 664 } 665 666 for (size_t i = 0; i < item_views_.size(); ++i) { 667 ProfileItemView* item_view = item_views_[i]; 668 if (sender == item_view) { 669 // Clicking on the active profile shouldn't do anything. 670 if (!item_view->item().active) { 671 avatar_menu_->SwitchToProfile( 672 i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW); 673 } 674 break; 675 } 676 } 677 } 678 679 void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) { 680 if (source == buttons_view_) { 681 avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON); 682 return; 683 } 684 if (source == switch_profile_link_) { 685 expanded_ = true; 686 OnAvatarMenuChanged(avatar_menu_.get()); 687 return; 688 } 689 690 for (size_t i = 0; i < item_views_.size(); ++i) { 691 ProfileItemView* item_view = item_views_[i]; 692 if (source == item_view->edit_link()) { 693 avatar_menu_->EditProfile(i); 694 return; 695 } 696 } 697 } 698 699 gfx::Rect AvatarMenuBubbleView::GetAnchorRect() { 700 return anchor_rect_; 701 } 702 703 void AvatarMenuBubbleView::Init() { 704 // Build the menu for the first time. 705 OnAvatarMenuChanged(avatar_menu_.get()); 706 AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE)); 707 AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE)); 708 } 709 710 void AvatarMenuBubbleView::WindowClosing() { 711 DCHECK_EQ(avatar_bubble_, this); 712 avatar_bubble_ = NULL; 713 } 714 715 void AvatarMenuBubbleView::InitMenuContents( 716 AvatarMenu* avatar_menu) { 717 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) { 718 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i); 719 ProfileItemView* item_view = new ProfileItemView(item, 720 this, 721 avatar_menu_.get()); 722 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( 723 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); 724 item_view->SetFocusable(true); 725 AddChildView(item_view); 726 item_views_.push_back(item_view); 727 } 728 729 if (CommandLine::ForCurrentProcess()->HasSwitch( 730 switches::kNewProfileManagement)) { 731 separator_ = new views::Separator(views::Separator::HORIZONTAL); 732 AddChildView(separator_); 733 buttons_view_ = new ActionButtonView(this, browser_->profile()); 734 AddChildView(buttons_view_); 735 } else if (avatar_menu_->ShouldShowAddNewProfileLink()) { 736 views::Link* add_profile_link = new views::Link( 737 l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)); 738 add_profile_link->set_listener(this); 739 add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER); 740 add_profile_link->SetBackgroundColor(color()); 741 separator_ = new views::Separator(views::Separator::HORIZONTAL); 742 AddChildView(separator_); 743 buttons_view_ = add_profile_link; 744 AddChildView(buttons_view_); 745 } 746 } 747 748 void AvatarMenuBubbleView::InitManagedUserContents( 749 AvatarMenu* avatar_menu) { 750 // Show the profile of the managed user. 751 size_t active_index = avatar_menu->GetActiveProfileIndex(); 752 const AvatarMenu::Item& item = 753 avatar_menu->GetItemAt(active_index); 754 ProfileItemView* item_view = new ProfileItemView(item, 755 this, 756 avatar_menu_.get()); 757 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( 758 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); 759 item_views_.push_back(item_view); 760 AddChildView(item_view); 761 separator_ = new views::Separator(views::Separator::HORIZONTAL); 762 AddChildView(separator_); 763 764 // Add information about managed users. 765 managed_user_info_ = 766 new views::Label(avatar_menu_->GetManagedUserInformation(), 767 ui::ResourceBundle::GetSharedInstance().GetFont( 768 ui::ResourceBundle::SmallFont)); 769 managed_user_info_->SetMultiLine(true); 770 managed_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 771 managed_user_info_->SetBackgroundColor(color()); 772 AddChildView(managed_user_info_); 773 774 // Add the managed user icon. 775 icon_view_ = new views::ImageView(); 776 icon_view_->SetImage(avatar_menu_->GetManagedUserIcon().ToImageSkia()); 777 AddChildView(icon_view_); 778 779 // Add a link for switching profiles. 780 separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL); 781 AddChildView(separator_switch_users_); 782 switch_profile_link_ = new views::Link( 783 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK)); 784 switch_profile_link_->set_listener(this); 785 switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 786 switch_profile_link_->SetBackgroundColor(color()); 787 AddChildView(switch_profile_link_); 788 } 789 790 void AvatarMenuBubbleView::OnAvatarMenuChanged( 791 AvatarMenu* avatar_menu) { 792 // Unset all our child view references and call RemoveAllChildViews() which 793 // will actually delete them. 794 buttons_view_ = NULL; 795 managed_user_info_ = NULL; 796 item_views_.clear(); 797 RemoveAllChildViews(true); 798 799 if (avatar_menu_->GetManagedUserInformation().empty() || expanded_) 800 InitMenuContents(avatar_menu); 801 else 802 InitManagedUserContents(avatar_menu); 803 804 // If the bubble has already been shown then resize and reposition the bubble. 805 Layout(); 806 if (GetBubbleFrameView()) 807 SizeToContents(); 808 } 809 810 void AvatarMenuBubbleView::SetBackgroundColors() { 811 for (size_t i = 0; i < item_views_.size(); ++i) { 812 item_views_[i]->OnHighlightStateChanged(); 813 } 814 } 815