1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/gtk/nsGtkIMModule.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1684 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim: set ts=4 et sw=4 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#ifdef MOZ_LOGGING 1.11 +#define FORCE_PR_LOG /* Allow logging in the release build */ 1.12 +#endif // MOZ_LOGGING 1.13 +#include "prlog.h" 1.14 +#include "prtime.h" 1.15 + 1.16 +#include "nsGtkIMModule.h" 1.17 +#include "nsWindow.h" 1.18 +#include "mozilla/Likely.h" 1.19 +#include "mozilla/MiscEvents.h" 1.20 +#include "mozilla/Preferences.h" 1.21 +#include "mozilla/TextEvents.h" 1.22 + 1.23 +using namespace mozilla; 1.24 +using namespace mozilla::widget; 1.25 + 1.26 +#ifdef PR_LOGGING 1.27 +PRLogModuleInfo* gGtkIMLog = nullptr; 1.28 + 1.29 +static const char* 1.30 +GetRangeTypeName(uint32_t aRangeType) 1.31 +{ 1.32 + switch (aRangeType) { 1.33 + case NS_TEXTRANGE_RAWINPUT: 1.34 + return "NS_TEXTRANGE_RAWINPUT"; 1.35 + case NS_TEXTRANGE_CONVERTEDTEXT: 1.36 + return "NS_TEXTRANGE_CONVERTEDTEXT"; 1.37 + case NS_TEXTRANGE_SELECTEDRAWTEXT: 1.38 + return "NS_TEXTRANGE_SELECTEDRAWTEXT"; 1.39 + case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT: 1.40 + return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT"; 1.41 + case NS_TEXTRANGE_CARETPOSITION: 1.42 + return "NS_TEXTRANGE_CARETPOSITION"; 1.43 + default: 1.44 + return "UNKNOWN SELECTION TYPE!!"; 1.45 + } 1.46 +} 1.47 + 1.48 +static const char* 1.49 +GetEnabledStateName(uint32_t aState) 1.50 +{ 1.51 + switch (aState) { 1.52 + case IMEState::DISABLED: 1.53 + return "DISABLED"; 1.54 + case IMEState::ENABLED: 1.55 + return "ENABLED"; 1.56 + case IMEState::PASSWORD: 1.57 + return "PASSWORD"; 1.58 + case IMEState::PLUGIN: 1.59 + return "PLUG_IN"; 1.60 + default: 1.61 + return "UNKNOWN ENABLED STATUS!!"; 1.62 + } 1.63 +} 1.64 +#endif 1.65 + 1.66 +const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2; 1.67 + 1.68 +nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nullptr; 1.69 +bool nsGtkIMModule::sUseSimpleContext; 1.70 + 1.71 +nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) : 1.72 + mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nullptr), 1.73 + mContext(nullptr), 1.74 + mSimpleContext(nullptr), 1.75 + mDummyContext(nullptr), 1.76 + mCompositionStart(UINT32_MAX), mProcessingKeyEvent(nullptr), 1.77 + mCompositionTargetOffset(UINT32_MAX), 1.78 + mCompositionState(eCompositionState_NotComposing), 1.79 + mIsIMFocused(false), mIgnoreNativeCompositionEvent(false) 1.80 +{ 1.81 +#ifdef PR_LOGGING 1.82 + if (!gGtkIMLog) { 1.83 + gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets"); 1.84 + } 1.85 +#endif 1.86 + static bool sFirstInstance = true; 1.87 + if (sFirstInstance) { 1.88 + sFirstInstance = false; 1.89 + sUseSimpleContext = 1.90 + Preferences::GetBool( 1.91 + "intl.ime.use_simple_context_on_password_field", 1.92 + kUseSimpleContextDefault); 1.93 + } 1.94 + Init(); 1.95 +} 1.96 + 1.97 +void 1.98 +nsGtkIMModule::Init() 1.99 +{ 1.100 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.101 + ("GtkIMModule(%p): Init, mOwnerWindow=%p", 1.102 + this, mOwnerWindow)); 1.103 + 1.104 + MozContainer* container = mOwnerWindow->GetMozContainer(); 1.105 + NS_PRECONDITION(container, "container is null"); 1.106 + GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container)); 1.107 + 1.108 + // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails. 1.109 + // So, we don't need to check the result. 1.110 + 1.111 + // Normal context. 1.112 + mContext = gtk_im_multicontext_new(); 1.113 + gtk_im_context_set_client_window(mContext, gdkWindow); 1.114 + g_signal_connect(mContext, "preedit_changed", 1.115 + G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback), 1.116 + this); 1.117 + g_signal_connect(mContext, "retrieve_surrounding", 1.118 + G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback), 1.119 + this); 1.120 + g_signal_connect(mContext, "delete_surrounding", 1.121 + G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback), 1.122 + this); 1.123 + g_signal_connect(mContext, "commit", 1.124 + G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback), 1.125 + this); 1.126 + g_signal_connect(mContext, "preedit_start", 1.127 + G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback), 1.128 + this); 1.129 + g_signal_connect(mContext, "preedit_end", 1.130 + G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback), 1.131 + this); 1.132 + 1.133 + // Simple context 1.134 + if (sUseSimpleContext) { 1.135 + mSimpleContext = gtk_im_context_simple_new(); 1.136 + gtk_im_context_set_client_window(mSimpleContext, gdkWindow); 1.137 + g_signal_connect(mSimpleContext, "preedit_changed", 1.138 + G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback), 1.139 + this); 1.140 + g_signal_connect(mSimpleContext, "retrieve_surrounding", 1.141 + G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback), 1.142 + this); 1.143 + g_signal_connect(mSimpleContext, "delete_surrounding", 1.144 + G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback), 1.145 + this); 1.146 + g_signal_connect(mSimpleContext, "commit", 1.147 + G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback), 1.148 + this); 1.149 + g_signal_connect(mSimpleContext, "preedit_start", 1.150 + G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback), 1.151 + this); 1.152 + g_signal_connect(mSimpleContext, "preedit_end", 1.153 + G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback), 1.154 + this); 1.155 + } 1.156 + 1.157 + // Dummy context 1.158 + mDummyContext = gtk_im_multicontext_new(); 1.159 + gtk_im_context_set_client_window(mDummyContext, gdkWindow); 1.160 +} 1.161 + 1.162 +nsGtkIMModule::~nsGtkIMModule() 1.163 +{ 1.164 + if (this == sLastFocusedModule) { 1.165 + sLastFocusedModule = nullptr; 1.166 + } 1.167 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.168 + ("GtkIMModule(%p) was gone", this)); 1.169 +} 1.170 + 1.171 +void 1.172 +nsGtkIMModule::OnDestroyWindow(nsWindow* aWindow) 1.173 +{ 1.174 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.175 + ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p", 1.176 + this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedModule)); 1.177 + 1.178 + NS_PRECONDITION(aWindow, "aWindow must not be null"); 1.179 + 1.180 + if (mLastFocusedWindow == aWindow) { 1.181 + CancelIMEComposition(aWindow); 1.182 + if (mIsIMFocused) { 1.183 + Blur(); 1.184 + } 1.185 + mLastFocusedWindow = nullptr; 1.186 + } 1.187 + 1.188 + if (mOwnerWindow != aWindow) { 1.189 + return; 1.190 + } 1.191 + 1.192 + if (sLastFocusedModule == this) { 1.193 + sLastFocusedModule = nullptr; 1.194 + } 1.195 + 1.196 + /** 1.197 + * NOTE: 1.198 + * The given window is the owner of this, so, we must release the 1.199 + * contexts now. But that might be referred from other nsWindows 1.200 + * (they are children of this. But we don't know why there are the 1.201 + * cases). So, we need to clear the pointers that refers to contexts 1.202 + * and this if the other referrers are still alive. See bug 349727. 1.203 + */ 1.204 + if (mContext) { 1.205 + PrepareToDestroyContext(mContext); 1.206 + gtk_im_context_set_client_window(mContext, nullptr); 1.207 + g_object_unref(mContext); 1.208 + mContext = nullptr; 1.209 + } 1.210 + 1.211 + if (mSimpleContext) { 1.212 + gtk_im_context_set_client_window(mSimpleContext, nullptr); 1.213 + g_object_unref(mSimpleContext); 1.214 + mSimpleContext = nullptr; 1.215 + } 1.216 + 1.217 + if (mDummyContext) { 1.218 + // mContext and mDummyContext have the same slaveType and signal_data 1.219 + // so no need for another workaround_gtk_im_display_closed. 1.220 + gtk_im_context_set_client_window(mDummyContext, nullptr); 1.221 + g_object_unref(mDummyContext); 1.222 + mDummyContext = nullptr; 1.223 + } 1.224 + 1.225 + mOwnerWindow = nullptr; 1.226 + mLastFocusedWindow = nullptr; 1.227 + mInputContext.mIMEState.mEnabled = IMEState::DISABLED; 1.228 + 1.229 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.230 + (" SUCCEEDED, Completely destroyed")); 1.231 +} 1.232 + 1.233 +// Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223: 1.234 +// (and the similar issue of GTK+ IIIM) 1.235 +// The GTK+ XIM and IIIM modules register handlers for the "closed" signal 1.236 +// on the display, but: 1.237 +// * The signal handlers are not disconnected when the module is unloaded. 1.238 +// 1.239 +// The GTK+ XIM module has another problem: 1.240 +// * When the signal handler is run (with the module loaded) it tries 1.241 +// XFree (and fails) on a pointer that did not come from Xmalloc. 1.242 +// 1.243 +// To prevent these modules from being unloaded, use static variables to 1.244 +// hold ref of GtkIMContext class. 1.245 +// For GTK+ XIM module, to prevent the signal handler from being run, 1.246 +// find the signal handlers and remove them. 1.247 +// 1.248 +// GtkIMContextXIMs share XOpenIM connections and display closed signal 1.249 +// handlers (where possible). 1.250 + 1.251 +void 1.252 +nsGtkIMModule::PrepareToDestroyContext(GtkIMContext *aContext) 1.253 +{ 1.254 + MozContainer* container = mOwnerWindow->GetMozContainer(); 1.255 + NS_PRECONDITION(container, "The container of the window is null"); 1.256 + 1.257 + GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext); 1.258 +#if (MOZ_WIDGET_GTK == 2) 1.259 + GtkIMContext *slave = multicontext->slave; 1.260 +#else 1.261 + GtkIMContext *slave = nullptr; //TODO GTK3 1.262 +#endif 1.263 + if (!slave) { 1.264 + return; 1.265 + } 1.266 + 1.267 + GType slaveType = G_TYPE_FROM_INSTANCE(slave); 1.268 + const gchar *im_type_name = g_type_name(slaveType); 1.269 + if (strcmp(im_type_name, "GtkIMContextXIM") == 0) { 1.270 + if (gtk_check_version(2, 12, 1) == nullptr) { 1.271 + return; // gtk bug has been fixed 1.272 + } 1.273 + 1.274 + struct GtkIMContextXIM 1.275 + { 1.276 + GtkIMContext parent; 1.277 + gpointer private_data; 1.278 + // ... other fields 1.279 + }; 1.280 + 1.281 + gpointer signal_data = 1.282 + reinterpret_cast<GtkIMContextXIM*>(slave)->private_data; 1.283 + if (!signal_data) { 1.284 + return; 1.285 + } 1.286 + 1.287 + g_signal_handlers_disconnect_matched( 1.288 + gtk_widget_get_display(GTK_WIDGET(container)), 1.289 + G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, signal_data); 1.290 + 1.291 + // Add a reference to prevent the XIM module from being unloaded 1.292 + // and reloaded: each time the module is loaded and used, it 1.293 + // opens (and doesn't close) new XOpenIM connections. 1.294 + static gpointer gtk_xim_context_class = 1.295 + g_type_class_ref(slaveType); 1.296 + // Mute unused variable warning: 1.297 + (void)gtk_xim_context_class; 1.298 + } else if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) { 1.299 + // Add a reference to prevent the IIIM module from being unloaded 1.300 + static gpointer gtk_iiim_context_class = 1.301 + g_type_class_ref(slaveType); 1.302 + // Mute unused variable warning: 1.303 + (void)gtk_iiim_context_class; 1.304 + } 1.305 +} 1.306 + 1.307 +void 1.308 +nsGtkIMModule::OnFocusWindow(nsWindow* aWindow) 1.309 +{ 1.310 + if (MOZ_UNLIKELY(IsDestroyed())) { 1.311 + return; 1.312 + } 1.313 + 1.314 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.315 + ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p", 1.316 + this, aWindow, mLastFocusedWindow)); 1.317 + mLastFocusedWindow = aWindow; 1.318 + Focus(); 1.319 +} 1.320 + 1.321 +void 1.322 +nsGtkIMModule::OnBlurWindow(nsWindow* aWindow) 1.323 +{ 1.324 + if (MOZ_UNLIKELY(IsDestroyed())) { 1.325 + return; 1.326 + } 1.327 + 1.328 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.329 + ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s", 1.330 + this, aWindow, mLastFocusedWindow, mIsIMFocused ? "YES" : "NO")); 1.331 + 1.332 + if (!mIsIMFocused || mLastFocusedWindow != aWindow) { 1.333 + return; 1.334 + } 1.335 + 1.336 + Blur(); 1.337 +} 1.338 + 1.339 +bool 1.340 +nsGtkIMModule::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent, 1.341 + bool aKeyDownEventWasSent /* = false */) 1.342 +{ 1.343 + NS_PRECONDITION(aEvent, "aEvent must be non-null"); 1.344 + 1.345 + if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) { 1.346 + return false; 1.347 + } 1.348 + 1.349 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.350 + ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s", 1.351 + this, aCaller, aKeyDownEventWasSent ? "TRUE" : "FALSE")); 1.352 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.353 + (" aEvent: type=%s, keyval=%s, unicode=0x%X", 1.354 + aEvent->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" : 1.355 + aEvent->type == GDK_KEY_RELEASE ? "GDK_KEY_RELEASE" : "Unknown", 1.356 + gdk_keyval_name(aEvent->keyval), 1.357 + gdk_keyval_to_unicode(aEvent->keyval))); 1.358 + 1.359 + if (aCaller != mLastFocusedWindow) { 1.360 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.361 + (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p", 1.362 + mLastFocusedWindow)); 1.363 + return false; 1.364 + } 1.365 + 1.366 + GtkIMContext* im = GetContext(); 1.367 + if (MOZ_UNLIKELY(!im)) { 1.368 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.369 + (" FAILED, there are no context")); 1.370 + return false; 1.371 + } 1.372 + 1.373 + mKeyDownEventWasSent = aKeyDownEventWasSent; 1.374 + mFilterKeyEvent = true; 1.375 + mProcessingKeyEvent = aEvent; 1.376 + gboolean isFiltered = gtk_im_context_filter_keypress(im, aEvent); 1.377 + mProcessingKeyEvent = nullptr; 1.378 + 1.379 + // We filter the key event if the event was not committed (because 1.380 + // it's probably part of a composition) or if the key event was 1.381 + // committed _and_ changed. This way we still let key press 1.382 + // events go through as simple key press events instead of 1.383 + // composed characters. 1.384 + bool filterThisEvent = isFiltered && mFilterKeyEvent; 1.385 + 1.386 + if (IsComposing() && !isFiltered) { 1.387 + if (aEvent->type == GDK_KEY_PRESS) { 1.388 + if (!mDispatchedCompositionString.IsEmpty()) { 1.389 + // If there is composition string, we shouldn't dispatch 1.390 + // any keydown events during composition. 1.391 + filterThisEvent = true; 1.392 + } else { 1.393 + // A Hangul input engine for SCIM doesn't emit preedit_end 1.394 + // signal even when composition string becomes empty. On the 1.395 + // other hand, we should allow to make composition with empty 1.396 + // string for other languages because there *might* be such 1.397 + // IM. For compromising this issue, we should dispatch 1.398 + // compositionend event, however, we don't need to reset IM 1.399 + // actually. 1.400 + CommitCompositionBy(EmptyString()); 1.401 + filterThisEvent = false; 1.402 + } 1.403 + } else { 1.404 + // Key release event may not be consumed by IM, however, we 1.405 + // shouldn't dispatch any keyup event during composition. 1.406 + filterThisEvent = true; 1.407 + } 1.408 + } 1.409 + 1.410 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.411 + (" filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)", 1.412 + filterThisEvent ? "TRUE" : "FALSE", isFiltered ? "YES" : "NO", 1.413 + mFilterKeyEvent ? "YES" : "NO")); 1.414 + 1.415 + return filterThisEvent; 1.416 +} 1.417 + 1.418 +void 1.419 +nsGtkIMModule::OnFocusChangeInGecko(bool aFocus) 1.420 +{ 1.421 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.422 + ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, " 1.423 + "mCompositionState=%s, mIsIMFocused=%s, " 1.424 + "mIgnoreNativeCompositionEvent=%s", 1.425 + this, aFocus ? "YES" : "NO", GetCompositionStateName(), 1.426 + mIsIMFocused ? "YES" : "NO", 1.427 + mIgnoreNativeCompositionEvent ? "YES" : "NO")); 1.428 + 1.429 + // We shouldn't carry over the removed string to another editor. 1.430 + mSelectedString.Truncate(); 1.431 + 1.432 + if (aFocus) { 1.433 + // If we failed to commit forcedely in previous focused editor, 1.434 + // we should reopen the gate for native signals in new focused editor. 1.435 + mIgnoreNativeCompositionEvent = false; 1.436 + } 1.437 +} 1.438 + 1.439 +void 1.440 +nsGtkIMModule::ResetIME() 1.441 +{ 1.442 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.443 + ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s", 1.444 + this, GetCompositionStateName(), mIsIMFocused ? "YES" : "NO")); 1.445 + 1.446 + GtkIMContext *im = GetContext(); 1.447 + if (MOZ_UNLIKELY(!im)) { 1.448 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.449 + (" FAILED, there are no context")); 1.450 + return; 1.451 + } 1.452 + 1.453 + mIgnoreNativeCompositionEvent = true; 1.454 + gtk_im_context_reset(im); 1.455 +} 1.456 + 1.457 +nsresult 1.458 +nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller) 1.459 +{ 1.460 + if (MOZ_UNLIKELY(IsDestroyed())) { 1.461 + return NS_OK; 1.462 + } 1.463 + 1.464 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.465 + ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, " 1.466 + "mCompositionState=%s", 1.467 + this, aCaller, GetCompositionStateName())); 1.468 + 1.469 + if (aCaller != mLastFocusedWindow) { 1.470 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.471 + (" WARNING: the caller isn't focused window, mLastFocusedWindow=%p", 1.472 + mLastFocusedWindow)); 1.473 + return NS_OK; 1.474 + } 1.475 + 1.476 + if (!IsComposing()) { 1.477 + return NS_OK; 1.478 + } 1.479 + 1.480 + // XXX We should commit composition ourselves temporary... 1.481 + ResetIME(); 1.482 + CommitCompositionBy(mDispatchedCompositionString); 1.483 + 1.484 + return NS_OK; 1.485 +} 1.486 + 1.487 +nsresult 1.488 +nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller) 1.489 +{ 1.490 + if (MOZ_UNLIKELY(IsDestroyed())) { 1.491 + return NS_OK; 1.492 + } 1.493 + 1.494 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.495 + ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p", 1.496 + this, aCaller)); 1.497 + 1.498 + if (aCaller != mLastFocusedWindow) { 1.499 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.500 + (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p", 1.501 + mLastFocusedWindow)); 1.502 + return NS_OK; 1.503 + } 1.504 + 1.505 + if (!IsComposing()) { 1.506 + return NS_OK; 1.507 + } 1.508 + 1.509 + GtkIMContext *im = GetContext(); 1.510 + if (MOZ_UNLIKELY(!im)) { 1.511 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.512 + (" FAILED, there are no context")); 1.513 + return NS_OK; 1.514 + } 1.515 + 1.516 + ResetIME(); 1.517 + CommitCompositionBy(EmptyString()); 1.518 + 1.519 + return NS_OK; 1.520 +} 1.521 + 1.522 +void 1.523 +nsGtkIMModule::OnUpdateComposition(void) 1.524 +{ 1.525 + if (MOZ_UNLIKELY(IsDestroyed())) { 1.526 + return; 1.527 + } 1.528 + 1.529 + SetCursorPosition(mCompositionTargetOffset); 1.530 +} 1.531 + 1.532 +void 1.533 +nsGtkIMModule::SetInputContext(nsWindow* aCaller, 1.534 + const InputContext* aContext, 1.535 + const InputContextAction* aAction) 1.536 +{ 1.537 + if (MOZ_UNLIKELY(IsDestroyed())) { 1.538 + return; 1.539 + } 1.540 + 1.541 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.542 + ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s", 1.543 + this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled), 1.544 + NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get())); 1.545 + 1.546 + if (aCaller != mLastFocusedWindow) { 1.547 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.548 + (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p", 1.549 + mLastFocusedWindow)); 1.550 + return; 1.551 + } 1.552 + 1.553 + if (!mContext) { 1.554 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.555 + (" FAILED, there are no context")); 1.556 + return; 1.557 + } 1.558 + 1.559 + 1.560 + if (sLastFocusedModule != this) { 1.561 + mInputContext = *aContext; 1.562 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.563 + (" SUCCEEDED, but we're not active")); 1.564 + return; 1.565 + } 1.566 + 1.567 + bool changingEnabledState = 1.568 + aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled || 1.569 + aContext->mHTMLInputType != mInputContext.mHTMLInputType; 1.570 + 1.571 + // Release current IME focus if IME is enabled. 1.572 + if (changingEnabledState && IsEditable()) { 1.573 + CommitIMEComposition(mLastFocusedWindow); 1.574 + Blur(); 1.575 + } 1.576 + 1.577 + mInputContext = *aContext; 1.578 + 1.579 + if (changingEnabledState) { 1.580 +#if (MOZ_WIDGET_GTK == 3) 1.581 + static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0); 1.582 + if (sInputPurposeSupported && IsEditable()) { 1.583 + GtkIMContext* context = GetContext(); 1.584 + if (context) { 1.585 + GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM; 1.586 + const nsString& inputType = mInputContext.mHTMLInputType; 1.587 + // Password case has difficult issue. Desktop IMEs disable 1.588 + // composition if input-purpose is password. 1.589 + // For disabling IME on |ime-mode: disabled;|, we need to check 1.590 + // mEnabled value instead of inputType value. This hack also 1.591 + // enables composition on 1.592 + // <input type="password" style="ime-mode: enabled;">. 1.593 + // This is right behavior of ime-mode on desktop. 1.594 + // 1.595 + // On the other hand, IME for tablet devices may provide a 1.596 + // specific software keyboard for password field. If so, 1.597 + // the behavior might look strange on both: 1.598 + // <input type="text" style="ime-mode: disabled;"> 1.599 + // <input type="password" style="ime-mode: enabled;"> 1.600 + // 1.601 + // Temporarily, we should focus on desktop environment for now. 1.602 + // I.e., let's ignore tablet devices for now. When somebody 1.603 + // reports actual trouble on tablet devices, we should try to 1.604 + // look for a way to solve actual problem. 1.605 + if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { 1.606 + purpose = GTK_INPUT_PURPOSE_PASSWORD; 1.607 + } else if (inputType.EqualsLiteral("email")) { 1.608 + purpose = GTK_INPUT_PURPOSE_EMAIL; 1.609 + } else if (inputType.EqualsLiteral("url")) { 1.610 + purpose = GTK_INPUT_PURPOSE_URL; 1.611 + } else if (inputType.EqualsLiteral("tel")) { 1.612 + purpose = GTK_INPUT_PURPOSE_PHONE; 1.613 + } else if (inputType.EqualsLiteral("number")) { 1.614 + purpose = GTK_INPUT_PURPOSE_NUMBER; 1.615 + } 1.616 + 1.617 + g_object_set(context, "input-purpose", purpose, nullptr); 1.618 + } 1.619 + } 1.620 +#endif // #if (MOZ_WIDGET_GTK == 3) 1.621 + 1.622 + // Even when aState is not enabled state, we need to set IME focus. 1.623 + // Because some IMs are updating the status bar of them at this time. 1.624 + // Be aware, don't use aWindow here because this method shouldn't move 1.625 + // focus actually. 1.626 + Focus(); 1.627 + 1.628 + // XXX Should we call Blur() when it's not editable? E.g., it might be 1.629 + // better to close VKB automatically. 1.630 + } 1.631 +} 1.632 + 1.633 +InputContext 1.634 +nsGtkIMModule::GetInputContext() 1.635 +{ 1.636 + mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; 1.637 + return mInputContext; 1.638 +} 1.639 + 1.640 +/* static */ 1.641 +bool 1.642 +nsGtkIMModule::IsVirtualKeyboardOpened() 1.643 +{ 1.644 + return false; 1.645 +} 1.646 + 1.647 +GtkIMContext* 1.648 +nsGtkIMModule::GetContext() 1.649 +{ 1.650 + if (IsEnabled()) { 1.651 + return mContext; 1.652 + } 1.653 + if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) { 1.654 + return mSimpleContext; 1.655 + } 1.656 + return mDummyContext; 1.657 +} 1.658 + 1.659 +bool 1.660 +nsGtkIMModule::IsEnabled() 1.661 +{ 1.662 + return mInputContext.mIMEState.mEnabled == IMEState::ENABLED || 1.663 + mInputContext.mIMEState.mEnabled == IMEState::PLUGIN || 1.664 + (!sUseSimpleContext && 1.665 + mInputContext.mIMEState.mEnabled == IMEState::PASSWORD); 1.666 +} 1.667 + 1.668 +bool 1.669 +nsGtkIMModule::IsEditable() 1.670 +{ 1.671 + return mInputContext.mIMEState.mEnabled == IMEState::ENABLED || 1.672 + mInputContext.mIMEState.mEnabled == IMEState::PLUGIN || 1.673 + mInputContext.mIMEState.mEnabled == IMEState::PASSWORD; 1.674 +} 1.675 + 1.676 +void 1.677 +nsGtkIMModule::Focus() 1.678 +{ 1.679 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.680 + ("GtkIMModule(%p): Focus, sLastFocusedModule=%p", 1.681 + this, sLastFocusedModule)); 1.682 + 1.683 + if (mIsIMFocused) { 1.684 + NS_ASSERTION(sLastFocusedModule == this, 1.685 + "We're not active, but the IM was focused?"); 1.686 + return; 1.687 + } 1.688 + 1.689 + GtkIMContext *im = GetContext(); 1.690 + if (!im) { 1.691 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.692 + (" FAILED, there are no context")); 1.693 + return; 1.694 + } 1.695 + 1.696 + if (sLastFocusedModule && sLastFocusedModule != this) { 1.697 + sLastFocusedModule->Blur(); 1.698 + } 1.699 + 1.700 + sLastFocusedModule = this; 1.701 + 1.702 + gtk_im_context_focus_in(im); 1.703 + mIsIMFocused = true; 1.704 + 1.705 + if (!IsEnabled()) { 1.706 + // We should release IME focus for uim and scim. 1.707 + // These IMs are using snooper that is released at losing focus. 1.708 + Blur(); 1.709 + } 1.710 +} 1.711 + 1.712 +void 1.713 +nsGtkIMModule::Blur() 1.714 +{ 1.715 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.716 + ("GtkIMModule(%p): Blur, mIsIMFocused=%s", 1.717 + this, mIsIMFocused ? "YES" : "NO")); 1.718 + 1.719 + if (!mIsIMFocused) { 1.720 + return; 1.721 + } 1.722 + 1.723 + GtkIMContext *im = GetContext(); 1.724 + if (!im) { 1.725 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.726 + (" FAILED, there are no context")); 1.727 + return; 1.728 + } 1.729 + 1.730 + gtk_im_context_focus_out(im); 1.731 + mIsIMFocused = false; 1.732 +} 1.733 + 1.734 +/* static */ 1.735 +void 1.736 +nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext, 1.737 + nsGtkIMModule* aModule) 1.738 +{ 1.739 + aModule->OnStartCompositionNative(aContext); 1.740 +} 1.741 + 1.742 +void 1.743 +nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext) 1.744 +{ 1.745 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.746 + ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p", 1.747 + this, aContext)); 1.748 + 1.749 + // See bug 472635, we should do nothing if IM context doesn't match. 1.750 + if (GetContext() != aContext) { 1.751 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.752 + (" FAILED, given context doesn't match, GetContext()=%p", 1.753 + GetContext())); 1.754 + return; 1.755 + } 1.756 + 1.757 + if (!DispatchCompositionStart()) { 1.758 + return; 1.759 + } 1.760 + mCompositionTargetOffset = mCompositionStart; 1.761 +} 1.762 + 1.763 +/* static */ 1.764 +void 1.765 +nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext, 1.766 + nsGtkIMModule* aModule) 1.767 +{ 1.768 + aModule->OnEndCompositionNative(aContext); 1.769 +} 1.770 + 1.771 +void 1.772 +nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext) 1.773 +{ 1.774 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.775 + ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p", 1.776 + this, aContext)); 1.777 + 1.778 + // See bug 472635, we should do nothing if IM context doesn't match. 1.779 + if (GetContext() != aContext) { 1.780 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.781 + (" FAILED, given context doesn't match, GetContext()=%p", 1.782 + GetContext())); 1.783 + return; 1.784 + } 1.785 + 1.786 + bool shouldIgnoreThisEvent = ShouldIgnoreNativeCompositionEvent(); 1.787 + 1.788 + // Finish the cancelling mode here rather than DispatchCompositionEnd() 1.789 + // because DispatchCompositionEnd() is called ourselves when we need to 1.790 + // commit the composition string *before* the focus moves completely. 1.791 + // Note that the native commit can be fired *after* ResetIME(). 1.792 + mIgnoreNativeCompositionEvent = false; 1.793 + 1.794 + if (!IsComposing() || shouldIgnoreThisEvent) { 1.795 + // If we already handled the commit event, we should do nothing here. 1.796 + return; 1.797 + } 1.798 + 1.799 + // Be aware, widget can be gone 1.800 + DispatchCompositionEnd(); 1.801 +} 1.802 + 1.803 +/* static */ 1.804 +void 1.805 +nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext, 1.806 + nsGtkIMModule* aModule) 1.807 +{ 1.808 + aModule->OnChangeCompositionNative(aContext); 1.809 +} 1.810 + 1.811 +void 1.812 +nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext) 1.813 +{ 1.814 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.815 + ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p", 1.816 + this, aContext)); 1.817 + 1.818 + // See bug 472635, we should do nothing if IM context doesn't match. 1.819 + if (GetContext() != aContext) { 1.820 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.821 + (" FAILED, given context doesn't match, GetContext()=%p", 1.822 + GetContext())); 1.823 + return; 1.824 + } 1.825 + 1.826 + if (ShouldIgnoreNativeCompositionEvent()) { 1.827 + return; 1.828 + } 1.829 + 1.830 + nsAutoString compositionString; 1.831 + GetCompositionString(compositionString); 1.832 + if (!IsComposing() && compositionString.IsEmpty()) { 1.833 + mDispatchedCompositionString.Truncate(); 1.834 + return; // Don't start the composition with empty string. 1.835 + } 1.836 + 1.837 + // Be aware, widget can be gone 1.838 + DispatchTextEvent(compositionString, false); 1.839 +} 1.840 + 1.841 +/* static */ 1.842 +gboolean 1.843 +nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext *aContext, 1.844 + nsGtkIMModule *aModule) 1.845 +{ 1.846 + return aModule->OnRetrieveSurroundingNative(aContext); 1.847 +} 1.848 + 1.849 +gboolean 1.850 +nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext *aContext) 1.851 +{ 1.852 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.853 + ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p", 1.854 + this, aContext, GetContext())); 1.855 + 1.856 + if (GetContext() != aContext) { 1.857 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.858 + (" FAILED, given context doesn't match, GetContext()=%p", 1.859 + GetContext())); 1.860 + return FALSE; 1.861 + } 1.862 + 1.863 + nsAutoString uniStr; 1.864 + uint32_t cursorPos; 1.865 + if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) { 1.866 + return FALSE; 1.867 + } 1.868 + 1.869 + NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos)); 1.870 + uint32_t cursorPosInUTF8 = utf8Str.Length(); 1.871 + AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str); 1.872 + gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(), 1.873 + cursorPosInUTF8); 1.874 + return TRUE; 1.875 +} 1.876 + 1.877 +/* static */ 1.878 +gboolean 1.879 +nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext *aContext, 1.880 + gint aOffset, 1.881 + gint aNChars, 1.882 + nsGtkIMModule *aModule) 1.883 +{ 1.884 + return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars); 1.885 +} 1.886 + 1.887 +gboolean 1.888 +nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext *aContext, 1.889 + gint aOffset, 1.890 + gint aNChars) 1.891 +{ 1.892 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.893 + ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p", 1.894 + this, aContext, GetContext())); 1.895 + 1.896 + if (GetContext() != aContext) { 1.897 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.898 + (" FAILED, given context doesn't match, GetContext()=%p", 1.899 + GetContext())); 1.900 + return FALSE; 1.901 + } 1.902 + 1.903 + if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) { 1.904 + return TRUE; 1.905 + } 1.906 + 1.907 + // failed 1.908 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.909 + (" FAILED, cannot delete text")); 1.910 + return FALSE; 1.911 +} 1.912 + 1.913 +/* static */ 1.914 +void 1.915 +nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext, 1.916 + const gchar *aString, 1.917 + nsGtkIMModule* aModule) 1.918 +{ 1.919 + aModule->OnCommitCompositionNative(aContext, aString); 1.920 +} 1.921 + 1.922 +void 1.923 +nsGtkIMModule::OnCommitCompositionNative(GtkIMContext *aContext, 1.924 + const gchar *aUTF8Char) 1.925 +{ 1.926 + const gchar emptyStr = 0; 1.927 + const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr; 1.928 + 1.929 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.930 + ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"", 1.931 + this, aContext, GetContext(), commitString)); 1.932 + 1.933 + // See bug 472635, we should do nothing if IM context doesn't match. 1.934 + if (GetContext() != aContext) { 1.935 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.936 + (" FAILED, given context doesn't match, GetContext()=%p", 1.937 + GetContext())); 1.938 + return; 1.939 + } 1.940 + 1.941 + // If we are not in composition and committing with empty string, 1.942 + // we need to do nothing because if we continued to handle this 1.943 + // signal, we would dispatch compositionstart, text, compositionend 1.944 + // events with empty string. Of course, they are unnecessary events 1.945 + // for Web applications and our editor. 1.946 + if (!IsComposing() && !commitString[0]) { 1.947 + return; 1.948 + } 1.949 + 1.950 + if (ShouldIgnoreNativeCompositionEvent()) { 1.951 + return; 1.952 + } 1.953 + 1.954 + // If IME doesn't change their keyevent that generated this commit, 1.955 + // don't send it through XIM - just send it as a normal key press 1.956 + // event. 1.957 + if (!IsComposing() && mProcessingKeyEvent) { 1.958 + char keyval_utf8[8]; /* should have at least 6 bytes of space */ 1.959 + gint keyval_utf8_len; 1.960 + guint32 keyval_unicode; 1.961 + 1.962 + keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval); 1.963 + keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8); 1.964 + keyval_utf8[keyval_utf8_len] = '\0'; 1.965 + 1.966 + if (!strcmp(commitString, keyval_utf8)) { 1.967 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.968 + ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event", 1.969 + this)); 1.970 + mFilterKeyEvent = false; 1.971 + return; 1.972 + } 1.973 + } 1.974 + 1.975 + NS_ConvertUTF8toUTF16 str(commitString); 1.976 + CommitCompositionBy(str); // Be aware, widget can be gone 1.977 +} 1.978 + 1.979 +bool 1.980 +nsGtkIMModule::CommitCompositionBy(const nsAString& aString) 1.981 +{ 1.982 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.983 + ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", " 1.984 + "mDispatchedCompositionString=\"%s\"", 1.985 + this, NS_ConvertUTF16toUTF8(aString).get(), 1.986 + NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get())); 1.987 + 1.988 + if (!DispatchTextEvent(aString, true)) { 1.989 + return false; 1.990 + } 1.991 + // We should dispatch the compositionend event here because some IMEs 1.992 + // might not fire "preedit_end" native event. 1.993 + return DispatchCompositionEnd(); // Be aware, widget can be gone 1.994 +} 1.995 + 1.996 +void 1.997 +nsGtkIMModule::GetCompositionString(nsAString &aCompositionString) 1.998 +{ 1.999 + gchar *preedit_string; 1.1000 + gint cursor_pos; 1.1001 + PangoAttrList *feedback_list; 1.1002 + gtk_im_context_get_preedit_string(GetContext(), &preedit_string, 1.1003 + &feedback_list, &cursor_pos); 1.1004 + if (preedit_string && *preedit_string) { 1.1005 + CopyUTF8toUTF16(preedit_string, aCompositionString); 1.1006 + } else { 1.1007 + aCompositionString.Truncate(); 1.1008 + } 1.1009 + 1.1010 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1011 + ("GtkIMModule(%p): GetCompositionString, result=\"%s\"", 1.1012 + this, preedit_string)); 1.1013 + 1.1014 + pango_attr_list_unref(feedback_list); 1.1015 + g_free(preedit_string); 1.1016 +} 1.1017 + 1.1018 +bool 1.1019 +nsGtkIMModule::DispatchCompositionStart() 1.1020 +{ 1.1021 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1022 + ("GtkIMModule(%p): DispatchCompositionStart", this)); 1.1023 + 1.1024 + if (IsComposing()) { 1.1025 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1026 + (" WARNING, we're already in composition")); 1.1027 + return true; 1.1028 + } 1.1029 + 1.1030 + if (!mLastFocusedWindow) { 1.1031 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1032 + (" FAILED, there are no focused window in this module")); 1.1033 + return false; 1.1034 + } 1.1035 + 1.1036 + nsEventStatus status; 1.1037 + WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, 1.1038 + mLastFocusedWindow); 1.1039 + InitEvent(selection); 1.1040 + mLastFocusedWindow->DispatchEvent(&selection, status); 1.1041 + 1.1042 + if (!selection.mSucceeded || selection.mReply.mOffset == UINT32_MAX) { 1.1043 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1044 + (" FAILED, cannot query the selection offset")); 1.1045 + return false; 1.1046 + } 1.1047 + 1.1048 + // XXX The composition start point might be changed by composition events 1.1049 + // even though we strongly hope it doesn't happen. 1.1050 + // Every composition event should have the start offset for the result 1.1051 + // because it may high cost if we query the offset every time. 1.1052 + mCompositionStart = selection.mReply.mOffset; 1.1053 + mDispatchedCompositionString.Truncate(); 1.1054 + 1.1055 + if (mProcessingKeyEvent && !mKeyDownEventWasSent && 1.1056 + mProcessingKeyEvent->type == GDK_KEY_PRESS) { 1.1057 + // If this composition is started by a native keydown event, we need to 1.1058 + // dispatch our keydown event here (before composition start). 1.1059 + nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow; 1.1060 + bool isCancelled; 1.1061 + mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent, 1.1062 + &isCancelled); 1.1063 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1064 + (" keydown event is dispatched")); 1.1065 + if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() || 1.1066 + kungFuDeathGrip != mLastFocusedWindow) { 1.1067 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1068 + (" NOTE, the focused widget was destroyed/changed by keydown event")); 1.1069 + return false; 1.1070 + } 1.1071 + } 1.1072 + 1.1073 + if (mIgnoreNativeCompositionEvent) { 1.1074 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1075 + (" WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset")); 1.1076 + mIgnoreNativeCompositionEvent = false; 1.1077 + } 1.1078 + 1.1079 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1080 + (" mCompositionStart=%u", mCompositionStart)); 1.1081 + mCompositionState = eCompositionState_CompositionStartDispatched; 1.1082 + WidgetCompositionEvent compEvent(true, NS_COMPOSITION_START, 1.1083 + mLastFocusedWindow); 1.1084 + InitEvent(compEvent); 1.1085 + nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow; 1.1086 + mLastFocusedWindow->DispatchEvent(&compEvent, status); 1.1087 + if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() || 1.1088 + kungFuDeathGrip != mLastFocusedWindow) { 1.1089 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1090 + (" NOTE, the focused widget was destroyed/changed by compositionstart event")); 1.1091 + return false; 1.1092 + } 1.1093 + 1.1094 + return true; 1.1095 +} 1.1096 + 1.1097 +bool 1.1098 +nsGtkIMModule::DispatchCompositionEnd() 1.1099 +{ 1.1100 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1101 + ("GtkIMModule(%p): DispatchCompositionEnd, " 1.1102 + "mDispatchedCompositionString=\"%s\"", 1.1103 + this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get())); 1.1104 + 1.1105 + if (!IsComposing()) { 1.1106 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1107 + (" WARNING, we have alrady finished the composition")); 1.1108 + return false; 1.1109 + } 1.1110 + 1.1111 + if (!mLastFocusedWindow) { 1.1112 + mDispatchedCompositionString.Truncate(); 1.1113 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1114 + (" FAILED, there are no focused window in this module")); 1.1115 + return false; 1.1116 + } 1.1117 + 1.1118 + WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END, 1.1119 + mLastFocusedWindow); 1.1120 + InitEvent(compEvent); 1.1121 + compEvent.data = mDispatchedCompositionString; 1.1122 + nsEventStatus status; 1.1123 + nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow; 1.1124 + mLastFocusedWindow->DispatchEvent(&compEvent, status); 1.1125 + mCompositionState = eCompositionState_NotComposing; 1.1126 + mCompositionStart = UINT32_MAX; 1.1127 + mCompositionTargetOffset = UINT32_MAX; 1.1128 + mDispatchedCompositionString.Truncate(); 1.1129 + if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() || 1.1130 + kungFuDeathGrip != mLastFocusedWindow) { 1.1131 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1132 + (" NOTE, the focused widget was destroyed/changed by compositionend event")); 1.1133 + return false; 1.1134 + } 1.1135 + 1.1136 + return true; 1.1137 +} 1.1138 + 1.1139 +bool 1.1140 +nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString, 1.1141 + bool aIsCommit) 1.1142 +{ 1.1143 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1144 + ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s", 1.1145 + this, aIsCommit ? "TRUE" : "FALSE")); 1.1146 + 1.1147 + if (!mLastFocusedWindow) { 1.1148 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1149 + (" FAILED, there are no focused window in this module")); 1.1150 + return false; 1.1151 + } 1.1152 + 1.1153 + if (!IsComposing()) { 1.1154 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1155 + (" The composition wasn't started, force starting...")); 1.1156 + nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow; 1.1157 + if (!DispatchCompositionStart()) { 1.1158 + return false; 1.1159 + } 1.1160 + } 1.1161 + 1.1162 + nsEventStatus status; 1.1163 + nsRefPtr<nsWindow> lastFocusedWindow = mLastFocusedWindow; 1.1164 + 1.1165 + if (aCompositionString != mDispatchedCompositionString) { 1.1166 + WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE, 1.1167 + mLastFocusedWindow); 1.1168 + InitEvent(compositionUpdate); 1.1169 + compositionUpdate.data = aCompositionString; 1.1170 + mDispatchedCompositionString = aCompositionString; 1.1171 + mLastFocusedWindow->DispatchEvent(&compositionUpdate, status); 1.1172 + if (lastFocusedWindow->IsDestroyed() || 1.1173 + lastFocusedWindow != mLastFocusedWindow) { 1.1174 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1175 + (" NOTE, the focused widget was destroyed/changed by compositionupdate")); 1.1176 + return false; 1.1177 + } 1.1178 + } 1.1179 + 1.1180 + // Store the selected string which will be removed by following text event. 1.1181 + if (mCompositionState == eCompositionState_CompositionStartDispatched) { 1.1182 + // XXX We should assume, for now, any web applications don't change 1.1183 + // selection at handling this text event. 1.1184 + WidgetQueryContentEvent querySelectedTextEvent(true, 1.1185 + NS_QUERY_SELECTED_TEXT, 1.1186 + mLastFocusedWindow); 1.1187 + mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status); 1.1188 + if (querySelectedTextEvent.mSucceeded) { 1.1189 + mSelectedString = querySelectedTextEvent.mReply.mString; 1.1190 + mCompositionStart = querySelectedTextEvent.mReply.mOffset; 1.1191 + } 1.1192 + } 1.1193 + 1.1194 + WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mLastFocusedWindow); 1.1195 + InitEvent(textEvent); 1.1196 + 1.1197 + uint32_t targetOffset = mCompositionStart; 1.1198 + 1.1199 + if (!aIsCommit) { 1.1200 + // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString 1.1201 + // has been updated already. 1.1202 + textEvent.mRanges = CreateTextRangeArray(); 1.1203 + targetOffset += textEvent.mRanges->TargetClauseOffset(); 1.1204 + } 1.1205 + 1.1206 + textEvent.theText = mDispatchedCompositionString.get(); 1.1207 + 1.1208 + mCompositionState = aIsCommit ? 1.1209 + eCompositionState_CommitTextEventDispatched : 1.1210 + eCompositionState_TextEventDispatched; 1.1211 + 1.1212 + mLastFocusedWindow->DispatchEvent(&textEvent, status); 1.1213 + if (lastFocusedWindow->IsDestroyed() || 1.1214 + lastFocusedWindow != mLastFocusedWindow) { 1.1215 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1216 + (" NOTE, the focused widget was destroyed/changed by text event")); 1.1217 + return false; 1.1218 + } 1.1219 + 1.1220 + // We cannot call SetCursorPosition for e10s-aware. 1.1221 + // DispatchEvent is async on e10s, so composition rect isn't updated now 1.1222 + // on tab parent. 1.1223 + mCompositionTargetOffset = targetOffset; 1.1224 + 1.1225 + return true; 1.1226 +} 1.1227 + 1.1228 +already_AddRefed<TextRangeArray> 1.1229 +nsGtkIMModule::CreateTextRangeArray() 1.1230 +{ 1.1231 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1232 + ("GtkIMModule(%p): CreateTextRangeArray", this)); 1.1233 + 1.1234 + nsRefPtr<TextRangeArray> textRangeArray = new TextRangeArray(); 1.1235 + 1.1236 + gchar *preedit_string; 1.1237 + gint cursor_pos; 1.1238 + PangoAttrList *feedback_list; 1.1239 + gtk_im_context_get_preedit_string(GetContext(), &preedit_string, 1.1240 + &feedback_list, &cursor_pos); 1.1241 + if (!preedit_string || !*preedit_string) { 1.1242 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1243 + (" preedit_string is null")); 1.1244 + pango_attr_list_unref(feedback_list); 1.1245 + g_free(preedit_string); 1.1246 + return textRangeArray.forget(); 1.1247 + } 1.1248 + 1.1249 + PangoAttrIterator* iter; 1.1250 + iter = pango_attr_list_get_iterator(feedback_list); 1.1251 + if (!iter) { 1.1252 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1253 + (" FAILED, iterator couldn't be allocated")); 1.1254 + pango_attr_list_unref(feedback_list); 1.1255 + g_free(preedit_string); 1.1256 + return textRangeArray.forget(); 1.1257 + } 1.1258 + 1.1259 + /* 1.1260 + * Depend on gtk2's implementation on XIM support. 1.1261 + * In aFeedback got from gtk2, there are only three types of data: 1.1262 + * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND. 1.1263 + * Corresponding to XIMUnderline, XIMReverse. 1.1264 + * Don't take PANGO_ATTR_BACKGROUND into account, since 1.1265 + * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always 1.1266 + * a couple. 1.1267 + */ 1.1268 + do { 1.1269 + PangoAttribute* attrUnderline = 1.1270 + pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); 1.1271 + PangoAttribute* attrForeground = 1.1272 + pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND); 1.1273 + if (!attrUnderline && !attrForeground) { 1.1274 + continue; 1.1275 + } 1.1276 + 1.1277 + // Get the range of the current attribute(s) 1.1278 + gint start, end; 1.1279 + pango_attr_iterator_range(iter, &start, &end); 1.1280 + 1.1281 + TextRange range; 1.1282 + // XIMReverse | XIMUnderline 1.1283 + if (attrUnderline && attrForeground) { 1.1284 + range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT; 1.1285 + } 1.1286 + // XIMUnderline 1.1287 + else if (attrUnderline) { 1.1288 + range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT; 1.1289 + } 1.1290 + // XIMReverse 1.1291 + else if (attrForeground) { 1.1292 + range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT; 1.1293 + } else { 1.1294 + range.mRangeType = NS_TEXTRANGE_RAWINPUT; 1.1295 + } 1.1296 + 1.1297 + gunichar2* uniStr = nullptr; 1.1298 + if (start == 0) { 1.1299 + range.mStartOffset = 0; 1.1300 + } else { 1.1301 + glong uniStrLen; 1.1302 + uniStr = g_utf8_to_utf16(preedit_string, start, 1.1303 + nullptr, &uniStrLen, nullptr); 1.1304 + if (uniStr) { 1.1305 + range.mStartOffset = uniStrLen; 1.1306 + g_free(uniStr); 1.1307 + uniStr = nullptr; 1.1308 + } 1.1309 + } 1.1310 + 1.1311 + glong uniStrLen; 1.1312 + uniStr = g_utf8_to_utf16(preedit_string + start, end - start, 1.1313 + nullptr, &uniStrLen, nullptr); 1.1314 + if (!uniStr) { 1.1315 + range.mEndOffset = range.mStartOffset; 1.1316 + } else { 1.1317 + range.mEndOffset = range.mStartOffset + uniStrLen; 1.1318 + g_free(uniStr); 1.1319 + uniStr = nullptr; 1.1320 + } 1.1321 + 1.1322 + textRangeArray->AppendElement(range); 1.1323 + 1.1324 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1325 + (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s", 1.1326 + range.mStartOffset, range.mEndOffset, 1.1327 + GetRangeTypeName(range.mRangeType))); 1.1328 + } while (pango_attr_iterator_next(iter)); 1.1329 + 1.1330 + TextRange range; 1.1331 + if (cursor_pos < 0) { 1.1332 + range.mStartOffset = 0; 1.1333 + } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) { 1.1334 + range.mStartOffset = mDispatchedCompositionString.Length(); 1.1335 + } else { 1.1336 + range.mStartOffset = uint32_t(cursor_pos); 1.1337 + } 1.1338 + range.mEndOffset = range.mStartOffset; 1.1339 + range.mRangeType = NS_TEXTRANGE_CARETPOSITION; 1.1340 + textRangeArray->AppendElement(range); 1.1341 + 1.1342 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1343 + (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s", 1.1344 + range.mStartOffset, range.mEndOffset, 1.1345 + GetRangeTypeName(range.mRangeType))); 1.1346 + 1.1347 + pango_attr_iterator_destroy(iter); 1.1348 + pango_attr_list_unref(feedback_list); 1.1349 + g_free(preedit_string); 1.1350 + 1.1351 + return textRangeArray.forget(); 1.1352 +} 1.1353 + 1.1354 +void 1.1355 +nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset) 1.1356 +{ 1.1357 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1358 + ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u", 1.1359 + this, aTargetOffset)); 1.1360 + 1.1361 + if (aTargetOffset == UINT32_MAX) { 1.1362 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1363 + (" FAILED, aTargetOffset is wrong offset")); 1.1364 + return; 1.1365 + } 1.1366 + 1.1367 + if (!mLastFocusedWindow) { 1.1368 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1369 + (" FAILED, there are no focused window")); 1.1370 + return; 1.1371 + } 1.1372 + 1.1373 + GtkIMContext *im = GetContext(); 1.1374 + if (!im) { 1.1375 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1376 + (" FAILED, there are no context")); 1.1377 + return; 1.1378 + } 1.1379 + 1.1380 + WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, 1.1381 + mLastFocusedWindow); 1.1382 + charRect.InitForQueryTextRect(aTargetOffset, 1); 1.1383 + InitEvent(charRect); 1.1384 + nsEventStatus status; 1.1385 + mLastFocusedWindow->DispatchEvent(&charRect, status); 1.1386 + if (!charRect.mSucceeded) { 1.1387 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1388 + (" FAILED, NS_QUERY_TEXT_RECT was failed")); 1.1389 + return; 1.1390 + } 1.1391 + nsWindow* rootWindow = 1.1392 + static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget()); 1.1393 + 1.1394 + // Get the position of the rootWindow in screen. 1.1395 + gint rootX, rootY; 1.1396 + gdk_window_get_origin(rootWindow->GetGdkWindow(), &rootX, &rootY); 1.1397 + 1.1398 + // Get the position of IM context owner window in screen. 1.1399 + gint ownerX, ownerY; 1.1400 + gdk_window_get_origin(mOwnerWindow->GetGdkWindow(), &ownerX, &ownerY); 1.1401 + 1.1402 + // Compute the caret position in the IM owner window. 1.1403 + GdkRectangle area; 1.1404 + area.x = charRect.mReply.mRect.x + rootX - ownerX; 1.1405 + area.y = charRect.mReply.mRect.y + rootY - ownerY; 1.1406 + area.width = 0; 1.1407 + area.height = charRect.mReply.mRect.height; 1.1408 + 1.1409 + gtk_im_context_set_cursor_location(im, &area); 1.1410 +} 1.1411 + 1.1412 +nsresult 1.1413 +nsGtkIMModule::GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos) 1.1414 +{ 1.1415 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1416 + ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s", 1.1417 + this, GetCompositionStateName())); 1.1418 + 1.1419 + if (!mLastFocusedWindow) { 1.1420 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1421 + (" FAILED, there are no focused window in this module")); 1.1422 + return NS_ERROR_NULL_POINTER; 1.1423 + } 1.1424 + 1.1425 + nsEventStatus status; 1.1426 + 1.1427 + uint32_t selOffset = mCompositionStart; 1.1428 + uint32_t selLength = mSelectedString.Length(); 1.1429 + 1.1430 + // If focused editor doesn't have composition string, we should use 1.1431 + // current selection. 1.1432 + if (!EditorHasCompositionString()) { 1.1433 + // Query cursor position & selection 1.1434 + WidgetQueryContentEvent querySelectedTextEvent(true, 1.1435 + NS_QUERY_SELECTED_TEXT, 1.1436 + mLastFocusedWindow); 1.1437 + mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status); 1.1438 + NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE); 1.1439 + 1.1440 + selOffset = querySelectedTextEvent.mReply.mOffset; 1.1441 + selLength = querySelectedTextEvent.mReply.mString.Length(); 1.1442 + } 1.1443 + 1.1444 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1445 + (" selOffset=%u, selLength=%u", 1.1446 + selOffset, selLength)); 1.1447 + 1.1448 + // XXX nsString::Find and nsString::RFind take int32_t for offset, so, 1.1449 + // we cannot support this request when the current offset is larger 1.1450 + // than INT32_MAX. 1.1451 + if (selOffset > INT32_MAX || selLength > INT32_MAX || 1.1452 + selOffset + selLength > INT32_MAX) { 1.1453 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1454 + (" FAILED, The selection is out of range")); 1.1455 + return NS_ERROR_FAILURE; 1.1456 + } 1.1457 + 1.1458 + // Get all text contents of the focused editor 1.1459 + WidgetQueryContentEvent queryTextContentEvent(true, 1.1460 + NS_QUERY_TEXT_CONTENT, 1.1461 + mLastFocusedWindow); 1.1462 + queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); 1.1463 + mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status); 1.1464 + NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE); 1.1465 + 1.1466 + nsAutoString textContent(queryTextContentEvent.mReply.mString); 1.1467 + if (selOffset + selLength > textContent.Length()) { 1.1468 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1469 + (" FAILED, The selection is invalid, textContent.Length()=%u", 1.1470 + textContent.Length())); 1.1471 + return NS_ERROR_FAILURE; 1.1472 + } 1.1473 + 1.1474 + // Remove composing string and restore the selected string because 1.1475 + // GtkEntry doesn't remove selected string until committing, however, 1.1476 + // our editor does it. We should emulate the behavior for IME. 1.1477 + if (EditorHasCompositionString() && 1.1478 + mDispatchedCompositionString != mSelectedString) { 1.1479 + textContent.Replace(mCompositionStart, 1.1480 + mDispatchedCompositionString.Length(), mSelectedString); 1.1481 + } 1.1482 + 1.1483 + // Get only the focused paragraph, by looking for newlines 1.1484 + int32_t parStart = (selOffset == 0) ? 0 : 1.1485 + textContent.RFind("\n", false, selOffset - 1, -1) + 1; 1.1486 + int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1); 1.1487 + if (parEnd < 0) { 1.1488 + parEnd = textContent.Length(); 1.1489 + } 1.1490 + aText = nsDependentSubstring(textContent, parStart, parEnd - parStart); 1.1491 + aCursorPos = selOffset - uint32_t(parStart); 1.1492 + 1.1493 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1494 + (" aText=%s, aText.Length()=%u, aCursorPos=%u", 1.1495 + NS_ConvertUTF16toUTF8(aText).get(), 1.1496 + aText.Length(), aCursorPos)); 1.1497 + 1.1498 + return NS_OK; 1.1499 +} 1.1500 + 1.1501 +nsresult 1.1502 +nsGtkIMModule::DeleteText(const int32_t aOffset, const uint32_t aNChars) 1.1503 +{ 1.1504 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1505 + ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, " 1.1506 + "mCompositionState=%s", 1.1507 + this, aOffset, aNChars, GetCompositionStateName())); 1.1508 + 1.1509 + if (!mLastFocusedWindow) { 1.1510 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1511 + (" FAILED, there are no focused window in this module")); 1.1512 + return NS_ERROR_NULL_POINTER; 1.1513 + } 1.1514 + 1.1515 + if (!aNChars) { 1.1516 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1517 + (" FAILED, aNChars must not be zero")); 1.1518 + return NS_ERROR_INVALID_ARG; 1.1519 + } 1.1520 + 1.1521 + nsRefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow); 1.1522 + nsEventStatus status; 1.1523 + 1.1524 + // First, we should cancel current composition because editor cannot 1.1525 + // handle changing selection and deleting text. 1.1526 + uint32_t selOffset; 1.1527 + bool wasComposing = IsComposing(); 1.1528 + bool editorHadCompositionString = EditorHasCompositionString(); 1.1529 + if (wasComposing) { 1.1530 + selOffset = mCompositionStart; 1.1531 + if (editorHadCompositionString && 1.1532 + !DispatchTextEvent(mSelectedString, false)) { 1.1533 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1534 + (" FAILED, quitting from DeletText")); 1.1535 + return NS_ERROR_FAILURE; 1.1536 + } 1.1537 + if (!DispatchCompositionEnd()) { 1.1538 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1539 + (" FAILED, quitting from DeletText")); 1.1540 + return NS_ERROR_FAILURE; 1.1541 + } 1.1542 + } else { 1.1543 + // Query cursor position & selection 1.1544 + WidgetQueryContentEvent querySelectedTextEvent(true, 1.1545 + NS_QUERY_SELECTED_TEXT, 1.1546 + mLastFocusedWindow); 1.1547 + lastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status); 1.1548 + NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE); 1.1549 + 1.1550 + selOffset = querySelectedTextEvent.mReply.mOffset; 1.1551 + } 1.1552 + 1.1553 + // Get all text contents of the focused editor 1.1554 + WidgetQueryContentEvent queryTextContentEvent(true, 1.1555 + NS_QUERY_TEXT_CONTENT, 1.1556 + mLastFocusedWindow); 1.1557 + queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); 1.1558 + mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status); 1.1559 + NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE); 1.1560 + if (queryTextContentEvent.mReply.mString.IsEmpty()) { 1.1561 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1562 + (" FAILED, there is no contents")); 1.1563 + return NS_ERROR_FAILURE; 1.1564 + } 1.1565 + 1.1566 + NS_ConvertUTF16toUTF8 utf8Str( 1.1567 + nsDependentSubstring(queryTextContentEvent.mReply.mString, 1.1568 + 0, selOffset)); 1.1569 + glong offsetInUTF8Characters = 1.1570 + g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset; 1.1571 + if (offsetInUTF8Characters < 0) { 1.1572 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1573 + (" FAILED, aOffset is too small for current cursor pos " 1.1574 + "(computed offset: %d)", 1.1575 + offsetInUTF8Characters)); 1.1576 + return NS_ERROR_FAILURE; 1.1577 + } 1.1578 + 1.1579 + AppendUTF16toUTF8( 1.1580 + nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset), 1.1581 + utf8Str); 1.1582 + glong countOfCharactersInUTF8 = 1.1583 + g_utf8_strlen(utf8Str.get(), utf8Str.Length()); 1.1584 + glong endInUTF8Characters = 1.1585 + offsetInUTF8Characters + aNChars; 1.1586 + if (countOfCharactersInUTF8 < endInUTF8Characters) { 1.1587 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1588 + (" FAILED, aNChars is too large for current contents " 1.1589 + "(content length: %d, computed end offset: %d)", 1.1590 + countOfCharactersInUTF8, endInUTF8Characters)); 1.1591 + return NS_ERROR_FAILURE; 1.1592 + } 1.1593 + 1.1594 + gchar* charAtOffset = 1.1595 + g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters); 1.1596 + gchar* charAtEnd = 1.1597 + g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters); 1.1598 + 1.1599 + // Set selection to delete 1.1600 + WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET, 1.1601 + mLastFocusedWindow); 1.1602 + 1.1603 + nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0, 1.1604 + charAtOffset - utf8Str.get()); 1.1605 + selectionEvent.mOffset = 1.1606 + NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length(); 1.1607 + 1.1608 + nsDependentCSubstring utf8DeletingStr(utf8Str, 1.1609 + utf8StrBeforeOffset.Length(), 1.1610 + charAtEnd - charAtOffset); 1.1611 + selectionEvent.mLength = 1.1612 + NS_ConvertUTF8toUTF16(utf8DeletingStr).Length(); 1.1613 + 1.1614 + selectionEvent.mReversed = false; 1.1615 + selectionEvent.mExpandToClusterBoundary = false; 1.1616 + lastFocusedWindow->DispatchEvent(&selectionEvent, status); 1.1617 + 1.1618 + if (!selectionEvent.mSucceeded || 1.1619 + lastFocusedWindow != mLastFocusedWindow || 1.1620 + lastFocusedWindow->Destroyed()) { 1.1621 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1622 + (" FAILED, setting selection caused focus change " 1.1623 + "or window destroyed")); 1.1624 + return NS_ERROR_FAILURE; 1.1625 + } 1.1626 + 1.1627 + // Delete the selection 1.1628 + WidgetContentCommandEvent contentCommandEvent(true, 1.1629 + NS_CONTENT_COMMAND_DELETE, 1.1630 + mLastFocusedWindow); 1.1631 + mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status); 1.1632 + 1.1633 + if (!contentCommandEvent.mSucceeded || 1.1634 + lastFocusedWindow != mLastFocusedWindow || 1.1635 + lastFocusedWindow->Destroyed()) { 1.1636 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1637 + (" FAILED, deleting the selection caused focus change " 1.1638 + "or window destroyed")); 1.1639 + return NS_ERROR_FAILURE; 1.1640 + } 1.1641 + 1.1642 + if (!wasComposing) { 1.1643 + return NS_OK; 1.1644 + } 1.1645 + 1.1646 + // Restore the composition at new caret position. 1.1647 + if (!DispatchCompositionStart()) { 1.1648 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1649 + (" FAILED, resterting composition start")); 1.1650 + return NS_ERROR_FAILURE; 1.1651 + } 1.1652 + 1.1653 + if (!editorHadCompositionString) { 1.1654 + return NS_OK; 1.1655 + } 1.1656 + 1.1657 + nsAutoString compositionString; 1.1658 + GetCompositionString(compositionString); 1.1659 + if (!DispatchTextEvent(compositionString, true)) { 1.1660 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1661 + (" FAILED, restoring composition string")); 1.1662 + return NS_ERROR_FAILURE; 1.1663 + } 1.1664 + 1.1665 + return NS_OK; 1.1666 +} 1.1667 + 1.1668 +void 1.1669 +nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent) 1.1670 +{ 1.1671 + aEvent.time = PR_Now() / 1000; 1.1672 +} 1.1673 + 1.1674 +bool 1.1675 +nsGtkIMModule::ShouldIgnoreNativeCompositionEvent() 1.1676 +{ 1.1677 + PR_LOG(gGtkIMLog, PR_LOG_ALWAYS, 1.1678 + ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s", 1.1679 + this, mLastFocusedWindow, 1.1680 + mIgnoreNativeCompositionEvent ? "YES" : "NO")); 1.1681 + 1.1682 + if (!mLastFocusedWindow) { 1.1683 + return true; // cannot continue 1.1684 + } 1.1685 + 1.1686 + return mIgnoreNativeCompositionEvent; 1.1687 +}