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/ui/gtk/gtk_util.h" 6 7 #include <cairo/cairo.h> 8 9 #include <algorithm> 10 #include <cstdarg> 11 #include <map> 12 13 #include "base/environment.h" 14 #include "base/i18n/rtl.h" 15 #include "base/logging.h" 16 #include "base/nix/xdg_util.h" 17 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "chrome/browser/autocomplete/autocomplete_classifier.h" 20 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" 21 #include "chrome/browser/autocomplete/autocomplete_match.h" 22 #include "chrome/browser/browser_process.h" 23 #include "chrome/browser/profiles/profile.h" 24 #include "chrome/browser/profiles/profile_info_cache.h" 25 #include "chrome/browser/profiles/profile_manager.h" 26 #include "chrome/browser/profiles/profiles_state.h" 27 #include "chrome/browser/themes/theme_properties.h" 28 #include "chrome/browser/ui/browser.h" 29 #include "chrome/browser/ui/browser_iterator.h" 30 #include "chrome/browser/ui/browser_list.h" 31 #include "chrome/browser/ui/browser_window.h" 32 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 33 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 34 #include "chrome/browser/ui/host_desktop.h" 35 #include "grit/chrome_unscaled_resources.h" 36 #include "grit/theme_resources.h" 37 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h" 38 #include "ui/base/gtk/gtk_hig_constants.h" 39 #include "ui/base/gtk/gtk_screen_util.h" 40 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/base/resource/resource_bundle.h" 42 #include "ui/base/x/x11_util.h" 43 #include "ui/gfx/gtk_compat.h" 44 #include "ui/gfx/image/cairo_cached_surface.h" 45 #include "ui/gfx/image/image.h" 46 #include "ui/gfx/pango_util.h" 47 #include "ui/gfx/text_elider.h" 48 #include "url/gurl.h" 49 50 // These conflict with base/tracked_objects.h, so need to come last. 51 #include <gdk/gdkx.h> // NOLINT 52 53 namespace { 54 55 #if defined(GOOGLE_CHROME_BUILD) 56 static const char* kIconName = "google-chrome"; 57 #else 58 static const char* kIconName = "chromium-browser"; 59 #endif 60 61 const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>"; 62 63 // Max size of each component of the button tooltips. 64 const size_t kMaxTooltipTitleLength = 100; 65 const size_t kMaxTooltipURLLength = 400; 66 67 // Callback used in RemoveAllChildren. 68 void RemoveWidget(GtkWidget* widget, gpointer container) { 69 gtk_container_remove(GTK_CONTAINER(container), widget); 70 } 71 72 // These two functions are copped almost directly from gtk core. The only 73 // difference is that they accept middle clicks. 74 gboolean OnMouseButtonPressed(GtkWidget* widget, GdkEventButton* event, 75 gpointer userdata) { 76 if (event->type == GDK_BUTTON_PRESS) { 77 if (gtk_button_get_focus_on_click(GTK_BUTTON(widget)) && 78 !gtk_widget_has_focus(widget)) { 79 gtk_widget_grab_focus(widget); 80 } 81 82 gint button_mask = GPOINTER_TO_INT(userdata); 83 if (button_mask & (1 << event->button)) 84 gtk_button_pressed(GTK_BUTTON(widget)); 85 } 86 87 return TRUE; 88 } 89 90 gboolean OnMouseButtonReleased(GtkWidget* widget, GdkEventButton* event, 91 gpointer userdata) { 92 gint button_mask = GPOINTER_TO_INT(userdata); 93 if (button_mask && (1 << event->button)) 94 gtk_button_released(GTK_BUTTON(widget)); 95 96 return TRUE; 97 } 98 99 // Returns the approximate number of characters that can horizontally fit in 100 // |pixel_width| pixels. 101 int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) { 102 DCHECK(gtk_widget_get_realized(widget)) 103 << " widget must be realized to compute font metrics correctly"; 104 105 PangoContext* context = gtk_widget_create_pango_context(widget); 106 GtkStyle* style = gtk_widget_get_style(widget); 107 PangoFontMetrics* metrics = pango_context_get_metrics(context, 108 style->font_desc, pango_context_get_language(context)); 109 110 // This technique (max of char and digit widths) matches the code in 111 // gtklabel.c. 112 int char_width = pixel_width * PANGO_SCALE / 113 std::max(pango_font_metrics_get_approximate_char_width(metrics), 114 pango_font_metrics_get_approximate_digit_width(metrics)); 115 116 pango_font_metrics_unref(metrics); 117 g_object_unref(context); 118 119 return char_width; 120 } 121 122 void OnLabelRealize(GtkWidget* label, gpointer pixel_width) { 123 gtk_label_set_width_chars( 124 GTK_LABEL(label), 125 GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width))); 126 } 127 128 // Ownership of |icon_list| is passed to the caller. 129 GList* GetIconList() { 130 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 131 GList* icon_list = NULL; 132 icon_list = g_list_append(icon_list, 133 rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToGdkPixbuf()); 134 icon_list = g_list_append(icon_list, 135 rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf()); 136 return icon_list; 137 } 138 139 // Returns the avatar icon for |profile|. 140 // 141 // Returns NULL if there is only one profile; always returns an icon for 142 // Incognito profiles. 143 // 144 // The returned pixbuf must not be unreferenced or freed because it's owned by 145 // either the resource bundle or the profile info cache. 146 GdkPixbuf* GetAvatarIcon(Profile* profile) { 147 if (profile->IsOffTheRecord()) { 148 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 149 return rb.GetNativeImageNamed(IDR_OTR_ICON).ToGdkPixbuf(); 150 } 151 152 const ProfileInfoCache& cache = 153 g_browser_process->profile_manager()->GetProfileInfoCache(); 154 155 if (!profiles::IsMultipleProfilesEnabled() || 156 cache.GetNumberOfProfiles() < 2) 157 return NULL; 158 159 const size_t index = cache.GetIndexOfProfileWithPath(profile->GetPath()); 160 161 return (index != std::string::npos ? 162 cache.GetAvatarIconOfProfileAtIndex(index).ToGdkPixbuf() : 163 static_cast<GdkPixbuf*>(NULL)); 164 } 165 166 // Gets the Chrome product icon. 167 // 168 // If it doesn't find the icon in |theme|, it looks among the icons packaged 169 // with Chrome. 170 // 171 // Supported values of |size| are 16, 32, and 64. If the Chrome icon is found 172 // in |theme|, the returned icon may not be of the requested size if |size| 173 // has an unsupported value (GTK might scale it). If the Chrome icon is not 174 // found in |theme|, and |size| has an unsupported value, the program will be 175 // aborted with CHECK(false). 176 // 177 // The caller is responsible for calling g_object_unref() on the returned 178 // pixbuf. 179 GdkPixbuf* GetChromeIcon(GtkIconTheme* theme, const int size) { 180 if (gtk_icon_theme_has_icon(theme, kIconName)) { 181 GdkPixbuf* icon = 182 gtk_icon_theme_load_icon(theme, 183 kIconName, 184 size, 185 static_cast<GtkIconLookupFlags>(0), 186 0); 187 GdkPixbuf* icon_copy = gdk_pixbuf_copy(icon); 188 g_object_unref(icon); 189 return icon_copy; 190 } 191 192 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 193 int id = 0; 194 195 switch (size) { 196 case 16: id = IDR_PRODUCT_LOGO_16; break; 197 case 32: id = IDR_PRODUCT_LOGO_32; break; 198 case 64: id = IDR_PRODUCT_LOGO_64; break; 199 default: CHECK(false); break; 200 } 201 202 return gdk_pixbuf_copy(rb.GetNativeImageNamed(id).ToGdkPixbuf()); 203 } 204 205 // Adds |emblem| to the bottom-right corner of |icon|. 206 // 207 // Taking the ceiling of the scaled destination rect's dimensions (|dest_w| 208 // and |dest_h|) because, if the destination rect is larger than the scaled 209 // emblem, gdk_pixbuf_composite() will replicate the edge pixels of the emblem 210 // to fill the gap, which is better than a cropped emblem, I think. 211 void AddEmblem(const GdkPixbuf* emblem, GdkPixbuf* icon) { 212 const int iw = gdk_pixbuf_get_width(icon); 213 const int ih = gdk_pixbuf_get_height(icon); 214 const int ew = gdk_pixbuf_get_width(emblem); 215 const int eh = gdk_pixbuf_get_height(emblem); 216 217 const double emblem_scale = 218 (static_cast<double>(ih) / static_cast<double>(eh)) * 0.5; 219 const int dest_w = ::ceil(ew * emblem_scale); 220 const int dest_h = ::ceil(eh * emblem_scale); 221 const int x = iw - dest_w; // Used for offset_x and dest_x. 222 const int y = ih - dest_h; // Used for offset_y and dest_y. 223 224 gdk_pixbuf_composite(emblem, icon, 225 x, y, 226 dest_w, dest_h, 227 x, y, 228 emblem_scale, emblem_scale, 229 GDK_INTERP_BILINEAR, 255); 230 } 231 232 // Returns a list containing Chrome icons of various sizes emblemed with the 233 // |profile|'s avatar. 234 // 235 // If there is only one profile, no emblem is added, but icons for Incognito 236 // profiles will always get the Incognito emblem. 237 // 238 // The caller owns the list and all the icons it contains will have had their 239 // reference counts incremented. Therefore the caller should unreference each 240 // element before freeing the list. 241 GList* GetIconListWithAvatars(GtkWindow* window, Profile* profile) { 242 GtkIconTheme* theme = 243 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window))); 244 245 GdkPixbuf* icon_16 = GetChromeIcon(theme, 16); 246 GdkPixbuf* icon_32 = GetChromeIcon(theme, 32); 247 GdkPixbuf* icon_64 = GetChromeIcon(theme, 64); 248 249 const GdkPixbuf* avatar = GetAvatarIcon(profile); 250 if (avatar) { 251 AddEmblem(avatar, icon_16); 252 AddEmblem(avatar, icon_32); 253 AddEmblem(avatar, icon_64); 254 } 255 256 GList* icon_list = NULL; 257 icon_list = g_list_append(icon_list, icon_64); 258 icon_list = g_list_append(icon_list, icon_32); 259 icon_list = g_list_append(icon_list, icon_16); 260 261 return icon_list; 262 } 263 264 // Expose event handler for a container that simply suppresses the default 265 // drawing and propagates the expose event to the container's children. 266 gboolean PaintNoBackground(GtkWidget* widget, 267 GdkEventExpose* event, 268 gpointer unused) { 269 GList* children = gtk_container_get_children(GTK_CONTAINER(widget)); 270 for (GList* item = children; item; item = item->next) { 271 gtk_container_propagate_expose(GTK_CONTAINER(widget), 272 GTK_WIDGET(item->data), 273 event); 274 } 275 g_list_free(children); 276 277 return TRUE; 278 } 279 280 } // namespace 281 282 namespace gtk_util { 283 284 GtkWidget* CreateLabeledControlsGroup(std::vector<GtkWidget*>* labels, 285 const char* text, ...) { 286 va_list ap; 287 va_start(ap, text); 288 GtkWidget* table = gtk_table_new(0, 2, FALSE); 289 gtk_table_set_col_spacing(GTK_TABLE(table), 0, ui::kLabelSpacing); 290 gtk_table_set_row_spacings(GTK_TABLE(table), ui::kControlSpacing); 291 292 for (guint row = 0; text; ++row) { 293 gtk_table_resize(GTK_TABLE(table), row + 1, 2); 294 GtkWidget* control = va_arg(ap, GtkWidget*); 295 GtkWidget* label = gtk_label_new(text); 296 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); 297 if (labels) 298 labels->push_back(label); 299 300 gtk_table_attach(GTK_TABLE(table), label, 301 0, 1, row, row + 1, 302 GTK_FILL, GTK_FILL, 303 0, 0); 304 gtk_table_attach_defaults(GTK_TABLE(table), control, 305 1, 2, row, row + 1); 306 text = va_arg(ap, const char*); 307 } 308 va_end(ap); 309 310 return table; 311 } 312 313 GtkWidget* CreateGtkBorderBin(GtkWidget* child, const GdkColor* color, 314 int top, int bottom, int left, int right) { 315 // Use a GtkEventBox to get the background painted. However, we can't just 316 // use a container border, since it won't paint there. Use an alignment 317 // inside to get the sizes exactly of how we want the border painted. 318 GtkWidget* ebox = gtk_event_box_new(); 319 if (color) 320 gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, color); 321 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 322 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), top, bottom, left, right); 323 gtk_container_add(GTK_CONTAINER(alignment), child); 324 gtk_container_add(GTK_CONTAINER(ebox), alignment); 325 return ebox; 326 } 327 328 GtkWidget* LeftAlignMisc(GtkWidget* misc) { 329 gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5); 330 return misc; 331 } 332 333 GtkWidget* CreateBoldLabel(const std::string& text) { 334 GtkWidget* label = gtk_label_new(NULL); 335 char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str()); 336 gtk_label_set_markup(GTK_LABEL(label), markup); 337 g_free(markup); 338 339 return LeftAlignMisc(label); 340 } 341 342 void GetWidgetSizeFromCharacters( 343 GtkWidget* widget, double width_chars, double height_lines, 344 int* width, int* height) { 345 DCHECK(gtk_widget_get_realized(widget)) 346 << " widget must be realized to compute font metrics correctly"; 347 PangoContext* context = gtk_widget_create_pango_context(widget); 348 GtkStyle* style = gtk_widget_get_style(widget); 349 PangoFontMetrics* metrics = pango_context_get_metrics(context, 350 style->font_desc, pango_context_get_language(context)); 351 if (width) { 352 *width = static_cast<int>( 353 pango_font_metrics_get_approximate_char_width(metrics) * 354 width_chars / PANGO_SCALE); 355 } 356 if (height) { 357 *height = static_cast<int>( 358 (pango_font_metrics_get_ascent(metrics) + 359 pango_font_metrics_get_descent(metrics)) * 360 height_lines / PANGO_SCALE); 361 } 362 pango_font_metrics_unref(metrics); 363 g_object_unref(context); 364 } 365 366 void GetWidgetSizeFromResources( 367 GtkWidget* widget, int width_chars, int height_lines, 368 int* width, int* height) { 369 DCHECK(gtk_widget_get_realized(widget)) 370 << " widget must be realized to compute font metrics correctly"; 371 372 double chars = 0; 373 if (width) 374 base::StringToDouble(l10n_util::GetStringUTF8(width_chars), &chars); 375 376 double lines = 0; 377 if (height) 378 base::StringToDouble(l10n_util::GetStringUTF8(height_lines), &lines); 379 380 GetWidgetSizeFromCharacters(widget, chars, lines, width, height); 381 } 382 383 void SetWindowSizeFromResources(GtkWindow* window, 384 int width_id, int height_id, bool resizable) { 385 int width = -1; 386 int height = -1; 387 gtk_util::GetWidgetSizeFromResources(GTK_WIDGET(window), width_id, height_id, 388 (width_id != -1) ? &width : NULL, 389 (height_id != -1) ? &height : NULL); 390 391 if (resizable) { 392 gtk_window_set_default_size(window, width, height); 393 } else { 394 // For a non-resizable window, GTK tries to snap the window size 395 // to the minimum size around the content. We use the sizes in 396 // the resources to set *minimum* window size to allow windows 397 // with long titles to be wide enough to display their titles. 398 // 399 // But if GTK wants to make the window *wider* due to very wide 400 // controls, we should allow that too, so be careful to pick the 401 // wider of the resources size and the natural window size. 402 403 gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(window))); 404 GtkRequisition requisition; 405 gtk_widget_size_request(GTK_WIDGET(window), &requisition); 406 gtk_widget_set_size_request( 407 GTK_WIDGET(window), 408 width == -1 ? -1 : std::max(width, requisition.width), 409 height == -1 ? -1 : std::max(height, requisition.height)); 410 } 411 gtk_window_set_resizable(window, resizable ? TRUE : FALSE); 412 } 413 414 void MakeAppModalWindowGroup() { 415 // Older versions of GTK+ don't give us gtk_window_group_list() which is what 416 // we need to add current non-browser modal dialogs to the list. If 417 // we have 2.14+ we can do things the correct way. 418 GtkWindowGroup* window_group = gtk_window_group_new(); 419 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 420 // List all windows in this current group 421 GtkWindowGroup* old_group = 422 gtk_window_get_group((*it)->window()->GetNativeWindow()); 423 424 GList* all_windows = gtk_window_group_list_windows(old_group); 425 for (GList* window = all_windows; window; window = window->next) { 426 gtk_window_group_add_window(window_group, GTK_WINDOW(window->data)); 427 } 428 g_list_free(all_windows); 429 } 430 g_object_unref(window_group); 431 } 432 433 void AppModalDismissedUngroupWindows() { 434 // GTK only has the native desktop. 435 const BrowserList* native_browser_list = 436 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE); 437 if (!native_browser_list->empty()) { 438 std::vector<GtkWindow*> transient_windows; 439 440 // All windows should be part of one big modal group right now. 441 GtkWindowGroup* window_group = gtk_window_get_group( 442 native_browser_list->get(0)->window()->GetNativeWindow()); 443 GList* windows = gtk_window_group_list_windows(window_group); 444 445 for (GList* item = windows; item; item = item->next) { 446 GtkWindow* window = GTK_WINDOW(item->data); 447 GtkWindow* transient_for = gtk_window_get_transient_for(window); 448 if (transient_for) { 449 transient_windows.push_back(window); 450 } else { 451 GtkWindowGroup* window_group = gtk_window_group_new(); 452 gtk_window_group_add_window(window_group, window); 453 g_object_unref(window_group); 454 } 455 } 456 457 // Put each transient window in the same group as its transient parent. 458 for (std::vector<GtkWindow*>::iterator it = transient_windows.begin(); 459 it != transient_windows.end(); ++it) { 460 GtkWindow* transient_parent = gtk_window_get_transient_for(*it); 461 GtkWindowGroup* group = gtk_window_get_group(transient_parent); 462 gtk_window_group_add_window(group, *it); 463 } 464 g_list_free(windows); 465 } 466 } 467 468 void RemoveAllChildren(GtkWidget* container) { 469 gtk_container_foreach(GTK_CONTAINER(container), RemoveWidget, container); 470 } 471 472 void ForceFontSizePixels(GtkWidget* widget, double size_pixels) { 473 gfx::ScopedPangoFontDescription font_desc(pango_font_description_new()); 474 // pango_font_description_set_absolute_size sets the font size in device 475 // units, which for us is pixels. 476 pango_font_description_set_absolute_size(font_desc.get(), 477 PANGO_SCALE * size_pixels); 478 gtk_widget_modify_font(widget, font_desc.get()); 479 } 480 481 void UndoForceFontSize(GtkWidget* widget) { 482 gtk_widget_modify_font(widget, NULL); 483 } 484 485 gfx::Size GetWidgetSize(GtkWidget* widget) { 486 GtkRequisition size; 487 gtk_widget_size_request(widget, &size); 488 return gfx::Size(size.width, size.height); 489 } 490 491 void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p) { 492 DCHECK(widget); 493 DCHECK(p); 494 495 *p += ui::GetWidgetScreenOffset(widget); 496 } 497 498 GtkWidget* CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget, 499 bool pack_at_end, int padding) { 500 GtkWidget* centering_vbox = gtk_vbox_new(FALSE, 0); 501 gtk_box_pack_start(GTK_BOX(centering_vbox), widget, TRUE, FALSE, 0); 502 if (pack_at_end) 503 gtk_box_pack_end(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding); 504 else 505 gtk_box_pack_start(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding); 506 507 return centering_vbox; 508 } 509 510 void SetButtonClickableByMouseButtons(GtkWidget* button, 511 bool left, bool middle, bool right) { 512 gint button_mask = 0; 513 if (left) 514 button_mask |= 1 << 1; 515 if (middle) 516 button_mask |= 1 << 2; 517 if (right) 518 button_mask |= 1 << 3; 519 void* userdata = GINT_TO_POINTER(button_mask); 520 521 g_signal_connect(button, "button-press-event", 522 G_CALLBACK(OnMouseButtonPressed), userdata); 523 g_signal_connect(button, "button-release-event", 524 G_CALLBACK(OnMouseButtonReleased), userdata); 525 } 526 527 void SetButtonTriggersNavigation(GtkWidget* button) { 528 SetButtonClickableByMouseButtons(button, true, true, false); 529 } 530 531 int MirroredLeftPointForRect(GtkWidget* widget, const gfx::Rect& bounds) { 532 if (!base::i18n::IsRTL()) 533 return bounds.x(); 534 535 GtkAllocation allocation; 536 gtk_widget_get_allocation(widget, &allocation); 537 return allocation.width - bounds.x() - bounds.width(); 538 } 539 540 int MirroredRightPointForRect(GtkWidget* widget, const gfx::Rect& bounds) { 541 if (!base::i18n::IsRTL()) 542 return bounds.right(); 543 544 GtkAllocation allocation; 545 gtk_widget_get_allocation(widget, &allocation); 546 return allocation.width - bounds.x(); 547 } 548 549 int MirroredXCoordinate(GtkWidget* widget, int x) { 550 if (base::i18n::IsRTL()) { 551 GtkAllocation allocation; 552 gtk_widget_get_allocation(widget, &allocation); 553 return allocation.width - x; 554 } 555 return x; 556 } 557 558 bool WidgetContainsCursor(GtkWidget* widget) { 559 gint x = 0; 560 gint y = 0; 561 gtk_widget_get_pointer(widget, &x, &y); 562 return WidgetBounds(widget).Contains(x, y); 563 } 564 565 void SetDefaultWindowIcon(GtkWindow* window) { 566 GtkIconTheme* theme = 567 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window))); 568 569 if (gtk_icon_theme_has_icon(theme, kIconName)) { 570 gtk_window_set_default_icon_name(kIconName); 571 // Sometimes the WM fails to update the icon when we tell it to. The above 572 // line should be enough to update all existing windows, but it can fail, 573 // e.g. with Lucid/metacity. The following line seems to fix the common 574 // case where the first window created doesn't have an icon. 575 gtk_window_set_icon_name(window, kIconName); 576 } else { 577 GList* icon_list = GetIconList(); 578 gtk_window_set_default_icon_list(icon_list); 579 // Same logic applies here. 580 gtk_window_set_icon_list(window, icon_list); 581 g_list_free(icon_list); 582 } 583 } 584 585 void SetWindowIcon(GtkWindow* window, Profile* profile) { 586 GList* icon_list = GetIconListWithAvatars(window, profile); 587 gtk_window_set_icon_list(window, icon_list); 588 g_list_foreach(icon_list, reinterpret_cast<GFunc>(g_object_unref), NULL); 589 g_list_free(icon_list); 590 } 591 592 void SetWindowIcon(GtkWindow* window, Profile* profile, GdkPixbuf* icon) { 593 const GdkPixbuf* avatar = GetAvatarIcon(profile); 594 if (avatar) AddEmblem(avatar, icon); 595 gtk_window_set_icon(window, icon); 596 } 597 598 GtkWidget* AddButtonToDialog(GtkWidget* dialog, const gchar* text, 599 const gchar* stock_id, gint response_id) { 600 GtkWidget* button = gtk_button_new_with_label(text); 601 gtk_button_set_image(GTK_BUTTON(button), 602 gtk_image_new_from_stock(stock_id, 603 GTK_ICON_SIZE_BUTTON)); 604 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, 605 response_id); 606 return button; 607 } 608 609 GtkWidget* BuildDialogButton(GtkWidget* dialog, int ids_id, 610 const gchar* stock_id) { 611 GtkWidget* button = gtk_button_new_with_mnemonic( 612 ui::ConvertAcceleratorsFromWindowsStyle( 613 l10n_util::GetStringUTF8(ids_id)).c_str()); 614 gtk_button_set_image(GTK_BUTTON(button), 615 gtk_image_new_from_stock(stock_id, 616 GTK_ICON_SIZE_BUTTON)); 617 return button; 618 } 619 620 GtkWidget* CreateEntryImageHBox(GtkWidget* entry, GtkWidget* image) { 621 GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing); 622 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); 623 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); 624 return hbox; 625 } 626 627 void SetLabelColor(GtkWidget* label, const GdkColor* color) { 628 gtk_widget_modify_fg(label, GTK_STATE_NORMAL, color); 629 gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, color); 630 gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, color); 631 gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, color); 632 } 633 634 GtkWidget* IndentWidget(GtkWidget* content) { 635 GtkWidget* content_alignment = gtk_alignment_new(0.0, 0.5, 1.0, 1.0); 636 gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment), 0, 0, 637 ui::kGroupIndent, 0); 638 gtk_container_add(GTK_CONTAINER(content_alignment), content); 639 return content_alignment; 640 } 641 642 GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr) { 643 GdkPoint point = {ltr ? x : width - x, y}; 644 return point; 645 } 646 647 std::string BuildTooltipTitleFor(base::string16 title, const GURL& url) { 648 const std::string& url_str = url.possibly_invalid_spec(); 649 const std::string& title_str = UTF16ToUTF8(title); 650 651 std::string truncated_url = UTF16ToUTF8(gfx::TruncateString( 652 UTF8ToUTF16(url_str), kMaxTooltipURLLength)); 653 gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(), 654 truncated_url.size()); 655 std::string escaped_url(escaped_url_cstr); 656 g_free(escaped_url_cstr); 657 658 if (url_str == title_str || title.empty()) { 659 return escaped_url; 660 } else { 661 std::string truncated_title = UTF16ToUTF8(gfx::TruncateString( 662 title, kMaxTooltipTitleLength)); 663 gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(), 664 truncated_title.size()); 665 std::string escaped_title(escaped_title_cstr); 666 g_free(escaped_title_cstr); 667 668 if (!escaped_url.empty()) 669 return std::string("<b>") + escaped_title + "</b>\n" + escaped_url; 670 else 671 return std::string("<b>") + escaped_title + "</b>"; 672 } 673 } 674 675 void DrawTextEntryBackground(GtkWidget* offscreen_entry, 676 GtkWidget* widget_to_draw_on, 677 GdkRectangle* dirty_rec, 678 GdkRectangle* rec) { 679 GtkStyle* gtk_owned_style = gtk_rc_get_style(offscreen_entry); 680 // GTK owns the above and we're going to have to make our own copy of it 681 // that we can edit. 682 GtkStyle* our_style = gtk_style_copy(gtk_owned_style); 683 our_style = gtk_style_attach(our_style, widget_to_draw_on->window); 684 685 // TODO(erg): Draw the focus ring if appropriate... 686 687 // We're using GTK rendering; draw a GTK entry widget onto the background. 688 gtk_paint_shadow(our_style, widget_to_draw_on->window, 689 GTK_STATE_NORMAL, GTK_SHADOW_IN, dirty_rec, 690 widget_to_draw_on, "entry", 691 rec->x, rec->y, rec->width, rec->height); 692 693 // Draw the interior background (not all themes draw the entry background 694 // above; this is a noop on themes that do). 695 gint xborder = our_style->xthickness; 696 gint yborder = our_style->ythickness; 697 gint width = rec->width - 2 * xborder; 698 gint height = rec->height - 2 * yborder; 699 if (width > 0 && height > 0) { 700 gtk_paint_flat_box(our_style, widget_to_draw_on->window, 701 GTK_STATE_NORMAL, GTK_SHADOW_NONE, dirty_rec, 702 widget_to_draw_on, "entry_bg", 703 rec->x + xborder, rec->y + yborder, 704 width, height); 705 } 706 707 gtk_style_detach(our_style); 708 g_object_unref(our_style); 709 } 710 711 void SetLayoutText(PangoLayout* layout, const base::string16& text) { 712 // Pango is really easy to overflow and send into a computational death 713 // spiral that can corrupt the screen. Assume that we'll never have more than 714 // 2000 characters, which should be a safe assumption until we all get robot 715 // eyes. http://crbug.com/66576 716 std::string text_utf8 = UTF16ToUTF8(text); 717 if (text_utf8.length() > 2000) 718 text_utf8 = text_utf8.substr(0, 2000); 719 720 pango_layout_set_text(layout, text_utf8.data(), text_utf8.length()); 721 } 722 723 void DrawThemedToolbarBackground(GtkWidget* widget, 724 cairo_t* cr, 725 GdkEventExpose* event, 726 const gfx::Point& tabstrip_origin, 727 GtkThemeService* theme_service) { 728 // Fill the entire region with the toolbar color. 729 GdkColor color = theme_service->GetGdkColor( 730 ThemeProperties::COLOR_TOOLBAR); 731 gdk_cairo_set_source_color(cr, &color); 732 cairo_fill(cr); 733 734 // The toolbar is supposed to blend in with the active tab, so we have to pass 735 // coordinates for the IDR_THEME_TOOLBAR bitmap relative to the top of the 736 // tab strip. 737 const gfx::Image background = 738 theme_service->GetImageNamed(IDR_THEME_TOOLBAR); 739 background.ToCairo()->SetSource(cr, widget, 740 tabstrip_origin.x(), tabstrip_origin.y()); 741 // We tile the toolbar background in both directions. 742 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); 743 cairo_rectangle(cr, 744 tabstrip_origin.x(), 745 tabstrip_origin.y(), 746 event->area.x + event->area.width - tabstrip_origin.x(), 747 event->area.y + event->area.height - tabstrip_origin.y()); 748 cairo_fill(cr); 749 } 750 751 void DrawFullImage(cairo_t* cr, 752 GtkWidget* widget, 753 const gfx::Image& image, 754 gint dest_x, 755 gint dest_y) { 756 gfx::CairoCachedSurface* surface = image.ToCairo(); 757 surface->SetSource(cr, widget, dest_x, dest_y); 758 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); 759 cairo_rectangle(cr, dest_x, dest_y, surface->Width(), surface->Height()); 760 cairo_fill(cr); 761 } 762 763 GdkColor AverageColors(GdkColor color_one, GdkColor color_two) { 764 GdkColor average_color; 765 average_color.pixel = 0; 766 average_color.red = (color_one.red + color_two.red) / 2; 767 average_color.green = (color_one.green + color_two.green) / 2; 768 average_color.blue = (color_one.blue + color_two.blue) / 2; 769 return average_color; 770 } 771 772 void SetAlwaysShowImage(GtkWidget* image_menu_item) { 773 gtk_image_menu_item_set_always_show_image( 774 GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE); 775 } 776 777 gfx::Rect GetWidgetRectRelativeToToplevel(GtkWidget* widget) { 778 DCHECK(gtk_widget_get_realized(widget)); 779 780 GtkWidget* toplevel = gtk_widget_get_toplevel(widget); 781 DCHECK(toplevel); 782 DCHECK(gtk_widget_get_realized(toplevel)); 783 784 gint x = 0, y = 0; 785 gtk_widget_translate_coordinates(widget, 786 toplevel, 787 0, 0, 788 &x, &y); 789 790 GtkAllocation allocation; 791 gtk_widget_get_allocation(widget, &allocation); 792 return gfx::Rect(x, y, allocation.width, allocation.height); 793 } 794 795 void SuppressDefaultPainting(GtkWidget* container) { 796 g_signal_connect(container, "expose-event", 797 G_CALLBACK(PaintNoBackground), NULL); 798 } 799 800 bool GrabAllInput(GtkWidget* widget) { 801 guint time = gtk_get_current_event_time(); 802 803 if (!gtk_widget_get_visible(widget)) 804 return false; 805 806 GdkWindow* gdk_window = gtk_widget_get_window(widget); 807 if (gdk_pointer_grab(gdk_window, 808 TRUE, 809 GdkEventMask(GDK_BUTTON_PRESS_MASK | 810 GDK_BUTTON_RELEASE_MASK | 811 GDK_ENTER_NOTIFY_MASK | 812 GDK_LEAVE_NOTIFY_MASK | 813 GDK_POINTER_MOTION_MASK), 814 NULL, NULL, time) != 0) { 815 return false; 816 } 817 818 if (gdk_keyboard_grab(gdk_window, TRUE, time) != 0) { 819 gdk_display_pointer_ungrab(gdk_drawable_get_display(gdk_window), time); 820 return false; 821 } 822 823 gtk_grab_add(widget); 824 return true; 825 } 826 827 gfx::Rect WidgetBounds(GtkWidget* widget) { 828 // To quote the gtk docs: 829 // 830 // Widget coordinates are a bit odd; for historical reasons, they are 831 // defined as widget->window coordinates for widgets that are not 832 // GTK_NO_WINDOW widgets, and are relative to allocation.x, allocation.y 833 // for widgets that are GTK_NO_WINDOW widgets. 834 // 835 // So the base is always (0,0). 836 GtkAllocation allocation; 837 gtk_widget_get_allocation(widget, &allocation); 838 return gfx::Rect(0, 0, allocation.width, allocation.height); 839 } 840 841 void SetWMLastUserActionTime(GtkWindow* window) { 842 gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window)), 843 XTimeNow()); 844 } 845 846 guint32 XTimeNow() { 847 struct timespec ts; 848 clock_gettime(CLOCK_MONOTONIC, &ts); 849 return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 850 } 851 852 bool URLFromPrimarySelection(Profile* profile, GURL* url) { 853 GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 854 DCHECK(clipboard); 855 gchar* selection_text = gtk_clipboard_wait_for_text(clipboard); 856 if (!selection_text) 857 return false; 858 859 // Use autocomplete to clean up the text, going so far as to turn it into 860 // a search query if necessary. 861 AutocompleteMatch match; 862 AutocompleteClassifierFactory::GetForProfile(profile)->Classify( 863 UTF8ToUTF16(selection_text), false, false, &match, NULL); 864 g_free(selection_text); 865 if (!match.destination_url.is_valid()) 866 return false; 867 868 *url = match.destination_url; 869 return true; 870 } 871 872 bool AddWindowAlphaChannel(GtkWidget* window) { 873 GdkScreen* screen = gtk_widget_get_screen(window); 874 GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen); 875 if (rgba) 876 gtk_widget_set_colormap(window, rgba); 877 878 return rgba; 879 } 880 881 void GetTextColors(GdkColor* normal_base, 882 GdkColor* selected_base, 883 GdkColor* normal_text, 884 GdkColor* selected_text) { 885 GtkWidget* fake_entry = gtk_entry_new(); 886 GtkStyle* style = gtk_rc_get_style(fake_entry); 887 888 if (normal_base) 889 *normal_base = style->base[GTK_STATE_NORMAL]; 890 if (selected_base) 891 *selected_base = style->base[GTK_STATE_SELECTED]; 892 if (normal_text) 893 *normal_text = style->text[GTK_STATE_NORMAL]; 894 if (selected_text) 895 *selected_text = style->text[GTK_STATE_SELECTED]; 896 897 g_object_ref_sink(fake_entry); 898 g_object_unref(fake_entry); 899 } 900 901 void ShowDialog(GtkWidget* dialog) { 902 gtk_widget_show_all(dialog); 903 } 904 905 void ShowDialogWithLocalizedSize(GtkWidget* dialog, 906 int width_id, 907 int height_id, 908 bool resizeable) { 909 gtk_widget_realize(dialog); 910 SetWindowSizeFromResources(GTK_WINDOW(dialog), 911 width_id, 912 height_id, 913 resizeable); 914 gtk_widget_show_all(dialog); 915 } 916 917 void ShowDialogWithMinLocalizedWidth(GtkWidget* dialog, 918 int width_id) { 919 gtk_widget_show_all(dialog); 920 921 // Suggest a minimum size. 922 gint width; 923 GtkRequisition req; 924 gtk_widget_size_request(dialog, &req); 925 gtk_util::GetWidgetSizeFromResources(dialog, width_id, 0, &width, NULL); 926 if (width > req.width) 927 gtk_widget_set_size_request(dialog, width, -1); 928 } 929 930 void PresentWindow(GtkWidget* window, int timestamp) { 931 if (timestamp) 932 gtk_window_present_with_time(GTK_WINDOW(window), timestamp); 933 else 934 gtk_window_present(GTK_WINDOW(window)); 935 } 936 937 gfx::Rect GetDialogBounds(GtkWidget* dialog) { 938 gint x = 0, y = 0, width = 1, height = 1; 939 gtk_window_get_position(GTK_WINDOW(dialog), &x, &y); 940 gtk_window_get_size(GTK_WINDOW(dialog), &width, &height); 941 942 return gfx::Rect(x, y, width, height); 943 } 944 945 base::string16 GetStockPreferencesMenuLabel() { 946 GtkStockItem stock_item; 947 base::string16 preferences; 948 if (gtk_stock_lookup(GTK_STOCK_PREFERENCES, &stock_item)) { 949 const char16 kUnderscore[] = { '_', 0 }; 950 base::RemoveChars(UTF8ToUTF16(stock_item.label), kUnderscore, &preferences); 951 } 952 return preferences; 953 } 954 955 bool IsWidgetAncestryVisible(GtkWidget* widget) { 956 GtkWidget* parent = widget; 957 while (parent && gtk_widget_get_visible(parent)) 958 parent = gtk_widget_get_parent(parent); 959 return !parent; 960 } 961 962 void SetLabelWidth(GtkWidget* label, int pixel_width) { 963 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); 964 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); 965 966 // Do the simple thing in LTR because the bug only affects right-aligned 967 // text. Also, when using the workaround, the label tries to maintain 968 // uniform line-length, which we don't really want. 969 if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) { 970 gtk_widget_set_size_request(label, pixel_width, -1); 971 } else { 972 // The label has to be realized before we can adjust its width. 973 if (gtk_widget_get_realized(label)) { 974 OnLabelRealize(label, GINT_TO_POINTER(pixel_width)); 975 } else { 976 g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize), 977 GINT_TO_POINTER(pixel_width)); 978 } 979 } 980 } 981 982 void InitLabelSizeRequestAndEllipsizeMode(GtkWidget* label) { 983 GtkRequisition size; 984 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE); 985 gtk_widget_set_size_request(label, -1, -1); 986 gtk_widget_size_request(label, &size); 987 gtk_widget_set_size_request(label, size.width, size.height); 988 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END); 989 } 990 991 void ApplyMessageDialogQuirks(GtkWidget* dialog) { 992 if (gtk_window_get_modal(GTK_WINDOW(dialog))) { 993 // Work around a KDE 3 window manager bug. 994 scoped_ptr<base::Environment> env(base::Environment::Create()); 995 if (base::nix::DESKTOP_ENVIRONMENT_KDE3 == 996 base::nix::GetDesktopEnvironment(env.get())) 997 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE); 998 } 999 } 1000 1001 } // namespace gtk_util 1002