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 53 void OnResponse(GtkWidget* dialog, int response_id) { 54 // We're done. 55 gtk_widget_destroy(dialog); 56 } 57 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 70 void OnLinkButtonClick(GtkWidget* button, const char* url) { 71 BrowserList::GetLastActive()-> 72 OpenURL(GURL(url), GURL(), NEW_WINDOW, PageTransition::LINK); 73 } 74 75 const char* GetChromiumUrl() { 76 static GURL url = google_util::AppendGoogleLocaleParam( 77 GURL(chrome::kChromiumProjectURL)); 78 return url.spec().c_str(); 79 } 80 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 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