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