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