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