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 27namespace { 28 29const wchar_t kHelpCenterUrl[] = 30 L"https://www.google.com/support/chrome/bin/answer.py?answer=150752"; 31 32} // namespace 33 34// static 35TryChromeDialogView::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 47TryChromeDialogView::TryChromeDialogView(size_t version) 48 : version_(version), 49 popup_(NULL), 50 try_chrome_(NULL), 51 kill_chrome_(NULL), 52 result_(COUNT) { 53} 54 55TryChromeDialogView::~TryChromeDialogView() { 56} 57 58TryChromeDialogView::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 213gfx::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 232void 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 242void 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 258void TryChromeDialogView::LinkActivated(views::Link* source, int event_flags) { 259 ::ShellExecuteW(NULL, L"open", kHelpCenterUrl, NULL, NULL, SW_SHOW); 260} 261