widget/gtk/nsGtkIMModule.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim: set ts=4 et sw=4 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #ifdef MOZ_LOGGING
michael@0 8 #define FORCE_PR_LOG /* Allow logging in the release build */
michael@0 9 #endif // MOZ_LOGGING
michael@0 10 #include "prlog.h"
michael@0 11 #include "prtime.h"
michael@0 12
michael@0 13 #include "nsGtkIMModule.h"
michael@0 14 #include "nsWindow.h"
michael@0 15 #include "mozilla/Likely.h"
michael@0 16 #include "mozilla/MiscEvents.h"
michael@0 17 #include "mozilla/Preferences.h"
michael@0 18 #include "mozilla/TextEvents.h"
michael@0 19
michael@0 20 using namespace mozilla;
michael@0 21 using namespace mozilla::widget;
michael@0 22
michael@0 23 #ifdef PR_LOGGING
michael@0 24 PRLogModuleInfo* gGtkIMLog = nullptr;
michael@0 25
michael@0 26 static const char*
michael@0 27 GetRangeTypeName(uint32_t aRangeType)
michael@0 28 {
michael@0 29 switch (aRangeType) {
michael@0 30 case NS_TEXTRANGE_RAWINPUT:
michael@0 31 return "NS_TEXTRANGE_RAWINPUT";
michael@0 32 case NS_TEXTRANGE_CONVERTEDTEXT:
michael@0 33 return "NS_TEXTRANGE_CONVERTEDTEXT";
michael@0 34 case NS_TEXTRANGE_SELECTEDRAWTEXT:
michael@0 35 return "NS_TEXTRANGE_SELECTEDRAWTEXT";
michael@0 36 case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
michael@0 37 return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
michael@0 38 case NS_TEXTRANGE_CARETPOSITION:
michael@0 39 return "NS_TEXTRANGE_CARETPOSITION";
michael@0 40 default:
michael@0 41 return "UNKNOWN SELECTION TYPE!!";
michael@0 42 }
michael@0 43 }
michael@0 44
michael@0 45 static const char*
michael@0 46 GetEnabledStateName(uint32_t aState)
michael@0 47 {
michael@0 48 switch (aState) {
michael@0 49 case IMEState::DISABLED:
michael@0 50 return "DISABLED";
michael@0 51 case IMEState::ENABLED:
michael@0 52 return "ENABLED";
michael@0 53 case IMEState::PASSWORD:
michael@0 54 return "PASSWORD";
michael@0 55 case IMEState::PLUGIN:
michael@0 56 return "PLUG_IN";
michael@0 57 default:
michael@0 58 return "UNKNOWN ENABLED STATUS!!";
michael@0 59 }
michael@0 60 }
michael@0 61 #endif
michael@0 62
michael@0 63 const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
michael@0 64
michael@0 65 nsGtkIMModule* nsGtkIMModule::sLastFocusedModule = nullptr;
michael@0 66 bool nsGtkIMModule::sUseSimpleContext;
michael@0 67
michael@0 68 nsGtkIMModule::nsGtkIMModule(nsWindow* aOwnerWindow) :
michael@0 69 mOwnerWindow(aOwnerWindow), mLastFocusedWindow(nullptr),
michael@0 70 mContext(nullptr),
michael@0 71 mSimpleContext(nullptr),
michael@0 72 mDummyContext(nullptr),
michael@0 73 mCompositionStart(UINT32_MAX), mProcessingKeyEvent(nullptr),
michael@0 74 mCompositionTargetOffset(UINT32_MAX),
michael@0 75 mCompositionState(eCompositionState_NotComposing),
michael@0 76 mIsIMFocused(false), mIgnoreNativeCompositionEvent(false)
michael@0 77 {
michael@0 78 #ifdef PR_LOGGING
michael@0 79 if (!gGtkIMLog) {
michael@0 80 gGtkIMLog = PR_NewLogModule("nsGtkIMModuleWidgets");
michael@0 81 }
michael@0 82 #endif
michael@0 83 static bool sFirstInstance = true;
michael@0 84 if (sFirstInstance) {
michael@0 85 sFirstInstance = false;
michael@0 86 sUseSimpleContext =
michael@0 87 Preferences::GetBool(
michael@0 88 "intl.ime.use_simple_context_on_password_field",
michael@0 89 kUseSimpleContextDefault);
michael@0 90 }
michael@0 91 Init();
michael@0 92 }
michael@0 93
michael@0 94 void
michael@0 95 nsGtkIMModule::Init()
michael@0 96 {
michael@0 97 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 98 ("GtkIMModule(%p): Init, mOwnerWindow=%p",
michael@0 99 this, mOwnerWindow));
michael@0 100
michael@0 101 MozContainer* container = mOwnerWindow->GetMozContainer();
michael@0 102 NS_PRECONDITION(container, "container is null");
michael@0 103 GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
michael@0 104
michael@0 105 // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
michael@0 106 // So, we don't need to check the result.
michael@0 107
michael@0 108 // Normal context.
michael@0 109 mContext = gtk_im_multicontext_new();
michael@0 110 gtk_im_context_set_client_window(mContext, gdkWindow);
michael@0 111 g_signal_connect(mContext, "preedit_changed",
michael@0 112 G_CALLBACK(nsGtkIMModule::OnChangeCompositionCallback),
michael@0 113 this);
michael@0 114 g_signal_connect(mContext, "retrieve_surrounding",
michael@0 115 G_CALLBACK(nsGtkIMModule::OnRetrieveSurroundingCallback),
michael@0 116 this);
michael@0 117 g_signal_connect(mContext, "delete_surrounding",
michael@0 118 G_CALLBACK(nsGtkIMModule::OnDeleteSurroundingCallback),
michael@0 119 this);
michael@0 120 g_signal_connect(mContext, "commit",
michael@0 121 G_CALLBACK(nsGtkIMModule::OnCommitCompositionCallback),
michael@0 122 this);
michael@0 123 g_signal_connect(mContext, "preedit_start",
michael@0 124 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
michael@0 125 this);
michael@0 126 g_signal_connect(mContext, "preedit_end",
michael@0 127 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
michael@0 128 this);
michael@0 129
michael@0 130 // Simple context
michael@0 131 if (sUseSimpleContext) {
michael@0 132 mSimpleContext = gtk_im_context_simple_new();
michael@0 133 gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
michael@0 134 g_signal_connect(mSimpleContext, "preedit_changed",
michael@0 135 G_CALLBACK(&nsGtkIMModule::OnChangeCompositionCallback),
michael@0 136 this);
michael@0 137 g_signal_connect(mSimpleContext, "retrieve_surrounding",
michael@0 138 G_CALLBACK(&nsGtkIMModule::OnRetrieveSurroundingCallback),
michael@0 139 this);
michael@0 140 g_signal_connect(mSimpleContext, "delete_surrounding",
michael@0 141 G_CALLBACK(&nsGtkIMModule::OnDeleteSurroundingCallback),
michael@0 142 this);
michael@0 143 g_signal_connect(mSimpleContext, "commit",
michael@0 144 G_CALLBACK(&nsGtkIMModule::OnCommitCompositionCallback),
michael@0 145 this);
michael@0 146 g_signal_connect(mSimpleContext, "preedit_start",
michael@0 147 G_CALLBACK(nsGtkIMModule::OnStartCompositionCallback),
michael@0 148 this);
michael@0 149 g_signal_connect(mSimpleContext, "preedit_end",
michael@0 150 G_CALLBACK(nsGtkIMModule::OnEndCompositionCallback),
michael@0 151 this);
michael@0 152 }
michael@0 153
michael@0 154 // Dummy context
michael@0 155 mDummyContext = gtk_im_multicontext_new();
michael@0 156 gtk_im_context_set_client_window(mDummyContext, gdkWindow);
michael@0 157 }
michael@0 158
michael@0 159 nsGtkIMModule::~nsGtkIMModule()
michael@0 160 {
michael@0 161 if (this == sLastFocusedModule) {
michael@0 162 sLastFocusedModule = nullptr;
michael@0 163 }
michael@0 164 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 165 ("GtkIMModule(%p) was gone", this));
michael@0 166 }
michael@0 167
michael@0 168 void
michael@0 169 nsGtkIMModule::OnDestroyWindow(nsWindow* aWindow)
michael@0 170 {
michael@0 171 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 172 ("GtkIMModule(%p): OnDestroyWindow, aWindow=%p, mLastFocusedWindow=%p, mOwnerWindow=%p, mLastFocusedModule=%p",
michael@0 173 this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedModule));
michael@0 174
michael@0 175 NS_PRECONDITION(aWindow, "aWindow must not be null");
michael@0 176
michael@0 177 if (mLastFocusedWindow == aWindow) {
michael@0 178 CancelIMEComposition(aWindow);
michael@0 179 if (mIsIMFocused) {
michael@0 180 Blur();
michael@0 181 }
michael@0 182 mLastFocusedWindow = nullptr;
michael@0 183 }
michael@0 184
michael@0 185 if (mOwnerWindow != aWindow) {
michael@0 186 return;
michael@0 187 }
michael@0 188
michael@0 189 if (sLastFocusedModule == this) {
michael@0 190 sLastFocusedModule = nullptr;
michael@0 191 }
michael@0 192
michael@0 193 /**
michael@0 194 * NOTE:
michael@0 195 * The given window is the owner of this, so, we must release the
michael@0 196 * contexts now. But that might be referred from other nsWindows
michael@0 197 * (they are children of this. But we don't know why there are the
michael@0 198 * cases). So, we need to clear the pointers that refers to contexts
michael@0 199 * and this if the other referrers are still alive. See bug 349727.
michael@0 200 */
michael@0 201 if (mContext) {
michael@0 202 PrepareToDestroyContext(mContext);
michael@0 203 gtk_im_context_set_client_window(mContext, nullptr);
michael@0 204 g_object_unref(mContext);
michael@0 205 mContext = nullptr;
michael@0 206 }
michael@0 207
michael@0 208 if (mSimpleContext) {
michael@0 209 gtk_im_context_set_client_window(mSimpleContext, nullptr);
michael@0 210 g_object_unref(mSimpleContext);
michael@0 211 mSimpleContext = nullptr;
michael@0 212 }
michael@0 213
michael@0 214 if (mDummyContext) {
michael@0 215 // mContext and mDummyContext have the same slaveType and signal_data
michael@0 216 // so no need for another workaround_gtk_im_display_closed.
michael@0 217 gtk_im_context_set_client_window(mDummyContext, nullptr);
michael@0 218 g_object_unref(mDummyContext);
michael@0 219 mDummyContext = nullptr;
michael@0 220 }
michael@0 221
michael@0 222 mOwnerWindow = nullptr;
michael@0 223 mLastFocusedWindow = nullptr;
michael@0 224 mInputContext.mIMEState.mEnabled = IMEState::DISABLED;
michael@0 225
michael@0 226 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 227 (" SUCCEEDED, Completely destroyed"));
michael@0 228 }
michael@0 229
michael@0 230 // Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
michael@0 231 // (and the similar issue of GTK+ IIIM)
michael@0 232 // The GTK+ XIM and IIIM modules register handlers for the "closed" signal
michael@0 233 // on the display, but:
michael@0 234 // * The signal handlers are not disconnected when the module is unloaded.
michael@0 235 //
michael@0 236 // The GTK+ XIM module has another problem:
michael@0 237 // * When the signal handler is run (with the module loaded) it tries
michael@0 238 // XFree (and fails) on a pointer that did not come from Xmalloc.
michael@0 239 //
michael@0 240 // To prevent these modules from being unloaded, use static variables to
michael@0 241 // hold ref of GtkIMContext class.
michael@0 242 // For GTK+ XIM module, to prevent the signal handler from being run,
michael@0 243 // find the signal handlers and remove them.
michael@0 244 //
michael@0 245 // GtkIMContextXIMs share XOpenIM connections and display closed signal
michael@0 246 // handlers (where possible).
michael@0 247
michael@0 248 void
michael@0 249 nsGtkIMModule::PrepareToDestroyContext(GtkIMContext *aContext)
michael@0 250 {
michael@0 251 MozContainer* container = mOwnerWindow->GetMozContainer();
michael@0 252 NS_PRECONDITION(container, "The container of the window is null");
michael@0 253
michael@0 254 GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
michael@0 255 #if (MOZ_WIDGET_GTK == 2)
michael@0 256 GtkIMContext *slave = multicontext->slave;
michael@0 257 #else
michael@0 258 GtkIMContext *slave = nullptr; //TODO GTK3
michael@0 259 #endif
michael@0 260 if (!slave) {
michael@0 261 return;
michael@0 262 }
michael@0 263
michael@0 264 GType slaveType = G_TYPE_FROM_INSTANCE(slave);
michael@0 265 const gchar *im_type_name = g_type_name(slaveType);
michael@0 266 if (strcmp(im_type_name, "GtkIMContextXIM") == 0) {
michael@0 267 if (gtk_check_version(2, 12, 1) == nullptr) {
michael@0 268 return; // gtk bug has been fixed
michael@0 269 }
michael@0 270
michael@0 271 struct GtkIMContextXIM
michael@0 272 {
michael@0 273 GtkIMContext parent;
michael@0 274 gpointer private_data;
michael@0 275 // ... other fields
michael@0 276 };
michael@0 277
michael@0 278 gpointer signal_data =
michael@0 279 reinterpret_cast<GtkIMContextXIM*>(slave)->private_data;
michael@0 280 if (!signal_data) {
michael@0 281 return;
michael@0 282 }
michael@0 283
michael@0 284 g_signal_handlers_disconnect_matched(
michael@0 285 gtk_widget_get_display(GTK_WIDGET(container)),
michael@0 286 G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, signal_data);
michael@0 287
michael@0 288 // Add a reference to prevent the XIM module from being unloaded
michael@0 289 // and reloaded: each time the module is loaded and used, it
michael@0 290 // opens (and doesn't close) new XOpenIM connections.
michael@0 291 static gpointer gtk_xim_context_class =
michael@0 292 g_type_class_ref(slaveType);
michael@0 293 // Mute unused variable warning:
michael@0 294 (void)gtk_xim_context_class;
michael@0 295 } else if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) {
michael@0 296 // Add a reference to prevent the IIIM module from being unloaded
michael@0 297 static gpointer gtk_iiim_context_class =
michael@0 298 g_type_class_ref(slaveType);
michael@0 299 // Mute unused variable warning:
michael@0 300 (void)gtk_iiim_context_class;
michael@0 301 }
michael@0 302 }
michael@0 303
michael@0 304 void
michael@0 305 nsGtkIMModule::OnFocusWindow(nsWindow* aWindow)
michael@0 306 {
michael@0 307 if (MOZ_UNLIKELY(IsDestroyed())) {
michael@0 308 return;
michael@0 309 }
michael@0 310
michael@0 311 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 312 ("GtkIMModule(%p): OnFocusWindow, aWindow=%p, mLastFocusedWindow=%p",
michael@0 313 this, aWindow, mLastFocusedWindow));
michael@0 314 mLastFocusedWindow = aWindow;
michael@0 315 Focus();
michael@0 316 }
michael@0 317
michael@0 318 void
michael@0 319 nsGtkIMModule::OnBlurWindow(nsWindow* aWindow)
michael@0 320 {
michael@0 321 if (MOZ_UNLIKELY(IsDestroyed())) {
michael@0 322 return;
michael@0 323 }
michael@0 324
michael@0 325 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 326 ("GtkIMModule(%p): OnBlurWindow, aWindow=%p, mLastFocusedWindow=%p, mIsIMFocused=%s",
michael@0 327 this, aWindow, mLastFocusedWindow, mIsIMFocused ? "YES" : "NO"));
michael@0 328
michael@0 329 if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
michael@0 330 return;
michael@0 331 }
michael@0 332
michael@0 333 Blur();
michael@0 334 }
michael@0 335
michael@0 336 bool
michael@0 337 nsGtkIMModule::OnKeyEvent(nsWindow* aCaller, GdkEventKey* aEvent,
michael@0 338 bool aKeyDownEventWasSent /* = false */)
michael@0 339 {
michael@0 340 NS_PRECONDITION(aEvent, "aEvent must be non-null");
michael@0 341
michael@0 342 if (!IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
michael@0 343 return false;
michael@0 344 }
michael@0 345
michael@0 346 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 347 ("GtkIMModule(%p): OnKeyEvent, aCaller=%p, aKeyDownEventWasSent=%s",
michael@0 348 this, aCaller, aKeyDownEventWasSent ? "TRUE" : "FALSE"));
michael@0 349 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 350 (" aEvent: type=%s, keyval=%s, unicode=0x%X",
michael@0 351 aEvent->type == GDK_KEY_PRESS ? "GDK_KEY_PRESS" :
michael@0 352 aEvent->type == GDK_KEY_RELEASE ? "GDK_KEY_RELEASE" : "Unknown",
michael@0 353 gdk_keyval_name(aEvent->keyval),
michael@0 354 gdk_keyval_to_unicode(aEvent->keyval)));
michael@0 355
michael@0 356 if (aCaller != mLastFocusedWindow) {
michael@0 357 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 358 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
michael@0 359 mLastFocusedWindow));
michael@0 360 return false;
michael@0 361 }
michael@0 362
michael@0 363 GtkIMContext* im = GetContext();
michael@0 364 if (MOZ_UNLIKELY(!im)) {
michael@0 365 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 366 (" FAILED, there are no context"));
michael@0 367 return false;
michael@0 368 }
michael@0 369
michael@0 370 mKeyDownEventWasSent = aKeyDownEventWasSent;
michael@0 371 mFilterKeyEvent = true;
michael@0 372 mProcessingKeyEvent = aEvent;
michael@0 373 gboolean isFiltered = gtk_im_context_filter_keypress(im, aEvent);
michael@0 374 mProcessingKeyEvent = nullptr;
michael@0 375
michael@0 376 // We filter the key event if the event was not committed (because
michael@0 377 // it's probably part of a composition) or if the key event was
michael@0 378 // committed _and_ changed. This way we still let key press
michael@0 379 // events go through as simple key press events instead of
michael@0 380 // composed characters.
michael@0 381 bool filterThisEvent = isFiltered && mFilterKeyEvent;
michael@0 382
michael@0 383 if (IsComposing() && !isFiltered) {
michael@0 384 if (aEvent->type == GDK_KEY_PRESS) {
michael@0 385 if (!mDispatchedCompositionString.IsEmpty()) {
michael@0 386 // If there is composition string, we shouldn't dispatch
michael@0 387 // any keydown events during composition.
michael@0 388 filterThisEvent = true;
michael@0 389 } else {
michael@0 390 // A Hangul input engine for SCIM doesn't emit preedit_end
michael@0 391 // signal even when composition string becomes empty. On the
michael@0 392 // other hand, we should allow to make composition with empty
michael@0 393 // string for other languages because there *might* be such
michael@0 394 // IM. For compromising this issue, we should dispatch
michael@0 395 // compositionend event, however, we don't need to reset IM
michael@0 396 // actually.
michael@0 397 CommitCompositionBy(EmptyString());
michael@0 398 filterThisEvent = false;
michael@0 399 }
michael@0 400 } else {
michael@0 401 // Key release event may not be consumed by IM, however, we
michael@0 402 // shouldn't dispatch any keyup event during composition.
michael@0 403 filterThisEvent = true;
michael@0 404 }
michael@0 405 }
michael@0 406
michael@0 407 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 408 (" filterThisEvent=%s (isFiltered=%s, mFilterKeyEvent=%s)",
michael@0 409 filterThisEvent ? "TRUE" : "FALSE", isFiltered ? "YES" : "NO",
michael@0 410 mFilterKeyEvent ? "YES" : "NO"));
michael@0 411
michael@0 412 return filterThisEvent;
michael@0 413 }
michael@0 414
michael@0 415 void
michael@0 416 nsGtkIMModule::OnFocusChangeInGecko(bool aFocus)
michael@0 417 {
michael@0 418 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 419 ("GtkIMModule(%p): OnFocusChangeInGecko, aFocus=%s, "
michael@0 420 "mCompositionState=%s, mIsIMFocused=%s, "
michael@0 421 "mIgnoreNativeCompositionEvent=%s",
michael@0 422 this, aFocus ? "YES" : "NO", GetCompositionStateName(),
michael@0 423 mIsIMFocused ? "YES" : "NO",
michael@0 424 mIgnoreNativeCompositionEvent ? "YES" : "NO"));
michael@0 425
michael@0 426 // We shouldn't carry over the removed string to another editor.
michael@0 427 mSelectedString.Truncate();
michael@0 428
michael@0 429 if (aFocus) {
michael@0 430 // If we failed to commit forcedely in previous focused editor,
michael@0 431 // we should reopen the gate for native signals in new focused editor.
michael@0 432 mIgnoreNativeCompositionEvent = false;
michael@0 433 }
michael@0 434 }
michael@0 435
michael@0 436 void
michael@0 437 nsGtkIMModule::ResetIME()
michael@0 438 {
michael@0 439 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 440 ("GtkIMModule(%p): ResetIME, mCompositionState=%s, mIsIMFocused=%s",
michael@0 441 this, GetCompositionStateName(), mIsIMFocused ? "YES" : "NO"));
michael@0 442
michael@0 443 GtkIMContext *im = GetContext();
michael@0 444 if (MOZ_UNLIKELY(!im)) {
michael@0 445 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 446 (" FAILED, there are no context"));
michael@0 447 return;
michael@0 448 }
michael@0 449
michael@0 450 mIgnoreNativeCompositionEvent = true;
michael@0 451 gtk_im_context_reset(im);
michael@0 452 }
michael@0 453
michael@0 454 nsresult
michael@0 455 nsGtkIMModule::CommitIMEComposition(nsWindow* aCaller)
michael@0 456 {
michael@0 457 if (MOZ_UNLIKELY(IsDestroyed())) {
michael@0 458 return NS_OK;
michael@0 459 }
michael@0 460
michael@0 461 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 462 ("GtkIMModule(%p): CommitIMEComposition, aCaller=%p, "
michael@0 463 "mCompositionState=%s",
michael@0 464 this, aCaller, GetCompositionStateName()));
michael@0 465
michael@0 466 if (aCaller != mLastFocusedWindow) {
michael@0 467 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 468 (" WARNING: the caller isn't focused window, mLastFocusedWindow=%p",
michael@0 469 mLastFocusedWindow));
michael@0 470 return NS_OK;
michael@0 471 }
michael@0 472
michael@0 473 if (!IsComposing()) {
michael@0 474 return NS_OK;
michael@0 475 }
michael@0 476
michael@0 477 // XXX We should commit composition ourselves temporary...
michael@0 478 ResetIME();
michael@0 479 CommitCompositionBy(mDispatchedCompositionString);
michael@0 480
michael@0 481 return NS_OK;
michael@0 482 }
michael@0 483
michael@0 484 nsresult
michael@0 485 nsGtkIMModule::CancelIMEComposition(nsWindow* aCaller)
michael@0 486 {
michael@0 487 if (MOZ_UNLIKELY(IsDestroyed())) {
michael@0 488 return NS_OK;
michael@0 489 }
michael@0 490
michael@0 491 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 492 ("GtkIMModule(%p): CancelIMEComposition, aCaller=%p",
michael@0 493 this, aCaller));
michael@0 494
michael@0 495 if (aCaller != mLastFocusedWindow) {
michael@0 496 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 497 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
michael@0 498 mLastFocusedWindow));
michael@0 499 return NS_OK;
michael@0 500 }
michael@0 501
michael@0 502 if (!IsComposing()) {
michael@0 503 return NS_OK;
michael@0 504 }
michael@0 505
michael@0 506 GtkIMContext *im = GetContext();
michael@0 507 if (MOZ_UNLIKELY(!im)) {
michael@0 508 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 509 (" FAILED, there are no context"));
michael@0 510 return NS_OK;
michael@0 511 }
michael@0 512
michael@0 513 ResetIME();
michael@0 514 CommitCompositionBy(EmptyString());
michael@0 515
michael@0 516 return NS_OK;
michael@0 517 }
michael@0 518
michael@0 519 void
michael@0 520 nsGtkIMModule::OnUpdateComposition(void)
michael@0 521 {
michael@0 522 if (MOZ_UNLIKELY(IsDestroyed())) {
michael@0 523 return;
michael@0 524 }
michael@0 525
michael@0 526 SetCursorPosition(mCompositionTargetOffset);
michael@0 527 }
michael@0 528
michael@0 529 void
michael@0 530 nsGtkIMModule::SetInputContext(nsWindow* aCaller,
michael@0 531 const InputContext* aContext,
michael@0 532 const InputContextAction* aAction)
michael@0 533 {
michael@0 534 if (MOZ_UNLIKELY(IsDestroyed())) {
michael@0 535 return;
michael@0 536 }
michael@0 537
michael@0 538 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 539 ("GtkIMModule(%p): SetInputContext, aCaller=%p, aState=%s mHTMLInputType=%s",
michael@0 540 this, aCaller, GetEnabledStateName(aContext->mIMEState.mEnabled),
michael@0 541 NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
michael@0 542
michael@0 543 if (aCaller != mLastFocusedWindow) {
michael@0 544 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 545 (" FAILED, the caller isn't focused window, mLastFocusedWindow=%p",
michael@0 546 mLastFocusedWindow));
michael@0 547 return;
michael@0 548 }
michael@0 549
michael@0 550 if (!mContext) {
michael@0 551 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 552 (" FAILED, there are no context"));
michael@0 553 return;
michael@0 554 }
michael@0 555
michael@0 556
michael@0 557 if (sLastFocusedModule != this) {
michael@0 558 mInputContext = *aContext;
michael@0 559 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 560 (" SUCCEEDED, but we're not active"));
michael@0 561 return;
michael@0 562 }
michael@0 563
michael@0 564 bool changingEnabledState =
michael@0 565 aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
michael@0 566 aContext->mHTMLInputType != mInputContext.mHTMLInputType;
michael@0 567
michael@0 568 // Release current IME focus if IME is enabled.
michael@0 569 if (changingEnabledState && IsEditable()) {
michael@0 570 CommitIMEComposition(mLastFocusedWindow);
michael@0 571 Blur();
michael@0 572 }
michael@0 573
michael@0 574 mInputContext = *aContext;
michael@0 575
michael@0 576 if (changingEnabledState) {
michael@0 577 #if (MOZ_WIDGET_GTK == 3)
michael@0 578 static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
michael@0 579 if (sInputPurposeSupported && IsEditable()) {
michael@0 580 GtkIMContext* context = GetContext();
michael@0 581 if (context) {
michael@0 582 GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
michael@0 583 const nsString& inputType = mInputContext.mHTMLInputType;
michael@0 584 // Password case has difficult issue. Desktop IMEs disable
michael@0 585 // composition if input-purpose is password.
michael@0 586 // For disabling IME on |ime-mode: disabled;|, we need to check
michael@0 587 // mEnabled value instead of inputType value. This hack also
michael@0 588 // enables composition on
michael@0 589 // <input type="password" style="ime-mode: enabled;">.
michael@0 590 // This is right behavior of ime-mode on desktop.
michael@0 591 //
michael@0 592 // On the other hand, IME for tablet devices may provide a
michael@0 593 // specific software keyboard for password field. If so,
michael@0 594 // the behavior might look strange on both:
michael@0 595 // <input type="text" style="ime-mode: disabled;">
michael@0 596 // <input type="password" style="ime-mode: enabled;">
michael@0 597 //
michael@0 598 // Temporarily, we should focus on desktop environment for now.
michael@0 599 // I.e., let's ignore tablet devices for now. When somebody
michael@0 600 // reports actual trouble on tablet devices, we should try to
michael@0 601 // look for a way to solve actual problem.
michael@0 602 if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
michael@0 603 purpose = GTK_INPUT_PURPOSE_PASSWORD;
michael@0 604 } else if (inputType.EqualsLiteral("email")) {
michael@0 605 purpose = GTK_INPUT_PURPOSE_EMAIL;
michael@0 606 } else if (inputType.EqualsLiteral("url")) {
michael@0 607 purpose = GTK_INPUT_PURPOSE_URL;
michael@0 608 } else if (inputType.EqualsLiteral("tel")) {
michael@0 609 purpose = GTK_INPUT_PURPOSE_PHONE;
michael@0 610 } else if (inputType.EqualsLiteral("number")) {
michael@0 611 purpose = GTK_INPUT_PURPOSE_NUMBER;
michael@0 612 }
michael@0 613
michael@0 614 g_object_set(context, "input-purpose", purpose, nullptr);
michael@0 615 }
michael@0 616 }
michael@0 617 #endif // #if (MOZ_WIDGET_GTK == 3)
michael@0 618
michael@0 619 // Even when aState is not enabled state, we need to set IME focus.
michael@0 620 // Because some IMs are updating the status bar of them at this time.
michael@0 621 // Be aware, don't use aWindow here because this method shouldn't move
michael@0 622 // focus actually.
michael@0 623 Focus();
michael@0 624
michael@0 625 // XXX Should we call Blur() when it's not editable? E.g., it might be
michael@0 626 // better to close VKB automatically.
michael@0 627 }
michael@0 628 }
michael@0 629
michael@0 630 InputContext
michael@0 631 nsGtkIMModule::GetInputContext()
michael@0 632 {
michael@0 633 mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
michael@0 634 return mInputContext;
michael@0 635 }
michael@0 636
michael@0 637 /* static */
michael@0 638 bool
michael@0 639 nsGtkIMModule::IsVirtualKeyboardOpened()
michael@0 640 {
michael@0 641 return false;
michael@0 642 }
michael@0 643
michael@0 644 GtkIMContext*
michael@0 645 nsGtkIMModule::GetContext()
michael@0 646 {
michael@0 647 if (IsEnabled()) {
michael@0 648 return mContext;
michael@0 649 }
michael@0 650 if (mInputContext.mIMEState.mEnabled == IMEState::PASSWORD) {
michael@0 651 return mSimpleContext;
michael@0 652 }
michael@0 653 return mDummyContext;
michael@0 654 }
michael@0 655
michael@0 656 bool
michael@0 657 nsGtkIMModule::IsEnabled()
michael@0 658 {
michael@0 659 return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
michael@0 660 mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
michael@0 661 (!sUseSimpleContext &&
michael@0 662 mInputContext.mIMEState.mEnabled == IMEState::PASSWORD);
michael@0 663 }
michael@0 664
michael@0 665 bool
michael@0 666 nsGtkIMModule::IsEditable()
michael@0 667 {
michael@0 668 return mInputContext.mIMEState.mEnabled == IMEState::ENABLED ||
michael@0 669 mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
michael@0 670 mInputContext.mIMEState.mEnabled == IMEState::PASSWORD;
michael@0 671 }
michael@0 672
michael@0 673 void
michael@0 674 nsGtkIMModule::Focus()
michael@0 675 {
michael@0 676 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 677 ("GtkIMModule(%p): Focus, sLastFocusedModule=%p",
michael@0 678 this, sLastFocusedModule));
michael@0 679
michael@0 680 if (mIsIMFocused) {
michael@0 681 NS_ASSERTION(sLastFocusedModule == this,
michael@0 682 "We're not active, but the IM was focused?");
michael@0 683 return;
michael@0 684 }
michael@0 685
michael@0 686 GtkIMContext *im = GetContext();
michael@0 687 if (!im) {
michael@0 688 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 689 (" FAILED, there are no context"));
michael@0 690 return;
michael@0 691 }
michael@0 692
michael@0 693 if (sLastFocusedModule && sLastFocusedModule != this) {
michael@0 694 sLastFocusedModule->Blur();
michael@0 695 }
michael@0 696
michael@0 697 sLastFocusedModule = this;
michael@0 698
michael@0 699 gtk_im_context_focus_in(im);
michael@0 700 mIsIMFocused = true;
michael@0 701
michael@0 702 if (!IsEnabled()) {
michael@0 703 // We should release IME focus for uim and scim.
michael@0 704 // These IMs are using snooper that is released at losing focus.
michael@0 705 Blur();
michael@0 706 }
michael@0 707 }
michael@0 708
michael@0 709 void
michael@0 710 nsGtkIMModule::Blur()
michael@0 711 {
michael@0 712 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 713 ("GtkIMModule(%p): Blur, mIsIMFocused=%s",
michael@0 714 this, mIsIMFocused ? "YES" : "NO"));
michael@0 715
michael@0 716 if (!mIsIMFocused) {
michael@0 717 return;
michael@0 718 }
michael@0 719
michael@0 720 GtkIMContext *im = GetContext();
michael@0 721 if (!im) {
michael@0 722 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 723 (" FAILED, there are no context"));
michael@0 724 return;
michael@0 725 }
michael@0 726
michael@0 727 gtk_im_context_focus_out(im);
michael@0 728 mIsIMFocused = false;
michael@0 729 }
michael@0 730
michael@0 731 /* static */
michael@0 732 void
michael@0 733 nsGtkIMModule::OnStartCompositionCallback(GtkIMContext *aContext,
michael@0 734 nsGtkIMModule* aModule)
michael@0 735 {
michael@0 736 aModule->OnStartCompositionNative(aContext);
michael@0 737 }
michael@0 738
michael@0 739 void
michael@0 740 nsGtkIMModule::OnStartCompositionNative(GtkIMContext *aContext)
michael@0 741 {
michael@0 742 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 743 ("GtkIMModule(%p): OnStartCompositionNative, aContext=%p",
michael@0 744 this, aContext));
michael@0 745
michael@0 746 // See bug 472635, we should do nothing if IM context doesn't match.
michael@0 747 if (GetContext() != aContext) {
michael@0 748 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 749 (" FAILED, given context doesn't match, GetContext()=%p",
michael@0 750 GetContext()));
michael@0 751 return;
michael@0 752 }
michael@0 753
michael@0 754 if (!DispatchCompositionStart()) {
michael@0 755 return;
michael@0 756 }
michael@0 757 mCompositionTargetOffset = mCompositionStart;
michael@0 758 }
michael@0 759
michael@0 760 /* static */
michael@0 761 void
michael@0 762 nsGtkIMModule::OnEndCompositionCallback(GtkIMContext *aContext,
michael@0 763 nsGtkIMModule* aModule)
michael@0 764 {
michael@0 765 aModule->OnEndCompositionNative(aContext);
michael@0 766 }
michael@0 767
michael@0 768 void
michael@0 769 nsGtkIMModule::OnEndCompositionNative(GtkIMContext *aContext)
michael@0 770 {
michael@0 771 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 772 ("GtkIMModule(%p): OnEndCompositionNative, aContext=%p",
michael@0 773 this, aContext));
michael@0 774
michael@0 775 // See bug 472635, we should do nothing if IM context doesn't match.
michael@0 776 if (GetContext() != aContext) {
michael@0 777 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 778 (" FAILED, given context doesn't match, GetContext()=%p",
michael@0 779 GetContext()));
michael@0 780 return;
michael@0 781 }
michael@0 782
michael@0 783 bool shouldIgnoreThisEvent = ShouldIgnoreNativeCompositionEvent();
michael@0 784
michael@0 785 // Finish the cancelling mode here rather than DispatchCompositionEnd()
michael@0 786 // because DispatchCompositionEnd() is called ourselves when we need to
michael@0 787 // commit the composition string *before* the focus moves completely.
michael@0 788 // Note that the native commit can be fired *after* ResetIME().
michael@0 789 mIgnoreNativeCompositionEvent = false;
michael@0 790
michael@0 791 if (!IsComposing() || shouldIgnoreThisEvent) {
michael@0 792 // If we already handled the commit event, we should do nothing here.
michael@0 793 return;
michael@0 794 }
michael@0 795
michael@0 796 // Be aware, widget can be gone
michael@0 797 DispatchCompositionEnd();
michael@0 798 }
michael@0 799
michael@0 800 /* static */
michael@0 801 void
michael@0 802 nsGtkIMModule::OnChangeCompositionCallback(GtkIMContext *aContext,
michael@0 803 nsGtkIMModule* aModule)
michael@0 804 {
michael@0 805 aModule->OnChangeCompositionNative(aContext);
michael@0 806 }
michael@0 807
michael@0 808 void
michael@0 809 nsGtkIMModule::OnChangeCompositionNative(GtkIMContext *aContext)
michael@0 810 {
michael@0 811 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 812 ("GtkIMModule(%p): OnChangeCompositionNative, aContext=%p",
michael@0 813 this, aContext));
michael@0 814
michael@0 815 // See bug 472635, we should do nothing if IM context doesn't match.
michael@0 816 if (GetContext() != aContext) {
michael@0 817 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 818 (" FAILED, given context doesn't match, GetContext()=%p",
michael@0 819 GetContext()));
michael@0 820 return;
michael@0 821 }
michael@0 822
michael@0 823 if (ShouldIgnoreNativeCompositionEvent()) {
michael@0 824 return;
michael@0 825 }
michael@0 826
michael@0 827 nsAutoString compositionString;
michael@0 828 GetCompositionString(compositionString);
michael@0 829 if (!IsComposing() && compositionString.IsEmpty()) {
michael@0 830 mDispatchedCompositionString.Truncate();
michael@0 831 return; // Don't start the composition with empty string.
michael@0 832 }
michael@0 833
michael@0 834 // Be aware, widget can be gone
michael@0 835 DispatchTextEvent(compositionString, false);
michael@0 836 }
michael@0 837
michael@0 838 /* static */
michael@0 839 gboolean
michael@0 840 nsGtkIMModule::OnRetrieveSurroundingCallback(GtkIMContext *aContext,
michael@0 841 nsGtkIMModule *aModule)
michael@0 842 {
michael@0 843 return aModule->OnRetrieveSurroundingNative(aContext);
michael@0 844 }
michael@0 845
michael@0 846 gboolean
michael@0 847 nsGtkIMModule::OnRetrieveSurroundingNative(GtkIMContext *aContext)
michael@0 848 {
michael@0 849 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 850 ("GtkIMModule(%p): OnRetrieveSurroundingNative, aContext=%p, current context=%p",
michael@0 851 this, aContext, GetContext()));
michael@0 852
michael@0 853 if (GetContext() != aContext) {
michael@0 854 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 855 (" FAILED, given context doesn't match, GetContext()=%p",
michael@0 856 GetContext()));
michael@0 857 return FALSE;
michael@0 858 }
michael@0 859
michael@0 860 nsAutoString uniStr;
michael@0 861 uint32_t cursorPos;
michael@0 862 if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
michael@0 863 return FALSE;
michael@0 864 }
michael@0 865
michael@0 866 NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
michael@0 867 uint32_t cursorPosInUTF8 = utf8Str.Length();
michael@0 868 AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
michael@0 869 gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
michael@0 870 cursorPosInUTF8);
michael@0 871 return TRUE;
michael@0 872 }
michael@0 873
michael@0 874 /* static */
michael@0 875 gboolean
michael@0 876 nsGtkIMModule::OnDeleteSurroundingCallback(GtkIMContext *aContext,
michael@0 877 gint aOffset,
michael@0 878 gint aNChars,
michael@0 879 nsGtkIMModule *aModule)
michael@0 880 {
michael@0 881 return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
michael@0 882 }
michael@0 883
michael@0 884 gboolean
michael@0 885 nsGtkIMModule::OnDeleteSurroundingNative(GtkIMContext *aContext,
michael@0 886 gint aOffset,
michael@0 887 gint aNChars)
michael@0 888 {
michael@0 889 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 890 ("GtkIMModule(%p): OnDeleteSurroundingNative, aContext=%p, current context=%p",
michael@0 891 this, aContext, GetContext()));
michael@0 892
michael@0 893 if (GetContext() != aContext) {
michael@0 894 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 895 (" FAILED, given context doesn't match, GetContext()=%p",
michael@0 896 GetContext()));
michael@0 897 return FALSE;
michael@0 898 }
michael@0 899
michael@0 900 if (NS_SUCCEEDED(DeleteText(aOffset, (uint32_t)aNChars))) {
michael@0 901 return TRUE;
michael@0 902 }
michael@0 903
michael@0 904 // failed
michael@0 905 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 906 (" FAILED, cannot delete text"));
michael@0 907 return FALSE;
michael@0 908 }
michael@0 909
michael@0 910 /* static */
michael@0 911 void
michael@0 912 nsGtkIMModule::OnCommitCompositionCallback(GtkIMContext *aContext,
michael@0 913 const gchar *aString,
michael@0 914 nsGtkIMModule* aModule)
michael@0 915 {
michael@0 916 aModule->OnCommitCompositionNative(aContext, aString);
michael@0 917 }
michael@0 918
michael@0 919 void
michael@0 920 nsGtkIMModule::OnCommitCompositionNative(GtkIMContext *aContext,
michael@0 921 const gchar *aUTF8Char)
michael@0 922 {
michael@0 923 const gchar emptyStr = 0;
michael@0 924 const gchar *commitString = aUTF8Char ? aUTF8Char : &emptyStr;
michael@0 925
michael@0 926 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 927 ("GtkIMModule(%p): OnCommitCompositionNative, aContext=%p, current context=%p, commitString=\"%s\"",
michael@0 928 this, aContext, GetContext(), commitString));
michael@0 929
michael@0 930 // See bug 472635, we should do nothing if IM context doesn't match.
michael@0 931 if (GetContext() != aContext) {
michael@0 932 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 933 (" FAILED, given context doesn't match, GetContext()=%p",
michael@0 934 GetContext()));
michael@0 935 return;
michael@0 936 }
michael@0 937
michael@0 938 // If we are not in composition and committing with empty string,
michael@0 939 // we need to do nothing because if we continued to handle this
michael@0 940 // signal, we would dispatch compositionstart, text, compositionend
michael@0 941 // events with empty string. Of course, they are unnecessary events
michael@0 942 // for Web applications and our editor.
michael@0 943 if (!IsComposing() && !commitString[0]) {
michael@0 944 return;
michael@0 945 }
michael@0 946
michael@0 947 if (ShouldIgnoreNativeCompositionEvent()) {
michael@0 948 return;
michael@0 949 }
michael@0 950
michael@0 951 // If IME doesn't change their keyevent that generated this commit,
michael@0 952 // don't send it through XIM - just send it as a normal key press
michael@0 953 // event.
michael@0 954 if (!IsComposing() && mProcessingKeyEvent) {
michael@0 955 char keyval_utf8[8]; /* should have at least 6 bytes of space */
michael@0 956 gint keyval_utf8_len;
michael@0 957 guint32 keyval_unicode;
michael@0 958
michael@0 959 keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
michael@0 960 keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
michael@0 961 keyval_utf8[keyval_utf8_len] = '\0';
michael@0 962
michael@0 963 if (!strcmp(commitString, keyval_utf8)) {
michael@0 964 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 965 ("GtkIMModule(%p): OnCommitCompositionNative, we'll send normal key event",
michael@0 966 this));
michael@0 967 mFilterKeyEvent = false;
michael@0 968 return;
michael@0 969 }
michael@0 970 }
michael@0 971
michael@0 972 NS_ConvertUTF8toUTF16 str(commitString);
michael@0 973 CommitCompositionBy(str); // Be aware, widget can be gone
michael@0 974 }
michael@0 975
michael@0 976 bool
michael@0 977 nsGtkIMModule::CommitCompositionBy(const nsAString& aString)
michael@0 978 {
michael@0 979 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 980 ("GtkIMModule(%p): CommitCompositionBy, aString=\"%s\", "
michael@0 981 "mDispatchedCompositionString=\"%s\"",
michael@0 982 this, NS_ConvertUTF16toUTF8(aString).get(),
michael@0 983 NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
michael@0 984
michael@0 985 if (!DispatchTextEvent(aString, true)) {
michael@0 986 return false;
michael@0 987 }
michael@0 988 // We should dispatch the compositionend event here because some IMEs
michael@0 989 // might not fire "preedit_end" native event.
michael@0 990 return DispatchCompositionEnd(); // Be aware, widget can be gone
michael@0 991 }
michael@0 992
michael@0 993 void
michael@0 994 nsGtkIMModule::GetCompositionString(nsAString &aCompositionString)
michael@0 995 {
michael@0 996 gchar *preedit_string;
michael@0 997 gint cursor_pos;
michael@0 998 PangoAttrList *feedback_list;
michael@0 999 gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
michael@0 1000 &feedback_list, &cursor_pos);
michael@0 1001 if (preedit_string && *preedit_string) {
michael@0 1002 CopyUTF8toUTF16(preedit_string, aCompositionString);
michael@0 1003 } else {
michael@0 1004 aCompositionString.Truncate();
michael@0 1005 }
michael@0 1006
michael@0 1007 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1008 ("GtkIMModule(%p): GetCompositionString, result=\"%s\"",
michael@0 1009 this, preedit_string));
michael@0 1010
michael@0 1011 pango_attr_list_unref(feedback_list);
michael@0 1012 g_free(preedit_string);
michael@0 1013 }
michael@0 1014
michael@0 1015 bool
michael@0 1016 nsGtkIMModule::DispatchCompositionStart()
michael@0 1017 {
michael@0 1018 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1019 ("GtkIMModule(%p): DispatchCompositionStart", this));
michael@0 1020
michael@0 1021 if (IsComposing()) {
michael@0 1022 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1023 (" WARNING, we're already in composition"));
michael@0 1024 return true;
michael@0 1025 }
michael@0 1026
michael@0 1027 if (!mLastFocusedWindow) {
michael@0 1028 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1029 (" FAILED, there are no focused window in this module"));
michael@0 1030 return false;
michael@0 1031 }
michael@0 1032
michael@0 1033 nsEventStatus status;
michael@0 1034 WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
michael@0 1035 mLastFocusedWindow);
michael@0 1036 InitEvent(selection);
michael@0 1037 mLastFocusedWindow->DispatchEvent(&selection, status);
michael@0 1038
michael@0 1039 if (!selection.mSucceeded || selection.mReply.mOffset == UINT32_MAX) {
michael@0 1040 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1041 (" FAILED, cannot query the selection offset"));
michael@0 1042 return false;
michael@0 1043 }
michael@0 1044
michael@0 1045 // XXX The composition start point might be changed by composition events
michael@0 1046 // even though we strongly hope it doesn't happen.
michael@0 1047 // Every composition event should have the start offset for the result
michael@0 1048 // because it may high cost if we query the offset every time.
michael@0 1049 mCompositionStart = selection.mReply.mOffset;
michael@0 1050 mDispatchedCompositionString.Truncate();
michael@0 1051
michael@0 1052 if (mProcessingKeyEvent && !mKeyDownEventWasSent &&
michael@0 1053 mProcessingKeyEvent->type == GDK_KEY_PRESS) {
michael@0 1054 // If this composition is started by a native keydown event, we need to
michael@0 1055 // dispatch our keydown event here (before composition start).
michael@0 1056 nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
michael@0 1057 bool isCancelled;
michael@0 1058 mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
michael@0 1059 &isCancelled);
michael@0 1060 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1061 (" keydown event is dispatched"));
michael@0 1062 if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
michael@0 1063 kungFuDeathGrip != mLastFocusedWindow) {
michael@0 1064 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1065 (" NOTE, the focused widget was destroyed/changed by keydown event"));
michael@0 1066 return false;
michael@0 1067 }
michael@0 1068 }
michael@0 1069
michael@0 1070 if (mIgnoreNativeCompositionEvent) {
michael@0 1071 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1072 (" WARNING, mIgnoreNativeCompositionEvent is already TRUE, but we forcedly reset"));
michael@0 1073 mIgnoreNativeCompositionEvent = false;
michael@0 1074 }
michael@0 1075
michael@0 1076 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1077 (" mCompositionStart=%u", mCompositionStart));
michael@0 1078 mCompositionState = eCompositionState_CompositionStartDispatched;
michael@0 1079 WidgetCompositionEvent compEvent(true, NS_COMPOSITION_START,
michael@0 1080 mLastFocusedWindow);
michael@0 1081 InitEvent(compEvent);
michael@0 1082 nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
michael@0 1083 mLastFocusedWindow->DispatchEvent(&compEvent, status);
michael@0 1084 if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
michael@0 1085 kungFuDeathGrip != mLastFocusedWindow) {
michael@0 1086 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1087 (" NOTE, the focused widget was destroyed/changed by compositionstart event"));
michael@0 1088 return false;
michael@0 1089 }
michael@0 1090
michael@0 1091 return true;
michael@0 1092 }
michael@0 1093
michael@0 1094 bool
michael@0 1095 nsGtkIMModule::DispatchCompositionEnd()
michael@0 1096 {
michael@0 1097 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1098 ("GtkIMModule(%p): DispatchCompositionEnd, "
michael@0 1099 "mDispatchedCompositionString=\"%s\"",
michael@0 1100 this, NS_ConvertUTF16toUTF8(mDispatchedCompositionString).get()));
michael@0 1101
michael@0 1102 if (!IsComposing()) {
michael@0 1103 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1104 (" WARNING, we have alrady finished the composition"));
michael@0 1105 return false;
michael@0 1106 }
michael@0 1107
michael@0 1108 if (!mLastFocusedWindow) {
michael@0 1109 mDispatchedCompositionString.Truncate();
michael@0 1110 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1111 (" FAILED, there are no focused window in this module"));
michael@0 1112 return false;
michael@0 1113 }
michael@0 1114
michael@0 1115 WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END,
michael@0 1116 mLastFocusedWindow);
michael@0 1117 InitEvent(compEvent);
michael@0 1118 compEvent.data = mDispatchedCompositionString;
michael@0 1119 nsEventStatus status;
michael@0 1120 nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
michael@0 1121 mLastFocusedWindow->DispatchEvent(&compEvent, status);
michael@0 1122 mCompositionState = eCompositionState_NotComposing;
michael@0 1123 mCompositionStart = UINT32_MAX;
michael@0 1124 mCompositionTargetOffset = UINT32_MAX;
michael@0 1125 mDispatchedCompositionString.Truncate();
michael@0 1126 if (static_cast<nsWindow*>(kungFuDeathGrip.get())->IsDestroyed() ||
michael@0 1127 kungFuDeathGrip != mLastFocusedWindow) {
michael@0 1128 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1129 (" NOTE, the focused widget was destroyed/changed by compositionend event"));
michael@0 1130 return false;
michael@0 1131 }
michael@0 1132
michael@0 1133 return true;
michael@0 1134 }
michael@0 1135
michael@0 1136 bool
michael@0 1137 nsGtkIMModule::DispatchTextEvent(const nsAString &aCompositionString,
michael@0 1138 bool aIsCommit)
michael@0 1139 {
michael@0 1140 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1141 ("GtkIMModule(%p): DispatchTextEvent, aIsCommit=%s",
michael@0 1142 this, aIsCommit ? "TRUE" : "FALSE"));
michael@0 1143
michael@0 1144 if (!mLastFocusedWindow) {
michael@0 1145 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1146 (" FAILED, there are no focused window in this module"));
michael@0 1147 return false;
michael@0 1148 }
michael@0 1149
michael@0 1150 if (!IsComposing()) {
michael@0 1151 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1152 (" The composition wasn't started, force starting..."));
michael@0 1153 nsCOMPtr<nsIWidget> kungFuDeathGrip = mLastFocusedWindow;
michael@0 1154 if (!DispatchCompositionStart()) {
michael@0 1155 return false;
michael@0 1156 }
michael@0 1157 }
michael@0 1158
michael@0 1159 nsEventStatus status;
michael@0 1160 nsRefPtr<nsWindow> lastFocusedWindow = mLastFocusedWindow;
michael@0 1161
michael@0 1162 if (aCompositionString != mDispatchedCompositionString) {
michael@0 1163 WidgetCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
michael@0 1164 mLastFocusedWindow);
michael@0 1165 InitEvent(compositionUpdate);
michael@0 1166 compositionUpdate.data = aCompositionString;
michael@0 1167 mDispatchedCompositionString = aCompositionString;
michael@0 1168 mLastFocusedWindow->DispatchEvent(&compositionUpdate, status);
michael@0 1169 if (lastFocusedWindow->IsDestroyed() ||
michael@0 1170 lastFocusedWindow != mLastFocusedWindow) {
michael@0 1171 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1172 (" NOTE, the focused widget was destroyed/changed by compositionupdate"));
michael@0 1173 return false;
michael@0 1174 }
michael@0 1175 }
michael@0 1176
michael@0 1177 // Store the selected string which will be removed by following text event.
michael@0 1178 if (mCompositionState == eCompositionState_CompositionStartDispatched) {
michael@0 1179 // XXX We should assume, for now, any web applications don't change
michael@0 1180 // selection at handling this text event.
michael@0 1181 WidgetQueryContentEvent querySelectedTextEvent(true,
michael@0 1182 NS_QUERY_SELECTED_TEXT,
michael@0 1183 mLastFocusedWindow);
michael@0 1184 mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
michael@0 1185 if (querySelectedTextEvent.mSucceeded) {
michael@0 1186 mSelectedString = querySelectedTextEvent.mReply.mString;
michael@0 1187 mCompositionStart = querySelectedTextEvent.mReply.mOffset;
michael@0 1188 }
michael@0 1189 }
michael@0 1190
michael@0 1191 WidgetTextEvent textEvent(true, NS_TEXT_TEXT, mLastFocusedWindow);
michael@0 1192 InitEvent(textEvent);
michael@0 1193
michael@0 1194 uint32_t targetOffset = mCompositionStart;
michael@0 1195
michael@0 1196 if (!aIsCommit) {
michael@0 1197 // NOTE: SetTextRangeList() assumes that mDispatchedCompositionString
michael@0 1198 // has been updated already.
michael@0 1199 textEvent.mRanges = CreateTextRangeArray();
michael@0 1200 targetOffset += textEvent.mRanges->TargetClauseOffset();
michael@0 1201 }
michael@0 1202
michael@0 1203 textEvent.theText = mDispatchedCompositionString.get();
michael@0 1204
michael@0 1205 mCompositionState = aIsCommit ?
michael@0 1206 eCompositionState_CommitTextEventDispatched :
michael@0 1207 eCompositionState_TextEventDispatched;
michael@0 1208
michael@0 1209 mLastFocusedWindow->DispatchEvent(&textEvent, status);
michael@0 1210 if (lastFocusedWindow->IsDestroyed() ||
michael@0 1211 lastFocusedWindow != mLastFocusedWindow) {
michael@0 1212 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1213 (" NOTE, the focused widget was destroyed/changed by text event"));
michael@0 1214 return false;
michael@0 1215 }
michael@0 1216
michael@0 1217 // We cannot call SetCursorPosition for e10s-aware.
michael@0 1218 // DispatchEvent is async on e10s, so composition rect isn't updated now
michael@0 1219 // on tab parent.
michael@0 1220 mCompositionTargetOffset = targetOffset;
michael@0 1221
michael@0 1222 return true;
michael@0 1223 }
michael@0 1224
michael@0 1225 already_AddRefed<TextRangeArray>
michael@0 1226 nsGtkIMModule::CreateTextRangeArray()
michael@0 1227 {
michael@0 1228 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1229 ("GtkIMModule(%p): CreateTextRangeArray", this));
michael@0 1230
michael@0 1231 nsRefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
michael@0 1232
michael@0 1233 gchar *preedit_string;
michael@0 1234 gint cursor_pos;
michael@0 1235 PangoAttrList *feedback_list;
michael@0 1236 gtk_im_context_get_preedit_string(GetContext(), &preedit_string,
michael@0 1237 &feedback_list, &cursor_pos);
michael@0 1238 if (!preedit_string || !*preedit_string) {
michael@0 1239 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1240 (" preedit_string is null"));
michael@0 1241 pango_attr_list_unref(feedback_list);
michael@0 1242 g_free(preedit_string);
michael@0 1243 return textRangeArray.forget();
michael@0 1244 }
michael@0 1245
michael@0 1246 PangoAttrIterator* iter;
michael@0 1247 iter = pango_attr_list_get_iterator(feedback_list);
michael@0 1248 if (!iter) {
michael@0 1249 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1250 (" FAILED, iterator couldn't be allocated"));
michael@0 1251 pango_attr_list_unref(feedback_list);
michael@0 1252 g_free(preedit_string);
michael@0 1253 return textRangeArray.forget();
michael@0 1254 }
michael@0 1255
michael@0 1256 /*
michael@0 1257 * Depend on gtk2's implementation on XIM support.
michael@0 1258 * In aFeedback got from gtk2, there are only three types of data:
michael@0 1259 * PANGO_ATTR_UNDERLINE, PANGO_ATTR_FOREGROUND, PANGO_ATTR_BACKGROUND.
michael@0 1260 * Corresponding to XIMUnderline, XIMReverse.
michael@0 1261 * Don't take PANGO_ATTR_BACKGROUND into account, since
michael@0 1262 * PANGO_ATTR_BACKGROUND and PANGO_ATTR_FOREGROUND are always
michael@0 1263 * a couple.
michael@0 1264 */
michael@0 1265 do {
michael@0 1266 PangoAttribute* attrUnderline =
michael@0 1267 pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
michael@0 1268 PangoAttribute* attrForeground =
michael@0 1269 pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND);
michael@0 1270 if (!attrUnderline && !attrForeground) {
michael@0 1271 continue;
michael@0 1272 }
michael@0 1273
michael@0 1274 // Get the range of the current attribute(s)
michael@0 1275 gint start, end;
michael@0 1276 pango_attr_iterator_range(iter, &start, &end);
michael@0 1277
michael@0 1278 TextRange range;
michael@0 1279 // XIMReverse | XIMUnderline
michael@0 1280 if (attrUnderline && attrForeground) {
michael@0 1281 range.mRangeType = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
michael@0 1282 }
michael@0 1283 // XIMUnderline
michael@0 1284 else if (attrUnderline) {
michael@0 1285 range.mRangeType = NS_TEXTRANGE_CONVERTEDTEXT;
michael@0 1286 }
michael@0 1287 // XIMReverse
michael@0 1288 else if (attrForeground) {
michael@0 1289 range.mRangeType = NS_TEXTRANGE_SELECTEDRAWTEXT;
michael@0 1290 } else {
michael@0 1291 range.mRangeType = NS_TEXTRANGE_RAWINPUT;
michael@0 1292 }
michael@0 1293
michael@0 1294 gunichar2* uniStr = nullptr;
michael@0 1295 if (start == 0) {
michael@0 1296 range.mStartOffset = 0;
michael@0 1297 } else {
michael@0 1298 glong uniStrLen;
michael@0 1299 uniStr = g_utf8_to_utf16(preedit_string, start,
michael@0 1300 nullptr, &uniStrLen, nullptr);
michael@0 1301 if (uniStr) {
michael@0 1302 range.mStartOffset = uniStrLen;
michael@0 1303 g_free(uniStr);
michael@0 1304 uniStr = nullptr;
michael@0 1305 }
michael@0 1306 }
michael@0 1307
michael@0 1308 glong uniStrLen;
michael@0 1309 uniStr = g_utf8_to_utf16(preedit_string + start, end - start,
michael@0 1310 nullptr, &uniStrLen, nullptr);
michael@0 1311 if (!uniStr) {
michael@0 1312 range.mEndOffset = range.mStartOffset;
michael@0 1313 } else {
michael@0 1314 range.mEndOffset = range.mStartOffset + uniStrLen;
michael@0 1315 g_free(uniStr);
michael@0 1316 uniStr = nullptr;
michael@0 1317 }
michael@0 1318
michael@0 1319 textRangeArray->AppendElement(range);
michael@0 1320
michael@0 1321 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1322 (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
michael@0 1323 range.mStartOffset, range.mEndOffset,
michael@0 1324 GetRangeTypeName(range.mRangeType)));
michael@0 1325 } while (pango_attr_iterator_next(iter));
michael@0 1326
michael@0 1327 TextRange range;
michael@0 1328 if (cursor_pos < 0) {
michael@0 1329 range.mStartOffset = 0;
michael@0 1330 } else if (uint32_t(cursor_pos) > mDispatchedCompositionString.Length()) {
michael@0 1331 range.mStartOffset = mDispatchedCompositionString.Length();
michael@0 1332 } else {
michael@0 1333 range.mStartOffset = uint32_t(cursor_pos);
michael@0 1334 }
michael@0 1335 range.mEndOffset = range.mStartOffset;
michael@0 1336 range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
michael@0 1337 textRangeArray->AppendElement(range);
michael@0 1338
michael@0 1339 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1340 (" mStartOffset=%u, mEndOffset=%u, mRangeType=%s",
michael@0 1341 range.mStartOffset, range.mEndOffset,
michael@0 1342 GetRangeTypeName(range.mRangeType)));
michael@0 1343
michael@0 1344 pango_attr_iterator_destroy(iter);
michael@0 1345 pango_attr_list_unref(feedback_list);
michael@0 1346 g_free(preedit_string);
michael@0 1347
michael@0 1348 return textRangeArray.forget();
michael@0 1349 }
michael@0 1350
michael@0 1351 void
michael@0 1352 nsGtkIMModule::SetCursorPosition(uint32_t aTargetOffset)
michael@0 1353 {
michael@0 1354 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1355 ("GtkIMModule(%p): SetCursorPosition, aTargetOffset=%u",
michael@0 1356 this, aTargetOffset));
michael@0 1357
michael@0 1358 if (aTargetOffset == UINT32_MAX) {
michael@0 1359 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1360 (" FAILED, aTargetOffset is wrong offset"));
michael@0 1361 return;
michael@0 1362 }
michael@0 1363
michael@0 1364 if (!mLastFocusedWindow) {
michael@0 1365 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1366 (" FAILED, there are no focused window"));
michael@0 1367 return;
michael@0 1368 }
michael@0 1369
michael@0 1370 GtkIMContext *im = GetContext();
michael@0 1371 if (!im) {
michael@0 1372 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1373 (" FAILED, there are no context"));
michael@0 1374 return;
michael@0 1375 }
michael@0 1376
michael@0 1377 WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT,
michael@0 1378 mLastFocusedWindow);
michael@0 1379 charRect.InitForQueryTextRect(aTargetOffset, 1);
michael@0 1380 InitEvent(charRect);
michael@0 1381 nsEventStatus status;
michael@0 1382 mLastFocusedWindow->DispatchEvent(&charRect, status);
michael@0 1383 if (!charRect.mSucceeded) {
michael@0 1384 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1385 (" FAILED, NS_QUERY_TEXT_RECT was failed"));
michael@0 1386 return;
michael@0 1387 }
michael@0 1388 nsWindow* rootWindow =
michael@0 1389 static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
michael@0 1390
michael@0 1391 // Get the position of the rootWindow in screen.
michael@0 1392 gint rootX, rootY;
michael@0 1393 gdk_window_get_origin(rootWindow->GetGdkWindow(), &rootX, &rootY);
michael@0 1394
michael@0 1395 // Get the position of IM context owner window in screen.
michael@0 1396 gint ownerX, ownerY;
michael@0 1397 gdk_window_get_origin(mOwnerWindow->GetGdkWindow(), &ownerX, &ownerY);
michael@0 1398
michael@0 1399 // Compute the caret position in the IM owner window.
michael@0 1400 GdkRectangle area;
michael@0 1401 area.x = charRect.mReply.mRect.x + rootX - ownerX;
michael@0 1402 area.y = charRect.mReply.mRect.y + rootY - ownerY;
michael@0 1403 area.width = 0;
michael@0 1404 area.height = charRect.mReply.mRect.height;
michael@0 1405
michael@0 1406 gtk_im_context_set_cursor_location(im, &area);
michael@0 1407 }
michael@0 1408
michael@0 1409 nsresult
michael@0 1410 nsGtkIMModule::GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos)
michael@0 1411 {
michael@0 1412 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1413 ("GtkIMModule(%p): GetCurrentParagraph, mCompositionState=%s",
michael@0 1414 this, GetCompositionStateName()));
michael@0 1415
michael@0 1416 if (!mLastFocusedWindow) {
michael@0 1417 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1418 (" FAILED, there are no focused window in this module"));
michael@0 1419 return NS_ERROR_NULL_POINTER;
michael@0 1420 }
michael@0 1421
michael@0 1422 nsEventStatus status;
michael@0 1423
michael@0 1424 uint32_t selOffset = mCompositionStart;
michael@0 1425 uint32_t selLength = mSelectedString.Length();
michael@0 1426
michael@0 1427 // If focused editor doesn't have composition string, we should use
michael@0 1428 // current selection.
michael@0 1429 if (!EditorHasCompositionString()) {
michael@0 1430 // Query cursor position & selection
michael@0 1431 WidgetQueryContentEvent querySelectedTextEvent(true,
michael@0 1432 NS_QUERY_SELECTED_TEXT,
michael@0 1433 mLastFocusedWindow);
michael@0 1434 mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
michael@0 1435 NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
michael@0 1436
michael@0 1437 selOffset = querySelectedTextEvent.mReply.mOffset;
michael@0 1438 selLength = querySelectedTextEvent.mReply.mString.Length();
michael@0 1439 }
michael@0 1440
michael@0 1441 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1442 (" selOffset=%u, selLength=%u",
michael@0 1443 selOffset, selLength));
michael@0 1444
michael@0 1445 // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
michael@0 1446 // we cannot support this request when the current offset is larger
michael@0 1447 // than INT32_MAX.
michael@0 1448 if (selOffset > INT32_MAX || selLength > INT32_MAX ||
michael@0 1449 selOffset + selLength > INT32_MAX) {
michael@0 1450 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1451 (" FAILED, The selection is out of range"));
michael@0 1452 return NS_ERROR_FAILURE;
michael@0 1453 }
michael@0 1454
michael@0 1455 // Get all text contents of the focused editor
michael@0 1456 WidgetQueryContentEvent queryTextContentEvent(true,
michael@0 1457 NS_QUERY_TEXT_CONTENT,
michael@0 1458 mLastFocusedWindow);
michael@0 1459 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
michael@0 1460 mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
michael@0 1461 NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
michael@0 1462
michael@0 1463 nsAutoString textContent(queryTextContentEvent.mReply.mString);
michael@0 1464 if (selOffset + selLength > textContent.Length()) {
michael@0 1465 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1466 (" FAILED, The selection is invalid, textContent.Length()=%u",
michael@0 1467 textContent.Length()));
michael@0 1468 return NS_ERROR_FAILURE;
michael@0 1469 }
michael@0 1470
michael@0 1471 // Remove composing string and restore the selected string because
michael@0 1472 // GtkEntry doesn't remove selected string until committing, however,
michael@0 1473 // our editor does it. We should emulate the behavior for IME.
michael@0 1474 if (EditorHasCompositionString() &&
michael@0 1475 mDispatchedCompositionString != mSelectedString) {
michael@0 1476 textContent.Replace(mCompositionStart,
michael@0 1477 mDispatchedCompositionString.Length(), mSelectedString);
michael@0 1478 }
michael@0 1479
michael@0 1480 // Get only the focused paragraph, by looking for newlines
michael@0 1481 int32_t parStart = (selOffset == 0) ? 0 :
michael@0 1482 textContent.RFind("\n", false, selOffset - 1, -1) + 1;
michael@0 1483 int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
michael@0 1484 if (parEnd < 0) {
michael@0 1485 parEnd = textContent.Length();
michael@0 1486 }
michael@0 1487 aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
michael@0 1488 aCursorPos = selOffset - uint32_t(parStart);
michael@0 1489
michael@0 1490 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1491 (" aText=%s, aText.Length()=%u, aCursorPos=%u",
michael@0 1492 NS_ConvertUTF16toUTF8(aText).get(),
michael@0 1493 aText.Length(), aCursorPos));
michael@0 1494
michael@0 1495 return NS_OK;
michael@0 1496 }
michael@0 1497
michael@0 1498 nsresult
michael@0 1499 nsGtkIMModule::DeleteText(const int32_t aOffset, const uint32_t aNChars)
michael@0 1500 {
michael@0 1501 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1502 ("GtkIMModule(%p): DeleteText, aOffset=%d, aNChars=%d, "
michael@0 1503 "mCompositionState=%s",
michael@0 1504 this, aOffset, aNChars, GetCompositionStateName()));
michael@0 1505
michael@0 1506 if (!mLastFocusedWindow) {
michael@0 1507 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1508 (" FAILED, there are no focused window in this module"));
michael@0 1509 return NS_ERROR_NULL_POINTER;
michael@0 1510 }
michael@0 1511
michael@0 1512 if (!aNChars) {
michael@0 1513 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1514 (" FAILED, aNChars must not be zero"));
michael@0 1515 return NS_ERROR_INVALID_ARG;
michael@0 1516 }
michael@0 1517
michael@0 1518 nsRefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
michael@0 1519 nsEventStatus status;
michael@0 1520
michael@0 1521 // First, we should cancel current composition because editor cannot
michael@0 1522 // handle changing selection and deleting text.
michael@0 1523 uint32_t selOffset;
michael@0 1524 bool wasComposing = IsComposing();
michael@0 1525 bool editorHadCompositionString = EditorHasCompositionString();
michael@0 1526 if (wasComposing) {
michael@0 1527 selOffset = mCompositionStart;
michael@0 1528 if (editorHadCompositionString &&
michael@0 1529 !DispatchTextEvent(mSelectedString, false)) {
michael@0 1530 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1531 (" FAILED, quitting from DeletText"));
michael@0 1532 return NS_ERROR_FAILURE;
michael@0 1533 }
michael@0 1534 if (!DispatchCompositionEnd()) {
michael@0 1535 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1536 (" FAILED, quitting from DeletText"));
michael@0 1537 return NS_ERROR_FAILURE;
michael@0 1538 }
michael@0 1539 } else {
michael@0 1540 // Query cursor position & selection
michael@0 1541 WidgetQueryContentEvent querySelectedTextEvent(true,
michael@0 1542 NS_QUERY_SELECTED_TEXT,
michael@0 1543 mLastFocusedWindow);
michael@0 1544 lastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
michael@0 1545 NS_ENSURE_TRUE(querySelectedTextEvent.mSucceeded, NS_ERROR_FAILURE);
michael@0 1546
michael@0 1547 selOffset = querySelectedTextEvent.mReply.mOffset;
michael@0 1548 }
michael@0 1549
michael@0 1550 // Get all text contents of the focused editor
michael@0 1551 WidgetQueryContentEvent queryTextContentEvent(true,
michael@0 1552 NS_QUERY_TEXT_CONTENT,
michael@0 1553 mLastFocusedWindow);
michael@0 1554 queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
michael@0 1555 mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
michael@0 1556 NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
michael@0 1557 if (queryTextContentEvent.mReply.mString.IsEmpty()) {
michael@0 1558 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1559 (" FAILED, there is no contents"));
michael@0 1560 return NS_ERROR_FAILURE;
michael@0 1561 }
michael@0 1562
michael@0 1563 NS_ConvertUTF16toUTF8 utf8Str(
michael@0 1564 nsDependentSubstring(queryTextContentEvent.mReply.mString,
michael@0 1565 0, selOffset));
michael@0 1566 glong offsetInUTF8Characters =
michael@0 1567 g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
michael@0 1568 if (offsetInUTF8Characters < 0) {
michael@0 1569 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1570 (" FAILED, aOffset is too small for current cursor pos "
michael@0 1571 "(computed offset: %d)",
michael@0 1572 offsetInUTF8Characters));
michael@0 1573 return NS_ERROR_FAILURE;
michael@0 1574 }
michael@0 1575
michael@0 1576 AppendUTF16toUTF8(
michael@0 1577 nsDependentSubstring(queryTextContentEvent.mReply.mString, selOffset),
michael@0 1578 utf8Str);
michael@0 1579 glong countOfCharactersInUTF8 =
michael@0 1580 g_utf8_strlen(utf8Str.get(), utf8Str.Length());
michael@0 1581 glong endInUTF8Characters =
michael@0 1582 offsetInUTF8Characters + aNChars;
michael@0 1583 if (countOfCharactersInUTF8 < endInUTF8Characters) {
michael@0 1584 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1585 (" FAILED, aNChars is too large for current contents "
michael@0 1586 "(content length: %d, computed end offset: %d)",
michael@0 1587 countOfCharactersInUTF8, endInUTF8Characters));
michael@0 1588 return NS_ERROR_FAILURE;
michael@0 1589 }
michael@0 1590
michael@0 1591 gchar* charAtOffset =
michael@0 1592 g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
michael@0 1593 gchar* charAtEnd =
michael@0 1594 g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
michael@0 1595
michael@0 1596 // Set selection to delete
michael@0 1597 WidgetSelectionEvent selectionEvent(true, NS_SELECTION_SET,
michael@0 1598 mLastFocusedWindow);
michael@0 1599
michael@0 1600 nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
michael@0 1601 charAtOffset - utf8Str.get());
michael@0 1602 selectionEvent.mOffset =
michael@0 1603 NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
michael@0 1604
michael@0 1605 nsDependentCSubstring utf8DeletingStr(utf8Str,
michael@0 1606 utf8StrBeforeOffset.Length(),
michael@0 1607 charAtEnd - charAtOffset);
michael@0 1608 selectionEvent.mLength =
michael@0 1609 NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
michael@0 1610
michael@0 1611 selectionEvent.mReversed = false;
michael@0 1612 selectionEvent.mExpandToClusterBoundary = false;
michael@0 1613 lastFocusedWindow->DispatchEvent(&selectionEvent, status);
michael@0 1614
michael@0 1615 if (!selectionEvent.mSucceeded ||
michael@0 1616 lastFocusedWindow != mLastFocusedWindow ||
michael@0 1617 lastFocusedWindow->Destroyed()) {
michael@0 1618 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1619 (" FAILED, setting selection caused focus change "
michael@0 1620 "or window destroyed"));
michael@0 1621 return NS_ERROR_FAILURE;
michael@0 1622 }
michael@0 1623
michael@0 1624 // Delete the selection
michael@0 1625 WidgetContentCommandEvent contentCommandEvent(true,
michael@0 1626 NS_CONTENT_COMMAND_DELETE,
michael@0 1627 mLastFocusedWindow);
michael@0 1628 mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
michael@0 1629
michael@0 1630 if (!contentCommandEvent.mSucceeded ||
michael@0 1631 lastFocusedWindow != mLastFocusedWindow ||
michael@0 1632 lastFocusedWindow->Destroyed()) {
michael@0 1633 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1634 (" FAILED, deleting the selection caused focus change "
michael@0 1635 "or window destroyed"));
michael@0 1636 return NS_ERROR_FAILURE;
michael@0 1637 }
michael@0 1638
michael@0 1639 if (!wasComposing) {
michael@0 1640 return NS_OK;
michael@0 1641 }
michael@0 1642
michael@0 1643 // Restore the composition at new caret position.
michael@0 1644 if (!DispatchCompositionStart()) {
michael@0 1645 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1646 (" FAILED, resterting composition start"));
michael@0 1647 return NS_ERROR_FAILURE;
michael@0 1648 }
michael@0 1649
michael@0 1650 if (!editorHadCompositionString) {
michael@0 1651 return NS_OK;
michael@0 1652 }
michael@0 1653
michael@0 1654 nsAutoString compositionString;
michael@0 1655 GetCompositionString(compositionString);
michael@0 1656 if (!DispatchTextEvent(compositionString, true)) {
michael@0 1657 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1658 (" FAILED, restoring composition string"));
michael@0 1659 return NS_ERROR_FAILURE;
michael@0 1660 }
michael@0 1661
michael@0 1662 return NS_OK;
michael@0 1663 }
michael@0 1664
michael@0 1665 void
michael@0 1666 nsGtkIMModule::InitEvent(WidgetGUIEvent& aEvent)
michael@0 1667 {
michael@0 1668 aEvent.time = PR_Now() / 1000;
michael@0 1669 }
michael@0 1670
michael@0 1671 bool
michael@0 1672 nsGtkIMModule::ShouldIgnoreNativeCompositionEvent()
michael@0 1673 {
michael@0 1674 PR_LOG(gGtkIMLog, PR_LOG_ALWAYS,
michael@0 1675 ("GtkIMModule(%p): ShouldIgnoreNativeCompositionEvent, mLastFocusedWindow=%p, mIgnoreNativeCompositionEvent=%s",
michael@0 1676 this, mLastFocusedWindow,
michael@0 1677 mIgnoreNativeCompositionEvent ? "YES" : "NO"));
michael@0 1678
michael@0 1679 if (!mLastFocusedWindow) {
michael@0 1680 return true; // cannot continue
michael@0 1681 }
michael@0 1682
michael@0 1683 return mIgnoreNativeCompositionEvent;
michael@0 1684 }

mercurial