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
36 using views::Widget;
37 using views::WidgetGtk;
38
39 namespace chromeos {
40
41 namespace {
42
43 // Gap between the border around the image/buttons and user name.
44 const 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)
49 const int kControlsHeight = 31;
50 #else
51 const int kControlsHeight = 28;
52 #endif
53
54 // Vertical interval between the image and the textfield.
55 const int kVerticalIntervalSize = 10;
56
57 // A window for controls that sets focus to the view when
58 // it first got focus.
59 class ControlsWindow : public WidgetGtk {
60 public:
ControlsWindow(views::View * initial_focus_view)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:
SetInitialFocus()68 virtual void SetInitialFocus() OVERRIDE {
69 if (initial_focus_view_)
70 initial_focus_view_->RequestFocus();
71 }
72
OnMap(GtkWidget * widget)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.
91 class ClickNotifyingWidget : public views::WidgetGtk {
92 public:
ClickNotifyingWidget(views::WidgetGtk::Type type,UserController * controller)93 ClickNotifyingWidget(views::WidgetGtk::Type type,
94 UserController* controller)
95 : WidgetGtk(type),
96 controller_(controller) {
97 }
98
99 private:
OnButtonPress(GtkWidget * widget,GdkEventButton * event)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
CloseWindow(views::Widget * window)112 void CloseWindow(views::Widget* window) {
113 if (!window)
114 return;
115 window->set_widget_delegate(NULL);
116 window->Close();
117 }
118
119 } // namespace
120
121 using login::kBorderSize;
122 using login::kUserImageSize;
123
124 // static
125 const int UserController::kPadding = 30;
126
127 // Max size needed when an entry is not selected.
128 const int UserController::kUnselectedSize = 100;
129 const int UserController::kNewUserUnselectedSize = 42;
130
131 ////////////////////////////////////////////////////////////////////////////////
132 // UserController, public:
133
UserController(Delegate * delegate,bool is_guest)134 UserController::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
UserController(Delegate * delegate,const UserManager::User & user)154 UserController::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
~UserController()179 UserController::~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
Init(int index,int total_user_count,bool need_browse_without_signin)190 void 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
ClearAndEnableFields()205 void UserController::ClearAndEnableFields() {
206 user_input_->EnableInputControls(true);
207 user_input_->ClearAndFocusControls();
208 StopThrobber();
209 }
210
ClearAndEnablePassword()211 void 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
EnableNameTooltip(bool enable)224 void 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
GetMainInputScreenBounds() const238 gfx::Rect UserController::GetMainInputScreenBounds() const {
239 return user_input_->GetMainInputScreenBounds();
240 }
241
OnUserImageChanged(UserManager::User * user)242 void 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
SelectUserRelative(int shift)252 void UserController::SelectUserRelative(int shift) {
253 delegate_->SelectUser(user_index() + shift);
254 }
255
StartThrobber()256 void UserController::StartThrobber() {
257 throbber_host_->StartThrobber();
258 }
259
StopThrobber()260 void UserController::StopThrobber() {
261 throbber_host_->StopThrobber();
262 }
263
UpdateUserCount(int index,int total_user_count)264 void 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 ¶ms);
275 }
276
GetAccessibleUserLabel()277 std::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 //
OnWidgetActivated(bool active)288 void 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 //
OnLogin(const std::string & username,const std::string & password)303 void 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
OnCreateAccount()314 void UserController::OnCreateAccount() {
315 user_input_->EnableInputControls(false);
316 StartThrobber();
317
318 delegate_->CreateAccount();
319 }
320
OnStartEnterpriseEnrollment()321 void UserController::OnStartEnterpriseEnrollment() {
322 delegate_->StartEnterpriseEnrollment();
323 }
324
OnLoginAsGuest()325 void UserController::OnLoginAsGuest() {
326 user_input_->EnableInputControls(false);
327 StartThrobber();
328
329 delegate_->LoginAsGuest();
330 }
331
ClearErrors()332 void UserController::ClearErrors() {
333 delegate_->ClearErrors();
334 }
335
NavigateAway()336 void UserController::NavigateAway() {
337 SelectUserRelative(-1);
338 }
339
340 ////////////////////////////////////////////////////////////////////////////////
341 // UserController, UserView::Delegate implementation:
342 //
OnLocaleChanged()343 void 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
OnRemoveUser()353 void UserController::OnRemoveUser() {
354 delegate_->RemoveUser(this);
355 }
356
357 ////////////////////////////////////////////////////////////////////////////////
358 // UserController, private:
359 //
ConfigureLoginWindow(WidgetGtk * window,int index,const gfx::Rect & bounds,chromeos::WmIpcWindowType type,views::View * contents_view)360 void 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 ¶ms);
376
377 GdkWindow* gdk_window = window->GetNativeView()->window;
378 gdk_window_set_back_pixmap(gdk_window, NULL, false);
379
380 window->Show();
381 }
382
CreateControlsWindow(int index,int * width,int * height,bool need_browse_without_signin)383 WidgetGtk* 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
CreateImageWindow(int index)426 WidgetGtk* 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
CreateBorderWindow(int index,int total_user_count,int controls_width,int controls_height)451 void 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
CreateLabelWindow(int index,WmIpcWindowType type)485 WidgetGtk* 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
GetLabelFont()538 gfx::Font UserController::GetLabelFont() {
539 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
540 return rb.GetFont(ResourceBundle::MediumBoldFont).DeriveFont(
541 kSelectedUsernameFontDelta);
542 }
543
GetUnselectedLabelFont()544 gfx::Font UserController::GetUnselectedLabelFont() {
545 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
546 return rb.GetFont(ResourceBundle::BaseFont).DeriveFont(
547 kUnselectedUsernameFontDelta, gfx::Font::BOLD);
548 }
549
550
GetNameTooltip() const551 std::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