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/captcha_view.h"
6
7#include "base/string_util.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/browser/chromeos/login/helper.h"
10#include "chrome/browser/chromeos/login/image_downloader.h"
11#include "chrome/browser/chromeos/login/rounded_rect_painter.h"
12#include "chrome/browser/chromeos/login/textfield_with_margin.h"
13#include "chrome/browser/chromeos/views/copy_background.h"
14#include "grit/chromium_strings.h"
15#include "grit/generated_resources.h"
16#include "grit/locale_settings.h"
17#include "ui/base/l10n/l10n_util.h"
18#include "views/background.h"
19#include "views/controls/button/text_button.h"
20#include "views/controls/image_view.h"
21#include "views/controls/label.h"
22#include "views/controls/textfield/textfield.h"
23#include "views/layout/grid_layout.h"
24#include "views/layout/layout_constants.h"
25#include "views/widget/widget_gtk.h"
26#include "views/window/window.h"
27
28using views::Label;
29using views::Textfield;
30using views::WidgetGtk;
31
32namespace chromeos {
33
34namespace {
35
36// A Textfield for captcha input, which also sets focus to itself
37// when a mouse is clicked on it. This is necessary in screen locker mode
38// as mouse events are grabbed in the screen locker.
39class CaptchaField : public TextfieldWithMargin {
40 public:
41  CaptchaField()
42      : TextfieldWithMargin() {
43  }
44
45  // views::View overrides.
46  virtual bool OnMousePressed(const views::MouseEvent& e) {
47    RequestFocus();
48    return false;
49  }
50
51 private:
52  DISALLOW_COPY_AND_ASSIGN(CaptchaField);
53};
54
55class WideTextButton : public views::TextButton {
56 public:
57  WideTextButton(views::ButtonListener* listener, const std::wstring& text)
58      : TextButton(listener, text) {
59    SetFont(font().DeriveFont(kFontSizeCorrectionDelta));
60  }
61
62  virtual ~WideTextButton() {}
63
64 private:
65  virtual gfx::Size GetPreferredSize() {
66    gfx::Size preferred_size = TextButton::GetPreferredSize();
67    // Set minimal width.
68    if (preferred_size.width() < login::kButtonMinWidth)
69      preferred_size.set_width(login::kButtonMinWidth);
70    return preferred_size;
71  }
72
73  DISALLOW_COPY_AND_ASSIGN(WideTextButton);
74};
75
76}  // namespace
77
78CaptchaView::CaptchaView(const GURL& captcha_url, bool is_standalone)
79    : delegate_(NULL),
80      captcha_url_(captcha_url),
81      captcha_image_(NULL),
82      captcha_textfield_(NULL),
83      is_standalone_(is_standalone),
84      ok_button_(NULL) {
85}
86
87bool CaptchaView::Accept() {
88  if (delegate_)
89    delegate_->OnCaptchaEntered(UTF16ToUTF8(captcha_textfield_->text()));
90  return true;
91}
92
93std::wstring CaptchaView::GetWindowTitle() const {
94  return UTF16ToWide(l10n_util::GetStringUTF16(IDS_LOGIN_CAPTCHA_DIALOG_TITLE));
95}
96
97void CaptchaView::SetCaptchaURL(const GURL& captcha_url) {
98  captcha_url_ = captcha_url;
99  captcha_textfield_->SetText(string16());
100  // ImageDownloader will delete itself once URL is fetched.
101  // TODO(nkostylev): Make sure that it works after view is deleted.
102  new ImageDownloader(this, GURL(captcha_url_), std::string());
103}
104
105gfx::Size CaptchaView::GetPreferredSize() {
106  gfx::Size size = gfx::Size(views::Window::GetLocalizedContentsSize(
107      IDS_CAPTCHA_INPUT_DIALOG_WIDTH_CHARS,
108      IDS_CAPTCHA_INPUT_DIALOG_HEIGHT_LINES));
109  if (is_standalone_)
110    size.set_height(size.height() + ok_button_->GetPreferredSize().height());
111
112  return size;
113}
114
115void CaptchaView::ViewHierarchyChanged(bool is_add,
116                                       views::View* parent,
117                                       views::View* child) {
118  // Can't focus before we're inserted into a Container.
119  if (is_add && child == this)
120    captcha_textfield_->RequestFocus();
121}
122
123bool CaptchaView::HandleKeyEvent(views::Textfield* sender,
124                                 const views::KeyEvent& key_event) {
125  if (sender == captcha_textfield_ &&
126      key_event.key_code() == ui::VKEY_RETURN) {
127    if (is_standalone_) {
128      Accept();
129    } else {
130      GetDialogClientView()->AcceptWindow();
131    }
132  }
133  return false;
134}
135
136void CaptchaView::OnImageDecoded(const ImageDecoder*,
137                                 const SkBitmap& decoded_image) {
138  captcha_image_->SetImage(decoded_image);
139  captcha_textfield_->RequestFocus();
140  Layout();
141}
142
143void CaptchaView::ButtonPressed(views::Button* sender,
144                                const views::Event& event) {
145  DCHECK(sender == ok_button_);
146  Accept();
147}
148
149void CaptchaView::Init() {
150  if (is_standalone_) {
151    // Use rounded rect background.
152    set_border(CreateWizardBorder(&BorderDefinition::kUserBorder));
153    views::Painter* painter = CreateWizardPainter(
154        &BorderDefinition::kUserBorder);
155    set_background(views::Background::CreateBackgroundPainter(true, painter));
156  }
157
158  views::GridLayout* layout = views::GridLayout::CreatePanel(this);
159  SetLayoutManager(layout);
160
161  int column_view_set_id = 0;
162  views::ColumnSet* column_set = layout->AddColumnSet(column_view_set_id);
163  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
164                        views::GridLayout::USE_PREF, 0, 0);
165  layout->StartRow(0, column_view_set_id);
166  Label* label = new views::Label(
167      UTF16ToWide(l10n_util::GetStringUTF16(IDS_LOGIN_CAPTCHA_INSTRUCTIONS)));
168  label->SetMultiLine(true);
169  layout->AddView(label);
170  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
171
172  layout->StartRow(0, column_view_set_id);
173  captcha_image_ = new views::ImageView();
174  layout->AddView(captcha_image_);
175  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
176
177  layout->StartRow(0, column_view_set_id);
178  captcha_textfield_ = new CaptchaField();
179  captcha_textfield_->SetController(this);
180  if (is_standalone_)
181    captcha_textfield_->set_background(new CopyBackground(this));
182  layout->AddView(captcha_textfield_);
183  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
184
185  layout->StartRow(0, column_view_set_id);
186  label = new views::Label(UTF16ToWide(
187      l10n_util::GetStringUTF16(IDS_SYNC_GAIA_CAPTCHA_CASE_INSENSITIVE_TIP)));
188  label->SetMultiLine(true);
189  layout->AddView(label);
190  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
191
192  if (is_standalone_) {
193    layout->StartRow(0, column_view_set_id);
194    ok_button_ = new WideTextButton(
195        this, UTF16ToWide(l10n_util::GetStringUTF16(IDS_OK)));
196    ok_button_->set_alignment(views::TextButton::ALIGN_CENTER);
197    ok_button_->SetFocusable(true);
198    ok_button_->SetNormalHasBorder(true);
199    ok_button_->set_animate_on_state_change(false);
200    ok_button_->SetEnabledColor(SK_ColorBLACK);
201    ok_button_->SetHighlightColor(SK_ColorBLACK);
202    ok_button_->SetHoverColor(SK_ColorBLACK);
203    layout->AddView(ok_button_, 1, 1,
204                    views::GridLayout::CENTER, views::GridLayout::CENTER);
205  }
206
207  // ImageDownloader will delete itself once URL is fetched.
208  // TODO(nkostylev): Make sure that it works after view is deleted.
209  new ImageDownloader(this, GURL(captcha_url_), std::string());
210}
211
212}  // namespace chromeos
213