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