| OLD | NEW |
| (Empty) |
| 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/strings/string16.h" | |
| 10 #include "base/strings/utf_string_conversions.h" | |
| 11 #include "chrome/app/chrome_command_ids.h" | |
| 12 #include "chrome/browser/browser_process.h" | |
| 13 #include "chrome/browser/profiles/avatar_menu.h" | |
| 14 #include "chrome/browser/profiles/profile_info_cache.h" | |
| 15 #include "chrome/browser/profiles/profile_info_util.h" | |
| 16 #include "chrome/browser/profiles/profile_manager.h" | |
| 17 #include "chrome/browser/profiles/profile_window.h" | |
| 18 #include "chrome/browser/signin/signin_manager_factory.h" | |
| 19 #include "chrome/browser/ui/browser.h" | |
| 20 #include "chrome/browser/ui/browser_commands.h" | |
| 21 #include "chrome/browser/ui/browser_list.h" | |
| 22 #include "chrome/browser/ui/browser_window.h" | |
| 23 #include "chrome/browser/ui/chrome_pages.h" | |
| 24 #include "chrome/common/profile_management_switches.h" | |
| 25 #include "chrome/common/url_constants.h" | |
| 26 #include "components/signin/core/browser/signin_manager.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 base::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 base::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 AvatarMenu::Item& item, | |
| 220 AvatarMenuBubbleView* parent, | |
| 221 AvatarMenu* menu); | |
| 222 | |
| 223 virtual gfx::Size GetPreferredSize() OVERRIDE; | |
| 224 virtual void Layout() OVERRIDE; | |
| 225 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; | |
| 226 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; | |
| 227 virtual void OnFocus() OVERRIDE; | |
| 228 virtual void OnBlur() OVERRIDE; | |
| 229 | |
| 230 virtual void OnHighlightStateChanged() OVERRIDE; | |
| 231 virtual void OnFocusStateChanged(bool has_focus) OVERRIDE; | |
| 232 | |
| 233 const AvatarMenu::Item& item() const { return item_; } | |
| 234 EditProfileLink* edit_link() { return edit_link_; } | |
| 235 | |
| 236 private: | |
| 237 gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon); | |
| 238 | |
| 239 bool IsHighlighted(); | |
| 240 | |
| 241 AvatarMenu::Item item_; | |
| 242 AvatarMenuBubbleView* parent_; | |
| 243 AvatarMenu* menu_; | |
| 244 views::ImageView* image_view_; | |
| 245 views::Label* name_label_; | |
| 246 views::Label* sync_state_label_; | |
| 247 EditProfileLink* edit_link_; | |
| 248 | |
| 249 DISALLOW_COPY_AND_ASSIGN(ProfileItemView); | |
| 250 }; | |
| 251 | |
| 252 ProfileItemView::ProfileItemView(const AvatarMenu::Item& item, | |
| 253 AvatarMenuBubbleView* parent, | |
| 254 AvatarMenu* menu) | |
| 255 : views::CustomButton(parent), | |
| 256 item_(item), | |
| 257 parent_(parent), | |
| 258 menu_(menu) { | |
| 259 set_notify_enter_exit_on_child(true); | |
| 260 | |
| 261 image_view_ = new ProfileImageView(); | |
| 262 gfx::ImageSkia profile_icon = *item_.icon.ToImageSkia(); | |
| 263 if (item_.active || item_.signin_required) | |
| 264 image_view_->SetImage(GetBadgedIcon(profile_icon)); | |
| 265 else | |
| 266 image_view_->SetImage(profile_icon); | |
| 267 AddChildView(image_view_); | |
| 268 | |
| 269 // Add a label to show the profile name. | |
| 270 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); | |
| 271 name_label_ = new views::Label(item_.name, | |
| 272 rb->GetFontList(item_.active ? | |
| 273 ui::ResourceBundle::BoldFont : | |
| 274 ui::ResourceBundle::BaseFont)); | |
| 275 name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 276 AddChildView(name_label_); | |
| 277 | |
| 278 // Add a label to show the sync state. | |
| 279 sync_state_label_ = new views::Label(item_.sync_state); | |
| 280 if (item_.signed_in) | |
| 281 sync_state_label_->SetElideBehavior(views::Label::ELIDE_AS_EMAIL); | |
| 282 sync_state_label_->SetFontList( | |
| 283 rb->GetFontList(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 AddChildView(edit_link_); | |
| 294 | |
| 295 OnHighlightStateChanged(); | |
| 296 } | |
| 297 | |
| 298 gfx::Size ProfileItemView::GetPreferredSize() { | |
| 299 int text_width = std::max(name_label_->GetPreferredSize().width(), | |
| 300 sync_state_label_->GetPreferredSize().width()); | |
| 301 text_width = std::max(edit_link_->GetPreferredSize().width(), text_width); | |
| 302 text_width = std::min(kMaxItemTextWidth, text_width); | |
| 303 return gfx::Size(profiles::kAvatarIconWidth + kIconMarginX + text_width, | |
| 304 kItemHeight); | |
| 305 } | |
| 306 | |
| 307 void ProfileItemView::Layout() { | |
| 308 // Profile icon. | |
| 309 gfx::Rect icon_rect; | |
| 310 if (item_.active) { | |
| 311 // If this is the active item then the icon is already scaled and so | |
| 312 // just use the preferred size. | |
| 313 icon_rect.set_size(image_view_->GetPreferredSize()); | |
| 314 icon_rect.set_y((height() - icon_rect.height()) / 2); | |
| 315 } else { | |
| 316 const gfx::ImageSkia& icon = image_view_->GetImage(); | |
| 317 icon_rect = GetCenteredAndScaledRect(icon.width(), icon.height(), 0, 0, | |
| 318 profiles::kAvatarIconWidth, height()); | |
| 319 } | |
| 320 image_view_->SetBoundsRect(icon_rect); | |
| 321 | |
| 322 int label_x = profiles::kAvatarIconWidth + kIconMarginX; | |
| 323 int max_label_width = std::max(width() - label_x, 0); | |
| 324 gfx::Size name_size = name_label_->GetPreferredSize(); | |
| 325 name_size.set_width(std::min(name_size.width(), max_label_width)); | |
| 326 gfx::Size state_size = sync_state_label_->GetPreferredSize(); | |
| 327 state_size.set_width(std::min(state_size.width(), max_label_width)); | |
| 328 gfx::Size edit_size = edit_link_->GetPreferredSize(); | |
| 329 edit_size.set_width(std::min(edit_size.width(), max_label_width)); | |
| 330 | |
| 331 const int kNameStatePaddingY = 2; | |
| 332 int labels_height = name_size.height() + kNameStatePaddingY + | |
| 333 std::max(state_size.height(), edit_size.height()); | |
| 334 int y = (height() - labels_height) / 2; | |
| 335 name_label_->SetBounds(label_x, y, name_size.width(), name_size.height()); | |
| 336 | |
| 337 int bottom = y + labels_height; | |
| 338 sync_state_label_->SetBounds(label_x, bottom - state_size.height(), | |
| 339 state_size.width(), state_size.height()); | |
| 340 // The edit link overlaps the sync state label. | |
| 341 edit_link_->SetBounds(label_x, bottom - edit_size.height(), | |
| 342 edit_size.width(), edit_size.height()); | |
| 343 } | |
| 344 | |
| 345 void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) { | |
| 346 views::CustomButton::OnMouseEntered(event); | |
| 347 OnHighlightStateChanged(); | |
| 348 } | |
| 349 | |
| 350 void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) { | |
| 351 views::CustomButton::OnMouseExited(event); | |
| 352 OnHighlightStateChanged(); | |
| 353 } | |
| 354 | |
| 355 void ProfileItemView::OnFocus() { | |
| 356 views::CustomButton::OnFocus(); | |
| 357 OnFocusStateChanged(true); | |
| 358 } | |
| 359 | |
| 360 void ProfileItemView::OnBlur() { | |
| 361 views::CustomButton::OnBlur(); | |
| 362 OnFocusStateChanged(false); | |
| 363 } | |
| 364 | |
| 365 void ProfileItemView::OnHighlightStateChanged() { | |
| 366 const SkColor color = IsHighlighted() ? kHighlightColor : parent_->color(); | |
| 367 set_background(views::Background::CreateSolidBackground(color)); | |
| 368 name_label_->SetBackgroundColor(color); | |
| 369 sync_state_label_->SetBackgroundColor(color); | |
| 370 edit_link_->SetBackgroundColor(color); | |
| 371 | |
| 372 bool show_edit = IsHighlighted() && item_.active && | |
| 373 menu_->ShouldShowEditProfileLink(); | |
| 374 sync_state_label_->SetVisible(!show_edit); | |
| 375 edit_link_->SetVisible(show_edit); | |
| 376 SchedulePaint(); | |
| 377 } | |
| 378 | |
| 379 void ProfileItemView::OnFocusStateChanged(bool has_focus) { | |
| 380 if (!has_focus && state() != views::CustomButton::STATE_DISABLED) | |
| 381 SetState(views::CustomButton::STATE_NORMAL); | |
| 382 OnHighlightStateChanged(); | |
| 383 } | |
| 384 | |
| 385 // static | |
| 386 gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) { | |
| 387 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); | |
| 388 const gfx::ImageSkia* badge = NULL; | |
| 389 | |
| 390 if (item_.active) | |
| 391 badge = rb->GetImageSkiaNamed(IDR_PROFILE_SELECTED); | |
| 392 else if (item_.signin_required) // TODO(bcwhite): create new icon | |
| 393 badge = rb->GetImageSkiaNamed(IDR_OMNIBOX_HTTPS_VALID); | |
| 394 else | |
| 395 NOTREACHED(); // function should only be called if one of above is true | |
| 396 | |
| 397 gfx::Size icon_size = GetCenteredAndScaledRect(icon.width(), icon.height(), | |
| 398 0, 0, profiles::kAvatarIconWidth, kItemHeight).size(); | |
| 399 gfx::CanvasImageSource* source = | |
| 400 new BadgeImageSource(icon, icon_size, *badge); | |
| 401 // ImageSkia takes ownership of |source|. | |
| 402 return gfx::ImageSkia(source, source->size()); | |
| 403 } | |
| 404 | |
| 405 bool ProfileItemView::IsHighlighted() { | |
| 406 return state() == views::CustomButton::STATE_PRESSED || | |
| 407 state() == views::CustomButton::STATE_HOVERED || | |
| 408 edit_link_->state() == views::CustomButton::STATE_PRESSED || | |
| 409 edit_link_->state() == views::CustomButton::STATE_HOVERED || | |
| 410 HasFocus() || | |
| 411 edit_link_->HasFocus(); | |
| 412 } | |
| 413 | |
| 414 | |
| 415 // ActionButtonView ----------------------------------------------------------- | |
| 416 | |
| 417 // A custom view that manages the "action" buttons at the bottom of the list | |
| 418 // of profiles. | |
| 419 class ActionButtonView : public views::View { | |
| 420 public: | |
| 421 ActionButtonView(views::ButtonListener* listener, Profile* profile); | |
| 422 | |
| 423 private: | |
| 424 views::LabelButton* manage_button_; | |
| 425 views::LabelButton* signout_button_; | |
| 426 | |
| 427 DISALLOW_COPY_AND_ASSIGN(ActionButtonView); | |
| 428 }; | |
| 429 | |
| 430 | |
| 431 ActionButtonView::ActionButtonView(views::ButtonListener* listener, | |
| 432 Profile* profile) | |
| 433 : manage_button_(NULL), | |
| 434 signout_button_(NULL) { | |
| 435 std::string username; | |
| 436 SigninManagerBase* signin = | |
| 437 SigninManagerFactory::GetForProfile(profile); | |
| 438 if (signin != NULL) | |
| 439 username = signin->GetAuthenticatedUsername(); | |
| 440 | |
| 441 manage_button_ = new views::LabelButton( | |
| 442 listener, l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON)); | |
| 443 manage_button_->SetStyle(views::Button::STYLE_BUTTON); | |
| 444 manage_button_->SetTooltipText( | |
| 445 l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON_TIP)); | |
| 446 manage_button_->set_tag(IDS_PROFILES_MANAGE_PROFILES_BUTTON); | |
| 447 | |
| 448 signout_button_ = new views::LabelButton( | |
| 449 listener, l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON)); | |
| 450 signout_button_->SetStyle(views::Button::STYLE_BUTTON); | |
| 451 if (username.empty()) { | |
| 452 signout_button_->SetTooltipText( | |
| 453 l10n_util::GetStringUTF16( | |
| 454 IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP_UNAVAILABLE)); | |
| 455 signout_button_->SetEnabled(false); | |
| 456 } else { | |
| 457 signout_button_->SetTooltipText( | |
| 458 l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP, | |
| 459 base::UTF8ToUTF16(username))); | |
| 460 } | |
| 461 signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON); | |
| 462 | |
| 463 views::GridLayout* layout = new views::GridLayout(this); | |
| 464 views::ColumnSet* columns = layout->AddColumnSet(0); | |
| 465 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1, | |
| 466 views::GridLayout::USE_PREF, 0, 0); | |
| 467 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1, | |
| 468 views::GridLayout::USE_PREF, 0, 0); | |
| 469 layout->StartRow(0, 0); | |
| 470 layout->AddView(signout_button_); | |
| 471 layout->AddView(manage_button_); | |
| 472 SetLayoutManager(layout); | |
| 473 } | |
| 474 | |
| 475 | |
| 476 // AvatarMenuBubbleView ------------------------------------------------------- | |
| 477 | |
| 478 // static | |
| 479 AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL; | |
| 480 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true; | |
| 481 | |
| 482 // static | |
| 483 void AvatarMenuBubbleView::ShowBubble( | |
| 484 views::View* anchor_view, | |
| 485 views::BubbleBorder::Arrow arrow, | |
| 486 views::BubbleBorder::BubbleAlignment border_alignment, | |
| 487 const gfx::Rect& anchor_rect, | |
| 488 Browser* browser) { | |
| 489 if (IsShowing()) | |
| 490 return; | |
| 491 | |
| 492 DCHECK(chrome::IsCommandEnabled(browser, IDC_SHOW_AVATAR_MENU)); | |
| 493 avatar_bubble_ = new AvatarMenuBubbleView( | |
| 494 anchor_view, arrow, anchor_rect, browser); | |
| 495 views::BubbleDelegateView::CreateBubble(avatar_bubble_); | |
| 496 avatar_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_); | |
| 497 avatar_bubble_->SetBackgroundColors(); | |
| 498 avatar_bubble_->SetAlignment(border_alignment); | |
| 499 avatar_bubble_->GetWidget()->Show(); | |
| 500 } | |
| 501 | |
| 502 // static | |
| 503 bool AvatarMenuBubbleView::IsShowing() { | |
| 504 return avatar_bubble_ != NULL; | |
| 505 } | |
| 506 | |
| 507 // static | |
| 508 void AvatarMenuBubbleView::Hide() { | |
| 509 if (IsShowing()) | |
| 510 avatar_bubble_->GetWidget()->Close(); | |
| 511 } | |
| 512 | |
| 513 AvatarMenuBubbleView::AvatarMenuBubbleView( | |
| 514 views::View* anchor_view, | |
| 515 views::BubbleBorder::Arrow arrow, | |
| 516 const gfx::Rect& anchor_rect, | |
| 517 Browser* browser) | |
| 518 : BubbleDelegateView(anchor_view, arrow), | |
| 519 anchor_rect_(anchor_rect), | |
| 520 browser_(browser), | |
| 521 separator_(NULL), | |
| 522 buttons_view_(NULL), | |
| 523 managed_user_info_(NULL), | |
| 524 separator_switch_users_(NULL), | |
| 525 expanded_(false) { | |
| 526 avatar_menu_.reset(new AvatarMenu( | |
| 527 &g_browser_process->profile_manager()->GetProfileInfoCache(), | |
| 528 this, | |
| 529 browser_)); | |
| 530 avatar_menu_->RebuildMenu(); | |
| 531 } | |
| 532 | |
| 533 AvatarMenuBubbleView::~AvatarMenuBubbleView() { | |
| 534 } | |
| 535 | |
| 536 gfx::Size AvatarMenuBubbleView::GetPreferredSize() { | |
| 537 const int kBubbleViewMinWidth = 175; | |
| 538 gfx::Size preferred_size(kBubbleViewMinWidth, 0); | |
| 539 for (size_t i = 0; i < item_views_.size(); ++i) { | |
| 540 gfx::Size size = item_views_[i]->GetPreferredSize(); | |
| 541 preferred_size.Enlarge(0, size.height() + kItemMarginY); | |
| 542 preferred_size.SetToMax(size); | |
| 543 } | |
| 544 | |
| 545 if (buttons_view_) { | |
| 546 preferred_size.Enlarge( | |
| 547 0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height()); | |
| 548 | |
| 549 gfx::Size buttons_size = buttons_view_->GetPreferredSize(); | |
| 550 preferred_size.Enlarge(0, buttons_size.height()); | |
| 551 preferred_size.SetToMax(buttons_size); | |
| 552 } | |
| 553 | |
| 554 | |
| 555 if (managed_user_info_) { | |
| 556 // First handle the switch profile link because it can still affect the | |
| 557 // preferred width. | |
| 558 gfx::Size size = switch_profile_link_->GetPreferredSize(); | |
| 559 preferred_size.Enlarge(0, size.height()); | |
| 560 preferred_size.SetToMax(size); | |
| 561 | |
| 562 // Add the height of the two separators. | |
| 563 preferred_size.Enlarge( | |
| 564 0, | |
| 565 kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2); | |
| 566 } | |
| 567 | |
| 568 const int kBubbleViewMaxWidth = 800; | |
| 569 preferred_size.SetToMin( | |
| 570 gfx::Size(kBubbleViewMaxWidth, preferred_size.height())); | |
| 571 | |
| 572 // We have to do this after the final width is calculated, since the label | |
| 573 // will wrap based on the width. | |
| 574 if (managed_user_info_) { | |
| 575 int remaining_width = | |
| 576 preferred_size.width() - icon_view_->GetPreferredSize().width() - | |
| 577 views::kRelatedControlSmallHorizontalSpacing; | |
| 578 preferred_size.Enlarge( | |
| 579 0, | |
| 580 managed_user_info_->GetHeightForWidth(remaining_width) + kItemMarginY); | |
| 581 } | |
| 582 | |
| 583 return preferred_size; | |
| 584 } | |
| 585 | |
| 586 void AvatarMenuBubbleView::Layout() { | |
| 587 int y = 0; | |
| 588 for (size_t i = 0; i < item_views_.size(); ++i) { | |
| 589 views::CustomButton* item_view = item_views_[i]; | |
| 590 int item_height = item_view->GetPreferredSize().height(); | |
| 591 int item_width = width(); | |
| 592 item_view->SetBounds(0, y, item_width, item_height); | |
| 593 y += item_height + kItemMarginY; | |
| 594 } | |
| 595 | |
| 596 int separator_height; | |
| 597 if (buttons_view_ || managed_user_info_) { | |
| 598 separator_height = separator_->GetPreferredSize().height(); | |
| 599 y += kSeparatorPaddingY; | |
| 600 separator_->SetBounds(0, y, width(), separator_height); | |
| 601 y += kSeparatorPaddingY + separator_height; | |
| 602 } | |
| 603 | |
| 604 if (buttons_view_) { | |
| 605 buttons_view_->SetBounds(0, y, | |
| 606 width(), buttons_view_->GetPreferredSize().height()); | |
| 607 } else if (managed_user_info_) { | |
| 608 gfx::Size icon_size = icon_view_->GetPreferredSize(); | |
| 609 gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height()); | |
| 610 icon_view_->SetBoundsRect(icon_bounds); | |
| 611 int info_width = width() - icon_bounds.right() - | |
| 612 views::kRelatedControlSmallHorizontalSpacing; | |
| 613 int height = managed_user_info_->GetHeightForWidth(info_width); | |
| 614 managed_user_info_->SetBounds( | |
| 615 icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing, | |
| 616 y, info_width, height); | |
| 617 y += height + kItemMarginY + kSeparatorPaddingY; | |
| 618 separator_switch_users_->SetBounds(0, y, width(), separator_height); | |
| 619 y += separator_height + kSeparatorPaddingY; | |
| 620 int link_height = switch_profile_link_->GetPreferredSize().height(); | |
| 621 switch_profile_link_->SetBounds(0, y, width(), link_height); | |
| 622 } | |
| 623 } | |
| 624 | |
| 625 bool AvatarMenuBubbleView::AcceleratorPressed( | |
| 626 const ui::Accelerator& accelerator) { | |
| 627 if (accelerator.key_code() != ui::VKEY_DOWN && | |
| 628 accelerator.key_code() != ui::VKEY_UP) | |
| 629 return BubbleDelegateView::AcceleratorPressed(accelerator); | |
| 630 | |
| 631 if (item_views_.empty()) | |
| 632 return true; | |
| 633 | |
| 634 // Find the currently focused item. Note that if there is no focused item, the | |
| 635 // code below correctly handles a |focus_index| of -1. | |
| 636 int focus_index = -1; | |
| 637 for (size_t i = 0; i < item_views_.size(); ++i) { | |
| 638 if (item_views_[i]->HasFocus()) { | |
| 639 focus_index = i; | |
| 640 break; | |
| 641 } | |
| 642 } | |
| 643 | |
| 644 // Moved the focus up or down by 1. | |
| 645 if (accelerator.key_code() == ui::VKEY_DOWN) | |
| 646 focus_index = (focus_index + 1) % item_views_.size(); | |
| 647 else | |
| 648 focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1; | |
| 649 item_views_[focus_index]->RequestFocus(); | |
| 650 | |
| 651 return true; | |
| 652 } | |
| 653 | |
| 654 void AvatarMenuBubbleView::ButtonPressed(views::Button* sender, | |
| 655 const ui::Event& event) { | |
| 656 if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) { | |
| 657 std::string subpage = chrome::kSearchUsersSubPage; | |
| 658 chrome::ShowSettingsSubPage(browser_, subpage); | |
| 659 return; | |
| 660 } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) { | |
| 661 profiles::LockProfile(browser_->profile()); | |
| 662 return; | |
| 663 } | |
| 664 | |
| 665 for (size_t i = 0; i < item_views_.size(); ++i) { | |
| 666 ProfileItemView* item_view = item_views_[i]; | |
| 667 if (sender == item_view) { | |
| 668 // Clicking on the active profile shouldn't do anything. | |
| 669 if (!item_view->item().active) { | |
| 670 avatar_menu_->SwitchToProfile( | |
| 671 i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW, | |
| 672 ProfileMetrics::SWITCH_PROFILE_ICON); | |
| 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 (switches::IsNewProfileManagement()) { | |
| 730 separator_ = new views::Separator(views::Separator::HORIZONTAL); | |
| 731 AddChildView(separator_); | |
| 732 buttons_view_ = new ActionButtonView(this, browser_->profile()); | |
| 733 AddChildView(buttons_view_); | |
| 734 } else if (avatar_menu_->ShouldShowAddNewProfileLink()) { | |
| 735 views::Link* add_profile_link = new views::Link( | |
| 736 l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)); | |
| 737 add_profile_link->set_listener(this); | |
| 738 add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER); | |
| 739 add_profile_link->SetBackgroundColor(color()); | |
| 740 separator_ = new views::Separator(views::Separator::HORIZONTAL); | |
| 741 AddChildView(separator_); | |
| 742 buttons_view_ = add_profile_link; | |
| 743 AddChildView(buttons_view_); | |
| 744 } | |
| 745 } | |
| 746 | |
| 747 void AvatarMenuBubbleView::InitManagedUserContents( | |
| 748 AvatarMenu* avatar_menu) { | |
| 749 // Show the profile of the managed user. | |
| 750 size_t active_index = avatar_menu->GetActiveProfileIndex(); | |
| 751 const AvatarMenu::Item& item = | |
| 752 avatar_menu->GetItemAt(active_index); | |
| 753 ProfileItemView* item_view = new ProfileItemView(item, | |
| 754 this, | |
| 755 avatar_menu_.get()); | |
| 756 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( | |
| 757 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); | |
| 758 item_views_.push_back(item_view); | |
| 759 AddChildView(item_view); | |
| 760 separator_ = new views::Separator(views::Separator::HORIZONTAL); | |
| 761 AddChildView(separator_); | |
| 762 | |
| 763 // Add information about managed users. | |
| 764 managed_user_info_ = | |
| 765 new views::Label(avatar_menu_->GetManagedUserInformation(), | |
| 766 ui::ResourceBundle::GetSharedInstance().GetFontList( | |
| 767 ui::ResourceBundle::SmallFont)); | |
| 768 managed_user_info_->SetMultiLine(true); | |
| 769 managed_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
| 770 managed_user_info_->SetBackgroundColor(color()); | |
| 771 AddChildView(managed_user_info_); | |
| 772 | |
| 773 // Add the managed user icon. | |
| 774 icon_view_ = new views::ImageView(); | |
| 775 icon_view_->SetImage(avatar_menu_->GetManagedUserIcon().ToImageSkia()); | |
| 776 AddChildView(icon_view_); | |
| 777 | |
| 778 // Add a link for switching profiles. | |
| 779 separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL); | |
| 780 AddChildView(separator_switch_users_); | |
| 781 switch_profile_link_ = new views::Link( | |
| 782 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK)); | |
| 783 switch_profile_link_->set_listener(this); | |
| 784 switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER); | |
| 785 switch_profile_link_->SetBackgroundColor(color()); | |
| 786 AddChildView(switch_profile_link_); | |
| 787 } | |
| 788 | |
| 789 void AvatarMenuBubbleView::OnAvatarMenuChanged( | |
| 790 AvatarMenu* avatar_menu) { | |
| 791 // Unset all our child view references and call RemoveAllChildViews() which | |
| 792 // will actually delete them. | |
| 793 buttons_view_ = NULL; | |
| 794 managed_user_info_ = NULL; | |
| 795 item_views_.clear(); | |
| 796 RemoveAllChildViews(true); | |
| 797 | |
| 798 if (avatar_menu_->GetManagedUserInformation().empty() || expanded_) | |
| 799 InitMenuContents(avatar_menu); | |
| 800 else | |
| 801 InitManagedUserContents(avatar_menu); | |
| 802 | |
| 803 // If the bubble has already been shown then resize and reposition the bubble. | |
| 804 Layout(); | |
| 805 if (GetBubbleFrameView()) | |
| 806 SizeToContents(); | |
| 807 } | |
| 808 | |
| 809 void AvatarMenuBubbleView::SetBackgroundColors() { | |
| 810 for (size_t i = 0; i < item_views_.size(); ++i) { | |
| 811 item_views_[i]->OnHighlightStateChanged(); | |
| 812 } | |
| 813 } | |
| OLD | NEW |