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

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

Issue 2125373002: mash: Migrate ash/system/user to ash/common. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Sync and rebase. Created 4 years, 5 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
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_card_view.h"
6
7 #include <algorithm>
8 #include <vector>
9
10 #include "ash/common/login_status.h"
11 #include "ash/common/session/session_state_delegate.h"
12 #include "ash/common/system/tray/system_tray_delegate.h"
13 #include "ash/common/system/tray/system_tray_notifier.h"
14 #include "ash/common/system/tray/tray_constants.h"
15 #include "ash/common/system/tray/tray_utils.h"
16 #include "ash/common/system/user/rounded_image_view.h"
17 #include "ash/common/wm_shell.h"
18 #include "base/i18n/rtl.h"
19 #include "base/memory/scoped_vector.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "components/user_manager/user_info.h"
24 #include "grit/ash_resources.h"
25 #include "grit/ash_strings.h"
26 #include "ui/accessibility/ax_view_state.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/resource/resource_bundle.h"
29 #include "ui/gfx/geometry/insets.h"
30 #include "ui/gfx/geometry/rect.h"
31 #include "ui/gfx/geometry/size.h"
32 #include "ui/gfx/range/range.h"
33 #include "ui/gfx/render_text.h"
34 #include "ui/gfx/text_elider.h"
35 #include "ui/gfx/text_utils.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/link.h"
38 #include "ui/views/controls/link_listener.h"
39 #include "ui/views/layout/box_layout.h"
40
41 #if defined(OS_CHROMEOS)
42 #include "ash/ash_view_ids.h"
43 #include "ash/common/media_delegate.h"
44 #include "ash/common/system/chromeos/media_security/media_capture_observer.h"
45 #include "ui/views/controls/image_view.h"
46 #include "ui/views/layout/fill_layout.h"
47 #endif
48
49 namespace ash {
50 namespace tray {
51
52 namespace {
53
54 const int kUserDetailsVerticalPadding = 5;
55
56 // The invisible word joiner character, used as a marker to indicate the start
57 // and end of the user's display name in the public account user card's text.
58 const base::char16 kDisplayNameMark[] = {0x2060, 0};
59
60 #if defined(OS_CHROMEOS)
61 class MediaIndicator : public views::View, public MediaCaptureObserver {
62 public:
63 explicit MediaIndicator(UserIndex index)
64 : index_(index), label_(new views::Label) {
65 SetLayoutManager(new views::FillLayout);
66 views::ImageView* icon = new views::ImageView;
67 icon->SetImage(ui::ResourceBundle::GetSharedInstance()
68 .GetImageNamed(IDR_AURA_UBER_TRAY_RECORDING_RED)
69 .ToImageSkia());
70 AddChildView(icon);
71 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
72 label_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
73 ui::ResourceBundle::SmallFont));
74 OnMediaCaptureChanged();
75 WmShell::Get()->system_tray_notifier()->AddMediaCaptureObserver(this);
76 set_id(VIEW_ID_USER_VIEW_MEDIA_INDICATOR);
77 }
78
79 ~MediaIndicator() override {
80 WmShell::Get()->system_tray_notifier()->RemoveMediaCaptureObserver(this);
81 }
82
83 // MediaCaptureObserver:
84 void OnMediaCaptureChanged() override {
85 MediaCaptureState state =
86 WmShell::Get()->media_delegate()->GetMediaCaptureState(index_);
87 int res_id = 0;
88 switch (state) {
89 case MEDIA_CAPTURE_AUDIO_VIDEO:
90 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO_VIDEO;
91 break;
92 case MEDIA_CAPTURE_AUDIO:
93 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO;
94 break;
95 case MEDIA_CAPTURE_VIDEO:
96 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_VIDEO;
97 break;
98 case MEDIA_CAPTURE_NONE:
99 break;
100 }
101 SetMessage(res_id ? l10n_util::GetStringUTF16(res_id) : base::string16());
102 }
103
104 views::View* GetMessageView() { return label_; }
105
106 void SetMessage(const base::string16& message) {
107 SetVisible(!message.empty());
108 label_->SetText(message);
109 label_->SetVisible(!message.empty());
110 }
111
112 private:
113 UserIndex index_;
114 views::Label* label_;
115
116 DISALLOW_COPY_AND_ASSIGN(MediaIndicator);
117 };
118 #endif
119
120 // The user details shown in public account mode. This is essentially a label
121 // but with custom painting code as the text is styled with multiple colors and
122 // contains a link.
123 class PublicAccountUserDetails : public views::View,
124 public views::LinkListener {
125 public:
126 PublicAccountUserDetails(int max_width);
127 ~PublicAccountUserDetails() override;
128
129 private:
130 // Overridden from views::View.
131 void Layout() override;
132 gfx::Size GetPreferredSize() const override;
133 void OnPaint(gfx::Canvas* canvas) override;
134
135 // Overridden from views::LinkListener.
136 void LinkClicked(views::Link* source, int event_flags) override;
137
138 // Calculate a preferred size that ensures the label text and the following
139 // link do not wrap over more than three lines in total for aesthetic reasons
140 // if possible.
141 void CalculatePreferredSize(int max_allowed_width);
142
143 base::string16 text_;
144 views::Link* learn_more_;
145 gfx::Size preferred_size_;
146 ScopedVector<gfx::RenderText> lines_;
147
148 DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
149 };
150
151 PublicAccountUserDetails::PublicAccountUserDetails(int max_width)
152 : learn_more_(NULL) {
153 const int inner_padding =
154 kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
155 const bool rtl = base::i18n::IsRTL();
156 SetBorder(views::Border::CreateEmptyBorder(
157 kUserDetailsVerticalPadding, rtl ? 0 : inner_padding,
158 kUserDetailsVerticalPadding, rtl ? inner_padding : 0));
159
160 // Retrieve the user's display name and wrap it with markers.
161 // Note that since this is a public account it always has to be the primary
162 // user.
163 base::string16 display_name = WmShell::Get()
164 ->GetSessionStateDelegate()
165 ->GetUserInfo(0)
166 ->GetDisplayName();
167 base::RemoveChars(display_name, kDisplayNameMark, &display_name);
168 display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
169 // Retrieve the domain managing the device and wrap it with markers.
170 base::string16 domain = base::UTF8ToUTF16(
171 WmShell::Get()->system_tray_delegate()->GetEnterpriseDomain());
172 base::RemoveChars(domain, kDisplayNameMark, &domain);
173 base::i18n::WrapStringWithLTRFormatting(&domain);
174 // Retrieve the label text, inserting the display name and domain.
175 text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
176 display_name, domain);
177
178 learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
179 learn_more_->SetUnderline(false);
180 learn_more_->set_listener(this);
181 AddChildView(learn_more_);
182
183 CalculatePreferredSize(max_width);
184 }
185
186 PublicAccountUserDetails::~PublicAccountUserDetails() {}
187
188 void PublicAccountUserDetails::Layout() {
189 lines_.clear();
190 const gfx::Rect contents_area = GetContentsBounds();
191 if (contents_area.IsEmpty())
192 return;
193
194 // Word-wrap the label text.
195 const gfx::FontList font_list;
196 std::vector<base::string16> lines;
197 gfx::ElideRectangleText(text_, font_list, contents_area.width(),
198 contents_area.height(), gfx::ELIDE_LONG_WORDS,
199 &lines);
200 // Loop through the lines, creating a renderer for each.
201 gfx::Point position = contents_area.origin();
202 gfx::Range display_name(gfx::Range::InvalidRange());
203 for (std::vector<base::string16>::const_iterator it = lines.begin();
204 it != lines.end(); ++it) {
205 gfx::RenderText* line = gfx::RenderText::CreateInstance();
206 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
207 line->SetText(*it);
208 const gfx::Size size(contents_area.width(), line->GetStringSize().height());
209 line->SetDisplayRect(gfx::Rect(position, size));
210 position.set_y(position.y() + size.height());
211
212 // Set the default text color for the line.
213 line->SetColor(kPublicAccountUserCardTextColor);
214
215 // If a range of the line contains the user's display name, apply a custom
216 // text color to it.
217 if (display_name.is_empty())
218 display_name.set_start(it->find(kDisplayNameMark));
219 if (!display_name.is_empty()) {
220 display_name.set_end(
221 it->find(kDisplayNameMark, display_name.start() + 1));
222 gfx::Range line_range(0, it->size());
223 line->ApplyColor(kPublicAccountUserCardNameColor,
224 display_name.Intersect(line_range));
225 // Update the range for the next line.
226 if (display_name.end() >= line_range.end())
227 display_name.set_start(0);
228 else
229 display_name = gfx::Range::InvalidRange();
230 }
231
232 lines_.push_back(line);
233 }
234
235 // Position the link after the label text, separated by a space. If it does
236 // not fit onto the last line of the text, wrap the link onto its own line.
237 const gfx::Size last_line_size = lines_.back()->GetStringSize();
238 const int space_width =
239 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
240 const gfx::Size link_size = learn_more_->GetPreferredSize();
241 if (contents_area.width() - last_line_size.width() >=
242 space_width + link_size.width()) {
243 position.set_x(position.x() + last_line_size.width() + space_width);
244 position.set_y(position.y() - last_line_size.height());
245 }
246 position.set_y(position.y() - learn_more_->GetInsets().top());
247 gfx::Rect learn_more_bounds(position, link_size);
248 learn_more_bounds.Intersect(contents_area);
249 if (base::i18n::IsRTL()) {
250 const gfx::Insets insets = GetInsets();
251 learn_more_bounds.Offset(insets.right() - insets.left(), 0);
252 }
253 learn_more_->SetBoundsRect(learn_more_bounds);
254 }
255
256 gfx::Size PublicAccountUserDetails::GetPreferredSize() const {
257 return preferred_size_;
258 }
259
260 void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
261 for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin();
262 it != lines_.end(); ++it) {
263 (*it)->Draw(canvas);
264 }
265 views::View::OnPaint(canvas);
266 }
267
268 void PublicAccountUserDetails::LinkClicked(views::Link* source,
269 int event_flags) {
270 DCHECK_EQ(source, learn_more_);
271 WmShell::Get()->system_tray_delegate()->ShowPublicAccountInfo();
272 }
273
274 void PublicAccountUserDetails::CalculatePreferredSize(int max_allowed_width) {
275 const gfx::FontList font_list;
276 const gfx::Size link_size = learn_more_->GetPreferredSize();
277 const int space_width =
278 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
279 const gfx::Insets insets = GetInsets();
280 int min_width = link_size.width();
281 int max_width = std::min(
282 gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(),
283 max_allowed_width - insets.width());
284 // Do a binary search for the minimum width that ensures no more than three
285 // lines are needed. The lower bound is the minimum of the current bubble
286 // width and the width of the link (as no wrapping is permitted inside the
287 // link). The upper bound is the maximum of the largest allowed bubble width
288 // and the sum of the label text and link widths when put on a single line.
289 std::vector<base::string16> lines;
290 while (min_width < max_width) {
291 lines.clear();
292 const int width = (min_width + max_width) / 2;
293 const bool too_narrow =
294 gfx::ElideRectangleText(text_, font_list, width, INT_MAX,
295 gfx::TRUNCATE_LONG_WORDS, &lines) != 0;
296 int line_count = lines.size();
297 if (!too_narrow && line_count == 3 &&
298 width - gfx::GetStringWidth(lines.back(), font_list) <=
299 space_width + link_size.width())
300 ++line_count;
301 if (too_narrow || line_count > 3)
302 min_width = width + 1;
303 else
304 max_width = width;
305 }
306
307 // Calculate the corresponding height and set the preferred size.
308 lines.clear();
309 gfx::ElideRectangleText(text_, font_list, min_width, INT_MAX,
310 gfx::TRUNCATE_LONG_WORDS, &lines);
311 int line_count = lines.size();
312 if (min_width - gfx::GetStringWidth(lines.back(), font_list) <=
313 space_width + link_size.width()) {
314 ++line_count;
315 }
316 const int line_height = font_list.GetHeight();
317 const int link_extra_height = std::max(
318 link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
319 preferred_size_ =
320 gfx::Size(min_width + insets.width(),
321 line_count * line_height + link_extra_height + insets.height());
322 }
323
324 } // namespace
325
326 UserCardView::UserCardView(LoginStatus login_status,
327 int max_width,
328 int user_index) {
329 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
330 kTrayPopupPaddingBetweenItems));
331 if (login_status == LoginStatus::PUBLIC) {
332 AddPublicModeUserContent(max_width);
333 } else {
334 AddUserContent(login_status, user_index);
335 }
336 }
337
338 UserCardView::~UserCardView() {}
339
340 void UserCardView::GetAccessibleState(ui::AXViewState* state) {
341 state->role = ui::AX_ROLE_STATIC_TEXT;
342 std::vector<base::string16> labels;
343 for (int i = 0; i < child_count(); ++i)
344 GetAccessibleLabelFromDescendantViews(child_at(i), labels);
345 state->name = base::JoinString(labels, base::ASCIIToUTF16(" "));
346 }
347
348 void UserCardView::AddPublicModeUserContent(int max_width) {
349 views::View* icon = CreateIcon(LoginStatus::PUBLIC, 0);
350 AddChildView(icon);
351 int details_max_width = max_width - icon->GetPreferredSize().width() -
352 kTrayPopupPaddingBetweenItems;
353 AddChildView(new PublicAccountUserDetails(details_max_width));
354 }
355
356 void UserCardView::AddUserContent(LoginStatus login_status, int user_index) {
357 views::View* icon = CreateIcon(login_status, user_index);
358 AddChildView(icon);
359 views::Label* user_name = NULL;
360 SessionStateDelegate* delegate = WmShell::Get()->GetSessionStateDelegate();
361 if (!user_index) {
362 base::string16 user_name_string =
363 login_status == LoginStatus::GUEST
364 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL)
365 : delegate->GetUserInfo(user_index)->GetDisplayName();
366 if (!user_name_string.empty()) {
367 user_name = new views::Label(user_name_string);
368 user_name->SetHorizontalAlignment(gfx::ALIGN_LEFT);
369 }
370 }
371
372 views::Label* user_email = NULL;
373 if (login_status != LoginStatus::GUEST) {
374 SystemTrayDelegate* tray_delegate = WmShell::Get()->system_tray_delegate();
375 base::string16 user_email_string =
376 tray_delegate->IsUserSupervised()
377 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL)
378 : base::UTF8ToUTF16(delegate->GetUserInfo(user_index)->GetEmail());
379 if (!user_email_string.empty()) {
380 user_email = new views::Label(user_email_string);
381 user_email->SetFontList(
382 ui::ResourceBundle::GetSharedInstance().GetFontList(
383 ui::ResourceBundle::SmallFont));
384 user_email->SetHorizontalAlignment(gfx::ALIGN_LEFT);
385 }
386 }
387
388 // Adjust text properties dependent on if it is an active or inactive user.
389 if (user_index) {
390 // Fade the text of non active users to 50%.
391 SkColor text_color = user_email->enabled_color();
392 text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2);
393 if (user_email)
394 user_email->SetDisabledColor(text_color);
395 if (user_name)
396 user_name->SetDisabledColor(text_color);
397 }
398
399 if (user_email && user_name) {
400 views::View* details = new views::View;
401 details->SetLayoutManager(new views::BoxLayout(
402 views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
403 details->AddChildView(user_name);
404 details->AddChildView(user_email);
405 AddChildView(details);
406 } else {
407 if (user_name)
408 AddChildView(user_name);
409 if (user_email) {
410 #if defined(OS_CHROMEOS)
411 // Only non active user can have a media indicator.
412 MediaIndicator* media_indicator = new MediaIndicator(user_index);
413 views::View* email_indicator_view = new views::View;
414 email_indicator_view->SetLayoutManager(new views::BoxLayout(
415 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems));
416 email_indicator_view->AddChildView(user_email);
417 email_indicator_view->AddChildView(media_indicator);
418
419 views::View* details = new views::View;
420 details->SetLayoutManager(new views::BoxLayout(
421 views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
422 details->AddChildView(email_indicator_view);
423 details->AddChildView(media_indicator->GetMessageView());
424 AddChildView(details);
425 #else
426 AddChildView(user_email);
427 #endif
428 }
429 }
430 }
431
432 views::View* UserCardView::CreateIcon(LoginStatus login_status,
433 int user_index) {
434 RoundedImageView* icon =
435 new RoundedImageView(kTrayRoundedBorderRadius, user_index == 0);
436 if (login_status == LoginStatus::GUEST) {
437 icon->SetImage(*ui::ResourceBundle::GetSharedInstance()
438 .GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON)
439 .ToImageSkia(),
440 gfx::Size(kTrayItemSize, kTrayItemSize));
441 } else {
442 SessionStateDelegate* delegate = WmShell::Get()->GetSessionStateDelegate();
443 icon->SetImage(delegate->GetUserInfo(user_index)->GetImage(),
444 gfx::Size(kTrayItemSize, kTrayItemSize));
445 }
446 return icon;
447 }
448
449 } // namespace tray
450 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698