| OLD | NEW |
| (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 | |
| OLD | NEW |