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

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

Issue 2734653002: chromeos: Move files in //ash/common to //ash (Closed)
Patch Set: fix a11y tests, fix docs Created 3 years, 9 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
« no previous file with comments | « ash/common/system/user/user_card_view.h ('k') | ash/common/system/user/user_observer.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/common/system/user/user_card_view.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <vector>
10
11 #include "ash/common/ash_view_ids.h"
12 #include "ash/common/login_status.h"
13 #include "ash/common/media_controller.h"
14 #include "ash/common/session/session_state_delegate.h"
15 #include "ash/common/system/tray/system_tray_controller.h"
16 #include "ash/common/system/tray/system_tray_delegate.h"
17 #include "ash/common/system/tray/tray_constants.h"
18 #include "ash/common/system/tray/tray_popup_item_style.h"
19 #include "ash/common/system/user/rounded_image_view.h"
20 #include "ash/common/wm_shell.h"
21 #include "ash/resources/vector_icons/vector_icons.h"
22 #include "ash/strings/grit/ash_strings.h"
23 #include "base/i18n/rtl.h"
24 #include "base/memory/ptr_util.h"
25 #include "base/strings/string16.h"
26 #include "base/strings/string_util.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "components/user_manager/user_info.h"
29 #include "ui/accessibility/ax_node_data.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/compositor/compositing_recorder.h"
32 #include "ui/gfx/canvas.h"
33 #include "ui/gfx/color_palette.h"
34 #include "ui/gfx/geometry/insets.h"
35 #include "ui/gfx/geometry/rect.h"
36 #include "ui/gfx/geometry/size.h"
37 #include "ui/gfx/paint_vector_icon.h"
38 #include "ui/gfx/range/range.h"
39 #include "ui/gfx/render_text.h"
40 #include "ui/gfx/text_elider.h"
41 #include "ui/gfx/text_utils.h"
42 #include "ui/views/border.h"
43 #include "ui/views/controls/image_view.h"
44 #include "ui/views/controls/link.h"
45 #include "ui/views/controls/link_listener.h"
46 #include "ui/views/layout/box_layout.h"
47
48 namespace ash {
49 namespace tray {
50
51 namespace {
52
53 const int kUserDetailsVerticalPadding = 5;
54
55 // The invisible word joiner character, used as a marker to indicate the start
56 // and end of the user's display name in the public account user card's text.
57 const base::char16 kDisplayNameMark[] = {0x2060, 0};
58
59 views::View* CreateUserAvatarView(LoginStatus login_status, int user_index) {
60 RoundedImageView* image_view = new RoundedImageView(kTrayItemSize / 2);
61 if (login_status == LoginStatus::GUEST) {
62 gfx::ImageSkia icon =
63 gfx::CreateVectorIcon(kSystemMenuGuestIcon, kMenuIconColor);
64 image_view->SetImage(icon, icon.size());
65 } else {
66 SessionStateDelegate* delegate = WmShell::Get()->GetSessionStateDelegate();
67 image_view->SetImage(delegate->GetUserInfo(user_index)->GetImage(),
68 gfx::Size(kTrayItemSize, kTrayItemSize));
69 }
70
71 image_view->SetBorder(views::CreateEmptyBorder(gfx::Insets(
72 (kTrayPopupItemMinStartWidth - image_view->GetPreferredSize().width()) /
73 2)));
74 return image_view;
75 }
76
77 // The user details shown in public account mode. This is essentially a label
78 // but with custom painting code as the text is styled with multiple colors and
79 // contains a link.
80 class PublicAccountUserDetails : public views::View,
81 public views::LinkListener {
82 public:
83 PublicAccountUserDetails(int max_width);
84 ~PublicAccountUserDetails() override;
85
86 private:
87 // Overridden from views::View.
88 void Layout() override;
89 gfx::Size GetPreferredSize() const override;
90 void OnPaint(gfx::Canvas* canvas) override;
91 void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
92
93 // Overridden from views::LinkListener.
94 void LinkClicked(views::Link* source, int event_flags) override;
95
96 // Calculate a preferred size that ensures the label text and the following
97 // link do not wrap over more than three lines in total for aesthetic reasons
98 // if possible.
99 void CalculatePreferredSize();
100
101 base::string16 text_;
102 views::Link* learn_more_;
103 gfx::Size preferred_size_;
104 std::vector<std::unique_ptr<gfx::RenderText>> lines_;
105
106 DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
107 };
108
109 PublicAccountUserDetails::PublicAccountUserDetails(int max_width)
110 : learn_more_(NULL) {
111 const int inner_padding =
112 kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
113 const bool rtl = base::i18n::IsRTL();
114 SetBorder(views::CreateEmptyBorder(
115 kUserDetailsVerticalPadding, rtl ? 0 : inner_padding,
116 kUserDetailsVerticalPadding, rtl ? inner_padding : 0));
117
118 // Retrieve the user's display name and wrap it with markers.
119 // Note that since this is a public account it always has to be the primary
120 // user.
121 base::string16 display_name = WmShell::Get()
122 ->GetSessionStateDelegate()
123 ->GetUserInfo(0)
124 ->GetDisplayName();
125 base::RemoveChars(display_name, kDisplayNameMark, &display_name);
126 display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
127 // Retrieve the domain managing the device and wrap it with markers.
128 base::string16 domain = base::UTF8ToUTF16(
129 WmShell::Get()->system_tray_delegate()->GetEnterpriseDomain());
130 base::RemoveChars(domain, kDisplayNameMark, &domain);
131 base::i18n::WrapStringWithLTRFormatting(&domain);
132 // Retrieve the label text, inserting the display name and domain.
133 text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
134 display_name, domain);
135
136 learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
137 learn_more_->SetUnderline(false);
138 learn_more_->set_listener(this);
139 AddChildView(learn_more_);
140
141 CalculatePreferredSize();
142 }
143
144 PublicAccountUserDetails::~PublicAccountUserDetails() {}
145
146 void PublicAccountUserDetails::Layout() {
147 lines_.clear();
148 const gfx::Rect contents_area = GetContentsBounds();
149 if (contents_area.IsEmpty())
150 return;
151
152 // Word-wrap the label text.
153 const gfx::FontList font_list;
154 std::vector<base::string16> lines;
155 gfx::ElideRectangleText(text_, font_list, contents_area.width(),
156 contents_area.height(), gfx::ELIDE_LONG_WORDS,
157 &lines);
158 // Loop through the lines, creating a renderer for each.
159 gfx::Point position = contents_area.origin();
160 gfx::Range display_name(gfx::Range::InvalidRange());
161 for (auto it = lines.begin(); it != lines.end(); ++it) {
162 auto line = base::WrapUnique(gfx::RenderText::CreateInstance());
163 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
164 line->SetText(*it);
165 const gfx::Size size(contents_area.width(), line->GetStringSize().height());
166 line->SetDisplayRect(gfx::Rect(position, size));
167 position.set_y(position.y() + size.height());
168
169 // Set the default text color for the line.
170 line->SetColor(kPublicAccountUserCardTextColor);
171
172 // If a range of the line contains the user's display name, apply a custom
173 // text color to it.
174 if (display_name.is_empty())
175 display_name.set_start(it->find(kDisplayNameMark));
176 if (!display_name.is_empty()) {
177 display_name.set_end(
178 it->find(kDisplayNameMark, display_name.start() + 1));
179 gfx::Range line_range(0, it->size());
180 line->ApplyColor(kPublicAccountUserCardNameColor,
181 display_name.Intersect(line_range));
182 // Update the range for the next line.
183 if (display_name.end() >= line_range.end())
184 display_name.set_start(0);
185 else
186 display_name = gfx::Range::InvalidRange();
187 }
188
189 lines_.push_back(std::move(line));
190 }
191
192 // Position the link after the label text, separated by a space. If it does
193 // not fit onto the last line of the text, wrap the link onto its own line.
194 const gfx::Size last_line_size = lines_.back()->GetStringSize();
195 const int space_width =
196 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
197 const gfx::Size link_size = learn_more_->GetPreferredSize();
198 if (contents_area.width() - last_line_size.width() >=
199 space_width + link_size.width()) {
200 position.set_x(position.x() + last_line_size.width() + space_width);
201 position.set_y(position.y() - last_line_size.height());
202 }
203 position.set_y(position.y() - learn_more_->GetInsets().top());
204 gfx::Rect learn_more_bounds(position, link_size);
205 learn_more_bounds.Intersect(contents_area);
206 if (base::i18n::IsRTL()) {
207 const gfx::Insets insets = GetInsets();
208 learn_more_bounds.Offset(insets.right() - insets.left(), 0);
209 }
210 learn_more_->SetBoundsRect(learn_more_bounds);
211 }
212
213 gfx::Size PublicAccountUserDetails::GetPreferredSize() const {
214 return preferred_size_;
215 }
216
217 void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
218 for (const auto& line : lines_)
219 line->Draw(canvas);
220
221 views::View::OnPaint(canvas);
222 }
223
224 void PublicAccountUserDetails::GetAccessibleNodeData(
225 ui::AXNodeData* node_data) {
226 node_data->role = ui::AX_ROLE_STATIC_TEXT;
227 node_data->SetName(text_);
228 }
229
230 void PublicAccountUserDetails::LinkClicked(views::Link* source,
231 int event_flags) {
232 DCHECK_EQ(source, learn_more_);
233 WmShell::Get()->system_tray_controller()->ShowPublicAccountInfo();
234 }
235
236 void PublicAccountUserDetails::CalculatePreferredSize() {
237 const gfx::FontList font_list;
238 const gfx::Size link_size = learn_more_->GetPreferredSize();
239 const int space_width =
240 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list);
241 const gfx::Insets insets = GetInsets();
242 int min_width = link_size.width();
243 int max_width =
244 gfx::GetStringWidth(text_, font_list) + space_width + link_size.width();
245
246 // Do a binary search for the minimum width that ensures no more than three
247 // lines are needed. The lower bound is the minimum of the current bubble
248 // width and the width of the link (as no wrapping is permitted inside the
249 // link). The upper bound is the maximum of the largest allowed bubble width
250 // and the sum of the label text and link widths when put on a single line.
251 std::vector<base::string16> lines;
252 while (min_width < max_width) {
253 lines.clear();
254 const int width = (min_width + max_width) / 2;
255 const bool too_narrow =
256 gfx::ElideRectangleText(text_, font_list, width, INT_MAX,
257 gfx::TRUNCATE_LONG_WORDS, &lines) != 0;
258 int line_count = lines.size();
259 if (!too_narrow && line_count == 3 &&
260 width - gfx::GetStringWidth(lines.back(), font_list) <=
261 space_width + link_size.width())
262 ++line_count;
263 if (too_narrow || line_count > 3)
264 min_width = width + 1;
265 else
266 max_width = width;
267 }
268
269 // Calculate the corresponding height and set the preferred size.
270 lines.clear();
271 gfx::ElideRectangleText(text_, font_list, min_width, INT_MAX,
272 gfx::TRUNCATE_LONG_WORDS, &lines);
273 int line_count = lines.size();
274 if (min_width - gfx::GetStringWidth(lines.back(), font_list) <=
275 space_width + link_size.width()) {
276 ++line_count;
277 }
278 const int line_height = font_list.GetHeight();
279 const int link_extra_height = std::max(
280 link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
281 preferred_size_ =
282 gfx::Size(min_width + insets.width(),
283 line_count * line_height + link_extra_height + insets.height());
284 }
285
286 } // namespace
287
288 UserCardView::UserCardView(LoginStatus login_status,
289 int max_width,
290 int user_index)
291 : user_index_(user_index),
292 user_name_(nullptr),
293 media_capture_label_(nullptr),
294 media_capture_icon_(nullptr) {
295 auto* layout = new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
296 kTrayPopupLabelHorizontalPadding);
297 SetLayoutManager(layout);
298 layout->set_minimum_cross_axis_size(kTrayPopupItemMinHeight);
299 layout->set_cross_axis_alignment(
300 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
301 // For active users, the left inset is provided by ActiveUserBorder, which
302 // is necessary to make sure the ripple does not cover that part of the row.
303 // For inactive users, we set the inset here and this causes the ripple to
304 // extend all the way to the edges of the menu.
305 if (!is_active_user())
306 SetBorder(views::CreateEmptyBorder(0, kMenuExtraMarginFromLeftEdge, 0, 0));
307
308 WmShell::Get()->media_controller()->AddObserver(this);
309
310 if (login_status == LoginStatus::PUBLIC)
311 AddPublicModeUserContent(max_width);
312 else
313 AddUserContent(layout, login_status);
314 }
315
316 UserCardView::~UserCardView() {
317 WmShell::Get()->media_controller()->RemoveObserver(this);
318 }
319
320 void UserCardView::PaintChildren(const ui::PaintContext& context) {
321 if (!is_active_user()) {
322 ui::CompositingRecorder alpha(context, 0xFF / 2, true);
323 View::PaintChildren(context);
324 } else {
325 View::PaintChildren(context);
326 }
327 }
328
329 void UserCardView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
330 node_data->role = ui::AX_ROLE_STATIC_TEXT;
331 std::vector<base::string16> labels;
332
333 // Construct the name by concatenating descendants' names.
334 std::list<views::View*> descendants;
335 descendants.push_back(this);
336 while (!descendants.empty()) {
337 auto* view = descendants.front();
338 descendants.pop_front();
339 if (view != this) {
340 ui::AXNodeData descendant_data;
341 view->GetAccessibleNodeData(&descendant_data);
342 base::string16 label =
343 descendant_data.GetString16Attribute(ui::AX_ATTR_NAME);
344 // If we find a non-empty name, use that and don't descend further into
345 // the tree.
346 if (!label.empty()) {
347 labels.push_back(label);
348 continue;
349 }
350 }
351
352 // This view didn't have its own name, so look over its children.
353 for (int i = view->child_count() - 1; i >= 0; --i)
354 descendants.push_front(view->child_at(i));
355 }
356 node_data->SetName(base::JoinString(labels, base::ASCIIToUTF16(" ")));
357 }
358
359 void UserCardView::OnMediaCaptureChanged(
360 const std::vector<mojom::MediaCaptureState>& capture_states) {
361 if (is_active_user())
362 return;
363
364 mojom::MediaCaptureState state = capture_states[user_index_];
365 int res_id = 0;
366 switch (state) {
367 case mojom::MediaCaptureState::AUDIO_VIDEO:
368 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO_VIDEO;
369 break;
370 case mojom::MediaCaptureState::AUDIO:
371 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO;
372 break;
373 case mojom::MediaCaptureState::VIDEO:
374 res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_VIDEO;
375 break;
376 case mojom::MediaCaptureState::NONE:
377 break;
378 }
379 if (res_id)
380 media_capture_label_->SetText(l10n_util::GetStringUTF16(res_id));
381 media_capture_label_->SetVisible(!!res_id);
382 media_capture_icon_->SetVisible(!!res_id);
383 user_name_->SetVisible(!res_id);
384 Layout();
385 }
386
387 void UserCardView::AddPublicModeUserContent(int max_width) {
388 views::View* avatar = CreateUserAvatarView(LoginStatus::PUBLIC, 0);
389 AddChildView(avatar);
390 int details_max_width = max_width - avatar->GetPreferredSize().width() -
391 kTrayPopupPaddingBetweenItems;
392 AddChildView(new PublicAccountUserDetails(details_max_width));
393 }
394
395 void UserCardView::AddUserContent(views::BoxLayout* layout,
396 LoginStatus login_status) {
397 AddChildView(CreateUserAvatarView(login_status, user_index_));
398 SessionStateDelegate* delegate = WmShell::Get()->GetSessionStateDelegate();
399 base::string16 user_name_string =
400 login_status == LoginStatus::GUEST
401 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL)
402 : delegate->GetUserInfo(user_index_)->GetDisplayName();
403 user_name_ = new views::Label(user_name_string);
404 user_name_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
405 TrayPopupItemStyle user_name_style(
406 TrayPopupItemStyle::FontStyle::DEFAULT_VIEW_LABEL);
407 user_name_style.SetupLabel(user_name_);
408
409 TrayPopupItemStyle user_email_style(TrayPopupItemStyle::FontStyle::CAPTION);
410 // Only the active user's email label is lightened (for the inactive user, the
411 // label starts as black and the entire row is 54% opacity).
412 if (is_active_user())
413 user_email_style.set_color_style(TrayPopupItemStyle::ColorStyle::INACTIVE);
414 auto* user_email = new views::Label();
415 base::string16 user_email_string;
416 if (login_status != LoginStatus::GUEST) {
417 user_email_string =
418 WmShell::Get()->system_tray_delegate()->IsUserSupervised()
419 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL)
420 : base::UTF8ToUTF16(
421 delegate->GetUserInfo(user_index_)->GetDisplayEmail());
422 }
423 user_email->SetText(user_email_string);
424 user_email->SetHorizontalAlignment(gfx::ALIGN_LEFT);
425 user_email_style.SetupLabel(user_email);
426 user_email->SetVisible(!user_email_string.empty());
427 user_email->set_collapse_when_hidden(true);
428
429 views::View* stack_of_labels = new views::View;
430 AddChildView(stack_of_labels);
431 layout->SetFlexForView(stack_of_labels, 1);
432 stack_of_labels->SetLayoutManager(
433 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
434 stack_of_labels->AddChildView(user_name_);
435 stack_of_labels->AddChildView(user_email);
436 // The name and email have different font sizes. This border is designed
437 // to make both views take up equal space so the whitespace between them
438 // is centered on the vertical midpoint.
439 int user_email_bottom_pad = user_name_->GetPreferredSize().height() -
440 user_email->GetPreferredSize().height();
441 user_email->SetBorder(
442 views::CreateEmptyBorder(0, 0, user_email_bottom_pad, 0));
443
444 // Only inactive users need media capture indicators.
445 if (!is_active_user()) {
446 media_capture_label_ = new views::Label();
447 media_capture_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
448 media_capture_label_->SetBorder(
449 views::CreateEmptyBorder(0, 0, user_email_bottom_pad, 0));
450 user_email_style.SetupLabel(media_capture_label_);
451 stack_of_labels->AddChildView(media_capture_label_);
452
453 media_capture_icon_ = new views::ImageView;
454 media_capture_icon_->SetImage(
455 gfx::CreateVectorIcon(kSystemTrayRecordingIcon, gfx::kGoogleRed700));
456 const int media_capture_width = kTrayPopupItemMinEndWidth;
457 media_capture_icon_->SetBorder(views::CreateEmptyBorder(
458 gfx::Insets(0, (media_capture_width -
459 media_capture_icon_->GetPreferredSize().width()) /
460 2)));
461
462 media_capture_icon_->set_id(VIEW_ID_USER_VIEW_MEDIA_INDICATOR);
463 AddChildView(media_capture_icon_);
464
465 WmShell::Get()->media_controller()->RequestCaptureState();
466 }
467 }
468
469 } // namespace tray
470 } // namespace ash
OLDNEW
« no previous file with comments | « ash/common/system/user/user_card_view.h ('k') | ash/common/system/user/user_observer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698