summaryrefslogtreecommitdiffstats
path: root/widget/gtk/IMContextWrapper.h
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/IMContextWrapper.h')
-rw-r--r--widget/gtk/IMContextWrapper.h481
1 files changed, 481 insertions, 0 deletions
diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h
new file mode 100644
index 000000000..e869c160a
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.h
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IMContextWrapper_h_
+#define IMContextWrapper_h_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class IMContextWrapper final : public TextEventDispatcherListener
+{
+public:
+ // TextEventDispatcherListener implementation
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(void) OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void) WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+public:
+ // aOwnerWindow is a pointer of the owner window. When aOwnerWindow is
+ // destroyed, the related IME contexts are released (i.e., IME cannot be
+ // used with the instance after that).
+ explicit IMContextWrapper(nsWindow* aOwnerWindow);
+
+ // "Enabled" means the users can use all IMEs.
+ // I.e., the focus is in the normal editors.
+ bool IsEnabled() const;
+
+ nsIMEUpdatePreference GetIMEUpdatePreference() const;
+
+ // OnFocusWindow is a notification that aWindow is going to be focused.
+ void OnFocusWindow(nsWindow* aWindow);
+ // OnBlurWindow is a notification that aWindow is going to be unfocused.
+ void OnBlurWindow(nsWindow* aWindow);
+ // OnDestroyWindow is a notification that aWindow is going to be destroyed.
+ void OnDestroyWindow(nsWindow* aWindow);
+ // OnFocusChangeInGecko is a notification that an editor gets focus.
+ void OnFocusChangeInGecko(bool aFocus);
+ // OnSelectionChange is a notification that selection (caret) is changed
+ // in the focused editor.
+ void OnSelectionChange(nsWindow* aCaller,
+ const IMENotification& aIMENotification);
+
+ // OnKeyEvent is called when aWindow gets a native key press event or a
+ // native key release event. If this returns TRUE, the key event was
+ // filtered by IME. Otherwise, this returns FALSE.
+ // NOTE: When the keypress event starts composition, this returns TRUE but
+ // this dispatches keydown event before compositionstart event.
+ bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
+ bool aKeyDownEventWasSent = false);
+
+ // IME related nsIWidget methods.
+ nsresult EndIMEComposition(nsWindow* aCaller);
+ void SetInputContext(nsWindow* aCaller,
+ const InputContext* aContext,
+ const InputContextAction* aAction);
+ InputContext GetInputContext();
+ void OnUpdateComposition();
+ void OnLayoutChange();
+
+ TextEventDispatcher* GetTextEventDispatcher();
+
+protected:
+ ~IMContextWrapper();
+
+ // Owner of an instance of this class. This should be top level window.
+ // The owner window must release the contexts when it's destroyed because
+ // the IME contexts need the native window. If OnDestroyWindow() is called
+ // with the owner window, it'll release IME contexts. Otherwise, it'll
+ // just clean up any existing composition if it's related to the destroying
+ // child window.
+ nsWindow* mOwnerWindow;
+
+ // A last focused window in this class's context.
+ nsWindow* mLastFocusedWindow;
+
+ // Actual context. This is used for handling the user's input.
+ GtkIMContext* mContext;
+
+ // mSimpleContext is used for the password field and
+ // the |ime-mode: disabled;| editors if sUseSimpleContext is true.
+ // These editors disable IME. But dead keys should work. Fortunately,
+ // the simple IM context of GTK2 support only them.
+ GtkIMContext* mSimpleContext;
+
+ // mDummyContext is a dummy context and will be used in Focus()
+ // when the state of mEnabled means disabled. This context's IME state is
+ // always "closed", so it closes IME forcedly.
+ GtkIMContext* mDummyContext;
+
+ // mComposingContext is not nullptr while one of mContext, mSimpleContext
+ // and mDummyContext has composition.
+ // XXX: We don't assume that two or more context have composition same time.
+ GtkIMContext* mComposingContext;
+
+ // IME enabled state and other things defined in InputContext.
+ // Use following helper methods if you don't need the detail of the status.
+ InputContext mInputContext;
+
+ // mCompositionStart is the start offset of the composition string in the
+ // current content. When <textarea> or <input> have focus, it means offset
+ // from the first character of them. When a HTML editor has focus, it
+ // means offset from the first character of the root element of the editor.
+ uint32_t mCompositionStart;
+
+ // mDispatchedCompositionString is the latest composition string which
+ // was dispatched by compositionupdate event.
+ nsString mDispatchedCompositionString;
+
+ // mSelectedString is the selected string which was removed by first
+ // compositionchange event.
+ nsString mSelectedString;
+
+ // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
+ // event.
+ GdkEventKey* mProcessingKeyEvent;
+
+ struct Range
+ {
+ uint32_t mOffset;
+ uint32_t mLength;
+
+ Range()
+ : mOffset(UINT32_MAX)
+ , mLength(UINT32_MAX)
+ {
+ }
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ }
+ };
+
+ // current target offset and length of IME composition
+ Range mCompositionTargetRange;
+
+ // mCompositionState indicates current status of composition.
+ enum eCompositionState {
+ eCompositionState_NotComposing,
+ eCompositionState_CompositionStartDispatched,
+ eCompositionState_CompositionChangeEventDispatched
+ };
+ eCompositionState mCompositionState;
+
+ bool IsComposing() const
+ {
+ return (mCompositionState != eCompositionState_NotComposing);
+ }
+
+ bool IsComposingOn(GtkIMContext* aContext) const
+ {
+ return IsComposing() && mComposingContext == aContext;
+ }
+
+ bool IsComposingOnCurrentContext() const
+ {
+ return IsComposingOn(GetCurrentContext());
+ }
+
+ bool EditorHasCompositionString()
+ {
+ return (mCompositionState ==
+ eCompositionState_CompositionChangeEventDispatched);
+ }
+
+ /**
+ * Checks if aContext is valid context for handling composition.
+ *
+ * @param aContext An IM context which is specified by native
+ * composition events.
+ * @return true if the context is valid context for
+ * handling composition. Otherwise, false.
+ */
+ bool IsValidContext(GtkIMContext* aContext) const;
+
+ const char* GetCompositionStateName()
+ {
+ switch (mCompositionState) {
+ case eCompositionState_NotComposing:
+ return "NotComposing";
+ case eCompositionState_CompositionStartDispatched:
+ return "CompositionStartDispatched";
+ case eCompositionState_CompositionChangeEventDispatched:
+ return "CompositionChangeEventDispatched";
+ default:
+ return "InvaildState";
+ }
+ }
+
+ struct Selection final
+ {
+ uint32_t mOffset;
+ uint32_t mLength;
+ WritingMode mWritingMode;
+
+ Selection()
+ : mOffset(UINT32_MAX)
+ , mLength(UINT32_MAX)
+ {
+ }
+
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ mWritingMode = WritingMode();
+ }
+
+ void Assign(const IMENotification& aIMENotification);
+ void Assign(const WidgetQueryContentEvent& aSelectedTextEvent);
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ bool Collapsed() const { return !mLength; }
+ uint32_t EndOffset() const
+ {
+ if (NS_WARN_IF(!IsValid())) {
+ return UINT32_MAX;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mOffset) + mLength;
+ if (NS_WARN_IF(!endOffset.isValid())) {
+ return UINT32_MAX;
+ }
+ return endOffset.value();
+ }
+ } mSelection;
+ bool EnsureToCacheSelection(nsAString* aSelectedString = nullptr);
+
+ // mIsIMFocused is set to TRUE when we call gtk_im_context_focus_in(). And
+ // it's set to FALSE when we call gtk_im_context_focus_out().
+ bool mIsIMFocused;
+ // mFilterKeyEvent is used by OnKeyEvent(). If the commit event should
+ // be processed as simple key event, this is set to TRUE by the commit
+ // handler.
+ bool mFilterKeyEvent;
+ // mKeyDownEventWasSent is used by OnKeyEvent() and
+ // DispatchCompositionStart(). DispatchCompositionStart() dispatches
+ // a keydown event if the composition start is caused by a native
+ // keypress event. If this is true, the keydown event has been dispatched.
+ // Then, DispatchCompositionStart() doesn't dispatch keydown event.
+ bool mKeyDownEventWasSent;
+ // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is
+ // trying to delete the surrounding text.
+ bool mIsDeletingSurrounding;
+ // mLayoutChanged is true after OnLayoutChange() is called. This is reset
+ // when eCompositionChange is being dispatched.
+ bool mLayoutChanged;
+ // mSetCursorPositionOnKeyEvent true when caret rect or position is updated
+ // with no composition. If true, we update candidate window position
+ // before key down
+ bool mSetCursorPositionOnKeyEvent;
+ // mPendingResettingIMContext becomes true if selection change notification
+ // is received during composition but the selection change occurred before
+ // starting the composition. In such case, we cannot notify IME of
+ // selection change during composition because we don't want to commit
+ // the composition in such case. However, we should notify IME of the
+ // selection change after the composition is committed.
+ bool mPendingResettingIMContext;
+ // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding"
+ // signal is received until selection is changed in Gecko.
+ bool mRetrieveSurroundingSignalReceived;
+
+ // sLastFocusedContext is a pointer to the last focused instance of this
+ // class. When a instance is destroyed and sLastFocusedContext refers it,
+ // this is cleared. So, this refers valid pointer always.
+ static IMContextWrapper* sLastFocusedContext;
+
+ // sUseSimpleContext indeicates if password editors and editors with
+ // |ime-mode: disabled;| should use GtkIMContextSimple.
+ // If true, they use GtkIMContextSimple. Otherwise, not.
+ static bool sUseSimpleContext;
+
+ // Callback methods for native IME events. These methods should call
+ // the related instance methods simply.
+ static gboolean OnRetrieveSurroundingCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static gboolean OnDeleteSurroundingCallback(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars,
+ IMContextWrapper* aModule);
+ static void OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule);
+ static void OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+
+ // The instance methods for the native IME events.
+ gboolean OnRetrieveSurroundingNative(GtkIMContext* aContext);
+ gboolean OnDeleteSurroundingNative(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars);
+ void OnCommitCompositionNative(GtkIMContext* aContext,
+ const gchar* aString);
+ void OnChangeCompositionNative(GtkIMContext* aContext);
+ void OnStartCompositionNative(GtkIMContext* aContext);
+ void OnEndCompositionNative(GtkIMContext* aContext);
+
+ /**
+ * GetCurrentContext() returns current IM context which is chosen with the
+ * enabled state.
+ * WARNING:
+ * When this class receives some signals for a composition after focus
+ * is moved in Gecko, the result of this may be different from given
+ * context by the signals.
+ */
+ GtkIMContext* GetCurrentContext() const;
+
+ /**
+ * GetActiveContext() returns a composing context or current context.
+ */
+ GtkIMContext* GetActiveContext() const
+ {
+ return mComposingContext ? mComposingContext : GetCurrentContext();
+ }
+
+ // If the owner window and IM context have been destroyed, returns TRUE.
+ bool IsDestroyed() { return !mOwnerWindow; }
+
+ // Sets focus to the instance of this class.
+ void Focus();
+
+ // Steals focus from the instance of this class.
+ void Blur();
+
+ // Initializes the instance.
+ void Init();
+
+ // Reset the current composition of IME. All native composition events
+ // during this processing are ignored.
+ void ResetIME();
+
+ // Gets the current composition string by the native APIs.
+ void GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString);
+
+ /**
+ * Generates our text range array from current composition string.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString The data to be dispatched with
+ * compositionchange event.
+ */
+ already_AddRefed<TextRangeArray>
+ CreateTextRangeArray(GtkIMContext* aContext,
+ const nsAString& aCompositionString);
+
+ /**
+ * SetTextRange() initializes aTextRange with aPangoAttrIter.
+ *
+ * @param aPangoAttrIter An iter which represents a clause of the
+ * composition string.
+ * @param aUTF8CompositionString The whole composition string (UTF-8).
+ * @param aUTF16CaretOffset The caret offset in the composition
+ * string encoded as UTF-16.
+ * @param aTextRange The result.
+ * @return true if this initializes aTextRange.
+ * Otherwise, false.
+ */
+ bool SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset,
+ TextRange& aTextRange) const;
+
+ /**
+ * ToNscolor() converts the PangoColor in aPangoAttrColor to nscolor.
+ */
+ static nscolor ToNscolor(PangoAttrColor* aPangoAttrColor);
+
+ /**
+ * Move the candidate window with "fake" cursor position.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ */
+ void SetCursorPosition(GtkIMContext* aContext);
+
+ // Queries the current selection offset of the window.
+ uint32_t GetSelectionOffset(nsWindow* aWindow);
+
+ // Get current paragraph text content and cursor position
+ nsresult GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos);
+
+ /**
+ * Delete text portion
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aOffset Start offset of the range to delete.
+ * @param aNChars Count of characters to delete. It depends
+ * on |g_utf8_strlen()| what is one character.
+ */
+ nsresult DeleteText(GtkIMContext* aContext,
+ int32_t aOffset,
+ uint32_t aNChars);
+
+ // Initializes the GUI event.
+ void InitEvent(WidgetGUIEvent& aEvent);
+
+ // Called before destroying the context to work around some platform bugs.
+ void PrepareToDestroyContext(GtkIMContext* aContext);
+
+ /**
+ * WARNING:
+ * Following methods dispatch gecko events. Then, the focused widget
+ * can be destroyed, and also it can be stolen focus. If they returns
+ * FALSE, callers cannot continue the composition.
+ * - DispatchCompositionStart
+ * - DispatchCompositionChangeEvent
+ * - DispatchCompositionCommitEvent
+ */
+
+ /**
+ * Dispatches a composition start event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionStart(GtkIMContext* aContext);
+
+ /**
+ * Dispatches a compositionchange event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString New composition string.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionChangeEvent(GtkIMContext* aContext,
+ const nsAString& aCompositionString);
+
+ /**
+ * Dispatches a compositioncommit event or compositioncommitasis event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCommitString If this is nullptr, the composition will
+ * be committed with last dispatched data.
+ * Otherwise, the composition will be
+ * committed with this value.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(
+ GtkIMContext* aContext,
+ const nsAString* aCommitString = nullptr);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef IMContextWrapper_h_