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
27namespace {
28
29const wchar_t kHelpCenterUrl[] =
30    L"https://www.google.com/support/chrome/bin/answer.py?answer=150752";
31
32}  // namespace
33
34// static
35TryChromeDialogView::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
47TryChromeDialogView::TryChromeDialogView(size_t version)
48    : version_(version),
49      popup_(NULL),
50      try_chrome_(NULL),
51      kill_chrome_(NULL),
52      result_(COUNT) {
53}
54
55TryChromeDialogView::~TryChromeDialogView() {
56}
57
58TryChromeDialogView::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
213gfx::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
232void 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
242void 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
258void TryChromeDialogView::LinkActivated(views::Link* source, int event_flags) {
259  ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);
260}
261