From c6032760479b202cacade684a38652069cc21f40 Mon Sep 17 00:00:00 2001 From: Alex Pakhotin Date: Tue, 17 Jan 2012 23:17:51 -0800 Subject: [PATCH] Bug 711648 - Pre-commit underline is not shown for the composing text. r=blassey --- mobile/android/base/GeckoInputConnection.java | 151 +++++++++++++++--- 1 file changed, 132 insertions(+), 19 deletions(-) diff --git a/mobile/android/base/GeckoInputConnection.java b/mobile/android/base/GeckoInputConnection.java index b988dd16765f..421dfe65f23a 100644 --- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -48,6 +48,7 @@ import org.mozilla.gecko.gfx.InputConnectionHandler; import android.os.*; import android.app.*; import android.text.*; +import android.text.style.*; import android.view.*; import android.view.inputmethod.*; import android.content.*; @@ -101,13 +102,20 @@ public class GeckoInputConnection @Override public boolean commitText(CharSequence text, int newCursorPosition) { replaceText(text, newCursorPosition, false); - mComposing = false; + + if (mComposing) { + if (DEBUG) Log.d(LOGTAG, ". . . commitText: endComposition"); + endComposition(); + } return true; } @Override public boolean finishComposingText() { - mComposing = false; + if (mComposing) { + if (DEBUG) Log.d(LOGTAG, ". . . finishComposingText: endComposition"); + endComposition(); + } final Editable content = getEditable(); if (content != null) { @@ -257,12 +265,12 @@ public class GeckoInputConnection } if (composing) { - mComposing = true; - Spannable sp = null; if (!(text instanceof Spannable)) { sp = new SpannableStringBuilder(text); text = sp; + sp.setSpan(COMPOSING_SPAN, 0, sp.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); } else { sp = (Spannable)text; } @@ -306,6 +314,16 @@ public class GeckoInputConnection endBatchEdit(); } + @Override + public boolean setComposingRegion(int start, int end) { + if (mComposing) { + if (DEBUG) Log.d(LOGTAG, ". . . setComposingRegion: endComposition"); + endComposition(); + } + + return super.setComposingRegion(start, end); + } + public String getComposingText() { final Editable content = getEditable(); if (content == null) { @@ -393,6 +411,19 @@ public class GeckoInputConnection if (start != a || end != b) { if (DEBUG) Log.d(LOGTAG, String.format(". . . notifySelectionChange: current editable selection: [%d, %d]", a, b)); super.setSelection(start, end); + + // Check if the selection is inside composing span + int ca = getComposingSpanStart(content); + int cb = getComposingSpanEnd(content); + if (cb < ca) { + int tmp = ca; + ca = cb; + cb = tmp; + } + if (start < ca || start > cb || end < ca || end > cb) { + if (DEBUG) Log.d(LOGTAG, ". . . notifySelectionChange: removeComposingSpans"); + removeComposingSpans(content); + } } } @@ -411,32 +442,112 @@ public class GeckoInputConnection // TextWatcher public void onTextChanged(CharSequence s, int start, int before, int count) { - GeckoAppShell.sendEventToGecko( - new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start, before)); + if (!mComposing) { + if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_COMPOSITION_BEGIN"); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0)); + mComposing = true; + + if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + start + ", len=" + before); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start, before)); + } if (count == 0) { + if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_DELETE_TEXT"); GeckoAppShell.sendEventToGecko( new GeckoEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0)); } else { - GeckoAppShell.sendEventToGecko( - new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0)); - - GeckoAppShell.sendEventToGecko( - new GeckoEvent(0, count, - GeckoEvent.IME_RANGE_RAWINPUT, 0, 0, 0, - s.subSequence(start, start + count).toString())); - - GeckoAppShell.sendEventToGecko( - new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0)); - - GeckoAppShell.sendEventToGecko( - new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start + count, 0)); + sendTextToGecko(s.subSequence(start, start + count), start + count); } + if (DEBUG) Log.d(LOGTAG, ". . . onTextChanged: IME_SET_SELECTION, start=" + (start + count) + ", 0"); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(GeckoEvent.IME_SET_SELECTION, start + count, 0)); + // Block this thread until all pending events are processed GeckoAppShell.geckoEventSync(); } + private void endComposition() { + if (DEBUG) Log.d(LOGTAG, "IME: endComposition: IME_COMPOSITION_END"); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0)); + mComposing = false; + } + + private void sendTextToGecko(CharSequence text, int caretPos) { + if (DEBUG) Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")"); + + // Handle composition text styles + if (text != null && text instanceof Spanned) { + Spanned span = (Spanned) text; + int spanStart = 0, spanEnd = 0; + boolean pastSelStart = false, pastSelEnd = false; + + do { + int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT; + int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0; + + // Find next offset where there is a style transition + spanEnd = span.nextSpanTransition(spanStart + 1, text.length(), + CharacterStyle.class); + + // Empty range, continue + if (spanEnd <= spanStart) + continue; + + // Get and iterate through list of span objects within range + CharacterStyle styles[] = span.getSpans( + spanStart, spanEnd, CharacterStyle.class); + + for (CharacterStyle style : styles) { + if (style instanceof UnderlineSpan) { + // Text should be underlined + rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE; + + } else if (style instanceof ForegroundColorSpan) { + // Text should be of a different foreground color + rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR; + rangeForeColor = + ((ForegroundColorSpan)style).getForegroundColor(); + + } else if (style instanceof BackgroundColorSpan) { + // Text should be of a different background color + rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR; + rangeBackColor = + ((BackgroundColorSpan)style).getBackgroundColor(); + } + } + + // Add range to array, the actual styles are + // applied when IME_SET_TEXT is sent + if (DEBUG) Log.d(LOGTAG, String.format(". . . sendTextToGecko: IME_ADD_RANGE, %d, %d, %d, %d, %d, %d", + spanStart, spanEnd - spanStart, rangeType, rangeStyles, rangeForeColor, rangeBackColor)); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(spanStart, spanEnd - spanStart, + rangeType, rangeStyles, + rangeForeColor, rangeBackColor)); + + spanStart = spanEnd; + } while (spanStart < text.length()); + } else { + if (DEBUG) Log.d(LOGTAG, ". . . sendTextToGecko: IME_ADD_RANGE, 0, " + text.length() + + ", IME_RANGE_RAWINPUT, IME_RANGE_UNDERLINE)"); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(0, text == null ? 0 : text.length(), + GeckoEvent.IME_RANGE_RAWINPUT, + GeckoEvent.IME_RANGE_UNDERLINE, 0, 0)); + } + + // Change composition (treating selection end as where the caret is) + if (DEBUG) Log.d(LOGTAG, ". . . sendTextToGecko: IME_SET_TEXT, IME_RANGE_CARETPOSITION, \"" + text + "\")"); + GeckoAppShell.sendEventToGecko( + new GeckoEvent(caretPos, 0, + GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0, + text.toString())); + } + public void afterTextChanged(Editable s) { } @@ -785,6 +896,8 @@ public class GeckoInputConnection public static final int IME_STATE_PASSWORD = 2; public static final int IME_STATE_PLUGIN = 3; + final CharacterStyle COMPOSING_SPAN = new UnderlineSpan(); + KeyListener mKeyListener; Editable mEditable; Editable.Factory mEditableFactory;