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/network_selection_view.h"
6
7#include <signal.h>
8#include <sys/types.h>
9#include <string>
10
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/chromeos/login/helper.h"
13#include "chrome/browser/chromeos/login/keyboard_switch_menu.h"
14#include "chrome/browser/chromeos/login/language_switch_menu.h"
15#include "chrome/browser/chromeos/login/network_screen_delegate.h"
16#include "chrome/browser/chromeos/login/proxy_settings_dialog.h"
17#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
18#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
19#include "chrome/browser/chromeos/status/network_dropdown_button.h"
20#include "grit/chromium_strings.h"
21#include "grit/generated_resources.h"
22#include "grit/theme_resources.h"
23#include "ui/base/l10n/l10n_util.h"
24#include "ui/base/resource/resource_bundle.h"
25#include "ui/gfx/size.h"
26#include "views/controls/label.h"
27#include "views/controls/throbber.h"
28#include "views/layout/fill_layout.h"
29#include "views/layout/grid_layout.h"
30#include "views/layout/layout_constants.h"
31#include "views/widget/widget.h"
32#include "views/widget/widget_gtk.h"
33#include "views/window/non_client_view.h"
34#include "views/window/window.h"
35#include "views/window/window_gtk.h"
36
37using views::Background;
38using views::GridLayout;
39using views::Label;
40using views::View;
41using views::Widget;
42using views::WidgetGtk;
43
44namespace {
45
46enum kLayoutColumnsets {
47  STANDARD_ROW,
48  THROBBER_ROW,
49};
50
51enum kContentsLayoutColumnsets {
52  WELCOME_ROW,
53  CONTENTS_ROW,
54};
55
56// Grid layout constants.
57const int kBorderSize = 10;
58const int kWelcomeTitlePadding = 10;
59const int kPaddingColumnWidth = 55;
60const int kMediumPaddingColumnWidth = 20;
61const int kControlPaddingRow = 15;
62
63// Fixed size for language/keyboard/network controls height.
64const int kSelectionBoxHeight = 29;
65
66// Menu button is drawn using our custom icons in resources. See
67// TextButtonBorder::OnPaint() for details. So this offset compensate
68// horizontal size, eaten by those icons.
69const int kMenuHorizontalOffset = -3;
70
71// Vertical addition to the menu window to make it appear exactly below
72// MenuButton.
73const int kMenuVerticalOffset = -1;
74
75// Offset that compensates menu width so that it matches
76// menu button visual width when being in pushed state.
77const int kMenuWidthOffset = 6;
78
79const SkColor kWelcomeColor = 0xFFCDD3D6;
80
81// Initializes menu button default properties.
82static void InitMenuButtonProperties(views::MenuButton* menu_button) {
83  menu_button->SetFocusable(true);
84  menu_button->SetNormalHasBorder(true);
85  menu_button->SetEnabledColor(SK_ColorBLACK);
86  menu_button->SetHighlightColor(SK_ColorBLACK);
87  menu_button->SetHoverColor(SK_ColorBLACK);
88  menu_button->set_animate_on_state_change(false);
89  // Menu is positioned by bottom right corner of the MenuButton.
90  menu_button->set_menu_offset(kMenuHorizontalOffset, kMenuVerticalOffset);
91}
92
93static void SetMenuButtonFont(views::MenuButton* menu_button,
94                              const gfx::Font& font) {
95  menu_button->SetFont(font);
96  chromeos::CorrectMenuButtonFontSize(menu_button);
97}
98
99}  // namespace
100
101namespace chromeos {
102
103// NetworkDropdownButton with custom Activate() behavior.
104class NetworkControlReportOnActivate : public NetworkDropdownButton {
105 public:
106  NetworkControlReportOnActivate(bool browser_mode,
107                                 gfx::NativeWindow parent_window,
108                                 NetworkScreenDelegate* delegate)
109      : NetworkDropdownButton(browser_mode, parent_window),
110        delegate_(delegate) {}
111
112  // Overridden from MenuButton:
113  virtual bool Activate() {
114    delegate_->ClearErrors();
115    return MenuButton::Activate();
116  }
117
118 private:
119  NetworkScreenDelegate* delegate_;
120
121  DISALLOW_COPY_AND_ASSIGN(NetworkControlReportOnActivate);
122};
123
124// MenuButton with custom processing on focus events.
125class NotifyingMenuButton : public DropDownButton {
126 public:
127  NotifyingMenuButton(views::ButtonListener* listener,
128                      const std::wstring& text,
129                      views::ViewMenuDelegate* menu_delegate,
130                      bool show_menu_marker,
131                      NetworkScreenDelegate* delegate)
132      : DropDownButton(listener, text, menu_delegate, show_menu_marker),
133        delegate_(delegate) {}
134
135  // Overridden from View:
136  virtual void OnFocus() OVERRIDE {
137    delegate_->ClearErrors();
138    GetWidget()->NotifyAccessibilityEvent(
139        this, ui::AccessibilityTypes::EVENT_FOCUS, true);
140  }
141
142 private:
143  NetworkScreenDelegate* delegate_;
144
145  DISALLOW_COPY_AND_ASSIGN(NotifyingMenuButton);
146};
147
148NetworkSelectionView::NetworkSelectionView(NetworkScreenDelegate* delegate)
149    : entire_screen_view_(NULL),
150      contents_view_(NULL),
151      languages_menubutton_(NULL),
152      keyboards_menubutton_(NULL),
153      welcome_label_(NULL),
154      select_language_label_(NULL),
155      select_keyboard_label_(NULL),
156      select_network_label_(NULL),
157      connecting_network_label_(NULL),
158      network_dropdown_(NULL),
159      continue_button_(NULL),
160      throbber_(CreateDefaultSmoothedThrobber()),
161      proxy_settings_link_(NULL),
162      show_keyboard_button_(false),
163      delegate_(delegate) {
164}
165
166NetworkSelectionView::~NetworkSelectionView() {
167  throbber_->Stop();
168  throbber_ = NULL;
169}
170
171void NetworkSelectionView::AddControlsToLayout(
172    views::GridLayout* contents_layout) {
173  // Padding rows will be resized.
174  const int kPadding = 0;
175  if (IsConnecting()) {
176    contents_layout->AddPaddingRow(1, kPadding);
177    contents_layout->StartRow(0, THROBBER_ROW);
178    contents_layout->AddView(connecting_network_label_);
179    contents_layout->AddView(throbber_);
180    contents_layout->AddPaddingRow(1, kPadding);
181  } else {
182    contents_layout->AddPaddingRow(1, kPadding);
183    contents_layout->StartRow(0, STANDARD_ROW);
184    contents_layout->AddView(select_language_label_);
185    contents_layout->AddView(languages_menubutton_, 1, 1,
186                    GridLayout::FILL, GridLayout::FILL,
187                    languages_menubutton_->GetPreferredSize().width(),
188                    kSelectionBoxHeight);
189    if (show_keyboard_button_) {
190      contents_layout->AddPaddingRow(0, kControlPaddingRow);
191      contents_layout->StartRow(0, STANDARD_ROW);
192      contents_layout->AddView(select_keyboard_label_);
193      contents_layout->AddView(
194          keyboards_menubutton_, 1, 1,
195          GridLayout::FILL, GridLayout::FILL,
196          keyboards_menubutton_->GetPreferredSize().width(),
197          kSelectionBoxHeight);
198    }
199    contents_layout->AddPaddingRow(0, kControlPaddingRow);
200    contents_layout->StartRow(0, STANDARD_ROW);
201    contents_layout->AddView(select_network_label_);
202    contents_layout->AddView(network_dropdown_, 1, 1,
203                    GridLayout::FILL, GridLayout::FILL,
204                    network_dropdown_->GetPreferredSize().width(),
205                    kSelectionBoxHeight);
206    contents_layout->AddPaddingRow(0, kControlPaddingRow);
207    contents_layout->StartRow(0, STANDARD_ROW);
208    contents_layout->SkipColumns(1);
209    contents_layout->AddView(proxy_settings_link_, 1, 1,
210                    GridLayout::LEADING, GridLayout::CENTER);
211    contents_layout->AddPaddingRow(0, kControlPaddingRow);
212    contents_layout->StartRow(0, STANDARD_ROW);
213    contents_layout->SkipColumns(1);
214    contents_layout->AddView(continue_button_, 1, 1,
215                    GridLayout::LEADING, GridLayout::CENTER);
216    contents_layout->AddPaddingRow(1, kPadding);
217  }
218}
219
220void NetworkSelectionView::InitLayout() {
221  gfx::Size screen_size = delegate_->size();
222  const int widest_label = std::max(
223      std::max(
224          select_language_label_->GetPreferredSize().width(),
225          select_keyboard_label_->GetPreferredSize().width()),
226      select_network_label_->GetPreferredSize().width());
227  const int dropdown_width = screen_size.width() - 2 * kBorderSize -
228      2 * kPaddingColumnWidth - kMediumPaddingColumnWidth - widest_label;
229  delegate_->language_switch_menu()->SetFirstLevelMenuWidth(
230      dropdown_width - kMenuWidthOffset);
231  delegate_->keyboard_switch_menu()->SetMinimumWidth(
232      dropdown_width - kMenuWidthOffset);
233  network_dropdown_->SetFirstLevelMenuWidth(dropdown_width - kMenuWidthOffset);
234
235  // Define layout and column set for entire screen (title + screen).
236  SetLayoutManager(new views::FillLayout);
237  views::GridLayout* screen_layout = new views::GridLayout(entire_screen_view_);
238  entire_screen_view_->SetLayoutManager(screen_layout);
239
240  views::ColumnSet* column_set = screen_layout->AddColumnSet(WELCOME_ROW);
241  const int welcome_width = screen_size.width() - 2 * kWelcomeTitlePadding -
242      2 * kBorderSize;
243  column_set->AddPaddingColumn(0, kWelcomeTitlePadding + kBorderSize);
244  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
245                        GridLayout::FIXED, welcome_width, welcome_width);
246  column_set->AddPaddingColumn(0, kWelcomeTitlePadding + kBorderSize);
247  column_set = screen_layout->AddColumnSet(CONTENTS_ROW);
248  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
249      GridLayout::FIXED, screen_size.width(), screen_size.width());
250  screen_layout->StartRow(0, WELCOME_ROW);
251  screen_layout->AddView(welcome_label_);
252  screen_layout->StartRow(1, CONTENTS_ROW);
253  screen_layout->AddView(contents_view_);
254
255  // Define layout and column set for screen contents.
256  views::GridLayout* contents_layout = new views::GridLayout(contents_view_);
257  contents_view_->SetLayoutManager(contents_layout);
258
259  column_set = contents_layout->AddColumnSet(STANDARD_ROW);
260  column_set->AddPaddingColumn(1, kPaddingColumnWidth);
261  column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
262                        GridLayout::FIXED, widest_label, widest_label);
263  column_set->AddPaddingColumn(0, kMediumPaddingColumnWidth);
264  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
265                        GridLayout::FIXED, dropdown_width, dropdown_width);
266  column_set->AddPaddingColumn(1, kPaddingColumnWidth);
267
268  const int h_padding = 30;
269  column_set = contents_layout->AddColumnSet(THROBBER_ROW);
270  column_set->AddPaddingColumn(1, h_padding);
271  column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
272                        GridLayout::USE_PREF, 0, 0);
273  column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
274  column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
275                        GridLayout::USE_PREF, 0, 0);
276  column_set->AddPaddingColumn(1, h_padding);
277
278  AddControlsToLayout(contents_layout);
279}
280
281void NetworkSelectionView::Init() {
282  contents_view_ = new views::View();
283
284  entire_screen_view_ = new views::View();
285  AddChildView(entire_screen_view_);
286
287  // Use rounded rect background.
288  views::Painter* painter = CreateWizardPainter(
289      &BorderDefinition::kScreenBorder);
290  contents_view_->set_background(
291      views::Background::CreateBackgroundPainter(true, painter));
292
293  welcome_label_ = new views::Label();
294  welcome_label_->SetColor(kWelcomeColor);
295  welcome_label_->SetMultiLine(true);
296
297  select_language_label_ = new views::Label();
298
299  languages_menubutton_ = new NotifyingMenuButton(
300      NULL, std::wstring(), delegate_->language_switch_menu(), true, delegate_);
301  InitMenuButtonProperties(languages_menubutton_);
302
303  select_keyboard_label_ = new views::Label();
304
305  keyboards_menubutton_ = new DropDownButton(
306      NULL /* listener */, L"", delegate_->keyboard_switch_menu(),
307      true /* show_menu_marker */);
308  InitMenuButtonProperties(keyboards_menubutton_);
309
310  select_network_label_ = new views::Label();
311
312  network_dropdown_ = new NetworkControlReportOnActivate(false,
313                                                         GetNativeWindow(),
314                                                         delegate_);
315  InitMenuButtonProperties(network_dropdown_);
316
317  connecting_network_label_ = new views::Label();
318  connecting_network_label_->SetVisible(false);
319
320  proxy_settings_link_ = new views::Link();
321  proxy_settings_link_->SetController(this);
322  proxy_settings_link_->SetVisible(true);
323  proxy_settings_link_->SetFocusable(true);
324  proxy_settings_link_->SetNormalColor(login::kLinkColor);
325  proxy_settings_link_->SetHighlightedColor(login::kLinkColor);
326
327  UpdateLocalizedStringsAndFonts();
328}
329
330void NetworkSelectionView::UpdateLocalizedStringsAndFonts() {
331  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
332  gfx::Font welcome_label_font = rb.GetFont(ResourceBundle::LargeFont).
333      DeriveFont(kWelcomeTitleFontDelta, gfx::Font::BOLD);
334  gfx::Font select_label_font = rb.GetFont(ResourceBundle::MediumFont).
335      DeriveFont(kNetworkSelectionLabelFontDelta);
336  const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
337
338  SetMenuButtonFont(languages_menubutton_, base_font);
339  languages_menubutton_->SetText(
340      UTF16ToWide(delegate_->language_switch_menu()->GetCurrentLocaleName()));
341  SetMenuButtonFont(keyboards_menubutton_, base_font);
342  keyboards_menubutton_->SetText(
343      UTF16ToWide(delegate_->keyboard_switch_menu()->GetCurrentKeyboardName()));
344  welcome_label_->SetFont(welcome_label_font);
345  welcome_label_->SetText(
346      UTF16ToWide(l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_TITLE)));
347  select_language_label_->SetFont(select_label_font);
348  select_language_label_->SetText(
349      UTF16ToWide(l10n_util::GetStringUTF16(IDS_LANGUAGE_SELECTION_SELECT)));
350  languages_menubutton_->SetAccessibleName(
351      l10n_util::GetStringUTF16(IDS_LANGUAGE_SELECTION_SELECT));
352  select_keyboard_label_->SetFont(select_label_font);
353  select_keyboard_label_->SetText(
354      UTF16ToWide(l10n_util::GetStringUTF16(IDS_KEYBOARD_SELECTION_SELECT)));
355  keyboards_menubutton_->SetAccessibleName(
356      l10n_util::GetStringUTF16(IDS_KEYBOARD_SELECTION_SELECT));
357  select_network_label_->SetFont(select_label_font);
358  select_network_label_->SetText(
359      UTF16ToWide(l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_SELECT)));
360  SetMenuButtonFont(network_dropdown_, base_font);
361  network_dropdown_->SetAccessibleName(
362      l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_SELECT));
363  proxy_settings_link_->SetFont(base_font);
364  proxy_settings_link_->SetText(UTF16ToWide(
365      l10n_util::GetStringUTF16(IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON)));
366  connecting_network_label_->SetFont(rb.GetFont(ResourceBundle::MediumFont));
367  RecreateNativeControls();
368  UpdateConnectingNetworkLabel();
369  network_dropdown_->Refresh();
370  InitLayout();
371}
372
373////////////////////////////////////////////////////////////////////////////////
374// views::View: implementation:
375
376bool NetworkSelectionView::OnKeyPressed(const views::KeyEvent&) {
377  if (delegate_->is_error_shown()) {
378    delegate_->ClearErrors();
379    return true;
380  }
381  return false;
382}
383
384void NetworkSelectionView::OnLocaleChanged() {
385  show_keyboard_button_ = true;
386  UpdateLocalizedStringsAndFonts();
387  // Proxy settings dialog contains localized title.  Zap it.
388  proxy_settings_dialog_.reset(NULL);
389
390  Layout();
391  SchedulePaint();
392}
393
394////////////////////////////////////////////////////////////////////////////////
395// NetworkSelectionView, public:
396
397gfx::NativeWindow NetworkSelectionView::GetNativeWindow() const {
398  return
399      GTK_WINDOW(static_cast<const WidgetGtk*>(GetWidget())->GetNativeView());
400}
401
402views::View* NetworkSelectionView::GetNetworkControlView() const {
403  return network_dropdown_;
404}
405
406void NetworkSelectionView::ShowConnectingStatus(bool connecting,
407                                                const string16& network_id) {
408  network_id_ = network_id;
409  UpdateConnectingNetworkLabel();
410  select_language_label_->SetVisible(!connecting);
411  languages_menubutton_->SetVisible(!connecting);
412  select_keyboard_label_->SetVisible(!connecting);
413  keyboards_menubutton_->SetVisible(!connecting);
414  select_network_label_->SetVisible(!connecting);
415  network_dropdown_->SetVisible(!connecting);
416  continue_button_->SetVisible(!connecting);
417  proxy_settings_link_->SetVisible(!connecting);
418  connecting_network_label_->SetVisible(connecting);
419  InitLayout();
420  Layout();
421  if (connecting) {
422    throbber_->Start();
423    network_dropdown_->CancelMenu();
424  } else {
425    throbber_->Stop();
426  }
427}
428
429bool NetworkSelectionView::IsConnecting() const {
430  return connecting_network_label_->IsVisible();
431}
432
433void NetworkSelectionView::EnableContinue(bool enabled) {
434  if (continue_button_)
435    continue_button_->SetEnabled(enabled);
436}
437
438bool NetworkSelectionView::IsContinueEnabled() const {
439  return continue_button_ && continue_button_->IsEnabled();
440}
441
442////////////////////////////////////////////////////////////////////////////////
443// views::LinkController implementation:
444
445void NetworkSelectionView::LinkActivated(views::Link* source, int) {
446  delegate_->ClearErrors();
447  if (source == proxy_settings_link_) {
448    if (!proxy_settings_dialog_.get()) {
449      proxy_settings_dialog_.reset(
450          new ProxySettingsDialog(this, GetNativeWindow()));
451    }
452    proxy_settings_dialog_->Show();
453  }
454}
455
456////////////////////////////////////////////////////////////////////////////////
457// NetworkSelectionView, private:
458
459void NetworkSelectionView::RecreateNativeControls() {
460  // There is no way to get native button preferred size after the button was
461  // sized so delete and recreate the button on text update.
462  bool is_continue_enabled = IsContinueEnabled();
463  delete continue_button_;
464  continue_button_ = new login::WideButton(
465      delegate_,
466      UTF16ToWide(
467          l10n_util::GetStringUTF16(IDS_NETWORK_SELECTION_CONTINUE_BUTTON)));
468  continue_button_->SetEnabled(is_continue_enabled);
469}
470
471void NetworkSelectionView::UpdateConnectingNetworkLabel() {
472  connecting_network_label_->SetText(UTF16ToWide(l10n_util::GetStringFUTF16(
473      IDS_NETWORK_SELECTION_CONNECTING, network_id_)));
474}
475
476}  // namespace chromeos
477