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); |
+} |