• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
Show(size_t version,ProcessSingleton * process_singleton)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 
TryChromeDialogView(size_t version)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 
~TryChromeDialogView()55 TryChromeDialogView::~TryChromeDialogView() {
56 }
57 
ShowModal(ProcessSingleton * process_singleton)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 
ComputeWindowPosition(int width,int height,bool is_RTL)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 
SetToastRegion(HWND window,int w,int h)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 
ButtonPressed(views::Button * sender,const views::Event & event)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 
LinkActivated(views::Link * source,int event_flags)258 void TryChromeDialogView::LinkActivated(views::Link* source, int event_flags) {
259   ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);
260 }
261