Home | History | Annotate | Download | only in first_run
      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/first_run/try_chrome_dialog_view.h"
      6 
      7 #include <shellapi.h>
      8 
      9 #include "base/logging.h"
     10 #include "base/message_loop.h"
     11 #include "base/string16.h"
     12 #include "chrome/browser/process_singleton.h"
     13 #include "chrome/installer/util/browser_distribution.h"
     14 #include "grit/chromium_strings.h"
     15 #include "grit/generated_resources.h"
     16 #include "grit/theme_resources.h"
     17 #include "ui/base/resource/resource_bundle.h"
     18 #include "views/controls/button/image_button.h"
     19 #include "views/controls/button/radio_button.h"
     20 #include "views/controls/image_view.h"
     21 #include "views/layout/grid_layout.h"
     22 #include "views/layout/layout_constants.h"
     23 #include "views/widget/root_view.h"
     24 #include "views/widget/widget.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 
     27 namespace {
     28 
     29 const wchar_t kHelpCenterUrl[] =
     30     L"https://www.google.com/support/chrome/bin/answer.py?answer=150752";
     31 
     32 }  // namespace
     33 
     34 // static
     35 TryChromeDialogView::Result TryChromeDialogView::Show(
     36     size_t version,
     37     ProcessSingleton* process_singleton) {
     38   if (version > 10000) {
     39     // This is a test value. We want to make sure we exercise
     40     // returning this early. See EarlyReturnTest test harness.
     41     return NOT_NOW;
     42   }
     43   TryChromeDialogView dialog(version);
     44   return dialog.ShowModal(process_singleton);
     45 }
     46 
     47 TryChromeDialogView::TryChromeDialogView(size_t version)
     48     : version_(version),
     49       popup_(NULL),
     50       try_chrome_(NULL),
     51       kill_chrome_(NULL),
     52       result_(COUNT) {
     53 }
     54 
     55 TryChromeDialogView::~TryChromeDialogView() {
     56 }
     57 
     58 TryChromeDialogView::Result TryChromeDialogView::ShowModal(
     59     ProcessSingleton* process_singleton) {
     60   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
     61 
     62   views::ImageView* icon = new views::ImageView();
     63   icon->SetImage(*rb.GetBitmapNamed(IDR_PRODUCT_ICON_32));
     64   gfx::Size icon_size = icon->GetPreferredSize();
     65 
     66   // An approximate window size. After Layout() we'll get better bounds.
     67   views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP);
     68   params.can_activate = true;
     69   popup_ = views::Widget::CreateWidget(params);
     70   if (!popup_) {
     71     NOTREACHED();
     72     return DIALOG_ERROR;
     73   }
     74 
     75   gfx::Rect pos(310, 160);
     76   popup_->Init(NULL, pos);
     77 
     78   views::RootView* root_view = popup_->GetRootView();
     79   // The window color is a tiny bit off-white.
     80   root_view->set_background(
     81       views::Background::CreateSolidBackground(0xfc, 0xfc, 0xfc));
     82 
     83   views::GridLayout* layout = views::GridLayout::CreatePanel(root_view);
     84   if (!layout) {
     85     NOTREACHED();
     86     return DIALOG_ERROR;
     87   }
     88   root_view->SetLayoutManager(layout);
     89 
     90   views::ColumnSet* columns;
     91   // First row: [icon][pad][text][button].
     92   columns = layout->AddColumnSet(0);
     93   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
     94                      views::GridLayout::FIXED, icon_size.width(),
     95                      icon_size.height());
     96   columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
     97   columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
     98                      views::GridLayout::USE_PREF, 0, 0);
     99   columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1,
    100                      views::GridLayout::USE_PREF, 0, 0);
    101   // Second row: [pad][pad][radio 1].
    102   columns = layout->AddColumnSet(1);
    103   columns->AddPaddingColumn(0, icon_size.width());
    104   columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    105   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
    106                      views::GridLayout::USE_PREF, 0, 0);
    107   // Third row: [pad][pad][radio 2].
    108   columns = layout->AddColumnSet(2);
    109   columns->AddPaddingColumn(0, icon_size.width());
    110   columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    111   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
    112                      views::GridLayout::USE_PREF, 0, 0);
    113   // Fourth row: [pad][pad][button][pad][button].
    114   columns = layout->AddColumnSet(3);
    115   columns->AddPaddingColumn(0, icon_size.width());
    116   columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    117   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 0,
    118                      views::GridLayout::USE_PREF, 0, 0);
    119   columns->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
    120   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 0,
    121                      views::GridLayout::USE_PREF, 0, 0);
    122   // Fifth row: [pad][pad][link].
    123   columns = layout->AddColumnSet(4);
    124   columns->AddPaddingColumn(0, icon_size.width());
    125   columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    126   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
    127                      views::GridLayout::USE_PREF, 0, 0);
    128   // First row views.
    129   layout->StartRow(0, 0);
    130   layout->AddView(icon);
    131 
    132   // Find out what experiment we are conducting.
    133   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    134   if (!dist) {
    135     NOTREACHED() << "Cannot determine browser distribution";
    136     return DIALOG_ERROR;
    137   }
    138   BrowserDistribution::UserExperiment experiment;
    139   if (!dist->GetExperimentDetails(&experiment, version_) ||
    140       !experiment.heading) {
    141     NOTREACHED() << "Cannot determine which headline to show.";
    142     return DIALOG_ERROR;
    143   }
    144   string16 heading = l10n_util::GetStringUTF16(experiment.heading);
    145   views::Label* label = new views::Label(heading);
    146   label->SetFont(rb.GetFont(ResourceBundle::MediumBoldFont));
    147   label->SetMultiLine(true);
    148   label->SizeToFit(200);
    149   label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    150   layout->AddView(label);
    151   // The close button is custom.
    152   views::ImageButton* close_button = new views::ImageButton(this);
    153   close_button->SetImage(views::CustomButton::BS_NORMAL,
    154                         rb.GetBitmapNamed(IDR_CLOSE_BAR));
    155   close_button->SetImage(views::CustomButton::BS_HOT,
    156                         rb.GetBitmapNamed(IDR_CLOSE_BAR_H));
    157   close_button->SetImage(views::CustomButton::BS_PUSHED,
    158                         rb.GetBitmapNamed(IDR_CLOSE_BAR_P));
    159   close_button->set_tag(BT_CLOSE_BUTTON);
    160   layout->AddView(close_button);
    161 
    162   // Second row views.
    163   const string16 try_it(l10n_util::GetStringUTF16(IDS_TRY_TOAST_TRY_OPT));
    164   layout->StartRowWithPadding(0, 1, 0, 10);
    165   try_chrome_ = new views::RadioButton(try_it, 1);
    166   layout->AddView(try_chrome_);
    167   try_chrome_->SetChecked(true);
    168 
    169   // Third row views.
    170   const string16 kill_it(l10n_util::GetStringUTF16(IDS_UNINSTALL_CHROME));
    171   layout->StartRow(0, 2);
    172   kill_chrome_ = new views::RadioButton(kill_it, 1);
    173   layout->AddView(kill_chrome_);
    174 
    175   // Fourth row views.
    176   const string16 ok_it(l10n_util::GetStringUTF16(IDS_OK));
    177   const string16 cancel_it(l10n_util::GetStringUTF16(IDS_TRY_TOAST_CANCEL));
    178   const string16 why_this(l10n_util::GetStringUTF16(IDS_TRY_TOAST_WHY));
    179   layout->StartRowWithPadding(0, 3, 0, 10);
    180   views::Button* accept_button = new views::NativeButton(this, ok_it);
    181   accept_button->set_tag(BT_OK_BUTTON);
    182   layout->AddView(accept_button);
    183   views::Button* cancel_button = new views::NativeButton(this, cancel_it);
    184   cancel_button->set_tag(BT_CLOSE_BUTTON);
    185   layout->AddView(cancel_button);
    186   // Fifth row views.
    187   layout->StartRowWithPadding(0, 4, 0, 10);
    188   views::Link* link = new views::Link(why_this);
    189   link->SetController(this);
    190   layout->AddView(link);
    191 
    192   // We resize the window according to the layout manager. This takes into
    193   // account the differences between XP and Vista fonts and buttons.
    194   layout->Layout(root_view);
    195   gfx::Size preferred = layout->GetPreferredSize(root_view);
    196   pos = ComputeWindowPosition(preferred.width(), preferred.height(),
    197                               base::i18n::IsRTL());
    198   popup_->SetBounds(pos);
    199 
    200   // Carve the toast shape into the window.
    201   SetToastRegion(popup_->GetNativeView(),
    202                  preferred.width(), preferred.height());
    203 
    204   // Time to show the window in a modal loop. We don't want this chrome
    205   // instance trying to serve WM_COPYDATA requests, as we'll surely crash.
    206   process_singleton->Lock(popup_->GetNativeView());
    207   popup_->Show();
    208   MessageLoop::current()->Run();
    209   process_singleton->Unlock();
    210   return result_;
    211 }
    212 
    213 gfx::Rect TryChromeDialogView::ComputeWindowPosition(int width,
    214                                                      int height,
    215                                                      bool is_RTL) {
    216   // The 'Shell_TrayWnd' is the taskbar. We like to show our window in that
    217   // monitor if we can. This code works even if such window is not found.
    218   HWND taskbar = ::FindWindowW(L"Shell_TrayWnd", NULL);
    219   HMONITOR monitor = ::MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
    220   MONITORINFO info = {sizeof(info)};
    221   if (!GetMonitorInfoW(monitor, &info)) {
    222     // Quite unexpected. Do a best guess at a visible rectangle.
    223     return gfx::Rect(20, 20, width + 20, height + 20);
    224   }
    225   // The |rcWork| is the work area. It should account for the taskbars that
    226   // are in the screen when we called the function.
    227   int left = is_RTL ? info.rcWork.left : info.rcWork.right - width;
    228   int top = info.rcWork.bottom - height;
    229   return gfx::Rect(left, top, width, height);
    230 }
    231 
    232 void TryChromeDialogView::SetToastRegion(HWND window, int w, int h) {
    233   static const POINT polygon[] = {
    234     {0,   4}, {1,   2}, {2,   1}, {4, 0},   // Left side.
    235     {w-4, 0}, {w-2, 1}, {w-1, 2}, {w, 4},   // Right side.
    236     {w, h}, {0, h}
    237   };
    238   HRGN region = ::CreatePolygonRgn(polygon, arraysize(polygon), WINDING);
    239   ::SetWindowRgn(window, region, FALSE);
    240 }
    241 
    242 void TryChromeDialogView::ButtonPressed(views::Button* sender,
    243                                         const views::Event& event) {
    244   if (sender->tag() == BT_CLOSE_BUTTON) {
    245     // The user pressed cancel or the [x] button.
    246     result_ = NOT_NOW;
    247   } else if (!try_chrome_) {
    248     // We don't have radio buttons, the user pressed ok.
    249     result_ = TRY_CHROME;
    250   } else {
    251     // The outcome is according to the selected ratio button.
    252     result_ = try_chrome_->checked() ? TRY_CHROME : UNINSTALL_CHROME;
    253   }
    254   popup_->Close();
    255   MessageLoop::current()->Quit();
    256 }
    257 
    258 void TryChromeDialogView::LinkActivated(views::Link* source, int event_flags) {
    259   ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);
    260 }
    261