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