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/session_state_delegate.h" |
| 11 #include "ash/shell.h" |
| 12 #include "ash/system/tray/system_tray_delegate.h" |
| 13 #include "ash/system/tray/tray_constants.h" |
| 14 #include "ash/system/user/config.h" |
| 15 #include "ash/system/user/rounded_image_view.h" |
| 16 #include "base/i18n/rtl.h" |
| 17 #include "base/memory/scoped_vector.h" |
| 18 #include "base/strings/string16.h" |
| 19 #include "base/strings/string_util.h" |
| 20 #include "base/strings/utf_string_conversions.h" |
| 21 #include "grit/ash_resources.h" |
| 22 #include "grit/ash_strings.h" |
| 23 #include "ui/base/l10n/l10n_util.h" |
| 24 #include "ui/base/resource/resource_bundle.h" |
| 25 #include "ui/gfx/insets.h" |
| 26 #include "ui/gfx/range/range.h" |
| 27 #include "ui/gfx/rect.h" |
| 28 #include "ui/gfx/render_text.h" |
| 29 #include "ui/gfx/size.h" |
| 30 #include "ui/gfx/text_elider.h" |
| 31 #include "ui/gfx/text_utils.h" |
| 32 #include "ui/views/border.h" |
| 33 #include "ui/views/controls/link.h" |
| 34 #include "ui/views/controls/link_listener.h" |
| 35 #include "ui/views/layout/box_layout.h" |
| 36 |
| 37 namespace ash { |
| 38 namespace tray { |
| 39 |
| 40 namespace { |
| 41 |
| 42 const int kUserDetailsVerticalPadding = 5; |
| 43 |
| 44 // The invisible word joiner character, used as a marker to indicate the start |
| 45 // and end of the user's display name in the public account user card's text. |
| 46 const base::char16 kDisplayNameMark[] = {0x2060, 0}; |
| 47 |
| 48 // The user details shown in public account mode. This is essentially a label |
| 49 // but with custom painting code as the text is styled with multiple colors and |
| 50 // contains a link. |
| 51 class PublicAccountUserDetails : public views::View, |
| 52 public views::LinkListener { |
| 53 public: |
| 54 PublicAccountUserDetails(int max_width); |
| 55 virtual ~PublicAccountUserDetails(); |
| 56 |
| 57 private: |
| 58 // Overridden from views::View. |
| 59 virtual void Layout() OVERRIDE; |
| 60 virtual gfx::Size GetPreferredSize() OVERRIDE; |
| 61 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; |
| 62 |
| 63 // Overridden from views::LinkListener. |
| 64 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; |
| 65 |
| 66 // Calculate a preferred size that ensures the label text and the following |
| 67 // link do not wrap over more than three lines in total for aesthetic reasons |
| 68 // if possible. |
| 69 void CalculatePreferredSize(int max_allowed_width); |
| 70 |
| 71 base::string16 text_; |
| 72 views::Link* learn_more_; |
| 73 gfx::Size preferred_size_; |
| 74 ScopedVector<gfx::RenderText> lines_; |
| 75 |
| 76 DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails); |
| 77 }; |
| 78 |
| 79 PublicAccountUserDetails::PublicAccountUserDetails(int max_width) |
| 80 : learn_more_(NULL) { |
| 81 const int inner_padding = |
| 82 kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems; |
| 83 const bool rtl = base::i18n::IsRTL(); |
| 84 SetBorder(views::Border::CreateEmptyBorder(kUserDetailsVerticalPadding, |
| 85 rtl ? 0 : inner_padding, |
| 86 kUserDetailsVerticalPadding, |
| 87 rtl ? inner_padding : 0)); |
| 88 |
| 89 // Retrieve the user's display name and wrap it with markers. |
| 90 // Note that since this is a public account it always has to be the primary |
| 91 // user. |
| 92 base::string16 display_name = |
| 93 Shell::GetInstance()->session_state_delegate()->GetUserDisplayName(0); |
| 94 base::RemoveChars(display_name, kDisplayNameMark, &display_name); |
| 95 display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0]; |
| 96 // Retrieve the domain managing the device and wrap it with markers. |
| 97 base::string16 domain = base::UTF8ToUTF16( |
| 98 Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain()); |
| 99 base::RemoveChars(domain, kDisplayNameMark, &domain); |
| 100 base::i18n::WrapStringWithLTRFormatting(&domain); |
| 101 // Retrieve the label text, inserting the display name and domain. |
| 102 text_ = l10n_util::GetStringFUTF16( |
| 103 IDS_ASH_STATUS_TRAY_PUBLIC_LABEL, display_name, domain); |
| 104 |
| 105 learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE)); |
| 106 learn_more_->SetUnderline(false); |
| 107 learn_more_->set_listener(this); |
| 108 AddChildView(learn_more_); |
| 109 |
| 110 CalculatePreferredSize(max_width); |
| 111 } |
| 112 |
| 113 PublicAccountUserDetails::~PublicAccountUserDetails() {} |
| 114 |
| 115 void PublicAccountUserDetails::Layout() { |
| 116 lines_.clear(); |
| 117 const gfx::Rect contents_area = GetContentsBounds(); |
| 118 if (contents_area.IsEmpty()) |
| 119 return; |
| 120 |
| 121 // Word-wrap the label text. |
| 122 const gfx::FontList font_list; |
| 123 std::vector<base::string16> lines; |
| 124 gfx::ElideRectangleText(text_, |
| 125 font_list, |
| 126 contents_area.width(), |
| 127 contents_area.height(), |
| 128 gfx::ELIDE_LONG_WORDS, |
| 129 &lines); |
| 130 // Loop through the lines, creating a renderer for each. |
| 131 gfx::Point position = contents_area.origin(); |
| 132 gfx::Range display_name(gfx::Range::InvalidRange()); |
| 133 for (std::vector<base::string16>::const_iterator it = lines.begin(); |
| 134 it != lines.end(); |
| 135 ++it) { |
| 136 gfx::RenderText* line = gfx::RenderText::CreateInstance(); |
| 137 line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI); |
| 138 line->SetText(*it); |
| 139 const gfx::Size size(contents_area.width(), line->GetStringSize().height()); |
| 140 line->SetDisplayRect(gfx::Rect(position, size)); |
| 141 position.set_y(position.y() + size.height()); |
| 142 |
| 143 // Set the default text color for the line. |
| 144 line->SetColor(kPublicAccountUserCardTextColor); |
| 145 |
| 146 // If a range of the line contains the user's display name, apply a custom |
| 147 // text color to it. |
| 148 if (display_name.is_empty()) |
| 149 display_name.set_start(it->find(kDisplayNameMark)); |
| 150 if (!display_name.is_empty()) { |
| 151 display_name.set_end( |
| 152 it->find(kDisplayNameMark, display_name.start() + 1)); |
| 153 gfx::Range line_range(0, it->size()); |
| 154 line->ApplyColor(kPublicAccountUserCardNameColor, |
| 155 display_name.Intersect(line_range)); |
| 156 // Update the range for the next line. |
| 157 if (display_name.end() >= line_range.end()) |
| 158 display_name.set_start(0); |
| 159 else |
| 160 display_name = gfx::Range::InvalidRange(); |
| 161 } |
| 162 |
| 163 lines_.push_back(line); |
| 164 } |
| 165 |
| 166 // Position the link after the label text, separated by a space. If it does |
| 167 // not fit onto the last line of the text, wrap the link onto its own line. |
| 168 const gfx::Size last_line_size = lines_.back()->GetStringSize(); |
| 169 const int space_width = |
| 170 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list); |
| 171 const gfx::Size link_size = learn_more_->GetPreferredSize(); |
| 172 if (contents_area.width() - last_line_size.width() >= |
| 173 space_width + link_size.width()) { |
| 174 position.set_x(position.x() + last_line_size.width() + space_width); |
| 175 position.set_y(position.y() - last_line_size.height()); |
| 176 } |
| 177 position.set_y(position.y() - learn_more_->GetInsets().top()); |
| 178 gfx::Rect learn_more_bounds(position, link_size); |
| 179 learn_more_bounds.Intersect(contents_area); |
| 180 if (base::i18n::IsRTL()) { |
| 181 const gfx::Insets insets = GetInsets(); |
| 182 learn_more_bounds.Offset(insets.right() - insets.left(), 0); |
| 183 } |
| 184 learn_more_->SetBoundsRect(learn_more_bounds); |
| 185 } |
| 186 |
| 187 gfx::Size PublicAccountUserDetails::GetPreferredSize() { |
| 188 return preferred_size_; |
| 189 } |
| 190 |
| 191 void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) { |
| 192 for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin(); |
| 193 it != lines_.end(); |
| 194 ++it) { |
| 195 (*it)->Draw(canvas); |
| 196 } |
| 197 views::View::OnPaint(canvas); |
| 198 } |
| 199 |
| 200 void PublicAccountUserDetails::LinkClicked(views::Link* source, |
| 201 int event_flags) { |
| 202 DCHECK_EQ(source, learn_more_); |
| 203 Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo(); |
| 204 } |
| 205 |
| 206 void PublicAccountUserDetails::CalculatePreferredSize(int max_allowed_width) { |
| 207 const gfx::FontList font_list; |
| 208 const gfx::Size link_size = learn_more_->GetPreferredSize(); |
| 209 const int space_width = |
| 210 gfx::GetStringWidth(base::ASCIIToUTF16(" "), font_list); |
| 211 const gfx::Insets insets = GetInsets(); |
| 212 int min_width = link_size.width(); |
| 213 int max_width = std::min( |
| 214 gfx::GetStringWidth(text_, font_list) + space_width + link_size.width(), |
| 215 max_allowed_width - insets.width()); |
| 216 // Do a binary search for the minimum width that ensures no more than three |
| 217 // lines are needed. The lower bound is the minimum of the current bubble |
| 218 // width and the width of the link (as no wrapping is permitted inside the |
| 219 // link). The upper bound is the maximum of the largest allowed bubble width |
| 220 // and the sum of the label text and link widths when put on a single line. |
| 221 std::vector<base::string16> lines; |
| 222 while (min_width < max_width) { |
| 223 lines.clear(); |
| 224 const int width = (min_width + max_width) / 2; |
| 225 const bool too_narrow = gfx::ElideRectangleText(text_, |
| 226 font_list, |
| 227 width, |
| 228 INT_MAX, |
| 229 gfx::TRUNCATE_LONG_WORDS, |
| 230 &lines) != 0; |
| 231 int line_count = lines.size(); |
| 232 if (!too_narrow && line_count == 3 && |
| 233 width - gfx::GetStringWidth(lines.back(), font_list) <= |
| 234 space_width + link_size.width()) |
| 235 ++line_count; |
| 236 if (too_narrow || line_count > 3) |
| 237 min_width = width + 1; |
| 238 else |
| 239 max_width = width; |
| 240 } |
| 241 |
| 242 // Calculate the corresponding height and set the preferred size. |
| 243 lines.clear(); |
| 244 gfx::ElideRectangleText( |
| 245 text_, font_list, min_width, INT_MAX, gfx::TRUNCATE_LONG_WORDS, &lines); |
| 246 int line_count = lines.size(); |
| 247 if (min_width - gfx::GetStringWidth(lines.back(), font_list) <= |
| 248 space_width + link_size.width()) { |
| 249 ++line_count; |
| 250 } |
| 251 const int line_height = font_list.GetHeight(); |
| 252 const int link_extra_height = std::max( |
| 253 link_size.height() - learn_more_->GetInsets().top() - line_height, 0); |
| 254 preferred_size_ = |
| 255 gfx::Size(min_width + insets.width(), |
| 256 line_count * line_height + link_extra_height + insets.height()); |
| 257 } |
| 258 |
| 259 } // namespace |
| 260 |
| 261 UserCardView::UserCardView(user::LoginStatus login_status, |
| 262 int max_width, |
| 263 int multiprofile_index) { |
| 264 SetLayoutManager(new views::BoxLayout( |
| 265 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems)); |
| 266 switch (login_status) { |
| 267 case user::LOGGED_IN_RETAIL_MODE: |
| 268 AddRetailModeUserContent(); |
| 269 break; |
| 270 case user::LOGGED_IN_PUBLIC: |
| 271 AddPublicModeUserContent(max_width); |
| 272 break; |
| 273 default: |
| 274 AddUserContent(login_status, multiprofile_index); |
| 275 break; |
| 276 } |
| 277 } |
| 278 |
| 279 UserCardView::~UserCardView() {} |
| 280 |
| 281 void UserCardView::AddRetailModeUserContent() { |
| 282 views::Label* details = new views::Label; |
| 283 details->SetText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_KIOSK_LABEL)); |
| 284 details->SetBorder(views::Border::CreateEmptyBorder(0, 4, 0, 1)); |
| 285 details->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 286 AddChildView(details); |
| 287 } |
| 288 |
| 289 void UserCardView::AddPublicModeUserContent(int max_width) { |
| 290 views::View* icon = CreateIcon(user::LOGGED_IN_PUBLIC, 0); |
| 291 AddChildView(icon); |
| 292 int details_max_width = max_width - icon->GetPreferredSize().width() - |
| 293 kTrayPopupPaddingBetweenItems; |
| 294 AddChildView(new PublicAccountUserDetails(details_max_width)); |
| 295 } |
| 296 |
| 297 void UserCardView::AddUserContent(user::LoginStatus login_status, |
| 298 int multiprofile_index) { |
| 299 views::View* icon = CreateIcon(login_status, multiprofile_index); |
| 300 AddChildView(icon); |
| 301 views::Label* username = NULL; |
| 302 SessionStateDelegate* delegate = |
| 303 Shell::GetInstance()->session_state_delegate(); |
| 304 if (!multiprofile_index) { |
| 305 base::string16 user_name_string = |
| 306 login_status == user::LOGGED_IN_GUEST |
| 307 ? l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL) |
| 308 : delegate->GetUserDisplayName(multiprofile_index); |
| 309 if (user_name_string.empty() && IsMultiAccountSupportedAndUserActive()) |
| 310 user_name_string = |
| 311 base::ASCIIToUTF16(delegate->GetUserEmail(multiprofile_index)); |
| 312 if (!user_name_string.empty()) { |
| 313 username = new views::Label(user_name_string); |
| 314 username->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 315 } |
| 316 } |
| 317 |
| 318 views::Label* additional = NULL; |
| 319 if (login_status != user::LOGGED_IN_GUEST && |
| 320 (multiprofile_index || !IsMultiAccountSupportedAndUserActive())) { |
| 321 base::string16 user_email_string = |
| 322 login_status == user::LOGGED_IN_LOCALLY_MANAGED |
| 323 ? l10n_util::GetStringUTF16( |
| 324 IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) |
| 325 : base::UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index)); |
| 326 if (!user_email_string.empty()) { |
| 327 additional = new views::Label(user_email_string); |
| 328 additional->SetFontList( |
| 329 ui::ResourceBundle::GetSharedInstance().GetFontList( |
| 330 ui::ResourceBundle::SmallFont)); |
| 331 additional->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 332 } |
| 333 } |
| 334 |
| 335 // Adjust text properties dependent on if it is an active or inactive user. |
| 336 if (multiprofile_index) { |
| 337 // Fade the text of non active users to 50%. |
| 338 SkColor text_color = additional->enabled_color(); |
| 339 text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2); |
| 340 if (additional) |
| 341 additional->SetDisabledColor(text_color); |
| 342 if (username) |
| 343 username->SetDisabledColor(text_color); |
| 344 } |
| 345 |
| 346 if (additional && username) { |
| 347 views::View* details = new views::View; |
| 348 details->SetLayoutManager(new views::BoxLayout( |
| 349 views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0)); |
| 350 details->AddChildView(username); |
| 351 details->AddChildView(additional); |
| 352 AddChildView(details); |
| 353 } else { |
| 354 if (username) |
| 355 AddChildView(username); |
| 356 if (additional) |
| 357 AddChildView(additional); |
| 358 } |
| 359 } |
| 360 |
| 361 views::View* UserCardView::CreateIcon(user::LoginStatus login_status, |
| 362 int multiprofile_index) { |
| 363 RoundedImageView* icon = |
| 364 new RoundedImageView(kTrayAvatarCornerRadius, multiprofile_index == 0); |
| 365 if (login_status == user::LOGGED_IN_GUEST) { |
| 366 icon->SetImage(*ui::ResourceBundle::GetSharedInstance() |
| 367 .GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON) |
| 368 .ToImageSkia(), |
| 369 gfx::Size(kTrayAvatarSize, kTrayAvatarSize)); |
| 370 } else { |
| 371 SessionStateDelegate* delegate = |
| 372 Shell::GetInstance()->session_state_delegate(); |
| 373 content::BrowserContext* context = |
| 374 delegate->GetBrowserContextByIndex(multiprofile_index); |
| 375 icon->SetImage(delegate->GetUserImage(context), |
| 376 gfx::Size(kTrayAvatarSize, kTrayAvatarSize)); |
| 377 } |
| 378 return icon; |
| 379 } |
| 380 |
| 381 } // namespace tray |
| 382 } // namespace ash |
OLD | NEW |