Index: chrome/browser/ui/libgtkui/gtk_util.cc |
diff --git a/chrome/browser/ui/libgtkui/gtk_util.cc b/chrome/browser/ui/libgtkui/gtk_util.cc |
index aab971785907c7b330a0fd56d43f49e7cde63823..caed0237fab0b4900d9c69d4db49dfc7998aadcd 100644 |
--- a/chrome/browser/ui/libgtkui/gtk_util.cc |
+++ b/chrome/browser/ui/libgtkui/gtk_util.cc |
@@ -14,6 +14,9 @@ |
#include "base/command_line.h" |
#include "base/debug/leak_annotations.h" |
#include "base/environment.h" |
+#include "base/strings/string_split.h" |
+#include "base/strings/string_tokenizer.h" |
+#include "base/strings/string_util.h" |
#include "ui/aura/window.h" |
#include "ui/aura/window_tree_host.h" |
#include "ui/base/accelerators/accelerator.h" |
@@ -188,4 +191,218 @@ void ClearAuraTransientParent(GtkWidget* dialog) { |
g_object_set_data(G_OBJECT(dialog), kAuraTransientParent, NULL); |
} |
+#if GTK_MAJOR_VERSION > 2 |
+ScopedStyleContext AppendNode(GtkStyleContext* context, |
+ const std::string& css_node) { |
+ GtkWidgetPath* path = |
+ context ? gtk_widget_path_copy(gtk_style_context_get_path(context)) |
+ : gtk_widget_path_new(); |
+ |
+ enum { |
+ // TODO(thomasanderson): Add CSS_NAME here to handle the Gtk3.20 case. |
+ CSS_TYPE, |
+ CSS_CLASS, |
+ CSS_PSEUDOCLASS, |
+ } part_type = CSS_TYPE; |
+ static const struct { |
+ const char* name; |
+ GtkStateFlags state_flag; |
+ } pseudo_classes[] = { |
+ {"active", GTK_STATE_FLAG_ACTIVE}, |
+ {"hover", GTK_STATE_FLAG_PRELIGHT}, |
+ {"selected", GTK_STATE_FLAG_SELECTED}, |
+ {"disabled", GTK_STATE_FLAG_INSENSITIVE}, |
+ {"indeterminate", GTK_STATE_FLAG_INCONSISTENT}, |
+ {"focus", GTK_STATE_FLAG_FOCUSED}, |
+ {"backdrop", GTK_STATE_FLAG_BACKDROP}, |
+ // TODO(thomasanderson): These state flags are only available in |
+ // GTK 3.10 or later, which is unavailable in the wheezy |
+ // sysroot. Add them once the sysroot is updated to jessie. |
+ // { "link", GTK_STATE_FLAG_LINK }, |
+ // { "visited", GTK_STATE_FLAG_VISITED }, |
+ // { "checked", GTK_STATE_FLAG_CHECKED }, |
+ }; |
+ GtkStateFlags state = |
+ context ? gtk_style_context_get_state(context) : GTK_STATE_FLAG_NORMAL; |
+ base::StringTokenizer t(css_node, ".:"); |
+ t.set_options(base::StringTokenizer::RETURN_DELIMS); |
+ while (t.GetNext()) { |
+ if (t.token_is_delim()) { |
+ if (t.token_begin() == css_node.begin()) { |
+ // Special case for the first token. |
+ gtk_widget_path_append_type(path, G_TYPE_NONE); |
+ } |
+ switch (*t.token_begin()) { |
+ case '.': |
+ part_type = CSS_CLASS; |
+ break; |
+ case ':': |
+ part_type = CSS_PSEUDOCLASS; |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+ } else { |
+ switch (part_type) { |
+ case CSS_TYPE: { |
+ GType type = g_type_from_name(t.token().c_str()); |
+ DCHECK(type); |
+ gtk_widget_path_append_type(path, type); |
+ break; |
+ } |
+ case CSS_CLASS: { |
+ gtk_widget_path_iter_add_class(path, -1, t.token().c_str()); |
+ break; |
+ } |
+ case CSS_PSEUDOCLASS: { |
+ GtkStateFlags state_flag = GTK_STATE_FLAG_NORMAL; |
+ for (const auto& pseudo_class_entry : pseudo_classes) { |
+ if (strcmp(pseudo_class_entry.name, t.token().c_str()) == 0) { |
+ state_flag = pseudo_class_entry.state_flag; |
+ break; |
+ } |
+ } |
+ state = static_cast<GtkStateFlags>(state | state_flag); |
+ break; |
+ } |
+ } |
+ } |
+ } |
+ auto child_context = ScopedStyleContext(gtk_style_context_new()); |
+ gtk_style_context_set_path(child_context, path); |
+ gtk_style_context_set_state(child_context, state); |
+ gtk_style_context_set_parent(child_context, context); |
+ gtk_widget_path_unref(path); |
+ return child_context; |
+} |
+ |
+ScopedStyleContext GetStyleContextFromCss(const char* css_selector) { |
+ // Prepend "GtkWindow.background" to the selector since all widgets must live |
+ // in a window, but we don't want to specify that every time. |
+ auto context = AppendNode(nullptr, "GtkWindow.background"); |
+ |
+ for (const auto& widget_type : |
+ base::SplitString(css_selector, base::kWhitespaceASCII, |
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { |
+ context = AppendNode(context, widget_type); |
+ } |
+ return context; |
+} |
+ |
+SkColor GdkRgbaToSkColor(const GdkRGBA& color) { |
+ return SkColorSetARGB(color.alpha * 255, color.red * 255, color.green * 255, |
+ color.blue * 255); |
+} |
+ |
+SkColor GetFGColor(const char* css_selector) { |
+ auto context = GetStyleContextFromCss(css_selector); |
+ GdkRGBA color; |
+ gtk_style_context_get_color(context, gtk_style_context_get_state(context), |
+ &color); |
+ return GdkRgbaToSkColor(color); |
+} |
+ |
+GtkCssProvider* GetCssProvider(const char* css) { |
+ GtkCssProvider* provider = gtk_css_provider_new(); |
+ GError* error = nullptr; |
+ gtk_css_provider_load_from_data(provider, css, -1, &error); |
+ DCHECK(!error); |
+ return provider; |
+} |
+ |
+void ApplyCssToContext(GtkStyleContext* context, GtkCssProvider* provider) { |
+ while (context) { |
+ gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), |
+ G_MAXUINT); |
+ context = gtk_style_context_get_parent(context); |
+ } |
+} |
+ |
+void RemoveBorders(GtkStyleContext* context) { |
+ static GtkCssProvider* provider = GetCssProvider( |
+ "* {" |
+ "border-style: none;" |
+ "border-radius: 0px;" |
+ "border-width: 0px;" |
+ "border-image-width: 0px;" |
+ "padding: 0px;" |
+ "margin: 0px;" |
+ "}"); |
+ ApplyCssToContext(context, provider); |
+} |
+ |
+void AddBorders(GtkStyleContext* context) { |
+ static GtkCssProvider* provider = GetCssProvider( |
+ "* {" |
+ "border-style: solid;" |
+ "border-radius: 0px;" |
+ "border-width: 1px;" |
+ "padding: 0px;" |
+ "margin: 0px;" |
+ "}"); |
+ ApplyCssToContext(context, provider); |
+} |
+ |
+// A 1x1 cairo surface that GTK can render into. |
+class PixelSurface { |
+ public: |
+ PixelSurface() |
+ : surface_(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1)), |
+ cairo_(cairo_create(surface_)) {} |
+ |
+ ~PixelSurface() { |
+ cairo_destroy(cairo_); |
+ cairo_surface_destroy(surface_); |
+ } |
+ |
+ // Get the drawing context for GTK to use. |
+ cairo_t* cairo() { return cairo_; } |
+ |
+ // Get the color value of the single pixel. |
+ SkColor GetPixelValue() { |
+ return *reinterpret_cast<SkColor*>(cairo_image_surface_get_data(surface_)); |
+ } |
+ |
+ private: |
+ cairo_surface_t* surface_; |
+ cairo_t* cairo_; |
+}; |
+ |
+SkColor GetBGColor(const char* css_selector) { |
+ // Backgrounds are more general than solid colors (eg. gradients), |
+ // but chromium requires us to boil this down to one color. We |
+ // cannot use the background-color here because some themes leave it |
+ // set to a garbage color because a background-image will cover it |
+ // anyway. So we instead render the background into a single pixel, |
+ // removing any borders, and hope that we get a good color. |
+ auto context = GetStyleContextFromCss(css_selector); |
+ RemoveBorders(context); |
+ PixelSurface surface; |
+ gtk_render_background(context, surface.cairo(), 0, 0, 1, 1); |
+ return surface.GetPixelValue(); |
+} |
+ |
+SkColor GetBorderColor(const char* css_selector) { |
+ // Borders have the same issue as backgrounds, due to the |
+ // border-image property. |
+ auto context = GetStyleContextFromCss(css_selector); |
+ GtkStateFlags state = gtk_style_context_get_state(context); |
+ GtkBorderStyle border_style = GTK_BORDER_STYLE_NONE; |
+ gtk_style_context_get(context, state, GTK_STYLE_PROPERTY_BORDER_STYLE, |
+ &border_style, nullptr); |
+ GtkBorder border; |
+ gtk_style_context_get_border(context, state, &border); |
+ if ((border_style == GTK_BORDER_STYLE_NONE || |
+ border_style == GTK_BORDER_STYLE_HIDDEN) || |
+ (!border.left && !border.right && !border.top && !border.bottom)) { |
+ return SK_ColorTRANSPARENT; |
+ } |
+ |
+ AddBorders(context); |
+ PixelSurface surface; |
+ gtk_render_frame(context, surface.cairo(), 0, 0, 1, 1); |
+ return surface.GetPixelValue(); |
+} |
+#endif |
+ |
} // namespace libgtkui |