- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / first_run / try_chrome_dialog_view.cc
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/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"
32
33 #if defined(USE_AURA)
34 #include "ui/aura/root_window.h"
35 #include "ui/aura/window.h"
36 #endif
37
38 namespace {
39
40 const wchar_t kHelpCenterUrl[] =
41     L"https://www.google.com/support/chrome/bin/answer.py?answer=150752";
42
43 enum ButtonTags {
44   BT_NONE,
45   BT_CLOSE_BUTTON,
46   BT_OK_BUTTON,
47   BT_TRY_IT_RADIO,
48   BT_DONT_BUG_RADIO
49 };
50
51 const int kRadioGroupID = 1;
52
53 }  // namespace
54
55 // static
56 TryChromeDialogView::Result TryChromeDialogView::Show(
57     size_t flavor,
58     const ActiveModalDialogListener& listener) {
59   if (flavor > 10000) {
60     // This is a test value. We want to make sure we exercise
61     // returning this early. See TryChromeDialogBrowserTest test.
62     return NOT_NOW;
63   }
64   TryChromeDialogView dialog(flavor);
65   return dialog.ShowModal(listener);
66 }
67
68 TryChromeDialogView::TryChromeDialogView(size_t flavor)
69     : flavor_(flavor),
70       popup_(NULL),
71       try_chrome_(NULL),
72       kill_chrome_(NULL),
73       dont_try_chrome_(NULL),
74       make_default_(NULL),
75       result_(COUNT)  {
76 }
77
78 TryChromeDialogView::~TryChromeDialogView() {
79 }
80
81 TryChromeDialogView::Result TryChromeDialogView::ShowModal(
82     const ActiveModalDialogListener& listener) {
83   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
84
85   views::ImageView* icon = new views::ImageView();
86   icon->SetImage(rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToImageSkia());
87   gfx::Size icon_size = icon->GetPreferredSize();
88
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;
94   popup_->Init(params);
95
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));
100
101   views::GridLayout* layout = views::GridLayout::CreatePanel(root_view);
102   root_view->SetLayoutManager(layout);
103   views::ColumnSet* columns;
104
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(),
109                      icon_size.height());
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);
116
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);
123
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);
130
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);
139
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);
146
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);
151
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);
156
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);
164
165   // First row.
166   layout->StartRow(0, 0);
167   layout->AddView(icon);
168
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.";
175     return DIALOG_ERROR;
176   }
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);
194
195   // Second row.
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_);
203
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);
207
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_);
216   }
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_);
222   }
223
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);
228
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);
238
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);
245   } else {
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);
258     }
259   }
260
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);
267   }
268
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);
273   if (separator) {
274     int separator_height = separator->GetPreferredSize().height();
275     separator->SetSize(gfx::Size(preferred.width(), separator_height));
276   }
277
278   gfx::Rect pos = ComputeWindowPosition(preferred.width(), preferred.height(),
279                                         base::i18n::IsRTL());
280   popup_->SetBounds(pos);
281
282   // Carve the toast shape into the window.
283   HWND toast_window;
284 #if defined(USE_AURA)
285   toast_window =
286       popup_->GetNativeView()->GetDispatcher()->GetAcceleratedWidget();
287 #else
288   toast_window = popup_->GetNativeView();
289 #endif
290   SetToastRegion(toast_window, preferred.width(), preferred.height());
291
292   // Time to show the window in a modal loop.
293   popup_->Show();
294   if (!listener.is_null())
295     listener.Run(popup_->GetNativeView());
296   base::MessageLoop::current()->Run();
297   if (!listener.is_null())
298     listener.Run(NULL);
299   return result_;
300 }
301
302 gfx::Rect TryChromeDialogView::ComputeWindowPosition(int width,
303                                                      int height,
304                                                      bool is_RTL) {
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);
313   }
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);
319 }
320
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.
325     {w, h}, {0, h}
326   };
327   HRGN region = ::CreatePolygonRgn(polygon, arraysize(polygon), WINDING);
328   ::SetWindowRgn(window, region, FALSE);
329 }
330
331 void TryChromeDialogView::ButtonPressed(views::Button* sender,
332                                         const ui::Event& event) {
333   if (sender->tag() == BT_DONT_BUG_RADIO) {
334     if (make_default_) {
335       make_default_->SetChecked(false);
336       make_default_->SetVisible(false);
337     }
338     return;
339   } else if (sender->tag() == BT_TRY_IT_RADIO) {
340     if (make_default_) {
341       make_default_->SetVisible(true);
342       make_default_->SetChecked(true);
343     }
344     return;
345   } else if (sender->tag() == BT_CLOSE_BUTTON) {
346     // The user pressed cancel or the [x] button.
347     result_ = NOT_NOW;
348   } else if (!try_chrome_) {
349     // We don't have radio buttons, the user pressed ok.
350     result_ = TRY_CHROME;
351   } else {
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())
356       result_ = NOT_NOW;
357     else if (kill_chrome_ && kill_chrome_->checked())
358       result_ = UNINSTALL_CHROME;
359     else
360       NOTREACHED() << "Unknown radio button selected";
361   }
362
363   if (make_default_) {
364     if ((result_ == TRY_CHROME) && make_default_->checked())
365         result_ = TRY_CHROME_AS_DEFAULT;
366   }
367
368   popup_->Close();
369   base::MessageLoop::current()->Quit();
370 }
371
372 void TryChromeDialogView::LinkClicked(views::Link* source, int event_flags) {
373   ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW);
374 }