// 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/network_selection_view.h"
#include <signal.h>
#include <sys/types.h>
#include <string>
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/login/helper.h"
#include "chrome/browser/chromeos/login/keyboard_switch_menu.h"
#include "chrome/browser/chromeos/login/language_switch_menu.h"
#include "chrome/browser/chromeos/login/network_screen_delegate.h"
#include "chrome/browser/chromeos/login/proxy_settings_dialog.h"
#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
#include "chrome/browser/chromeos/status/network_dropdown_button.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 "ui/gfx/size.h"
#include "views/controls/label.h"
#include "views/controls/throbber.h"
#include "views/layout/fill_layout.h"
#include "views/layout/grid_layout.h"
#include "views/layout/layout_constants.h"
#include "views/widget/widget.h"
#include "views/widget/widget_gtk.h"
#include "views/window/non_client_view.h"
#include "views/window/window.h"
#include "views/window/window_gtk.h"
using views::Background;
using views::GridLayout;
using views::Label;
using views::View;
using views::Widget;
using views::WidgetGtk;
namespace {
enum kLayoutColumnsets {
STANDARD_ROW,
THROBBER_ROW,
};
enum kContentsLayoutColumnsets {
WELCOME_ROW,
CONTENTS_ROW,
};
// Grid layout constants.
const int kBorderSize = 10;
const int kWelcomeTitlePadding = 10;
const int kPaddingColumnWidth = 55;
const int kMediumPaddingColumnWidth = 20;
const int kControlPaddingRow = 15;
// Fixed size for language/keyboard/network controls height.
const int kSelectionBoxHeight = 29;
// Menu button is drawn using our custom icons in resources. See
// TextButtonBorder::OnPaint() for details. So this offset compensate
// horizontal size, eaten by those icons.
const int kMenuHorizontalOffset = -3;
// Vertical addition to the menu window to make it appear exactly below
// MenuButton.
const int kMenuVerticalOffset = -1;
// Offset that compensates menu width so that it matches
// menu button visual width when being in pushed state.
const int kMenuWidthOffset = 6;
const SkColor kWelcomeColor = 0xFFCDD3D6;
// Initializes menu button default properties.
static void InitMenuButtonProperties(views::MenuButton* menu_button) {
menu_button->SetFocusable(true);
menu_button->SetNormalHasBorder(true);
menu_button->SetEnabledColor(SK_ColorBLACK);
menu_button->SetHighlightColor(SK_ColorBLACK);
menu_button->SetHoverColor(SK_ColorBLACK);
menu_button->set_animate_on_state_change(false);
// Menu is positioned by bottom right corner of the MenuButton.
menu_button->set_menu_offset(kMenuHorizontalOffset, kMenuVerticalOffset);
}
static void SetMenuButtonFont(views::MenuButton* menu_button,
const gfx::Font& font) {
menu_button->SetFont(font);
chromeos::CorrectMenuButtonFontSize(menu_button);
}
} // namespace
namespace chromeos {
// NetworkDropdownButton with custom Activate() behavior.
class NetworkControlReportOnActivate : public NetworkDropdownButton {
public:
NetworkControlReportOnActivate(bool browser_mode,
gfx::NativeWindow parent_window,
NetworkScreenDelegate* delegate)
: NetworkDropdownButton(browser_mode, parent_window),
delegate_(delegate) {}
// Overridden from MenuButton:
virtual bool Activate() {
delegate_->ClearErrors();
return MenuButton::Activate();
}
private:
NetworkScreenDelegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(NetworkControlReportOnActivate);
};
// MenuButton with custom processing on focus events.
class NotifyingMenuButton : public DropDownButton {
public:
NotifyingMenuButton(views::ButtonListener* listener,
const std::wstring& text,
views::ViewMenuDelegate* menu_delegate,
bool show_menu_marker,
NetworkScreenDelegate* delegate)
: DropDownButton(listener, text, menu_delegate, show_menu_marker),
delegate_(delegate) {}
// Overridden from View:
virtual void OnFocus() OVERRIDE {
delegate_->ClearErrors();
GetWidget()->NotifyAccessibilityEvent(
this, ui::AccessibilityTypes::EVENT_FOCUS, true);
}
private:
NetworkScreenDelegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(NotifyingMenuButton);
};
NetworkSelectionView::NetworkSelectionView(NetworkScreenDelegate* delegate)
: entire_screen_view_(NULL),
contents_view_(NULL),
languages_menubutton_(NULL),
keyboards_menubutton_(NULL),
welcome_label_(NULL),
select_language_label_(NULL),
select_keyboard_label_(NULL),
select_network_label_(NULL),
connecting_network_label_(NULL),
network_dropdown_(NULL),
continue_button_(NULL),
throbber_(CreateDefaultSmoothedThrobber()),
proxy_settings_link_(NULL),
show_keyboard_button_(false),
delegate_(delegate) {
}
NetworkSelectionView::~NetworkSelectionView() {
throbber_->Stop();
throbber_ = NULL;
}
void NetworkSelectionView::AddControlsToLayout(
views::GridLayout* contents_layout) {
// Padding rows will be resized.
const int kPadding = 0;
if (IsConnecting()) {
contents_layout->AddPaddingRow(1, kPadding);
contents_layout->StartRow(0, THROBBER_ROW);
contents_layout->AddView(connecting_network_label_);
contents_layout->AddView(throbber_);
contents_layout->AddPaddingRow(1, kPadding);
} else {
contents_layout->AddPaddingRow(1, kPadding);
contents_layout->StartRow(0, STANDARD_ROW);
contents_layout->AddView(select_language_label_);
contents_layout->AddView(languages_menubutton_, 1, 1,
GridLayout::FILL, GridLayout::FILL,
languages_menubutton_->GetPreferredSize().width(),
kSelectionBoxHeight);
if (show_keyboard_button_) {
contents_layout->AddPaddingRow(0, kControlPaddingRow);
contents_layout->StartRow(0, STANDARD_ROW);
contents_layout->AddView(select_keyboard_label_);
contents_layout->AddView(
keyboards_menubutton_, 1, 1,
GridLayout::FILL, GridLayout::FILL,
keyboards_menubutton_->GetPreferredSize().width(),
kSelectionBoxHeight);
}
contents_layout->AddPaddingRow(0, kControlPaddingRow);
contents_layout->StartRow(0, STANDARD_ROW);
contents_layout->AddView(select_network_label_);
contents_layout->AddView(network_dropdown_, 1, 1,
GridLayout::FILL, GridLayout::FILL,
network_dropdown_->GetPreferredSize().width(),
kSelectionBoxHeight);
contents_layout->AddPaddingRow(0, kControlPaddingRow);
contents_layout->StartRow(0, STANDARD_ROW);
contents_layout->SkipColumns(1);
contents_layout->AddView(proxy_settings_link_, 1, 1,
GridLayout::LEADING, GridLayout::CENTER);
contents_layout->AddPaddingRow(0, kControlPaddingRow);
contents_layout->StartRow(0, STANDARD_ROW);
contents_layout->SkipColumns(1);
contents_layout->AddView(continue_button_, 1, 1,
GridLayout::LEADING, GridLayout::CENTER);
contents_layout->AddPaddingRow(1, kPadding);
}
}
void NetworkSelectionView::InitLayout() {
gfx::Size screen_size = delegate_->size();
const int widest_label = std::max(
std::max(
select_language_label_->GetPreferredSize().width(),
select_keyboard_label_->GetPreferredSize().width()),
select_network_label_->GetPreferredSize().width());
const int dropdown_width = screen_size.width() - 2 * kBorderSize -
2 * kPaddingColumnWidth - kMediumPaddingColumnWidth - widest_label;
delegate_->language_switch_menu()->SetFirstLevelMenuWidth(
dropdown_width - kMenuWidthOffset);
delegate_->keyboard_switch_menu()->SetMinimumWidth(
dropdown_width - kMenuWidthOffset);
network_dropdown_->SetFirstLevelMenuWidth(dropdown_width - kMenuWidthOffset);
// Define layout and column set for entire screen (title + screen).
SetLayoutManager(new views::FillLayout);
views::GridLayout* screen_layout = new views::GridLayout(entire_screen_view_);
entire_screen_view_->SetLayoutManager(screen_layout);
views::ColumnSet* column_set = screen_layout->AddColumnSet(WELCOME_ROW);
const int welcome_width = screen_size.width() - 2 * kWelcomeTitlePadding -
2 * kBorderSize;
column_set->AddPaddingColumn(0, kWelcomeTitlePadding + kBorderSize);
column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
GridLayout::FIXED, welcome_width, welcome_width);
column_set->AddPaddingColumn(0, kWelcomeTitlePadding + kBorderSize);
column_set = screen_layout->AddColumnSet(CONTENTS_ROW);
column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
GridLayout::FIXED, screen_size.width(), screen_size.width());
screen_layout->StartRow(0, WELCOME_ROW);
screen_layout->AddView(welcome_label_);
screen_layout->StartRow(1, CONTENTS_ROW);
screen_layout->AddView(contents_view_);
// Define layout and column set for screen contents.
views::GridLayout* contents_layout = new views::GridLayout(contents_view_);
contents_view_->SetLayoutManager(contents_layout);
column_set = contents_layout->AddColumnSet(STANDARD_ROW);
column_set->AddPaddingColumn(1, kPaddingColumnWidth);
column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
GridLayout::FIXED, widest_label, widest_label);
column_set->AddPaddingColumn(0, kMediumPaddingColumnWidth);
column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
GridLayout::FIXED, dropdown_width, dropdown_width);
column_set->AddPaddingColumn(1, kPaddingColumnWidth);
const int h_padding = 30;
column_set = contents_layout->AddColumnSet(THROBBER_ROW);
column_set->AddPaddingColumn(1, h_padding);
column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
GridLayout::USE_PREF, 0, 0);
column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
GridLayout::USE_PREF, 0, 0);
column_set->AddPaddingColumn(1, h_padding);
AddControlsToLayout(contents_layout);
}
void NetworkSelectionView::Init() {
contents_view_ = new views::View();
entire_screen_view_ = new views::View();
AddChildView(entire_screen_view_);
// Use rounded rect background.
views::Painter* painter = CreateWizardPainter(
&BorderDefinition::kScreenBorder);
contents_view_->set_background(
views::Background::CreateBackgroundPainter(true, painter));
welcome_label_ = new views::Label();
welcome_label_->SetColor(kWelcomeColor);
welcome_label_->SetMultiLine(true);
select_language_label_ = new views::Label();
languages_menubutton_ = new NotifyingMenuButton(
NULL, std::wstring(), delegate_->language_switch_menu(), true, delegate_);
InitMenuButtonProperties(languages_menubutton_);
select_keyboard_label_ = new views::Label();
keyboards_menubutton_ = new DropDownButton(
NULL /* listener */, L"", delegate_->keyboard_switch_menu(),
true /* show_menu_marker */);
InitMenuButtonProperties(keyboards_menubutton_);
select_network_label_ = new views::Label();
network_dropdown_ = new NetworkControlReportOnActivate(false,
GetNativeWindow(),
delegate_);
InitMenuButtonProperties(network_dropdown_);
connecting_network_label_ = new views::Label();
connecting_network_label_->SetVisible(false);
proxy_settings_link_ = new views::Link();
proxy_settings_link_->SetController(this);
proxy_settings_link_->SetVisible(true);
proxy_settings_link_->SetFocusable(true);
proxy_settings_link_->SetNormalColor(login::kLinkColor);
proxy_settings_link_->SetHighlightedColor(login::kLinkColor);
UpdateLocalizedStringsAndFonts();
}
void NetworkSelectionView::UpdateLocalizedStringsAndFonts() {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
gfx::Font welcome_label_font = rb.GetFont(ResourceBundle::LargeFont).
DeriveFont(kWelcomeTitleFontDelta, gfx::Font::BOLD);
gfx::Font select_label_font = rb.GetFont(ResourceBundle::MediumFont).
DeriveFont(kNetworkSelectionLabelFontDelta);
const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
SetMenuButtonFont(languages_menubutton_, base_font);
languages_menubutton_->SetText(
UTF16ToWide(delegate_->language_switch_menu()->GetCurrentLocaleName()));
SetMenuButtonFont(keyboards_menubutton_, base_font);
keyboards_menubutton_->SetText(
UTF16ToWide(delegate_->keyboard_switch_menu()->GetCurrentKeyboardName()));
welcome_label_->SetFont(welcome_label_font);
welcome_label_->SetText(
UTF16ToWide(l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_TITLE)));
select_language_label_->SetFont(select_label_font);
select_language_label_->SetText(
UTF16ToWide(l10n_util::GetStringUTF16(IDS_LANGUAGE_SELECTION_SELECT)));
languages_menubutton_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_LANGUAGE_SELECTION_SELECT));
select_keyboard_label_->SetFont(select_label_font);
select_keyboard_label_->SetText(
UTF16ToWide(l10n_util::GetStringUTF16(IDS_KEYBOARD_SELECTION_SELECT)));
keyboards_menubutton_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_KEYBOARD_SELECTION_SELECT));
select_network_label_->SetFont(select_label_font);
select_network_label_->SetText(
UTF16ToWide(l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_SELECT)));
SetMenuButtonFont(network_dropdown_, base_font);
network_dropdown_->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_SELECT));
proxy_settings_link_->SetFont(base_font);
proxy_settings_link_->SetText(UTF16ToWide(
l10n_util::GetStringUTF16(IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON)));
connecting_network_label_->SetFont(rb.GetFont(ResourceBundle::MediumFont));
RecreateNativeControls();
UpdateConnectingNetworkLabel();
network_dropdown_->Refresh();
InitLayout();
}
////////////////////////////////////////////////////////////////////////////////
// views::View: implementation:
bool NetworkSelectionView::OnKeyPressed(const views::KeyEvent&) {
if (delegate_->is_error_shown()) {
delegate_->ClearErrors();
return true;
}
return false;
}
void NetworkSelectionView::OnLocaleChanged() {
show_keyboard_button_ = true;
UpdateLocalizedStringsAndFonts();
// Proxy settings dialog contains localized title. Zap it.
proxy_settings_dialog_.reset(NULL);
Layout();
SchedulePaint();
}
////////////////////////////////////////////////////////////////////////////////
// NetworkSelectionView, public:
gfx::NativeWindow NetworkSelectionView::GetNativeWindow() const {
return
GTK_WINDOW(static_cast<const WidgetGtk*>(GetWidget())->GetNativeView());
}
views::View* NetworkSelectionView::GetNetworkControlView() const {
return network_dropdown_;
}
void NetworkSelectionView::ShowConnectingStatus(bool connecting,
const string16& network_id) {
network_id_ = network_id;
UpdateConnectingNetworkLabel();
select_language_label_->SetVisible(!connecting);
languages_menubutton_->SetVisible(!connecting);
select_keyboard_label_->SetVisible(!connecting);
keyboards_menubutton_->SetVisible(!connecting);
select_network_label_->SetVisible(!connecting);
network_dropdown_->SetVisible(!connecting);
continue_button_->SetVisible(!connecting);
proxy_settings_link_->SetVisible(!connecting);
connecting_network_label_->SetVisible(connecting);
InitLayout();
Layout();
if (connecting) {
throbber_->Start();
network_dropdown_->CancelMenu();
} else {
throbber_->Stop();
}
}
bool NetworkSelectionView::IsConnecting() const {
return connecting_network_label_->IsVisible();
}
void NetworkSelectionView::EnableContinue(bool enabled) {
if (continue_button_)
continue_button_->SetEnabled(enabled);
}
bool NetworkSelectionView::IsContinueEnabled() const {
return continue_button_ && continue_button_->IsEnabled();
}
////////////////////////////////////////////////////////////////////////////////
// views::LinkController implementation:
void NetworkSelectionView::LinkActivated(views::Link* source, int) {
delegate_->ClearErrors();
if (source == proxy_settings_link_) {
if (!proxy_settings_dialog_.get()) {
proxy_settings_dialog_.reset(
new ProxySettingsDialog(this, GetNativeWindow()));
}
proxy_settings_dialog_->Show();
}
}
////////////////////////////////////////////////////////////////////////////////
// NetworkSelectionView, private:
void NetworkSelectionView::RecreateNativeControls() {
// There is no way to get native button preferred size after the button was
// sized so delete and recreate the button on text update.
bool is_continue_enabled = IsContinueEnabled();
delete continue_button_;
continue_button_ = new login::WideButton(
delegate_,
UTF16ToWide(
l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_CONTINUE_BUTTON)));
continue_button_->SetEnabled(is_continue_enabled);
}
void NetworkSelectionView::UpdateConnectingNetworkLabel() {
connecting_network_label_->SetText(UTF16ToWide(l10n_util::GetStringFUTF16(
IDS_NETWORK_SELECTION_CONNECTING, network_id_)));
}
} // namespace chromeos