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