tray_user.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 "ash/system/user/tray_user.h" 6 7#include <algorithm> 8#include <climits> 9#include <vector> 10 11#include "ash/ash_switches.h" 12#include "ash/metrics/user_metrics_recorder.h" 13#include "ash/multi_profile_uma.h" 14#include "ash/popup_message.h" 15#include "ash/root_window_controller.h" 16#include "ash/session_state_delegate.h" 17#include "ash/shelf/shelf_layout_manager.h" 18#include "ash/shell.h" 19#include "ash/shell_delegate.h" 20#include "ash/system/tray/system_tray.h" 21#include "ash/system/tray/system_tray_delegate.h" 22#include "ash/system/tray/system_tray_notifier.h" 23#include "ash/system/tray/tray_constants.h" 24#include "ash/system/tray/tray_item_view.h" 25#include "ash/system/tray/tray_popup_label_button.h" 26#include "ash/system/tray/tray_popup_label_button_border.h" 27#include "ash/system/tray/tray_utils.h" 28#include "base/i18n/rtl.h" 29#include "base/logging.h" 30#include "base/memory/scoped_vector.h" 31#include "base/strings/string16.h" 32#include "base/strings/string_util.h" 33#include "base/strings/utf_string_conversions.h" 34#include "grit/ash_resources.h" 35#include "grit/ash_strings.h" 36#include "skia/ext/image_operations.h" 37#include "third_party/skia/include/core/SkCanvas.h" 38#include "third_party/skia/include/core/SkPaint.h" 39#include "third_party/skia/include/core/SkPath.h" 40#include "ui/aura/window.h" 41#include "ui/base/l10n/l10n_util.h" 42#include "ui/base/resource/resource_bundle.h" 43#include "ui/gfx/canvas.h" 44#include "ui/gfx/font_list.h" 45#include "ui/gfx/image/image.h" 46#include "ui/gfx/image/image_skia_operations.h" 47#include "ui/gfx/insets.h" 48#include "ui/gfx/range/range.h" 49#include "ui/gfx/rect.h" 50#include "ui/gfx/render_text.h" 51#include "ui/gfx/size.h" 52#include "ui/gfx/skia_util.h" 53#include "ui/gfx/text_elider.h" 54#include "ui/gfx/text_utils.h" 55#include "ui/views/border.h" 56#include "ui/views/bubble/tray_bubble_view.h" 57#include "ui/views/controls/button/button.h" 58#include "ui/views/controls/button/custom_button.h" 59#include "ui/views/controls/image_view.h" 60#include "ui/views/controls/label.h" 61#include "ui/views/controls/link.h" 62#include "ui/views/controls/link_listener.h" 63#include "ui/views/layout/box_layout.h" 64#include "ui/views/layout/fill_layout.h" 65#include "ui/views/mouse_watcher.h" 66#include "ui/views/painter.h" 67#include "ui/views/view.h" 68#include "ui/views/widget/widget.h" 69#include "ui/wm/core/shadow_types.h" 70 71namespace { 72 73const int kUserDetailsVerticalPadding = 5; 74const int kUserCardVerticalPadding = 10; 75const int kProfileRoundedCornerRadius = 2; 76const int kUserIconSize = 27; 77const int kUserIconLargeSize = 32; 78const int kUserIconLargeCornerRadius = 2; 79const int kUserLabelToIconPadding = 5; 80 81// When a hover border is used, it is starting this many pixels before the icon 82// position. 83const int kTrayUserTileHoverBorderInset = 10; 84 85// The border color of the user button. 86const SkColor kBorderColor = 0xffdcdcdc; 87 88// The invisible word joiner character, used as a marker to indicate the start 89// and end of the user's display name in the public account user card's text. 90const base::char16 kDisplayNameMark[] = { 0x2060, 0 }; 91 92const int kPublicAccountLogoutButtonBorderImagesNormal[] = { 93 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 94 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, 95 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, 96 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 97 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, 98 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, 99 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 100 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, 101 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, 102}; 103 104const int kPublicAccountLogoutButtonBorderImagesHovered[] = { 105 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 106 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 107 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 108 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 109 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND, 110 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 111 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 112 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 113 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, 114}; 115 116// Offsetting the popup message relative to the tray menu. 117const int kPopupMessageOffset = 25; 118 119// Switch to a user with the given |user_index|. 120void SwitchUser(ash::MultiProfileIndex user_index) { 121 // Do not switch users when the log screen is presented. 122 if (ash::Shell::GetInstance()->session_state_delegate()-> 123 IsUserSessionBlocked()) 124 return; 125 126 DCHECK(user_index > 0); 127 ash::SessionStateDelegate* delegate = 128 ash::Shell::GetInstance()->session_state_delegate(); 129 ash::MultiProfileUMA::RecordSwitchActiveUser( 130 ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY); 131 delegate->SwitchActiveUser(delegate->GetUserID(user_index)); 132} 133 134} // namespace 135 136namespace ash { 137namespace tray { 138 139// A custom image view with rounded edges. 140class RoundedImageView : public views::View { 141 public: 142 // Constructs a new rounded image view with rounded corners of radius 143 // |corner_radius|. If |active_user| is set, the icon will be drawn in 144 // full colors - otherwise it will fade into the background. 145 RoundedImageView(int corner_radius, bool active_user); 146 virtual ~RoundedImageView(); 147 148 // Set the image that should be displayed. The image contents is copied to the 149 // receiver's image. 150 void SetImage(const gfx::ImageSkia& img, const gfx::Size& size); 151 152 // Set the radii of the corners independently. 153 void SetCornerRadii(int top_left, 154 int top_right, 155 int bottom_right, 156 int bottom_left); 157 158 private: 159 // Overridden from views::View. 160 virtual gfx::Size GetPreferredSize() OVERRIDE; 161 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; 162 163 gfx::ImageSkia image_; 164 gfx::ImageSkia resized_; 165 gfx::Size image_size_; 166 int corner_radius_[4]; 167 168 // True if the given user is the active user and the icon should get 169 // painted as active. 170 bool active_user_; 171 172 DISALLOW_COPY_AND_ASSIGN(RoundedImageView); 173}; 174 175// The user details shown in public account mode. This is essentially a label 176// but with custom painting code as the text is styled with multiple colors and 177// contains a link. 178class PublicAccountUserDetails : public views::View, 179 public views::LinkListener { 180 public: 181 PublicAccountUserDetails(SystemTrayItem* owner, int used_width); 182 virtual ~PublicAccountUserDetails(); 183 184 private: 185 // Overridden from views::View. 186 virtual void Layout() OVERRIDE; 187 virtual gfx::Size GetPreferredSize() OVERRIDE; 188 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; 189 190 // Overridden from views::LinkListener. 191 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; 192 193 // Calculate a preferred size that ensures the label text and the following 194 // link do not wrap over more than three lines in total for aesthetic reasons 195 // if possible. 196 void CalculatePreferredSize(SystemTrayItem* owner, int used_width); 197 198 base::string16 text_; 199 views::Link* learn_more_; 200 gfx::Size preferred_size_; 201 ScopedVector<gfx::RenderText> lines_; 202 203 DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails); 204}; 205 206// The button which holds the user card in case of multi profile. 207class UserCard : public views::CustomButton { 208 public: 209 UserCard(views::ButtonListener* listener, bool active_user); 210 virtual ~UserCard(); 211 212 // Called when the border should remain even in the non highlighted state. 213 void ForceBorderVisible(bool show); 214 215 // Overridden from views::View 216 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; 217 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; 218 219 // Check if the item is hovered. 220 bool is_hovered_for_test() {return button_hovered_; } 221 222 private: 223 // Change the hover/active state of the "button" when the status changes. 224 void ShowActive(); 225 226 // True if this is the active user. 227 bool is_active_user_; 228 229 // True if button is hovered. 230 bool button_hovered_; 231 232 // True if the border should be visible. 233 bool show_border_; 234 235 DISALLOW_COPY_AND_ASSIGN(UserCard); 236}; 237 238class UserViewMouseWatcherHost : public views::MouseWatcherHost { 239public: 240 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area) 241 : screen_area_(screen_area) {} 242 virtual ~UserViewMouseWatcherHost() {} 243 244 // Implementation of MouseWatcherHost. 245 virtual bool Contains(const gfx::Point& screen_point, 246 views::MouseWatcherHost::MouseEventType type) OVERRIDE { 247 return screen_area_.Contains(screen_point); 248 } 249 250private: 251 gfx::Rect screen_area_; 252 253 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost); 254}; 255 256// The view of a user item. 257class UserView : public views::View, 258 public views::ButtonListener, 259 public views::MouseWatcherListener { 260 public: 261 UserView(SystemTrayItem* owner, 262 ash::user::LoginStatus login, 263 MultiProfileIndex index); 264 virtual ~UserView(); 265 266 // Overridden from MouseWatcherListener: 267 virtual void MouseMovedOutOfHost() OVERRIDE; 268 269 TrayUser::TestState GetStateForTest() const; 270 gfx::Rect GetBoundsInScreenOfUserButtonForTest(); 271 272 private: 273 // Overridden from views::View. 274 virtual gfx::Size GetPreferredSize() OVERRIDE; 275 virtual int GetHeightForWidth(int width) OVERRIDE; 276 virtual void Layout() OVERRIDE; 277 278 // Overridden from views::ButtonListener. 279 virtual void ButtonPressed(views::Button* sender, 280 const ui::Event& event) OVERRIDE; 281 282 void AddLogoutButton(user::LoginStatus login); 283 void AddUserCard(SystemTrayItem* owner, user::LoginStatus login); 284 285 // Create a user icon representation for the user card. 286 views::View* CreateIconForUserCard(user::LoginStatus login); 287 288 // Create the additional user card content for the retail logged in mode. 289 void AddLoggedInRetailModeUserCardContent(); 290 291 // Create the additional user card content for the public mode. 292 void AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner); 293 294 // Create the menu option to add another user. If |disabled| is set the user 295 // cannot actively click on the item. 296 void ToggleAddUserMenuOption(); 297 298 // Returns true when multi profile is supported. 299 bool SupportsMultiProfile(); 300 301 MultiProfileIndex multiprofile_index_; 302 // The view of the user card. 303 views::View* user_card_view_; 304 305 // This is the owner system tray item of this view. 306 SystemTrayItem* owner_; 307 308 // True if |user_card_view_| is a |UserView| - otherwise it is only a 309 // |views::View|. 310 bool is_user_card_; 311 views::View* logout_button_; 312 scoped_ptr<PopupMessage> popup_message_; 313 scoped_ptr<views::Widget> add_menu_option_; 314 315 // True when the add user panel is visible but not activatable. 316 bool add_user_visible_but_disabled_; 317 318 // The mouse watcher which takes care of out of window hover events. 319 scoped_ptr<views::MouseWatcher> mouse_watcher_; 320 321 DISALLOW_COPY_AND_ASSIGN(UserView); 322}; 323 324// The menu item view which gets shown when the user clicks in multi profile 325// mode onto the user item. 326class AddUserView : public views::CustomButton, 327 public views::ButtonListener { 328 public: 329 // The |owner| is the view for which this view gets created. The |listener| 330 // will get notified when this item gets clicked. 331 AddUserView(UserCard* owner, views::ButtonListener* listener); 332 virtual ~AddUserView(); 333 334 // Get the anchor view for a message. 335 views::View* anchor() { return anchor_; } 336 337 // Overridden from views::ButtonListener. 338 virtual void ButtonPressed(views::Button* sender, 339 const ui::Event& event) OVERRIDE; 340 341 private: 342 // Overridden from views::View. 343 virtual gfx::Size GetPreferredSize() OVERRIDE; 344 virtual int GetHeightForWidth(int width) OVERRIDE; 345 virtual void Layout() OVERRIDE; 346 347 // Create the additional client content for this item. 348 void AddContent(); 349 350 // This is the content we create and show. 351 views::View* add_user_; 352 353 // This listener will get informed when someone clicks on this button. 354 views::ButtonListener* listener_; 355 356 // This is the owner view of this item. 357 UserCard* owner_; 358 359 // The anchor view for targetted bubble messages. 360 views::View* anchor_; 361 362 DISALLOW_COPY_AND_ASSIGN(AddUserView); 363}; 364 365RoundedImageView::RoundedImageView(int corner_radius, bool active_user) 366 : active_user_(active_user) { 367 for (int i = 0; i < 4; ++i) 368 corner_radius_[i] = corner_radius; 369} 370 371RoundedImageView::~RoundedImageView() {} 372 373void RoundedImageView::SetImage(const gfx::ImageSkia& img, 374 const gfx::Size& size) { 375 image_ = img; 376 image_size_ = size; 377 378 // Try to get the best image quality for the avatar. 379 resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_, 380 skia::ImageOperations::RESIZE_BEST, size); 381 if (GetWidget() && visible()) { 382 PreferredSizeChanged(); 383 SchedulePaint(); 384 } 385} 386 387void RoundedImageView::SetCornerRadii(int top_left, 388 int top_right, 389 int bottom_right, 390 int bottom_left) { 391 corner_radius_[0] = top_left; 392 corner_radius_[1] = top_right; 393 corner_radius_[2] = bottom_right; 394 corner_radius_[3] = bottom_left; 395} 396 397gfx::Size RoundedImageView::GetPreferredSize() { 398 return gfx::Size(image_size_.width() + GetInsets().width(), 399 image_size_.height() + GetInsets().height()); 400} 401 402void RoundedImageView::OnPaint(gfx::Canvas* canvas) { 403 View::OnPaint(canvas); 404 gfx::Rect image_bounds(size()); 405 image_bounds.ClampToCenteredSize(GetPreferredSize()); 406 image_bounds.Inset(GetInsets()); 407 const SkScalar kRadius[8] = { 408 SkIntToScalar(corner_radius_[0]), 409 SkIntToScalar(corner_radius_[0]), 410 SkIntToScalar(corner_radius_[1]), 411 SkIntToScalar(corner_radius_[1]), 412 SkIntToScalar(corner_radius_[2]), 413 SkIntToScalar(corner_radius_[2]), 414 SkIntToScalar(corner_radius_[3]), 415 SkIntToScalar(corner_radius_[3]) 416 }; 417 SkPath path; 418 path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius); 419 SkPaint paint; 420 paint.setAntiAlias(true); 421 paint.setXfermodeMode(active_user_ ? SkXfermode::kSrcOver_Mode : 422 SkXfermode::kLuminosity_Mode); 423 canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(), 424 path, paint); 425} 426 427PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner, 428 int used_width) 429 : learn_more_(NULL) { 430 const int inner_padding = 431 kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems; 432 const bool rtl = base::i18n::IsRTL(); 433 SetBorder(views::Border::CreateEmptyBorder(kUserDetailsVerticalPadding, 434 rtl ? 0 : inner_padding, 435 kUserDetailsVerticalPadding, 436 rtl ? inner_padding : 0)); 437 438 // Retrieve the user's display name and wrap it with markers. 439 // Note that since this is a public account it always has to be the primary 440 // user. 441 base::string16 display_name = 442 Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0); 443 base::RemoveChars(display_name, kDisplayNameMark, &display_name); 444 display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0]; 445 // Retrieve the domain managing the device and wrap it with markers. 446 base::string16 domain = base::UTF8ToUTF16( 447 Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain()); 448 base::RemoveChars(domain, kDisplayNameMark, &domain); 449 base::i18n::WrapStringWithLTRFormatting(&domain); 450 // Retrieve the label text, inserting the display name and domain. 451 text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL, 452 display_name, domain); 453 454 learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE)); 455 learn_more_->SetUnderline(false); 456 learn_more_->set_listener(this); 457 AddChildView(learn_more_); 458 459 CalculatePreferredSize(owner, used_width); 460} 461 462PublicAccountUserDetails::~PublicAccountUserDetails() {} 463 464void PublicAccountUserDetails::Layout() { 465 lines_.clear(); 466 const gfx::Rect contents_area = GetContentsBounds(); 467 if (contents_area.IsEmpty()) 468 return; 469 470 // Word-wrap the label text. 471 const gfx::FontList font_list; 472 std::vector<base::string16> lines; 473 gfx::ElideRectangleText(text_, font_list, contents_area.width(), 474 contents_area.height(), gfx::ELIDE_LONG_WORDS, 475 &lines); 476 // Loop through the lines, creating a renderer for each. 477 gfx::Point position = contents_area.origin(); 478 gfx::Range display_name(gfx::Range::InvalidRange()); 479 for (std::vector<base::string16>::const_iterator it = lines.begin(); 480 it != lines.end(); ++it) { 481 gfx::RenderText* line = gfx::RenderText::CreateInstance(); 482 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI); 483 line->SetText(*it); 484 const gfx::Size size(contents_area.width(), line->GetStringSize().height()); 485 line->SetDisplayRect(gfx::Rect(position, size)); 486 position.set_y(position.y() + size.height()); 487 488 // Set the default text color for the line. 489 line->SetColor(kPublicAccountUserCardTextColor); 490 491 // If a range of the line contains the user's display name, apply a custom 492 // text color to it. 493 if (display_name.is_empty()) 494 display_name.set_start(it->find(kDisplayNameMark)); 495 if (!display_name.is_empty()) { 496 display_name.set_end( 497 it->find(kDisplayNameMark, display_name.start() + 1)); 498 gfx::Range line_range(0, it->size()); 499 line->ApplyColor(kPublicAccountUserCardNameColor, 500 display_name.Intersect(line_range)); 501 // Update the range for the next line. 502 if (display_name.end() >= line_range.end()) 503 display_name.set_start(0); 504 else 505 display_name = gfx::Range::InvalidRange(); 506 } 507 508 lines_.push_back(line); 509 } 510 511 // Position the link after the label text, separated by a space. If it does 512 // not fit onto the last line of the text, wrap the link onto its own line. 513 const gfx::Size last_line_size = lines_.back()->GetStringSize(); 514 const int space_width = 515 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list); 516 const gfx::Size link_size = learn_more_->GetPreferredSize(); 517 if (contents_area.width() - last_line_size.width() >= 518 space_width + link_size.width()) { 519 position.set_x(position.x() + last_line_size.width() + space_width); 520 position.set_y(position.y() - last_line_size.height()); 521 } 522 position.set_y(position.y() - learn_more_->GetInsets().top()); 523 gfx::Rect learn_more_bounds(position, link_size); 524 learn_more_bounds.Intersect(contents_area); 525 if (base::i18n::IsRTL()) { 526 const gfx::Insets insets = GetInsets(); 527 learn_more_bounds.Offset(insets.right() - insets.left(), 0); 528 } 529 learn_more_->SetBoundsRect(learn_more_bounds); 530} 531 532gfx::Size PublicAccountUserDetails::GetPreferredSize() { 533 return preferred_size_; 534} 535 536void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) { 537 for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin(); 538 it != lines_.end(); ++it) { 539 (*it)->Draw(canvas); 540 } 541 views::View::OnPaint(canvas); 542} 543 544void PublicAccountUserDetails::LinkClicked(views::Link* source, 545 int event_flags) { 546 DCHECK_EQ(source, learn_more_); 547 Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo(); 548} 549 550void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner, 551 int used_width) { 552 const gfx::FontList font_list; 553 const gfx::Size link_size = learn_more_->GetPreferredSize(); 554 const int space_width = 555 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list); 556 const gfx::Insets insets = GetInsets(); 557 views::TrayBubbleView* bubble_view = 558 owner->system_tray()->GetSystemBubble()->bubble_view(); 559 int min_width = std::max( 560 link_size.width(), 561 bubble_view->GetPreferredSize().width() - (used_width + insets.width())); 562 int max_width = std::min( 563 gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(), 564 bubble_view->GetMaximumSize().width() - (used_width + insets.width())); 565 // Do a binary search for the minimum width that ensures no more than three 566 // lines are needed. The lower bound is the minimum of the current bubble 567 // width and the width of the link (as no wrapping is permitted inside the 568 // link). The upper bound is the maximum of the largest allowed bubble width 569 // and the sum of the label text and link widths when put on a single line. 570 std::vector<base::string16> lines; 571 while (min_width < max_width) { 572 lines.clear(); 573 const int width = (min_width + max_width) / 2; 574 const bool too_narrow = 575 gfx::ElideRectangleText(text_, font_list, width, INT_MAX, 576 gfx::TRUNCATE_LONG_WORDS, &lines) != 0; 577 int line_count = lines.size(); 578 if (!too_narrow && line_count == 3 && 579 width - gfx::GetStringWidth(lines.back(), font_list) <= 580 space_width + link_size.width()) 581 ++line_count; 582 if (too_narrow || line_count > 3) 583 min_width = width + 1; 584 else 585 max_width = width; 586 } 587 588 // Calculate the corresponding height and set the preferred size. 589 lines.clear(); 590 gfx::ElideRectangleText( 591 text_, font_list, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines); 592 int line_count = lines.size(); 593 if (min_width - gfx::GetStringWidth(lines.back(), font_list) <= 594 space_width + link_size.width()) { 595 ++line_count; 596 } 597 const int line_height = font_list.GetHeight(); 598 const int link_extra_height = std::max( 599 link_size.height() - learn_more_->GetInsets().top() - line_height, 0); 600 preferred_size_ = gfx::Size( 601 min_width + insets.width(), 602 line_count * line_height + link_extra_height + insets.height()); 603 604 bubble_view->SetWidth(preferred_size_.width() + used_width); 605} 606 607UserCard::UserCard(views::ButtonListener* listener, bool active_user) 608 : CustomButton(listener), 609 is_active_user_(active_user), 610 button_hovered_(false), 611 show_border_(false) { 612 if (is_active_user_) { 613 set_background( 614 views::Background::CreateSolidBackground(kBackgroundColor)); 615 ShowActive(); 616 } 617} 618 619UserCard::~UserCard() {} 620 621void UserCard::ForceBorderVisible(bool show) { 622 show_border_ = show; 623 ShowActive(); 624} 625 626void UserCard::OnMouseEntered(const ui::MouseEvent& event) { 627 if (is_active_user_) { 628 button_hovered_ = true; 629 background()->SetNativeControlColor(kHoverBackgroundColor); 630 ShowActive(); 631 } 632} 633 634void UserCard::OnMouseExited(const ui::MouseEvent& event) { 635 if (is_active_user_) { 636 button_hovered_ = false; 637 background()->SetNativeControlColor(kBackgroundColor); 638 ShowActive(); 639 } 640} 641 642void UserCard::ShowActive() { 643 int width = button_hovered_ || show_border_ ? 1 : 0; 644 SetBorder(views::Border::CreateSolidSidedBorder( 645 width, width, width, 1, kBorderColor)); 646 SchedulePaint(); 647} 648 649UserView::UserView(SystemTrayItem* owner, 650 user::LoginStatus login, 651 MultiProfileIndex index) 652 : multiprofile_index_(index), 653 user_card_view_(NULL), 654 owner_(owner), 655 is_user_card_(false), 656 logout_button_(NULL), 657 add_user_visible_but_disabled_(false) { 658 CHECK_NE(user::LOGGED_IN_NONE, login); 659 if (!index) { 660 // Only the logged in user will have a background. All other users will have 661 // to allow the TrayPopupContainer highlighting the menu line. 662 set_background(views::Background::CreateSolidBackground( 663 login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor : 664 kBackgroundColor)); 665 } 666 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 667 kTrayPopupPaddingBetweenItems)); 668 // The logout button must be added before the user card so that the user card 669 // can correctly calculate the remaining available width. 670 // Note that only the current multiprofile user gets a button. 671 if (!multiprofile_index_) 672 AddLogoutButton(login); 673 AddUserCard(owner, login); 674} 675 676UserView::~UserView() {} 677 678void UserView::MouseMovedOutOfHost() { 679 popup_message_.reset(); 680 mouse_watcher_.reset(); 681 add_menu_option_.reset(); 682} 683 684TrayUser::TestState UserView::GetStateForTest() const { 685 if (add_menu_option_.get()) { 686 return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED : 687 TrayUser::ACTIVE; 688 } 689 690 if (!is_user_card_) 691 return TrayUser::SHOWN; 692 693 return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ? 694 TrayUser::HOVERED : TrayUser::SHOWN; 695} 696 697gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() { 698 DCHECK(user_card_view_); 699 return user_card_view_->GetBoundsInScreen(); 700} 701 702gfx::Size UserView::GetPreferredSize() { 703 gfx::Size size = views::View::GetPreferredSize(); 704 // Only the active user panel will be forced to a certain height. 705 if (!multiprofile_index_) { 706 size.set_height(std::max(size.height(), 707 kTrayPopupItemHeight + GetInsets().height())); 708 } 709 return size; 710} 711 712int UserView::GetHeightForWidth(int width) { 713 return GetPreferredSize().height(); 714} 715 716void UserView::Layout() { 717 gfx::Rect contents_area(GetContentsBounds()); 718 if (user_card_view_ && logout_button_) { 719 // Give the logout button the space it requests. 720 gfx::Rect logout_area = contents_area; 721 logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize()); 722 logout_area.set_x(contents_area.right() - logout_area.width()); 723 724 // Give the remaining space to the user card. 725 gfx::Rect user_card_area = contents_area; 726 int remaining_width = contents_area.width() - logout_area.width(); 727 if (SupportsMultiProfile()) { 728 // In multiprofile case |user_card_view_| and |logout_button_| have to 729 // have the same height. 730 int y = std::min(user_card_area.y(), logout_area.y()); 731 int height = std::max(user_card_area.height(), logout_area.height()); 732 logout_area.set_y(y); 733 logout_area.set_height(height); 734 user_card_area.set_y(y); 735 user_card_area.set_height(height); 736 737 // In multiprofile mode we have also to increase the size of the card by 738 // the size of the border to make it overlap with the logout button. 739 user_card_area.set_width(std::max(0, remaining_width + 1)); 740 741 // To make the logout button symmetrical with the user card we also make 742 // the button longer by the same size the hover area in front of the icon 743 // got inset. 744 logout_area.set_width(logout_area.width() + 745 kTrayUserTileHoverBorderInset); 746 } else { 747 // In all other modes we have to make sure that there is enough spacing 748 // between the two. 749 remaining_width -= kTrayPopupPaddingBetweenItems; 750 } 751 user_card_area.set_width(remaining_width); 752 user_card_view_->SetBoundsRect(user_card_area); 753 logout_button_->SetBoundsRect(logout_area); 754 } else if (user_card_view_) { 755 user_card_view_->SetBoundsRect(contents_area); 756 } else if (logout_button_) { 757 logout_button_->SetBoundsRect(contents_area); 758 } 759} 760 761void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) { 762 if (sender == logout_button_) { 763 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 764 ash::UMA_STATUS_AREA_SIGN_OUT); 765 Shell::GetInstance()->system_tray_delegate()->SignOut(); 766 } else if (sender == user_card_view_ && SupportsMultiProfile()) { 767 if (!multiprofile_index_) { 768 ToggleAddUserMenuOption(); 769 } else { 770 SwitchUser(multiprofile_index_); 771 // Since the user list is about to change the system menu should get 772 // closed. 773 owner_->system_tray()->CloseSystemBubble(); 774 } 775 } else if (add_menu_option_.get() && 776 sender == add_menu_option_->GetContentsView()) { 777 // Let the user add another account to the session. 778 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY); 779 Shell::GetInstance()->system_tray_delegate()->ShowUserLogin(); 780 owner_->system_tray()->CloseSystemBubble(); 781 } else { 782 NOTREACHED(); 783 } 784} 785 786void UserView::AddLogoutButton(user::LoginStatus login) { 787 const base::string16 title = user::GetLocalizedSignOutStringForStatus(login, 788 true); 789 TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title); 790 logout_button->SetAccessibleName(title); 791 logout_button_ = logout_button; 792 // In public account mode, the logout button border has a custom color. 793 if (login == user::LOGGED_IN_PUBLIC) { 794 scoped_ptr<TrayPopupLabelButtonBorder> border( 795 new TrayPopupLabelButtonBorder()); 796 border->SetPainter(false, views::Button::STATE_NORMAL, 797 views::Painter::CreateImageGridPainter( 798 kPublicAccountLogoutButtonBorderImagesNormal)); 799 border->SetPainter(false, views::Button::STATE_HOVERED, 800 views::Painter::CreateImageGridPainter( 801 kPublicAccountLogoutButtonBorderImagesHovered)); 802 border->SetPainter(false, views::Button::STATE_PRESSED, 803 views::Painter::CreateImageGridPainter( 804 kPublicAccountLogoutButtonBorderImagesHovered)); 805 logout_button_->SetBorder(border.PassAs<views::Border>()); 806 } 807 AddChildView(logout_button_); 808} 809 810void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) { 811 // Add padding around the panel. 812 SetBorder(views::Border::CreateEmptyBorder(kUserCardVerticalPadding, 813 kTrayPopupPaddingHorizontal, 814 kUserCardVerticalPadding, 815 kTrayPopupPaddingHorizontal)); 816 817 if (SupportsMultiProfile() && login != user::LOGGED_IN_RETAIL_MODE) { 818 user_card_view_ = new UserCard(this, multiprofile_index_ == 0); 819 is_user_card_ = true; 820 } else { 821 user_card_view_ = new views::View(); 822 is_user_card_ = false; 823 } 824 825 user_card_view_->SetLayoutManager(new views::BoxLayout( 826 views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems)); 827 AddChildViewAt(user_card_view_, 0); 828 829 if (login == user::LOGGED_IN_RETAIL_MODE) { 830 AddLoggedInRetailModeUserCardContent(); 831 return; 832 } 833 834 // The entire user card should trigger hover (the inner items get disabled). 835 user_card_view_->SetEnabled(true); 836 user_card_view_->set_notify_enter_exit_on_child(true); 837 838 if (login == user::LOGGED_IN_PUBLIC) { 839 AddLoggedInPublicModeUserCardContent(owner); 840 return; 841 } 842 843 views::View* icon = CreateIconForUserCard(login); 844 user_card_view_->AddChildView(icon); 845 846 // To allow the border to start before the icon, reduce the size before and 847 // add an inset to the icon to get the spacing. 848 if (multiprofile_index_ == 0 && SupportsMultiProfile()) { 849 icon->SetBorder(views::Border::CreateEmptyBorder( 850 0, kTrayUserTileHoverBorderInset, 0, 0)); 851 SetBorder(views::Border::CreateEmptyBorder( 852 kUserCardVerticalPadding, 853 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset, 854 kUserCardVerticalPadding, 855 kTrayPopupPaddingHorizontal)); 856 } 857 SessionStateDelegate* delegate = 858 Shell::GetInstance()->session_state_delegate(); 859 views::Label* username = NULL; 860 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 861 if (!multiprofile_index_) { 862 base::string16 user_name_string = 863 login == user::LOGGED_IN_GUEST ? 864 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) : 865 delegate->GetUserDisplayName(multiprofile_index_); 866 if (!user_name_string.empty()) { 867 username = new views::Label(user_name_string); 868 username->SetHorizontalAlignment(gfx::ALIGN_LEFT); 869 } 870 } 871 872 views::Label* additional = NULL; 873 if (login != user::LOGGED_IN_GUEST) { 874 base::string16 user_email_string = 875 login == user::LOGGED_IN_LOCALLY_MANAGED ? 876 bundle.GetLocalizedString( 877 IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) : 878 base::UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_)); 879 if (!user_email_string.empty()) { 880 additional = new views::Label(user_email_string); 881 additional->SetFontList( 882 bundle.GetFontList(ui::ResourceBundle::SmallFont)); 883 additional->SetHorizontalAlignment(gfx::ALIGN_LEFT); 884 } 885 } 886 887 // Adjust text properties dependent on if it is an active or inactive user. 888 if (multiprofile_index_) { 889 // Fade the text of non active users to 50%. 890 SkColor text_color = additional->enabled_color(); 891 text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2); 892 if (additional) 893 additional->SetDisabledColor(text_color); 894 if (username) 895 username->SetDisabledColor(text_color); 896 } 897 898 if (additional && username) { 899 views::View* details = new views::View; 900 details->SetLayoutManager(new views::BoxLayout( 901 views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0)); 902 details->AddChildView(username); 903 details->AddChildView(additional); 904 user_card_view_->AddChildView(details); 905 } else { 906 if (username) 907 user_card_view_->AddChildView(username); 908 if (additional) 909 user_card_view_->AddChildView(additional); 910 } 911} 912 913views::View* UserView::CreateIconForUserCard(user::LoginStatus login) { 914 RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius, 915 multiprofile_index_ == 0); 916 icon->SetEnabled(false); 917 if (login == user::LOGGED_IN_GUEST) { 918 icon->SetImage(*ui::ResourceBundle::GetSharedInstance(). 919 GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(), 920 gfx::Size(kUserIconSize, kUserIconSize)); 921 } else { 922 SessionStateDelegate* delegate = 923 Shell::GetInstance()->session_state_delegate(); 924 content::BrowserContext* context = delegate->GetBrowserContextByIndex( 925 multiprofile_index_); 926 icon->SetImage(delegate->GetUserImage(context), 927 gfx::Size(kUserIconSize, kUserIconSize)); 928 } 929 return icon; 930} 931 932void UserView::AddLoggedInRetailModeUserCardContent() { 933 views::Label* details = new views::Label; 934 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 935 details->SetText( 936 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL)); 937 details->SetBorder(views::Border::CreateEmptyBorder(0, 4, 0, 1)); 938 details->SetHorizontalAlignment(gfx::ALIGN_LEFT); 939 user_card_view_->AddChildView(details); 940} 941 942void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) { 943 user_card_view_->AddChildView(CreateIconForUserCard(user::LOGGED_IN_PUBLIC)); 944 user_card_view_->AddChildView(new PublicAccountUserDetails( 945 owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems)); 946} 947 948void UserView::ToggleAddUserMenuOption() { 949 if (add_menu_option_.get()) { 950 popup_message_.reset(); 951 mouse_watcher_.reset(); 952 add_menu_option_.reset(); 953 return; 954 } 955 956 // Note: We do not need to install a global event handler to delete this 957 // item since it will destroyed automatically before the menu / user menu item 958 // gets destroyed.. 959 const SessionStateDelegate* session_state_delegate = 960 Shell::GetInstance()->session_state_delegate(); 961 add_user_visible_but_disabled_ = 962 session_state_delegate->NumberOfLoggedInUsers() >= 963 session_state_delegate->GetMaximumNumberOfLoggedInUsers(); 964 add_menu_option_.reset(new views::Widget); 965 views::Widget::InitParams params; 966 params.type = views::Widget::InitParams::TYPE_TOOLTIP; 967 params.keep_on_top = true; 968 params.context = this->GetWidget()->GetNativeWindow(); 969 params.accept_events = true; 970 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 971 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 972 add_menu_option_->Init(params); 973 add_menu_option_->SetOpacity(0xFF); 974 add_menu_option_->GetNativeWindow()->set_owned_by_parent(false); 975 SetShadowType(add_menu_option_->GetNativeView(), 976 wm::SHADOW_TYPE_NONE); 977 978 // Position it below our user card. 979 gfx::Rect bounds = user_card_view_->GetBoundsInScreen(); 980 bounds.set_y(bounds.y() + bounds.height()); 981 add_menu_option_->SetBounds(bounds); 982 983 // Show the content. 984 AddUserView* add_user_view = new AddUserView( 985 static_cast<UserCard*>(user_card_view_), this); 986 add_menu_option_->SetContentsView(add_user_view); 987 add_menu_option_->SetAlwaysOnTop(true); 988 add_menu_option_->Show(); 989 if (add_user_visible_but_disabled_) { 990 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 991 popup_message_.reset(new PopupMessage( 992 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER), 993 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER), 994 PopupMessage::ICON_WARNING, 995 add_user_view->anchor(), 996 views::BubbleBorder::TOP_LEFT, 997 gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0), 998 2 * kPopupMessageOffset)); 999 } 1000 // Find the screen area which encloses both elements and sets then a mouse 1001 // watcher which will close the "menu". 1002 gfx::Rect area = user_card_view_->GetBoundsInScreen(); 1003 area.set_height(2 * area.height()); 1004 mouse_watcher_.reset(new views::MouseWatcher( 1005 new UserViewMouseWatcherHost(area), 1006 this)); 1007 mouse_watcher_->Start(); 1008} 1009 1010bool UserView::SupportsMultiProfile() { 1011 // We do not want to see any multi profile additions to a user view when the 1012 // log in screen is shown. 1013 return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() && 1014 !Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked(); 1015} 1016 1017AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener) 1018 : CustomButton(listener), 1019 add_user_(NULL), 1020 listener_(listener), 1021 owner_(owner), 1022 anchor_(NULL) { 1023 AddContent(); 1024 owner_->ForceBorderVisible(true); 1025} 1026 1027AddUserView::~AddUserView() { 1028 owner_->ForceBorderVisible(false); 1029} 1030 1031gfx::Size AddUserView::GetPreferredSize() { 1032 return owner_->bounds().size(); 1033} 1034 1035int AddUserView::GetHeightForWidth(int width) { 1036 return owner_->bounds().size().height(); 1037} 1038 1039void AddUserView::Layout() { 1040 gfx::Rect contents_area(GetContentsBounds()); 1041 add_user_->SetBoundsRect(contents_area); 1042} 1043 1044void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) { 1045 if (add_user_ == sender) 1046 listener_->ButtonPressed(this, event); 1047 else 1048 NOTREACHED(); 1049} 1050 1051void AddUserView::AddContent() { 1052 set_notify_enter_exit_on_child(true); 1053 1054 const SessionStateDelegate* delegate = 1055 Shell::GetInstance()->session_state_delegate(); 1056 bool enable = delegate->NumberOfLoggedInUsers() < 1057 delegate->GetMaximumNumberOfLoggedInUsers(); 1058 1059 SetLayoutManager(new views::FillLayout()); 1060 set_background(views::Background::CreateSolidBackground(kBackgroundColor)); 1061 1062 // Add padding around the panel. 1063 SetBorder(views::Border::CreateSolidBorder(1, kBorderColor)); 1064 1065 add_user_ = new UserCard(this, enable); 1066 add_user_->SetBorder(views::Border::CreateEmptyBorder( 1067 kUserCardVerticalPadding, 1068 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset, 1069 kUserCardVerticalPadding, 1070 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset)); 1071 1072 add_user_->SetLayoutManager(new views::BoxLayout( 1073 views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems)); 1074 AddChildViewAt(add_user_, 0); 1075 1076 // Add the [+] icon which is also the anchor for messages. 1077 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 1078 RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius, 1079 true); 1080 anchor_ = icon; 1081 icon->SetImage(*ui::ResourceBundle::GetSharedInstance(). 1082 GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(), 1083 gfx::Size(kUserIconSize, kUserIconSize)); 1084 add_user_->AddChildView(icon); 1085 1086 // Add the command text. 1087 views::Label* command_label = new views::Label( 1088 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT)); 1089 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1090 add_user_->AddChildView(command_label); 1091} 1092 1093} // namespace tray 1094 1095TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index) 1096 : SystemTrayItem(system_tray), 1097 multiprofile_index_(index), 1098 user_(NULL), 1099 layout_view_(NULL), 1100 avatar_(NULL), 1101 label_(NULL) { 1102 Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this); 1103} 1104 1105TrayUser::~TrayUser() { 1106 Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this); 1107} 1108 1109TrayUser::TestState TrayUser::GetStateForTest() const { 1110 if (!user_) 1111 return HIDDEN; 1112 return user_->GetStateForTest(); 1113} 1114 1115gfx::Size TrayUser::GetLayoutSizeForTest() const { 1116 if (!layout_view_) { 1117 return gfx::Size(0, 0); 1118 } else { 1119 return layout_view_->size(); 1120 } 1121} 1122 1123gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const { 1124 DCHECK(user_); 1125 return user_->GetBoundsInScreenOfUserButtonForTest(); 1126} 1127 1128void TrayUser::UpdateAfterLoginStatusChangeForTest(user::LoginStatus status) { 1129 UpdateAfterLoginStatusChange(status); 1130} 1131 1132views::View* TrayUser::CreateTrayView(user::LoginStatus status) { 1133 CHECK(layout_view_ == NULL); 1134 1135 layout_view_ = new views::View(); 1136 layout_view_->SetLayoutManager( 1137 new views::BoxLayout(views::BoxLayout::kHorizontal, 1138 0, 0, kUserLabelToIconPadding)); 1139 UpdateAfterLoginStatusChange(status); 1140 return layout_view_; 1141} 1142 1143views::View* TrayUser::CreateDefaultView(user::LoginStatus status) { 1144 if (status == user::LOGGED_IN_NONE) 1145 return NULL; 1146 const SessionStateDelegate* session_state_delegate = 1147 Shell::GetInstance()->session_state_delegate(); 1148 1149 // If the screen is locked show only the currently active user. 1150 if (multiprofile_index_ && session_state_delegate->IsUserSessionBlocked()) 1151 return NULL; 1152 1153 CHECK(user_ == NULL); 1154 1155 int logged_in_users = session_state_delegate->NumberOfLoggedInUsers(); 1156 1157 // Do not show more UserView's then there are logged in users. 1158 if (multiprofile_index_ >= logged_in_users) 1159 return NULL; 1160 1161 user_ = new tray::UserView(this, status, multiprofile_index_); 1162 return user_; 1163} 1164 1165views::View* TrayUser::CreateDetailedView(user::LoginStatus status) { 1166 return NULL; 1167} 1168 1169void TrayUser::DestroyTrayView() { 1170 layout_view_ = NULL; 1171 avatar_ = NULL; 1172 label_ = NULL; 1173} 1174 1175void TrayUser::DestroyDefaultView() { 1176 user_ = NULL; 1177} 1178 1179void TrayUser::DestroyDetailedView() { 1180} 1181 1182void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) { 1183 // Only the active user is represented in the tray. 1184 if (!layout_view_) 1185 return; 1186 if (GetTrayIndex() > 0) 1187 return; 1188 bool need_label = false; 1189 bool need_avatar = false; 1190 switch (status) { 1191 case user::LOGGED_IN_LOCKED: 1192 case user::LOGGED_IN_USER: 1193 case user::LOGGED_IN_OWNER: 1194 case user::LOGGED_IN_PUBLIC: 1195 need_avatar = true; 1196 break; 1197 case user::LOGGED_IN_LOCALLY_MANAGED: 1198 need_avatar = true; 1199 need_label = true; 1200 break; 1201 case user::LOGGED_IN_GUEST: 1202 need_label = true; 1203 break; 1204 case user::LOGGED_IN_RETAIL_MODE: 1205 case user::LOGGED_IN_KIOSK_APP: 1206 case user::LOGGED_IN_NONE: 1207 break; 1208 } 1209 1210 if ((need_avatar != (avatar_ != NULL)) || 1211 (need_label != (label_ != NULL))) { 1212 layout_view_->RemoveAllChildViews(true); 1213 if (need_label) { 1214 label_ = new views::Label; 1215 SetupLabelForTray(label_); 1216 layout_view_->AddChildView(label_); 1217 } else { 1218 label_ = NULL; 1219 } 1220 if (need_avatar) { 1221 avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true); 1222 layout_view_->AddChildView(avatar_); 1223 } else { 1224 avatar_ = NULL; 1225 } 1226 } 1227 1228 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 1229 if (status == user::LOGGED_IN_LOCALLY_MANAGED) { 1230 label_->SetText( 1231 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL)); 1232 } else if (status == user::LOGGED_IN_GUEST) { 1233 label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL)); 1234 } 1235 1236 if (avatar_ && switches::UseAlternateShelfLayout()) { 1237 avatar_->SetCornerRadii( 1238 0, kUserIconLargeCornerRadius, kUserIconLargeCornerRadius, 0); 1239 avatar_->SetBorder(views::Border::NullBorder()); 1240 } 1241 UpdateAvatarImage(status); 1242 1243 // Update layout after setting label_ and avatar_ with new login status. 1244 UpdateLayoutOfItem(); 1245} 1246 1247void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { 1248 // Inactive users won't have a layout. 1249 if (!layout_view_) 1250 return; 1251 if (alignment == SHELF_ALIGNMENT_BOTTOM || 1252 alignment == SHELF_ALIGNMENT_TOP) { 1253 if (avatar_) { 1254 if (switches::UseAlternateShelfLayout()) { 1255 avatar_->SetBorder(views::Border::NullBorder()); 1256 avatar_->SetCornerRadii( 1257 0, kUserIconLargeCornerRadius, kUserIconLargeCornerRadius, 0); 1258 } else { 1259 avatar_->SetBorder(views::Border::CreateEmptyBorder( 1260 0, 1261 kTrayImageItemHorizontalPaddingBottomAlignment + 2, 1262 0, 1263 kTrayImageItemHorizontalPaddingBottomAlignment)); 1264 } 1265 } 1266 if (label_) { 1267 // If label_ hasn't figured out its size yet, do that first. 1268 if (label_->GetContentsBounds().height() == 0) 1269 label_->SizeToPreferredSize(); 1270 int height = label_->GetContentsBounds().height(); 1271 int vertical_pad = (kTrayItemSize - height) / 2; 1272 int remainder = height % 2; 1273 label_->SetBorder(views::Border::CreateEmptyBorder( 1274 vertical_pad + remainder, 1275 kTrayLabelItemHorizontalPaddingBottomAlignment, 1276 vertical_pad, 1277 kTrayLabelItemHorizontalPaddingBottomAlignment)); 1278 } 1279 layout_view_->SetLayoutManager( 1280 new views::BoxLayout(views::BoxLayout::kHorizontal, 1281 0, 0, kUserLabelToIconPadding)); 1282 } else { 1283 if (avatar_) { 1284 if (switches::UseAlternateShelfLayout()) { 1285 avatar_->SetBorder(views::Border::NullBorder()); 1286 avatar_->SetCornerRadii( 1287 0, 0, kUserIconLargeCornerRadius, kUserIconLargeCornerRadius); 1288 } else { 1289 SetTrayImageItemBorder(avatar_, alignment); 1290 } 1291 } 1292 if (label_) { 1293 label_->SetBorder(views::Border::CreateEmptyBorder( 1294 kTrayLabelItemVerticalPaddingVerticalAlignment, 1295 kTrayLabelItemHorizontalPaddingBottomAlignment, 1296 kTrayLabelItemVerticalPaddingVerticalAlignment, 1297 kTrayLabelItemHorizontalPaddingBottomAlignment)); 1298 } 1299 layout_view_->SetLayoutManager( 1300 new views::BoxLayout(views::BoxLayout::kVertical, 1301 0, 0, kUserLabelToIconPadding)); 1302 } 1303} 1304 1305void TrayUser::OnUserUpdate() { 1306 UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()-> 1307 GetUserLoginStatus()); 1308} 1309 1310void TrayUser::OnUserAddedToSession() { 1311 SessionStateDelegate* session_state_delegate = 1312 Shell::GetInstance()->session_state_delegate(); 1313 // Only create views for user items which are logged in. 1314 if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers()) 1315 return; 1316 1317 // Enforce a layout change that newly added items become visible. 1318 UpdateLayoutOfItem(); 1319 1320 // Update the user item. 1321 UpdateAvatarImage( 1322 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()); 1323} 1324 1325void TrayUser::UpdateAvatarImage(user::LoginStatus status) { 1326 SessionStateDelegate* session_state_delegate = 1327 Shell::GetInstance()->session_state_delegate(); 1328 if (!avatar_ || 1329 GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers()) 1330 return; 1331 1332 int icon_size = switches::UseAlternateShelfLayout() ? 1333 kUserIconLargeSize : kUserIconSize; 1334 1335 content::BrowserContext* context = session_state_delegate-> 1336 GetBrowserContextByIndex(GetTrayIndex()); 1337 avatar_->SetImage(session_state_delegate->GetUserImage(context), 1338 gfx::Size(icon_size, icon_size)); 1339 1340 // Unit tests might come here with no images for some users. 1341 if (avatar_->size().IsEmpty()) 1342 avatar_->SetSize(gfx::Size(icon_size, icon_size)); 1343} 1344 1345MultiProfileIndex TrayUser::GetTrayIndex() { 1346 Shell* shell = Shell::GetInstance(); 1347 // If multi profile is not enabled we can use the normal index. 1348 if (!shell->delegate()->IsMultiProfilesEnabled()) 1349 return multiprofile_index_; 1350 // In case of multi profile we need to mirror the indices since the system 1351 // tray items are in the reverse order then the menu items. 1352 return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() - 1353 1 - multiprofile_index_; 1354} 1355 1356void TrayUser::UpdateLayoutOfItem() { 1357 RootWindowController* controller = GetRootWindowController( 1358 system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow()); 1359 if (controller && controller->shelf()) { 1360 UpdateAfterShelfAlignmentChange( 1361 controller->GetShelfLayoutManager()->GetAlignment()); 1362 } 1363} 1364 1365} // namespace ash 1366