1 // Copyright (c) 2011 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 "chrome/browser/chromeos/login/new_user_view.h" 6 7 #include <signal.h> 8 #include <sys/types.h> 9 10 #include <algorithm> 11 #include <vector> 12 13 #include "base/callback.h" 14 #include "base/command_line.h" 15 #include "base/message_loop.h" 16 #include "base/process_util.h" 17 #include "base/string_util.h" 18 #include "base/utf_string_conversions.h" 19 #include "chrome/browser/browser_process.h" 20 #include "chrome/browser/chromeos/cros/cros_library.h" 21 #include "chrome/browser/chromeos/login/rounded_rect_painter.h" 22 #include "chrome/browser/chromeos/login/textfield_with_margin.h" 23 #include "chrome/browser/chromeos/login/wizard_accessibility_helper.h" 24 #include "chrome/browser/chromeos/user_cros_settings_provider.h" 25 #include "chrome/browser/chromeos/views/copy_background.h" 26 #include "chrome/browser/prefs/pref_service.h" 27 #include "chrome/common/pref_names.h" 28 #include "grit/app_resources.h" 29 #include "grit/chromium_strings.h" 30 #include "grit/generated_resources.h" 31 #include "ui/base/keycodes/keyboard_codes.h" 32 #include "ui/base/l10n/l10n_util.h" 33 #include "ui/base/resource/resource_bundle.h" 34 #include "ui/gfx/font.h" 35 #include "views/controls/button/menu_button.h" 36 #include "views/controls/label.h" 37 #include "views/controls/textfield/textfield.h" 38 #include "views/controls/throbber.h" 39 #include "views/widget/widget_gtk.h" 40 41 using views::View; 42 43 namespace { 44 45 const int kTextfieldWidth = 230; 46 const int kSplitterHeight = 1; 47 const int kTitlePad = 20; 48 const int kRowPad = 13; 49 const int kBottomPad = 33; 50 const int kLeftPad = 33; 51 const int kColumnPad = 7; 52 const int kLanguagesMenuHeight = 25; 53 const int kLanguagesMenuPad = 5; 54 const SkColor kLanguagesMenuTextColor = 0xFF999999; 55 const SkColor kErrorColor = 0xFF8F384F; 56 const SkColor kSplitterUp1Color = 0xFFD0D2D3; 57 const SkColor kSplitterUp2Color = 0xFFE1E3E4; 58 const SkColor kSplitterDown1Color = 0xFFE3E6E8; 59 const SkColor kSplitterDown2Color = 0xFFEAEDEE; 60 const char kDefaultDomain[] = "@gmail.com"; 61 62 // Textfield that adds domain to the entered username if focus is lost and 63 // username doesn't have full domain. 64 class UsernameField : public chromeos::TextfieldWithMargin { 65 public: 66 explicit UsernameField(chromeos::NewUserView* controller) 67 : controller_(controller) {} 68 69 // views::Textfield overrides: 70 virtual void OnBlur() OVERRIDE { 71 string16 user_input; 72 bool was_trim = TrimWhitespace(text(), TRIM_ALL, &user_input) != TRIM_NONE; 73 if (!user_input.empty()) { 74 std::string username = UTF16ToUTF8(user_input); 75 76 if (username.find('@') == std::string::npos) { 77 username += kDefaultDomain; 78 SetText(UTF8ToUTF16(username)); 79 was_trim = false; 80 } 81 } 82 83 if (was_trim) 84 SetText(user_input); 85 } 86 87 // Overridden from views::View: 88 virtual bool OnKeyPressed(const views::KeyEvent& e) OVERRIDE { 89 if (e.key_code() == ui::VKEY_LEFT) { 90 return controller_->NavigateAway(); 91 } 92 return TextfieldWithMargin::OnKeyPressed(e); 93 } 94 95 private: 96 chromeos::NewUserView* controller_; 97 DISALLOW_COPY_AND_ASSIGN(UsernameField); 98 }; 99 100 } // namespace 101 102 namespace chromeos { 103 104 NewUserView::NewUserView(Delegate* delegate, 105 bool need_border, 106 bool need_guest_link) 107 : username_field_(NULL), 108 password_field_(NULL), 109 title_label_(NULL), 110 title_hint_label_(NULL), 111 splitter_up1_(NULL), 112 splitter_up2_(NULL), 113 splitter_down1_(NULL), 114 splitter_down2_(NULL), 115 sign_in_button_(NULL), 116 guest_link_(NULL), 117 create_account_link_(NULL), 118 languages_menubutton_(NULL), 119 accel_focus_pass_(ui::VKEY_P, false, false, true), 120 accel_focus_user_(ui::VKEY_U, false, false, true), 121 accel_enterprise_enrollment_(ui::VKEY_E, false, true, true), 122 accel_login_off_the_record_(ui::VKEY_B, false, false, true), 123 accel_toggle_accessibility_(WizardAccessibilityHelper::GetAccelerator()), 124 delegate_(delegate), 125 ALLOW_THIS_IN_INITIALIZER_LIST(focus_grabber_factory_(this)), 126 login_in_process_(false), 127 need_border_(need_border), 128 need_guest_link_(false), 129 need_create_account_(false), 130 languages_menubutton_order_(-1), 131 sign_in_button_order_(-1) { 132 if (UserCrosSettingsProvider::cached_allow_guest()) { 133 need_create_account_ = true; 134 if (need_guest_link) 135 need_guest_link_ = true; 136 } 137 } 138 139 NewUserView::~NewUserView() { 140 } 141 142 void NewUserView::Init() { 143 if (need_border_) { 144 // Use rounded rect background. 145 set_border(CreateWizardBorder(&BorderDefinition::kUserBorder)); 146 views::Painter* painter = CreateWizardPainter( 147 &BorderDefinition::kUserBorder); 148 set_background(views::Background::CreateBackgroundPainter(true, painter)); 149 } 150 151 title_label_ = new views::Label(); 152 title_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 153 title_label_->SetMultiLine(true); 154 AddChildView(title_label_); 155 156 title_hint_label_ = new views::Label(); 157 title_hint_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 158 title_hint_label_->SetColor(SK_ColorGRAY); 159 title_hint_label_->SetMultiLine(true); 160 AddChildView(title_hint_label_); 161 162 splitter_up1_ = CreateSplitter(kSplitterUp1Color); 163 splitter_up2_ = CreateSplitter(kSplitterUp2Color); 164 splitter_down1_ = CreateSplitter(kSplitterDown1Color); 165 splitter_down2_ = CreateSplitter(kSplitterDown2Color); 166 167 username_field_ = new UsernameField(this); 168 username_field_->set_background(new CopyBackground(this)); 169 username_field_->SetAccessibleName( 170 l10n_util::GetStringUTF16(IDS_CHROMEOS_ACC_USERNAME_LABEL)); 171 AddChildView(username_field_); 172 173 password_field_ = new TextfieldWithMargin(views::Textfield::STYLE_PASSWORD); 174 password_field_->set_background(new CopyBackground(this)); 175 AddChildView(password_field_); 176 177 language_switch_menu_.InitLanguageMenu(); 178 179 RecreatePeculiarControls(); 180 181 AddChildView(sign_in_button_); 182 if (need_guest_link_) { 183 InitLink(&guest_link_); 184 } 185 if (need_create_account_) { 186 InitLink(&create_account_link_); 187 } 188 AddChildView(languages_menubutton_); 189 190 // Set up accelerators. 191 AddAccelerator(accel_focus_user_); 192 AddAccelerator(accel_focus_pass_); 193 AddAccelerator(accel_enterprise_enrollment_); 194 AddAccelerator(accel_login_off_the_record_); 195 AddAccelerator(accel_toggle_accessibility_); 196 197 OnLocaleChanged(); 198 199 // Controller to handle events from textfields 200 username_field_->SetController(this); 201 password_field_->SetController(this); 202 if (!CrosLibrary::Get()->EnsureLoaded()) { 203 EnableInputControls(false); 204 } 205 206 // The 'Sign in' button should be disabled when there is no text in the 207 // username and password fields. 208 sign_in_button_->SetEnabled(false); 209 } 210 211 bool NewUserView::AcceleratorPressed(const views::Accelerator& accelerator) { 212 if (accelerator == accel_focus_user_) { 213 username_field_->RequestFocus(); 214 } else if (accelerator == accel_focus_pass_) { 215 password_field_->RequestFocus(); 216 } else if (accelerator == accel_enterprise_enrollment_) { 217 delegate_->OnStartEnterpriseEnrollment(); 218 } else if (accelerator == accel_login_off_the_record_) { 219 delegate_->OnLoginAsGuest(); 220 } else if (accelerator == accel_toggle_accessibility_) { 221 WizardAccessibilityHelper::GetInstance()->ToggleAccessibility(); 222 } else { 223 return false; 224 } 225 return true; 226 } 227 228 void NewUserView::RecreatePeculiarControls() { 229 // PreferredSize reported by MenuButton (and TextField) is not able 230 // to shrink, only grow; so recreate on text change. 231 delete languages_menubutton_; 232 languages_menubutton_ = new views::MenuButton( 233 NULL, std::wstring(), &language_switch_menu_, true); 234 languages_menubutton_->set_menu_marker( 235 ResourceBundle::GetSharedInstance().GetBitmapNamed( 236 IDR_MENU_DROPARROW_SHARP)); 237 languages_menubutton_->SetEnabledColor(kLanguagesMenuTextColor); 238 languages_menubutton_->SetFocusable(true); 239 languages_menubutton_->SetEnabled(!g_browser_process->local_state()-> 240 IsManagedPreference(prefs::kApplicationLocale)); 241 242 // There is no way to get native button preferred size after the button was 243 // sized so delete and recreate the button on text update. 244 delete sign_in_button_; 245 sign_in_button_ = new login::WideButton(this, std::wstring()); 246 UpdateSignInButtonState(); 247 248 if (!CrosLibrary::Get()->EnsureLoaded()) 249 sign_in_button_->SetEnabled(false); 250 } 251 252 void NewUserView::UpdateSignInButtonState() { 253 bool enabled = !username_field_->text().empty() && 254 !password_field_->text().empty(); 255 sign_in_button_->SetEnabled(enabled); 256 } 257 258 views::View* NewUserView::CreateSplitter(SkColor color) { 259 views::View* splitter = new views::View(); 260 splitter->set_background(views::Background::CreateSolidBackground(color)); 261 AddChildView(splitter); 262 return splitter; 263 } 264 265 void NewUserView::AddChildView(View* view) { 266 // languages_menubutton_ and sign_in_button_ are recreated on text change, 267 // so we restore their original position in layout. 268 if (view == languages_menubutton_) { 269 if (languages_menubutton_order_ < 0) { 270 languages_menubutton_order_ = child_count(); 271 } 272 views::View::AddChildViewAt(view, languages_menubutton_order_); 273 } else if (view == sign_in_button_) { 274 if (sign_in_button_order_ < 0) { 275 sign_in_button_order_ = child_count(); 276 } 277 views::View::AddChildViewAt(view, sign_in_button_order_); 278 } else { 279 views::View::AddChildView(view); 280 } 281 } 282 283 void NewUserView::UpdateLocalizedStringsAndFonts() { 284 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 285 gfx::Font title_font = rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont( 286 kLoginTitleFontDelta); 287 const gfx::Font& title_hint_font = rb.GetFont(ResourceBundle::BoldFont); 288 const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont); 289 290 title_label_->SetFont(title_font); 291 title_label_->SetText(UTF16ToWide( 292 l10n_util::GetStringUTF16(IDS_LOGIN_TITLE))); 293 title_hint_label_->SetFont(title_hint_font); 294 title_hint_label_->SetText(UTF16ToWide( 295 l10n_util::GetStringUTF16(IDS_LOGIN_TITLE_HINT))); 296 SetAndCorrectTextfieldFont(username_field_, base_font); 297 username_field_->set_text_to_display_when_empty( 298 l10n_util::GetStringUTF16(IDS_LOGIN_USERNAME)); 299 SetAndCorrectTextfieldFont(password_field_, base_font); 300 password_field_->set_text_to_display_when_empty( 301 l10n_util::GetStringUTF16(IDS_LOGIN_PASSWORD)); 302 sign_in_button_->SetLabel(UTF16ToWide( 303 l10n_util::GetStringUTF16(IDS_LOGIN_BUTTON))); 304 if (need_guest_link_) { 305 guest_link_->SetFont(base_font); 306 guest_link_->SetText(UTF16ToWide( 307 l10n_util::GetStringUTF16(IDS_BROWSE_WITHOUT_SIGNING_IN_BUTTON))); 308 } 309 if (need_create_account_) { 310 create_account_link_->SetFont(base_font); 311 create_account_link_->SetText( 312 UTF16ToWide(l10n_util::GetStringUTF16(IDS_CREATE_ACCOUNT_BUTTON))); 313 } 314 delegate_->ClearErrors(); 315 languages_menubutton_->SetText( 316 UTF16ToWide(language_switch_menu_.GetCurrentLocaleName())); 317 } 318 319 void NewUserView::OnLocaleChanged() { 320 RecreatePeculiarControls(); 321 UpdateLocalizedStringsAndFonts(); 322 AddChildView(sign_in_button_); 323 AddChildView(languages_menubutton_); 324 325 Layout(); 326 SchedulePaint(); 327 RequestFocus(); 328 } 329 330 void NewUserView::RequestFocus() { 331 if (username_field_->text().empty()) 332 username_field_->RequestFocus(); 333 else 334 password_field_->RequestFocus(); 335 } 336 337 void NewUserView::ViewHierarchyChanged(bool is_add, 338 View *parent, 339 View *child) { 340 if (is_add && (child == username_field_ || child == password_field_)) { 341 MessageLoop::current()->PostTask(FROM_HERE, 342 focus_grabber_factory_.NewRunnableMethod( 343 &NewUserView::Layout)); 344 } 345 } 346 347 // Sets the bounds of the view, using x and y as the origin. 348 // The width is determined by the min of width and the preferred size 349 // of the view, unless force_width is true in which case it is always used. 350 // The height is gotten from the preferred size and returned. 351 static int setViewBounds( 352 views::View* view, int x, int y, int width, bool force_width) { 353 gfx::Size pref_size = view->GetPreferredSize(); 354 if (!force_width) { 355 if (pref_size.width() < width) { 356 width = pref_size.width(); 357 } 358 } 359 int height = pref_size.height(); 360 view->SetBounds(x, y, width, height); 361 return height; 362 } 363 364 void NewUserView::Layout() { 365 gfx::Insets insets = GetInsets(); 366 367 // Place language selection in top right corner. 368 int x = std::max(0, 369 this->width() - insets.right() - 370 languages_menubutton_->GetPreferredSize().width() - kColumnPad); 371 int y = insets.top() + kLanguagesMenuPad; 372 int width = std::min(this->width() - insets.width() - 2 * kColumnPad, 373 languages_menubutton_->GetPreferredSize().width()); 374 int height = kLanguagesMenuHeight; 375 languages_menubutton_->SetBounds(x, y, width, height); 376 y += height + kTitlePad; 377 378 width = std::min(this->width() - insets.width() - 2 * kColumnPad, 379 kTextfieldWidth); 380 x = insets.left() + kLeftPad; 381 int max_width = this->width() - x - std::max(insets.right(), x); 382 title_label_->SizeToFit(max_width); 383 title_hint_label_->SizeToFit(max_width); 384 385 // Top align title and title hint. 386 y += setViewBounds(title_label_, x, y, max_width, false); 387 y += setViewBounds(title_hint_label_, x, y, max_width, false); 388 int title_end = y + kTitlePad; 389 390 splitter_up1_->SetBounds(0, title_end, this->width(), kSplitterHeight); 391 splitter_up2_->SetBounds(0, title_end + 1, this->width(), kSplitterHeight); 392 393 // Bottom controls. 394 int links_height = 0; 395 if (need_create_account_) 396 links_height += create_account_link_->GetPreferredSize().height(); 397 if (need_guest_link_) 398 links_height += guest_link_->GetPreferredSize().height(); 399 if (need_create_account_ && need_guest_link_) 400 links_height += kRowPad; 401 y = this->height() - insets.bottom() - kBottomPad; 402 if (links_height) 403 y -= links_height + kBottomPad; 404 int bottom_start = y; 405 406 splitter_down1_->SetBounds(0, y, this->width(), kSplitterHeight); 407 splitter_down2_->SetBounds(0, y + 1, this->width(), kSplitterHeight); 408 409 y += kBottomPad; 410 if (need_guest_link_) { 411 y += setViewBounds(guest_link_, 412 x, y, max_width, false) + kRowPad; 413 } 414 if (need_create_account_) { 415 y += setViewBounds(create_account_link_, x, y, max_width, false); 416 } 417 418 // Center main controls. 419 height = username_field_->GetPreferredSize().height() + 420 password_field_->GetPreferredSize().height() + 421 sign_in_button_->GetPreferredSize().height() + 422 2 * kRowPad; 423 y = title_end + (bottom_start - title_end - height) / 2; 424 425 y += setViewBounds(username_field_, x, y, width, true) + kRowPad; 426 y += setViewBounds(password_field_, x, y, width, true) + kRowPad; 427 428 int sign_in_button_width = sign_in_button_->GetPreferredSize().width(); 429 setViewBounds(sign_in_button_, x, y, sign_in_button_width,true); 430 431 SchedulePaint(); 432 } 433 434 gfx::Size NewUserView::GetPreferredSize() { 435 return need_guest_link_ ? 436 gfx::Size(kNewUserPodFullWidth, kNewUserPodFullHeight) : 437 gfx::Size(kNewUserPodSmallWidth, kNewUserPodSmallHeight); 438 } 439 440 void NewUserView::SetUsername(const std::string& username) { 441 username_field_->SetText(UTF8ToUTF16(username)); 442 } 443 444 void NewUserView::SetPassword(const std::string& password) { 445 password_field_->SetText(UTF8ToUTF16(password)); 446 } 447 448 void NewUserView::Login() { 449 if (login_in_process_ || 450 username_field_->text().empty() || 451 password_field_->text().empty()) { 452 UpdateSignInButtonState(); 453 return; 454 } 455 456 login_in_process_ = true; 457 std::string username = UTF16ToUTF8(username_field_->text()); 458 // todo(cmasone) Need to sanitize memory used to store password. 459 std::string password = UTF16ToUTF8(password_field_->text()); 460 461 if (username.find('@') == std::string::npos) { 462 username += kDefaultDomain; 463 username_field_->SetText(UTF8ToUTF16(username)); 464 } 465 466 delegate_->OnLogin(username, password); 467 } 468 469 // Sign in button causes a login attempt. 470 void NewUserView::ButtonPressed(views::Button* sender, 471 const views::Event& event) { 472 DCHECK(sender == sign_in_button_); 473 Login(); 474 } 475 476 void NewUserView::LinkActivated(views::Link* source, int event_flags) { 477 if (source == create_account_link_) { 478 delegate_->OnCreateAccount(); 479 } else if (source == guest_link_) { 480 delegate_->OnLoginAsGuest(); 481 } 482 } 483 484 void NewUserView::ClearAndFocusControls() { 485 login_in_process_ = false; 486 SetUsername(std::string()); 487 SetPassword(std::string()); 488 username_field_->RequestFocus(); 489 UpdateSignInButtonState(); 490 } 491 492 void NewUserView::ClearAndFocusPassword() { 493 login_in_process_ = false; 494 SetPassword(std::string()); 495 password_field_->RequestFocus(); 496 UpdateSignInButtonState(); 497 } 498 499 gfx::Rect NewUserView::GetMainInputScreenBounds() const { 500 return GetUsernameBounds(); 501 } 502 503 gfx::Rect NewUserView::CalculateThrobberBounds(views::Throbber* throbber) { 504 DCHECK(password_field_); 505 DCHECK(sign_in_button_); 506 507 gfx::Size throbber_size = throbber->GetPreferredSize(); 508 int x = password_field_->x(); 509 x += password_field_->width() - throbber_size.width(); 510 int y = sign_in_button_->y(); 511 y += (sign_in_button_->height() - throbber_size.height()) / 2; 512 513 return gfx::Rect(gfx::Point(x, y), throbber_size); 514 } 515 516 gfx::Rect NewUserView::GetPasswordBounds() const { 517 return password_field_->GetScreenBounds(); 518 } 519 520 gfx::Rect NewUserView::GetUsernameBounds() const { 521 return username_field_->GetScreenBounds(); 522 } 523 524 bool NewUserView::HandleKeyEvent(views::Textfield* sender, 525 const views::KeyEvent& key_event) { 526 if (!CrosLibrary::Get()->EnsureLoaded() || login_in_process_) 527 return false; 528 529 if (key_event.key_code() == ui::VKEY_RETURN) { 530 if (!username_field_->text().empty() && !password_field_->text().empty()) 531 Login(); 532 // Return true so that processing ends 533 return true; 534 } 535 delegate_->ClearErrors(); 536 // Return false so that processing does not end 537 return false; 538 } 539 540 void NewUserView::ContentsChanged(views::Textfield* sender, 541 const string16& new_contents) { 542 UpdateSignInButtonState(); 543 if (!new_contents.empty()) 544 delegate_->ClearErrors(); 545 } 546 547 void NewUserView::EnableInputControls(bool enabled) { 548 languages_menubutton_->SetEnabled(enabled && 549 !g_browser_process->local_state()->IsManagedPreference( 550 prefs::kApplicationLocale)); 551 username_field_->SetEnabled(enabled); 552 password_field_->SetEnabled(enabled); 553 if (need_guest_link_) { 554 guest_link_->SetEnabled(enabled); 555 } 556 if (need_create_account_) { 557 create_account_link_->SetEnabled(enabled); 558 } 559 UpdateSignInButtonState(); 560 } 561 562 bool NewUserView::NavigateAway() { 563 if (username_field_->text().empty() && 564 password_field_->text().empty()) { 565 delegate_->NavigateAway(); 566 return true; 567 } else { 568 return false; 569 } 570 } 571 572 void NewUserView::InitLink(views::Link** link) { 573 *link = new views::Link(std::wstring()); 574 (*link)->SetController(this); 575 (*link)->SetNormalColor(login::kLinkColor); 576 (*link)->SetHighlightedColor(login::kLinkColor); 577 AddChildView(*link); 578 } 579 580 } // namespace chromeos 581