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/ui/gtk/about_chrome_dialog.h"
6
7 #include <gtk/gtk.h>
8
9 #include <string>
10 #include <vector>
11
12 #include "base/utf_string_conversions.h"
13 #include "chrome/browser/google/google_util.h"
14 #include "chrome/browser/platform_util.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser_list.h"
17 #include "chrome/browser/ui/gtk/cairo_cached_surface.h"
18 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
19 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
20 #include "chrome/browser/ui/gtk/gtk_util.h"
21 #include "chrome/common/chrome_constants.h"
22 #include "chrome/common/chrome_version_info.h"
23 #include "chrome/common/url_constants.h"
24 #include "grit/chromium_strings.h"
25 #include "grit/generated_resources.h"
26 #include "grit/locale_settings.h"
27 #include "grit/theme_resources.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "webkit/glue/webkit_glue.h"
31
32 namespace {
33
34 // Left or right margin.
35 const int kPanelHorizMargin = 13;
36
37 // Top or bottom margin.
38 const int kPanelVertMargin = 20;
39
40 // Extra spacing between product name and version number.
41 const int kExtraLineSpacing = 5;
42
43 // These are used as placeholder text around the links in the text in the about
44 // dialog.
45 const char* kBeginLinkChr = "BEGIN_LINK_CHR";
46 const char* kBeginLinkOss = "BEGIN_LINK_OSS";
47 // We don't actually use this one.
48 // const char* kEndLinkChr = "END_LINK_CHR";
49 const char* kEndLinkOss = "END_LINK_OSS";
50 const char* kBeginLink = "BEGIN_LINK";
51 const char* kEndLink = "END_LINK";
52
OnResponse(GtkWidget * dialog,int response_id)53 void OnResponse(GtkWidget* dialog, int response_id) {
54 // We're done.
55 gtk_widget_destroy(dialog);
56 }
57
MakeMarkupLabel(const char * format,const std::string & str)58 GtkWidget* MakeMarkupLabel(const char* format, const std::string& str) {
59 GtkWidget* label = gtk_label_new(NULL);
60 char* markup = g_markup_printf_escaped(format, str.c_str());
61 gtk_label_set_markup(GTK_LABEL(label), markup);
62 g_free(markup);
63
64 // Left align it.
65 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
66
67 return label;
68 }
69
OnLinkButtonClick(GtkWidget * button,const char * url)70 void OnLinkButtonClick(GtkWidget* button, const char* url) {
71 BrowserList::GetLastActive()->
72 OpenURL(GURL(url), GURL(), NEW_WINDOW, PageTransition::LINK);
73 }
74
GetChromiumUrl()75 const char* GetChromiumUrl() {
76 static GURL url = google_util::AppendGoogleLocaleParam(
77 GURL(chrome::kChromiumProjectURL));
78 return url.spec().c_str();
79 }
80
OnEventBoxExpose(GtkWidget * event_box,GdkEventExpose * expose,gboolean user_data)81 gboolean OnEventBoxExpose(GtkWidget* event_box,
82 GdkEventExpose* expose,
83 gboolean user_data) {
84 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(event_box->window));
85 gdk_cairo_rectangle(cr, &expose->area);
86 cairo_clip(cr);
87 GtkThemeService* theme_provider =
88 GtkThemeService::GetFrom(BrowserList::GetLastActive()->profile());
89 CairoCachedSurface* background = theme_provider->GetSurfaceNamed(
90 IDR_ABOUT_BACKGROUND_COLOR, event_box);
91
92 background->SetSource(cr, 0, 0);
93 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
94 gdk_cairo_rectangle(cr, &expose->area);
95 cairo_fill(cr);
96 cairo_destroy(cr);
97 return FALSE;
98 }
99
100 } // namespace
101
ShowAboutDialogForProfile(GtkWindow * parent,Profile * profile)102 void ShowAboutDialogForProfile(GtkWindow* parent, Profile* profile) {
103 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
104 static GdkPixbuf* background = rb.GetPixbufNamed(IDR_ABOUT_BACKGROUND);
105 chrome::VersionInfo version_info;
106 std::string current_version = version_info.Version();
107 #if !defined(GOOGLE_CHROME_BUILD)
108 current_version += " (";
109 current_version += version_info.LastChange();
110 current_version += ")";
111 #endif
112 std::string channel = platform_util::GetVersionStringModifier();
113 if (!channel.empty())
114 current_version += " " + channel;
115
116 // Build the dialog.
117 GtkWidget* dialog = gtk_dialog_new_with_buttons(
118 l10n_util::GetStringUTF8(IDS_ABOUT_CHROME_TITLE).c_str(),
119 parent,
120 GTK_DIALOG_MODAL,
121 NULL);
122 // Pick up the style set in gtk_util.cc:InitRCStyles().
123 // The layout of this dialog is special because the logo should be flush
124 // with the edges of the window.
125 gtk_widget_set_name(dialog, "about-dialog");
126 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
127
128 GtkWidget* close_button = gtk_dialog_add_button(GTK_DIALOG(dialog),
129 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
130
131 GtkWidget* content_area = GTK_DIALOG(dialog)->vbox;
132
133 // Use an event box to get the background painting correctly
134 GtkWidget* ebox = gtk_event_box_new();
135 gtk_widget_set_app_paintable(ebox, TRUE);
136 g_signal_connect(ebox, "expose-event", G_CALLBACK(OnEventBoxExpose), NULL);
137
138 GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
139
140 GtkWidget* text_alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
141 gtk_alignment_set_padding(GTK_ALIGNMENT(text_alignment),
142 kPanelVertMargin, kPanelVertMargin,
143 kPanelHorizMargin, kPanelHorizMargin);
144
145 GtkWidget* text_vbox = gtk_vbox_new(FALSE, kExtraLineSpacing);
146
147 GdkColor black = gtk_util::kGdkBlack;
148 GtkWidget* product_label = MakeMarkupLabel(
149 "<span font_desc=\"18\" style=\"normal\">%s</span>",
150 l10n_util::GetStringUTF8(IDS_PRODUCT_NAME));
151 gtk_widget_modify_fg(product_label, GTK_STATE_NORMAL, &black);
152 gtk_box_pack_start(GTK_BOX(text_vbox), product_label, FALSE, FALSE, 0);
153
154 GtkWidget* version_label = gtk_label_new(current_version.c_str());
155 gtk_misc_set_alignment(GTK_MISC(version_label), 0.0, 0.5);
156 gtk_label_set_selectable(GTK_LABEL(version_label), TRUE);
157 gtk_widget_modify_fg(version_label, GTK_STATE_NORMAL, &black);
158 gtk_box_pack_start(GTK_BOX(text_vbox), version_label, FALSE, FALSE, 0);
159
160 gtk_container_add(GTK_CONTAINER(text_alignment), text_vbox);
161 gtk_box_pack_start(GTK_BOX(hbox), text_alignment, TRUE, TRUE, 0);
162
163 GtkWidget* image_vbox = gtk_vbox_new(FALSE, 0);
164 gtk_box_pack_end(GTK_BOX(image_vbox),
165 gtk_image_new_from_pixbuf(background),
166 FALSE, FALSE, 0);
167
168 gtk_box_pack_start(GTK_BOX(hbox), image_vbox, FALSE, FALSE, 0);
169 gtk_container_add(GTK_CONTAINER(ebox), hbox);
170 gtk_box_pack_start(GTK_BOX(content_area), ebox, TRUE, TRUE, 0);
171
172 // We use a separate box for the licensing etc. text. See the comment near
173 // the top of this function about using a special layout for this dialog.
174 GtkWidget* vbox = gtk_vbox_new(FALSE, 0);
175
176 GtkWidget* copyright_label = gtk_label_new(
177 l10n_util::GetStringUTF8(IDS_ABOUT_VERSION_COPYRIGHT).c_str());
178 gtk_misc_set_alignment(GTK_MISC(copyright_label), 0.0, 0.5);
179 gtk_box_pack_start(GTK_BOX(vbox), copyright_label, FALSE, FALSE, 5);
180
181 std::string license = l10n_util::GetStringUTF8(IDS_ABOUT_VERSION_LICENSE);
182 bool chromium_url_appears_first =
183 license.find(kBeginLinkChr) < license.find(kBeginLinkOss);
184 size_t link1 = license.find(kBeginLink);
185 DCHECK(link1 != std::string::npos);
186 size_t link1_end = license.find(kEndLink, link1);
187 DCHECK(link1_end != std::string::npos);
188 size_t link2 = license.find(kBeginLink, link1_end);
189 DCHECK(link2 != std::string::npos);
190 size_t link2_end = license.find(kEndLink, link2);
191 DCHECK(link1_end != std::string::npos);
192
193 GtkWidget* license_chunk1 = gtk_label_new(license.substr(0, link1).c_str());
194 gtk_misc_set_alignment(GTK_MISC(license_chunk1), 0.0, 0.5);
195 GtkWidget* license_chunk2 = gtk_label_new(
196 license.substr(link1_end + strlen(kEndLinkOss),
197 link2 - link1_end - strlen(kEndLinkOss)).c_str());
198 gtk_misc_set_alignment(GTK_MISC(license_chunk2), 0.0, 0.5);
199 GtkWidget* license_chunk3 = gtk_label_new(
200 license.substr(link2_end + strlen(kEndLinkOss)).c_str());
201 gtk_misc_set_alignment(GTK_MISC(license_chunk3), 0.0, 0.5);
202
203 std::string first_link_text =
204 license.substr(link1 + strlen(kBeginLinkOss),
205 link1_end - link1 - strlen(kBeginLinkOss));
206 std::string second_link_text =
207 license.substr(link2 + strlen(kBeginLinkOss),
208 link2_end - link2 - strlen(kBeginLinkOss));
209
210 GtkWidget* first_link = gtk_chrome_link_button_new(first_link_text.c_str());
211 GtkWidget* second_link = gtk_chrome_link_button_new(second_link_text.c_str());
212 if (!chromium_url_appears_first) {
213 GtkWidget* swap = second_link;
214 second_link = first_link;
215 first_link = swap;
216 }
217
218 g_signal_connect(chromium_url_appears_first ? first_link : second_link,
219 "clicked", G_CALLBACK(OnLinkButtonClick),
220 const_cast<char*>(GetChromiumUrl()));
221 g_signal_connect(chromium_url_appears_first ? second_link : first_link,
222 "clicked", G_CALLBACK(OnLinkButtonClick),
223 const_cast<char*>(chrome::kAboutCreditsURL));
224
225 GtkWidget* license_hbox = gtk_hbox_new(FALSE, 0);
226 gtk_box_pack_start(GTK_BOX(license_hbox), license_chunk1,
227 FALSE, FALSE, 0);
228 gtk_box_pack_start(GTK_BOX(license_hbox), first_link,
229 FALSE, FALSE, 0);
230 gtk_box_pack_start(GTK_BOX(license_hbox), license_chunk2,
231 FALSE, FALSE, 0);
232
233 // Since there's no good way to dynamically wrap the license block, force
234 // a line break right before the second link (which matches en-US Windows
235 // chromium).
236 GtkWidget* license_hbox2 = gtk_hbox_new(FALSE, 0);
237 gtk_box_pack_start(GTK_BOX(license_hbox2), second_link,
238 FALSE, FALSE, 0);
239 gtk_box_pack_start(GTK_BOX(license_hbox2), license_chunk3,
240 FALSE, FALSE, 0);
241
242 GtkWidget* license_vbox = gtk_vbox_new(FALSE, 0);
243 gtk_box_pack_start(GTK_BOX(license_vbox), license_hbox, FALSE, FALSE, 0);
244 gtk_box_pack_start(GTK_BOX(license_vbox), license_hbox2, FALSE, FALSE, 0);
245 gtk_box_pack_start(GTK_BOX(vbox), license_vbox, FALSE, FALSE, 0);
246
247 #if defined(GOOGLE_CHROME_BUILD)
248 // Spacing line.
249 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(""), FALSE, FALSE, 0);
250
251 std::vector<size_t> url_offsets;
252 string16 text = l10n_util::GetStringFUTF16(IDS_ABOUT_TERMS_OF_SERVICE,
253 string16(),
254 string16(),
255 &url_offsets);
256
257 GtkWidget* tos_chunk1 = gtk_label_new(
258 UTF16ToUTF8(text.substr(0, url_offsets[0])).c_str());
259 gtk_misc_set_alignment(GTK_MISC(tos_chunk1), 0.0, 0.5);
260 GtkWidget* tos_link = gtk_chrome_link_button_new(
261 l10n_util::GetStringUTF8(IDS_TERMS_OF_SERVICE).c_str());
262 GtkWidget* tos_chunk2 = gtk_label_new(
263 UTF16ToUTF8(text.substr(url_offsets[0])).c_str());
264 gtk_misc_set_alignment(GTK_MISC(tos_chunk2), 0.0, 0.5);
265
266 GtkWidget* tos_hbox = gtk_hbox_new(FALSE, 0);
267 gtk_box_pack_start(GTK_BOX(tos_hbox), tos_chunk1, FALSE, FALSE, 0);
268 gtk_box_pack_start(GTK_BOX(tos_hbox), tos_link, FALSE, FALSE, 0);
269 gtk_box_pack_start(GTK_BOX(tos_hbox), tos_chunk2, FALSE, FALSE, 0);
270
271 g_signal_connect(tos_link, "clicked", G_CALLBACK(OnLinkButtonClick),
272 const_cast<char*>(chrome::kAboutTermsURL));
273 gtk_box_pack_start(GTK_BOX(vbox), tos_hbox, TRUE, TRUE, 0);
274 #endif
275
276 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
277 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment),
278 gtk_util::kContentAreaBorder, 0,
279 gtk_util::kContentAreaBorder, gtk_util::kContentAreaBorder);
280 gtk_container_add(GTK_CONTAINER(alignment), vbox);
281 gtk_box_pack_start(GTK_BOX(content_area), alignment, FALSE, FALSE, 0);
282
283 g_signal_connect(dialog, "response", G_CALLBACK(OnResponse), NULL);
284 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
285 gtk_widget_show_all(dialog);
286 gtk_widget_grab_focus(close_button);
287 }
288