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
41using views::View;
42
43namespace {
44
45const int kTextfieldWidth = 230;
46const int kSplitterHeight = 1;
47const int kTitlePad = 20;
48const int kRowPad = 13;
49const int kBottomPad = 33;
50const int kLeftPad = 33;
51const int kColumnPad = 7;
52const int kLanguagesMenuHeight = 25;
53const int kLanguagesMenuPad = 5;
54const SkColor kLanguagesMenuTextColor = 0xFF999999;
55const SkColor kErrorColor = 0xFF8F384F;
56const SkColor kSplitterUp1Color = 0xFFD0D2D3;
57const SkColor kSplitterUp2Color = 0xFFE1E3E4;
58const SkColor kSplitterDown1Color = 0xFFE3E6E8;
59const SkColor kSplitterDown2Color = 0xFFEAEDEE;
60const 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.
64class 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
102namespace chromeos {
103
104NewUserView::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
139NewUserView::~NewUserView() {
140}
141
142void 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
211bool 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
228void 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
252void NewUserView::UpdateSignInButtonState() {
253  bool enabled = !username_field_->text().empty() &&
254                 !password_field_->text().empty();
255  sign_in_button_->SetEnabled(enabled);
256}
257
258views::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
265void 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
283void 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
319void NewUserView::OnLocaleChanged() {
320  RecreatePeculiarControls();
321  UpdateLocalizedStringsAndFonts();
322  AddChildView(sign_in_button_);
323  AddChildView(languages_menubutton_);
324
325  Layout();
326  SchedulePaint();
327  RequestFocus();
328}
329
330void NewUserView::RequestFocus() {
331  if (username_field_->text().empty())
332    username_field_->RequestFocus();
333  else
334    password_field_->RequestFocus();
335}
336
337void 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.
351static 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
364void 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
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_ ||
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.
470void NewUserView::ButtonPressed(views::Button* sender,
471                                const views::Event& event) {
472  DCHECK(sender == sign_in_button_);
473  Login();
474}
475
476void 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
484void NewUserView::ClearAndFocusControls() {
485  login_in_process_ = false;
486  SetUsername(std::string());
487  SetPassword(std::string());
488  username_field_->RequestFocus();
489  UpdateSignInButtonState();
490}
491
492void NewUserView::ClearAndFocusPassword() {
493  login_in_process_ = false;
494  SetPassword(std::string());
495  password_field_->RequestFocus();
496  UpdateSignInButtonState();
497}
498
499gfx::Rect NewUserView::GetMainInputScreenBounds() const {
500  return GetUsernameBounds();
501}
502
503gfx::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
516gfx::Rect NewUserView::GetPasswordBounds() const {
517  return password_field_->GetScreenBounds();
518}
519
520gfx::Rect NewUserView::GetUsernameBounds() const {
521  return username_field_->GetScreenBounds();
522}
523
524bool 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
540void NewUserView::ContentsChanged(views::Textfield* sender,
541                                  const string16& new_contents) {
542  UpdateSignInButtonState();
543  if (!new_contents.empty())
544    delegate_->ClearErrors();
545}
546
547void 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
562bool 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
572void 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