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.
5 #include "chrome/browser/first_run/try_chrome_dialog_view.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string16.h"
12 #include "chrome/browser/process_singleton.h"
13 #include "chrome/installer/util/browser_distribution.h"
14 #include "chrome/installer/util/user_experiment.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/aura/window.h"
20 #include "ui/aura/window_tree_host.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/image/image.h"
24 #include "ui/views/background.h"
25 #include "ui/views/controls/button/checkbox.h"
26 #include "ui/views/controls/button/image_button.h"
27 #include "ui/views/controls/button/label_button.h"
28 #include "ui/views/controls/button/radio_button.h"
29 #include "ui/views/controls/image_view.h"
30 #include "ui/views/controls/link.h"
31 #include "ui/views/controls/separator.h"
32 #include "ui/views/layout/grid_layout.h"
33 #include "ui/views/layout/layout_constants.h"
34 #include "ui/views/widget/widget.h"
38 const wchar_t kHelpCenterUrl
[] =
39 L
"https://www.google.com/support/chrome/bin/answer.py?answer=150752";
49 const int kRadioGroupID
= 1;
54 TryChromeDialogView::Result
TryChromeDialogView::Show(
56 const ActiveModalDialogListener
& listener
) {
58 // This is a test value. We want to make sure we exercise
59 // returning this early. See TryChromeDialogBrowserTest test.
62 TryChromeDialogView
dialog(flavor
);
63 return dialog
.ShowModal(listener
);
66 TryChromeDialogView::TryChromeDialogView(size_t flavor
)
71 dont_try_chrome_(NULL
),
76 TryChromeDialogView::~TryChromeDialogView() {
79 TryChromeDialogView::Result
TryChromeDialogView::ShowModal(
80 const ActiveModalDialogListener
& listener
) {
81 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
83 views::ImageView
* icon
= new views::ImageView();
84 icon
->SetImage(rb
.GetNativeImageNamed(IDR_PRODUCT_LOGO_32
).ToImageSkia());
85 gfx::Size icon_size
= icon
->GetPreferredSize();
87 // An approximate window size. After Layout() we'll get better bounds.
88 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
89 params
.activatable
= views::Widget::InitParams::ACTIVATABLE_YES
;
90 params
.bounds
= gfx::Rect(310, 200);
91 popup_
= new views::Widget
;
94 views::View
* root_view
= popup_
->GetRootView();
95 // The window color is a tiny bit off-white.
96 root_view
->set_background(
97 views::Background::CreateSolidBackground(0xfc, 0xfc, 0xfc));
99 views::GridLayout
* layout
= views::GridLayout::CreatePanel(root_view
);
100 root_view
->SetLayoutManager(layout
);
101 views::ColumnSet
* columns
;
103 // First row: [icon][pad][text][pad][button].
104 columns
= layout
->AddColumnSet(0);
105 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::LEADING
, 0,
106 views::GridLayout::FIXED
, icon_size
.width(),
108 columns
->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing
);
109 columns
->AddColumn(views::GridLayout::FILL
, views::GridLayout::FILL
, 1,
110 views::GridLayout::USE_PREF
, 0, 0);
111 columns
->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing
);
112 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
, 1,
113 views::GridLayout::USE_PREF
, 0, 0);
115 // Optional second row: [pad][pad][radio 1].
116 columns
= layout
->AddColumnSet(1);
117 columns
->AddPaddingColumn(0, icon_size
.width());
118 columns
->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing
);
119 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
, 1,
120 views::GridLayout::USE_PREF
, 0, 0);
122 // Third row: [pad][pad][radio 2].
123 columns
= layout
->AddColumnSet(2);
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);
129 // Fourth row: [pad][pad][button][pad][button].
130 columns
= layout
->AddColumnSet(3);
131 columns
->AddPaddingColumn(0, icon_size
.width());
132 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
, 0,
133 views::GridLayout::USE_PREF
, 0, 0);
134 columns
->AddPaddingColumn(0, views::kRelatedButtonHSpacing
);
135 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
, 0,
136 views::GridLayout::USE_PREF
, 0, 0);
138 // Fifth row: [pad][pad][link].
139 columns
= layout
->AddColumnSet(4);
140 columns
->AddPaddingColumn(0, icon_size
.width());
141 columns
->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing
);
142 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
, 1,
143 views::GridLayout::USE_PREF
, 0, 0);
145 // Optional fourth row: [button].
146 columns
= layout
->AddColumnSet(5);
147 columns
->AddColumn(views::GridLayout::CENTER
, views::GridLayout::FILL
, 1,
148 views::GridLayout::USE_PREF
, 0, 0);
150 // Optional fourth row: [divider]
151 columns
= layout
->AddColumnSet(6);
152 columns
->AddColumn(views::GridLayout::CENTER
, views::GridLayout::FILL
, 1,
153 views::GridLayout::USE_PREF
, 0, 0);
155 // Optional fifth row [checkbox][pad][button]
156 columns
= layout
->AddColumnSet(7);
157 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
, 0,
158 views::GridLayout::USE_PREF
, 0, 0);
159 columns
->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing
);
160 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
, 1,
161 views::GridLayout::USE_PREF
, 0, 0);
164 layout
->StartRow(0, 0);
165 layout
->AddView(icon
);
167 // Find out what experiment we are conducting.
168 installer::ExperimentDetails experiment
;
169 if (!BrowserDistribution::GetDistribution()->HasUserExperiments() ||
170 !installer::CreateExperimentDetails(flavor_
, &experiment
) ||
171 !experiment
.heading
) {
172 NOTREACHED() << "Cannot determine which headline to show.";
175 views::Label
* label
= new views::Label(
176 l10n_util::GetStringUTF16(experiment
.heading
),
177 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
178 label
->SetMultiLine(true);
179 label
->SizeToFit(200);
180 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
181 layout
->AddView(label
);
182 // The close button is custom.
183 views::ImageButton
* close_button
= new views::ImageButton(this);
184 close_button
->SetImage(views::CustomButton::STATE_NORMAL
,
185 rb
.GetNativeImageNamed(IDR_CLOSE_2
).ToImageSkia());
186 close_button
->SetImage(views::CustomButton::STATE_HOVERED
,
187 rb
.GetNativeImageNamed(IDR_CLOSE_2_H
).ToImageSkia());
188 close_button
->SetImage(views::CustomButton::STATE_PRESSED
,
189 rb
.GetNativeImageNamed(IDR_CLOSE_2_P
).ToImageSkia());
190 close_button
->set_tag(BT_CLOSE_BUTTON
);
191 layout
->AddView(close_button
);
194 layout
->StartRowWithPadding(0, 1, 0, 10);
195 try_chrome_
= new views::RadioButton(
196 l10n_util::GetStringUTF16(IDS_TRY_TOAST_TRY_OPT
), kRadioGroupID
);
197 try_chrome_
->SetChecked(true);
198 try_chrome_
->set_tag(BT_TRY_IT_RADIO
);
199 try_chrome_
->set_listener(this);
200 layout
->AddView(try_chrome_
);
202 // Decide if the don't bug me is a button or a radio button.
203 bool dont_bug_me_button
=
204 !!(experiment
.flags
& installer::kToastUiDontBugMeAsButton
);
206 // Optional third and fourth row.
207 if (!dont_bug_me_button
) {
208 layout
->StartRow(0, 1);
209 dont_try_chrome_
= new views::RadioButton(
210 l10n_util::GetStringUTF16(IDS_TRY_TOAST_CANCEL
), kRadioGroupID
);
211 dont_try_chrome_
->set_tag(BT_DONT_BUG_RADIO
);
212 dont_try_chrome_
->set_listener(this);
213 layout
->AddView(dont_try_chrome_
);
215 if (experiment
.flags
& installer::kToastUiUninstall
) {
216 layout
->StartRow(0, 2);
217 kill_chrome_
= new views::RadioButton(
218 l10n_util::GetStringUTF16(IDS_UNINSTALL_CHROME
), kRadioGroupID
);
219 layout
->AddView(kill_chrome_
);
222 views::LabelButton
* accept_button
= new views::LabelButton(
223 this, l10n_util::GetStringUTF16(IDS_OK
));
224 accept_button
->SetStyle(views::Button::STYLE_BUTTON
);
225 accept_button
->set_tag(BT_OK_BUTTON
);
227 views::Separator
* separator
= NULL
;
228 if (experiment
.flags
& installer::kToastUiMakeDefault
) {
229 // In this flavor we have some veritical space, then a separator line
230 // and the 'make default' checkbox and the OK button on the same row.
231 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
232 layout
->StartRow(0, 6);
233 separator
= new views::Separator(views::Separator::HORIZONTAL
);
234 layout
->AddView(separator
);
235 layout
->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing
);
237 layout
->StartRow(0, 7);
238 make_default_
= new views::Checkbox(
239 l10n_util::GetStringUTF16(IDS_TRY_TOAST_SET_DEFAULT
));
240 make_default_
->SetChecked(true);
241 layout
->AddView(make_default_
);
242 layout
->AddView(accept_button
);
244 // On this other flavor there is no checkbox, the OK button and possibly
245 // the cancel button are in the same row.
246 layout
->StartRowWithPadding(0, dont_bug_me_button
? 3 : 5, 0, 10);
247 layout
->AddView(accept_button
);
248 if (dont_bug_me_button
) {
249 // The dialog needs a "Don't bug me" as a button or as a radio button,
250 // this the button case.
251 views::LabelButton
* cancel_button
= new views::LabelButton(
252 this, l10n_util::GetStringUTF16(IDS_TRY_TOAST_CANCEL
));
253 cancel_button
->SetStyle(views::Button::STYLE_BUTTON
);
254 cancel_button
->set_tag(BT_CLOSE_BUTTON
);
255 layout
->AddView(cancel_button
);
259 if (experiment
.flags
& installer::kToastUiWhyLink
) {
260 layout
->StartRowWithPadding(0, 4, 0, 10);
261 views::Link
* link
= new views::Link(
262 l10n_util::GetStringUTF16(IDS_TRY_TOAST_WHY
));
263 link
->set_listener(this);
264 layout
->AddView(link
);
267 // We resize the window according to the layout manager. This takes into
268 // account the differences between XP and Vista fonts and buttons.
269 layout
->Layout(root_view
);
270 gfx::Size preferred
= layout
->GetPreferredSize(root_view
);
272 int separator_height
= separator
->GetPreferredSize().height();
273 separator
->SetSize(gfx::Size(preferred
.width(), separator_height
));
276 gfx::Rect pos
= ComputeWindowPosition(preferred
.width(), preferred
.height(),
277 base::i18n::IsRTL());
278 popup_
->SetBounds(pos
);
280 // Carve the toast shape into the window.
282 toast_window
= popup_
->GetNativeView()->GetHost()->GetAcceleratedWidget();
283 SetToastRegion(toast_window
, preferred
.width(), preferred
.height());
285 // Time to show the window in a modal loop.
287 if (!listener
.is_null())
288 listener
.Run(popup_
->GetNativeView());
289 base::MessageLoop::current()->Run();
290 if (!listener
.is_null())
295 gfx::Rect
TryChromeDialogView::ComputeWindowPosition(int width
,
298 // The 'Shell_TrayWnd' is the taskbar. We like to show our window in that
299 // monitor if we can. This code works even if such window is not found.
300 HWND taskbar
= ::FindWindowW(L
"Shell_TrayWnd", NULL
);
301 HMONITOR monitor
= ::MonitorFromWindow(taskbar
, MONITOR_DEFAULTTOPRIMARY
);
302 MONITORINFO info
= {sizeof(info
)};
303 if (!GetMonitorInfoW(monitor
, &info
)) {
304 // Quite unexpected. Do a best guess at a visible rectangle.
305 return gfx::Rect(20, 20, width
+ 20, height
+ 20);
307 // The |rcWork| is the work area. It should account for the taskbars that
308 // are in the screen when we called the function.
309 int left
= is_RTL
? info
.rcWork
.left
: info
.rcWork
.right
- width
;
310 int top
= info
.rcWork
.bottom
- height
;
311 return gfx::Rect(left
, top
, width
, height
);
314 void TryChromeDialogView::SetToastRegion(HWND window
, int w
, int h
) {
315 static const POINT polygon
[] = {
316 {0, 4}, {1, 2}, {2, 1}, {4, 0}, // Left side.
317 {w
-4, 0}, {w
-2, 1}, {w
-1, 2}, {w
, 4}, // Right side.
320 HRGN region
= ::CreatePolygonRgn(polygon
, arraysize(polygon
), WINDING
);
321 ::SetWindowRgn(window
, region
, FALSE
);
324 void TryChromeDialogView::ButtonPressed(views::Button
* sender
,
325 const ui::Event
& event
) {
326 if (sender
->tag() == BT_DONT_BUG_RADIO
) {
328 make_default_
->SetChecked(false);
329 make_default_
->SetVisible(false);
332 } else if (sender
->tag() == BT_TRY_IT_RADIO
) {
334 make_default_
->SetVisible(true);
335 make_default_
->SetChecked(true);
338 } else if (sender
->tag() == BT_CLOSE_BUTTON
) {
339 // The user pressed cancel or the [x] button.
341 } else if (!try_chrome_
) {
342 // We don't have radio buttons, the user pressed ok.
343 result_
= TRY_CHROME
;
345 // The outcome is according to the selected radio button.
346 if (try_chrome_
->checked())
347 result_
= TRY_CHROME
;
348 else if (dont_try_chrome_
&& dont_try_chrome_
->checked())
350 else if (kill_chrome_
&& kill_chrome_
->checked())
351 result_
= UNINSTALL_CHROME
;
353 NOTREACHED() << "Unknown radio button selected";
357 if ((result_
== TRY_CHROME
) && make_default_
->checked())
358 result_
= TRY_CHROME_AS_DEFAULT
;
362 base::MessageLoop::current()->Quit();
365 void TryChromeDialogView::LinkClicked(views::Link
* source
, int event_flags
) {
366 ::ShellExecuteW(NULL
, L
"open", kHelpCenterUrl
, NULL
, NULL
, SW_SHOW
);