| OLD | NEW |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" | 5 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" |
| 6 | 6 |
| 7 #include <gtk/gtk.h> | 7 #include <gtk/gtk.h> |
| 8 #include <gdk/gdk.h> | 8 #include <gdk/gdk.h> |
| 9 #include <gdk/gdkkeysyms.h> | 9 #include <gdk/gdkkeysyms.h> |
| 10 #include <gdk/gdkx.h> | 10 #include <gdk/gdkx.h> |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 64 G_CALLBACK(OnFocusOut), host_view); | 64 G_CALLBACK(OnFocusOut), host_view); |
| 65 g_signal_connect(widget, "button-press-event", | 65 g_signal_connect(widget, "button-press-event", |
| 66 G_CALLBACK(ButtonPressReleaseEvent), host_view); | 66 G_CALLBACK(ButtonPressReleaseEvent), host_view); |
| 67 g_signal_connect(widget, "button-release-event", | 67 g_signal_connect(widget, "button-release-event", |
| 68 G_CALLBACK(ButtonPressReleaseEvent), host_view); | 68 G_CALLBACK(ButtonPressReleaseEvent), host_view); |
| 69 g_signal_connect(widget, "motion-notify-event", | 69 g_signal_connect(widget, "motion-notify-event", |
| 70 G_CALLBACK(MouseMoveEvent), host_view); | 70 G_CALLBACK(MouseMoveEvent), host_view); |
| 71 g_signal_connect(widget, "scroll-event", | 71 g_signal_connect(widget, "scroll-event", |
| 72 G_CALLBACK(MouseScrollEvent), host_view); | 72 G_CALLBACK(MouseScrollEvent), host_view); |
| 73 | 73 |
| 74 // Create a GtkIMContext instance and attach its signal handlers. | |
| 75 host_view->im_context_ = gtk_im_multicontext_new(); | |
| 76 g_signal_connect(host_view->im_context_, "preedit_start", | |
| 77 G_CALLBACK(InputMethodPreeditStart), host_view); | |
| 78 g_signal_connect(host_view->im_context_, "preedit_end", | |
| 79 G_CALLBACK(InputMethodPreeditEnd), host_view); | |
| 80 g_signal_connect(host_view->im_context_, "preedit_changed", | |
| 81 G_CALLBACK(InputMethodPreeditChanged), host_view); | |
| 82 g_signal_connect(host_view->im_context_, "commit", | |
| 83 G_CALLBACK(InputMethodCommit), host_view); | |
| 84 | |
| 85 GtkTargetList* target_list = gtk_target_list_new(NULL, 0); | 74 GtkTargetList* target_list = gtk_target_list_new(NULL, 0); |
| 86 gtk_target_list_add_text_targets(target_list, 0); | 75 gtk_target_list_add_text_targets(target_list, 0); |
| 87 gint num_targets = 0; | 76 gint num_targets = 0; |
| 88 GtkTargetEntry* targets = gtk_target_table_new_from_list(target_list, | 77 GtkTargetEntry* targets = gtk_target_table_new_from_list(target_list, |
| 89 &num_targets); | 78 &num_targets); |
| 90 gtk_selection_clear_targets(widget, GDK_SELECTION_PRIMARY); | 79 gtk_selection_clear_targets(widget, GDK_SELECTION_PRIMARY); |
| 91 gtk_selection_add_targets(widget, GDK_SELECTION_PRIMARY, targets, | 80 gtk_selection_add_targets(widget, GDK_SELECTION_PRIMARY, targets, |
| 92 num_targets); | 81 num_targets); |
| 93 gtk_target_list_unref(target_list); | 82 gtk_target_list_unref(target_list); |
| 94 gtk_target_table_free(targets, num_targets); | 83 gtk_target_table_free(targets, num_targets); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 113 RenderWidgetHostViewGtk* host_view) { | 102 RenderWidgetHostViewGtk* host_view) { |
| 114 if (host_view->parent_ && host_view->activatable() && | 103 if (host_view->parent_ && host_view->activatable() && |
| 115 GDK_Escape == event->keyval) { | 104 GDK_Escape == event->keyval) { |
| 116 // Force popups to close on Esc just in case the renderer is hung. This | 105 // Force popups to close on Esc just in case the renderer is hung. This |
| 117 // allows us to release our keyboard grab. | 106 // allows us to release our keyboard grab. |
| 118 host_view->host_->Shutdown(); | 107 host_view->host_->Shutdown(); |
| 119 } else { | 108 } else { |
| 120 NativeWebKeyboardEvent wke(event); | 109 NativeWebKeyboardEvent wke(event); |
| 121 host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(wke); | 110 host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(wke); |
| 122 } | 111 } |
| 123 | |
| 124 // Dispatch this event to the GtkIMContext object. | |
| 125 // It sends a "commit" signal when it has a character to be inserted | |
| 126 // even when we use a US keyboard so that we can send a Char event | |
| 127 // (or an IME event) to the renderer in our "commit"-signal handler. | |
| 128 // We should send a KeyDown (or a KeyUp) event before dispatching this | |
| 129 // event to the GtkIMContext object (and send a Char event) so that WebKit | |
| 130 // can dispatch the JavaScript events in the following order: onkeydown(), | |
| 131 // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.) | |
| 132 // TODO(hbono): we should not dispatch a key event when the input focus | |
| 133 // is in a password input? | |
| 134 gtk_im_context_filter_keypress(host_view->im_context_, event); | |
| 135 | |
| 136 // We return TRUE because we did handle the event. If it turns out webkit | 112 // We return TRUE because we did handle the event. If it turns out webkit |
| 137 // can't handle the event, we'll deal with it in | 113 // can't handle the event, we'll deal with it in |
| 138 // RenderView::UnhandledKeyboardEvent(). | 114 // RenderView::UnhandledKeyboardEvent(). |
| 139 return TRUE; | 115 return TRUE; |
| 140 } | 116 } |
| 141 | 117 |
| 142 static gboolean OnFocusIn(GtkWidget* widget, GdkEventFocus* focus, | 118 static gboolean OnFocusIn(GtkWidget* widget, GdkEventFocus* focus, |
| 143 RenderWidgetHostViewGtk* host_view) { | 119 RenderWidgetHostViewGtk* host_view) { |
| 144 int x, y; | 120 int x, y; |
| 145 gtk_widget_get_pointer(widget, &x, &y); | 121 gtk_widget_get_pointer(widget, &x, &y); |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 return FALSE; | 208 return FALSE; |
| 233 } | 209 } |
| 234 | 210 |
| 235 static gboolean MouseScrollEvent(GtkWidget* widget, GdkEventScroll* event, | 211 static gboolean MouseScrollEvent(GtkWidget* widget, GdkEventScroll* event, |
| 236 RenderWidgetHostViewGtk* host_view) { | 212 RenderWidgetHostViewGtk* host_view) { |
| 237 host_view->GetRenderWidgetHost()->ForwardWheelEvent( | 213 host_view->GetRenderWidgetHost()->ForwardWheelEvent( |
| 238 WebInputEventFactory::mouseWheelEvent(event)); | 214 WebInputEventFactory::mouseWheelEvent(event)); |
| 239 return FALSE; | 215 return FALSE; |
| 240 } | 216 } |
| 241 | 217 |
| 242 static void InputMethodCommit(GtkIMContext* im_context, | |
| 243 gchar* text, | |
| 244 RenderWidgetHostViewGtk* host_view) { | |
| 245 std::wstring im_text = UTF8ToWide(text); | |
| 246 if (!host_view->im_is_composing_cjk_text_ && im_text.length() == 1) { | |
| 247 // Send a Char event when we input a composed character without IMEs so | |
| 248 // that this event is to be dispatched to onkeypress() handlers, | |
| 249 // autofill, etc. | |
| 250 ForwardCharEvent(host_view, im_text[0]); | |
| 251 } else { | |
| 252 // Send an IME event. | |
| 253 // Unlike a Char event, an IME event is NOT dispatched to onkeypress() | |
| 254 // handlers or autofill. | |
| 255 host_view->GetRenderWidgetHost()->ImeConfirmComposition(im_text); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 static void InputMethodPreeditStart(GtkIMContext* im_context, | |
| 260 RenderWidgetHostViewGtk* host_view) { | |
| 261 // Start monitoring IME events of the renderer. | |
| 262 // TODO(hbono): a renderer sends these IME events not only for sending the | |
| 263 // caret position, but also for enabling/disabling IMEs. If we need to | |
| 264 // enable/disable IMEs, we should move this code to a better place. | |
| 265 // (This signal handler is called only when an IME is enabled. So, once | |
| 266 // we disable an IME, we cannot receive any IME events from the renderer, | |
| 267 // i.e. we cannot re-enable the IME any longer.) | |
| 268 host_view->GetRenderWidgetHost()->ImeSetInputMode(true); | |
| 269 host_view->im_is_composing_cjk_text_ = true; | |
| 270 } | |
| 271 | |
| 272 static void InputMethodPreeditEnd(GtkIMContext* im_context, | |
| 273 RenderWidgetHostViewGtk* host_view) { | |
| 274 // End monitoring IME events. | |
| 275 host_view->GetRenderWidgetHost()->ImeSetInputMode(false); | |
| 276 host_view->im_is_composing_cjk_text_ = false; | |
| 277 } | |
| 278 | |
| 279 static void InputMethodPreeditChanged(GtkIMContext* im_context, | |
| 280 RenderWidgetHostViewGtk* host_view) { | |
| 281 // Send an IME event to update the composition node of the renderer. | |
| 282 // TODO(hbono): an IME intercepts all key events while composing a text, | |
| 283 // i.e. we cannot receive any GDK_KEY_PRESS (or GDK_KEY_UP) events. | |
| 284 // Should we send pseudo KeyDown (and KeyUp) events to emulate Windows? | |
| 285 gchar* preedit_text = NULL; | |
| 286 gint cursor_position = 0; | |
| 287 gtk_im_context_get_preedit_string(im_context, &preedit_text, NULL, | |
| 288 &cursor_position); | |
| 289 host_view->GetRenderWidgetHost()->ImeSetComposition( | |
| 290 UTF8ToWide(preedit_text), cursor_position, -1, -1); | |
| 291 g_free(preedit_text); | |
| 292 } | |
| 293 | |
| 294 static void ForwardCharEvent(RenderWidgetHostViewGtk* host_view, | |
| 295 wchar_t im_character) { | |
| 296 if (!im_character) | |
| 297 return; | |
| 298 | |
| 299 NativeWebKeyboardEvent char_event(im_character, | |
| 300 base::Time::Now().ToDoubleT()); | |
| 301 host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(char_event); | |
| 302 } | |
| 303 | |
| 304 DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget); | 218 DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget); |
| 305 }; | 219 }; |
| 306 | 220 |
| 307 // static | 221 // static |
| 308 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( | 222 RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( |
| 309 RenderWidgetHost* widget) { | 223 RenderWidgetHost* widget) { |
| 310 return new RenderWidgetHostViewGtk(widget); | 224 return new RenderWidgetHostViewGtk(widget); |
| 311 } | 225 } |
| 312 | 226 |
| 313 RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host) | 227 RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host) |
| 314 : host_(widget_host), | 228 : host_(widget_host), |
| 315 about_to_validate_and_paint_(false), | 229 about_to_validate_and_paint_(false), |
| 316 is_hidden_(false), | 230 is_hidden_(false), |
| 317 is_loading_(false), | 231 is_loading_(false), |
| 318 is_showing_context_menu_(false), | 232 is_showing_context_menu_(false), |
| 319 parent_host_view_(NULL), | 233 parent_host_view_(NULL), |
| 320 parent_(NULL), | 234 parent_(NULL), |
| 321 is_popup_first_mouse_release_(true), | 235 is_popup_first_mouse_release_(true) { |
| 322 im_context_(NULL), | |
| 323 im_is_composing_cjk_text_(false) { | |
| 324 host_->set_view(this); | 236 host_->set_view(this); |
| 325 } | 237 } |
| 326 | 238 |
| 327 RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() { | 239 RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() { |
| 328 if (im_context_) | |
| 329 g_object_unref(im_context_); | |
| 330 view_.Destroy(); | 240 view_.Destroy(); |
| 331 } | 241 } |
| 332 | 242 |
| 333 void RenderWidgetHostViewGtk::InitAsChild() { | 243 void RenderWidgetHostViewGtk::InitAsChild() { |
| 334 view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this)); | 244 view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this)); |
| 335 gtk_widget_show(view_.get()); | 245 gtk_widget_show(view_.get()); |
| 336 } | 246 } |
| 337 | 247 |
| 338 void RenderWidgetHostViewGtk::InitAsPopup( | 248 void RenderWidgetHostViewGtk::InitAsPopup( |
| 339 RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) { | 249 RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) { |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 479 | 389 |
| 480 void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) { | 390 void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) { |
| 481 is_loading_ = is_loading; | 391 is_loading_ = is_loading; |
| 482 // Only call ShowCurrentCursor() when it will actually change the cursor. | 392 // Only call ShowCurrentCursor() when it will actually change the cursor. |
| 483 if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) | 393 if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) |
| 484 ShowCurrentCursor(); | 394 ShowCurrentCursor(); |
| 485 } | 395 } |
| 486 | 396 |
| 487 void RenderWidgetHostViewGtk::IMEUpdateStatus(int control, | 397 void RenderWidgetHostViewGtk::IMEUpdateStatus(int control, |
| 488 const gfx::Rect& caret_rect) { | 398 const gfx::Rect& caret_rect) { |
| 489 // The renderer has updated its IME status. | 399 NOTIMPLEMENTED(); |
| 490 // Control the GtkIMContext object according to this status. | |
| 491 if (!im_context_) | |
| 492 return; | |
| 493 | |
| 494 if (control == IME_DISABLE) { | |
| 495 // TODO(hbono): this code just resets the GtkIMContext object and | |
| 496 // detaches it from this window. Should we prevent sending key events to | |
| 497 // the GtkIMContext object (or unref it) when we disable IMEs? | |
| 498 gtk_im_context_reset(im_context_); | |
| 499 gtk_im_context_set_client_window(im_context_, NULL); | |
| 500 gtk_im_context_set_cursor_location(im_context_, NULL); | |
| 501 } else { | |
| 502 // TODO(hbono): we should finish (not reset) an ongoing composition | |
| 503 // when |control| is IME_COMPLETE_COMPOSITION. | |
| 504 | |
| 505 // Attach the GtkIMContext object to this window. | |
| 506 gtk_im_context_set_client_window(im_context_, view_.get()->window); | |
| 507 | |
| 508 // Updates the position of the IME candidate window. | |
| 509 // The position sent from the renderer is a relative one, so we need to | |
| 510 // attach the GtkIMContext object to this window before changing the | |
| 511 // position. | |
| 512 GdkRectangle cursor_rect(caret_rect.ToGdkRectangle()); | |
| 513 gtk_im_context_set_cursor_location(im_context_, &cursor_rect); | |
| 514 } | |
| 515 } | 400 } |
| 516 | 401 |
| 517 void RenderWidgetHostViewGtk::DidPaintRect(const gfx::Rect& rect) { | 402 void RenderWidgetHostViewGtk::DidPaintRect(const gfx::Rect& rect) { |
| 518 if (is_hidden_) | 403 if (is_hidden_) |
| 519 return; | 404 return; |
| 520 | 405 |
| 521 if (about_to_validate_and_paint_) | 406 if (about_to_validate_and_paint_) |
| 522 invalid_rect_ = invalid_rect_.Union(rect); | 407 invalid_rect_ = invalid_rect_.Union(rect); |
| 523 else | 408 else |
| 524 Paint(rect); | 409 Paint(rect); |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 661 void RenderWidgetHostViewGtk::ReceivedSelectionText(GtkClipboard* clipboard, | 546 void RenderWidgetHostViewGtk::ReceivedSelectionText(GtkClipboard* clipboard, |
| 662 const gchar* text, gpointer userdata) { | 547 const gchar* text, gpointer userdata) { |
| 663 // If there's nothing to paste (|text| is NULL), do nothing. | 548 // If there's nothing to paste (|text| is NULL), do nothing. |
| 664 if (!text) | 549 if (!text) |
| 665 return; | 550 return; |
| 666 RenderWidgetHostViewGtk* host_view = | 551 RenderWidgetHostViewGtk* host_view = |
| 667 reinterpret_cast<RenderWidgetHostViewGtk*>(userdata); | 552 reinterpret_cast<RenderWidgetHostViewGtk*>(userdata); |
| 668 host_view->host_->Send(new ViewMsg_InsertText(host_view->host_->routing_id(), | 553 host_view->host_->Send(new ViewMsg_InsertText(host_view->host_->routing_id(), |
| 669 UTF8ToUTF16(text))); | 554 UTF8ToUTF16(text))); |
| 670 } | 555 } |
| OLD | NEW |