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