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