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