Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim:expandtab:shiftwidth=4:tabstop=4:
3 : */
4 : /* This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 :
8 : #ifndef IMContextWrapper_h_
9 : #define IMContextWrapper_h_
10 :
11 : #include <gdk/gdk.h>
12 : #include <gtk/gtk.h>
13 :
14 : #include "nsString.h"
15 : #include "nsCOMPtr.h"
16 : #include "nsTArray.h"
17 : #include "nsIWidget.h"
18 : #include "mozilla/CheckedInt.h"
19 : #include "mozilla/EventForwards.h"
20 : #include "mozilla/TextEventDispatcherListener.h"
21 : #include "WritingModes.h"
22 :
23 : class nsWindow;
24 :
25 : namespace mozilla {
26 : namespace widget {
27 :
28 : class IMContextWrapper final : public TextEventDispatcherListener
29 : {
30 : public:
31 : // TextEventDispatcherListener implementation
32 : NS_DECL_ISUPPORTS
33 :
34 : NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
35 : const IMENotification& aNotification) override;
36 : NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
37 : NS_IMETHOD_(void) OnRemovedFrom(
38 : TextEventDispatcher* aTextEventDispatcher) override;
39 : NS_IMETHOD_(void) WillDispatchKeyboardEvent(
40 : TextEventDispatcher* aTextEventDispatcher,
41 : WidgetKeyboardEvent& aKeyboardEvent,
42 : uint32_t aIndexOfKeypress,
43 : void* aData) override;
44 :
45 : public:
46 : // aOwnerWindow is a pointer of the owner window. When aOwnerWindow is
47 : // destroyed, the related IME contexts are released (i.e., IME cannot be
48 : // used with the instance after that).
49 : explicit IMContextWrapper(nsWindow* aOwnerWindow);
50 :
51 : // "Enabled" means the users can use all IMEs.
52 : // I.e., the focus is in the normal editors.
53 : bool IsEnabled() const;
54 :
55 : // OnFocusWindow is a notification that aWindow is going to be focused.
56 : void OnFocusWindow(nsWindow* aWindow);
57 : // OnBlurWindow is a notification that aWindow is going to be unfocused.
58 : void OnBlurWindow(nsWindow* aWindow);
59 : // OnDestroyWindow is a notification that aWindow is going to be destroyed.
60 : void OnDestroyWindow(nsWindow* aWindow);
61 : // OnFocusChangeInGecko is a notification that an editor gets focus.
62 : void OnFocusChangeInGecko(bool aFocus);
63 : // OnSelectionChange is a notification that selection (caret) is changed
64 : // in the focused editor.
65 : void OnSelectionChange(nsWindow* aCaller,
66 : const IMENotification& aIMENotification);
67 :
68 : // OnKeyEvent is called when aWindow gets a native key press event or a
69 : // native key release event. If this returns TRUE, the key event was
70 : // filtered by IME. Otherwise, this returns FALSE.
71 : // NOTE: When the keypress event starts composition, this returns TRUE but
72 : // this dispatches keydown event before compositionstart event.
73 : bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
74 : bool aKeyDownEventWasSent = false);
75 :
76 : // IME related nsIWidget methods.
77 : nsresult EndIMEComposition(nsWindow* aCaller);
78 : void SetInputContext(nsWindow* aCaller,
79 : const InputContext* aContext,
80 : const InputContextAction* aAction);
81 : InputContext GetInputContext();
82 : void OnUpdateComposition();
83 : void OnLayoutChange();
84 :
85 : TextEventDispatcher* GetTextEventDispatcher();
86 :
87 : protected:
88 : ~IMContextWrapper();
89 :
90 : // Owner of an instance of this class. This should be top level window.
91 : // The owner window must release the contexts when it's destroyed because
92 : // the IME contexts need the native window. If OnDestroyWindow() is called
93 : // with the owner window, it'll release IME contexts. Otherwise, it'll
94 : // just clean up any existing composition if it's related to the destroying
95 : // child window.
96 : nsWindow* mOwnerWindow;
97 :
98 : // A last focused window in this class's context.
99 : nsWindow* mLastFocusedWindow;
100 :
101 : // Actual context. This is used for handling the user's input.
102 : GtkIMContext* mContext;
103 :
104 : // mSimpleContext is used for the password field and
105 : // the |ime-mode: disabled;| editors if sUseSimpleContext is true.
106 : // These editors disable IME. But dead keys should work. Fortunately,
107 : // the simple IM context of GTK2 support only them.
108 : GtkIMContext* mSimpleContext;
109 :
110 : // mDummyContext is a dummy context and will be used in Focus()
111 : // when the state of mEnabled means disabled. This context's IME state is
112 : // always "closed", so it closes IME forcedly.
113 : GtkIMContext* mDummyContext;
114 :
115 : // mComposingContext is not nullptr while one of mContext, mSimpleContext
116 : // and mDummyContext has composition.
117 : // XXX: We don't assume that two or more context have composition same time.
118 : GtkIMContext* mComposingContext;
119 :
120 : // IME enabled state and other things defined in InputContext.
121 : // Use following helper methods if you don't need the detail of the status.
122 : InputContext mInputContext;
123 :
124 : // mCompositionStart is the start offset of the composition string in the
125 : // current content. When <textarea> or <input> have focus, it means offset
126 : // from the first character of them. When a HTML editor has focus, it
127 : // means offset from the first character of the root element of the editor.
128 : uint32_t mCompositionStart;
129 :
130 : // mDispatchedCompositionString is the latest composition string which
131 : // was dispatched by compositionupdate event.
132 : nsString mDispatchedCompositionString;
133 :
134 : // mSelectedStringRemovedByComposition is the selected string which was
135 : // removed by first compositionchange event.
136 : nsString mSelectedStringRemovedByComposition;
137 :
138 : // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
139 : // event.
140 : GdkEventKey* mProcessingKeyEvent;
141 :
142 : struct Range
143 : {
144 : uint32_t mOffset;
145 : uint32_t mLength;
146 :
147 2 : Range()
148 2 : : mOffset(UINT32_MAX)
149 2 : , mLength(UINT32_MAX)
150 : {
151 2 : }
152 :
153 0 : bool IsValid() const { return mOffset != UINT32_MAX; }
154 0 : void Clear()
155 : {
156 0 : mOffset = UINT32_MAX;
157 0 : mLength = UINT32_MAX;
158 0 : }
159 : };
160 :
161 : // current target offset and length of IME composition
162 : Range mCompositionTargetRange;
163 :
164 : // mCompositionState indicates current status of composition.
165 : enum eCompositionState {
166 : eCompositionState_NotComposing,
167 : eCompositionState_CompositionStartDispatched,
168 : eCompositionState_CompositionChangeEventDispatched
169 : };
170 : eCompositionState mCompositionState;
171 :
172 0 : bool IsComposing() const
173 : {
174 0 : return (mCompositionState != eCompositionState_NotComposing);
175 : }
176 :
177 0 : bool IsComposingOn(GtkIMContext* aContext) const
178 : {
179 0 : return IsComposing() && mComposingContext == aContext;
180 : }
181 :
182 0 : bool IsComposingOnCurrentContext() const
183 : {
184 0 : return IsComposingOn(GetCurrentContext());
185 : }
186 :
187 0 : bool EditorHasCompositionString()
188 : {
189 0 : return (mCompositionState ==
190 0 : eCompositionState_CompositionChangeEventDispatched);
191 : }
192 :
193 : /**
194 : * Checks if aContext is valid context for handling composition.
195 : *
196 : * @param aContext An IM context which is specified by native
197 : * composition events.
198 : * @return true if the context is valid context for
199 : * handling composition. Otherwise, false.
200 : */
201 : bool IsValidContext(GtkIMContext* aContext) const;
202 :
203 0 : const char* GetCompositionStateName()
204 : {
205 0 : switch (mCompositionState) {
206 : case eCompositionState_NotComposing:
207 0 : return "NotComposing";
208 : case eCompositionState_CompositionStartDispatched:
209 0 : return "CompositionStartDispatched";
210 : case eCompositionState_CompositionChangeEventDispatched:
211 0 : return "CompositionChangeEventDispatched";
212 : default:
213 0 : return "InvaildState";
214 : }
215 : }
216 :
217 0 : struct Selection final
218 : {
219 : nsString mString;
220 : uint32_t mOffset;
221 : WritingMode mWritingMode;
222 :
223 2 : Selection()
224 2 : : mOffset(UINT32_MAX)
225 : {
226 2 : }
227 :
228 0 : void Clear()
229 : {
230 0 : mString.Truncate();
231 0 : mOffset = UINT32_MAX;
232 0 : mWritingMode = WritingMode();
233 0 : }
234 0 : void CollapseTo(uint32_t aOffset,
235 : const WritingMode& aWritingMode)
236 : {
237 0 : mWritingMode = aWritingMode;
238 0 : mOffset = aOffset;
239 0 : mString.Truncate();
240 0 : }
241 :
242 : void Assign(const IMENotification& aIMENotification);
243 : void Assign(const WidgetQueryContentEvent& aSelectedTextEvent);
244 :
245 0 : bool IsValid() const { return mOffset != UINT32_MAX; }
246 0 : bool Collapsed() const { return mString.IsEmpty(); }
247 0 : uint32_t Length() const { return mString.Length(); }
248 : uint32_t EndOffset() const
249 : {
250 : if (NS_WARN_IF(!IsValid())) {
251 : return UINT32_MAX;
252 : }
253 : CheckedInt<uint32_t> endOffset =
254 : CheckedInt<uint32_t>(mOffset) + mString.Length();
255 : if (NS_WARN_IF(!endOffset.isValid())) {
256 : return UINT32_MAX;
257 : }
258 : return endOffset.value();
259 : }
260 : } mSelection;
261 : bool EnsureToCacheSelection(nsAString* aSelectedString = nullptr);
262 :
263 : // mIsIMFocused is set to TRUE when we call gtk_im_context_focus_in(). And
264 : // it's set to FALSE when we call gtk_im_context_focus_out().
265 : bool mIsIMFocused;
266 : // mFilterKeyEvent is used by OnKeyEvent(). If the commit event should
267 : // be processed as simple key event, this is set to TRUE by the commit
268 : // handler.
269 : bool mFilterKeyEvent;
270 : // mKeyDownEventWasSent is used by OnKeyEvent() and
271 : // DispatchCompositionStart(). DispatchCompositionStart() dispatches
272 : // a keydown event if the composition start is caused by a native
273 : // keypress event. If this is true, the keydown event has been dispatched.
274 : // Then, DispatchCompositionStart() doesn't dispatch keydown event.
275 : bool mKeyDownEventWasSent;
276 : // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is
277 : // trying to delete the surrounding text.
278 : bool mIsDeletingSurrounding;
279 : // mLayoutChanged is true after OnLayoutChange() is called. This is reset
280 : // when eCompositionChange is being dispatched.
281 : bool mLayoutChanged;
282 : // mSetCursorPositionOnKeyEvent true when caret rect or position is updated
283 : // with no composition. If true, we update candidate window position
284 : // before key down
285 : bool mSetCursorPositionOnKeyEvent;
286 : // mPendingResettingIMContext becomes true if selection change notification
287 : // is received during composition but the selection change occurred before
288 : // starting the composition. In such case, we cannot notify IME of
289 : // selection change during composition because we don't want to commit
290 : // the composition in such case. However, we should notify IME of the
291 : // selection change after the composition is committed.
292 : bool mPendingResettingIMContext;
293 : // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding"
294 : // signal is received until selection is changed in Gecko.
295 : bool mRetrieveSurroundingSignalReceived;
296 :
297 : // sLastFocusedContext is a pointer to the last focused instance of this
298 : // class. When a instance is destroyed and sLastFocusedContext refers it,
299 : // this is cleared. So, this refers valid pointer always.
300 : static IMContextWrapper* sLastFocusedContext;
301 :
302 : // sUseSimpleContext indeicates if password editors and editors with
303 : // |ime-mode: disabled;| should use GtkIMContextSimple.
304 : // If true, they use GtkIMContextSimple. Otherwise, not.
305 : static bool sUseSimpleContext;
306 :
307 : // Callback methods for native IME events. These methods should call
308 : // the related instance methods simply.
309 : static gboolean OnRetrieveSurroundingCallback(GtkIMContext* aContext,
310 : IMContextWrapper* aModule);
311 : static gboolean OnDeleteSurroundingCallback(GtkIMContext* aContext,
312 : gint aOffset,
313 : gint aNChars,
314 : IMContextWrapper* aModule);
315 : static void OnCommitCompositionCallback(GtkIMContext* aContext,
316 : const gchar* aString,
317 : IMContextWrapper* aModule);
318 : static void OnChangeCompositionCallback(GtkIMContext* aContext,
319 : IMContextWrapper* aModule);
320 : static void OnStartCompositionCallback(GtkIMContext* aContext,
321 : IMContextWrapper* aModule);
322 : static void OnEndCompositionCallback(GtkIMContext* aContext,
323 : IMContextWrapper* aModule);
324 :
325 : // The instance methods for the native IME events.
326 : gboolean OnRetrieveSurroundingNative(GtkIMContext* aContext);
327 : gboolean OnDeleteSurroundingNative(GtkIMContext* aContext,
328 : gint aOffset,
329 : gint aNChars);
330 : void OnCommitCompositionNative(GtkIMContext* aContext,
331 : const gchar* aString);
332 : void OnChangeCompositionNative(GtkIMContext* aContext);
333 : void OnStartCompositionNative(GtkIMContext* aContext);
334 : void OnEndCompositionNative(GtkIMContext* aContext);
335 :
336 : /**
337 : * GetCurrentContext() returns current IM context which is chosen with the
338 : * enabled state.
339 : * WARNING:
340 : * When this class receives some signals for a composition after focus
341 : * is moved in Gecko, the result of this may be different from given
342 : * context by the signals.
343 : */
344 : GtkIMContext* GetCurrentContext() const;
345 :
346 : /**
347 : * GetActiveContext() returns a composing context or current context.
348 : */
349 0 : GtkIMContext* GetActiveContext() const
350 : {
351 0 : return mComposingContext ? mComposingContext : GetCurrentContext();
352 : }
353 :
354 : // If the owner window and IM context have been destroyed, returns TRUE.
355 4 : bool IsDestroyed() { return !mOwnerWindow; }
356 :
357 : // Sets focus to the instance of this class.
358 : void Focus();
359 :
360 : // Steals focus from the instance of this class.
361 : void Blur();
362 :
363 : // Initializes the instance.
364 : void Init();
365 :
366 : // Reset the current composition of IME. All native composition events
367 : // during this processing are ignored.
368 : void ResetIME();
369 :
370 : // Gets the current composition string by the native APIs.
371 : void GetCompositionString(GtkIMContext* aContext,
372 : nsAString& aCompositionString);
373 :
374 : /**
375 : * Generates our text range array from current composition string.
376 : *
377 : * @param aContext A GtkIMContext which is being handled.
378 : * @param aCompositionString The data to be dispatched with
379 : * compositionchange event.
380 : */
381 : already_AddRefed<TextRangeArray>
382 : CreateTextRangeArray(GtkIMContext* aContext,
383 : const nsAString& aCompositionString);
384 :
385 : /**
386 : * SetTextRange() initializes aTextRange with aPangoAttrIter.
387 : *
388 : * @param aPangoAttrIter An iter which represents a clause of the
389 : * composition string.
390 : * @param aUTF8CompositionString The whole composition string (UTF-8).
391 : * @param aUTF16CaretOffset The caret offset in the composition
392 : * string encoded as UTF-16.
393 : * @param aTextRange The result.
394 : * @return true if this initializes aTextRange.
395 : * Otherwise, false.
396 : */
397 : bool SetTextRange(PangoAttrIterator* aPangoAttrIter,
398 : const gchar* aUTF8CompositionString,
399 : uint32_t aUTF16CaretOffset,
400 : TextRange& aTextRange) const;
401 :
402 : /**
403 : * ToNscolor() converts the PangoColor in aPangoAttrColor to nscolor.
404 : */
405 : static nscolor ToNscolor(PangoAttrColor* aPangoAttrColor);
406 :
407 : /**
408 : * Move the candidate window with "fake" cursor position.
409 : *
410 : * @param aContext A GtkIMContext which is being handled.
411 : */
412 : void SetCursorPosition(GtkIMContext* aContext);
413 :
414 : // Queries the current selection offset of the window.
415 : uint32_t GetSelectionOffset(nsWindow* aWindow);
416 :
417 : // Get current paragraph text content and cursor position
418 : nsresult GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos);
419 :
420 : /**
421 : * Delete text portion
422 : *
423 : * @param aContext A GtkIMContext which is being handled.
424 : * @param aOffset Start offset of the range to delete.
425 : * @param aNChars Count of characters to delete. It depends
426 : * on |g_utf8_strlen()| what is one character.
427 : */
428 : nsresult DeleteText(GtkIMContext* aContext,
429 : int32_t aOffset,
430 : uint32_t aNChars);
431 :
432 : // Initializes the GUI event.
433 : void InitEvent(WidgetGUIEvent& aEvent);
434 :
435 : // Called before destroying the context to work around some platform bugs.
436 : void PrepareToDestroyContext(GtkIMContext* aContext);
437 :
438 : /**
439 : * WARNING:
440 : * Following methods dispatch gecko events. Then, the focused widget
441 : * can be destroyed, and also it can be stolen focus. If they returns
442 : * FALSE, callers cannot continue the composition.
443 : * - DispatchCompositionStart
444 : * - DispatchCompositionChangeEvent
445 : * - DispatchCompositionCommitEvent
446 : */
447 :
448 : /**
449 : * Dispatches a composition start event.
450 : *
451 : * @param aContext A GtkIMContext which is being handled.
452 : * @return true if the focused widget is neither
453 : * destroyed nor changed. Otherwise, false.
454 : */
455 : bool DispatchCompositionStart(GtkIMContext* aContext);
456 :
457 : /**
458 : * Dispatches a compositionchange event.
459 : *
460 : * @param aContext A GtkIMContext which is being handled.
461 : * @param aCompositionString New composition string.
462 : * @return true if the focused widget is neither
463 : * destroyed nor changed. Otherwise, false.
464 : */
465 : bool DispatchCompositionChangeEvent(GtkIMContext* aContext,
466 : const nsAString& aCompositionString);
467 :
468 : /**
469 : * Dispatches a compositioncommit event or compositioncommitasis event.
470 : *
471 : * @param aContext A GtkIMContext which is being handled.
472 : * @param aCommitString If this is nullptr, the composition will
473 : * be committed with last dispatched data.
474 : * Otherwise, the composition will be
475 : * committed with this value.
476 : * @return true if the focused widget is neither
477 : * destroyed nor changed. Otherwise, false.
478 : */
479 : bool DispatchCompositionCommitEvent(
480 : GtkIMContext* aContext,
481 : const nsAString* aCommitString = nullptr);
482 : };
483 :
484 : } // namespace widget
485 : } // namespace mozilla
486 :
487 : #endif // #ifndef IMContextWrapper_h_
|