Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Side by Side Diff: chrome/browser/ui/gtk/gtk_util.cc

Issue 231733005: Delete the GTK+ port of Chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remerge to ToT Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698