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

Unified Diff: chrome/browser/renderer_host/render_widget_host_view_gtk.cc

Issue 159586: This CL fixes issue 11480: Support GTK keyboard themes (emacs keybindings).... (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: '' Created 11 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/renderer_host/render_widget_host_view_gtk.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/renderer_host/render_widget_host_view_gtk.cc
===================================================================
--- chrome/browser/renderer_host/render_widget_host_view_gtk.cc (版本 21941)
+++ chrome/browser/renderer_host/render_widget_host_view_gtk.cc (工作副本)
@@ -30,7 +30,352 @@
using WebKit::WebInputEventFactory;
-// This class is a conveience wrapper for GtkIMContext.
+// This class is a convenience class for handling editor key bindings defined
+// in gtk keyboard theme.
+// In gtk, only GtkEntry and GtkTextView support customizing editor key bindings
+// through keyboard theme. And in gtk keyboard theme definition file, each key
+// binding must be bound to a specific class or object. So existing keyboard
+// themes only define editor key bindings exactly for GtkEntry and GtkTextView.
+// Then, the only way for us to intercept editor key bindings defined in
+// keyboard theme, is to create a GtkEntry or GtkTextView object and call
+// gtk_bindings_activate_event() against it for the key events. If a key event
+// matches a predefined key binding, corresponding signal will be emitted.
+// GtkTextView is used here because it supports more key bindings than GtkEntry,
+// but in order to minimize the side effect of using a GtkTextView object, a new
+// class derived from GtkTextView is used, which overrides all signals related
+// to key bindings, to make sure GtkTextView won't receive them.
+// Key binding signals will be translated into corresponding webkit editor
+// commands and send to webkit by calling
+// RenderWidgetHost::ForwardEditCommand().
+// See third_party/WebKit/WebCore/editing/EditorCommand.cpp for detailed
+// definition of webkit editor commands.
+// See webkit/glue/editor_client_impl.cc for key bindings predefined in our
+// webkit glue.
+//
+// TODO(james.su@gmail.com): Check if current event flow is appropriate, which
+// sends a key event to renderer(webkit) first, then calls this key bindings
+// handler if the key event is not handled by renderer. So key bindings
+// defined in our webkit glue have higher priority.
+// However, some key bindings defined in our webkit glue may conflict with the
+// key bindings defined in gtk keyboard theme, for example ctrl-a is
+// bound to "SelectAll" in our webkit glue. But in gtk Emacs keyboard theme,
+// it's bound to "MoveToBeginningOfParagraph".
+// In such case, an Emacs user would prefer Emacs key bindings over our
+// built-in key bindings.
+// But it may also cause problem if we call this key bindings handler before
+// sending key events to renderer, because there is no way to prevent webkit
+// from interpreting it as a key binding. Then unexpected behavior may occur,
+// if a key event matches a key binding defined in gtk keyboard theme as well as
+// a (different) key binding defined in webkit glue.
+class RenderWidgetHostViewGtkKeyBindings {
Evan Stade 2009/07/30 01:14:56 ok this file is beginning to get absurdly long. Ca
james.su_gmail.com 2009/07/30 06:24:10 I'd like to split this file. Any suggestion to the
+ public:
+ explicit RenderWidgetHostViewGtkKeyBindings(
+ RenderWidgetHostViewGtk* host_view)
+ : host_view_(host_view),
+ handler_(CreateNewHandler()),
+ enabled_(false),
+ handled_(false) {
+ GtkWidget* parent_widget = host_view->native_view();
+ DCHECK(GTK_IS_FIXED(parent_widget));
+ // We need add the |handler_| object into gtk widget hierarchy, so that
+ // gtk_bindings_activate_event() can find correct display and keymaps from
+ // the |handler_| object.
+ gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1);
+ }
+
+ ~RenderWidgetHostViewGtkKeyBindings() {
+ handler_.Destroy();
+ }
+
+ // Key bindings handler will be disabled when IME is disabled by webkit.
+ void set_enabled(bool enabled) {
+ enabled_ = enabled;
+ }
+
+ // Handles a key event, false will be returned if the key event doesn't
+ // correspond to a predefined key binding.
+ bool Handle(const NativeWebKeyboardEvent& wke) {
+ if (!enabled_ || wke.type == WebKit::WebInputEvent::Char ||
+ !wke.os_event || !wke.os_event->keyval)
+ return false;
+
+ DLOG(INFO) << "Unhandled key "
+ << (wke.os_event->type == GDK_KEY_PRESS ? "press" : "release")
+ << ": keyval=" << wke.os_event->keyval
+ << " state=" << wke.os_event->state;
+
+ handled_ = false;
+
+ // If this key event matches a predefined key binding, corresponding signal
+ // will be emitted.
+ gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), wke.os_event);
+ return handled_;
+ }
+
+ private:
+ struct Handler {
+ GtkTextView parent_object;
+ RenderWidgetHostViewGtkKeyBindings *owner;
+ };
+
+ struct HandlerClass {
+ GtkTextViewClass parent_class;
+ };
+
+ GtkWidget* CreateNewHandler() {
+ Handler* handler =
+ static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
+
+ handler->owner = this;
+
+ // We don't need to show the |handler| object on screen, so set its size to
+ // zero.
+ gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
+
+ // Prevents it from handling any events by itself.
+ gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
+ gtk_widget_set_events(GTK_WIDGET(handler), 0);
+ GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(handler), GTK_CAN_FOCUS);
+
+#if !GTK_CHECK_VERSION(2, 14, 0)
+ // "move-viewport", "select-all" and "toggle-cursor-visible" have no
+ // corresponding virtual methods. Prior to glib 2.18 (gtk 2.14), there is no
+ // way to override the default class handler of a signal. So we need hook
+ // these signal explicitly, and the default signal handlers of GtkTextView
+ // class will still be called. Hope it won't cause any side effect.
+ g_signal_connect(handler, "move-viewport", G_CALLBACK(MoveViewport), NULL);
+ g_signal_connect(handler, "select-all", G_CALLBACK(SelectAll), NULL);
+ g_signal_connect(handler, "toggle-cursor-visible",
+ G_CALLBACK(ToggleCursorVisible), NULL);
+#endif
+ return GTK_WIDGET(handler);
+ }
+
+ void ForwardEditCommand(const std::string& name, const std::string& value) {
+ DLOG(INFO) << "ForwardEditCommand:" << name << " value:" << value;
+ host_view_->GetRenderWidgetHost()->ForwardEditCommand(name, value);
Evan Stade 2009/07/30 01:14:56 It looks like ForwardEditCommand does nothing. Is
james.su_gmail.com 2009/07/30 06:24:10 this DLOG is just for debugging, will be removed a
+
+ // A key event is treated as handled only if an editor command is sent to
+ // webkit.
+ handled_ = true;
+ }
+
+ static void HandlerInit(Handler *self) {
+ self->owner = NULL;
+ }
+
+ static void HandlerClassInit(HandlerClass *klass) {
+ GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
+
+ // Overrides all virtual methods related to editor key bindings.
+ text_view_class->move_cursor = MoveCursor;
+ text_view_class->set_anchor = SetAnchor;
+ text_view_class->insert_at_cursor = InsertAtCursor;
+ text_view_class->delete_from_cursor = DeleteFromCursor;
+ text_view_class->backspace = BackSpace;
+ text_view_class->cut_clipboard = CutClipboard;
+ text_view_class->copy_clipboard = CopyClipboard;
+ text_view_class->paste_clipboard = PasteClipboard;
+ text_view_class->toggle_overwrite = ToggleOverwrite;
+
+#if GTK_CHECK_VERSION(2, 14, 0)
+ // "move-viewport", "select-all" and "toggle-cursor-visible" have no
+ // corresponding virtual methods. Since glib 2.18 (gtk 2.14),
+ // g_signal_override_class_handler() is introduced to override a signal
+ // handler.
+ g_signal_override_class_handler("move-viewport",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(MoveViewport));
+
+ g_signal_override_class_handler("select-all",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(SelectAll));
+
+ g_signal_override_class_handler("toggle-cursor-visible",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(ToggleCursorVisible));
+#endif
+ }
+
+ static GType HandlerGetType() {
+ static volatile gsize type_id_volatile = 0;
+ if (g_once_init_enter(&type_id_volatile)) {
+ GType type_id = g_type_register_static_simple(
+ GTK_TYPE_TEXT_VIEW,
+ g_intern_static_string("RenderWidgetHostViewGtkKeyBindingsHandler"),
+ sizeof(HandlerClass),
+ reinterpret_cast<GClassInitFunc>(HandlerClassInit),
+ sizeof(Handler),
+ reinterpret_cast<GInstanceInitFunc>(HandlerInit),
+ static_cast<GTypeFlags>(0));
+ g_once_init_leave(&type_id_volatile, type_id);
+ }
+ return type_id_volatile;
+ }
+
+ static RenderWidgetHostViewGtkKeyBindings* GetHandlerOwner(
+ GtkTextView* text_view) {
+ Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
+ text_view, HandlerGetType(), Handler);
+ DCHECK(handler);
+ return handler->owner;
+ }
+
+ static void MoveCursor(GtkTextView* text_view, GtkMovementStep step,
+ gint count, gboolean extend_selection) {
+ if (!count)
+ return;
+
+ std::string command;
+ switch (step) {
+ case GTK_MOVEMENT_LOGICAL_POSITIONS:
+ command = (count > 0 ? "MoveForward" : "MoveBackward");
+ break;
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ command = (count > 0 ? "MoveRight" : "MoveLeft");
+ break;
+ case GTK_MOVEMENT_WORDS:
+ command = (count > 0 ? "MoveWordForward" : "MoveWordBackward");
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ command = (count > 0 ? "MoveDown" : "MoveUp");
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+ command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine");
+ break;
+ case GTK_MOVEMENT_PARAGRAPH_ENDS:
+ command = (count > 0 ? "MoveToEndOfParagraph" :
+ "MoveToBeginningOfParagraph");
+ break;
+ case GTK_MOVEMENT_PAGES:
+ command = (count > 0 ? "MovePageDown" : "MovePageUp");
+ break;
+ case GTK_MOVEMENT_BUFFER_ENDS:
+ command = (count > 0 ? "MoveToEndOfDocument" :
+ "MoveToBeginningOfDocument");
+ break;
+ default:
+ // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
+ // no corresponding editor commands.
+ return;
+ }
+
+ RenderWidgetHostViewGtkKeyBindings* owner = GetHandlerOwner(text_view);
+ if (extend_selection)
+ command.append("AndModifySelection");
+ if (count < 0)
+ count = -count;
+ for (; count > 0; --count)
+ owner->ForwardEditCommand(command, "");
+ }
+
+ static void DeleteFromCursor(GtkTextView* text_view, GtkDeleteType type,
+ gint count) {
+ if (!count)
+ return;
+
+ const char *commands[3] = { NULL, NULL, NULL };
+ switch (type) {
+ case GTK_DELETE_CHARS:
+ commands[0] = (count > 0 ? "Delete" : "DeleteBackward");
+ break;
+ case GTK_DELETE_WORD_ENDS:
+ commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward");
+ break;
+ case GTK_DELETE_WORDS:
+ if (count > 0) {
+ commands[0] = "MoveWordForward";
+ commands[1] = "DeleteWordBackward";
+ } else {
+ commands[0] = "MoveWordBackward";
+ commands[1] = "DeleteWordForward";
+ }
+ break;
+ case GTK_DELETE_DISPLAY_LINES:
+ commands[0] = "MoveToBeginningOfLine";
+ commands[1] = "DeleteToEndOfLine";
+ break;
+ case GTK_DELETE_DISPLAY_LINE_ENDS:
+ commands[0] = (count > 0 ? "DeleteToEndOfLine" :
+ "DeleteToBeginningOfLine");
+ break;
+ case GTK_DELETE_PARAGRAPH_ENDS:
+ commands[0] = (count > 0 ? "DeleteToEndOfParagraph" :
+ "DeleteToBeginningOfParagraph");
+ break;
+ case GTK_DELETE_PARAGRAPHS:
+ commands[0] = "MoveToBeginningOfParagraph";
+ commands[1] = "DeleteToEndOfParagraph";
+ break;
+ default:
+ // GTK_DELETE_WHITESPACE has no corresponding editor command.
+ return;
+ }
+
+ RenderWidgetHostViewGtkKeyBindings* owner = GetHandlerOwner(text_view);
+ if (count < 0)
+ count = -count;
+ for (; count > 0; --count) {
+ for (const char* const* p = commands; *p; ++p)
+ owner->ForwardEditCommand(*p, "");
+ }
+ }
+
+ static void InsertAtCursor(GtkTextView* text_view, const gchar* str) {
+ if (str && *str)
+ GetHandlerOwner(text_view)->ForwardEditCommand("InsertText", str);
+ }
+
+ static void BackSpace(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->ForwardEditCommand("DeleteBackward", "");
+ }
+
+ static void CopyClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->ForwardEditCommand("Copy", "");
+ }
+
+ static void CutClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->ForwardEditCommand("Cut", "");
+ }
+
+ static void PasteClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->ForwardEditCommand("Paste", "");
+ }
+
+ static void SelectAll(GtkTextView* text_view, gboolean select) {
+ if (select)
+ GetHandlerOwner(text_view)->ForwardEditCommand("SelectAll", "");
+ else
+ GetHandlerOwner(text_view)->ForwardEditCommand("Unselect", "");
+ }
+
+ static void SetAnchor(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->ForwardEditCommand("SetMark", "");
+ }
+
+ static void MoveViewport(GtkTextView* text_view, GtkScrollStep step,
+ gint count) {
+ // Not supported by webkit.
+ }
+
+ static void ToggleOverwrite(GtkTextView* text_view) {
+ // Not supported by webkit.
+ }
+
+ static void ToggleCursorVisible(GtkTextView* text_view) {
+ // Not supported by webkit.
+ }
+
+ RenderWidgetHostViewGtk* host_view_;
+ OwnedWidgetGtk handler_;
+
+ // Indicates if key bindings handler is enabled or not.
+ // It'll only be enabled if IME is enabled by webkit.
+ bool enabled_;
+ bool handled_;
+};
+
+// This class is a convenience wrapper for GtkIMContext.
class RenderWidgetHostViewGtkIMContext {
public:
explicit RenderWidgetHostViewGtkIMContext(RenderWidgetHostViewGtk* host_view)
@@ -856,6 +1201,7 @@
void RenderWidgetHostViewGtk::InitAsChild() {
view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
+ key_bindings_handler_.reset(new RenderWidgetHostViewGtkKeyBindings(this));
plugin_container_manager_.set_host_widget(view_.get());
gtk_widget_show(view_.get());
}
@@ -866,6 +1212,7 @@
parent_ = parent_host_view->GetNativeView();
GtkWidget* popup = gtk_window_new(GTK_WINDOW_POPUP);
view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
+ key_bindings_handler_.reset(new RenderWidgetHostViewGtkKeyBindings(this));
plugin_container_manager_.set_host_widget(view_.get());
gtk_container_add(GTK_CONTAINER(popup), view_.get());
@@ -1016,6 +1363,7 @@
void RenderWidgetHostViewGtk::IMEUpdateStatus(int control,
const gfx::Rect& caret_rect) {
+ key_bindings_handler_->set_enabled(control != IME_DISABLE);
im_context_->UpdateStatus(control, caret_rect);
}
@@ -1203,3 +1551,9 @@
}
plugin_pid_map_.erase(pid);
}
+
+bool RenderWidgetHostViewGtk::UnhandledKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ DCHECK(key_bindings_handler_.get());
+ return key_bindings_handler_->Handle(event);
+}
« no previous file with comments | « chrome/browser/renderer_host/render_widget_host_view_gtk.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698