OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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/gtk/first_run_dialog.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "app/l10n_util.h" | |
11 #include "app/resource_bundle.h" | |
12 #include "base/i18n/rtl.h" | |
13 #include "base/message_loop.h" | |
14 #include "base/utf_string_conversions.h" | |
15 #include "chrome/browser/google/google_util.h" | |
16 #include "chrome/browser/gtk/gtk_chrome_link_button.h" | |
17 #include "chrome/browser/gtk/gtk_floating_container.h" | |
18 #include "chrome/browser/gtk/gtk_util.h" | |
19 #include "chrome/browser/platform_util.h" | |
20 #include "chrome/browser/process_singleton.h" | |
21 #include "chrome/browser/profiles/profile.h" | |
22 #include "chrome/browser/search_engines/template_url.h" | |
23 #include "chrome/browser/search_engines/template_url_model.h" | |
24 #include "chrome/browser/shell_integration.h" | |
25 #include "chrome/common/pref_names.h" | |
26 #include "chrome/common/url_constants.h" | |
27 #include "chrome/installer/util/google_update_settings.h" | |
28 #include "grit/chromium_strings.h" | |
29 #include "grit/generated_resources.h" | |
30 #include "grit/locale_settings.h" | |
31 #include "grit/theme_resources.h" | |
32 | |
33 #if defined(USE_LINUX_BREAKPAD) | |
34 #include "chrome/app/breakpad_linux.h" | |
35 #endif | |
36 | |
37 #if defined(GOOGLE_CHROME_BUILD) | |
38 #include "chrome/browser/browser_process.h" | |
39 #include "chrome/browser/prefs/pref_service.h" | |
40 #endif | |
41 | |
42 namespace { | |
43 | |
44 const gchar* kSearchEngineKey = "template-url-search-engine"; | |
45 | |
46 // Height of the label that displays the search engine's logo (in lieu of the | |
47 // actual logo) in chromium. | |
48 const int kLogoLabelHeight = 100; | |
49 | |
50 // Size of the small logo (for when we show 4 search engines). | |
51 const int kLogoLabelWidthSmall = 132; | |
52 const int kLogoLabelHeightSmall = 88; | |
53 | |
54 // The number of search engine options we normally show. It may be less than | |
55 // this number if there are not enough search engines for the current locale, | |
56 // or more if the user's imported default is not one of the top search engines | |
57 // for the current locale. | |
58 const size_t kNormalBallotSize = 3; | |
59 | |
60 // The width of the explanatory label. The 180 is the width of the large images. | |
61 const int kExplanationWidth = kNormalBallotSize * 180; | |
62 | |
63 // Horizontal spacing between search engine choices. | |
64 const int kSearchEngineSpacing = 6; | |
65 | |
66 // Set the (x, y) coordinates of the welcome message (which floats on top of | |
67 // the omnibox image at the top of the first run dialog). | |
68 void SetWelcomePosition(GtkFloatingContainer* container, | |
69 GtkAllocation* allocation, | |
70 GtkWidget* label) { | |
71 GValue value = { 0, }; | |
72 g_value_init(&value, G_TYPE_INT); | |
73 | |
74 GtkRequisition req; | |
75 gtk_widget_size_request(label, &req); | |
76 | |
77 int x = base::i18n::IsRTL() ? | |
78 allocation->width - req.width - gtk_util::kContentAreaSpacing : | |
79 gtk_util::kContentAreaSpacing; | |
80 g_value_set_int(&value, x); | |
81 gtk_container_child_set_property(GTK_CONTAINER(container), | |
82 label, "x", &value); | |
83 | |
84 int y = allocation->height / 2 - req.height / 2; | |
85 g_value_set_int(&value, y); | |
86 gtk_container_child_set_property(GTK_CONTAINER(container), | |
87 label, "y", &value); | |
88 g_value_unset(&value); | |
89 } | |
90 | |
91 } // namespace | |
92 | |
93 // static | |
94 bool FirstRunDialog::Show(Profile* profile, | |
95 bool randomize_search_engine_order) { | |
96 // Figure out which dialogs we will show. | |
97 // If the default search is managed via policy, we won't ask. | |
98 const TemplateURLModel* search_engines_model = profile->GetTemplateURLModel(); | |
99 bool show_search_engines_dialog = search_engines_model && | |
100 !search_engines_model->is_default_search_managed(); | |
101 | |
102 #if defined(GOOGLE_CHROME_BUILD) | |
103 // If the metrics reporting is managed, we won't ask. | |
104 const PrefService::Preference* metrics_reporting_pref = | |
105 g_browser_process->local_state()->FindPreference( | |
106 prefs::kMetricsReportingEnabled); | |
107 bool show_reporting_dialog = !metrics_reporting_pref || | |
108 !metrics_reporting_pref->IsManaged(); | |
109 #else | |
110 bool show_reporting_dialog = false; | |
111 #endif | |
112 | |
113 if (!show_search_engines_dialog && !show_reporting_dialog) | |
114 return true; // Nothing to do | |
115 | |
116 int response = -1; | |
117 // Object deletes itself. | |
118 new FirstRunDialog(profile, | |
119 show_reporting_dialog, | |
120 show_search_engines_dialog, | |
121 &response); | |
122 | |
123 // TODO(port): it should be sufficient to just run the dialog: | |
124 // int response = gtk_dialog_run(GTK_DIALOG(dialog)); | |
125 // but that spins a nested message loop and hoses us. :( | |
126 // http://code.google.com/p/chromium/issues/detail?id=12552 | |
127 // Instead, run a loop and extract the response manually. | |
128 MessageLoop::current()->Run(); | |
129 | |
130 return (response == GTK_RESPONSE_ACCEPT); | |
131 } | |
132 | |
133 FirstRunDialog::FirstRunDialog(Profile* profile, | |
134 bool show_reporting_dialog, | |
135 bool show_search_engines_dialog, | |
136 int* response) | |
137 : search_engine_window_(NULL), | |
138 dialog_(NULL), | |
139 report_crashes_(NULL), | |
140 make_default_(NULL), | |
141 profile_(profile), | |
142 chosen_search_engine_(NULL), | |
143 show_reporting_dialog_(show_reporting_dialog), | |
144 response_(response) { | |
145 if (!show_search_engines_dialog) { | |
146 ShowReportingDialog(); | |
147 return; | |
148 } | |
149 search_engines_model_ = profile_->GetTemplateURLModel(); | |
150 | |
151 ShowSearchEngineWindow(); | |
152 | |
153 search_engines_model_->AddObserver(this); | |
154 if (search_engines_model_->loaded()) | |
155 OnTemplateURLModelChanged(); | |
156 else | |
157 search_engines_model_->Load(); | |
158 } | |
159 | |
160 FirstRunDialog::~FirstRunDialog() { | |
161 } | |
162 | |
163 void FirstRunDialog::ShowSearchEngineWindow() { | |
164 search_engine_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); | |
165 gtk_window_set_deletable(GTK_WINDOW(search_engine_window_), FALSE); | |
166 gtk_window_set_title( | |
167 GTK_WINDOW(search_engine_window_), | |
168 l10n_util::GetStringUTF8(IDS_FIRSTRUN_DLG_TITLE).c_str()); | |
169 gtk_window_set_resizable(GTK_WINDOW(search_engine_window_), FALSE); | |
170 g_signal_connect(search_engine_window_, "destroy", | |
171 G_CALLBACK(OnSearchEngineWindowDestroyThunk), this); | |
172 GtkWidget* content_area = gtk_vbox_new(FALSE, 0); | |
173 gtk_container_add(GTK_CONTAINER(search_engine_window_), content_area); | |
174 | |
175 GdkPixbuf* pixbuf = | |
176 ResourceBundle::GetSharedInstance().GetRTLEnabledPixbufNamed( | |
177 IDR_SEARCH_ENGINE_DIALOG_TOP); | |
178 GtkWidget* top_image = gtk_image_new_from_pixbuf(pixbuf); | |
179 // Right align the image. | |
180 gtk_misc_set_alignment(GTK_MISC(top_image), 1, 0); | |
181 gtk_widget_set_size_request(top_image, 0, -1); | |
182 | |
183 GtkWidget* welcome_message = gtk_util::CreateBoldLabel( | |
184 l10n_util::GetStringUTF8(IDS_FR_SEARCH_MAIN_LABEL)); | |
185 // Force the font size to make sure the label doesn't overlap the image. | |
186 // 13.4px == 10pt @ 96dpi | |
187 gtk_util::ForceFontSizePixels(welcome_message, 13.4); | |
188 | |
189 GtkWidget* top_area = gtk_floating_container_new(); | |
190 gtk_container_add(GTK_CONTAINER(top_area), top_image); | |
191 gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(top_area), | |
192 welcome_message); | |
193 g_signal_connect(top_area, "set-floating-position", | |
194 G_CALLBACK(SetWelcomePosition), welcome_message); | |
195 | |
196 gtk_box_pack_start(GTK_BOX(content_area), top_area, | |
197 FALSE, FALSE, 0); | |
198 | |
199 GtkWidget* bubble_area_background = gtk_event_box_new(); | |
200 gtk_widget_modify_bg(bubble_area_background, | |
201 GTK_STATE_NORMAL, >k_util::kGdkWhite); | |
202 | |
203 GtkWidget* bubble_area_box = gtk_vbox_new(FALSE, 0); | |
204 gtk_container_set_border_width(GTK_CONTAINER(bubble_area_box), | |
205 gtk_util::kContentAreaSpacing); | |
206 gtk_container_add(GTK_CONTAINER(bubble_area_background), | |
207 bubble_area_box); | |
208 | |
209 GtkWidget* explanation = gtk_label_new( | |
210 l10n_util::GetStringFUTF8(IDS_FR_SEARCH_TEXT, | |
211 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)).c_str()); | |
212 gtk_misc_set_alignment(GTK_MISC(explanation), 0, 0.5); | |
213 gtk_util::SetLabelColor(explanation, >k_util::kGdkBlack); | |
214 gtk_util::SetLabelWidth(explanation, kExplanationWidth); | |
215 gtk_box_pack_start(GTK_BOX(bubble_area_box), explanation, FALSE, FALSE, 0); | |
216 | |
217 // We will fill this in after the TemplateURLModel has loaded. | |
218 // GtkHButtonBox because we want all children to have the same size. | |
219 search_engine_hbox_ = gtk_hbutton_box_new(); | |
220 gtk_box_set_spacing(GTK_BOX(search_engine_hbox_), kSearchEngineSpacing); | |
221 gtk_box_pack_start(GTK_BOX(bubble_area_box), search_engine_hbox_, | |
222 FALSE, FALSE, 0); | |
223 | |
224 gtk_box_pack_start(GTK_BOX(content_area), bubble_area_background, | |
225 TRUE, TRUE, 0); | |
226 | |
227 gtk_widget_show_all(content_area); | |
228 gtk_window_present(GTK_WINDOW(search_engine_window_)); | |
229 } | |
230 | |
231 void FirstRunDialog::ShowReportingDialog() { | |
232 // The purpose of the dialog is to ask the user to enable stats and crash | |
233 // reporting. This setting may be controlled through configuration management | |
234 // in enterprise scenarios. If that is the case, skip the dialog entirely, | |
235 // it's not worth bothering the user for only the default browser question | |
236 // (which is likely to be forced in enterprise deployments anyway). | |
237 if (!show_reporting_dialog_) { | |
238 OnResponseDialog(NULL, GTK_RESPONSE_ACCEPT); | |
239 return; | |
240 } | |
241 | |
242 dialog_ = gtk_dialog_new_with_buttons( | |
243 l10n_util::GetStringUTF8(IDS_FIRSTRUN_DLG_TITLE).c_str(), | |
244 NULL, // No parent | |
245 (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR), | |
246 NULL); | |
247 gtk_util::AddButtonToDialog(dialog_, | |
248 l10n_util::GetStringUTF8(IDS_FIRSTRUN_DLG_OK).c_str(), | |
249 GTK_STOCK_APPLY, GTK_RESPONSE_ACCEPT); | |
250 gtk_window_set_deletable(GTK_WINDOW(dialog_), FALSE); | |
251 | |
252 gtk_window_set_resizable(GTK_WINDOW(dialog_), FALSE); | |
253 | |
254 g_signal_connect(dialog_, "delete-event", | |
255 G_CALLBACK(gtk_widget_hide_on_delete), NULL); | |
256 | |
257 GtkWidget* content_area = GTK_DIALOG(dialog_)->vbox; | |
258 | |
259 make_default_ = gtk_check_button_new_with_label( | |
260 l10n_util::GetStringUTF8(IDS_FR_CUSTOMIZE_DEFAULT_BROWSER).c_str()); | |
261 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(make_default_), TRUE); | |
262 gtk_box_pack_start(GTK_BOX(content_area), make_default_, FALSE, FALSE, 0); | |
263 | |
264 report_crashes_ = gtk_check_button_new(); | |
265 GtkWidget* check_label = gtk_label_new( | |
266 l10n_util::GetStringUTF8(IDS_OPTIONS_ENABLE_LOGGING).c_str()); | |
267 gtk_label_set_line_wrap(GTK_LABEL(check_label), TRUE); | |
268 gtk_container_add(GTK_CONTAINER(report_crashes_), check_label); | |
269 GtkWidget* learn_more_vbox = gtk_vbox_new(FALSE, 0); | |
270 gtk_box_pack_start(GTK_BOX(learn_more_vbox), report_crashes_, | |
271 FALSE, FALSE, 0); | |
272 | |
273 GtkWidget* learn_more_link = gtk_chrome_link_button_new( | |
274 l10n_util::GetStringUTF8(IDS_LEARN_MORE).c_str()); | |
275 gtk_button_set_alignment(GTK_BUTTON(learn_more_link), 0.0, 0.5); | |
276 gtk_box_pack_start(GTK_BOX(learn_more_vbox), | |
277 gtk_util::IndentWidget(learn_more_link), | |
278 FALSE, FALSE, 0); | |
279 g_signal_connect(learn_more_link, "clicked", | |
280 G_CALLBACK(OnLearnMoreLinkClickedThunk), this); | |
281 | |
282 gtk_box_pack_start(GTK_BOX(content_area), learn_more_vbox, FALSE, FALSE, 0); | |
283 | |
284 g_signal_connect(dialog_, "response", | |
285 G_CALLBACK(OnResponseDialogThunk), this); | |
286 gtk_widget_show_all(dialog_); | |
287 } | |
288 | |
289 void FirstRunDialog::OnTemplateURLModelChanged() { | |
290 // We only watch the search engine model change once, on load. Remove | |
291 // observer so we don't try to redraw if engines change under us. | |
292 search_engines_model_->RemoveObserver(this); | |
293 | |
294 // Add search engines in |search_engines_model_| to buttons list. | |
295 std::vector<const TemplateURL*> ballot_engines = | |
296 search_engines_model_->GetTemplateURLs(); | |
297 // Drop any not in the first 3. | |
298 if (ballot_engines.size() > kNormalBallotSize) | |
299 ballot_engines.resize(kNormalBallotSize); | |
300 | |
301 const TemplateURL* default_search_engine = | |
302 search_engines_model_->GetDefaultSearchProvider(); | |
303 if (std::find(ballot_engines.begin(), | |
304 ballot_engines.end(), | |
305 default_search_engine) == | |
306 ballot_engines.end()) { | |
307 ballot_engines.push_back(default_search_engine); | |
308 } | |
309 | |
310 std::string choose_text = l10n_util::GetStringUTF8(IDS_FR_SEARCH_CHOOSE); | |
311 for (std::vector<const TemplateURL*>::iterator search_engine_iter = | |
312 ballot_engines.begin(); | |
313 search_engine_iter < ballot_engines.end(); | |
314 ++search_engine_iter) { | |
315 // Create a container for the search engine widgets. | |
316 GtkWidget* vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing); | |
317 | |
318 // We show text on Chromium and images on Google Chrome. | |
319 bool show_images = false; | |
320 #if defined(GOOGLE_CHROME_BUILD) | |
321 show_images = true; | |
322 #endif | |
323 | |
324 // Create the image (maybe). | |
325 int logo_id = (*search_engine_iter)->logo_id(); | |
326 if (show_images && logo_id > 0) { | |
327 GdkPixbuf* pixbuf = | |
328 ResourceBundle::GetSharedInstance().GetPixbufNamed(logo_id); | |
329 if (ballot_engines.size() > kNormalBallotSize) { | |
330 pixbuf = gdk_pixbuf_scale_simple(pixbuf, | |
331 kLogoLabelWidthSmall, | |
332 kLogoLabelHeightSmall, | |
333 GDK_INTERP_HYPER); | |
334 } else { | |
335 g_object_ref(pixbuf); | |
336 } | |
337 | |
338 GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); | |
339 gtk_box_pack_start(GTK_BOX(vbox), image, FALSE, FALSE, 0); | |
340 g_object_unref(pixbuf); | |
341 } else { | |
342 GtkWidget* logo_label = gtk_label_new(NULL); | |
343 char* markup = g_markup_printf_escaped( | |
344 "<span weight='bold' size='x-large' color='black'>%s</span>", | |
345 WideToUTF8((*search_engine_iter)->short_name()).c_str()); | |
346 gtk_label_set_markup(GTK_LABEL(logo_label), markup); | |
347 g_free(markup); | |
348 gtk_widget_set_size_request(logo_label, -1, | |
349 ballot_engines.size() > kNormalBallotSize ? kLogoLabelHeightSmall : | |
350 kLogoLabelHeight); | |
351 gtk_box_pack_start(GTK_BOX(vbox), logo_label, FALSE, FALSE, 0); | |
352 } | |
353 | |
354 // Create the button. | |
355 GtkWidget* button = gtk_button_new_with_label(choose_text.c_str()); | |
356 g_signal_connect(button, "clicked", | |
357 G_CALLBACK(OnSearchEngineButtonClickedThunk), this); | |
358 g_object_set_data(G_OBJECT(button), kSearchEngineKey, | |
359 const_cast<TemplateURL*>(*search_engine_iter)); | |
360 | |
361 GtkWidget* button_centerer = gtk_hbox_new(FALSE, 0); | |
362 gtk_box_pack_start(GTK_BOX(button_centerer), button, TRUE, FALSE, 0); | |
363 gtk_box_pack_start(GTK_BOX(vbox), button_centerer, FALSE, FALSE, 0); | |
364 | |
365 gtk_container_add(GTK_CONTAINER(search_engine_hbox_), vbox); | |
366 gtk_widget_show_all(search_engine_hbox_); | |
367 } | |
368 } | |
369 | |
370 void FirstRunDialog::OnSearchEngineButtonClicked(GtkWidget* sender) { | |
371 chosen_search_engine_ = static_cast<TemplateURL*>( | |
372 g_object_get_data(G_OBJECT(sender), kSearchEngineKey)); | |
373 gtk_widget_destroy(search_engine_window_); | |
374 } | |
375 | |
376 void FirstRunDialog::OnSearchEngineWindowDestroy(GtkWidget* sender) { | |
377 search_engine_window_ = NULL; | |
378 if (chosen_search_engine_) { | |
379 search_engines_model_->SetDefaultSearchProvider(chosen_search_engine_); | |
380 ShowReportingDialog(); | |
381 } else { | |
382 FirstRunDone(); | |
383 } | |
384 } | |
385 | |
386 void FirstRunDialog::OnResponseDialog(GtkWidget* widget, int response) { | |
387 if (dialog_) | |
388 gtk_widget_hide_all(dialog_); | |
389 *response_ = response; | |
390 | |
391 // Mark that first run has ran. | |
392 FirstRun::CreateSentinel(); | |
393 | |
394 // Check if user has opted into reporting. | |
395 if (report_crashes_ && | |
396 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(report_crashes_))) { | |
397 #if defined(USE_LINUX_BREAKPAD) | |
398 if (GoogleUpdateSettings::SetCollectStatsConsent(true)) | |
399 InitCrashReporter(); | |
400 #endif | |
401 } else { | |
402 GoogleUpdateSettings::SetCollectStatsConsent(false); | |
403 } | |
404 | |
405 // If selected set as default browser. | |
406 if (make_default_ && | |
407 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(make_default_))) { | |
408 ShellIntegration::SetAsDefaultBrowser(); | |
409 } | |
410 | |
411 FirstRunDone(); | |
412 } | |
413 | |
414 void FirstRunDialog::OnLearnMoreLinkClicked(GtkButton* button) { | |
415 platform_util::OpenExternal(google_util::AppendGoogleLocaleParam( | |
416 GURL(chrome::kLearnMoreReportingURL))); | |
417 } | |
418 | |
419 void FirstRunDialog::FirstRunDone() { | |
420 FirstRun::SetShowWelcomePagePref(); | |
421 | |
422 if (dialog_) | |
423 gtk_widget_destroy(dialog_); | |
424 MessageLoop::current()->Quit(); | |
425 delete this; | |
426 } | |
OLD | NEW |