// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/views_login_display.h"
#include <algorithm>
#include "base/stl_util-inl.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/login/help_app_launcher.h"
#include "chrome/browser/chromeos/login/message_bubble.h"
#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
#include "chrome/browser/chromeos/view_ids.h"
#include "chrome/browser/chromeos/wm_ipc.h"
#include "chrome/browser/ui/views/window.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "views/widget/widget_gtk.h"
#include "views/window/window.h"
namespace {
// Max number of users we'll show. The true max is the min of this and the
// number of windows that fit on the screen.
const size_t kMaxUsers = 6;
// Minimum number of users we'll show (including Guest and New User pods).
const size_t kMinUsers = 3;
// Used to indicate no user has been selected.
const size_t kNotSelected = -1;
// Offset of cursor in first position from edit left side. It's used to position
// info bubble arrow to cursor.
const int kCursorOffset = 5;
// Checks if display names are unique. If there are duplicates, enables
// tooltips with full emails to let users distinguish their accounts.
// Otherwise, disables the tooltips.
void EnableTooltipsIfNeeded(
const std::vector<chromeos::UserController*>& controllers) {
std::map<std::string, int> visible_display_names;
for (size_t i = 0; i + 1 < controllers.size(); ++i) {
const std::string& display_name =
controllers[i]->user().GetDisplayName();
++visible_display_names[display_name];
}
for (size_t i = 0; i < controllers.size(); ++i) {
const std::string& display_name =
controllers[i]->user().GetDisplayName();
bool show_tooltip = controllers[i]->is_new_user() ||
controllers[i]->is_guest() ||
visible_display_names[display_name] > 1;
controllers[i]->EnableNameTooltip(show_tooltip);
}
}
} // namespace
namespace chromeos {
ViewsLoginDisplay::ViewsLoginDisplay(LoginDisplay::Delegate* delegate,
const gfx::Rect& background_bounds)
: LoginDisplay(delegate, background_bounds),
bubble_(NULL),
controller_for_removal_(NULL),
selected_view_index_(kNotSelected) {
}
ViewsLoginDisplay::~ViewsLoginDisplay() {
ClearErrors();
STLDeleteElements(&controllers_);
STLDeleteElements(&invisible_controllers_);
}
////////////////////////////////////////////////////////////////////////////////
// ViewsLoginDisplay, LoginDisplay implementation:
//
void ViewsLoginDisplay::Init(const std::vector<UserManager::User>& users,
bool show_guest,
bool show_new_user) {
size_t max_users = kMaxUsers;
if (width() > 0) {
size_t users_per_screen = (width() - login::kUserImageSize) /
(UserController::kUnselectedSize + UserController::kPadding);
max_users = std::max(kMinUsers, std::min(kMaxUsers, users_per_screen));
}
size_t visible_users_count = std::min(users.size(), max_users -
static_cast<int>(show_guest) - static_cast<int>(show_new_user));
for (size_t i = 0; i < users.size(); ++i) {
UserController* user_controller = new UserController(this, users[i]);
if (controllers_.size() < visible_users_count) {
controllers_.push_back(user_controller);
} else if (user_controller->is_owner()) {
// Make sure that owner of the device is always visible on login screen.
invisible_controllers_.insert(invisible_controllers_.begin(),
controllers_.back());
controllers_.back() = user_controller;
} else {
invisible_controllers_.push_back(user_controller);
}
}
if (show_guest)
controllers_.push_back(new UserController(this, true));
if (show_new_user)
controllers_.push_back(new UserController(this, false));
// If there's only new user pod, show the guest session link on it.
bool show_guest_link = controllers_.size() == 1;
for (size_t i = 0; i < controllers_.size(); ++i) {
(controllers_[i])->Init(static_cast<int>(i),
static_cast<int>(controllers_.size()),
show_guest_link);
}
EnableTooltipsIfNeeded(controllers_);
}
void ViewsLoginDisplay::OnBeforeUserRemoved(const std::string& username) {
controller_for_removal_ = controllers_[selected_view_index_];
controllers_.erase(controllers_.begin() + selected_view_index_);
EnableTooltipsIfNeeded(controllers_);
// Update user count before unmapping windows, otherwise window manager won't
// be in the right state.
int new_size = static_cast<int>(controllers_.size());
for (int i = 0; i < new_size; ++i)
controllers_[i]->UpdateUserCount(i, new_size);
}
void ViewsLoginDisplay::OnUserImageChanged(UserManager::User* user) {
UserController* controller = GetUserControllerByEmail(user->email());
if (controller)
controller->OnUserImageChanged(user);
}
void ViewsLoginDisplay::OnUserRemoved(const std::string& username) {
// We need to unmap entry windows, the windows will be unmapped in destructor.
MessageLoop::current()->DeleteSoon(FROM_HERE, controller_for_removal_);
controller_for_removal_ = NULL;
// Nothing to insert.
if (invisible_controllers_.empty())
return;
// Insert just before guest or add new user pods if any.
size_t new_size = controllers_.size();
size_t insert_position = new_size;
while (insert_position > 0 &&
(controllers_[insert_position - 1]->is_new_user() ||
controllers_[insert_position - 1]->is_guest())) {
--insert_position;
}
controllers_.insert(controllers_.begin() + insert_position,
invisible_controllers_[0]);
invisible_controllers_.erase(invisible_controllers_.begin());
// Update counts for exiting pods.
new_size = controllers_.size();
for (size_t i = 0; i < new_size; ++i) {
if (i != insert_position)
controllers_[i]->UpdateUserCount(i, new_size);
}
// And initialize new one that was invisible.
controllers_[insert_position]->Init(insert_position, new_size, false);
EnableTooltipsIfNeeded(controllers_);
}
void ViewsLoginDisplay::OnFadeOut() {
controllers_[selected_view_index_]->StopThrobber();
}
void ViewsLoginDisplay::SetUIEnabled(bool is_enabled) {
// Send message to WM to enable/disable click on windows.
WmIpc::Message message(WM_IPC_MESSAGE_WM_SET_LOGIN_STATE);
message.set_param(0, is_enabled);
WmIpc::instance()->SendMessage(message);
if (is_enabled)
controllers_[selected_view_index_]->ClearAndEnablePassword();
}
void ViewsLoginDisplay::ShowError(int error_msg_id,
int login_attempts,
HelpAppLauncher::HelpTopic help_topic_id) {
ClearErrors();
string16 error_text;
error_msg_id_ = error_msg_id;
help_topic_id_ = help_topic_id;
// GetStringF fails on debug build if there's no replacement in the string.
if (error_msg_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED) {
error_text = l10n_util::GetStringFUTF16(
error_msg_id, l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME));
} else if (error_msg_id == IDS_LOGIN_ERROR_CAPTIVE_PORTAL) {
error_text = l10n_util::GetStringFUTF16(
error_msg_id, delegate()->GetConnectedNetworkName());
} else {
error_text = l10n_util::GetStringUTF16(error_msg_id);
}
gfx::Rect bounds =
controllers_[selected_view_index_]->GetMainInputScreenBounds();
BubbleBorder::ArrowLocation arrow;
if (controllers_[selected_view_index_]->is_new_user()) {
arrow = BubbleBorder::LEFT_TOP;
} else {
// Point info bubble arrow to cursor position (approximately).
bounds.set_width(kCursorOffset * 2);
arrow = BubbleBorder::BOTTOM_LEFT;
}
string16 help_link;
if (error_msg_id == IDS_LOGIN_ERROR_CAPTIVE_PORTAL) {
help_link = l10n_util::GetStringUTF16(IDS_LOGIN_FIX_CAPTIVE_PORTAL);
} else if (error_msg_id == IDS_LOGIN_ERROR_CAPTIVE_PORTAL_NO_GUEST_MODE) {
// No help link is needed.
} else if (error_msg_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED ||
login_attempts > 1) {
help_link = l10n_util::GetStringUTF16(IDS_LEARN_MORE);
}
bubble_ = MessageBubble::Show(
controllers_[selected_view_index_]->controls_window(),
bounds,
arrow,
ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING),
UTF16ToWide(error_text),
UTF16ToWide(help_link),
this);
WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
UTF16ToUTF8(error_text).c_str(), false, false);
}
////////////////////////////////////////////////////////////////////////////////
// ViewsLoginDisplay, UserController::Delegate implementation:
//
void ViewsLoginDisplay::CreateAccount() {
delegate()->CreateAccount();
}
void ViewsLoginDisplay::Login(UserController* source,
const string16& password) {
delegate()->Login(source->user().email(), UTF16ToUTF8(password));
}
void ViewsLoginDisplay::LoginAsGuest() {
delegate()->LoginAsGuest();
}
void ViewsLoginDisplay::ClearErrors() {
// bubble_ will be set to NULL in callback.
if (bubble_)
bubble_->Close();
}
void ViewsLoginDisplay::OnUserSelected(UserController* source) {
std::vector<UserController*>::const_iterator i =
std::find(controllers_.begin(), controllers_.end(), source);
DCHECK(i != controllers_.end());
size_t new_selected_index = i - controllers_.begin();
if (new_selected_index != selected_view_index_ &&
selected_view_index_ != kNotSelected) {
controllers_[selected_view_index_]->ClearAndEnableFields();
controllers_[new_selected_index]->ClearAndEnableFields();
delegate()->OnUserSelected(source->user().email());
}
selected_view_index_ = new_selected_index;
WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
source->GetAccessibleUserLabel().c_str(), false, true);
}
void ViewsLoginDisplay::RemoveUser(UserController* source) {
ClearErrors();
UserManager::Get()->RemoveUser(source->user().email(), this);
}
void ViewsLoginDisplay::SelectUser(int index) {
if (index >= 0 && index < static_cast<int>(controllers_.size()) &&
index != static_cast<int>(selected_view_index_)) {
WmIpc::Message message(WM_IPC_MESSAGE_WM_SELECT_LOGIN_USER);
message.set_param(0, index);
WmIpc::instance()->SendMessage(message);
}
}
void ViewsLoginDisplay::StartEnterpriseEnrollment() {
delegate()->OnStartEnterpriseEnrollment();
}
////////////////////////////////////////////////////////////////////////////////
// ViewsLoginDisplay, views::MessageBubbleDelegate implementation:
//
void ViewsLoginDisplay::OnHelpLinkActivated() {
ClearErrors();
if (error_msg_id_ == IDS_LOGIN_ERROR_CAPTIVE_PORTAL) {
delegate()->FixCaptivePortal();
return;
}
if (!parent_window())
return;
if (!help_app_.get())
help_app_ = new HelpAppLauncher(parent_window());
help_app_->ShowHelpTopic(help_topic_id_);
}
////////////////////////////////////////////////////////////////////////////////
// ViewsLoginDisplay, private:
//
UserController* ViewsLoginDisplay::GetUserControllerByEmail(
const std::string& email) {
std::vector<UserController*>::iterator i;
for (i = controllers_.begin(); i != controllers_.end(); ++i) {
if ((*i)->user().email() == email)
return *i;
}
for (i = invisible_controllers_.begin();
i != invisible_controllers_.end(); ++i) {
if ((*i)->user().email() == email)
return *i;
}
return NULL;
}
} // namespace chromeos