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