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:
UsernameField(chromeos::NewUserView * controller)66 explicit UsernameField(chromeos::NewUserView* controller)
67 : controller_(controller) {}
68
69 // views::Textfield overrides:
OnBlur()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:
OnKeyPressed(const views::KeyEvent & e)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
NewUserView(Delegate * delegate,bool need_border,bool need_guest_link)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
~NewUserView()139 NewUserView::~NewUserView() {
140 }
141
Init()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
AcceleratorPressed(const views::Accelerator & accelerator)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
RecreatePeculiarControls()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
UpdateSignInButtonState()252 void NewUserView::UpdateSignInButtonState() {
253 bool enabled = !username_field_->text().empty() &&
254 !password_field_->text().empty();
255 sign_in_button_->SetEnabled(enabled);
256 }
257
CreateSplitter(SkColor color)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
AddChildView(View * view)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
UpdateLocalizedStringsAndFonts()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
OnLocaleChanged()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
RequestFocus()330 void NewUserView::RequestFocus() {
331 if (username_field_->text().empty())
332 username_field_->RequestFocus();
333 else
334 password_field_->RequestFocus();
335 }
336
ViewHierarchyChanged(bool is_add,View * parent,View * child)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.
setViewBounds(views::View * view,int x,int y,int width,bool force_width)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
Layout()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
GetPreferredSize()434 gfx::Size NewUserView::GetPreferredSize() {
435 return need_guest_link_ ?
436 gfx::Size(kNewUserPodFullWidth, kNewUserPodFullHeight) :
437 gfx::Size(kNewUserPodSmallWidth, kNewUserPodSmallHeight);
438 }
439
SetUsername(const std::string & username)440 void NewUserView::SetUsername(const std::string& username) {
441 username_field_->SetText(UTF8ToUTF16(username));
442 }
443
SetPassword(const std::string & password)444 void NewUserView::SetPassword(const std::string& password) {
445 password_field_->SetText(UTF8ToUTF16(password));
446 }
447
Login()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.
ButtonPressed(views::Button * sender,const views::Event & event)470 void NewUserView::ButtonPressed(views::Button* sender,
471 const views::Event& event) {
472 DCHECK(sender == sign_in_button_);
473 Login();
474 }
475
LinkActivated(views::Link * source,int event_flags)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
ClearAndFocusControls()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
ClearAndFocusPassword()492 void NewUserView::ClearAndFocusPassword() {
493 login_in_process_ = false;
494 SetPassword(std::string());
495 password_field_->RequestFocus();
496 UpdateSignInButtonState();
497 }
498
GetMainInputScreenBounds() const499 gfx::Rect NewUserView::GetMainInputScreenBounds() const {
500 return GetUsernameBounds();
501 }
502
CalculateThrobberBounds(views::Throbber * throbber)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
GetPasswordBounds() const516 gfx::Rect NewUserView::GetPasswordBounds() const {
517 return password_field_->GetScreenBounds();
518 }
519
GetUsernameBounds() const520 gfx::Rect NewUserView::GetUsernameBounds() const {
521 return username_field_->GetScreenBounds();
522 }
523
HandleKeyEvent(views::Textfield * sender,const views::KeyEvent & key_event)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
ContentsChanged(views::Textfield * sender,const string16 & new_contents)540 void NewUserView::ContentsChanged(views::Textfield* sender,
541 const string16& new_contents) {
542 UpdateSignInButtonState();
543 if (!new_contents.empty())
544 delegate_->ClearErrors();
545 }
546
EnableInputControls(bool enabled)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
NavigateAway()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
InitLink(views::Link ** link)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