1// Copyright (c) 2012 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/ui/views/sync/one_click_signin_bubble_view.h"
6
7#include "base/callback_helpers.h"
8#include "base/logging.h"
9#include "chrome/browser/ui/browser.h"
10#include "chrome/browser/ui/sync/one_click_signin_helper.h"
11#include "chrome/browser/ui/sync/one_click_signin_histogram.h"
12#include "chrome/common/url_constants.h"
13#include "chrome/grit/chromium_strings.h"
14#include "chrome/grit/generated_resources.h"
15#include "components/google/core/browser/google_util.h"
16#include "grit/components_strings.h"
17#include "ui/base/l10n/l10n_util.h"
18#include "ui/base/resource/resource_bundle.h"
19#include "ui/events/keycodes/keyboard_codes.h"
20#include "ui/resources/grit/ui_resources.h"
21#include "ui/views/controls/button/image_button.h"
22#include "ui/views/controls/button/label_button.h"
23#include "ui/views/controls/image_view.h"
24#include "ui/views/controls/label.h"
25#include "ui/views/controls/link.h"
26#include "ui/views/layout/grid_layout.h"
27#include "ui/views/layout/layout_constants.h"
28#include "ui/views/widget/widget.h"
29
30// Minimum width of the the bubble.
31const int kMinBubbleWidth = 310;
32
33// Minimum width for the multi-line label.
34const int kMinimumDialogLabelWidth = 400;
35const int kMinimumLabelWidth = 240;
36const int kDialogMargin = 16;
37
38namespace {
39
40// The column set constants that can be used in the InitContent() function
41// to layout views.
42enum OneClickSigninBubbleColumnTypes {
43  COLUMN_SET_FILL_ALIGN,
44  COLUMN_SET_CONTROLS,
45  COLUMN_SET_TITLE_BAR
46};
47}  // namespace
48
49// static
50OneClickSigninBubbleView* OneClickSigninBubbleView::bubble_view_ = NULL;
51
52// static
53void OneClickSigninBubbleView::ShowBubble(
54    BrowserWindow::OneClickSigninBubbleType type,
55    const base::string16& email,
56    const base::string16& error_message,
57    scoped_ptr<OneClickSigninBubbleDelegate> delegate,
58    views::View* anchor_view,
59    const BrowserWindow::StartSyncCallback& start_sync) {
60  if (IsShowing())
61    return;
62
63  switch (type) {
64    case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE:
65      bubble_view_ = new OneClickSigninBubbleView(
66          error_message, base::string16(), delegate.Pass(),
67          anchor_view, start_sync, false);
68      break;
69    case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_MODAL_DIALOG:
70      bubble_view_ = new OneClickSigninBubbleView(
71          base::string16(), base::string16(), delegate.Pass(),
72          anchor_view, start_sync, true);
73      break;
74    case BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_SAML_MODAL_DIALOG:
75      bubble_view_ = new OneClickSigninBubbleView(
76          base::string16(), email, delegate.Pass(),
77          anchor_view, start_sync, true);
78      break;
79  }
80
81  views::BubbleDelegateView::CreateBubble(bubble_view_)->Show();
82}
83
84// static
85bool OneClickSigninBubbleView::IsShowing() {
86  return bubble_view_ != NULL;
87}
88
89// static
90void OneClickSigninBubbleView::Hide() {
91  if (IsShowing())
92    bubble_view_->GetWidget()->Close();
93}
94
95OneClickSigninBubbleView::OneClickSigninBubbleView(
96    const base::string16& error_message,
97    const base::string16& email,
98    scoped_ptr<OneClickSigninBubbleDelegate> delegate,
99    views::View* anchor_view,
100    const BrowserWindow::StartSyncCallback& start_sync_callback,
101    bool is_sync_dialog)
102    : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
103      delegate_(delegate.Pass()),
104      error_message_(error_message),
105      email_(email),
106      start_sync_callback_(start_sync_callback),
107      is_sync_dialog_(is_sync_dialog),
108      advanced_link_(NULL),
109      learn_more_link_(NULL),
110      ok_button_(NULL),
111      undo_button_(NULL),
112      close_button_(NULL),
113      clicked_learn_more_(false) {
114  if (is_sync_dialog_) {
115    DCHECK(!start_sync_callback_.is_null());
116    set_arrow(views::BubbleBorder::NONE);
117    set_anchor_view_insets(gfx::Insets(0, 0, anchor_view->height() / 2, 0));
118    set_close_on_deactivate(false);
119  }
120  int margin = is_sync_dialog_ ? kDialogMargin : views::kButtonVEdgeMarginNew;
121  set_margins(gfx::Insets(margin, margin, margin, margin));
122}
123
124OneClickSigninBubbleView::~OneClickSigninBubbleView() {
125}
126
127ui::ModalType OneClickSigninBubbleView::GetModalType() const {
128  return is_sync_dialog_? ui::MODAL_TYPE_CHILD : ui::MODAL_TYPE_NONE;
129}
130
131void OneClickSigninBubbleView::Init() {
132  views::GridLayout* layout = new views::GridLayout(this);
133  SetLayoutManager(layout);
134
135  SetBorder(views::Border::CreateEmptyBorder(8, 8, 8, 8));
136
137  // Column set for descriptive text and link.
138  views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_FILL_ALIGN);
139  cs->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1,
140                views::GridLayout::USE_PREF, 0, 0);
141
142  // Column set for buttons at bottom of bubble.
143  cs = layout->AddColumnSet(COLUMN_SET_CONTROLS);
144  cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
145                views::GridLayout::USE_PREF, 0, 0);
146  cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing);
147  cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
148                views::GridLayout::USE_PREF, 0, 0);
149  cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
150                views::GridLayout::USE_PREF, 0, 0);
151
152  is_sync_dialog_ ? InitDialogContent(layout) : InitBubbleContent(layout);
153
154  // Add controls at the bottom.
155  // Don't display the advanced link for the error bubble.
156  if (is_sync_dialog_ || error_message_.empty()) {
157    InitAdvancedLink();
158    layout->StartRow(0, COLUMN_SET_CONTROLS);
159    layout->AddView(advanced_link_);
160  }
161
162  InitButtons(layout);
163  ok_button_->SetIsDefault(true);
164
165  AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, 0));
166}
167
168void OneClickSigninBubbleView::InitBubbleContent(views::GridLayout* layout) {
169  layout->set_minimum_size(gfx::Size(kMinBubbleWidth, 0));
170
171  // If no error occurred, add title message.
172  if (error_message_.empty()) {
173    views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_TITLE_BAR);
174    cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
175                  views::GridLayout::USE_PREF, 0, 0);
176    {
177      layout->StartRow(0, COLUMN_SET_TITLE_BAR);
178
179      ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
180      views::Label* label = new views::Label(
181          l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW),
182          rb.GetFontList(ui::ResourceBundle::MediumFont));
183      label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
184      layout->AddView(label);
185    }
186
187    layout->AddPaddingRow(0, views::kUnrelatedControlLargeVerticalSpacing);
188  }
189
190  // Add main text description.
191  layout->StartRow(0, COLUMN_SET_FILL_ALIGN);
192
193  views::Label* label = !error_message_.empty() ?
194      new views::Label(error_message_) :
195      new views::Label(
196          l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_BUBBLE_MESSAGE));
197
198  label->SetMultiLine(true);
199  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
200  label->SizeToFit(kMinimumLabelWidth);
201  layout->AddView(label);
202
203  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
204
205  layout->StartRow(0, COLUMN_SET_CONTROLS);
206
207  InitLearnMoreLink();
208  layout->AddView(learn_more_link_);
209}
210
211void OneClickSigninBubbleView::InitDialogContent(views::GridLayout* layout) {
212  OneClickSigninHelper::LogConfirmHistogramValue(
213      one_click_signin::HISTOGRAM_CONFIRM_SHOWN);
214
215  // Column set for title bar.
216  views::ColumnSet* cs = layout->AddColumnSet(COLUMN_SET_TITLE_BAR);
217  cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
218                views::GridLayout::USE_PREF, 0, 0);
219  cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing);
220  cs->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, 0,
221                views::GridLayout::USE_PREF, 0, 0);
222
223  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
224
225  {
226    layout->StartRow(0, COLUMN_SET_TITLE_BAR);
227
228    views::Label* label = new views::Label(
229        l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_TITLE_NEW),
230        rb.GetFontList(ui::ResourceBundle::MediumBoldFont));
231    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
232    layout->AddView(label);
233
234    close_button_ = new views::ImageButton(this);
235    close_button_->SetImage(views::ImageButton::STATE_NORMAL,
236                            rb.GetImageNamed(IDR_CLOSE_2).ToImageSkia());
237    close_button_->SetImage(views::ImageButton::STATE_HOVERED,
238                            rb.GetImageNamed(IDR_CLOSE_2_H).ToImageSkia());
239    close_button_->SetImage(views::ImageButton::STATE_PRESSED,
240                            rb.GetImageNamed(IDR_CLOSE_2_P).ToImageSkia());
241
242    layout->AddView(close_button_);
243  }
244
245  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
246
247  {
248    layout->StartRow(0, COLUMN_SET_FILL_ALIGN);
249
250    views::Label* label = new views::Label(
251        l10n_util::GetStringFUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_MESSAGE_NEW,
252                                   email_));
253    label->SetMultiLine(true);
254    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
255    label->SizeToFit(kMinimumDialogLabelWidth);
256    layout->AddView(label);
257
258    layout->StartRow(0, COLUMN_SET_FILL_ALIGN);
259
260    InitLearnMoreLink();
261    layout->AddView(learn_more_link_, 1, 1, views::GridLayout::TRAILING,
262                    views::GridLayout::CENTER);
263  }
264
265  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
266}
267
268void OneClickSigninBubbleView::InitButtons(views::GridLayout* layout) {
269  GetButtons(&ok_button_, &undo_button_);
270  layout->AddView(ok_button_);
271
272  if (is_sync_dialog_)
273    layout->AddView(undo_button_);
274}
275
276void OneClickSigninBubbleView::GetButtons(views::LabelButton** ok_button,
277                                          views::LabelButton** undo_button) {
278  base::string16 ok_label = !error_message_.empty() ?
279      l10n_util::GetStringUTF16(IDS_OK) :
280      l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_OK_BUTTON);
281
282  *ok_button = new views::LabelButton(this, ok_label);
283  (*ok_button)->SetStyle(views::Button::STYLE_BUTTON);
284
285  // The default size of the buttons is too large.  To allow them to be smaller
286  // ignore the minimum default size.,
287  (*ok_button)->SetMinSize(gfx::Size());
288
289  if (is_sync_dialog_) {
290    *undo_button = new views::LabelButton(this, base::string16());
291    (*undo_button)->SetStyle(views::Button::STYLE_BUTTON);
292    (*undo_button)->SetMinSize(gfx::Size());
293
294    base::string16 undo_label =
295        l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_UNDO_BUTTON);
296    (*undo_button)->SetText(undo_label);
297  }
298}
299
300void OneClickSigninBubbleView::InitAdvancedLink() {
301  advanced_link_ = new views::Link(
302      l10n_util::GetStringUTF16(IDS_ONE_CLICK_SIGNIN_DIALOG_ADVANCED));
303
304  advanced_link_->set_listener(this);
305  advanced_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
306}
307
308void OneClickSigninBubbleView::InitLearnMoreLink() {
309  learn_more_link_ = new views::Link(
310      l10n_util::GetStringUTF16(IDS_LEARN_MORE));
311  learn_more_link_->set_listener(this);
312  learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
313}
314
315bool OneClickSigninBubbleView::AcceleratorPressed(
316  const ui::Accelerator& accelerator) {
317  if (accelerator.key_code() == ui::VKEY_RETURN ||
318      accelerator.key_code() == ui::VKEY_ESCAPE) {
319    OneClickSigninBubbleView::Hide();
320
321    if (is_sync_dialog_) {
322      if (accelerator.key_code() == ui::VKEY_RETURN) {
323        OneClickSigninHelper::LogConfirmHistogramValue(
324        clicked_learn_more_ ?
325            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_RETURN :
326            one_click_signin::HISTOGRAM_CONFIRM_RETURN);
327
328        base::ResetAndReturn(&start_sync_callback_).Run(
329            OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
330      } else if (accelerator.key_code() == ui::VKEY_ESCAPE) {
331        OneClickSigninHelper::LogConfirmHistogramValue(
332        clicked_learn_more_ ?
333            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ESCAPE :
334            one_click_signin::HISTOGRAM_CONFIRM_ESCAPE);
335
336        base::ResetAndReturn(&start_sync_callback_).Run(
337            OneClickSigninSyncStarter::UNDO_SYNC);
338      }
339    }
340
341    return true;
342  }
343
344  return BubbleDelegateView::AcceleratorPressed(accelerator);
345}
346
347void OneClickSigninBubbleView::LinkClicked(views::Link* source,
348                                           int event_flags) {
349  if (source == learn_more_link_) {
350    if (is_sync_dialog_ && !clicked_learn_more_) {
351      OneClickSigninHelper::LogConfirmHistogramValue(
352          one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE);
353      clicked_learn_more_ = true;
354    }
355    delegate_->OnLearnMoreLinkClicked(is_sync_dialog_);
356
357    // don't hide the modal dialog, as this is an informational link
358    if (is_sync_dialog_)
359      return;
360  } else if (advanced_link_ && source == advanced_link_) {
361    if (is_sync_dialog_) {
362      OneClickSigninHelper::LogConfirmHistogramValue(
363        clicked_learn_more_ ?
364            one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED :
365            one_click_signin::HISTOGRAM_CONFIRM_ADVANCED);
366
367      base::ResetAndReturn(&start_sync_callback_).Run(
368          OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
369    } else {
370      delegate_->OnAdvancedLinkClicked();
371    }
372  }
373
374  Hide();
375}
376
377void OneClickSigninBubbleView::ButtonPressed(views::Button* sender,
378                                             const ui::Event& event) {
379  Hide();
380
381  if (is_sync_dialog_) {
382    if (sender == ok_button_)
383      OneClickSigninHelper::LogConfirmHistogramValue(
384          clicked_learn_more_ ?
385              one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_OK :
386              one_click_signin::HISTOGRAM_CONFIRM_OK);
387
388    if (sender == undo_button_)
389      OneClickSigninHelper::LogConfirmHistogramValue(
390          clicked_learn_more_ ?
391              one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO :
392              one_click_signin::HISTOGRAM_CONFIRM_UNDO);
393
394    if (sender == close_button_)
395      OneClickSigninHelper::LogConfirmHistogramValue(
396          clicked_learn_more_ ?
397              one_click_signin::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE :
398              one_click_signin::HISTOGRAM_CONFIRM_CLOSE);
399
400    base::ResetAndReturn(&start_sync_callback_).Run((sender == ok_button_) ?
401      OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS :
402      OneClickSigninSyncStarter::UNDO_SYNC);
403  }
404}
405
406void OneClickSigninBubbleView::WindowClosing() {
407  // We have to reset |bubble_view_| here, not in our destructor, because
408  // we'll be destroyed asynchronously and the shown state will be checked
409  // before then.
410  DCHECK_EQ(bubble_view_, this);
411  bubble_view_ = NULL;
412
413  if (is_sync_dialog_ && !start_sync_callback_.is_null()) {
414    base::ResetAndReturn(&start_sync_callback_).Run(
415        OneClickSigninSyncStarter::UNDO_SYNC);
416  }
417}
418