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