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 71 namespace { 72 73 const int kUserDetailsVerticalPadding = 5; 74 const int kUserCardVerticalPadding = 10; 75 const int kProfileRoundedCornerRadius = 2; 76 const int kUserIconSize = 27; 77 const int kUserIconLargeSize = 32; 78 const int kUserIconLargeCornerRadius = 2; 79 const int kUserLabelToIconPadding = 5; 80 // When using multi login, this spacing is added between user icons. 81 const int kTrayLabelSpacing = 1; 82 83 // When a hover border is used, it is starting this many pixels before the icon 84 // position. 85 const int kTrayUserTileHoverBorderInset = 10; 86 87 // The border color of the user button. 88 const 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. 92 const char16 kDisplayNameMark[] = { 0x2060, 0 }; 93 94 const 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 106 const 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. 119 const int kPopupMessageOffset = 25; 120 121 // Switch to a user with the given |user_index|. 122 void 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 138 namespace ash { 139 namespace internal { 140 141 namespace tray { 142 143 // A custom image view with rounded edges. 144 class 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. 181 class 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. 200 class 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. 229 class 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 260 class UserViewMouseWatcherHost : public views::MouseWatcherHost { 261 public: 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 272 private: 273 gfx::Rect screen_area_; 274 275 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost); 276 }; 277 278 // The view of a user item. 279 class 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. 348 class 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 387 RoundedImageView::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 393 RoundedImageView::~RoundedImageView() {} 394 395 void 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 409 void 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 419 gfx::Size RoundedImageView::GetPreferredSize() { 420 return gfx::Size(image_size_.width() + GetInsets().width(), 421 image_size_.height() + GetInsets().height()); 422 } 423 424 void 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 449 UserSwitcherView::UserSwitcherView(int corner_radius, 450 MultiProfileIndex user_index) 451 : RoundedImageView(corner_radius, false), 452 user_index_(user_index) { 453 SetEnabled(true); 454 } 455 456 void UserSwitcherView::OnMouseEvent(ui::MouseEvent* event) { 457 if (event->type() == ui::ET_MOUSE_PRESSED) { 458 SwitchUser(user_index_); 459 event->SetHandled(); 460 } 461 } 462 463 void UserSwitcherView::OnTouchEvent(ui::TouchEvent* event) { 464 if (event->type() == ui::ET_TOUCH_PRESSED) { 465 SwitchUser(user_index_); 466 event->SetHandled(); 467 } 468 } 469 470 PublicAccountUserDetails::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 set_border(views::Border::CreateEmptyBorder( 477 kUserDetailsVerticalPadding, rtl ? 0 : inner_padding, 478 kUserDetailsVerticalPadding, rtl ? inner_padding : 0)); 479 480 // Retrieve the user's display name and wrap it with markers. 481 // Note that since this is a public account it always has to be the primary 482 // user. 483 base::string16 display_name = 484 Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0); 485 base::RemoveChars(display_name, kDisplayNameMark, &display_name); 486 display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0]; 487 // Retrieve the domain managing the device and wrap it with markers. 488 base::string16 domain = UTF8ToUTF16( 489 Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain()); 490 base::RemoveChars(domain, kDisplayNameMark, &domain); 491 base::i18n::WrapStringWithLTRFormatting(&domain); 492 // Retrieve the label text, inserting the display name and domain. 493 text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL, 494 display_name, domain); 495 496 learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE)); 497 learn_more_->SetUnderline(false); 498 learn_more_->set_listener(this); 499 AddChildView(learn_more_); 500 501 CalculatePreferredSize(owner, used_width); 502 } 503 504 PublicAccountUserDetails::~PublicAccountUserDetails() {} 505 506 void PublicAccountUserDetails::Layout() { 507 lines_.clear(); 508 const gfx::Rect contents_area = GetContentsBounds(); 509 if (contents_area.IsEmpty()) 510 return; 511 512 // Word-wrap the label text. 513 const gfx::FontList font_list; 514 std::vector<base::string16> lines; 515 gfx::ElideRectangleText(text_, font_list, contents_area.width(), 516 contents_area.height(), gfx::ELIDE_LONG_WORDS, 517 &lines); 518 // Loop through the lines, creating a renderer for each. 519 gfx::Point position = contents_area.origin(); 520 gfx::Range display_name(gfx::Range::InvalidRange()); 521 for (std::vector<base::string16>::const_iterator it = lines.begin(); 522 it != lines.end(); ++it) { 523 gfx::RenderText* line = gfx::RenderText::CreateInstance(); 524 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI); 525 line->SetText(*it); 526 const gfx::Size size(contents_area.width(), line->GetStringSize().height()); 527 line->SetDisplayRect(gfx::Rect(position, size)); 528 position.set_y(position.y() + size.height()); 529 530 // Set the default text color for the line. 531 line->SetColor(kPublicAccountUserCardTextColor); 532 533 // If a range of the line contains the user's display name, apply a custom 534 // text color to it. 535 if (display_name.is_empty()) 536 display_name.set_start(it->find(kDisplayNameMark)); 537 if (!display_name.is_empty()) { 538 display_name.set_end( 539 it->find(kDisplayNameMark, display_name.start() + 1)); 540 gfx::Range line_range(0, it->size()); 541 line->ApplyColor(kPublicAccountUserCardNameColor, 542 display_name.Intersect(line_range)); 543 // Update the range for the next line. 544 if (display_name.end() >= line_range.end()) 545 display_name.set_start(0); 546 else 547 display_name = gfx::Range::InvalidRange(); 548 } 549 550 lines_.push_back(line); 551 } 552 553 // Position the link after the label text, separated by a space. If it does 554 // not fit onto the last line of the text, wrap the link onto its own line. 555 const gfx::Size last_line_size = lines_.back()->GetStringSize(); 556 const int space_width = gfx::GetStringWidth(ASCIIToUTF16(" "), font_list); 557 const gfx::Size link_size = learn_more_->GetPreferredSize(); 558 if (contents_area.width() - last_line_size.width() >= 559 space_width + link_size.width()) { 560 position.set_x(position.x() + last_line_size.width() + space_width); 561 position.set_y(position.y() - last_line_size.height()); 562 } 563 position.set_y(position.y() - learn_more_->GetInsets().top()); 564 gfx::Rect learn_more_bounds(position, link_size); 565 learn_more_bounds.Intersect(contents_area); 566 if (base::i18n::IsRTL()) { 567 const gfx::Insets insets = GetInsets(); 568 learn_more_bounds.Offset(insets.right() - insets.left(), 0); 569 } 570 learn_more_->SetBoundsRect(learn_more_bounds); 571 } 572 573 gfx::Size PublicAccountUserDetails::GetPreferredSize() { 574 return preferred_size_; 575 } 576 577 void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) { 578 for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin(); 579 it != lines_.end(); ++it) { 580 (*it)->Draw(canvas); 581 } 582 views::View::OnPaint(canvas); 583 } 584 585 void PublicAccountUserDetails::LinkClicked(views::Link* source, 586 int event_flags) { 587 DCHECK_EQ(source, learn_more_); 588 Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo(); 589 } 590 591 void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner, 592 int used_width) { 593 const gfx::FontList font_list; 594 const gfx::Size link_size = learn_more_->GetPreferredSize(); 595 const int space_width = gfx::GetStringWidth(ASCIIToUTF16(" "), font_list); 596 const gfx::Insets insets = GetInsets(); 597 views::TrayBubbleView* bubble_view = 598 owner->system_tray()->GetSystemBubble()->bubble_view(); 599 int min_width = std::max( 600 link_size.width(), 601 bubble_view->GetPreferredSize().width() - (used_width + insets.width())); 602 int max_width = std::min( 603 gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(), 604 bubble_view->GetMaximumSize().width() - (used_width + insets.width())); 605 // Do a binary search for the minimum width that ensures no more than three 606 // lines are needed. The lower bound is the minimum of the current bubble 607 // width and the width of the link (as no wrapping is permitted inside the 608 // link). The upper bound is the maximum of the largest allowed bubble width 609 // and the sum of the label text and link widths when put on a single line. 610 std::vector<base::string16> lines; 611 while (min_width < max_width) { 612 lines.clear(); 613 const int width = (min_width + max_width) / 2; 614 const bool too_narrow = 615 gfx::ElideRectangleText(text_, font_list, width, INT_MAX, 616 gfx::TRUNCATE_LONG_WORDS, &lines) != 0; 617 int line_count = lines.size(); 618 if (!too_narrow && line_count == 3 && 619 width - gfx::GetStringWidth(lines.back(), font_list) <= 620 space_width + link_size.width()) 621 ++line_count; 622 if (too_narrow || line_count > 3) 623 min_width = width + 1; 624 else 625 max_width = width; 626 } 627 628 // Calculate the corresponding height and set the preferred size. 629 lines.clear(); 630 gfx::ElideRectangleText( 631 text_, font_list, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines); 632 int line_count = lines.size(); 633 if (min_width - gfx::GetStringWidth(lines.back(), font_list) <= 634 space_width + link_size.width()) { 635 ++line_count; 636 } 637 const int line_height = font_list.GetHeight(); 638 const int link_extra_height = std::max( 639 link_size.height() - learn_more_->GetInsets().top() - line_height, 0); 640 preferred_size_ = gfx::Size( 641 min_width + insets.width(), 642 line_count * line_height + link_extra_height + insets.height()); 643 644 bubble_view->SetWidth(preferred_size_.width() + used_width); 645 } 646 647 UserCard::UserCard(views::ButtonListener* listener, bool active_user) 648 : CustomButton(listener), 649 is_active_user_(active_user), 650 button_hovered_(false), 651 show_border_(false) { 652 if (is_active_user_) { 653 set_background( 654 views::Background::CreateSolidBackground(kBackgroundColor)); 655 ShowActive(); 656 } 657 } 658 659 UserCard::~UserCard() {} 660 661 void UserCard::ForceBorderVisible(bool show) { 662 show_border_ = show; 663 ShowActive(); 664 } 665 666 void UserCard::OnMouseEntered(const ui::MouseEvent& event) { 667 if (is_active_user_) { 668 button_hovered_ = true; 669 background()->SetNativeControlColor(kHoverBackgroundColor); 670 ShowActive(); 671 } 672 } 673 674 void UserCard::OnMouseExited(const ui::MouseEvent& event) { 675 if (is_active_user_) { 676 button_hovered_ = false; 677 background()->SetNativeControlColor(kBackgroundColor); 678 ShowActive(); 679 } 680 } 681 682 void UserCard::ShowActive() { 683 int width = button_hovered_ || show_border_ ? 1 : 0; 684 set_border(views::Border::CreateSolidSidedBorder(width, width, width, 1, 685 kBorderColor)); 686 SchedulePaint(); 687 } 688 689 UserView::UserView(SystemTrayItem* owner, 690 user::LoginStatus login, 691 MultiProfileIndex index) 692 : multiprofile_index_(index), 693 user_card_view_(NULL), 694 owner_(owner), 695 is_user_card_(false), 696 logout_button_(NULL), 697 add_user_visible_but_disabled_(false) { 698 CHECK_NE(user::LOGGED_IN_NONE, login); 699 if (!index) { 700 // Only the logged in user will have a background. All other users will have 701 // to allow the TrayPopupContainer highlighting the menu line. 702 set_background(views::Background::CreateSolidBackground( 703 login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor : 704 kBackgroundColor)); 705 } 706 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 707 kTrayPopupPaddingBetweenItems)); 708 // The logout button must be added before the user card so that the user card 709 // can correctly calculate the remaining available width. 710 // Note that only the current multiprofile user gets a button. 711 if (!multiprofile_index_) 712 AddLogoutButton(login); 713 AddUserCard(owner, login); 714 } 715 716 UserView::~UserView() {} 717 718 void UserView::MouseMovedOutOfHost() { 719 popup_message_.reset(); 720 mouse_watcher_.reset(); 721 add_menu_option_.reset(); 722 } 723 724 TrayUser::TestState UserView::GetStateForTest() const { 725 if (add_menu_option_.get()) { 726 return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED : 727 TrayUser::ACTIVE; 728 } 729 730 if (!is_user_card_) 731 return TrayUser::SHOWN; 732 733 return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ? 734 TrayUser::HOVERED : TrayUser::SHOWN; 735 } 736 737 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() { 738 DCHECK(user_card_view_); 739 return user_card_view_->GetBoundsInScreen(); 740 } 741 742 gfx::Size UserView::GetPreferredSize() { 743 gfx::Size size = views::View::GetPreferredSize(); 744 // Only the active user panel will be forced to a certain height. 745 if (!multiprofile_index_) { 746 size.set_height(std::max(size.height(), 747 kTrayPopupItemHeight + GetInsets().height())); 748 } 749 return size; 750 } 751 752 int UserView::GetHeightForWidth(int width) { 753 return GetPreferredSize().height(); 754 } 755 756 void UserView::Layout() { 757 gfx::Rect contents_area(GetContentsBounds()); 758 if (user_card_view_ && logout_button_) { 759 // Give the logout button the space it requests. 760 gfx::Rect logout_area = contents_area; 761 logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize()); 762 logout_area.set_x(contents_area.right() - logout_area.width()); 763 764 // Give the remaining space to the user card. 765 gfx::Rect user_card_area = contents_area; 766 int remaining_width = contents_area.width() - logout_area.width(); 767 if (SupportsMultiProfile()) { 768 // In multiprofile case |user_card_view_| and |logout_button_| have to 769 // have the same height. 770 int y = std::min(user_card_area.y(), logout_area.y()); 771 int height = std::max(user_card_area.height(), logout_area.height()); 772 logout_area.set_y(y); 773 logout_area.set_height(height); 774 user_card_area.set_y(y); 775 user_card_area.set_height(height); 776 777 // In multiprofile mode we have also to increase the size of the card by 778 // the size of the border to make it overlap with the logout button. 779 user_card_area.set_width(std::max(0, remaining_width + 1)); 780 781 // To make the logout button symmetrical with the user card we also make 782 // the button longer by the same size the hover area in front of the icon 783 // got inset. 784 logout_area.set_width(logout_area.width() + 785 kTrayUserTileHoverBorderInset); 786 } else { 787 // In all other modes we have to make sure that there is enough spacing 788 // between the two. 789 remaining_width -= kTrayPopupPaddingBetweenItems; 790 } 791 user_card_area.set_width(remaining_width); 792 user_card_view_->SetBoundsRect(user_card_area); 793 logout_button_->SetBoundsRect(logout_area); 794 } else if (user_card_view_) { 795 user_card_view_->SetBoundsRect(contents_area); 796 } else if (logout_button_) { 797 logout_button_->SetBoundsRect(contents_area); 798 } 799 } 800 801 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) { 802 if (sender == logout_button_) { 803 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 804 ash::UMA_STATUS_AREA_SIGN_OUT); 805 Shell::GetInstance()->system_tray_delegate()->SignOut(); 806 } else if (sender == user_card_view_ && SupportsMultiProfile()) { 807 if (!multiprofile_index_) { 808 ToggleAddUserMenuOption(); 809 } else { 810 SwitchUser(multiprofile_index_); 811 // Since the user list is about to change the system menu should get 812 // closed. 813 owner_->system_tray()->CloseSystemBubble(); 814 } 815 } else if (add_menu_option_.get() && 816 sender == add_menu_option_->GetContentsView()) { 817 // Let the user add another account to the session. 818 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY); 819 Shell::GetInstance()->system_tray_delegate()->ShowUserLogin(); 820 } else { 821 NOTREACHED(); 822 } 823 } 824 825 void UserView::AddLogoutButton(user::LoginStatus login) { 826 const base::string16 title = user::GetLocalizedSignOutStringForStatus(login, 827 true); 828 TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title); 829 logout_button->SetAccessibleName(title); 830 logout_button_ = logout_button; 831 // In public account mode, the logout button border has a custom color. 832 if (login == user::LOGGED_IN_PUBLIC) { 833 TrayPopupLabelButtonBorder* border = 834 static_cast<TrayPopupLabelButtonBorder*>(logout_button_->border()); 835 border->SetPainter(false, views::Button::STATE_NORMAL, 836 views::Painter::CreateImageGridPainter( 837 kPublicAccountLogoutButtonBorderImagesNormal)); 838 border->SetPainter(false, views::Button::STATE_HOVERED, 839 views::Painter::CreateImageGridPainter( 840 kPublicAccountLogoutButtonBorderImagesHovered)); 841 border->SetPainter(false, views::Button::STATE_PRESSED, 842 views::Painter::CreateImageGridPainter( 843 kPublicAccountLogoutButtonBorderImagesHovered)); 844 } 845 AddChildView(logout_button_); 846 } 847 848 void UserView::AddUserCard(SystemTrayItem* owner, user::LoginStatus login) { 849 // Add padding around the panel. 850 set_border(views::Border::CreateEmptyBorder( 851 kUserCardVerticalPadding, kTrayPopupPaddingHorizontal, 852 kUserCardVerticalPadding, kTrayPopupPaddingHorizontal)); 853 854 if (SupportsMultiProfile() && login != user::LOGGED_IN_RETAIL_MODE) { 855 user_card_view_ = new UserCard(this, multiprofile_index_ == 0); 856 is_user_card_ = true; 857 } else { 858 user_card_view_ = new views::View(); 859 is_user_card_ = false; 860 } 861 862 user_card_view_->SetLayoutManager(new views::BoxLayout( 863 views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems)); 864 AddChildViewAt(user_card_view_, 0); 865 866 if (login == user::LOGGED_IN_RETAIL_MODE) { 867 AddLoggedInRetailModeUserCardContent(); 868 return; 869 } 870 871 // The entire user card should trigger hover (the inner items get disabled). 872 user_card_view_->SetEnabled(true); 873 user_card_view_->set_notify_enter_exit_on_child(true); 874 875 if (login == user::LOGGED_IN_PUBLIC) { 876 AddLoggedInPublicModeUserCardContent(owner); 877 return; 878 } 879 880 views::View* icon = CreateIconForUserCard(login); 881 user_card_view_->AddChildView(icon); 882 883 // To allow the border to start before the icon, reduce the size before and 884 // add an inset to the icon to get the spacing. 885 if (multiprofile_index_ == 0 && SupportsMultiProfile()) { 886 icon->set_border(views::Border::CreateEmptyBorder( 887 0, kTrayUserTileHoverBorderInset, 0, 0)); 888 set_border(views::Border::CreateEmptyBorder( 889 kUserCardVerticalPadding, 890 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset, 891 kUserCardVerticalPadding, 892 kTrayPopupPaddingHorizontal)); 893 } 894 SessionStateDelegate* delegate = 895 Shell::GetInstance()->session_state_delegate(); 896 views::Label* username = NULL; 897 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 898 if (!multiprofile_index_) { 899 base::string16 user_name_string = 900 login == user::LOGGED_IN_GUEST ? 901 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) : 902 delegate->GetUserDisplayName(multiprofile_index_); 903 if (!user_name_string.empty()) { 904 username = new views::Label(user_name_string); 905 username->SetHorizontalAlignment(gfx::ALIGN_LEFT); 906 } 907 } 908 909 views::Label* additional = NULL; 910 if (login != user::LOGGED_IN_GUEST) { 911 base::string16 user_email_string = 912 login == user::LOGGED_IN_LOCALLY_MANAGED ? 913 bundle.GetLocalizedString( 914 IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) : 915 UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_)); 916 if (!user_email_string.empty()) { 917 additional = new views::Label(user_email_string); 918 additional->SetFontList( 919 bundle.GetFontList(ui::ResourceBundle::SmallFont)); 920 additional->SetHorizontalAlignment(gfx::ALIGN_LEFT); 921 } 922 } 923 924 // Adjust text properties dependent on if it is an active or inactive user. 925 if (multiprofile_index_) { 926 // Fade the text of non active users to 50%. 927 SkColor text_color = additional->enabled_color(); 928 text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2); 929 if (additional) 930 additional->SetDisabledColor(text_color); 931 if (username) 932 username->SetDisabledColor(text_color); 933 } 934 935 if (additional && username) { 936 views::View* details = new views::View; 937 details->SetLayoutManager(new views::BoxLayout( 938 views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0)); 939 details->AddChildView(username); 940 details->AddChildView(additional); 941 user_card_view_->AddChildView(details); 942 } else { 943 if (username) 944 user_card_view_->AddChildView(username); 945 if (additional) 946 user_card_view_->AddChildView(additional); 947 } 948 } 949 950 views::View* UserView::CreateIconForUserCard(user::LoginStatus login) { 951 RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius, 952 multiprofile_index_ == 0); 953 icon->SetEnabled(false); 954 if (login == user::LOGGED_IN_GUEST) { 955 icon->SetImage(*ui::ResourceBundle::GetSharedInstance(). 956 GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(), 957 gfx::Size(kUserIconSize, kUserIconSize)); 958 } else { 959 icon->SetImage( 960 Shell::GetInstance()->session_state_delegate()-> 961 GetUserImage(multiprofile_index_), 962 gfx::Size(kUserIconSize, kUserIconSize)); 963 } 964 return icon; 965 } 966 967 void UserView::AddLoggedInRetailModeUserCardContent() { 968 views::Label* details = new views::Label; 969 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 970 details->SetText( 971 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL)); 972 details->set_border(views::Border::CreateEmptyBorder(0, 4, 0, 1)); 973 details->SetHorizontalAlignment(gfx::ALIGN_LEFT); 974 user_card_view_->AddChildView(details); 975 } 976 977 void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) { 978 user_card_view_->AddChildView(CreateIconForUserCard(user::LOGGED_IN_PUBLIC)); 979 user_card_view_->AddChildView(new PublicAccountUserDetails( 980 owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems)); 981 } 982 983 void UserView::ToggleAddUserMenuOption() { 984 if (add_menu_option_.get()) { 985 popup_message_.reset(); 986 mouse_watcher_.reset(); 987 add_menu_option_.reset(); 988 return; 989 } 990 991 // Note: We do not need to install a global event handler to delete this 992 // item since it will destroyed automatically before the menu / user menu item 993 // gets destroyed.. 994 const SessionStateDelegate* session_state_delegate = 995 Shell::GetInstance()->session_state_delegate(); 996 add_user_visible_but_disabled_ = 997 session_state_delegate->NumberOfLoggedInUsers() >= 998 session_state_delegate->GetMaximumNumberOfLoggedInUsers(); 999 add_menu_option_.reset(new views::Widget); 1000 views::Widget::InitParams params; 1001 params.type = views::Widget::InitParams::TYPE_TOOLTIP; 1002 params.keep_on_top = true; 1003 params.context = this->GetWidget()->GetNativeWindow(); 1004 params.accept_events = true; 1005 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 1006 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 1007 add_menu_option_->Init(params); 1008 add_menu_option_->SetOpacity(0xFF); 1009 add_menu_option_->GetNativeWindow()->set_owned_by_parent(false); 1010 SetShadowType(add_menu_option_->GetNativeView(), 1011 views::corewm::SHADOW_TYPE_NONE); 1012 1013 // Position it below our user card. 1014 gfx::Rect bounds = user_card_view_->GetBoundsInScreen(); 1015 bounds.set_y(bounds.y() + bounds.height()); 1016 add_menu_option_->SetBounds(bounds); 1017 1018 // Show the content. 1019 AddUserView* add_user_view = new AddUserView( 1020 static_cast<UserCard*>(user_card_view_), this); 1021 add_menu_option_->SetContentsView(add_user_view); 1022 add_menu_option_->SetAlwaysOnTop(true); 1023 add_menu_option_->Show(); 1024 if (add_user_visible_but_disabled_) { 1025 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 1026 popup_message_.reset(new PopupMessage( 1027 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER), 1028 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER), 1029 PopupMessage::ICON_WARNING, 1030 add_user_view->anchor(), 1031 views::BubbleBorder::TOP_LEFT, 1032 gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0), 1033 2 * kPopupMessageOffset)); 1034 } 1035 // Find the screen area which encloses both elements and sets then a mouse 1036 // watcher which will close the "menu". 1037 gfx::Rect area = user_card_view_->GetBoundsInScreen(); 1038 area.set_height(2 * area.height()); 1039 mouse_watcher_.reset(new views::MouseWatcher( 1040 new UserViewMouseWatcherHost(area), 1041 this)); 1042 mouse_watcher_->Start(); 1043 } 1044 1045 bool UserView::SupportsMultiProfile() { 1046 // We do not want to see any multi profile additions to a user view when the 1047 // log in screen is shown. 1048 return Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() && 1049 !Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked(); 1050 } 1051 1052 AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener) 1053 : CustomButton(listener_), 1054 add_user_(NULL), 1055 listener_(listener), 1056 owner_(owner), 1057 anchor_(NULL) { 1058 AddContent(); 1059 owner_->ForceBorderVisible(true); 1060 } 1061 1062 AddUserView::~AddUserView() { 1063 owner_->ForceBorderVisible(false); 1064 } 1065 1066 gfx::Size AddUserView::GetPreferredSize() { 1067 return owner_->bounds().size(); 1068 } 1069 1070 int AddUserView::GetHeightForWidth(int width) { 1071 return owner_->bounds().size().height(); 1072 } 1073 1074 void AddUserView::Layout() { 1075 gfx::Rect contents_area(GetContentsBounds()); 1076 add_user_->SetBoundsRect(contents_area); 1077 } 1078 1079 void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) { 1080 if (add_user_ == sender) 1081 listener_->ButtonPressed(this, event); 1082 else 1083 NOTREACHED(); 1084 } 1085 1086 void AddUserView::AddContent() { 1087 set_notify_enter_exit_on_child(true); 1088 1089 const SessionStateDelegate* delegate = 1090 Shell::GetInstance()->session_state_delegate(); 1091 bool enable = delegate->NumberOfLoggedInUsers() < 1092 delegate->GetMaximumNumberOfLoggedInUsers(); 1093 1094 SetLayoutManager(new views::FillLayout()); 1095 set_background(views::Background::CreateSolidBackground(kBackgroundColor)); 1096 1097 // Add padding around the panel. 1098 set_border(views::Border::CreateSolidBorder(1, kBorderColor)); 1099 1100 add_user_ = new UserCard(this, enable); 1101 add_user_->set_border(views::Border::CreateEmptyBorder( 1102 kUserCardVerticalPadding, 1103 kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset, 1104 kUserCardVerticalPadding, 1105 kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset)); 1106 1107 add_user_->SetLayoutManager(new views::BoxLayout( 1108 views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems)); 1109 AddChildViewAt(add_user_, 0); 1110 1111 // Add the [+] icon which is also the anchor for messages. 1112 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 1113 RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius, 1114 true); 1115 anchor_ = icon; 1116 icon->SetImage(*ui::ResourceBundle::GetSharedInstance(). 1117 GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(), 1118 gfx::Size(kUserIconSize, kUserIconSize)); 1119 add_user_->AddChildView(icon); 1120 1121 // Add the command text. 1122 views::Label* command_label = new views::Label( 1123 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT)); 1124 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1125 add_user_->AddChildView(command_label); 1126 } 1127 1128 } // namespace tray 1129 1130 TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index) 1131 : SystemTrayItem(system_tray), 1132 multiprofile_index_(index), 1133 user_(NULL), 1134 layout_view_(NULL), 1135 avatar_(NULL), 1136 label_(NULL) { 1137 Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this); 1138 } 1139 1140 TrayUser::~TrayUser() { 1141 Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this); 1142 } 1143 1144 TrayUser::TestState TrayUser::GetStateForTest() const { 1145 if (!user_) 1146 return HIDDEN; 1147 return user_->GetStateForTest(); 1148 } 1149 1150 bool TrayUser::CanDropWindowHereToTransferToUser( 1151 const gfx::Point& point_in_screen) { 1152 // Check that this item is shown in the system tray (which means it must have 1153 // a view there) and that the user it represents is not the current user (in 1154 // which case |GetTrayIndex()| would return NULL). 1155 if (!layout_view_ || !GetTrayIndex()) 1156 return false; 1157 return layout_view_->GetBoundsInScreen().Contains(point_in_screen); 1158 } 1159 1160 bool TrayUser::TransferWindowToUser(aura::Window* window) { 1161 SessionStateDelegate* session_state_delegate = 1162 ash::Shell::GetInstance()->session_state_delegate(); 1163 return session_state_delegate->TransferWindowToDesktopOfUser(window, 1164 GetTrayIndex()); 1165 } 1166 1167 gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const { 1168 DCHECK(user_); 1169 return user_->GetBoundsInScreenOfUserButtonForTest(); 1170 } 1171 1172 views::View* TrayUser::CreateTrayView(user::LoginStatus status) { 1173 CHECK(layout_view_ == NULL); 1174 // When the full multi profile mode is used, only the active user will be 1175 // shown in the system tray, otherwise all users which are logged in. 1176 if (GetTrayIndex() && switches::UseFullMultiProfileMode()) 1177 return NULL; 1178 1179 layout_view_ = new views::View(); 1180 layout_view_->SetLayoutManager( 1181 new views::BoxLayout(views::BoxLayout::kHorizontal, 1182 0, 0, kUserLabelToIconPadding)); 1183 UpdateAfterLoginStatusChange(status); 1184 return layout_view_; 1185 } 1186 1187 views::View* TrayUser::CreateDefaultView(user::LoginStatus status) { 1188 if (status == user::LOGGED_IN_NONE) 1189 return NULL; 1190 const SessionStateDelegate* session_state_delegate = 1191 Shell::GetInstance()->session_state_delegate(); 1192 1193 // If the screen is locked show only the currently active user. 1194 if (multiprofile_index_ && session_state_delegate->IsUserSessionBlocked()) 1195 return NULL; 1196 1197 CHECK(user_ == NULL); 1198 1199 int logged_in_users = session_state_delegate->NumberOfLoggedInUsers(); 1200 1201 // Do not show more UserView's then there are logged in users. 1202 if (multiprofile_index_ >= logged_in_users) 1203 return NULL; 1204 1205 user_ = new tray::UserView(this, status, multiprofile_index_); 1206 return user_; 1207 } 1208 1209 views::View* TrayUser::CreateDetailedView(user::LoginStatus status) { 1210 return NULL; 1211 } 1212 1213 void TrayUser::DestroyTrayView() { 1214 layout_view_ = NULL; 1215 avatar_ = NULL; 1216 label_ = NULL; 1217 } 1218 1219 void TrayUser::DestroyDefaultView() { 1220 user_ = NULL; 1221 } 1222 1223 void TrayUser::DestroyDetailedView() { 1224 } 1225 1226 void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) { 1227 // Only the active user is represented in the tray. 1228 if (!layout_view_) 1229 return; 1230 if (GetTrayIndex() > 0 && !ash::switches::UseMultiUserTray()) 1231 return; 1232 bool need_label = false; 1233 bool need_avatar = false; 1234 switch (status) { 1235 case user::LOGGED_IN_LOCKED: 1236 case user::LOGGED_IN_USER: 1237 case user::LOGGED_IN_OWNER: 1238 case user::LOGGED_IN_PUBLIC: 1239 need_avatar = true; 1240 break; 1241 case user::LOGGED_IN_LOCALLY_MANAGED: 1242 need_avatar = true; 1243 need_label = true; 1244 break; 1245 case user::LOGGED_IN_GUEST: 1246 need_label = true; 1247 break; 1248 case user::LOGGED_IN_RETAIL_MODE: 1249 case user::LOGGED_IN_KIOSK_APP: 1250 case user::LOGGED_IN_NONE: 1251 break; 1252 } 1253 1254 if ((need_avatar != (avatar_ != NULL)) || 1255 (need_label != (label_ != NULL))) { 1256 layout_view_->RemoveAllChildViews(true); 1257 if (need_label) { 1258 label_ = new views::Label; 1259 SetupLabelForTray(label_); 1260 layout_view_->AddChildView(label_); 1261 } else { 1262 label_ = NULL; 1263 } 1264 if (need_avatar) { 1265 MultiProfileIndex tray_index = GetTrayIndex(); 1266 if (!tray_index) { 1267 // The active user (index #0) will always be the first. 1268 avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true); 1269 } else { 1270 // All other users will be inactive users. 1271 avatar_ = new tray::UserSwitcherView(kProfileRoundedCornerRadius, 1272 tray_index); 1273 } 1274 layout_view_->AddChildView(avatar_); 1275 } else { 1276 avatar_ = NULL; 1277 } 1278 } 1279 1280 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 1281 if (status == user::LOGGED_IN_LOCALLY_MANAGED) { 1282 label_->SetText( 1283 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL)); 1284 } else if (status == user::LOGGED_IN_GUEST) { 1285 label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL)); 1286 } 1287 1288 if (avatar_ && switches::UseAlternateShelfLayout()) { 1289 int corner_radius = GetTrayItemRadius(); 1290 avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0); 1291 avatar_->set_border(NULL); 1292 } 1293 UpdateAvatarImage(status); 1294 1295 // Update layout after setting label_ and avatar_ with new login status. 1296 UpdateLayoutOfItem(); 1297 } 1298 1299 void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) { 1300 // Inactive users won't have a layout. 1301 if (!layout_view_) 1302 return; 1303 int corner_radius = GetTrayItemRadius(); 1304 if (alignment == SHELF_ALIGNMENT_BOTTOM || 1305 alignment == SHELF_ALIGNMENT_TOP) { 1306 if (avatar_) { 1307 if (switches::UseAlternateShelfLayout()) { 1308 if (multiprofile_index_) { 1309 avatar_->set_border( 1310 views::Border::CreateEmptyBorder(0, kTrayLabelSpacing, 0, 0)); 1311 } else { 1312 avatar_->set_border(NULL); 1313 } 1314 avatar_->SetCornerRadii(0, corner_radius, corner_radius, 0); 1315 } else { 1316 avatar_->set_border(views::Border::CreateEmptyBorder( 1317 0, kTrayImageItemHorizontalPaddingBottomAlignment + 2, 1318 0, kTrayImageItemHorizontalPaddingBottomAlignment)); 1319 } 1320 } 1321 if (label_) { 1322 label_->set_border(views::Border::CreateEmptyBorder( 1323 0, kTrayLabelItemHorizontalPaddingBottomAlignment, 1324 0, kTrayLabelItemHorizontalPaddingBottomAlignment)); 1325 } 1326 layout_view_->SetLayoutManager( 1327 new views::BoxLayout(views::BoxLayout::kHorizontal, 1328 0, 0, kUserLabelToIconPadding)); 1329 } else { 1330 if (avatar_) { 1331 if (switches::UseAlternateShelfLayout()) { 1332 if (multiprofile_index_) { 1333 avatar_->set_border( 1334 views::Border::CreateEmptyBorder(kTrayLabelSpacing, 0, 0, 0)); 1335 } else { 1336 avatar_->set_border(NULL); 1337 } 1338 avatar_->SetCornerRadii(0, 0, corner_radius, corner_radius); 1339 } else { 1340 SetTrayImageItemBorder(avatar_, alignment); 1341 } 1342 } 1343 if (label_) { 1344 label_->set_border(views::Border::CreateEmptyBorder( 1345 kTrayLabelItemVerticalPaddingVerticalAlignment, 1346 kTrayLabelItemHorizontalPaddingBottomAlignment, 1347 kTrayLabelItemVerticalPaddingVerticalAlignment, 1348 kTrayLabelItemHorizontalPaddingBottomAlignment)); 1349 } 1350 layout_view_->SetLayoutManager( 1351 new views::BoxLayout(views::BoxLayout::kVertical, 1352 0, 0, kUserLabelToIconPadding)); 1353 } 1354 } 1355 1356 void TrayUser::OnUserUpdate() { 1357 UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()-> 1358 GetUserLoginStatus()); 1359 } 1360 1361 void TrayUser::OnUserAddedToSession() { 1362 SessionStateDelegate* session_state_delegate = 1363 Shell::GetInstance()->session_state_delegate(); 1364 // Only create views for user items which are logged in. 1365 if (GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers()) 1366 return; 1367 1368 // Enforce a layout change that newly added items become visible. 1369 UpdateLayoutOfItem(); 1370 1371 // Update the user item. 1372 UpdateAvatarImage( 1373 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()); 1374 } 1375 1376 void TrayUser::UpdateAvatarImage(user::LoginStatus status) { 1377 SessionStateDelegate* session_state_delegate = 1378 Shell::GetInstance()->session_state_delegate(); 1379 if (!avatar_ || 1380 GetTrayIndex() >= session_state_delegate->NumberOfLoggedInUsers()) 1381 return; 1382 1383 int icon_size = switches::UseAlternateShelfLayout() ? 1384 kUserIconLargeSize : kUserIconSize; 1385 1386 avatar_->SetImage( 1387 Shell::GetInstance()->session_state_delegate()->GetUserImage( 1388 GetTrayIndex()), 1389 gfx::Size(icon_size, icon_size)); 1390 1391 // Unit tests might come here with no images for some users. 1392 if (avatar_->size().IsEmpty()) 1393 avatar_->SetSize(gfx::Size(icon_size, icon_size)); 1394 } 1395 1396 MultiProfileIndex TrayUser::GetTrayIndex() { 1397 Shell* shell = Shell::GetInstance(); 1398 // If multi profile is not enabled we can use the normal index. 1399 if (!shell->delegate()->IsMultiProfilesEnabled()) 1400 return multiprofile_index_; 1401 // In case of multi profile we need to mirror the indices since the system 1402 // tray items are in the reverse order then the menu items. 1403 return shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() - 1404 1 - multiprofile_index_; 1405 } 1406 1407 int TrayUser::GetTrayItemRadius() { 1408 SessionStateDelegate* delegate = 1409 Shell::GetInstance()->session_state_delegate(); 1410 bool is_last_item = GetTrayIndex() == (delegate->NumberOfLoggedInUsers() - 1); 1411 return is_last_item ? kUserIconLargeCornerRadius : 0; 1412 } 1413 1414 void TrayUser::UpdateLayoutOfItem() { 1415 internal::RootWindowController* controller = 1416 internal::GetRootWindowController( 1417 system_tray()->GetWidget()->GetNativeWindow()->GetRootWindow()); 1418 if (controller && controller->shelf()) { 1419 UpdateAfterShelfAlignmentChange( 1420 controller->GetShelfLayoutManager()->GetAlignment()); 1421 } 1422 } 1423 1424 } // namespace internal 1425 } // namespace ash 1426