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/user_controller.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/chromeos/login/existing_user_view.h"
12#include "chrome/browser/chromeos/login/guest_user_view.h"
13#include "chrome/browser/chromeos/login/helper.h"
14#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
15#include "chrome/browser/chromeos/login/user_view.h"
16#include "chrome/browser/chromeos/login/username_view.h"
17#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
18#include "chrome/browser/chromeos/login/wizard_controller.h"
19#include "chrome/browser/chromeos/user_cros_settings_provider.h"
20#include "grit/generated_resources.h"
21#include "grit/theme_resources.h"
22#include "third_party/cros/chromeos_wm_ipc_enums.h"
23#include "ui/base/l10n/l10n_util.h"
24#include "ui/base/resource/resource_bundle.h"
25#include "ui/gfx/canvas.h"
26#include "views/background.h"
27#include "views/controls/button/native_button.h"
28#include "views/controls/label.h"
29#include "views/controls/throbber.h"
30#include "views/focus/focus_manager.h"
31#include "views/painter.h"
32#include "views/screen.h"
33#include "views/widget/root_view.h"
34#include "views/widget/widget_gtk.h"
35
36using views::Widget;
37using views::WidgetGtk;
38
39namespace chromeos {
40
41namespace {
42
43// Gap between the border around the image/buttons and user name.
44const int kUserNameGap = 4;
45
46// Approximate height of controls window, this constant is used in new user
47// case to make border window size close to existing users.
48#if defined(CROS_FONTS_USING_BCI)
49const int kControlsHeight = 31;
50#else
51const int kControlsHeight = 28;
52#endif
53
54// Vertical interval between the image and the textfield.
55const int kVerticalIntervalSize = 10;
56
57// A window for controls that sets focus to the view when
58// it first got focus.
59class ControlsWindow : public WidgetGtk {
60 public:
61  explicit ControlsWindow(views::View* initial_focus_view)
62      : WidgetGtk(WidgetGtk::TYPE_WINDOW),
63        initial_focus_view_(initial_focus_view) {
64  }
65
66 private:
67  // WidgetGtk overrides:
68  virtual void SetInitialFocus() OVERRIDE {
69    if (initial_focus_view_)
70      initial_focus_view_->RequestFocus();
71  }
72
73  virtual void OnMap(GtkWidget* widget) OVERRIDE {
74    // For some reason, Controls window never gets first expose event,
75    // which makes WM believe that the login screen is not ready.
76    // This is a workaround to let WM show the login screen. While
77    // this may allow WM to show unpainted window, we haven't seen any
78    // issue (yet). We will not investigate this further because we're
79    // migrating to different implemention (WebUI).
80    UpdateFreezeUpdatesProperty(GTK_WINDOW(GetNativeView()),
81                                false /* remove */);
82  }
83
84  views::View* initial_focus_view_;
85
86  DISALLOW_COPY_AND_ASSIGN(ControlsWindow);
87};
88
89// Widget that notifies window manager about clicking on itself.
90// Doesn't send anything if user is selected.
91class ClickNotifyingWidget : public views::WidgetGtk {
92 public:
93  ClickNotifyingWidget(views::WidgetGtk::Type type,
94                       UserController* controller)
95      : WidgetGtk(type),
96        controller_(controller) {
97  }
98
99 private:
100  gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) {
101    if (!controller_->IsUserSelected())
102      controller_->SelectUserRelative(0);
103
104    return views::WidgetGtk::OnButtonPress(widget, event);
105  }
106
107  UserController* controller_;
108
109  DISALLOW_COPY_AND_ASSIGN(ClickNotifyingWidget);
110};
111
112void CloseWindow(views::Widget* window) {
113  if (!window)
114    return;
115  window->set_widget_delegate(NULL);
116  window->Close();
117}
118
119}  // namespace
120
121using login::kBorderSize;
122using login::kUserImageSize;
123
124// static
125const int UserController::kPadding = 30;
126
127// Max size needed when an entry is not selected.
128const int UserController::kUnselectedSize = 100;
129const int UserController::kNewUserUnselectedSize = 42;
130
131////////////////////////////////////////////////////////////////////////////////
132// UserController, public:
133
134UserController::UserController(Delegate* delegate, bool is_guest)
135    : user_index_(-1),
136      is_user_selected_(false),
137      is_new_user_(!is_guest),
138      is_guest_(is_guest),
139      is_owner_(false),
140      show_name_tooltip_(false),
141      delegate_(delegate),
142      controls_window_(NULL),
143      image_window_(NULL),
144      border_window_(NULL),
145      label_window_(NULL),
146      unselected_label_window_(NULL),
147      user_view_(NULL),
148      label_view_(NULL),
149      unselected_label_view_(NULL),
150      user_input_(NULL),
151      throbber_host_(NULL) {
152}
153
154UserController::UserController(Delegate* delegate,
155                               const UserManager::User& user)
156    : user_index_(-1),
157      is_user_selected_(false),
158      is_new_user_(false),
159      is_guest_(false),
160      // Empty 'cached_owner()' means that owner hasn't been cached yet, not
161      // that owner has an empty email.
162      is_owner_(user.email() == UserCrosSettingsProvider::cached_owner()),
163      show_name_tooltip_(false),
164      user_(user),
165      delegate_(delegate),
166      controls_window_(NULL),
167      image_window_(NULL),
168      border_window_(NULL),
169      label_window_(NULL),
170      unselected_label_window_(NULL),
171      user_view_(NULL),
172      label_view_(NULL),
173      unselected_label_view_(NULL),
174      user_input_(NULL),
175      throbber_host_(NULL) {
176  DCHECK(!user.email().empty());
177}
178
179UserController::~UserController() {
180  // Reset the widget delegate of every window to NULL, so the user
181  // controller will not get notified about the active window change.
182  // See also crosbug.com/7400.
183  CloseWindow(controls_window_);
184  CloseWindow(image_window_);
185  CloseWindow(border_window_);
186  CloseWindow(label_window_);
187  CloseWindow(unselected_label_window_);
188}
189
190void UserController::Init(int index,
191                          int total_user_count,
192                          bool need_browse_without_signin) {
193  int controls_height = 0;
194  int controls_width = 0;
195  controls_window_ =
196      CreateControlsWindow(index, &controls_width, &controls_height,
197                           need_browse_without_signin);
198  image_window_ = CreateImageWindow(index);
199  CreateBorderWindow(index, total_user_count, controls_width, controls_height);
200  label_window_ = CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_LABEL);
201  unselected_label_window_ =
202      CreateLabelWindow(index, WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL);
203}
204
205void UserController::ClearAndEnableFields() {
206  user_input_->EnableInputControls(true);
207  user_input_->ClearAndFocusControls();
208  StopThrobber();
209}
210
211void UserController::ClearAndEnablePassword() {
212  // Somehow focus manager thinks that textfield is still focused but the
213  // textfield doesn't know that. So we clear focus for focus manager so it
214  // sets focus on the textfield again.
215  // TODO(avayvod): Fix the actual issue.
216  views::FocusManager* focus_manager = controls_window_->GetFocusManager();
217  if (focus_manager)
218    focus_manager->ClearFocus();
219  user_input_->EnableInputControls(true);
220  user_input_->ClearAndFocusPassword();
221  StopThrobber();
222}
223
224void UserController::EnableNameTooltip(bool enable) {
225  name_tooltip_enabled_ = enable;
226  std::wstring tooltip_text;
227  if (enable)
228    tooltip_text = GetNameTooltip();
229
230  if (user_view_)
231    user_view_->SetTooltipText(tooltip_text);
232  if (label_view_)
233    label_view_->SetTooltipText(tooltip_text);
234  if (unselected_label_view_)
235    unselected_label_view_->SetTooltipText(tooltip_text);
236}
237
238gfx::Rect UserController::GetMainInputScreenBounds() const {
239  return user_input_->GetMainInputScreenBounds();
240}
241
242void UserController::OnUserImageChanged(UserManager::User* user) {
243  if (user_.email() != user->email())
244    return;
245  user_.set_image(user->image());
246  // Controller might exist without windows,
247  // i.e. if user pod doesn't fit on the screen.
248  if (user_view_)
249    user_view_->SetImage(user_.image(), user_.image());
250}
251
252void UserController::SelectUserRelative(int shift) {
253  delegate_->SelectUser(user_index() + shift);
254}
255
256void UserController::StartThrobber() {
257  throbber_host_->StartThrobber();
258}
259
260void UserController::StopThrobber() {
261  throbber_host_->StopThrobber();
262}
263
264void UserController::UpdateUserCount(int index, int total_user_count) {
265  user_index_ = index;
266  std::vector<int> params;
267  params.push_back(index);
268  params.push_back(total_user_count);
269  params.push_back(is_new_user_ ? kNewUserUnselectedSize : kUnselectedSize);
270  params.push_back(kPadding);
271  WmIpc::instance()->SetWindowType(
272      border_window_->GetNativeView(),
273      WM_IPC_WINDOW_LOGIN_BORDER,
274      &params);
275}
276
277std::string UserController::GetAccessibleUserLabel() {
278  if (is_new_user_)
279    return l10n_util::GetStringUTF8(IDS_ADD_USER);
280  if (is_guest_)
281    return l10n_util::GetStringUTF8(IDS_GUEST);
282  return user_.email();
283}
284
285////////////////////////////////////////////////////////////////////////////////
286// UserController, WidgetDelegate implementation:
287//
288void UserController::OnWidgetActivated(bool active) {
289  is_user_selected_ = active;
290  if (active) {
291    delegate_->OnUserSelected(this);
292    user_view_->SetRemoveButtonVisible(
293        !is_new_user_ && !is_guest_ && !is_owner_);
294  } else {
295    user_view_->SetRemoveButtonVisible(false);
296    delegate_->ClearErrors();
297  }
298}
299
300////////////////////////////////////////////////////////////////////////////////
301// UserController, NewUserView::Delegate implementation:
302//
303void UserController::OnLogin(const std::string& username,
304                             const std::string& password) {
305  if (is_new_user_)
306    user_.set_email(username);
307
308  user_input_->EnableInputControls(false);
309  StartThrobber();
310
311  delegate_->Login(this, UTF8ToUTF16(password));
312}
313
314void UserController::OnCreateAccount() {
315  user_input_->EnableInputControls(false);
316  StartThrobber();
317
318  delegate_->CreateAccount();
319}
320
321void UserController::OnStartEnterpriseEnrollment() {
322  delegate_->StartEnterpriseEnrollment();
323}
324
325void UserController::OnLoginAsGuest() {
326  user_input_->EnableInputControls(false);
327  StartThrobber();
328
329  delegate_->LoginAsGuest();
330}
331
332void UserController::ClearErrors() {
333  delegate_->ClearErrors();
334}
335
336void UserController::NavigateAway() {
337  SelectUserRelative(-1);
338}
339
340////////////////////////////////////////////////////////////////////////////////
341// UserController, UserView::Delegate implementation:
342//
343void UserController::OnLocaleChanged() {
344  // Update text tooltips on guest and new user pods.
345  if (is_guest_ || is_new_user_) {
346    if (name_tooltip_enabled_)
347      EnableNameTooltip(name_tooltip_enabled_);
348  }
349  label_view_->SetFont(GetLabelFont());
350  unselected_label_view_->SetFont(GetUnselectedLabelFont());
351}
352
353void UserController::OnRemoveUser() {
354  delegate_->RemoveUser(this);
355}
356
357////////////////////////////////////////////////////////////////////////////////
358// UserController, private:
359//
360void UserController::ConfigureLoginWindow(WidgetGtk* window,
361                                          int index,
362                                          const gfx::Rect& bounds,
363                                          chromeos::WmIpcWindowType type,
364                                          views::View* contents_view) {
365  window->MakeTransparent();
366  window->Init(NULL, bounds);
367  window->SetContentsView(contents_view);
368  window->set_widget_delegate(this);
369
370  std::vector<int> params;
371  params.push_back(index);
372  WmIpc::instance()->SetWindowType(
373      window->GetNativeView(),
374      type,
375      &params);
376
377  GdkWindow* gdk_window = window->GetNativeView()->window;
378  gdk_window_set_back_pixmap(gdk_window, NULL, false);
379
380  window->Show();
381}
382
383WidgetGtk* UserController::CreateControlsWindow(
384    int index,
385    int* width, int* height,
386    bool need_browse_without_signin) {
387  views::View* control_view;
388  if (is_new_user_) {
389    NewUserView* new_user_view =
390        new NewUserView(this, true, need_browse_without_signin);
391    new_user_view->Init();
392    control_view = new_user_view;
393    user_input_ = new_user_view;
394    throbber_host_ = new_user_view;
395  } else if (is_guest_) {
396    GuestUserView* guest_user_view = new GuestUserView(this);
397    guest_user_view->RecreateFields();
398    control_view = guest_user_view;
399    user_input_ = guest_user_view;
400    throbber_host_ = guest_user_view;
401  } else {
402    ExistingUserView* existing_user_view = new ExistingUserView(this);
403    existing_user_view->RecreateFields();
404    control_view = existing_user_view;
405    user_input_ = existing_user_view;
406    throbber_host_ = existing_user_view;
407  }
408
409  *height = kControlsHeight;
410  *width = kUserImageSize;
411  if (is_new_user_) {
412    gfx::Size size = control_view->GetPreferredSize();
413    *width = size.width();
414    *height = size.height();
415  }
416
417  WidgetGtk* window = new ControlsWindow(control_view);
418  ConfigureLoginWindow(window,
419                       index,
420                       gfx::Rect(*width, *height),
421                       WM_IPC_WINDOW_LOGIN_CONTROLS,
422                       control_view);
423  return window;
424}
425
426WidgetGtk* UserController::CreateImageWindow(int index) {
427  user_view_ = new UserView(this, true, !is_new_user_);
428
429  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
430  if (is_guest_) {
431    SkBitmap* image = rb.GetBitmapNamed(IDR_LOGIN_GUEST);
432    user_view_->SetImage(*image, *image);
433  } else if (is_new_user_) {
434    SkBitmap* image = rb.GetBitmapNamed(IDR_LOGIN_ADD_USER);
435    SkBitmap* image_hover = rb.GetBitmapNamed(IDR_LOGIN_ADD_USER_HOVER);
436    user_view_->SetImage(*image, *image_hover);
437  } else {
438    user_view_->SetImage(user_.image(), user_.image());
439  }
440
441  WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this);
442  ConfigureLoginWindow(window,
443                       index,
444                       gfx::Rect(user_view_->GetPreferredSize()),
445                       WM_IPC_WINDOW_LOGIN_IMAGE,
446                       user_view_);
447
448  return window;
449}
450
451void UserController::CreateBorderWindow(int index,
452                                        int total_user_count,
453                                        int controls_width,
454                                        int controls_height) {
455  // New user login controls window is much higher than existing user's controls
456  // window so window manager will place the control instead of image window.
457  // New user will have 0 size border.
458  int width = controls_width;
459  int height = controls_height;
460  if (!is_new_user_) {
461    width += kBorderSize * 2;
462    height += 2 * kBorderSize + kUserImageSize + kVerticalIntervalSize;
463  }
464
465  Widget::CreateParams params(Widget::CreateParams::TYPE_WINDOW);
466  params.transparent = true;
467  border_window_ = Widget::CreateWidget(params);
468  border_window_->Init(NULL, gfx::Rect(0, 0, width, height));
469  if (!is_new_user_) {
470    views::View* background_view = new views::View();
471    views::Painter* painter = CreateWizardPainter(
472        &BorderDefinition::kUserBorder);
473    background_view->set_background(
474        views::Background::CreateBackgroundPainter(true, painter));
475    border_window_->SetContentsView(background_view);
476  }
477  UpdateUserCount(index, total_user_count);
478
479  GdkWindow* gdk_window = border_window_->GetNativeView()->window;
480  gdk_window_set_back_pixmap(gdk_window, NULL, false);
481
482  border_window_->Show();
483}
484
485WidgetGtk* UserController::CreateLabelWindow(int index,
486                                             WmIpcWindowType type) {
487  std::wstring text;
488  if (is_guest_) {
489    text = std::wstring();
490  } else if (is_new_user_) {
491    // Add user should have label only in activated state.
492    // When new user is the only, label is not needed.
493    if (type == WM_IPC_WINDOW_LOGIN_LABEL && index != 0)
494      text = UTF16ToWide(l10n_util::GetStringUTF16(IDS_ADD_USER));
495  } else {
496    text = UTF8ToWide(user_.GetDisplayName());
497  }
498
499  views::Label* label = NULL;
500
501  if (is_new_user_) {
502    label = new views::Label(text);
503  } else if (type == WM_IPC_WINDOW_LOGIN_LABEL) {
504    label = UsernameView::CreateShapedUsernameView(text, false);
505  } else {
506    DCHECK(type == WM_IPC_WINDOW_LOGIN_UNSELECTED_LABEL);
507    // TODO(altimofeev): switch to the rounded username view.
508    label = UsernameView::CreateShapedUsernameView(text, true);
509  }
510
511  const gfx::Font& font = (type == WM_IPC_WINDOW_LOGIN_LABEL) ?
512      GetLabelFont() : GetUnselectedLabelFont();
513  label->SetFont(font);
514  label->SetColor(login::kTextColor);
515
516  if (type == WM_IPC_WINDOW_LOGIN_LABEL)
517    label_view_ = label;
518  else
519    unselected_label_view_ = label;
520
521  int width = (type == WM_IPC_WINDOW_LOGIN_LABEL) ?
522      kUserImageSize : kUnselectedSize;
523  if (is_new_user_) {
524    // Make label as small as possible to don't show tooltip.
525    width = 0;
526  }
527  int height = (type == WM_IPC_WINDOW_LOGIN_LABEL) ?
528      login::kSelectedLabelHeight : login::kUnselectedLabelHeight;
529  WidgetGtk* window = new ClickNotifyingWidget(WidgetGtk::TYPE_WINDOW, this);
530  ConfigureLoginWindow(window,
531                       index,
532                       gfx::Rect(0, 0, width, height),
533                       type,
534                       label);
535  return window;
536}
537
538gfx::Font UserController::GetLabelFont() {
539  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
540  return rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont(
541      kSelectedUsernameFontDelta);
542}
543
544gfx::Font UserController::GetUnselectedLabelFont() {
545  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
546  return rb.GetFont(ResourceBundle::BaseFont).DeriveFont(
547      kUnselectedUsernameFontDelta, gfx::Font::BOLD);
548}
549
550
551std::wstring UserController::GetNameTooltip() const {
552  if (is_new_user_)
553    return UTF16ToWide(l10n_util::GetStringUTF16(IDS_ADD_USER));
554  else if (is_guest_)
555    return UTF16ToWide(l10n_util::GetStringUTF16(IDS_GO_INCOGNITO_BUTTON));
556  else
557    return UTF8ToWide(user_.GetNameTooltip());
558}
559
560}  // namespace chromeos
561