Bug 711648 - Pre-commit underline is not shown for the composing text. r=blassey
authorAlex Pakhotin <alexp@mozilla.com>
Tue, 17 Jan 2012 23:17:51 -0800
changeset 86189 c53d7932f6b7cb8c66bf328ede8a277332d62541
parent 86188 bd0ef67d45dd2946f6a2060e0df5a968078bb780
child 86190 5d0f4e08e31e196acb0f4a74144e31f6e2abdf41
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs711648
milestone12.0a1
Bug 711648 - Pre-commit underline is not shown for the composing text. r=blassey
mobile/android/base/GeckoInputConnection.java
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -43,16 +43,17 @@ import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
 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.*;
 import android.R;
 import android.text.method.TextKeyListener;
 import android.text.method.KeyListener;
 import android.util.*;
 
@@ -96,23 +97,30 @@ public class GeckoInputConnection
     @Override
     public boolean commitCompletion(CompletionInfo text) {
         return commitText(text.getText(), 1);
     }
 
     @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) {
             beginBatchEdit();
             removeComposingSpans(content);
             endBatchEdit();
         }
         return true;
@@ -252,22 +260,22 @@ public class GeckoInputConnection
             if (b < a) {
                 int tmp = a;
                 a = b;
                 b = tmp;
             }
         }
 
         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;
             }
             setComposingSpans(sp);
         }
         
         if (DEBUG) Log.d(LOGTAG, "Replacing from " + a + " to " + b + " with \""
                 + text + "\", composing=" + composing
@@ -301,16 +309,26 @@ public class GeckoInputConnection
             LogPrinter lp = new LogPrinter(Log.VERBOSE, LOGTAG);
             lp.println("Final text:");
             TextUtils.dumpSpans(content, lp, "  ");
         }
         
         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) {
             return null;
         }
         int a = getComposingSpanStart(content);
         int b = getComposingSpanEnd(content);
 
@@ -388,16 +406,29 @@ public class GeckoInputConnection
                                       int start, int end) {
         if (!mBatchMode) {
             final Editable content = getEditable();
             int a = Selection.getSelectionStart(content);
             int b = Selection.getSelectionEnd(content);
             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);
+                }
             }
         }
 
         if (imm != null && imm.isFullscreenMode()) {
             View v = GeckoApp.mAppContext.getLayerController().getView();
             imm.updateSelection(v, start, end, -1, -1);
         }
     }
@@ -406,42 +437,122 @@ public class GeckoInputConnection
         mComposing = false;
         mBatchMode = false;
         mUpdateRequest = null;
     }
 
     // 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));
+            sendTextToGecko(s.subSequence(start, start + count), start + count);
+        }
 
-            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));
-        }
+        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)
     {
     }
 
     public void beforeTextChanged(CharSequence s, int start, int count, int after)
     {
     }
 
@@ -780,16 +891,18 @@ public class GeckoInputConnection
     boolean mComposing;
 
     // IME stuff
     public static final int IME_STATE_DISABLED = 0;
     public static final int IME_STATE_ENABLED = 1;
     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;
     static int mIMEState;
     static String mIMETypeHint;
     static String mIMEActionHint;
     static boolean mIMELandscapeFS;