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/base/l10n/l10n_util.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/views/controls/button/checkbox.h"
23 #include "ui/views/controls/button/image_button.h"
24 #include "ui/views/controls/button/label_button.h"
25 #include "ui/views/controls/button/radio_button.h"
26 #include "ui/views/controls/image_view.h"
27 #include "ui/views/controls/link.h"
28 #include "ui/views/controls/separator.h"
29 #include "ui/views/layout/grid_layout.h"
30 #include "ui/views/layout/layout_constants.h"
31 #include "ui/views/widget/widget.h"
34 #include "ui/aura/root_window.h"
35 #include "ui/aura/window.h"
40 const wchar_t kHelpCenterUrl[] =
41 L"https://www.google.com/support/chrome/bin/answer.py?answer=150752";
51 const int kRadioGroupID = 1;
56 TryChromeDialogView::Result TryChromeDialogView::Show(
58 const ActiveModalDialogListener& listener) {
60 // This is a test value. We want to make sure we exercise
61 // returning this early. See TryChromeDialogBrowserTest test.
64 TryChromeDialogView dialog(flavor);
65 return dialog.ShowModal(listener);
68 TryChromeDialogView::TryChromeDialogView(size_t flavor)
73 dont_try_chrome_(NULL),
78 TryChromeDialogView::~TryChromeDialogView() {
81 TryChromeDialogView::Result TryChromeDialogView::ShowModal(
82 const ActiveModalDialogListener& listener) {
83 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
85 views::ImageView* icon = new views::ImageView();
86 icon->SetImage(rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToImageSkia());
87 gfx::Size icon_size = icon->GetPreferredSize();
89 // An approximate window size. After Layout() we'll get better bounds.
90 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
91 params.can_activate = true;
92 params.bounds = gfx::Rect(310, 200);
93 popup_ = new views::Widget;
96 views::View* root_view = popup_->GetRootView();
97 // The window color is a tiny bit off-white.
98 root_view->set_background(
99 views::Background::CreateSolidBackground(0xfc, 0xfc, 0xfc));
101 views::GridLayout* layout = views::GridLayout::CreatePanel(root_view);
102 root_view->SetLayoutManager(layout);
103 views::ColumnSet* columns;
105 // First row: [icon][pad][text][pad][button].
106 columns = layout->AddColumnSet(0);
107 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
108 views::GridLayout::FIXED, icon_size.width(),
110 columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
111 columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
112 views::GridLayout::USE_PREF, 0, 0);
113 columns->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
114 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1,
115 views::GridLayout::USE_PREF, 0, 0);
117 // Optional second row: [pad][pad][radio 1].
118 columns = layout->AddColumnSet(1);
119 columns->AddPaddingColumn(0, icon_size.width());
120 columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
121 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
122 views::GridLayout::USE_PREF, 0, 0);
124 // Third row: [pad][pad][radio 2].
125 columns = layout->AddColumnSet(2);
126 columns->AddPaddingColumn(0, icon_size.width());
127 columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
128 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
129 views::GridLayout::USE_PREF, 0, 0);
131 // Fourth row: [pad][pad][button][pad][button].
132 columns = layout->AddColumnSet(3);
133 columns->AddPaddingColumn(0, icon_size.width());
134 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 0,
135 views::GridLayout::USE_PREF, 0, 0);
136 columns->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
137 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 0,
138 views::GridLayout::USE_PREF, 0, 0);
140 // Fifth row: [pad][pad][link].
141 columns = layout->AddColumnSet(4);
142 columns->AddPaddingColumn(0, icon_size.width());
143 columns->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
144 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
145 views::GridLayout::USE_PREF, 0, 0);
147 // Optional fourth row: [button].
148 columns = layout->AddColumnSet(5);
149 columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::FILL, 1,
150 views::GridLayout::USE_PREF, 0, 0);
152 // Optional fourth row: [divider]
153 columns = layout->AddColumnSet(6);
154 columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::FILL, 1,
155 views::GridLayout::USE_PREF, 0, 0);
157 // Optional fifth row [checkbox][pad][button]
158 columns = layout->AddColumnSet(7);
159 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 0,
160 views::GridLayout::USE_PREF, 0, 0);
161 columns->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
162 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1,
163 views::GridLayout::USE_PREF, 0, 0);
166 layout->StartRow(0, 0);
167 layout->AddView(icon);
169 // Find out what experiment we are conducting.
170 installer::ExperimentDetails experiment;
171 if (!BrowserDistribution::GetDistribution()->HasUserExperiments() ||
172 !installer::CreateExperimentDetails(flavor_, &experiment) ||
173 !experiment.heading) {
174 NOTREACHED() << "Cannot determine which headline to show.";
177 views::Label* label = new views::Label(
178 l10n_util::GetStringUTF16(experiment.heading));
179 label->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
180 label->SetMultiLine(true);
181 label->SizeToFit(200);
182 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
183 layout->AddView(label);
184 // The close button is custom.
185 views::ImageButton* close_button = new views::ImageButton(this);
186 close_button->SetImage(views::CustomButton::STATE_NORMAL,
187 rb.GetNativeImageNamed(IDR_CLOSE_2).ToImageSkia());
188 close_button->SetImage(views::CustomButton::STATE_HOVERED,
189 rb.GetNativeImageNamed(IDR_CLOSE_2_H).ToImageSkia());
190 close_button->SetImage(views::CustomButton::STATE_PRESSED,
191 rb.GetNativeImageNamed(IDR_CLOSE_2_P).ToImageSkia());
192 close_button->set_tag(BT_CLOSE_BUTTON);
193 layout->AddView(close_button);
196 layout->StartRowWithPadding(0, 1, 0, 10);
197 try_chrome_ = new views::RadioButton(
198 l10n_util::GetStringUTF16(IDS_TRY_TOAST_TRY_OPT), kRadioGroupID);
199 try_chrome_->SetChecked(true);
200 try_chrome_->set_tag(BT_TRY_IT_RADIO);
201 try_chrome_->set_listener(this);
202 layout->AddView(try_chrome_);
204 // Decide if the don't bug me is a button or a radio button.
205 bool dont_bug_me_button =
206 !!(experiment.flags & installer::kToastUiDontBugMeAsButton);
208 // Optional third and fourth row.
209 if (!dont_bug_me_button) {
210 layout->StartRow(0, 1);
211 dont_try_chrome_ = new views::RadioButton(
212 l10n_util::GetStringUTF16(IDS_TRY_TOAST_CANCEL), kRadioGroupID);
213 dont_try_chrome_->set_tag(BT_DONT_BUG_RADIO);
214 dont_try_chrome_->set_listener(this);
215 layout->AddView(dont_try_chrome_);
217 if (experiment.flags & installer::kToastUiUninstall) {
218 layout->StartRow(0, 2);
219 kill_chrome_ = new views::RadioButton(
220 l10n_util::GetStringUTF16(IDS_UNINSTALL_CHROME), kRadioGroupID);
221 layout->AddView(kill_chrome_);
224 views::LabelButton* accept_button = new views::LabelButton(
225 this, l10n_util::GetStringUTF16(IDS_OK));
226 accept_button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
227 accept_button->set_tag(BT_OK_BUTTON);
229 views::Separator* separator = NULL;
230 if (experiment.flags & installer::kToastUiMakeDefault) {
231 // In this flavor we have some veritical space, then a separator line
232 // and the 'make default' checkbox and the OK button on the same row.
233 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
234 layout->StartRow(0, 6);
235 separator = new views::Separator(views::Separator::HORIZONTAL);
236 layout->AddView(separator);
237 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
239 layout->StartRow(0, 7);
240 make_default_ = new views::Checkbox(
241 l10n_util::GetStringUTF16(IDS_TRY_TOAST_SET_DEFAULT));
242 make_default_->SetChecked(true);
243 layout->AddView(make_default_);
244 layout->AddView(accept_button);
246 // On this other flavor there is no checkbox, the OK button and possibly
247 // the cancel button are in the same row.
248 layout->StartRowWithPadding(0, dont_bug_me_button ? 3 : 5, 0, 10);
249 layout->AddView(accept_button);
250 if (dont_bug_me_button) {
251 // The dialog needs a "Don't bug me" as a button or as a radio button,
252 // this the button case.
253 views::LabelButton* cancel_button = new views::LabelButton(
254 this, l10n_util::GetStringUTF16(IDS_TRY_TOAST_CANCEL));
255 cancel_button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
256 cancel_button->set_tag(BT_CLOSE_BUTTON);
257 layout->AddView(cancel_button);
261 if (experiment.flags & installer::kToastUiWhyLink) {
262 layout->StartRowWithPadding(0, 4, 0, 10);
263 views::Link* link = new views::Link(
264 l10n_util::GetStringUTF16(IDS_TRY_TOAST_WHY));
265 link->set_listener(this);
266 layout->AddView(link);
269 // We resize the window according to the layout manager. This takes into
270 // account the differences between XP and Vista fonts and buttons.
271 layout->Layout(root_view);
272 gfx::Size preferred = layout->GetPreferredSize(root_view);
274 int separator_height = separator->GetPreferredSize().height();
275 separator->SetSize(gfx::Size(preferred.width(), separator_height));
278 gfx::Rect pos = ComputeWindowPosition(preferred.width(), preferred.height(),
279 base::i18n::IsRTL());
280 popup_->SetBounds(pos);
282 // Carve the toast shape into the window.
284 #if defined(USE_AURA)
286 popup_->GetNativeView()->GetDispatcher()->GetAcceleratedWidget();
288 toast_window = popup_->GetNativeView();
290 SetToastRegion(toast_window, preferred.width(), preferred.height());
292 // Time to show the window in a modal loop.
294 if (!listener.is_null())
295 listener.Run(popup_->GetNativeView());
296 base::MessageLoop::current()->Run();
297 if (!listener.is_null())
302 gfx::Rect TryChromeDialogView::ComputeWindowPosition(int width,
305 // The 'Shell_TrayWnd' is the taskbar. We like to show our window in that
306 // monitor if we can. This code works even if such window is not found.
307 HWND taskbar = ::FindWindowW(L"Shell_TrayWnd", NULL);
308 HMONITOR monitor = ::MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
309 MONITORINFO info = {sizeof(info)};
310 if (!GetMonitorInfoW(monitor, &info)) {
311 // Quite unexpected. Do a best guess at a visible rectangle.
312 return gfx::Rect(20, 20, width + 20, height + 20);
314 // The |rcWork| is the work area. It should account for the taskbars that
315 // are in the screen when we called the function.
316 int left = is_RTL ? info.rcWork.left : info.rcWork.right - width;
317 int top = info.rcWork.bottom - height;
318 return gfx::Rect(left, top, width, height);
321 void TryChromeDialogView::SetToastRegion(HWND window, int w, int h) {
322 static const POINT polygon[] = {
323 {0, 4}, {1, 2}, {2, 1}, {4, 0}, // Left side.
324 {w-4, 0}, {w-2, 1}, {w-1, 2}, {w, 4}, // Right side.
327 HRGN region = ::CreatePolygonRgn(polygon, arraysize(polygon), WINDING);
328 ::SetWindowRgn(window, region, FALSE);
331 void TryChromeDialogView::ButtonPressed(views::Button* sender,
332 const ui::Event& event) {
333 if (sender->tag() == BT_DONT_BUG_RADIO) {
335 make_default_->SetChecked(false);
336 make_default_->SetVisible(false);
339 } else if (sender->tag() == BT_TRY_IT_RADIO) {
341 make_default_->SetVisible(true);
342 make_default_->SetChecked(true);
345 } else if (sender->tag() == BT_CLOSE_BUTTON) {
346 // The user pressed cancel or the [x] button.
348 } else if (!try_chrome_) {
349 // We don't have radio buttons, the user pressed ok.
350 result_ = TRY_CHROME;
352 // The outcome is according to the selected radio button.
353 if (try_chrome_->checked())
354 result_ = TRY_CHROME;
355 else if (dont_try_chrome_ && dont_try_chrome_->checked())
357 else if (kill_chrome_ && kill_chrome_->checked())
358 result_ = UNINSTALL_CHROME;
360 NOTREACHED() << "Unknown radio button selected";
364 if ((result_ == TRY_CHROME) && make_default_->checked())
365 result_ = TRY_CHROME_AS_DEFAULT;
369 base::MessageLoop::current()->Quit();
372 void TryChromeDialogView::LinkClicked(views::Link* source, int event_flags) {
373 ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);