• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
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/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"
35 
36 namespace {
37 
38 const wchar_t kHelpCenterUrl[] =
39     L"https://www.google.com/support/chrome/bin/answer.py?answer=150752";
40 
41 enum ButtonTags {
42   BT_NONE,
43   BT_CLOSE_BUTTON,
44   BT_OK_BUTTON,
45   BT_TRY_IT_RADIO,
46   BT_DONT_BUG_RADIO
47 };
48 
49 const int kRadioGroupID = 1;
50 
51 }  // namespace
52 
53 // static
Show(size_t flavor,const ActiveModalDialogListener & listener)54 TryChromeDialogView::Result TryChromeDialogView::Show(
55     size_t flavor,
56     const ActiveModalDialogListener& listener) {
57   if (flavor > 10000) {
58     // This is a test value. We want to make sure we exercise
59     // returning this early. See TryChromeDialogBrowserTest test.
60     return NOT_NOW;
61   }
62   TryChromeDialogView dialog(flavor);
63   return dialog.ShowModal(listener);
64 }
65 
TryChromeDialogView(size_t flavor)66 TryChromeDialogView::TryChromeDialogView(size_t flavor)
67     : flavor_(flavor),
68       popup_(NULL),
69       try_chrome_(NULL),
70       kill_chrome_(NULL),
71       dont_try_chrome_(NULL),
72       make_default_(NULL),
73       result_(COUNT)  {
74 }
75 
~TryChromeDialogView()76 TryChromeDialogView::~TryChromeDialogView() {
77 }
78 
ShowModal(const ActiveModalDialogListener & listener)79 TryChromeDialogView::Result TryChromeDialogView::ShowModal(
80     const ActiveModalDialogListener& listener) {
81   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
82 
83   views::ImageView* icon = new views::ImageView();
84   icon->SetImage(rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToImageSkia());
85   gfx::Size icon_size = icon->GetPreferredSize();
86 
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;
92   popup_->Init(params);
93 
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));
98 
99   views::GridLayout* layout = views::GridLayout::CreatePanel(root_view);
100   root_view->SetLayoutManager(layout);
101   views::ColumnSet* columns;
102 
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(),
107                      icon_size.height());
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);
114 
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);
121 
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);
128 
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);
137 
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);
144 
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);
149 
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);
154 
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);
162 
163   // First row.
164   layout->StartRow(0, 0);
165   layout->AddView(icon);
166 
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.";
173     return DIALOG_ERROR;
174   }
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);
192 
193   // Second row.
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_);
201 
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);
205 
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_);
214   }
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_);
220   }
221 
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);
226 
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);
236 
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);
243   } else {
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);
256     }
257   }
258 
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);
265   }
266 
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);
271   if (separator) {
272     int separator_height = separator->GetPreferredSize().height();
273     separator->SetSize(gfx::Size(preferred.width(), separator_height));
274   }
275 
276   gfx::Rect pos = ComputeWindowPosition(preferred.width(), preferred.height(),
277                                         base::i18n::IsRTL());
278   popup_->SetBounds(pos);
279 
280   // Carve the toast shape into the window.
281   HWND toast_window;
282   toast_window = popup_->GetNativeView()->GetHost()->GetAcceleratedWidget();
283   SetToastRegion(toast_window, preferred.width(), preferred.height());
284 
285   // Time to show the window in a modal loop.
286   popup_->Show();
287   if (!listener.is_null())
288     listener.Run(popup_->GetNativeView());
289   base::MessageLoop::current()->Run();
290   if (!listener.is_null())
291     listener.Run(NULL);
292   return result_;
293 }
294 
ComputeWindowPosition(int width,int height,bool is_RTL)295 gfx::Rect TryChromeDialogView::ComputeWindowPosition(int width,
296                                                      int height,
297                                                      bool is_RTL) {
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);
306   }
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);
312 }
313 
SetToastRegion(HWND window,int w,int h)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.
318     {w, h}, {0, h}
319   };
320   HRGN region = ::CreatePolygonRgn(polygon, arraysize(polygon), WINDING);
321   ::SetWindowRgn(window, region, FALSE);
322 }
323 
ButtonPressed(views::Button * sender,const ui::Event & event)324 void TryChromeDialogView::ButtonPressed(views::Button* sender,
325                                         const ui::Event& event) {
326   if (sender->tag() == BT_DONT_BUG_RADIO) {
327     if (make_default_) {
328       make_default_->SetChecked(false);
329       make_default_->SetVisible(false);
330     }
331     return;
332   } else if (sender->tag() == BT_TRY_IT_RADIO) {
333     if (make_default_) {
334       make_default_->SetVisible(true);
335       make_default_->SetChecked(true);
336     }
337     return;
338   } else if (sender->tag() == BT_CLOSE_BUTTON) {
339     // The user pressed cancel or the [x] button.
340     result_ = NOT_NOW;
341   } else if (!try_chrome_) {
342     // We don't have radio buttons, the user pressed ok.
343     result_ = TRY_CHROME;
344   } else {
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())
349       result_ = NOT_NOW;
350     else if (kill_chrome_ && kill_chrome_->checked())
351       result_ = UNINSTALL_CHROME;
352     else
353       NOTREACHED() << "Unknown radio button selected";
354   }
355 
356   if (make_default_) {
357     if ((result_ == TRY_CHROME) && make_default_->checked())
358         result_ = TRY_CHROME_AS_DEFAULT;
359   }
360 
361   popup_->Close();
362   base::MessageLoop::current()->Quit();
363 }
364 
LinkClicked(views::Link * source,int event_flags)365 void TryChromeDialogView::LinkClicked(views::Link* source, int event_flags) {
366   ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);
367 }
368