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