Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: ash/system/user/user_view.cc

Issue 210903003: Implemented system tray UI for new account management. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merge conflicts resolved. Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « ash/system/user/user_view.h ('k') | ash/test/test_session_state_delegate.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 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/user_view.h"
6
7 #include <algorithm>
8
9 #include "ash/multi_profile_uma.h"
10 #include "ash/popup_message.h"
11 #include "ash/session_state_delegate.h"
12 #include "ash/shell.h"
13 #include "ash/shell_delegate.h"
14 #include "ash/system/tray/system_tray.h"
15 #include "ash/system/tray/system_tray_delegate.h"
16 #include "ash/system/tray/tray_popup_label_button.h"
17 #include "ash/system/tray/tray_popup_label_button_border.h"
18 #include "ash/system/user/button_from_view.h"
19 #include "ash/system/user/config.h"
20 #include "ash/system/user/rounded_image_view.h"
21 #include "ash/system/user/user_card_view.h"
22 #include "grit/ash_resources.h"
23 #include "grit/ash_strings.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/views/layout/fill_layout.h"
27 #include "ui/views/painter.h"
28 #include "ui/wm/core/shadow_types.h"
29
30 namespace ash {
31 namespace tray {
32
33 namespace {
34
35 const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
36 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
37 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
38 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
39 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
40 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
41 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
42 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
43 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
44 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
45 };
46
47 const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
48 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
49 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
50 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
51 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
52 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
53 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
54 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
55 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
56 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
57 };
58
59 // When a hover border is used, it is starting this many pixels before the icon
60 // position.
61 const int kTrayUserTileHoverBorderInset = 10;
62
63 // Offsetting the popup message relative to the tray menu.
64 const int kPopupMessageOffset = 25;
65
66 // Switch to a user with the given |user_index|.
67 void SwitchUser(ash::MultiProfileIndex user_index) {
68 // Do not switch users when the log screen is presented.
69 if (ash::Shell::GetInstance()
70 ->session_state_delegate()
71 ->IsUserSessionBlocked())
72 return;
73
74 DCHECK(user_index > 0);
75 ash::SessionStateDelegate* delegate =
76 ash::Shell::GetInstance()->session_state_delegate();
77 ash::MultiProfileUMA::RecordSwitchActiveUser(
78 ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY);
79 delegate->SwitchActiveUser(delegate->GetUserID(user_index));
80 }
81
82 class LogoutButton : public TrayPopupLabelButton {
83 public:
84 // If |placeholder| is true, button is used as placeholder. That means that
85 // button is inactive and is not painted, but consume the same ammount of
86 // space, as if it was painted.
87 LogoutButton(views::ButtonListener* listener,
88 const base::string16& text,
89 bool placeholder)
90 : TrayPopupLabelButton(listener, text), placeholder_(placeholder) {
91 SetEnabled(!placeholder_);
92 }
93
94 virtual ~LogoutButton() {}
95
96 private:
97 virtual void Paint(gfx::Canvas* canvas) OVERRIDE {
98 // Just skip paint if this button used as a placeholder.
99 if (!placeholder_)
100 TrayPopupLabelButton::Paint(canvas);
101 }
102
103 bool placeholder_;
104 DISALLOW_COPY_AND_ASSIGN(LogoutButton);
105 };
106
107 class UserViewMouseWatcherHost : public views::MouseWatcherHost {
108 public:
109 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
110 : screen_area_(screen_area) {}
111 virtual ~UserViewMouseWatcherHost() {}
112
113 // Implementation of MouseWatcherHost.
114 virtual bool Contains(const gfx::Point& screen_point,
115 views::MouseWatcherHost::MouseEventType type) OVERRIDE {
116 return screen_area_.Contains(screen_point);
117 }
118
119 private:
120 gfx::Rect screen_area_;
121
122 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
123 };
124
125 // The menu item view which gets shown when the user clicks in multi profile
126 // mode onto the user item.
127 class AddUserView : public views::View {
128 public:
129 // The |owner| is the view for which this view gets created.
130 AddUserView(ButtonFromView* owner);
131 virtual ~AddUserView();
132
133 // Get the anchor view for a message.
134 views::View* anchor() { return anchor_; }
135
136 private:
137 // Overridden from views::View.
138 virtual gfx::Size GetPreferredSize() OVERRIDE;
139
140 // Create the additional client content for this item.
141 void AddContent();
142
143 // This is the content we create and show.
144 views::View* add_user_;
145
146 // This is the owner view of this item.
147 ButtonFromView* owner_;
148
149 // The anchor view for targetted bubble messages.
150 views::View* anchor_;
151
152 DISALLOW_COPY_AND_ASSIGN(AddUserView);
153 };
154
155 AddUserView::AddUserView(ButtonFromView* owner)
156 : add_user_(NULL), owner_(owner), anchor_(NULL) {
157 AddContent();
158 owner_->ForceBorderVisible(true);
159 }
160
161 AddUserView::~AddUserView() {
162 owner_->ForceBorderVisible(false);
163 }
164
165 gfx::Size AddUserView::GetPreferredSize() {
166 return owner_->bounds().size();
167 }
168
169 void AddUserView::AddContent() {
170 SetLayoutManager(new views::FillLayout());
171 set_background(views::Background::CreateSolidBackground(kBackgroundColor));
172
173 add_user_ = new views::View;
174 add_user_->SetBorder(views::Border::CreateEmptyBorder(
175 kTrayPopupUserCardVerticalPadding,
176 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
177 kTrayPopupUserCardVerticalPadding,
178 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset));
179
180 add_user_->SetLayoutManager(new views::BoxLayout(
181 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
182 AddChildViewAt(add_user_, 0);
183
184 // Add the [+] icon which is also the anchor for messages.
185 RoundedImageView* icon = new RoundedImageView(kTrayAvatarCornerRadius, true);
186 anchor_ = icon;
187 icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
188 .GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER)
189 .ToImageSkia(),
190 gfx::Size(kTrayAvatarSize, kTrayAvatarSize));
191 add_user_->AddChildView(icon);
192
193 // Add the command text.
194 views::Label* command_label = new views::Label(
195 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
196 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
197 add_user_->AddChildView(command_label);
198 }
199
200 } // namespace
201
202 UserView::UserView(SystemTrayItem* owner,
203 user::LoginStatus login,
204 MultiProfileIndex index,
205 bool for_detailed_view)
206 : multiprofile_index_(index),
207 user_card_view_(NULL),
208 owner_(owner),
209 is_user_card_button_(false),
210 logout_button_(NULL),
211 add_user_disabled_(false),
212 for_detailed_view_(for_detailed_view) {
213 CHECK_NE(user::LOGGED_IN_NONE, login);
214 if (!index) {
215 // Only the logged in user will have a background. All other users will have
216 // to allow the TrayPopupContainer highlighting the menu line.
217 set_background(views::Background::CreateSolidBackground(
218 login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor
219 : kBackgroundColor));
220 }
221 SetLayoutManager(new views::BoxLayout(
222 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
223 // The logout button must be added before the user card so that the user card
224 // can correctly calculate the remaining available width.
225 // Note that only the current multiprofile user gets a button.
226 if (!multiprofile_index_)
227 AddLogoutButton(login);
228 AddUserCard(login);
229 }
230
231 UserView::~UserView() {}
232
233 void UserView::MouseMovedOutOfHost() {
234 popup_message_.reset();
235 mouse_watcher_.reset();
236 add_menu_option_.reset();
237 }
238
239 TrayUser::TestState UserView::GetStateForTest() const {
240 if (add_menu_option_.get()) {
241 return add_user_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED
242 : TrayUser::ACTIVE;
243 }
244
245 if (!is_user_card_button_)
246 return TrayUser::SHOWN;
247
248 return static_cast<ButtonFromView*>(user_card_view_)->is_hovered_for_test()
249 ? TrayUser::HOVERED
250 : TrayUser::SHOWN;
251 }
252
253 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
254 DCHECK(user_card_view_);
255 return user_card_view_->GetBoundsInScreen();
256 }
257
258 gfx::Size UserView::GetPreferredSize() {
259 gfx::Size size = views::View::GetPreferredSize();
260 // Only the active user panel will be forced to a certain height.
261 if (!multiprofile_index_) {
262 size.set_height(
263 std::max(size.height(), kTrayPopupItemHeight + GetInsets().height()));
264 }
265 return size;
266 }
267
268 int UserView::GetHeightForWidth(int width) {
269 return GetPreferredSize().height();
270 }
271
272 void UserView::Layout() {
273 gfx::Rect contents_area(GetContentsBounds());
274 if (user_card_view_ && logout_button_) {
275 // Give the logout button the space it requests.
276 gfx::Rect logout_area = contents_area;
277 logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
278 logout_area.set_x(contents_area.right() - logout_area.width());
279
280 // Give the remaining space to the user card.
281 gfx::Rect user_card_area = contents_area;
282 int remaining_width = contents_area.width() - logout_area.width();
283 if (IsMultiProfileSupportedAndUserActive() ||
284 IsMultiAccountSupportedAndUserActive()) {
285 // In multiprofile case |user_card_view_| and |logout_button_| have to
286 // have the same height.
287 int y = std::min(user_card_area.y(), logout_area.y());
288 int height = std::max(user_card_area.height(), logout_area.height());
289 logout_area.set_y(y);
290 logout_area.set_height(height);
291 user_card_area.set_y(y);
292 user_card_area.set_height(height);
293
294 // In multiprofile mode we have also to increase the size of the card by
295 // the size of the border to make it overlap with the logout button.
296 user_card_area.set_width(std::max(0, remaining_width + 1));
297
298 // To make the logout button symmetrical with the user card we also make
299 // the button longer by the same size the hover area in front of the icon
300 // got inset.
301 logout_area.set_width(logout_area.width() +
302 kTrayUserTileHoverBorderInset);
303 } else {
304 // In all other modes we have to make sure that there is enough spacing
305 // between the two.
306 remaining_width -= kTrayPopupPaddingBetweenItems;
307 }
308 user_card_area.set_width(remaining_width);
309 user_card_view_->SetBoundsRect(user_card_area);
310 logout_button_->SetBoundsRect(logout_area);
311 } else if (user_card_view_) {
312 user_card_view_->SetBoundsRect(contents_area);
313 } else if (logout_button_) {
314 logout_button_->SetBoundsRect(contents_area);
315 }
316 }
317
318 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
319 if (sender == logout_button_) {
320 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
321 ash::UMA_STATUS_AREA_SIGN_OUT);
322 Shell::GetInstance()->system_tray_delegate()->SignOut();
323 } else if (sender == user_card_view_ && !multiprofile_index_ &&
324 IsMultiAccountSupportedAndUserActive()) {
325 owner_->TransitionDetailedView();
326 } else if (sender == user_card_view_ &&
327 IsMultiProfileSupportedAndUserActive()) {
328 if (!multiprofile_index_) {
329 ToggleAddUserMenuOption();
330 } else {
331 SwitchUser(multiprofile_index_);
332 // Since the user list is about to change the system menu should get
333 // closed.
334 owner_->system_tray()->CloseSystemBubble();
335 }
336 } else if (add_menu_option_.get() &&
337 sender == add_menu_option_->GetContentsView()) {
338 // Let the user add another account to the session.
339 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY);
340 Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
341 owner_->system_tray()->CloseSystemBubble();
342 } else {
343 NOTREACHED();
344 }
345 }
346
347 void UserView::AddLogoutButton(user::LoginStatus login) {
348 const base::string16 title =
349 user::GetLocalizedSignOutStringForStatus(login, true);
350 TrayPopupLabelButton* logout_button =
351 new LogoutButton(this, title, for_detailed_view_);
352 logout_button->SetAccessibleName(title);
353 logout_button_ = logout_button;
354 // In public account mode, the logout button border has a custom color.
355 if (login == user::LOGGED_IN_PUBLIC) {
356 scoped_ptr<TrayPopupLabelButtonBorder> border(
357 new TrayPopupLabelButtonBorder());
358 border->SetPainter(false,
359 views::Button::STATE_NORMAL,
360 views::Painter::CreateImageGridPainter(
361 kPublicAccountLogoutButtonBorderImagesNormal));
362 border->SetPainter(false,
363 views::Button::STATE_HOVERED,
364 views::Painter::CreateImageGridPainter(
365 kPublicAccountLogoutButtonBorderImagesHovered));
366 border->SetPainter(false,
367 views::Button::STATE_PRESSED,
368 views::Painter::CreateImageGridPainter(
369 kPublicAccountLogoutButtonBorderImagesHovered));
370 logout_button_->SetBorder(border.PassAs<views::Border>());
371 }
372 AddChildView(logout_button_);
373 }
374
375 void UserView::AddUserCard(user::LoginStatus login) {
376 // Add padding around the panel.
377 SetBorder(views::Border::CreateEmptyBorder(kTrayPopupUserCardVerticalPadding,
378 kTrayPopupPaddingHorizontal,
379 kTrayPopupUserCardVerticalPadding,
380 kTrayPopupPaddingHorizontal));
381
382 views::TrayBubbleView* bubble_view =
383 owner_->system_tray()->GetSystemBubble()->bubble_view();
384 int max_card_width =
385 bubble_view->GetMaximumSize().width() -
386 (2 * kTrayPopupPaddingHorizontal + kTrayPopupPaddingBetweenItems);
387 if (logout_button_)
388 max_card_width -= logout_button_->GetPreferredSize().width();
389 user_card_view_ =
390 new UserCardView(login, max_card_width, multiprofile_index_);
391 bool clickable = IsMultiProfileSupportedAndUserActive() ||
392 IsMultiAccountSupportedAndUserActive();
393 if (clickable) {
394 // To allow the border to start before the icon, reduce the size before and
395 // add an inset to the icon to get the spacing.
396 if (!multiprofile_index_) {
397 SetBorder(views::Border::CreateEmptyBorder(
398 kTrayPopupUserCardVerticalPadding,
399 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
400 kTrayPopupUserCardVerticalPadding,
401 kTrayPopupPaddingHorizontal));
402 user_card_view_->SetBorder(views::Border::CreateEmptyBorder(
403 0, kTrayUserTileHoverBorderInset, 0, 0));
404 }
405 if (!for_detailed_view_) {
406 user_card_view_ =
407 new ButtonFromView(user_card_view_, this, !multiprofile_index_);
408 } else {
409 // We want user card for detailed view to have exactly the same look
410 // as user card for default view. That's why we wrap it in a button
411 // without click listener and special hover behaviour.
412 user_card_view_ = new ButtonFromView(user_card_view_, NULL, false);
413 }
414 is_user_card_button_ = true;
415 }
416 AddChildViewAt(user_card_view_, 0);
417 // Card for locally managed user can consume more space than currently
418 // available. In that case we should increase system bubble's width.
419 if (login == user::LOGGED_IN_PUBLIC)
420 bubble_view->SetWidth(GetPreferredSize().width());
421 }
422
423 void UserView::ToggleAddUserMenuOption() {
424 if (add_menu_option_.get()) {
425 popup_message_.reset();
426 mouse_watcher_.reset();
427 add_menu_option_.reset();
428 return;
429 }
430
431 // Note: We do not need to install a global event handler to delete this
432 // item since it will destroyed automatically before the menu / user menu item
433 // gets destroyed..
434 add_menu_option_.reset(new views::Widget);
435 views::Widget::InitParams params;
436 params.type = views::Widget::InitParams::TYPE_TOOLTIP;
437 params.keep_on_top = true;
438 params.context = this->GetWidget()->GetNativeWindow();
439 params.accept_events = true;
440 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
441 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
442 add_menu_option_->Init(params);
443 add_menu_option_->SetOpacity(0xFF);
444 add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
445 SetShadowType(add_menu_option_->GetNativeView(), wm::SHADOW_TYPE_NONE);
446
447 // Position it below our user card.
448 gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
449 bounds.set_y(bounds.y() + bounds.height());
450 add_menu_option_->SetBounds(bounds);
451
452 // Show the content.
453 add_menu_option_->SetAlwaysOnTop(true);
454 add_menu_option_->Show();
455
456 AddUserView* add_user_view =
457 new AddUserView(static_cast<ButtonFromView*>(user_card_view_));
458
459 const SessionStateDelegate* delegate =
460 Shell::GetInstance()->session_state_delegate();
461 add_user_disabled_ = delegate->NumberOfLoggedInUsers() >=
462 delegate->GetMaximumNumberOfLoggedInUsers();
463 ButtonFromView* button = add_user_disabled_
464 ? new ButtonFromView(add_user_view, NULL, false)
465 : new ButtonFromView(add_user_view, this, true);
466 button->ForceBorderVisible(true);
467 add_menu_option_->SetContentsView(button);
468
469 if (add_user_disabled_) {
470 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
471 popup_message_.reset(new PopupMessage(
472 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
473 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER),
474 PopupMessage::ICON_WARNING,
475 add_user_view->anchor(),
476 views::BubbleBorder::TOP_LEFT,
477 gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
478 2 * kPopupMessageOffset));
479 }
480 // Find the screen area which encloses both elements and sets then a mouse
481 // watcher which will close the "menu".
482 gfx::Rect area = user_card_view_->GetBoundsInScreen();
483 area.set_height(2 * area.height());
484 mouse_watcher_.reset(
485 new views::MouseWatcher(new UserViewMouseWatcherHost(area), this));
486 mouse_watcher_->Start();
487 }
488
489 } // namespace tray
490 } // namespace ash
OLDNEW
« no previous file with comments | « ash/system/user/user_view.h ('k') | ash/test/test_session_state_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698