summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-20 18:27:06 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-20 18:27:06 +0000
commit9dd7e3d78c14f67c5c3d78868a3a63bbc4f90634 (patch)
tree3b7332926a05a1c8382eb27422c385b56b29cb24 /ui
parenta12f7fbe12afffb4b1b31ec0d6b0988f1f9a6554 (diff)
downloadchromium_src-9dd7e3d78c14f67c5c3d78868a3a63bbc4f90634.zip
chromium_src-9dd7e3d78c14f67c5c3d78868a3a63bbc4f90634.tar.gz
chromium_src-9dd7e3d78c14f67c5c3d78868a3a63bbc4f90634.tar.bz2
Move a bunch of random other files to src/ui/base
BUG=none TEST=none TBR=brettw Review URL: http://codereview.chromium.org/6257006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71970 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r--ui/base/gtk/event_synthesis_gtk.cc90
-rw-r--r--ui/base/gtk/event_synthesis_gtk.h37
-rw-r--r--ui/base/gtk/gtk_integers.h28
-rw-r--r--ui/base/gtk/gtk_signal.h117
-rw-r--r--ui/base/gtk/gtk_signal_registrar.cc78
-rw-r--r--ui/base/gtk/gtk_signal_registrar.h72
-rw-r--r--ui/base/gtk/scoped_handle_gtk.h55
-rw-r--r--ui/base/l10n/l10n_font_util.cc40
-rw-r--r--ui/base/l10n/l10n_font_util.h31
-rw-r--r--ui/base/message_box_flags.h62
-rw-r--r--ui/base/text/text_elider.cc665
-rw-r--r--ui/base/text/text_elider.h119
-rw-r--r--ui/base/text/text_elider_unittest.cc442
-rw-r--r--ui/base/theme_provider.cc16
-rw-r--r--ui/base/theme_provider.h120
-rw-r--r--ui/base/view_prop.cc103
-rw-r--r--ui/base/view_prop.h47
-rw-r--r--ui/base/view_prop_unittest.cc74
-rw-r--r--ui/base/x/active_window_watcher_x.cc106
-rw-r--r--ui/base/x/active_window_watcher_x.h60
-rw-r--r--ui/base/x/x11_util.cc880
-rw-r--r--ui/base/x/x11_util.h183
-rw-r--r--ui/base/x/x11_util_internal.h48
23 files changed, 3473 insertions, 0 deletions
diff --git a/ui/base/gtk/event_synthesis_gtk.cc b/ui/base/gtk/event_synthesis_gtk.cc
new file mode 100644
index 0000000..bd3cb8a
--- /dev/null
+++ b/ui/base/gtk/event_synthesis_gtk.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/gtk/event_synthesis_gtk.h"
+
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+
+namespace ui {
+
+GdkEvent* SynthesizeKeyEvent(GdkWindow* window,
+ bool press, guint gdk_key, guint state) {
+ GdkEvent* event = gdk_event_new(press ? GDK_KEY_PRESS : GDK_KEY_RELEASE);
+
+ event->key.type = press ? GDK_KEY_PRESS : GDK_KEY_RELEASE;
+ event->key.window = window;
+ if (window)
+ g_object_ref(window);
+ event->key.send_event = false;
+
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ event->key.time = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+
+ event->key.state = state;
+ event->key.keyval = gdk_key;
+
+ GdkKeymapKey* keys;
+ gint n_keys;
+ if (event->key.keyval != 0 &&
+ gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
+ event->key.keyval, &keys, &n_keys)) {
+ event->key.hardware_keycode = keys[0].keycode;
+ event->key.group = keys[0].group;
+ g_free(keys);
+ }
+
+ return event;
+}
+
+void SynthesizeKeyPressEvents(GdkWindow* window,
+ KeyboardCode key,
+ bool control, bool shift, bool alt,
+ std::vector<GdkEvent*>* events) {
+ if (control)
+ events->push_back(
+ SynthesizeKeyEvent(window, true, GDK_Control_L, 0));
+
+ if (shift) {
+ events->push_back(SynthesizeKeyEvent(window, true, GDK_Shift_L,
+ control ? GDK_CONTROL_MASK : 0));
+ }
+
+ if (alt) {
+ guint state = (control ? GDK_CONTROL_MASK : 0) |
+ (shift ? GDK_SHIFT_MASK : 0);
+ events->push_back(
+ SynthesizeKeyEvent(window, true, GDK_Alt_L, state));
+ }
+
+ // TODO(estade): handle other state flags besides control, shift, alt?
+ // For example caps lock.
+ guint state = (control ? GDK_CONTROL_MASK : 0) |
+ (shift ? GDK_SHIFT_MASK : 0) |
+ (alt ? GDK_MOD1_MASK : 0);
+
+ guint gdk_key = GdkKeyCodeForWindowsKeyCode(key, shift);
+ events->push_back(SynthesizeKeyEvent(window, true, gdk_key, state));
+ events->push_back(SynthesizeKeyEvent(window, false, gdk_key, state));
+
+ if (alt) {
+ guint state = (control ? GDK_CONTROL_MASK : 0) |
+ (shift ? GDK_SHIFT_MASK : 0) | GDK_MOD1_MASK;
+ events->push_back(
+ SynthesizeKeyEvent(window, false, GDK_Alt_L, state));
+ }
+
+ if (shift) {
+ events->push_back(
+ SynthesizeKeyEvent(window, false, GDK_Shift_L,
+ (control ? GDK_CONTROL_MASK : 0) | GDK_SHIFT_MASK));
+ }
+
+ if (control) {
+ events->push_back(
+ SynthesizeKeyEvent(window, false, GDK_Control_L, GDK_CONTROL_MASK));
+ }
+}
+
+} // namespace ui
diff --git a/ui/base/gtk/event_synthesis_gtk.h b/ui/base/gtk/event_synthesis_gtk.h
new file mode 100644
index 0000000..9ffdf40
--- /dev/null
+++ b/ui/base/gtk/event_synthesis_gtk.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file declares routines for creating fake GDK events (at the moment,
+// only keyboard events). This is useful for a variety of testing purposes.
+// NOTE: This should not be used outside of testing.
+
+#ifndef UI_BASE_GTK_EVENT_SYNTHESIS_GTK_
+#define UI_BASE_GTK_EVENT_SYNTHESIS_GTK_
+#pragma once
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <vector>
+
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace ui {
+
+// Creates and returns a key event. Passes ownership to the caller.
+GdkEvent* SynthesizeKeyEvent(GdkWindow* event_window,
+ bool press,
+ guint gdk_key,
+ guint state);
+
+// Creates the proper sequence of key events for a key press + release.
+// Ownership of the events in the vector is passed to the caller.
+void SynthesizeKeyPressEvents(
+ GdkWindow* window,
+ KeyboardCode key,
+ bool control, bool shift, bool alt,
+ std::vector<GdkEvent*>* events);
+
+} // namespace ui
+
+#endif // UI_BASE_GTK_EVENT_SYNTHESIS_GTK_
diff --git a/ui/base/gtk/gtk_integers.h b/ui/base/gtk/gtk_integers.h
new file mode 100644
index 0000000..41eeb93
--- /dev/null
+++ b/ui/base/gtk/gtk_integers.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_GTK_GTK_INTEGERS_H_
+#define UI_BASE_GTK_GTK_INTEGERS_H_
+#pragma once
+
+// GLib/Gobject/Gtk all use their own integer typedefs. They are copied here
+// for forward declaration reasons so we don't pull in all of gtk.h when we
+// just need a gpointer.
+typedef char gchar;
+typedef short gshort;
+typedef long glong;
+typedef int gint;
+typedef gint gboolean;
+typedef unsigned char guchar;
+typedef unsigned short gushort;
+typedef unsigned long gulong;
+typedef unsigned int guint;
+
+typedef unsigned short guint16;
+typedef unsigned int guint32;
+
+typedef void* gpointer;
+typedef const void *gconstpointer;
+
+#endif // UI_BASE_GTK_GTK_INTEGERS_H_
diff --git a/ui/base/gtk/gtk_signal.h b/ui/base/gtk/gtk_signal.h
new file mode 100644
index 0000000..64d59d6
--- /dev/null
+++ b/ui/base/gtk/gtk_signal.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_GTK_GTK_SIGNAL_H_
+#define UI_BASE_GTK_GTK_SIGNAL_H_
+#pragma once
+
+typedef void* gpointer;
+typedef struct _GtkWidget GtkWidget;
+
+// At the time of writing this, there were two common ways of binding our C++
+// code to the gobject C system. We either defined a whole bunch of "static
+// MethodThunk()" which just called nonstatic Method()s on a class (which hurt
+// readability of the headers and signal connection code) OR we declared
+// "static Method()" and passed in the current object as the gpointer (and hurt
+// readability in the implementation by having "context->" before every
+// variable).
+
+// The hopeful result of using these macros is that the code will be more
+// readable and regular. There shouldn't be a bunch of static Thunks visible in
+// the headers and the implementations shouldn't be filled with "context->"
+// de-references.
+
+#define CHROMEG_CALLBACK_0(CLASS, RETURN, METHOD, SENDER) \
+ static RETURN METHOD ## Thunk(SENDER sender, gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)->METHOD(sender); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER);
+
+#define CHROMEG_CALLBACK_1(CLASS, RETURN, METHOD, SENDER, ARG1) \
+ static RETURN METHOD ## Thunk(SENDER sender, ARG1 one, \
+ gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)->METHOD(sender, one); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER, ARG1);
+
+#define CHROMEG_CALLBACK_2(CLASS, RETURN, METHOD, SENDER, ARG1, ARG2) \
+ static RETURN METHOD ## Thunk(SENDER sender, ARG1 one, ARG2 two, \
+ gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)->METHOD(sender, one, two); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER, ARG1, ARG2);
+
+#define CHROMEG_CALLBACK_3(CLASS, RETURN, METHOD, SENDER, ARG1, ARG2, ARG3) \
+ static RETURN METHOD ## Thunk(SENDER sender, ARG1 one, ARG2 two, \
+ ARG3 three, gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)-> \
+ METHOD(sender, one, two, three); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER, ARG1, ARG2, ARG3);
+
+#define CHROMEG_CALLBACK_4(CLASS, RETURN, METHOD, SENDER, ARG1, ARG2, ARG3, \
+ ARG4) \
+ static RETURN METHOD ## Thunk(SENDER sender, ARG1 one, ARG2 two, \
+ ARG3 three, ARG4 four, \
+ gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)-> \
+ METHOD(sender, one, two, three, four); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER, ARG1, ARG2, ARG3, ARG4);
+
+#define CHROMEG_CALLBACK_5(CLASS, RETURN, METHOD, SENDER, ARG1, ARG2, ARG3, \
+ ARG4, ARG5) \
+ static RETURN METHOD ## Thunk(SENDER sender, ARG1 one, ARG2 two, \
+ ARG3 three, ARG4 four, ARG5 five, \
+ gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)-> \
+ METHOD(sender, one, two, three, four, five); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER, ARG1, ARG2, ARG3, ARG4, ARG5);
+
+#define CHROMEG_CALLBACK_6(CLASS, RETURN, METHOD, SENDER, ARG1, ARG2, ARG3, \
+ ARG4, ARG5, ARG6) \
+ static RETURN METHOD ## Thunk(SENDER sender, ARG1 one, ARG2 two, \
+ ARG3 three, ARG4 four, ARG5 five, \
+ ARG6 six, gpointer userdata) { \
+ return reinterpret_cast<CLASS*>(userdata)-> \
+ METHOD(sender, one, two, three, four, five, six); \
+ } \
+ \
+ virtual RETURN METHOD(SENDER, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6);
+
+// These macros handle the common case where the sender object will be a
+// GtkWidget*.
+#define CHROMEGTK_CALLBACK_0(CLASS, RETURN, METHOD) \
+ CHROMEG_CALLBACK_0(CLASS, RETURN, METHOD, GtkWidget*);
+
+#define CHROMEGTK_CALLBACK_1(CLASS, RETURN, METHOD, ARG1) \
+ CHROMEG_CALLBACK_1(CLASS, RETURN, METHOD, GtkWidget*, ARG1);
+
+#define CHROMEGTK_CALLBACK_2(CLASS, RETURN, METHOD, ARG1, ARG2) \
+ CHROMEG_CALLBACK_2(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2);
+
+#define CHROMEGTK_CALLBACK_3(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3) \
+ CHROMEG_CALLBACK_3(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3);
+
+#define CHROMEGTK_CALLBACK_4(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, ARG4) \
+ CHROMEG_CALLBACK_4(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3, ARG4);
+
+#define CHROMEGTK_CALLBACK_5(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, ARG4, \
+ ARG5) \
+ CHROMEG_CALLBACK_5(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3, \
+ ARG4, ARG5);
+
+#define CHROMEGTK_CALLBACK_6(CLASS, RETURN, METHOD, ARG1, ARG2, ARG3, ARG4, \
+ ARG5, ARG6) \
+ CHROMEG_CALLBACK_6(CLASS, RETURN, METHOD, GtkWidget*, ARG1, ARG2, ARG3, \
+ ARG4, ARG5, ARG6);
+
+#endif // UI_BASE_GTK_GTK_SIGNAL_H_
diff --git a/ui/base/gtk/gtk_signal_registrar.cc b/ui/base/gtk/gtk_signal_registrar.cc
new file mode 100644
index 0000000..c6b170f
--- /dev/null
+++ b/ui/base/gtk/gtk_signal_registrar.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/gtk/gtk_signal_registrar.h"
+
+#include <glib-object.h>
+
+#include "base/logging.h"
+
+namespace ui {
+
+GtkSignalRegistrar::GtkSignalRegistrar() {
+}
+
+GtkSignalRegistrar::~GtkSignalRegistrar() {
+ for (HandlerMap::iterator list_iter = handler_lists_.begin();
+ list_iter != handler_lists_.end(); ++list_iter) {
+ GObject* object = list_iter->first;
+ g_object_weak_unref(object, WeakNotifyThunk, this);
+
+ HandlerList& handlers = list_iter->second;
+ for (HandlerList::iterator ids_iter = handlers.begin();
+ ids_iter != handlers.end(); ids_iter++) {
+ g_signal_handler_disconnect(list_iter->first, *ids_iter);
+ }
+ }
+}
+
+glong GtkSignalRegistrar::Connect(gpointer instance,
+ const gchar* detailed_signal,
+ GCallback signal_handler,
+ gpointer data) {
+ return ConnectInternal(instance, detailed_signal, signal_handler, data,
+ false);
+}
+
+glong GtkSignalRegistrar::ConnectAfter(gpointer instance,
+ const gchar* detailed_signal,
+ GCallback signal_handler,
+ gpointer data) {
+ return ConnectInternal(instance, detailed_signal, signal_handler, data, true);
+}
+
+glong GtkSignalRegistrar::ConnectInternal(gpointer instance,
+ const gchar* detailed_signal,
+ GCallback signal_handler,
+ gpointer data,
+ bool after) {
+ GObject* object = G_OBJECT(instance);
+
+ HandlerMap::iterator iter = handler_lists_.find(object);
+ if (iter == handler_lists_.end()) {
+ g_object_weak_ref(object, WeakNotifyThunk, this);
+ handler_lists_[object] = HandlerList();
+ iter = handler_lists_.find(object);
+ }
+
+ glong handler_id = after ?
+ g_signal_connect_after(instance, detailed_signal, signal_handler, data) :
+ g_signal_connect(instance, detailed_signal, signal_handler, data);
+ iter->second.push_back(handler_id);
+
+ return handler_id;
+}
+
+void GtkSignalRegistrar::WeakNotify(GObject* where_the_object_was) {
+ HandlerMap::iterator iter = handler_lists_.find(where_the_object_was);
+ if (iter == handler_lists_.end()) {
+ NOTREACHED();
+ return;
+ }
+ // The signal handlers will be disconnected automatically. Just erase the
+ // handler id list.
+ handler_lists_.erase(iter);
+}
+
+} // namespace ui
diff --git a/ui/base/gtk/gtk_signal_registrar.h b/ui/base/gtk/gtk_signal_registrar.h
new file mode 100644
index 0000000..b7e4e2f
--- /dev/null
+++ b/ui/base/gtk/gtk_signal_registrar.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_GTK_GTK_SIGNAL_REGISTRAR_H_
+#define UI_BASE_GTK_GTK_SIGNAL_REGISTRAR_H_
+#pragma once
+
+#include <glib.h>
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+
+typedef void (*GCallback) (void);
+typedef struct _GObject GObject;
+typedef struct _GtkWidget GtkWidget;
+
+namespace ui {
+
+// A class that ensures that callbacks don't run on stale owner objects. Similar
+// in spirit to NotificationRegistrar. Use as follows:
+//
+// class ChromeObject {
+// public:
+// ChromeObject() {
+// ...
+//
+// signals_.Connect(widget, "event", CallbackThunk, this);
+// }
+//
+// ...
+//
+// private:
+// GtkSignalRegistrar signals_;
+// };
+//
+// When |signals_| goes down, it will disconnect the handlers connected via
+// Connect.
+class GtkSignalRegistrar {
+ public:
+ GtkSignalRegistrar();
+ ~GtkSignalRegistrar();
+
+ // Connect before the default handler. Returns the handler id.
+ glong Connect(gpointer instance, const gchar* detailed_signal,
+ GCallback signal_handler, gpointer data);
+ // Connect after the default handler. Returns the handler id.
+ glong ConnectAfter(gpointer instance, const gchar* detailed_signal,
+ GCallback signal_handler, gpointer data);
+
+ private:
+ typedef std::vector<glong> HandlerList;
+ typedef std::map<GObject*, HandlerList> HandlerMap;
+
+ static void WeakNotifyThunk(gpointer data, GObject* where_the_object_was) {
+ reinterpret_cast<GtkSignalRegistrar*>(data)->WeakNotify(
+ where_the_object_was);
+ }
+ void WeakNotify(GObject* where_the_object_was);
+
+ glong ConnectInternal(gpointer instance, const gchar* detailed_signal,
+ GCallback signal_handler, gpointer data, bool after);
+
+ HandlerMap handler_lists_;
+
+ DISALLOW_COPY_AND_ASSIGN(GtkSignalRegistrar);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GTK_GTK_SIGNAL_REGISTRAR_H_
diff --git a/ui/base/gtk/scoped_handle_gtk.h b/ui/base/gtk/scoped_handle_gtk.h
new file mode 100644
index 0000000..4ee4ec6
--- /dev/null
+++ b/ui/base/gtk/scoped_handle_gtk.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_GTK_SCOPED_HANDLE_GTK_H_
+#define UI_BASE_GTK_SCOPED_HANDLE_GTK_H_
+#pragma once
+
+#include <gdk/gdk.h>
+
+namespace ui {
+
+// Wraps a GdkRegion. This class provides the same methods as ScopedGDIObject in
+// scoped_handle_win.
+class ScopedRegion {
+ public:
+ ScopedRegion() : region_(NULL) {}
+ explicit ScopedRegion(GdkRegion* region) : region_(region) {}
+
+ ~ScopedRegion() {
+ Close();
+ }
+
+ void Set(GdkRegion* region) {
+ Close();
+
+ region_ = region;
+ }
+
+ GdkRegion* Get() {
+ return region_;
+ }
+
+ GdkRegion* release() {
+ GdkRegion* region = region_;
+ region_ = NULL;
+ return region;
+ }
+
+ private:
+ void Close() {
+ if (region_) {
+ gdk_region_destroy(region_);
+ region_ = NULL;
+ }
+ }
+
+ GdkRegion* region_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedRegion);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GTK_SCOPED_HANDLE_GTK_H_
diff --git a/ui/base/l10n/l10n_font_util.cc b/ui/base/l10n/l10n_font_util.cc
new file mode 100644
index 0000000..19f058f
--- /dev/null
+++ b/ui/base/l10n/l10n_font_util.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/l10n/l10n_font_util.h"
+
+#include "app/l10n_util.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "gfx/font.h"
+
+namespace ui {
+
+int GetLocalizedContentsWidthForFont(int col_resource_id,
+ const gfx::Font& font) {
+ double chars = 0;
+ base::StringToDouble(l10n_util::GetStringUTF8(col_resource_id), &chars);
+ int width = font.GetExpectedTextWidth(static_cast<int>(chars));
+ DCHECK_GT(width, 0);
+ return width;
+}
+
+int GetLocalizedContentsHeightForFont(int row_resource_id,
+ const gfx::Font& font) {
+ double lines = 0;
+ base::StringToDouble(l10n_util::GetStringUTF8(row_resource_id), &lines);
+ int height = static_cast<int>(font.GetHeight() * lines);
+ DCHECK_GT(height, 0);
+ return height;
+}
+
+gfx::Size GetLocalizedContentsSizeForFont(int col_resource_id,
+ int row_resource_id,
+ const gfx::Font& font) {
+ return gfx::Size(GetLocalizedContentsWidthForFont(col_resource_id, font),
+ GetLocalizedContentsHeightForFont(row_resource_id, font));
+}
+
+} // namespace ui
diff --git a/ui/base/l10n/l10n_font_util.h b/ui/base/l10n/l10n_font_util.h
new file mode 100644
index 0000000..eca17ee
--- /dev/null
+++ b/ui/base/l10n/l10n_font_util.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_L10N_FONT_UTIL_H_
+#define UI_BASE_L10N_FONT_UTIL_H_
+#pragma once
+
+#include "gfx/size.h"
+
+namespace gfx {
+class Font;
+}
+
+namespace ui {
+
+// Returns the preferred size of the contents view of a window based on
+// its localized size data and the given font. The width in cols is held in a
+// localized string resource identified by |col_resource_id|, the height in the
+// same fashion.
+int GetLocalizedContentsWidthForFont(int col_resource_id,
+ const gfx::Font& font);
+int GetLocalizedContentsHeightForFont(int row_resource_id,
+ const gfx::Font& font);
+gfx::Size GetLocalizedContentsSizeForFont(int col_resource_id,
+ int row_resource_id,
+ const gfx::Font& font);
+
+} // namespace ui
+
+#endif // UI_BASE_L10N_FONT_UTIL_H_
diff --git a/ui/base/message_box_flags.h b/ui/base/message_box_flags.h
new file mode 100644
index 0000000..d3cd569
--- /dev/null
+++ b/ui/base/message_box_flags.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_MESSAGE_BOX_FLAGS_H_
+#define UI_BASE_MESSAGE_BOX_FLAGS_H_
+#pragma once
+
+#include "base/basictypes.h"
+
+namespace ui {
+
+// This class contains flags used to communicate the type of message box
+// to show. E.g., the renderer can request the browser to show a
+// javascript alert or a javascript confirm message.
+class MessageBoxFlags {
+ public:
+ static const int kFlagHasOKButton = 0x1;
+ static const int kFlagHasCancelButton = 0x2;
+ static const int kFlagHasPromptField = 0x4;
+ static const int kFlagHasMessage = 0x8;
+
+ // The following flag is used to indicate whether the message's alignment
+ // should be autodetected or inherited from Chrome UI. Callers should pass
+ // the correct flag based on the origin of the message. If the message is
+ // from a web page (such as the JavaScript alert message), its alignment and
+ // directionality are based on the first character with strong directionality
+ // in the message. Chrome UI strings are localized string and therefore they
+ // should have the same alignment and directionality as those of the Chrome
+ // UI. For example, in RTL locales, even though some strings might begin with
+ // an English character, they should still be right aligned and be displayed
+ // Right-To-Left.
+ //
+ // TODO(xji): If the message is from a web page, then the message
+ // directionality should be determined based on the directionality of the web
+ // page. Please refer to http://crbug.com/7166 for more information.
+ static const int kAutoDetectAlignment = 0x10;
+
+ static const int kIsConfirmMessageBox = kFlagHasMessage |
+ kFlagHasOKButton |
+ kFlagHasCancelButton;
+ static const int kIsJavascriptAlert = kFlagHasOKButton | kFlagHasMessage;
+ static const int kIsJavascriptConfirm = kIsJavascriptAlert |
+ kFlagHasCancelButton;
+ static const int kIsJavascriptPrompt = kIsJavascriptConfirm |
+ kFlagHasPromptField;
+
+ // Dialog button identifiers used to specify which buttons to show the user.
+ enum DialogButton {
+ DIALOGBUTTON_NONE = 0, // No dialog buttons, for WindowType == WINDOW.
+ DIALOGBUTTON_OK = 1, // Has an OK button.
+ DIALOGBUTTON_CANCEL = 2, // Has a Cancel button (becomes a Close button if
+ }; // no OK button).
+
+ private:
+ MessageBoxFlags() {}
+ DISALLOW_COPY_AND_ASSIGN(MessageBoxFlags);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_MESSAGE_BOX_FLAGS_H_
diff --git a/ui/base/text/text_elider.cc b/ui/base/text/text_elider.cc
new file mode 100644
index 0000000..8a262fd
--- /dev/null
+++ b/ui/base/text/text_elider.cc
@@ -0,0 +1,665 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "ui/base/text/text_elider.h"
+
+#include "base/file_path.h"
+#include "base/i18n/break_iterator.h"
+#include "base/i18n/char_iterator.h"
+#include "base/i18n/rtl.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "gfx/font.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/escape.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domain.h"
+
+namespace ui {
+
+namespace {
+
+const char* kEllipsis = "\xE2\x80\xA6";
+
+// Cuts |text| to be |length| characters long. If |cut_in_middle| is true, the
+// middle of the string is removed to leave equal-length pieces from the
+// beginning and end of the string; otherwise, the end of the string is removed
+// and only the beginning remains. If |insert_ellipsis| is true, then an
+// ellipsis character will by inserted at the cut point.
+string16 CutString(const string16& text,
+ size_t length,
+ bool cut_in_middle,
+ bool insert_ellipsis) {
+ // TODO(tony): This is wrong, it might split the string in the middle of a
+ // surrogate pair.
+ const string16 kInsert = insert_ellipsis ? UTF8ToUTF16(kEllipsis) :
+ ASCIIToUTF16("");
+ if (!cut_in_middle)
+ return text.substr(0, length) + kInsert;
+ // We put the extra character, if any, before the cut.
+ const size_t half_length = length / 2;
+ return text.substr(0, length - half_length) + kInsert +
+ text.substr(text.length() - half_length, half_length);
+}
+
+} // namespace
+
+// This function takes a GURL object and elides it. It returns a string
+// which composed of parts from subdomain, domain, path, filename and query.
+// A "..." is added automatically at the end if the elided string is bigger
+// than the available pixel width. For available pixel width = 0, a formatted,
+// but un-elided, string is returned.
+//
+// TODO(pkasting): http://b/119635 This whole function gets
+// kerning/ligatures/etc. issues potentially wrong by assuming that the width of
+// a rendered string is always the sum of the widths of its substrings. Also I
+// suspect it could be made simpler.
+string16 ElideUrl(const GURL& url,
+ const gfx::Font& font,
+ int available_pixel_width,
+ const std::wstring& languages) {
+ // Get a formatted string and corresponding parsing of the url.
+ url_parse::Parsed parsed;
+ string16 url_string = net::FormatUrl(url, WideToUTF8(languages),
+ net::kFormatUrlOmitAll, UnescapeRule::SPACES, &parsed, NULL, NULL);
+ if (available_pixel_width <= 0)
+ return url_string;
+
+ // If non-standard or not file type, return plain eliding.
+ if (!(url.SchemeIsFile() || url.IsStandard()))
+ return ElideText(url_string, font, available_pixel_width, false);
+
+ // Now start eliding url_string to fit within available pixel width.
+ // Fist pass - check to see whether entire url_string fits.
+ int pixel_width_url_string = font.GetStringWidth(url_string);
+ if (available_pixel_width >= pixel_width_url_string)
+ return url_string;
+
+ // Get the path substring, including query and reference.
+ size_t path_start_index = parsed.path.begin;
+ size_t path_len = parsed.path.len;
+ string16 url_path_query_etc = url_string.substr(path_start_index);
+ string16 url_path = url_string.substr(path_start_index, path_len);
+
+ // Return general elided text if url minus the query fits.
+ string16 url_minus_query = url_string.substr(0, path_start_index + path_len);
+ if (available_pixel_width >= font.GetStringWidth(url_minus_query))
+ return ElideText(url_string, font, available_pixel_width, false);
+
+ // Get Host.
+ string16 url_host = UTF8ToUTF16(url.host());
+
+ // Get domain and registry information from the URL.
+ string16 url_domain = UTF8ToUTF16(
+ net::RegistryControlledDomainService::GetDomainAndRegistry(url));
+ if (url_domain.empty())
+ url_domain = url_host;
+
+ // Add port if required.
+ if (!url.port().empty()) {
+ url_host += UTF8ToUTF16(":" + url.port());
+ url_domain += UTF8ToUTF16(":" + url.port());
+ }
+
+ // Get sub domain.
+ string16 url_subdomain;
+ size_t domain_start_index = url_host.find(url_domain);
+ if (domain_start_index > 0)
+ url_subdomain = url_host.substr(0, domain_start_index);
+ static const string16 kWwwPrefix = UTF8ToUTF16("www.");
+ if ((url_subdomain == kWwwPrefix || url_subdomain.empty() ||
+ url.SchemeIsFile())) {
+ url_subdomain.clear();
+ }
+
+ // If this is a file type, the path is now defined as everything after ":".
+ // For example, "C:/aa/aa/bb", the path is "/aa/bb/cc". Interesting, the
+ // domain is now C: - this is a nice hack for eliding to work pleasantly.
+ if (url.SchemeIsFile()) {
+ // Split the path string using ":"
+ std::vector<string16> file_path_split;
+ base::SplitString(url_path, ':', &file_path_split);
+ if (file_path_split.size() > 1) { // File is of type "file:///C:/.."
+ url_host.clear();
+ url_domain.clear();
+ url_subdomain.clear();
+
+ static const string16 kColon = UTF8ToUTF16(":");
+ url_host = url_domain = file_path_split.at(0).substr(1) + kColon;
+ url_path_query_etc = url_path = file_path_split.at(1);
+ }
+ }
+
+ // Second Pass - remove scheme - the rest fits.
+ int pixel_width_url_host = font.GetStringWidth(url_host);
+ int pixel_width_url_path = font.GetStringWidth(url_path_query_etc);
+ if (available_pixel_width >=
+ pixel_width_url_host + pixel_width_url_path)
+ return url_host + url_path_query_etc;
+
+ // Third Pass: Subdomain, domain and entire path fits.
+ int pixel_width_url_domain = font.GetStringWidth(url_domain);
+ int pixel_width_url_subdomain = font.GetStringWidth(url_subdomain);
+ if (available_pixel_width >=
+ pixel_width_url_subdomain + pixel_width_url_domain +
+ pixel_width_url_path)
+ return url_subdomain + url_domain + url_path_query_etc;
+
+ // Query element.
+ string16 url_query;
+ const int kPixelWidthDotsTrailer =
+ font.GetStringWidth(UTF8ToUTF16(kEllipsis));
+ if (parsed.query.is_nonempty()) {
+ url_query = UTF8ToUTF16("?") + url_string.substr(parsed.query.begin);
+ if (available_pixel_width >= (pixel_width_url_subdomain +
+ pixel_width_url_domain + pixel_width_url_path -
+ font.GetStringWidth(url_query))) {
+ return ElideText(url_subdomain + url_domain + url_path_query_etc,
+ font, available_pixel_width, false);
+ }
+ }
+
+ // Parse url_path using '/'.
+ static const string16 kForwardSlash = UTF8ToUTF16("/");
+ std::vector<string16> url_path_elements;
+ base::SplitString(url_path, kForwardSlash[0], &url_path_elements);
+
+ // Get filename - note that for a path ending with /
+ // such as www.google.com/intl/ads/, the file name is ads/.
+ size_t url_path_number_of_elements = url_path_elements.size();
+ DCHECK(url_path_number_of_elements != 0);
+ string16 url_filename;
+ if ((url_path_elements.at(url_path_number_of_elements - 1)).length() > 0) {
+ url_filename = *(url_path_elements.end() - 1);
+ } else if (url_path_number_of_elements > 1) { // Path ends with a '/'.
+ url_filename = url_path_elements.at(url_path_number_of_elements - 2) +
+ kForwardSlash;
+ url_path_number_of_elements--;
+ }
+ DCHECK(url_path_number_of_elements != 0);
+
+ const size_t kMaxNumberOfUrlPathElementsAllowed = 1024;
+ if (url_path_number_of_elements <= 1 ||
+ url_path_number_of_elements > kMaxNumberOfUrlPathElementsAllowed) {
+ // No path to elide, or too long of a path (could overflow in loop below)
+ // Just elide this as a text string.
+ return ElideText(url_subdomain + url_domain + url_path_query_etc, font,
+ available_pixel_width, false);
+ }
+
+ // Start eliding the path and replacing elements by "../".
+ const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash;
+ int pixel_width_url_filename = font.GetStringWidth(url_filename);
+ int pixel_width_dot_dot_slash = font.GetStringWidth(kEllipsisAndSlash);
+ int pixel_width_slash = font.GetStringWidth(ASCIIToUTF16("/"));
+ int pixel_width_url_path_elements[kMaxNumberOfUrlPathElementsAllowed];
+ for (size_t i = 0; i < url_path_number_of_elements; ++i) {
+ pixel_width_url_path_elements[i] =
+ font.GetStringWidth(url_path_elements.at(i));
+ }
+
+ // Check with both subdomain and domain.
+ string16 elided_path;
+ int pixel_width_elided_path;
+ for (size_t i = url_path_number_of_elements - 1; i >= 1; --i) {
+ // Add the initial elements of the path.
+ elided_path.clear();
+ pixel_width_elided_path = 0;
+ for (size_t j = 0; j < i; ++j) {
+ elided_path += url_path_elements.at(j) + kForwardSlash;
+ pixel_width_elided_path += pixel_width_url_path_elements[j] +
+ pixel_width_slash;
+ }
+
+ // Add url_file_name.
+ if (i == (url_path_number_of_elements - 1)) {
+ elided_path += url_filename;
+ pixel_width_elided_path += pixel_width_url_filename;
+ } else {
+ elided_path += kEllipsisAndSlash + url_filename;
+ pixel_width_elided_path += pixel_width_dot_dot_slash +
+ pixel_width_url_filename;
+ }
+
+ if (available_pixel_width >=
+ pixel_width_url_subdomain + pixel_width_url_domain +
+ pixel_width_elided_path) {
+ return ElideText(url_subdomain + url_domain + elided_path + url_query,
+ font, available_pixel_width, false);
+ }
+ }
+
+ // Check with only domain.
+ // If a subdomain is present, add an ellipsis before domain.
+ // This is added only if the subdomain pixel width is larger than
+ // the pixel width of kEllipsis. Otherwise, subdomain remains,
+ // which means that this case has been resolved earlier.
+ string16 url_elided_domain = url_subdomain + url_domain;
+ int pixel_width_url_elided_domain = pixel_width_url_domain;
+ if (pixel_width_url_subdomain > kPixelWidthDotsTrailer) {
+ if (!url_subdomain.empty()) {
+ url_elided_domain = kEllipsisAndSlash[0] + url_domain;
+ pixel_width_url_elided_domain += kPixelWidthDotsTrailer;
+ } else {
+ url_elided_domain = url_domain;
+ }
+
+ for (size_t i = url_path_number_of_elements - 1; i >= 1; --i) {
+ // Add the initial elements of the path.
+ elided_path.clear();
+ pixel_width_elided_path = 0;
+ for (size_t j = 0; j < i; ++j) {
+ elided_path += url_path_elements.at(j) + kForwardSlash;
+ pixel_width_elided_path += pixel_width_url_path_elements[j] +
+ pixel_width_slash;
+ }
+
+ // Add url_file_name.
+ if (i == (url_path_number_of_elements - 1)) {
+ elided_path += url_filename;
+ pixel_width_elided_path += pixel_width_url_filename;
+ } else {
+ elided_path += kEllipsisAndSlash + url_filename;
+ pixel_width_elided_path += pixel_width_dot_dot_slash +
+ pixel_width_url_filename;
+ }
+
+ if (available_pixel_width >=
+ pixel_width_url_elided_domain + pixel_width_elided_path) {
+ return ElideText(url_elided_domain + elided_path + url_query, font,
+ available_pixel_width, false);
+ }
+ }
+ }
+
+ // Return elided domain/../filename anyway.
+ string16 final_elided_url_string(url_elided_domain);
+ int url_elided_domain_width = font.GetStringWidth(url_elided_domain);
+
+ // A hack to prevent trailing "../...".
+ if ((available_pixel_width - url_elided_domain_width) >
+ pixel_width_dot_dot_slash + kPixelWidthDotsTrailer +
+ font.GetStringWidth(ASCIIToUTF16("UV"))) {
+ final_elided_url_string += elided_path;
+ } else {
+ final_elided_url_string += url_path;
+ }
+
+ return ElideText(final_elided_url_string, font, available_pixel_width, false);
+}
+
+string16 ElideFilename(const FilePath& filename,
+ const gfx::Font& font,
+ int available_pixel_width) {
+#if defined(OS_WIN)
+ string16 filename_utf16 = filename.value();
+ string16 extension = filename.Extension();
+ string16 rootname = filename.BaseName().RemoveExtension().value();
+#elif defined(OS_POSIX)
+ string16 filename_utf16 = WideToUTF16(base::SysNativeMBToWide(
+ filename.value()));
+ string16 extension = WideToUTF16(base::SysNativeMBToWide(
+ filename.Extension()));
+ string16 rootname = WideToUTF16(base::SysNativeMBToWide(
+ filename.BaseName().RemoveExtension().value()));
+#endif
+
+ int full_width = font.GetStringWidth(filename_utf16);
+ if (full_width <= available_pixel_width)
+ return base::i18n::GetDisplayStringInLTRDirectionality(filename_utf16);
+
+ if (rootname.empty() || extension.empty()) {
+ string16 elided_name = ElideText(filename_utf16, font,
+ available_pixel_width, false);
+ return base::i18n::GetDisplayStringInLTRDirectionality(elided_name);
+ }
+
+ int ext_width = font.GetStringWidth(extension);
+ int root_width = font.GetStringWidth(rootname);
+
+ // We may have trimmed the path.
+ if (root_width + ext_width <= available_pixel_width) {
+ string16 elided_name = rootname + extension;
+ return base::i18n::GetDisplayStringInLTRDirectionality(elided_name);
+ }
+
+ int available_root_width = available_pixel_width - ext_width;
+ string16 elided_name =
+ ElideText(rootname, font, available_root_width, false);
+ elided_name += extension;
+ return base::i18n::GetDisplayStringInLTRDirectionality(elided_name);
+}
+
+// This function adds an ellipsis at the end of the text if the text
+// does not fit the given pixel width.
+string16 ElideText(const string16& text,
+ const gfx::Font& font,
+ int available_pixel_width,
+ bool elide_in_middle) {
+ if (text.empty())
+ return text;
+
+ int current_text_pixel_width = font.GetStringWidth(text);
+
+ // Pango will return 0 width for absurdly long strings. Cut the string in
+ // half and try again.
+ // This is caused by an int overflow in Pango (specifically, in
+ // pango_glyph_string_extents_range). It's actually more subtle than just
+ // returning 0, since on super absurdly long strings, the int can wrap and
+ // return positive numbers again. Detecting that is probably not worth it
+ // (eliding way too much from a ridiculous string is probably still
+ // ridiculous), but we should check other widths for bogus values as well.
+ if (current_text_pixel_width <= 0 && !text.empty()) {
+ return ElideText(CutString(text, text.length() / 2, elide_in_middle, false),
+ font, available_pixel_width, false);
+ }
+
+ if (current_text_pixel_width <= available_pixel_width)
+ return text;
+
+ if (font.GetStringWidth(UTF8ToUTF16(kEllipsis)) > available_pixel_width)
+ return string16();
+
+ // Use binary search to compute the elided text.
+ size_t lo = 0;
+ size_t hi = text.length() - 1;
+ for (size_t guess = (lo + hi) / 2; guess != lo; guess = (lo + hi) / 2) {
+ // We check the length of the whole desired string at once to ensure we
+ // handle kerning/ligatures/etc. correctly.
+ int guess_length = font.GetStringWidth(
+ CutString(text, guess, elide_in_middle, true));
+ // Check again that we didn't hit a Pango width overflow. If so, cut the
+ // current string in half and start over.
+ if (guess_length <= 0) {
+ return ElideText(CutString(text, guess / 2, elide_in_middle, false),
+ font, available_pixel_width, elide_in_middle);
+ }
+ if (guess_length > available_pixel_width)
+ hi = guess;
+ else
+ lo = guess;
+ }
+
+ return CutString(text, lo, elide_in_middle, true);
+}
+
+// TODO(viettrungluu): convert |languages| to an |std::string|.
+SortedDisplayURL::SortedDisplayURL(const GURL& url,
+ const std::wstring& languages) {
+ std::wstring host;
+ net::AppendFormattedHost(url, languages, &host, NULL, NULL);
+ sort_host_ = WideToUTF16Hack(host);
+ string16 host_minus_www = net::StripWWW(WideToUTF16Hack(host));
+ url_parse::Parsed parsed;
+ display_url_ = net::FormatUrl(url, WideToUTF8(languages),
+ net::kFormatUrlOmitAll, UnescapeRule::SPACES, &parsed, &prefix_end_,
+ NULL);
+ if (sort_host_.length() > host_minus_www.length()) {
+ prefix_end_ += sort_host_.length() - host_minus_www.length();
+ sort_host_.swap(host_minus_www);
+ }
+}
+
+SortedDisplayURL::SortedDisplayURL() {
+}
+
+SortedDisplayURL::~SortedDisplayURL() {
+}
+
+int SortedDisplayURL::Compare(const SortedDisplayURL& other,
+ icu::Collator* collator) const {
+ // Compare on hosts first. The host won't contain 'www.'.
+ UErrorCode compare_status = U_ZERO_ERROR;
+ UCollationResult host_compare_result = collator->compare(
+ static_cast<const UChar*>(sort_host_.c_str()),
+ static_cast<int>(sort_host_.length()),
+ static_cast<const UChar*>(other.sort_host_.c_str()),
+ static_cast<int>(other.sort_host_.length()),
+ compare_status);
+ DCHECK(U_SUCCESS(compare_status));
+ if (host_compare_result != 0)
+ return host_compare_result;
+
+ // Hosts match, compare on the portion of the url after the host.
+ string16 path = this->AfterHost();
+ string16 o_path = other.AfterHost();
+ compare_status = U_ZERO_ERROR;
+ UCollationResult path_compare_result = collator->compare(
+ static_cast<const UChar*>(path.c_str()),
+ static_cast<int>(path.length()),
+ static_cast<const UChar*>(o_path.c_str()),
+ static_cast<int>(o_path.length()),
+ compare_status);
+ DCHECK(U_SUCCESS(compare_status));
+ if (path_compare_result != 0)
+ return path_compare_result;
+
+ // Hosts and paths match, compare on the complete url. This'll push the www.
+ // ones to the end.
+ compare_status = U_ZERO_ERROR;
+ UCollationResult display_url_compare_result = collator->compare(
+ static_cast<const UChar*>(display_url_.c_str()),
+ static_cast<int>(display_url_.length()),
+ static_cast<const UChar*>(other.display_url_.c_str()),
+ static_cast<int>(other.display_url_.length()),
+ compare_status);
+ DCHECK(U_SUCCESS(compare_status));
+ return display_url_compare_result;
+}
+
+string16 SortedDisplayURL::AfterHost() const {
+ size_t slash_index = display_url_.find(sort_host_, prefix_end_);
+ if (slash_index == string16::npos) {
+ NOTREACHED();
+ return string16();
+ }
+ return display_url_.substr(slash_index + sort_host_.length());
+}
+
+bool ElideString(const std::wstring& input, int max_len, std::wstring* output) {
+ DCHECK_GE(max_len, 0);
+ if (static_cast<int>(input.length()) <= max_len) {
+ output->assign(input);
+ return false;
+ }
+
+ switch (max_len) {
+ case 0:
+ output->clear();
+ break;
+ case 1:
+ output->assign(input.substr(0, 1));
+ break;
+ case 2:
+ output->assign(input.substr(0, 2));
+ break;
+ case 3:
+ output->assign(input.substr(0, 1) + L"." +
+ input.substr(input.length() - 1));
+ break;
+ case 4:
+ output->assign(input.substr(0, 1) + L".." +
+ input.substr(input.length() - 1));
+ break;
+ default: {
+ int rstr_len = (max_len - 3) / 2;
+ int lstr_len = rstr_len + ((max_len - 3) % 2);
+ output->assign(input.substr(0, lstr_len) + L"..." +
+ input.substr(input.length() - rstr_len));
+ break;
+ }
+ }
+
+ return true;
+}
+
+} // namespace ui
+
+namespace {
+
+// Internal class used to track progress of a rectangular string elide
+// operation. Exists so the top-level ElideRectangleString() function
+// can be broken into smaller methods sharing this state.
+class RectangleString {
+ public:
+ RectangleString(size_t max_rows, size_t max_cols, string16 *output)
+ : max_rows_(max_rows),
+ max_cols_(max_cols),
+ current_row_(0),
+ current_col_(0),
+ suppressed_(false),
+ output_(output) {}
+
+ // Perform deferred initializions following creation. Must be called
+ // before any input can be added via AddString().
+ void Init() { output_->clear(); }
+
+ // Add an input string, reformatting to fit the desired dimensions.
+ // AddString() may be called multiple times to concatenate together
+ // multiple strings into the region (the current caller doesn't do
+ // this, however).
+ void AddString(const string16& input);
+
+ // Perform any deferred output processing. Must be called after the
+ // last AddString() call has occured.
+ bool Finalize();
+
+ private:
+ // Add a line to the rectangular region at the current position,
+ // either by itself or by breaking it into words.
+ void AddLine(const string16& line);
+
+ // Add a word to the rectangluar region at the current position,
+ // either by itelf or by breaking it into characters.
+ void AddWord(const string16& word);
+
+ // Add text to the output string if the rectangular boundaries
+ // have not been exceeded, advancing the current position.
+ void Append(const string16& string);
+
+ // Add a newline to the output string if the rectangular boundaries
+ // have not been exceeded, resetting the current position to the
+ // beginning of the next line.
+ void NewLine();
+
+ // Maximum number of rows allowed in the output string.
+ size_t max_rows_;
+
+ // Maximum number of characters allowed in the output string.
+ size_t max_cols_;
+
+ // Current row position, always incremented and may exceed max_rows_
+ // when the input can not fit in the region. We stop appending to
+ // the output string, however, when this condition occurs. In the
+ // future, we may want to expose this value to allow the caller to
+ // determine how many rows would actually be required to hold the
+ // formatted string.
+ size_t current_row_;
+
+ // Current character position, should never exceed max_cols_.
+ size_t current_col_;
+
+ // True when some of the input has been truncated.
+ bool suppressed_;
+
+ // String onto which the output is accumulated.
+ string16 *output_;
+};
+
+void RectangleString::AddString(const string16& input) {
+ base::BreakIterator lines(&input, base::BreakIterator::BREAK_NEWLINE);
+ if (lines.Init()) {
+ while (lines.Advance())
+ AddLine(lines.GetString());
+ } else {
+ NOTREACHED() << "BreakIterator (lines) init failed";
+ }
+}
+
+bool RectangleString::Finalize() {
+ if (suppressed_) {
+ output_->append(ASCIIToUTF16("..."));
+ return true;
+ }
+ return false;
+}
+
+void RectangleString::AddLine(const string16& line) {
+ if (line.length() < max_cols_) {
+ Append(line);
+ } else {
+ base::BreakIterator words(&line, base::BreakIterator::BREAK_SPACE);
+ if (words.Init()) {
+ while (words.Advance())
+ AddWord(words.GetString());
+ } else {
+ NOTREACHED() << "BreakIterator (words) init failed";
+ }
+ }
+ // Account for naturally-occuring newlines.
+ ++current_row_;
+ current_col_ = 0;
+}
+
+void RectangleString::AddWord(const string16& word) {
+ if (word.length() < max_cols_) {
+ // Word can be made to fit, no need to fragment it.
+ if (current_col_ + word.length() >= max_cols_)
+ NewLine();
+ Append(word);
+ } else {
+ // Word is so big that it must be fragmented.
+ int array_start = 0;
+ int char_start = 0;
+ base::i18n::UTF16CharIterator chars(&word);
+ while (!chars.end()) {
+ // When boundary is hit, add as much as will fit on this line.
+ if (current_col_ + (chars.char_pos() - char_start) >= max_cols_) {
+ Append(word.substr(array_start, chars.array_pos() - array_start));
+ NewLine();
+ array_start = chars.array_pos();
+ char_start = chars.char_pos();
+ }
+ chars.Advance();
+ }
+ // add last remaining fragment, if any.
+ if (array_start != chars.array_pos())
+ Append(word.substr(array_start, chars.array_pos() - array_start));
+ }
+}
+
+void RectangleString::Append(const string16& string) {
+ if (current_row_ < max_rows_)
+ output_->append(string);
+ else
+ suppressed_ = true;
+ current_col_ += string.length();
+}
+
+void RectangleString::NewLine() {
+ if (current_row_ < max_rows_)
+ output_->append(ASCIIToUTF16("\n"));
+ else
+ suppressed_ = true;
+ ++current_row_;
+ current_col_ = 0;
+}
+
+} // namespace
+
+namespace ui {
+
+bool ElideRectangleString(const string16& input, size_t max_rows,
+ size_t max_cols, string16* output) {
+ RectangleString rect(max_rows, max_cols, output);
+ rect.Init();
+ rect.AddString(input);
+ return rect.Finalize();
+}
+
+} // namespace ui
diff --git a/ui/base/text/text_elider.h b/ui/base/text/text_elider.h
new file mode 100644
index 0000000..97b9542
--- /dev/null
+++ b/ui/base/text/text_elider.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_TEXT_TEXT_ELIDER_H_
+#define UI_BASE_TEXT_TEXT_ELIDER_H_
+#pragma once
+
+#include <unicode/coll.h>
+#include <unicode/uchar.h>
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+#include "gfx/font.h"
+
+class FilePath;
+class GURL;
+
+// TODO(port): this file should deal in string16s rather than wstrings.
+namespace ui {
+
+// This function takes a GURL object and elides it. It returns a string
+// which composed of parts from subdomain, domain, path, filename and query.
+// A "..." is added automatically at the end if the elided string is bigger
+// than the available pixel width. For available pixel width = 0, empty
+// string is returned. |languages| is a comma separted list of ISO 639
+// language codes and is used to determine what characters are understood
+// by a user. It should come from |prefs::kAcceptLanguages|.
+//
+// Note: in RTL locales, if the URL returned by this function is going to be
+// displayed in the UI, then it is likely that the string needs to be marked
+// as an LTR string (using base::i18n::WrapStringWithLTRFormatting()) so that it
+// is displayed properly in an RTL context. Please refer to
+// http://crbug.com/6487 for more information.
+string16 ElideUrl(const GURL& url,
+ const gfx::Font& font,
+ int available_pixel_width,
+ const std::wstring& languages);
+
+// Elides |text| to fit in |available_pixel_width|. If |elide_in_middle| is
+// set the ellipsis is placed in the middle of the string; otherwise it is
+// placed at the end.
+string16 ElideText(const string16& text,
+ const gfx::Font& font,
+ int available_pixel_width,
+ bool elide_in_middle);
+
+// Elide a filename to fit a given pixel width, with an emphasis on not hiding
+// the extension unless we have to. If filename contains a path, the path will
+// be removed if filename doesn't fit into available_pixel_width. The elided
+// filename is forced to have LTR directionality, which means that in RTL UI
+// the elided filename is wrapped with LRE (Left-To-Right Embedding) mark and
+// PDF (Pop Directional Formatting) mark.
+string16 ElideFilename(const FilePath& filename,
+ const gfx::Font& font,
+ int available_pixel_width);
+
+// SortedDisplayURL maintains a string from a URL suitable for display to the
+// use. SortedDisplayURL also provides a function used for comparing two
+// SortedDisplayURLs for use in visually ordering the SortedDisplayURLs.
+//
+// SortedDisplayURL is relatively cheap and supports value semantics.
+class SortedDisplayURL {
+ public:
+ SortedDisplayURL(const GURL& url, const std::wstring& languages);
+ SortedDisplayURL();
+ ~SortedDisplayURL();
+
+ // Compares this SortedDisplayURL to |url| using |collator|. Returns a value
+ // < 0, = 1 or > 0 as to whether this url is less then, equal to or greater
+ // than the supplied url.
+ int Compare(const SortedDisplayURL& other, icu::Collator* collator) const;
+
+ // Returns the display string for the URL.
+ const string16& display_url() const { return display_url_; }
+
+ private:
+ // Returns everything after the host. This is used by Compare if the hosts
+ // match.
+ string16 AfterHost() const;
+
+ // Host name minus 'www.'. Used by Compare.
+ string16 sort_host_;
+
+ // End of the prefix (spec and separator) in display_url_.
+ size_t prefix_end_;
+
+ string16 display_url_;
+};
+
+// Functions to elide strings when the font information is unknown. As
+// opposed to the above functions, the ElideString() and
+// ElideRectangleString() functions operate in terms of character units,
+// not pixels.
+
+// If the size of |input| is more than |max_len|, this function returns
+// true and |input| is shortened into |output| by removing chars in the
+// middle (they are replaced with up to 3 dots, as size permits).
+// Ex: ElideString(L"Hello", 10, &str) puts Hello in str and returns false.
+// ElideString(L"Hello my name is Tom", 10, &str) puts "Hell...Tom" in str
+// and returns true.
+// TODO(tsepez): Doesn't handle UTF-16 surrogate pairs properly.
+// TODO(tsepez): Doesn't handle bidi properly
+bool ElideString(const std::wstring& input, int max_len, std::wstring* output);
+
+// Reformat |input| into |output| so that it fits into a |max_rows| by
+// |max_cols| rectangle of characters. Input newlines are respected, but
+// lines that are too long are broken into pieces, first at naturally
+// occuring whitespace boundaries, and then intra-word (respecting UTF-16
+// surrogate pairs) as necssary. Truncation (indicated by an added 3 dots)
+// occurs if the result is still too long. Returns true if the input had
+// to be truncated (and not just reformatted).
+bool ElideRectangleString(const string16& input, size_t max_rows,
+ size_t max_cols, string16* output);
+
+
+} // namespace ui
+
+#endif // UI_BASE_TEXT_TEXT_ELIDER_H_
diff --git a/ui/base/text/text_elider_unittest.cc b/ui/base/text/text_elider_unittest.cc
new file mode 100644
index 0000000..0889d27
--- /dev/null
+++ b/ui/base/text/text_elider_unittest.cc
@@ -0,0 +1,442 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_path.h"
+#include "base/i18n/rtl.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "gfx/font.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/text/text_elider.h"
+
+namespace ui {
+
+namespace {
+
+const wchar_t kEllipsis[] = L"\x2026";
+
+struct Testcase {
+ const std::string input;
+ const std::wstring output;
+};
+
+struct FileTestcase {
+ const FilePath::StringType input;
+ const std::wstring output;
+};
+
+struct UTF16Testcase {
+ const string16 input;
+ const string16 output;
+};
+
+struct TestData {
+ const std::string a;
+ const std::string b;
+ const int compare_result;
+};
+
+void RunTest(Testcase* testcases, size_t num_testcases) {
+ static const gfx::Font font;
+ for (size_t i = 0; i < num_testcases; ++i) {
+ const GURL url(testcases[i].input);
+ // Should we test with non-empty language list?
+ // That's kinda redundant with net_util_unittests.
+ EXPECT_EQ(WideToUTF16(testcases[i].output),
+ ElideUrl(url, font,
+ font.GetStringWidth(WideToUTF16(testcases[i].output)),
+ std::wstring()));
+ }
+}
+
+} // namespace
+
+// Test eliding of commonplace URLs.
+TEST(TextEliderTest, TestGeneralEliding) {
+ const std::wstring kEllipsisStr(kEllipsis);
+ Testcase testcases[] = {
+ {"http://www.google.com/intl/en/ads/",
+ L"www.google.com/intl/en/ads/"},
+ {"http://www.google.com/intl/en/ads/", L"www.google.com/intl/en/ads/"},
+// TODO(port): make this test case work on mac.
+#if !defined(OS_MACOSX)
+ {"http://www.google.com/intl/en/ads/",
+ L"google.com/intl/" + kEllipsisStr + L"/ads/"},
+#endif
+ {"http://www.google.com/intl/en/ads/",
+ L"google.com/" + kEllipsisStr + L"/ads/"},
+ {"http://www.google.com/intl/en/ads/", L"google.com/" + kEllipsisStr},
+ {"http://www.google.com/intl/en/ads/", L"goog" + kEllipsisStr},
+ {"https://subdomain.foo.com/bar/filename.html",
+ L"subdomain.foo.com/bar/filename.html"},
+ {"https://subdomain.foo.com/bar/filename.html",
+ L"subdomain.foo.com/" + kEllipsisStr + L"/filename.html"},
+ {"http://subdomain.foo.com/bar/filename.html",
+ kEllipsisStr + L"foo.com/" + kEllipsisStr + L"/filename.html"},
+ {"http://www.google.com/intl/en/ads/?aLongQueryWhichIsNotRequired",
+ L"www.google.com/intl/en/ads/?aLongQ" + kEllipsisStr},
+ };
+
+ RunTest(testcases, arraysize(testcases));
+}
+
+// Test eliding of empty strings, URLs with ports, passwords, queries, etc.
+TEST(TextEliderTest, TestMoreEliding) {
+ const std::wstring kEllipsisStr(kEllipsis);
+ Testcase testcases[] = {
+ {"http://www.google.com/foo?bar", L"www.google.com/foo?bar"},
+ {"http://xyz.google.com/foo?bar", L"xyz.google.com/foo?" + kEllipsisStr},
+ {"http://xyz.google.com/foo?bar", L"xyz.google.com/foo" + kEllipsisStr},
+ {"http://xyz.google.com/foo?bar", L"xyz.google.com/fo" + kEllipsisStr},
+ {"http://a.b.com/pathname/c?d", L"a.b.com/" + kEllipsisStr + L"/c?d"},
+ {"", L""},
+ {"http://foo.bar..example.com...hello/test/filename.html",
+ L"foo.bar..example.com...hello/" + kEllipsisStr + L"/filename.html"},
+ {"http://foo.bar../", L"foo.bar.."},
+ {"http://xn--1lq90i.cn/foo", L"\x5317\x4eac.cn/foo"},
+ {"http://me:mypass@secrethost.com:99/foo?bar#baz",
+ L"secrethost.com:99/foo?bar#baz"},
+ {"http://me:mypass@ss%xxfdsf.com/foo", L"ss%25xxfdsf.com/foo"},
+ {"mailto:elgoato@elgoato.com", L"mailto:elgoato@elgoato.com"},
+ {"javascript:click(0)", L"javascript:click(0)"},
+ {"https://chess.eecs.berkeley.edu:4430/login/arbitfilename",
+ L"chess.eecs.berkeley.edu:4430/login/arbitfilename"},
+ {"https://chess.eecs.berkeley.edu:4430/login/arbitfilename",
+ kEllipsisStr + L"berkeley.edu:4430/" + kEllipsisStr + L"/arbitfilename"},
+
+ // Unescaping.
+ {"http://www/%E4%BD%A0%E5%A5%BD?q=%E4%BD%A0%E5%A5%BD#\xe4\xbd\xa0",
+ L"www/\x4f60\x597d?q=\x4f60\x597d#\x4f60"},
+
+ // Invalid unescaping for path. The ref will always be valid UTF-8. We don't
+ // bother to do too many edge cases, since these are handled by the escaper
+ // unittest.
+ {"http://www/%E4%A0%E5%A5%BD?q=%E4%BD%A0%E5%A5%BD#\xe4\xbd\xa0",
+ L"www/%E4%A0%E5%A5%BD?q=\x4f60\x597d#\x4f60"},
+ };
+
+ RunTest(testcases, arraysize(testcases));
+}
+
+// Test eliding of file: URLs.
+TEST(TextEliderTest, TestFileURLEliding) {
+ const std::wstring kEllipsisStr(kEllipsis);
+ Testcase testcases[] = {
+ {"file:///C:/path1/path2/path3/filename",
+ L"file:///C:/path1/path2/path3/filename"},
+ {"file:///C:/path1/path2/path3/filename",
+ L"C:/path1/path2/path3/filename"},
+// GURL parses "file:///C:path" differently on windows than it does on posix.
+#if defined(OS_WIN)
+ {"file:///C:path1/path2/path3/filename",
+ L"C:/path1/path2/" + kEllipsisStr + L"/filename"},
+ {"file:///C:path1/path2/path3/filename",
+ L"C:/path1/" + kEllipsisStr + L"/filename"},
+ {"file:///C:path1/path2/path3/filename",
+ L"C:/" + kEllipsisStr + L"/filename"},
+#endif
+ {"file://filer/foo/bar/file", L"filer/foo/bar/file"},
+ {"file://filer/foo/bar/file", L"filer/foo/" + kEllipsisStr + L"/file"},
+ {"file://filer/foo/bar/file", L"filer/" + kEllipsisStr + L"/file"},
+ };
+
+ RunTest(testcases, arraysize(testcases));
+}
+
+TEST(TextEliderTest, TestFilenameEliding) {
+ const std::wstring kEllipsisStr(kEllipsis);
+ const FilePath::StringType kPathSeparator =
+ FilePath::StringType().append(1, FilePath::kSeparators[0]);
+
+ FileTestcase testcases[] = {
+ {FILE_PATH_LITERAL(""), L""},
+ {FILE_PATH_LITERAL("."), L"."},
+ {FILE_PATH_LITERAL("filename.exe"), L"filename.exe"},
+ {FILE_PATH_LITERAL(".longext"), L".longext"},
+ {FILE_PATH_LITERAL("pie"), L"pie"},
+ {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") +
+ kPathSeparator + FILE_PATH_LITERAL("filename.pie"),
+ L"filename.pie"},
+ {FILE_PATH_LITERAL("c:") + kPathSeparator + FILE_PATH_LITERAL("path") +
+ kPathSeparator + FILE_PATH_LITERAL("longfilename.pie"),
+ L"long" + kEllipsisStr + L".pie"},
+ {FILE_PATH_LITERAL("http://path.com/filename.pie"), L"filename.pie"},
+ {FILE_PATH_LITERAL("http://path.com/longfilename.pie"),
+ L"long" + kEllipsisStr + L".pie"},
+ {FILE_PATH_LITERAL("piesmashingtacularpants"), L"pie" + kEllipsisStr},
+ {FILE_PATH_LITERAL(".piesmashingtacularpants"), L".pie" + kEllipsisStr},
+ {FILE_PATH_LITERAL("cheese."), L"cheese."},
+ {FILE_PATH_LITERAL("file name.longext"),
+ L"file" + kEllipsisStr + L".longext"},
+ {FILE_PATH_LITERAL("fil ename.longext"),
+ L"fil " + kEllipsisStr + L".longext"},
+ {FILE_PATH_LITERAL("filename.longext"),
+ L"file" + kEllipsisStr + L".longext"},
+ {FILE_PATH_LITERAL("filename.middleext.longext"),
+ L"filename.mid" + kEllipsisStr + L".longext"}
+ };
+
+ static const gfx::Font font;
+ for (size_t i = 0; i < arraysize(testcases); ++i) {
+ FilePath filepath(testcases[i].input);
+ string16 expected = WideToUTF16(testcases[i].output);
+ expected = base::i18n::GetDisplayStringInLTRDirectionality(expected);
+ EXPECT_EQ(expected, ElideFilename(filepath,
+ font,
+ font.GetStringWidth(WideToUTF16(testcases[i].output))));
+ }
+}
+
+TEST(TextEliderTest, ElideTextLongStrings) {
+ const string16 kEllipsisStr(WideToUTF16(kEllipsis));
+ string16 data_scheme(UTF8ToUTF16("data:text/plain,"));
+ size_t data_scheme_length = data_scheme.length();
+
+ string16 ten_a(10, 'a');
+ string16 hundred_a(100, 'a');
+ string16 thousand_a(1000, 'a');
+ string16 ten_thousand_a(10000, 'a');
+ string16 hundred_thousand_a(100000, 'a');
+ string16 million_a(1000000, 'a');
+
+ size_t number_of_as = 156;
+ string16 long_string_end(
+ data_scheme + string16(number_of_as, 'a') + kEllipsisStr);
+ UTF16Testcase testcases_end[] = {
+ {data_scheme + ten_a, data_scheme + ten_a},
+ {data_scheme + hundred_a, data_scheme + hundred_a},
+ {data_scheme + thousand_a, long_string_end},
+ {data_scheme + ten_thousand_a, long_string_end},
+ {data_scheme + hundred_thousand_a, long_string_end},
+ {data_scheme + million_a, long_string_end},
+ };
+
+ const gfx::Font font;
+ int ellipsis_width = font.GetStringWidth(kEllipsisStr);
+ for (size_t i = 0; i < arraysize(testcases_end); ++i) {
+ // Compare sizes rather than actual contents because if the test fails,
+ // output is rather long.
+ EXPECT_EQ(testcases_end[i].output.size(),
+ ElideText(testcases_end[i].input, font,
+ font.GetStringWidth(testcases_end[i].output),
+ false).size());
+ EXPECT_EQ(kEllipsisStr,
+ ElideText(testcases_end[i].input, font, ellipsis_width, false));
+ }
+
+ size_t number_of_trailing_as = (data_scheme_length + number_of_as) / 2;
+ string16 long_string_middle(data_scheme +
+ string16(number_of_as - number_of_trailing_as, 'a') + kEllipsisStr +
+ string16(number_of_trailing_as, 'a'));
+ UTF16Testcase testcases_middle[] = {
+ {data_scheme + ten_a, data_scheme + ten_a},
+ {data_scheme + hundred_a, data_scheme + hundred_a},
+ {data_scheme + thousand_a, long_string_middle},
+ {data_scheme + ten_thousand_a, long_string_middle},
+ {data_scheme + hundred_thousand_a, long_string_middle},
+ {data_scheme + million_a, long_string_middle},
+ };
+
+ for (size_t i = 0; i < arraysize(testcases_middle); ++i) {
+ // Compare sizes rather than actual contents because if the test fails,
+ // output is rather long.
+ EXPECT_EQ(testcases_middle[i].output.size(),
+ ElideText(testcases_middle[i].input, font,
+ font.GetStringWidth(testcases_middle[i].output),
+ false).size());
+ EXPECT_EQ(kEllipsisStr,
+ ElideText(testcases_middle[i].input, font, ellipsis_width,
+ false));
+ }
+}
+
+// Verifies display_url is set correctly.
+TEST(TextEliderTest, SortedDisplayURL) {
+ ui::SortedDisplayURL d_url(GURL("http://www.google.com"), std::wstring());
+ EXPECT_EQ("www.google.com", UTF16ToASCII(d_url.display_url()));
+}
+
+// Verifies DisplayURL::Compare works correctly.
+TEST(TextEliderTest, SortedDisplayURLCompare) {
+ UErrorCode create_status = U_ZERO_ERROR;
+ scoped_ptr<icu::Collator> collator(
+ icu::Collator::createInstance(create_status));
+ if (!U_SUCCESS(create_status))
+ return;
+
+ TestData tests[] = {
+ // IDN comparison. Hosts equal, so compares on path.
+ { "http://xn--1lq90i.cn/a", "http://xn--1lq90i.cn/b", -1},
+
+ // Because the host and after host match, this compares the full url.
+ { "http://www.x/b", "http://x/b", -1 },
+
+ // Because the host and after host match, this compares the full url.
+ { "http://www.a:1/b", "http://a:1/b", 1 },
+
+ // The hosts match, so these end up comparing on the after host portion.
+ { "http://www.x:0/b", "http://x:1/b", -1 },
+ { "http://www.x/a", "http://x/b", -1 },
+ { "http://x/b", "http://www.x/a", 1 },
+
+ // Trivial Equality.
+ { "http://a/", "http://a/", 0 },
+
+ // Compares just hosts.
+ { "http://www.a/", "http://b/", -1 },
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ ui::SortedDisplayURL url1(GURL(tests[i].a), std::wstring());
+ ui::SortedDisplayURL url2(GURL(tests[i].b), std::wstring());
+ EXPECT_EQ(tests[i].compare_result, url1.Compare(url2, collator.get()));
+ EXPECT_EQ(-tests[i].compare_result, url2.Compare(url1, collator.get()));
+ }
+}
+
+TEST(TextEliderTest, ElideString) {
+ struct TestData {
+ const wchar_t* input;
+ int max_len;
+ bool result;
+ const wchar_t* output;
+ } cases[] = {
+ { L"Hello", 0, true, L"" },
+ { L"", 0, false, L"" },
+ { L"Hello, my name is Tom", 1, true, L"H" },
+ { L"Hello, my name is Tom", 2, true, L"He" },
+ { L"Hello, my name is Tom", 3, true, L"H.m" },
+ { L"Hello, my name is Tom", 4, true, L"H..m" },
+ { L"Hello, my name is Tom", 5, true, L"H...m" },
+ { L"Hello, my name is Tom", 6, true, L"He...m" },
+ { L"Hello, my name is Tom", 7, true, L"He...om" },
+ { L"Hello, my name is Tom", 10, true, L"Hell...Tom" },
+ { L"Hello, my name is Tom", 100, false, L"Hello, my name is Tom" }
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ std::wstring output;
+ EXPECT_EQ(cases[i].result,
+ ui::ElideString(cases[i].input, cases[i].max_len, &output));
+ EXPECT_EQ(cases[i].output, output);
+ }
+}
+
+TEST(TextEliderTest, ElideRectangleString) {
+ struct TestData {
+ const wchar_t* input;
+ int max_rows;
+ int max_cols;
+ bool result;
+ const wchar_t* output;
+ } cases[] = {
+ { L"", 0, 0, false, L"" },
+ { L"", 1, 1, false, L"" },
+ { L"Hi, my name is\nTom", 0, 0, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 0, true, L"\n..." },
+ { L"Hi, my name is\nTom", 0, 1, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 1, true, L"H\n..." },
+ { L"Hi, my name is\nTom", 2, 1, true, L"H\ni\n..." },
+ { L"Hi, my name is\nTom", 3, 1, true, L"H\ni\n,\n..." },
+ { L"Hi, my name is\nTom", 4, 1, true, L"H\ni\n,\n \n..." },
+ { L"Hi, my name is\nTom", 5, 1, true, L"H\ni\n,\n \nm\n..." },
+ { L"Hi, my name is\nTom", 0, 2, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 2, true, L"Hi\n..." },
+ { L"Hi, my name is\nTom", 2, 2, true, L"Hi\n, \n..." },
+ { L"Hi, my name is\nTom", 3, 2, true, L"Hi\n, \nmy\n..." },
+ { L"Hi, my name is\nTom", 4, 2, true, L"Hi\n, \nmy\n n\n..." },
+ { L"Hi, my name is\nTom", 5, 2, true, L"Hi\n, \nmy\n n\nam\n..." },
+ { L"Hi, my name is\nTom", 0, 3, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 3, true, L"Hi,\n..." },
+ { L"Hi, my name is\nTom", 2, 3, true, L"Hi,\n my\n..." },
+ { L"Hi, my name is\nTom", 3, 3, true, L"Hi,\n my\n na\n..." },
+ { L"Hi, my name is\nTom", 4, 3, true, L"Hi,\n my\n na\nme \n..." },
+ { L"Hi, my name is\nTom", 5, 3, true, L"Hi,\n my\n na\nme \nis\n..." },
+ { L"Hi, my name is\nTom", 1, 4, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 4, true, L"Hi, \nmy n\n..." },
+ { L"Hi, my name is\nTom", 3, 4, true, L"Hi, \nmy n\name \n..." },
+ { L"Hi, my name is\nTom", 4, 4, true, L"Hi, \nmy n\name \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 4, false, L"Hi, \nmy n\name \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 5, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 5, true, L"Hi, \nmy na\n..." },
+ { L"Hi, my name is\nTom", 3, 5, true, L"Hi, \nmy na\nme \n..." },
+ { L"Hi, my name is\nTom", 4, 5, true, L"Hi, \nmy na\nme \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 5, false, L"Hi, \nmy na\nme \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 6, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 6, true, L"Hi, \nmy \n..." },
+ { L"Hi, my name is\nTom", 3, 6, true, L"Hi, \nmy \nname \n..." },
+ { L"Hi, my name is\nTom", 4, 6, true, L"Hi, \nmy \nname \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 6, false, L"Hi, \nmy \nname \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 7, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 7, true, L"Hi, \nmy \n..." },
+ { L"Hi, my name is\nTom", 3, 7, true, L"Hi, \nmy \nname \n..." },
+ { L"Hi, my name is\nTom", 4, 7, true, L"Hi, \nmy \nname \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 7, false, L"Hi, \nmy \nname \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 8, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 8, true, L"Hi, my \nname \n..." },
+ { L"Hi, my name is\nTom", 3, 8, true, L"Hi, my \nname \nis\n..." },
+ { L"Hi, my name is\nTom", 4, 8, false, L"Hi, my \nname \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 9, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 9, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 9, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 10, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 10, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 10, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 11, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 11, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 11, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 12, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 12, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 12, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 13, true, L"Hi, my name \n..." },
+ { L"Hi, my name is\nTom", 2, 13, true, L"Hi, my name \nis\n..." },
+ { L"Hi, my name is\nTom", 3, 13, false, L"Hi, my name \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 20, true, L"Hi, my name is\n..." },
+ { L"Hi, my name is\nTom", 2, 20, false, L"Hi, my name is\nTom" },
+ { L"Hi, my name is Tom", 1, 40, false, L"Hi, my name is Tom" },
+ };
+ string16 output;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ EXPECT_EQ(cases[i].result,
+ ui::ElideRectangleString(WideToUTF16(cases[i].input),
+ cases[i].max_rows, cases[i].max_cols,
+ &output));
+ EXPECT_EQ(cases[i].output, UTF16ToWide(output));
+ }
+}
+
+TEST(TextEliderTest, ElideRectangleWide16) {
+ // Two greek words separated by space.
+ const string16 str(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+ L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2"));
+ const string16 out1(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\n"
+ L"\x03cc\x03c3\x03bc\x03b9\n"
+ L"..."));
+ const string16 out2(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9\x03bf\x03c2\x0020\n"
+ L"\x0399\x03c3\x03c4\x03cc\x03c2"));
+ string16 output;
+ EXPECT_TRUE(ui::ElideRectangleString(str, 2, 4, &output));
+ EXPECT_EQ(out1, output);
+ EXPECT_FALSE(ui::ElideRectangleString(str, 2, 12, &output));
+ EXPECT_EQ(out2, output);
+}
+
+TEST(TextEliderTest, ElideRectangleWide32) {
+ // Four U+1D49C MATHEMATICAL SCRIPT CAPITAL A followed by space "aaaaa".
+ const string16 str(UTF8ToUTF16(
+ "\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C"
+ " aaaaa"));
+ const string16 out(UTF8ToUTF16(
+ "\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\n"
+ "\xF0\x9D\x92\x9C \naaa\n..."));
+ string16 output;
+ EXPECT_TRUE(ui::ElideRectangleString(str, 3, 3, &output));
+ EXPECT_EQ(out, output);
+}
+
+} // namespace ui
diff --git a/ui/base/theme_provider.cc b/ui/base/theme_provider.cc
new file mode 100644
index 0000000..a32c7aa
--- /dev/null
+++ b/ui/base/theme_provider.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/theme_provider.h"
+
+namespace ui {
+
+// We have the destructor here because GCC puts the vtable in the first file
+// that includes a virtual function of the class. Leaving it just in the .h file
+// means that GCC will fail to link.
+
+ThemeProvider::~ThemeProvider() {
+}
+
+} // namespace ui
diff --git a/ui/base/theme_provider.h b/ui/base/theme_provider.h
new file mode 100644
index 0000000..43aa28f
--- /dev/null
+++ b/ui/base/theme_provider.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_THEME_PROVIDER_H_
+#define UI_BASE_THEME_PROVIDER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+#if defined(OS_MACOSX)
+#ifdef __OBJC__
+@class NSColor;
+@class NSGradient;
+@class NSImage;
+#else
+class NSColor;
+class NSGradient;
+class NSImage;
+#endif // __OBJC__
+#elif !defined(OS_WIN)
+typedef struct _GdkColor GdkColor;
+typedef struct _GdkPixbuf GdkPixbuf;
+#endif // OS_*
+
+class Profile;
+class RefCountedMemory;
+class SkBitmap;
+
+namespace ui {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ThemeProvider
+//
+// ThemeProvider is an abstract class that defines the API that should be
+// implemented to provide bitmaps and color information for a given theme.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+class ThemeProvider {
+ public:
+ virtual ~ThemeProvider();
+
+ // TODO(beng): This dependency is horrible!
+ // Initialize the provider with the passed in profile.
+ virtual void Init(Profile* profile) = 0;
+
+ // Get the bitmap specified by |id|. An implementation of ThemeProvider should
+ // have its own source of ids (e.g. an enum, or external resource bundle).
+ virtual SkBitmap* GetBitmapNamed(int id) const = 0;
+
+ // Get the color specified by |id|.
+ virtual SkColor GetColor(int id) const = 0;
+
+ // Get the property (e.g. an alignment expressed in an enum, or a width or
+ // height) specified by |id|.
+ virtual bool GetDisplayProperty(int id, int* result) const = 0;
+
+ // Whether we should use the native system frame (typically Aero glass) or
+ // a custom frame.
+ virtual bool ShouldUseNativeFrame() const = 0;
+
+ // Whether or not we have a certain image. Used for when the default theme
+ // doesn't provide a certain image, but custom themes might (badges, etc).
+ virtual bool HasCustomImage(int id) const = 0;
+
+ // Reads the image data from the theme file into the specified vector. Only
+ // valid for un-themed resources and the themed IDR_THEME_NTP_* in most
+ // implementations of ThemeProvider. Returns NULL on error.
+ virtual RefCountedMemory* GetRawData(int id) const = 0;
+
+#if defined(OS_MACOSX)
+ // Gets the NSImage with the specified |id|.
+ //
+ // The bitmap is not assumed to exist. If a theme does not provide an image,
+ // if |allow_default| is true, then the default image will be returned, else
+ // this function will return nil.
+ virtual NSImage* GetNSImageNamed(int id, bool allow_default) const = 0;
+
+ // Gets the NSImage that GetNSImageNamed (above) would return, but returns it
+ // as a pattern color.
+ virtual NSColor* GetNSImageColorNamed(int id, bool allow_default) const = 0;
+
+ // Gets the NSColor with the specified |id|.
+ //
+ // The color is not assumed to exist. If a theme does not provide an color, if
+ // |allow_default| is true, then the default color will be returned, else this
+ // function will return nil.
+ virtual NSColor* GetNSColor(int id, bool allow_default) const = 0;
+
+ // Gets the NSColor for tinting with the specified |id|.
+ //
+ // The tint is not assumed to exist. If a theme does not provide a tint with
+ // that id, if |allow_default| is true, then the default tint will be
+ // returned, else this function will return nil.
+ virtual NSColor* GetNSColorTint(int id, bool allow_default) const = 0;
+
+ // Gets the NSGradient with the specified |id|.
+ virtual NSGradient* GetNSGradient(int id) const = 0;
+#elif defined(OS_POSIX) && !defined(TOOLKIT_VIEWS)
+ // Gets the GdkPixbuf with the specified |id|. Returns a pointer to a shared
+ // instance of the GdkPixbuf. This shared GdkPixbuf is owned by the theme
+ // provider and should not be freed.
+ //
+ // The bitmap is assumed to exist. This function will log in release, and
+ // assert in debug mode if it does not. On failure, this will return a
+ // pointer to a shared empty placeholder bitmap so it will be visible what
+ // is missing.
+ virtual GdkPixbuf* GetPixbufNamed(int id) const = 0;
+
+ // As above, but flips it in RTL locales.
+ virtual GdkPixbuf* GetRTLEnabledPixbufNamed(int id) const = 0;
+#endif
+};
+
+} // namespace ui
+
+#endif // UI_BASE_THEME_PROVIDER_H_
diff --git a/ui/base/view_prop.cc b/ui/base/view_prop.cc
new file mode 100644
index 0000000..119228f
--- /dev/null
+++ b/ui/base/view_prop.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/view_prop.h"
+
+#include <set>
+
+namespace ui {
+
+// Maints the actual view, key and data.
+class ViewProp::Data : public base::RefCounted<ViewProp::Data> {
+ public:
+ // Returns the Data* for the view/key pair. If |create| is false and |Get|
+ // has not been invoked for the view/key pair, NULL is returned.
+ static void Get(gfx::NativeView view,
+ const char* key,
+ bool create,
+ scoped_refptr<Data>* data) {
+ if (!data_set_)
+ data_set_ = new DataSet;
+ scoped_refptr<Data> new_data(new Data(view, key));
+ DataSet::const_iterator i = data_set_->find(new_data.get());
+ if (i != data_set_->end()) {
+ *data = *i;
+ return;
+ }
+ if (!create)
+ return;
+ data_set_->insert(new_data.get());
+ *data = new_data.get();
+ }
+
+ // The data.
+ void set_data(void* data) { data_ = data; }
+ void* data() const { return data_; }
+
+ const char* key() const { return key_; }
+
+ private:
+ friend class base::RefCounted<Data>;
+
+ // Used to order the Data in the map.
+ class DataComparator {
+ public:
+ bool operator()(const Data* d1, const Data* d2) const {
+ return (d1->view_ == d2->view_) ? (d1->key_ < d2->key_) :
+ (d1->view_ < d2->view_);
+ }
+ };
+
+ typedef std::set<Data*, DataComparator> DataSet;
+
+ Data(gfx::NativeView view, const char* key)
+ : view_(view),
+ key_(key),
+ data_(NULL) {}
+
+ ~Data() {
+ DataSet::iterator i = data_set_->find(this);
+ // Also check for equality using == as |Get| creates dummy values in order
+ // to look up a value.
+ if (i != data_set_->end() && *i == this)
+ data_set_->erase(i);
+ }
+
+ // The existing set of Data is stored here. ~Data removes from the set.
+ static DataSet* data_set_;
+
+ const gfx::NativeView view_;
+ const char* key_;
+ void* data_;
+
+ DISALLOW_COPY_AND_ASSIGN(Data);
+};
+
+// static
+ViewProp::Data::DataSet* ViewProp::Data::data_set_ = NULL;
+
+ViewProp::ViewProp(gfx::NativeView view, const char* key, void* data) {
+ Data::Get(view, key, true, &data_);
+ data_->set_data(data);
+}
+
+ViewProp::~ViewProp() {
+ // This is done to provide similar semantics to SetProp. In particular it's
+ // assumed that ~ViewProp should behave as though RemoveProp was invoked.
+ data_->set_data(NULL);
+}
+
+// static
+void* ViewProp::GetValue(gfx::NativeView view, const char* key) {
+ scoped_refptr<Data> data;
+ Data::Get(view, key, false, &data);
+ return data.get() ? data->data() : NULL;
+}
+
+// static
+const char* ViewProp::Key() const {
+ return data_->key();
+}
+
+} // namespace ui
diff --git a/ui/base/view_prop.h b/ui/base/view_prop.h
new file mode 100644
index 0000000..339ef7b
--- /dev/null
+++ b/ui/base/view_prop.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_VIEW_PROP_H_
+#define UI_BASE_VIEW_PROP_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "gfx/native_widget_types.h"
+
+namespace ui {
+
+// ViewProp maintains a key/value pair for a particular view. ViewProp is
+// designed as a replacement for the Win32's SetProp, but does not make use of
+// window manager memory. ViewProp shares similar semantics as SetProp, the
+// value for a particular view/key pair comes from the last ViewProp created.
+class ViewProp {
+ public:
+ // Associates data with a view/key pair. If a ViewProp has already been
+ // created for the specified pair |data| replaces the current value.
+ //
+ // ViewProp does *not* make a copy of the char*, the pointer is used for
+ // sorting.
+ ViewProp(gfx::NativeView view, const char* key, void* data);
+ ~ViewProp();
+
+ // Returns the value associated with the view/key pair, or NULL if there is
+ // none.
+ static void* GetValue(gfx::NativeView view, const char* key);
+
+ // Returns the key.
+ const char* Key() const;
+
+ private:
+ class Data;
+
+ // Stores the actual data.
+ scoped_refptr<Data> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewProp);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_VIEW_PROP_H_
diff --git a/ui/base/view_prop_unittest.cc b/ui/base/view_prop_unittest.cc
new file mode 100644
index 0000000..686099f
--- /dev/null
+++ b/ui/base/view_prop_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/scoped_ptr.h"
+#include "ui/base/view_prop.h"
+
+namespace ui {
+
+typedef testing::Test ViewPropTest;
+
+static const char* kKey1 = "key_1";
+static const char* kKey2 = "key_2";
+
+using ui::ViewProp;
+
+// Test a handful of viewprop assertions.
+TEST_F(ViewPropTest, Basic) {
+ gfx::NativeView nv1 = reinterpret_cast<gfx::NativeView>(1);
+ gfx::NativeView nv2 = reinterpret_cast<gfx::NativeView>(2);
+
+ void* data1 = reinterpret_cast<void*>(11);
+ void* data2 = reinterpret_cast<void*>(12);
+
+ // Initial value for a new view/key pair should be NULL.
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv1, kKey1));
+
+ {
+ // Register a value for a view/key pair.
+ ViewProp prop(nv1, kKey1, data1);
+ EXPECT_EQ(data1, ViewProp::GetValue(nv1, kKey1));
+ }
+
+ // The property fell out of scope, so the value should now be NULL.
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv1, kKey1));
+
+ {
+ // Register a value for a view/key pair.
+ scoped_ptr<ViewProp> v1(new ViewProp(nv1, kKey1, data1));
+ EXPECT_EQ(data1, ViewProp::GetValue(nv1, kKey1));
+
+ // Register a value for the same view/key pair.
+ scoped_ptr<ViewProp> v2(new ViewProp(nv1, kKey1, data2));
+ // The new value should take over.
+ EXPECT_EQ(data2, ViewProp::GetValue(nv1, kKey1));
+
+ // Null out the first ViewProp, which should NULL out the value.
+ v1.reset(NULL);
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv1, kKey1));
+ }
+
+ // The property fell out of scope, so the value should now be NULL.
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv1, kKey1));
+
+ {
+ // Register a value for a view/key pair.
+ scoped_ptr<ViewProp> v1(new ViewProp(nv1, kKey1, data1));
+ scoped_ptr<ViewProp> v2(new ViewProp(nv2, kKey2, data2));
+ EXPECT_EQ(data1, ViewProp::GetValue(nv1, kKey1));
+ EXPECT_EQ(data2, ViewProp::GetValue(nv2, kKey2));
+
+ v1.reset(NULL);
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv1, kKey1));
+ EXPECT_EQ(data2, ViewProp::GetValue(nv2, kKey2));
+
+ v2.reset(NULL);
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv1, kKey1));
+ EXPECT_EQ(NULL, ViewProp::GetValue(nv2, kKey2));
+ }
+}
+
+} // namespace ui
diff --git a/ui/base/x/active_window_watcher_x.cc b/ui/base/x/active_window_watcher_x.cc
new file mode 100644
index 0000000..97e0466
--- /dev/null
+++ b/ui/base/x/active_window_watcher_x.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <X11/Xlib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include "ui/base/x/active_window_watcher_x.h"
+
+namespace ui {
+
+static Atom kNetActiveWindowAtom = None;
+
+// static
+ActiveWindowWatcherX* ActiveWindowWatcherX::GetInstance() {
+ return Singleton<ActiveWindowWatcherX>::get();
+}
+
+// static
+void ActiveWindowWatcherX::AddObserver(Observer* observer) {
+ GetInstance()->observers_.AddObserver(observer);
+}
+
+// static
+void ActiveWindowWatcherX::RemoveObserver(Observer* observer) {
+ GetInstance()->observers_.RemoveObserver(observer);
+}
+
+ActiveWindowWatcherX::ActiveWindowWatcherX() {
+ Init();
+}
+
+ActiveWindowWatcherX::~ActiveWindowWatcherX() {
+}
+
+void ActiveWindowWatcherX::Init() {
+ GdkAtom kNetActiveWindow = gdk_atom_intern("_NET_ACTIVE_WINDOW", FALSE);
+ kNetActiveWindowAtom = gdk_x11_atom_to_xatom_for_display(
+ gdk_screen_get_display(gdk_screen_get_default()), kNetActiveWindow);
+
+ GdkWindow* root = gdk_get_default_root_window();
+
+ // Set up X Event filter to listen for PropertyChange X events. These events
+ // tell us when the active window changes.
+ // Don't use XSelectInput directly here, as gdk internally seems to cache the
+ // mask and reapply XSelectInput after this, resetting any mask we set here.
+ gdk_window_set_events(root,
+ static_cast<GdkEventMask>(gdk_window_get_events(root) |
+ GDK_PROPERTY_CHANGE_MASK));
+ gdk_window_add_filter(NULL, &ActiveWindowWatcherX::OnWindowXEvent, this);
+}
+
+void ActiveWindowWatcherX::NotifyActiveWindowChanged() {
+ // We don't use gdk_screen_get_active_window() because it caches
+ // whether or not the window manager supports _NET_ACTIVE_WINDOW.
+ // This causes problems at startup for chromiumos.
+ Atom type = None;
+ int format = 0; // size in bits of each item in 'property'
+ long unsigned int num_items = 0, remaining_bytes = 0;
+ unsigned char* property = NULL;
+
+ XGetWindowProperty(gdk_x11_get_default_xdisplay(),
+ GDK_WINDOW_XID(gdk_get_default_root_window()),
+ kNetActiveWindowAtom,
+ 0, // offset into property data to read
+ 1, // length to get in 32-bit quantities
+ False, // deleted
+ AnyPropertyType,
+ &type,
+ &format,
+ &num_items,
+ &remaining_bytes,
+ &property);
+
+ // Check that the property was set and contained a single 32-bit item (we
+ // don't check that remaining_bytes is 0, though, as XFCE's window manager
+ // seems to actually store two values in the property for some unknown
+ // reason.)
+ if (format == 32 && num_items == 1) {
+ int xid = *reinterpret_cast<int*>(property);
+ GdkWindow* active_window = gdk_window_lookup(xid);
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ ActiveWindowChanged(active_window));
+ }
+ if (property)
+ XFree(property);
+}
+
+GdkFilterReturn ActiveWindowWatcherX::OnWindowXEvent(GdkXEvent* xevent,
+ GdkEvent* event, gpointer window_watcher) {
+ ActiveWindowWatcherX* watcher = reinterpret_cast<ActiveWindowWatcherX*>(
+ window_watcher);
+ XEvent* xev = static_cast<XEvent*>(xevent);
+
+ if (xev->xany.type == PropertyNotify &&
+ xev->xproperty.atom == kNetActiveWindowAtom) {
+ watcher->NotifyActiveWindowChanged();
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+} // namespace ui
diff --git a/ui/base/x/active_window_watcher_x.h b/ui/base/x/active_window_watcher_x.h
new file mode 100644
index 0000000..68907c4
--- /dev/null
+++ b/ui/base/x/active_window_watcher_x.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_X_ACTIVE_WINDOW_WATCHER_X_H_
+#define UI_BASE_X_ACTIVE_WINDOW_WATCHER_X_H_
+#pragma once
+
+#include <gdk/gdk.h>
+
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "base/singleton.h"
+
+namespace ui {
+
+// This is a helper class that is used to keep track of which window the X
+// window manager thinks is active. Add an Observer to listener for changes to
+// the active window.
+class ActiveWindowWatcherX {
+ public:
+ class Observer {
+ public:
+ // |active_window| will be NULL if the active window isn't one of Chrome's.
+ virtual void ActiveWindowChanged(GdkWindow* active_window) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ static ActiveWindowWatcherX* GetInstance();
+
+ static void AddObserver(Observer* observer);
+ static void RemoveObserver(Observer* observer);
+
+ private:
+ friend struct DefaultSingletonTraits<ActiveWindowWatcherX>;
+
+ ActiveWindowWatcherX();
+ ~ActiveWindowWatcherX();
+
+ void Init();
+
+ // Sends a notification out through the NotificationService that the active
+ // window has changed.
+ void NotifyActiveWindowChanged();
+
+ // Callback for PropertyChange XEvents.
+ static GdkFilterReturn OnWindowXEvent(GdkXEvent* xevent,
+ GdkEvent* event,
+ gpointer window_watcher);
+
+ ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ActiveWindowWatcherX);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_X_ACTIVE_WINDOW_WATCHER_X_H_
diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc
new file mode 100644
index 0000000..2b5863a
--- /dev/null
+++ b/ui/base/x/x11_util.cc
@@ -0,0 +1,880 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines utility functions for X11 (Linux only). This code has been
+// ported from XCB since we can't use XCB on Ubuntu while its 32-bit support
+// remains woefully incomplete.
+
+#include "ui/base/x/x11_util.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include <list>
+#include <set>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "base/string_number_conversions.h"
+#include "base/threading/thread.h"
+#include "gfx/rect.h"
+#include "gfx/size.h"
+#include "ui/base/x/x11_util_internal.h"
+
+namespace ui {
+
+namespace {
+
+// Used to cache the XRenderPictFormat for a visual/display pair.
+struct CachedPictFormat {
+ bool equals(Display* display, Visual* visual) const {
+ return display == this->display && visual == this->visual;
+ }
+
+ Display* display;
+ Visual* visual;
+ XRenderPictFormat* format;
+};
+
+typedef std::list<CachedPictFormat> CachedPictFormats;
+
+// Returns the cache of pict formats.
+CachedPictFormats* get_cached_pict_formats() {
+ static CachedPictFormats* formats = NULL;
+ if (!formats)
+ formats = new CachedPictFormats();
+ return formats;
+}
+
+// Maximum number of CachedPictFormats we keep around.
+const size_t kMaxCacheSize = 5;
+
+int DefaultX11ErrorHandler(Display* d, XErrorEvent* e) {
+ LOG(ERROR) << GetErrorEventDescription(d, e);
+ return 0;
+}
+
+int DefaultX11IOErrorHandler(Display* d) {
+ // If there's an IO error it likely means the X server has gone away
+ LOG(ERROR) << "X IO Error detected";
+ _exit(1);
+}
+
+} // namespace
+
+bool XDisplayExists() {
+ return (gdk_display_get_default() != NULL);
+}
+
+Display* GetXDisplay() {
+ static Display* display = NULL;
+
+ if (!display)
+ display = gdk_x11_get_default_xdisplay();
+
+ return display;
+}
+
+static SharedMemorySupport DoQuerySharedMemorySupport(Display* dpy) {
+ // A temporary flag for tracking down shared memory problems.
+ // TODO(evanm): remove this.
+ if (CommandLine::ForCurrentProcess()->HasSwitch("disable-xshm"))
+ return SHARED_MEMORY_NONE;
+
+ int dummy;
+ Bool pixmaps_supported;
+ // Query the server's support for XSHM.
+ if (!XShmQueryVersion(dpy, &dummy, &dummy, &pixmaps_supported))
+ return SHARED_MEMORY_NONE;
+
+ // Next we probe to see if shared memory will really work
+ int shmkey = shmget(IPC_PRIVATE, 1, 0666);
+ if (shmkey == -1)
+ return SHARED_MEMORY_NONE;
+ void* address = shmat(shmkey, NULL, 0);
+ // Mark the shared memory region for deletion
+ shmctl(shmkey, IPC_RMID, NULL);
+
+ XShmSegmentInfo shminfo;
+ memset(&shminfo, 0, sizeof(shminfo));
+ shminfo.shmid = shmkey;
+
+ gdk_error_trap_push();
+ bool result = XShmAttach(dpy, &shminfo);
+ XSync(dpy, False);
+ if (gdk_error_trap_pop())
+ result = false;
+ shmdt(address);
+ if (!result)
+ return SHARED_MEMORY_NONE;
+
+ XShmDetach(dpy, &shminfo);
+ return pixmaps_supported ? SHARED_MEMORY_PIXMAP : SHARED_MEMORY_PUTIMAGE;
+}
+
+SharedMemorySupport QuerySharedMemorySupport(Display* dpy) {
+ static SharedMemorySupport shared_memory_support = SHARED_MEMORY_NONE;
+ static bool shared_memory_support_cached = false;
+
+ if (shared_memory_support_cached)
+ return shared_memory_support;
+
+ shared_memory_support = DoQuerySharedMemorySupport(dpy);
+ shared_memory_support_cached = true;
+
+ return shared_memory_support;
+}
+
+bool QueryRenderSupport(Display* dpy) {
+ static bool render_supported = false;
+ static bool render_supported_cached = false;
+
+ if (render_supported_cached)
+ return render_supported;
+
+ // We don't care about the version of Xrender since all the features which
+ // we use are included in every version.
+ int dummy;
+ render_supported = XRenderQueryExtension(dpy, &dummy, &dummy);
+ render_supported_cached = true;
+
+ return render_supported;
+}
+
+int GetDefaultScreen(Display* display) {
+ return XDefaultScreen(display);
+}
+
+XID GetX11RootWindow() {
+ return GDK_WINDOW_XID(gdk_get_default_root_window());
+}
+
+bool GetCurrentDesktop(int* desktop) {
+ return GetIntProperty(GetX11RootWindow(), "_NET_CURRENT_DESKTOP", desktop);
+}
+
+XID GetX11WindowFromGtkWidget(GtkWidget* widget) {
+ return GDK_WINDOW_XID(widget->window);
+}
+
+XID GetX11WindowFromGdkWindow(GdkWindow* window) {
+ return GDK_WINDOW_XID(window);
+}
+
+void* GetVisualFromGtkWidget(GtkWidget* widget) {
+ return GDK_VISUAL_XVISUAL(gtk_widget_get_visual(widget));
+}
+
+int BitsPerPixelForPixmapDepth(Display* dpy, int depth) {
+ int count;
+ XPixmapFormatValues* formats = XListPixmapFormats(dpy, &count);
+ if (!formats)
+ return -1;
+
+ int bits_per_pixel = -1;
+ for (int i = 0; i < count; ++i) {
+ if (formats[i].depth == depth) {
+ bits_per_pixel = formats[i].bits_per_pixel;
+ break;
+ }
+ }
+
+ XFree(formats);
+ return bits_per_pixel;
+}
+
+bool IsWindowVisible(XID window) {
+ XWindowAttributes win_attributes;
+ XGetWindowAttributes(GetXDisplay(), window, &win_attributes);
+ if (win_attributes.map_state != IsViewable)
+ return false;
+ // Some compositing window managers (notably kwin) do not actually unmap
+ // windows on desktop switch, so we also must check the current desktop.
+ int window_desktop, current_desktop;
+ return (!GetWindowDesktop(window, &window_desktop) ||
+ !GetCurrentDesktop(&current_desktop) ||
+ window_desktop == kAllDesktops ||
+ window_desktop == current_desktop);
+}
+
+bool GetWindowRect(XID window, gfx::Rect* rect) {
+ Window root, child;
+ int x, y;
+ unsigned int width, height;
+ unsigned int border_width, depth;
+
+ if (!XGetGeometry(GetXDisplay(), window, &root, &x, &y,
+ &width, &height, &border_width, &depth))
+ return false;
+
+ if (!XTranslateCoordinates(GetSecondaryDisplay(), window, root,
+ 0, 0, &x, &y, &child))
+ return false;
+
+ *rect = gfx::Rect(x, y, width, height);
+ return true;
+}
+
+bool GetIntProperty(XID window, const std::string& property_name, int* value) {
+ Atom property_atom = gdk_x11_get_xatom_by_name_for_display(
+ gdk_display_get_default(), property_name.c_str());
+
+ Atom type = None;
+ int format = 0; // size in bits of each item in 'property'
+ long unsigned int num_items = 0, remaining_bytes = 0;
+ unsigned char* property = NULL;
+
+ int result = XGetWindowProperty(GetXDisplay(),
+ window,
+ property_atom,
+ 0, // offset into property data to read
+ 1, // max length to get
+ False, // deleted
+ AnyPropertyType,
+ &type,
+ &format,
+ &num_items,
+ &remaining_bytes,
+ &property);
+ if (result != Success)
+ return false;
+
+ if (format != 32 || num_items != 1) {
+ XFree(property);
+ return false;
+ }
+
+ *value = *(reinterpret_cast<int*>(property));
+ XFree(property);
+ return true;
+}
+
+bool GetIntArrayProperty(XID window,
+ const std::string& property_name,
+ std::vector<int>* value) {
+ Atom property_atom = gdk_x11_get_xatom_by_name_for_display(
+ gdk_display_get_default(), property_name.c_str());
+
+ Atom type = None;
+ int format = 0; // size in bits of each item in 'property'
+ long unsigned int num_items = 0, remaining_bytes = 0;
+ unsigned char* properties = NULL;
+
+ int result = XGetWindowProperty(GetXDisplay(),
+ window,
+ property_atom,
+ 0, // offset into property data to read
+ (~0L), // max length to get (all of them)
+ False, // deleted
+ AnyPropertyType,
+ &type,
+ &format,
+ &num_items,
+ &remaining_bytes,
+ &properties);
+ if (result != Success)
+ return false;
+
+ if (format != 32) {
+ XFree(properties);
+ return false;
+ }
+
+ int* int_properties = reinterpret_cast<int*>(properties);
+ value->clear();
+ value->insert(value->begin(), int_properties, int_properties + num_items);
+ XFree(properties);
+ return true;
+}
+
+bool GetStringProperty(
+ XID window, const std::string& property_name, std::string* value) {
+ Atom property_atom = gdk_x11_get_xatom_by_name_for_display(
+ gdk_display_get_default(), property_name.c_str());
+
+ Atom type = None;
+ int format = 0; // size in bits of each item in 'property'
+ long unsigned int num_items = 0, remaining_bytes = 0;
+ unsigned char* property = NULL;
+
+ int result = XGetWindowProperty(GetXDisplay(),
+ window,
+ property_atom,
+ 0, // offset into property data to read
+ 1024, // max length to get
+ False, // deleted
+ AnyPropertyType,
+ &type,
+ &format,
+ &num_items,
+ &remaining_bytes,
+ &property);
+ if (result != Success)
+ return false;
+
+ if (format != 8) {
+ XFree(property);
+ return false;
+ }
+
+ value->assign(reinterpret_cast<char*>(property), num_items);
+ XFree(property);
+ return true;
+}
+
+XID GetParentWindow(XID window) {
+ XID root = None;
+ XID parent = None;
+ XID* children = NULL;
+ unsigned int num_children = 0;
+ XQueryTree(GetXDisplay(), window, &root, &parent, &children, &num_children);
+ if (children)
+ XFree(children);
+ return parent;
+}
+
+XID GetHighestAncestorWindow(XID window, XID root) {
+ while (true) {
+ XID parent = GetParentWindow(window);
+ if (parent == None)
+ return None;
+ if (parent == root)
+ return window;
+ window = parent;
+ }
+}
+
+bool GetWindowDesktop(XID window, int* desktop) {
+ return GetIntProperty(window, "_NET_WM_DESKTOP", desktop);
+}
+
+// Returns true if |window| is a named window.
+bool IsWindowNamed(XID window) {
+ XTextProperty prop;
+ if (!XGetWMName(GetXDisplay(), window, &prop) || !prop.value)
+ return false;
+
+ XFree(prop.value);
+ return true;
+}
+
+bool EnumerateChildren(EnumerateWindowsDelegate* delegate, XID window,
+ const int max_depth, int depth) {
+ if (depth > max_depth)
+ return false;
+
+ XID root, parent, *children;
+ unsigned int num_children;
+ int status = XQueryTree(GetXDisplay(), window, &root, &parent, &children,
+ &num_children);
+ if (status == 0)
+ return false;
+
+ std::set<XID> windows;
+ for (unsigned int i = 0; i < num_children; i++)
+ windows.insert(children[i]);
+
+ XFree(children);
+
+ // XQueryTree returns the children of |window| in bottom-to-top order, so
+ // reverse-iterate the list to check the windows from top-to-bottom.
+ std::set<XID>::reverse_iterator iter;
+ for (iter = windows.rbegin(); iter != windows.rend(); iter++) {
+ if (IsWindowNamed(*iter) && delegate->ShouldStopIterating(*iter))
+ return true;
+ }
+
+ // If we're at this point, we didn't find the window we're looking for at the
+ // current level, so we need to recurse to the next level. We use a second
+ // loop because the recursion and call to XQueryTree are expensive and is only
+ // needed for a small number of cases.
+ if (++depth <= max_depth) {
+ for (iter = windows.rbegin(); iter != windows.rend(); iter++) {
+ if (EnumerateChildren(delegate, *iter, max_depth, depth))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool EnumerateAllWindows(EnumerateWindowsDelegate* delegate, int max_depth) {
+ XID root = GetX11RootWindow();
+ return EnumerateChildren(delegate, root, max_depth, 0);
+}
+
+bool GetXWindowStack(std::vector<XID>* windows) {
+ windows->clear();
+
+ static Atom atom = XInternAtom(GetXDisplay(),
+ "_NET_CLIENT_LIST_STACKING", False);
+
+ Atom type;
+ int format;
+ unsigned long count;
+ unsigned long bytes_after;
+ unsigned char *data = NULL;
+ if (XGetWindowProperty(GetXDisplay(),
+ GetX11RootWindow(),
+ atom,
+ 0, // offset
+ ~0L, // length
+ False, // delete
+ AnyPropertyType, // requested type
+ &type,
+ &format,
+ &count,
+ &bytes_after,
+ &data) != Success) {
+ return false;
+ }
+
+ bool result = false;
+ if (type == XA_WINDOW && format == 32 && data && count > 0) {
+ result = true;
+ XID* stack = reinterpret_cast<XID*>(data);
+ for (unsigned long i = 0; i < count; i++)
+ windows->insert(windows->begin(), stack[i]);
+ }
+
+ if (data)
+ XFree(data);
+
+ return result;
+}
+
+void RestackWindow(XID window, XID sibling, bool above) {
+ XWindowChanges changes;
+ changes.sibling = sibling;
+ changes.stack_mode = above ? Above : Below;
+ XConfigureWindow(GetXDisplay(), window, CWSibling | CWStackMode, &changes);
+}
+
+XSharedMemoryId AttachSharedMemory(Display* display, int shared_memory_key) {
+ DCHECK(QuerySharedMemorySupport(display));
+
+ XShmSegmentInfo shminfo;
+ memset(&shminfo, 0, sizeof(shminfo));
+ shminfo.shmid = shared_memory_key;
+
+ // This function is only called if QuerySharedMemorySupport returned true. In
+ // which case we've already succeeded in having the X server attach to one of
+ // our shared memory segments.
+ if (!XShmAttach(display, &shminfo))
+ NOTREACHED();
+
+ return shminfo.shmseg;
+}
+
+void DetachSharedMemory(Display* display, XSharedMemoryId shmseg) {
+ DCHECK(QuerySharedMemorySupport(display));
+
+ XShmSegmentInfo shminfo;
+ memset(&shminfo, 0, sizeof(shminfo));
+ shminfo.shmseg = shmseg;
+
+ if (!XShmDetach(display, &shminfo))
+ NOTREACHED();
+}
+
+XID CreatePictureFromSkiaPixmap(Display* display, XID pixmap) {
+ XID picture = XRenderCreatePicture(
+ display, pixmap, GetRenderARGB32Format(display), 0, NULL);
+
+ return picture;
+}
+
+void PutARGBImage(Display* display, void* visual, int depth, XID pixmap,
+ void* pixmap_gc, const uint8* data, int width, int height) {
+ // TODO(scherkus): potential performance impact... consider passing in as a
+ // parameter.
+ int pixmap_bpp = BitsPerPixelForPixmapDepth(display, depth);
+
+ XImage image;
+ memset(&image, 0, sizeof(image));
+
+ image.width = width;
+ image.height = height;
+ image.format = ZPixmap;
+ image.byte_order = LSBFirst;
+ image.bitmap_unit = 8;
+ image.bitmap_bit_order = LSBFirst;
+ image.depth = depth;
+ image.bits_per_pixel = pixmap_bpp;
+ image.bytes_per_line = width * pixmap_bpp / 8;
+
+ if (pixmap_bpp == 32) {
+ image.red_mask = 0xff0000;
+ image.green_mask = 0xff00;
+ image.blue_mask = 0xff;
+
+ // If the X server depth is already 32-bits and the color masks match,
+ // then our job is easy.
+ Visual* vis = static_cast<Visual*>(visual);
+ if (image.red_mask == vis->red_mask &&
+ image.green_mask == vis->green_mask &&
+ image.blue_mask == vis->blue_mask) {
+ image.data = const_cast<char*>(reinterpret_cast<const char*>(data));
+ XPutImage(display, pixmap, static_cast<GC>(pixmap_gc), &image,
+ 0, 0 /* source x, y */, 0, 0 /* dest x, y */,
+ width, height);
+ } else {
+ // Otherwise, we need to shuffle the colors around. Assume red and blue
+ // need to be swapped.
+ //
+ // It's possible to use some fancy SSE tricks here, but since this is the
+ // slow path anyway, we do it slowly.
+
+ uint8_t* bitmap32 = static_cast<uint8_t*>(malloc(4 * width * height));
+ if (!bitmap32)
+ return;
+ uint8_t* const orig_bitmap32 = bitmap32;
+ const uint32_t* bitmap_in = reinterpret_cast<const uint32_t*>(data);
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ const uint32_t pixel = *(bitmap_in++);
+ bitmap32[0] = (pixel >> 16) & 0xff; // Red
+ bitmap32[1] = (pixel >> 8) & 0xff; // Green
+ bitmap32[2] = pixel & 0xff; // Blue
+ bitmap32[3] = (pixel >> 24) & 0xff; // Alpha
+ bitmap32 += 4;
+ }
+ }
+ image.data = reinterpret_cast<char*>(orig_bitmap32);
+ XPutImage(display, pixmap, static_cast<GC>(pixmap_gc), &image,
+ 0, 0 /* source x, y */, 0, 0 /* dest x, y */,
+ width, height);
+ free(orig_bitmap32);
+ }
+ } else if (pixmap_bpp == 16) {
+ // Some folks have VNC setups which still use 16-bit visuals and VNC
+ // doesn't include Xrender.
+
+ uint16_t* bitmap16 = static_cast<uint16_t*>(malloc(2 * width * height));
+ if (!bitmap16)
+ return;
+ uint16_t* const orig_bitmap16 = bitmap16;
+ const uint32_t* bitmap_in = reinterpret_cast<const uint32_t*>(data);
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ const uint32_t pixel = *(bitmap_in++);
+ uint16_t out_pixel = ((pixel >> 8) & 0xf800) |
+ ((pixel >> 5) & 0x07e0) |
+ ((pixel >> 3) & 0x001f);
+ *(bitmap16++) = out_pixel;
+ }
+ }
+
+ image.data = reinterpret_cast<char*>(orig_bitmap16);
+ image.red_mask = 0xf800;
+ image.green_mask = 0x07e0;
+ image.blue_mask = 0x001f;
+
+ XPutImage(display, pixmap, static_cast<GC>(pixmap_gc), &image,
+ 0, 0 /* source x, y */, 0, 0 /* dest x, y */,
+ width, height);
+ free(orig_bitmap16);
+ } else {
+ LOG(FATAL) << "Sorry, we don't support your visual depth without "
+ "Xrender support (depth:" << depth
+ << " bpp:" << pixmap_bpp << ")";
+ }
+}
+
+void FreePicture(Display* display, XID picture) {
+ XRenderFreePicture(display, picture);
+}
+
+void FreePixmap(Display* display, XID pixmap) {
+ XFreePixmap(display, pixmap);
+}
+
+// Called on BACKGROUND_X11 thread.
+Display* GetSecondaryDisplay() {
+ static Display* display = NULL;
+ if (!display) {
+ display = XOpenDisplay(NULL);
+ CHECK(display);
+ }
+
+ return display;
+}
+
+// Called on BACKGROUND_X11 thread.
+bool GetWindowGeometry(int* x, int* y, unsigned* width, unsigned* height,
+ XID window) {
+ Window root_window, child_window;
+ unsigned border_width, depth;
+ int temp;
+
+ if (!XGetGeometry(GetSecondaryDisplay(), window, &root_window, &temp, &temp,
+ width, height, &border_width, &depth))
+ return false;
+ if (!XTranslateCoordinates(GetSecondaryDisplay(), window, root_window,
+ 0, 0 /* input x, y */, x, y /* output x, y */,
+ &child_window))
+ return false;
+
+ return true;
+}
+
+// Called on BACKGROUND_X11 thread.
+bool GetWindowParent(XID* parent_window, bool* parent_is_root, XID window) {
+ XID root_window, *children;
+ unsigned num_children;
+
+ Status s = XQueryTree(GetSecondaryDisplay(), window, &root_window,
+ parent_window, &children, &num_children);
+ if (!s)
+ return false;
+
+ if (children)
+ XFree(children);
+
+ *parent_is_root = root_window == *parent_window;
+ return true;
+}
+
+bool GetWindowManagerName(std::string* wm_name) {
+ DCHECK(wm_name);
+ int wm_window = 0;
+ if (!GetIntProperty(GetX11RootWindow(),
+ "_NET_SUPPORTING_WM_CHECK",
+ &wm_window)) {
+ return false;
+ }
+
+ // It's possible that a window manager started earlier in this X session left
+ // a stale _NET_SUPPORTING_WM_CHECK property when it was replaced by a
+ // non-EWMH window manager, so we trap errors in the following requests to
+ // avoid crashes (issue 23860).
+
+ // EWMH requires the supporting-WM window to also have a
+ // _NET_SUPPORTING_WM_CHECK property pointing to itself (to avoid a stale
+ // property referencing an ID that's been recycled for another window), so we
+ // check that too.
+ gdk_error_trap_push();
+ int wm_window_property = 0;
+ bool result = GetIntProperty(
+ wm_window, "_NET_SUPPORTING_WM_CHECK", &wm_window_property);
+ gdk_flush();
+ bool got_error = gdk_error_trap_pop();
+ if (got_error || !result || wm_window_property != wm_window)
+ return false;
+
+ gdk_error_trap_push();
+ result = GetStringProperty(
+ static_cast<XID>(wm_window), "_NET_WM_NAME", wm_name);
+ gdk_flush();
+ got_error = gdk_error_trap_pop();
+ return !got_error && result;
+}
+
+static cairo_status_t SnapshotCallback(
+ void *closure, const unsigned char *data, unsigned int length) {
+ std::vector<unsigned char>* png_representation =
+ static_cast<std::vector<unsigned char>*>(closure);
+
+ size_t old_size = png_representation->size();
+ png_representation->resize(old_size + length);
+ memcpy(&(*png_representation)[old_size], data, length);
+ return CAIRO_STATUS_SUCCESS;
+}
+
+void GrabWindowSnapshot(GtkWindow* gtk_window,
+ std::vector<unsigned char>* png_representation) {
+ GdkWindow* gdk_window = GTK_WIDGET(gtk_window)->window;
+ Display* display = GDK_WINDOW_XDISPLAY(gdk_window);
+ XID win = GDK_WINDOW_XID(gdk_window);
+ XWindowAttributes attr;
+ if (XGetWindowAttributes(display, win, &attr) == 0) {
+ LOG(ERROR) << "Couldn't get window attributes";
+ return;
+ }
+ XImage* image = XGetImage(
+ display, win, 0, 0, attr.width, attr.height, AllPlanes, ZPixmap);
+ if (!image) {
+ LOG(ERROR) << "Couldn't get image";
+ return;
+ }
+ if (image->depth != 24) {
+ LOG(ERROR)<< "Unsupported image depth " << image->depth;
+ return;
+ }
+ cairo_surface_t* surface =
+ cairo_image_surface_create_for_data(
+ reinterpret_cast<unsigned char*>(image->data),
+ CAIRO_FORMAT_RGB24,
+ image->width,
+ image->height,
+ image->bytes_per_line);
+
+ if (!surface) {
+ LOG(ERROR) << "Unable to create Cairo surface from XImage data";
+ return;
+ }
+ cairo_surface_write_to_png_stream(
+ surface, SnapshotCallback, png_representation);
+ cairo_surface_destroy(surface);
+}
+
+bool ChangeWindowDesktop(XID window, XID destination) {
+ int desktop;
+ if (!GetWindowDesktop(destination, &desktop))
+ return false;
+
+ // If |window| is sticky, use the current desktop.
+ if (desktop == kAllDesktops &&
+ !GetCurrentDesktop(&desktop))
+ return false;
+
+ XEvent event;
+ event.xclient.type = ClientMessage;
+ event.xclient.window = window;
+ event.xclient.message_type = gdk_x11_get_xatom_by_name_for_display(
+ gdk_display_get_default(), "_NET_WM_DESKTOP");
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = desktop;
+ event.xclient.data.l[1] = 1; // source indication
+
+ int result = XSendEvent(GetXDisplay(), GetX11RootWindow(), False,
+ SubstructureNotifyMask, &event);
+ return result == Success;
+}
+
+void SetDefaultX11ErrorHandlers() {
+ SetX11ErrorHandlers(NULL, NULL);
+}
+
+// ----------------------------------------------------------------------------
+// These functions are declared in x11_util_internal.h because they require
+// XLib.h to be included, and it conflicts with many other headers.
+XRenderPictFormat* GetRenderARGB32Format(Display* dpy) {
+ static XRenderPictFormat* pictformat = NULL;
+ if (pictformat)
+ return pictformat;
+
+ // First look for a 32-bit format which ignores the alpha value
+ XRenderPictFormat templ;
+ templ.depth = 32;
+ templ.type = PictTypeDirect;
+ templ.direct.red = 16;
+ templ.direct.green = 8;
+ templ.direct.blue = 0;
+ templ.direct.redMask = 0xff;
+ templ.direct.greenMask = 0xff;
+ templ.direct.blueMask = 0xff;
+ templ.direct.alphaMask = 0;
+
+ static const unsigned long kMask =
+ PictFormatType | PictFormatDepth |
+ PictFormatRed | PictFormatRedMask |
+ PictFormatGreen | PictFormatGreenMask |
+ PictFormatBlue | PictFormatBlueMask |
+ PictFormatAlphaMask;
+
+ pictformat = XRenderFindFormat(dpy, kMask, &templ, 0 /* first result */);
+
+ if (!pictformat) {
+ // Not all X servers support xRGB32 formats. However, the XRENDER spec says
+ // that they must support an ARGB32 format, so we can always return that.
+ pictformat = XRenderFindStandardFormat(dpy, PictStandardARGB32);
+ CHECK(pictformat) << "XRENDER ARGB32 not supported.";
+ }
+
+ return pictformat;
+}
+
+XRenderPictFormat* GetRenderVisualFormat(Display* dpy, Visual* visual) {
+ DCHECK(QueryRenderSupport(dpy));
+
+ CachedPictFormats* formats = get_cached_pict_formats();
+
+ for (CachedPictFormats::const_iterator i = formats->begin();
+ i != formats->end(); ++i) {
+ if (i->equals(dpy, visual))
+ return i->format;
+ }
+
+ // Not cached, look up the value.
+ XRenderPictFormat* pictformat = XRenderFindVisualFormat(dpy, visual);
+ CHECK(pictformat) << "XRENDER does not support default visual";
+
+ // And store it in the cache.
+ CachedPictFormat cached_value;
+ cached_value.visual = visual;
+ cached_value.display = dpy;
+ cached_value.format = pictformat;
+ formats->push_front(cached_value);
+
+ if (formats->size() == kMaxCacheSize) {
+ formats->pop_back();
+ // We should really only have at most 2 display/visual combinations:
+ // one for normal browser windows, and possibly another for an argb window
+ // created to display a menu.
+ //
+ // If we get here it's not fatal, we just need to make sure we aren't
+ // always blowing away the cache. If we are, then we should figure out why
+ // and make it bigger.
+ NOTREACHED();
+ }
+
+ return pictformat;
+}
+
+void SetX11ErrorHandlers(XErrorHandler error_handler,
+ XIOErrorHandler io_error_handler) {
+ XSetErrorHandler(error_handler ? error_handler : DefaultX11ErrorHandler);
+ XSetIOErrorHandler(
+ io_error_handler ? io_error_handler : DefaultX11IOErrorHandler);
+}
+
+std::string GetErrorEventDescription(Display *dpy,
+ XErrorEvent *error_event) {
+ char error_str[256];
+ char request_str[256];
+
+ XGetErrorText(dpy, error_event->error_code, error_str, sizeof(error_str));
+
+ strncpy(request_str, "Unknown", sizeof(request_str));
+ if (error_event->request_code < 128) {
+ std::string num = base::UintToString(error_event->request_code);
+ XGetErrorDatabaseText(
+ dpy, "XRequest", num.c_str(), "Unknown", request_str,
+ sizeof(request_str));
+ } else {
+ int num_ext;
+ char **ext_list = XListExtensions(dpy, &num_ext);
+
+ for (int i = 0; i < num_ext; i++) {
+ int ext_code, first_event, first_error;
+ XQueryExtension(dpy, ext_list[i], &ext_code, &first_event, &first_error);
+ if (error_event->request_code == ext_code) {
+ std::string msg = StringPrintf(
+ "%s.%d", ext_list[i], error_event->minor_code);
+ XGetErrorDatabaseText(
+ dpy, "XRequest", msg.c_str(), "Unknown", request_str,
+ sizeof(request_str));
+ break;
+ }
+ }
+ XFreeExtensionList(ext_list);
+ }
+
+ return base::StringPrintf(
+ "X Error detected: serial %lu, error_code %u (%s), "
+ "request_code %u minor_code %u (%s)",
+ error_event->serial, error_event->error_code, error_str,
+ error_event->request_code, error_event->minor_code, request_str);
+}
+// ----------------------------------------------------------------------------
+// End of x11_util_internal.h
+
+
+} // namespace ui
diff --git a/ui/base/x/x11_util.h b/ui/base/x/x11_util.h
new file mode 100644
index 0000000..58f9157
--- /dev/null
+++ b/ui/base/x/x11_util.h
@@ -0,0 +1,183 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_X_X11_UTIL_H_
+#define UI_BASE_X_X11_UTIL_H_
+#pragma once
+
+// This file declares utility functions for X11 (Linux only).
+//
+// These functions do not require the Xlib headers to be included (which is why
+// we use a void* for Visual*). The Xlib headers are highly polluting so we try
+// hard to limit their spread into the rest of the code.
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+typedef struct _GdkDrawable GdkWindow;
+typedef struct _GtkWidget GtkWidget;
+typedef struct _GtkWindow GtkWindow;
+typedef unsigned long XID;
+typedef unsigned long XSharedMemoryId; // ShmSeg in the X headers.
+typedef struct _XDisplay Display;
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+
+// These functions use the GDK default display and this /must/ be called from
+// the UI thread. Thus, they don't support multiple displays.
+
+// These functions cache their results ---------------------------------
+
+// Check if there's an open connection to an X server.
+bool XDisplayExists();
+// Return an X11 connection for the current, primary display.
+Display* GetXDisplay();
+
+// X shared memory comes in three flavors:
+// 1) No SHM support,
+// 2) SHM putimage,
+// 3) SHM pixmaps + putimage.
+enum SharedMemorySupport {
+ SHARED_MEMORY_NONE,
+ SHARED_MEMORY_PUTIMAGE,
+ SHARED_MEMORY_PIXMAP
+};
+// Return the shared memory type of our X connection.
+SharedMemorySupport QuerySharedMemorySupport(Display* dpy);
+
+// Return true iff the display supports Xrender
+bool QueryRenderSupport(Display* dpy);
+
+// Return the default screen number for the display
+int GetDefaultScreen(Display* display);
+
+// These functions do not cache their results --------------------------
+
+// Get the X window id for the default root window
+XID GetX11RootWindow();
+// Returns the user's current desktop.
+bool GetCurrentDesktop(int* desktop);
+// Get the X window id for the given GTK widget.
+XID GetX11WindowFromGtkWidget(GtkWidget* widget);
+XID GetX11WindowFromGdkWindow(GdkWindow* window);
+// Get a Visual from the given widget. Since we don't include the Xlib
+// headers, this is returned as a void*.
+void* GetVisualFromGtkWidget(GtkWidget* widget);
+// Return the number of bits-per-pixel for a pixmap of the given depth
+int BitsPerPixelForPixmapDepth(Display* display, int depth);
+// Returns true if |window| is visible.
+bool IsWindowVisible(XID window);
+// Returns the bounds of |window|.
+bool GetWindowRect(XID window, gfx::Rect* rect);
+// Get the value of an int, int array, or string property. On
+// success, true is returned and the value is stored in |value|.
+bool GetIntProperty(XID window, const std::string& property_name, int* value);
+bool GetIntArrayProperty(XID window, const std::string& property_name,
+ std::vector<int>* value);
+bool GetStringProperty(
+ XID window, const std::string& property_name, std::string* value);
+
+// Get |window|'s parent window, or None if |window| is the root window.
+XID GetParentWindow(XID window);
+
+// Walk up |window|'s hierarchy until we find a direct child of |root|.
+XID GetHighestAncestorWindow(XID window, XID root);
+
+static const int kAllDesktops = -1;
+// Queries the desktop |window| is on, kAllDesktops if sticky. Returns false if
+// property not found.
+bool GetWindowDesktop(XID window, int* desktop);
+
+// Implementers of this interface receive a notification for every X window of
+// the main display.
+class EnumerateWindowsDelegate {
+ public:
+ // |xid| is the X Window ID of the enumerated window. Return true to stop
+ // further iteration.
+ virtual bool ShouldStopIterating(XID xid) = 0;
+
+ protected:
+ virtual ~EnumerateWindowsDelegate() {}
+};
+
+// Enumerates all windows in the current display. Will recurse into child
+// windows up to a depth of |max_depth|.
+bool EnumerateAllWindows(EnumerateWindowsDelegate* delegate, int max_depth);
+
+// Returns a list of top-level windows in top-to-bottom stacking order.
+bool GetXWindowStack(std::vector<XID>* windows);
+
+// Restack a window in relation to one of its siblings. If |above| is true,
+// |window| will be stacked directly above |sibling|; otherwise it will stacked
+// directly below it. Both windows must be immediate children of the same
+// window.
+void RestackWindow(XID window, XID sibling, bool above);
+
+// Return a handle to a X ShmSeg. |shared_memory_key| is a SysV
+// IPC key. The shared memory region must contain 32-bit pixels.
+XSharedMemoryId AttachSharedMemory(Display* display, int shared_memory_support);
+void DetachSharedMemory(Display* display, XSharedMemoryId shmseg);
+
+// Return a handle to an XRender picture where |pixmap| is a handle to a
+// pixmap containing Skia ARGB data.
+XID CreatePictureFromSkiaPixmap(Display* display, XID pixmap);
+
+// Draws ARGB data on the given pixmap using the given GC, converting to the
+// server side visual depth as needed. Destination is assumed to be the same
+// dimensions as |data| or larger. |data| is also assumed to be in row order
+// with each line being exactly |width| * 4 bytes long.
+void PutARGBImage(Display* display, void* visual, int depth, XID pixmap,
+ void* pixmap_gc, const uint8* data, int width, int height);
+
+void FreePicture(Display* display, XID picture);
+void FreePixmap(Display* display, XID pixmap);
+
+// These functions are for performing X opertions outside of the UI thread.
+
+// Return the Display for the secondary X connection. We keep a second
+// connection around for making X requests outside of the UI thread.
+// This function may only be called from the BACKGROUND_X11 thread.
+Display* GetSecondaryDisplay();
+
+// Since one cannot include both WebKit header and Xlib headers in the same
+// file (due to collisions), we wrap all the Xlib functions that we need here.
+// These functions must be called on the BACKGROUND_X11 thread since they
+// reference GetSecondaryDisplay().
+
+// Get the position of the given window in screen coordinates as well as its
+// current size.
+bool GetWindowGeometry(int* x, int* y, unsigned* width, unsigned* height,
+ XID window);
+
+// Find the immediate parent of an X window.
+//
+// parent_window: (output) the parent window of |window|, or 0.
+// parent_is_root: (output) true iff the parent of |window| is the root window.
+bool GetWindowParent(XID* parent_window, bool* parent_is_root, XID window);
+
+// Get the window manager name.
+bool GetWindowManagerName(std::string* name);
+
+// Grabs a snapshot of the designated window and stores a PNG representation
+// into a byte vector.
+void GrabWindowSnapshot(GtkWindow* gdk_window,
+ std::vector<unsigned char>* png_representation);
+
+// Change desktop for |window| to the desktop of |destination| window.
+bool ChangeWindowDesktop(XID window, XID destination);
+
+// Enable the default X error handlers. These will log the error and abort
+// the process if called. Use SetX11ErrorHandlers() from x11_util_internal.h
+// to set your own error handlers.
+void SetDefaultX11ErrorHandlers();
+
+} // namespace ui
+
+#endif // UI_BASE_X_X11_UTIL_H_
diff --git a/ui/base/x/x11_util_internal.h b/ui/base/x/x11_util_internal.h
new file mode 100644
index 0000000..cee2bec
--- /dev/null
+++ b/ui/base/x/x11_util_internal.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_X_X11_UTIL_INTERNAL_H_
+#define UI_BASE_X_X11_UTIL_INTERNAL_H_
+#pragma once
+
+// This file declares utility functions for X11 (Linux only).
+//
+// These functions require the inclusion of the Xlib headers. Since the Xlib
+// headers pollute so much of the namespace, this should only be included
+// when needed.
+
+extern "C" {
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/XShm.h>
+#include <X11/extensions/Xrender.h>
+}
+
+namespace ui {
+
+ // --------------------------------------------------------------------------
+ // NOTE: these functions cache the results and must be called from the UI
+ // thread.
+ // Get the XRENDER format id for ARGB32 (Skia's format).
+ //
+ // NOTE:Currently this don't support multiple screens/displays.
+ XRenderPictFormat* GetRenderARGB32Format(Display* dpy);
+
+ // Get the XRENDER format id for the default visual on the first screen. This
+ // is the format which our GTK window will have.
+ XRenderPictFormat* GetRenderVisualFormat(Display* dpy, Visual* visual);
+
+ // --------------------------------------------------------------------------
+ // X11 error handling.
+ // Sets the X Error Handlers. Passing NULL for either will enable the default
+ // error handler, which if called will log the error and abort the process.
+ void SetX11ErrorHandlers(XErrorHandler error_handler,
+ XIOErrorHandler io_error_handler);
+
+ // Returns a string suitable for logging the error event.
+ std::string GetErrorEventDescription(Display* dpy, XErrorEvent* error_event);
+
+} // namespace ui
+
+#endif // UI_BASE_X_X11_UTIL_INTERNAL_H_