/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:expandtab:shiftwidth=2:tabstop=2:
*/ /* 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/. */
/** * KeyHandlingState is result of IMContextWrapper::OnKeyEvent().
*/ enumclass KeyHandlingState { // The native key event has not been handled by IMContextWrapper.
eNotHandled, // The native key event was handled by IMContextWrapper.
eHandled, // The native key event has not been handled by IMContextWrapper, // but eKeyDown or eKeyUp event has been dispatched.
eNotHandledButEventDispatched, // The native key event has not been handled by IMContextWrapper, // but eKeyDown or eKeyUp event has been dispatched and consumed.
eNotHandledButEventConsumed,
};
class IMContextWrapper final : public TextEventDispatcherListener { public: // TextEventDispatcherListener implementation
NS_DECL_ISUPPORTS
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);
// Called when the process is being shut down. staticvoid Shutdown();
// "Enabled" means the users can use all IMEs. // I.e., the focus is in the normal editors. bool IsEnabled() 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); // OnThemeChanged is called when desktop theme is changed. staticvoid OnThemeChanged();
/** * 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 native key press event starts composition, this returns * true but dispatches an eKeyDown event or eKeyUp event before * dispatching composition events or content command event. * * @param aWindow A window on which user operate the * key. * @param aEvent A native key press or release * event. * @param aKeyboardEventWasDispatched true if eKeyDown or eKeyUp event * for aEvent has already been * dispatched. In this case, * this class doesn't dispatch * keyboard event anymore.
*/
KeyHandlingState OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent, bool aKeyboardEventWasDispatched = false);
// TODO: Typically, new IM comes every several years. And now, our code // becomes really IM behavior dependent. So, perhaps, we need prefs // to control related flags for IM developers. enumclass IMContextID : uint8_t {
Fcitx, // 4.x or earlier
Fcitx5,
IBus,
IIIMF,
Scim,
Uim,
Wayland,
Unknown,
};
friend std::ostream& operator<<(std::ostream& aStream, const IMContextID& aIMContextID) { switch (aIMContextID) { case IMContextID::Fcitx: return aStream << "Fcitx"; case IMContextID::Fcitx5: return aStream << "Fcitx5"; case IMContextID::IBus: return aStream << "IBus"; case IMContextID::IIIMF: return aStream << "IIIMF"; case IMContextID::Scim: return aStream << "Scim"; case IMContextID::Uim: return aStream << "Uim"; case IMContextID::Wayland: return aStream << "Wayland"; case IMContextID::Unknown: return aStream << "Unknown";
}
MOZ_ASSERT_UNREACHABLE("Add new case for the new IM support"); return aStream << "Unknown";
}
/** * GetIMName() returns IM name associated with mContext. If the context is * xim, this look for actual engine from XMODIFIERS environment variable.
*/
nsDependentCSubstring GetIMName() const;
/** * GetWaitingSynthesizedKeyPressHardwareKeyCode() returns hardware_keycode * value of last handled GDK_KEY_PRESS event which is probable handled by * IME asynchronously and we have not received synthesized GDK_KEY_PRESS * event yet.
*/ static guint16 GetWaitingSynthesizedKeyPressHardwareKeyCode() { return sWaitingSynthesizedKeyPressHardwareKeyCode;
}
protected:
~IMContextWrapper();
/** * SetInputPurposeAndInputHints() sets input-purpose and input-hints of * current IM context to the values computed with mInputContext.
*/ void SetInputPurposeAndInputHints();
// 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;
// mSelectedStringRemovedByComposition is the selected string which was // removed by first compositionchange event.
nsString mSelectedStringRemovedByComposition;
// OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native // event.
GdkEventKey* mProcessingKeyEvent;
/** * GdkEventKeyQueue stores *copy* of GdkEventKey instances. However, this * must be safe to our usecase since it has |time| and the value should not * be same as older event.
*/ class GdkEventKeyQueue final { public:
~GdkEventKeyQueue() { Clear(); }
void Clear() { mEvents.Clear(); }
/** * PutEvent() puts new event into the queue.
*/ void PutEvent(const GdkEventKey* aEvent) {
GdkEventKey* newEvent = reinterpret_cast<GdkEventKey*>(
gdk_event_copy(reinterpret_cast<const GdkEvent*>(aEvent)));
newEvent->state &= GDK_MODIFIER_MASK;
mEvents.AppendElement(newEvent);
}
/** * RemoveEvent() removes oldest same event and its preceding events * from the queue.
*/ void RemoveEvent(const GdkEventKey* aEvent) {
size_t index = IndexOf(aEvent); if (NS_WARN_IF(index == GdkEventKeyQueue::NoIndex())) { return;
}
mEvents.RemoveElementAt(index);
}
/** * Return corresponding GDK_KEY_PRESS event for aEvent. aEvent must be a * GDK_KEY_RELEASE event.
*/ const GdkEventKey* GetCorrespondingKeyPressEvent( const GdkEventKey* aEvent) const {
MOZ_ASSERT(aEvent->type == GDK_KEY_RELEASE); for (const GUniquePtr<GdkEventKey>& pendingKeyEvent : mEvents) { if (pendingKeyEvent->type == GDK_KEY_PRESS &&
aEvent->hardware_keycode == pendingKeyEvent->hardware_keycode) { return pendingKeyEvent.get();
}
} return nullptr;
}
/** * FirstEvent() returns oldest event in the queue.
*/
GdkEventKey* GetFirstEvent() const { if (mEvents.IsEmpty()) { return nullptr;
} return mEvents[0].get();
}
static size_t NoIndex() { return nsTArray<GdkEventKey*>::NoIndex; }
size_t Length() const { return mEvents.Length(); }
size_t IndexOf(const GdkEventKey* aEvent) const {
static_assert(!(GDK_MODIFIER_MASK & (1 << 24)), "We assumes 25th bit is used by some IM, but used by GDK");
static_assert(!(GDK_MODIFIER_MASK & (1 << 25)), "We assumes 26th bit is used by some IM, but used by GDK"); for (size_t i = 0; i < mEvents.Length(); i++) {
GdkEventKey* event = mEvents[i].get(); // It must be enough to compare only type, time, keyval and // part of state. Note that we cannot compaire two events // simply since IME may have changed unused bits of state. if (event->time == aEvent->time) { if (NS_WARN_IF(event->type != aEvent->type) ||
NS_WARN_IF(event->keyval != aEvent->keyval) ||
NS_WARN_IF(event->state != (aEvent->state & GDK_MODIFIER_MASK))) { continue;
}
} return i;
} return GdkEventKeyQueue::NoIndex();
}
private:
nsTArray<GUniquePtr<GdkEventKey>> mEvents;
}; // OnKeyEvent() append mPostingKeyEvents when it believes that a key event // is posted to other IME process.
GdkEventKeyQueue mPostingKeyEvents;
/** * 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;
constchar* GetCompositionStateName() { switch (mCompositionState) { case eCompositionState_NotComposing: return"NotComposing"; case eCompositionState_CompositionStartDispatched: return"CompositionStartDispatched"; case eCompositionState_CompositionChangeEventDispatched: return"CompositionChangeEventDispatched"; default: return"InvaildState";
}
}
// mIMContextID indicates the ID of mContext. This is actually indicates // IM which user selected.
IMContextID mIMContextID;
// If mContentSelection is Nothing, it means that // EnsureToCacheContentSelection failed to get selection or just not caching // the selection.
Maybe<ContentSelection> mContentSelection;
/** * Return true if mContentSelection is set to some. Otherwise, false.
*/ bool EnsureToCacheContentSelection(nsAString* aSelectedString = nullptr);
enumclass IMEFocusState : uint8_t { // IME has focus
Focused, // IME was blurred
Blurred, // IME was blurred without a focus change
BlurredWithoutFocusChange,
}; friend std::ostream& operator<<(std::ostream& aStream, IMEFocusState aState) { switch (aState) { case IMEFocusState::Focused: return aStream << "IMEFocusState::Focused"; case IMEFocusState::Blurred: return aStream << "IMEFocusState::Blurred"; case IMEFocusState::BlurredWithoutFocusChange: return aStream << "IMEFocusState::BlurredWithoutFocusChange"; default:
MOZ_ASSERT_UNREACHABLE("Invalid value"); return aStream << "";
}
}
IMEFocusState mIMEFocusState = IMEFocusState::Blurred;
// mFallbackToKeyEvent is set to false when this class starts to handle // a native key event (at that time, mProcessingKeyEvent is set to the // native event). If active IME just commits composition with a character // which is produced by the key with current keyboard layout, this is set // to true. bool mFallbackToKeyEvent; // mKeyboardEventWasDispatched is used by OnKeyEvent() and // MaybeDispatchKeyEventAsProcessedByIME(). // MaybeDispatchKeyEventAsProcessedByIME() dispatches an eKeyDown or // eKeyUp event event if the composition is caused by a native // key press event. If this is true, a keyboard event has been dispatched // for the native event. If so, MaybeDispatchKeyEventAsProcessedByIME() // won't dispatch keyboard event anymore. bool mKeyboardEventWasDispatched; // Whether the keyboard event which as dispatched at setting // mKeyboardEventWasDispatched to true was consumed or not. bool mKeyboardEventWasConsumed; // 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; // mMaybeInDeadKeySequence is set to true when we detect a dead key press // and set to false when we're sure dead key sequence has been finished. // Note that we cannot detect which key event causes ending a dead key // sequence. For example, when you press dead key grave with ibus Spanish // keyboard layout, it just consumes the key event when we call // gtk_im_context_filter_keypress(). Then, pressing "Escape" key cancels // the dead key sequence but we don't receive any signal and it's consumed // by gtk_im_context_filter_keypress() normally. On the other hand, when // pressing "Shift" key causes exactly same behavior but dead key sequence // isn't finished yet. bool mMaybeInDeadKeySequence; // mIsIMInAsyncKeyHandlingMode is set to true if we know that IM handles // key events asynchronously. I.e., filtered key event may come again // later. bool mIsIMInAsyncKeyHandlingMode; // mIsKeySnooped is set to true if IM uses key snooper to listen key events. // In such case, we won't receive key events if IME consumes the event. bool mIsKeySnooped; // mSetInputPurposeAndInputHints is set if `SetInputContext` wants `Focus` // to set input-purpose and input-hints. bool mSetInputPurposeAndInputHints;
// 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. staticbool 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); staticvoid OnCommitCompositionCallback(GtkIMContext* aContext, const gchar* aString,
IMContextWrapper* aModule); staticvoid OnChangeCompositionCallback(GtkIMContext* aContext,
IMContextWrapper* aModule); staticvoid OnStartCompositionCallback(GtkIMContext* aContext,
IMContextWrapper* aModule); staticvoid 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; }
/** * Reset the active context, i.e., if there is mComposingContext, reset it. * Otherwise, reset current context. Note that all native composition * events during calling this will be 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);
// 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. * - MaybeDispatchKeyEventAsProcessedByIME * - DispatchCompositionStart * - DispatchCompositionChangeEvent * - DispatchCompositionCommitEvent
*/
/** * Dispatch an eKeyDown or eKeyUp event whose mKeyCode value is * NS_VK_PROCESSKEY and mKeyNameIndex is KEY_NAME_INDEX_Process if * we're not in a dead key sequence, mProcessingKeyEvent is nullptr * but mPostingKeyEvents is not empty or mProcessingKeyEvent is not * nullptr and mKeyboardEventWasDispatched is still false. If this * dispatches a keyboard event, this sets mKeyboardEventWasDispatched * to true. * * @param aFollowingEvent The following event message. * @return If the caller can continue to handle * composition, returns true. Otherwise, * false. For example, if focus is moved * by dispatched keyboard event, returns * false.
*/ bool MaybeDispatchKeyEventAsProcessedByIME(EventMessage aFollowingEvent);
/** * 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_
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.