private void processImage(boolean opening, Editable output) {
    Object[] spans = output.getSpans(0, output.length(), ImageSpan.class);
    if (spans.length == 0) {
      return;
    }

    ImageSpan imageSpan = (ImageSpan) spans[0];
    String imageUrl = imageSpan.getSource();

    // Handle image video thumbnail clicked
    if (imageUrl.startsWith(BBCodeParser.VIDEO_URL_PREFIX)) {
      VideoClickableSpan clickableSpan = mVideoClickableSpanProvider.get();
      clickableSpan.setVideoUrl(imageUrl.substring(BBCodeParser.VIDEO_URL_PREFIX.length()));
      output.setSpan(
          clickableSpan,
          output.getSpanStart(imageSpan),
          output.length(),
          Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
      return;
    }

    // Not Image Video Thumbnail, This is normal image
    if (TextUtils.isEmpty(imageUrl) || !RichTextUtils.isUrl(imageUrl)) {
      return;
    }

    ImageClickableSpan clickableSpan = mImageClickableSpanProvider.get();
    clickableSpan.setImageUrl(imageUrl);
    output.setSpan(
        clickableSpan,
        output.getSpanStart(imageSpan),
        output.length(),
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  }
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
      Editable text = getText();
      if (text == null) return;

      clearSelections();
      updateHint();

      TokenImageSpan[] spans =
          text.getSpans(start - before, start - before + count, TokenImageSpan.class);

      for (TokenImageSpan token : spans) {

        int position = start + count;
        if (text.getSpanStart(token) < position && position <= text.getSpanEnd(token)) {
          // We may have to manually reverse the auto-complete and remove the extra ,'s
          int spanStart = text.getSpanStart(token);
          int spanEnd = text.getSpanEnd(token);

          removeToken(token, text);

          // The end of the span is the character index after it
          spanEnd--;

          if (spanEnd >= 0 && text.charAt(spanEnd) == ',') {
            text.delete(spanEnd, spanEnd + 1);
          }

          if (spanStart >= 0 && text.charAt(spanStart) == ',') {
            text.delete(spanStart, spanStart + 1);
          }
        }
      }
    }
  private void updateHint() {
    Editable text = getText();
    CharSequence hintText = getHint();
    if (text == null || hintText == null) {
      return;
    }

    // Show hint if we need to
    if (prefix.length() > 0) {
      HintSpan[] hints = text.getSpans(0, text.length(), HintSpan.class);
      HintSpan hint = null;
      int testLength = prefix.length();
      if (hints.length > 0) {
        hint = hints[0];
        testLength += text.getSpanEnd(hint) - text.getSpanStart(hint);
      }

      if (text.length() == testLength) {
        hintVisible = true;

        if (hint != null) {
          return; // hint already visible
        }

        // We need to display the hint manually
        Typeface tf = getTypeface();
        int style = Typeface.NORMAL;
        if (tf != null) {
          style = tf.getStyle();
        }
        ColorStateList colors = getHintTextColors();

        HintSpan hintSpan = new HintSpan(null, style, (int) getTextSize(), colors, colors);
        text.insert(prefix.length(), hintText);
        text.setSpan(
            hintSpan,
            prefix.length(),
            prefix.length() + getHint().length(),
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        setSelection(prefix.length());

      } else {
        if (hint == null) {
          return; // hint already removed
        }

        // Remove the hint. There should only ever be one
        int sStart = text.getSpanStart(hint);
        int sEnd = text.getSpanEnd(hint);

        text.removeSpan(hint);
        text.replace(sStart, sEnd, "");

        hintVisible = false;
      }
    }
  }
Example #4
0
 private static void endSpan(Class<?> type, Editable output) {
   int length = output.length();
   Object span = getLast(output, type);
   int start = output.getSpanStart(span);
   output.removeSpan(span);
   if (start != length) output.setSpan(span, start, length, SPAN_EXCLUSIVE_EXCLUSIVE);
 }
Example #5
0
        public void handleTag(
            final boolean opening,
            final String tag,
            final Editable output,
            final XMLReader xmlReader) {
          if (TAG_DEL.equalsIgnoreCase(tag)) {
            if (opening) startSpan(new StrikethroughSpan(), output);
            else endSpan(StrikethroughSpan.class, output);
            return;
          }

          if (TAG_UL.equalsIgnoreCase(tag) || TAG_OL.equalsIgnoreCase(tag)) {
            if (opening) {
              listElements.add(new ListSeparator(TAG_OL.equalsIgnoreCase(tag)));
            } else if (!listElements.isEmpty()) {
              listElements.removeLast();
            }

            if (!opening && listElements.isEmpty()) output.append('\n');
            return;
          }

          if (TAG_LI.equalsIgnoreCase(tag) && opening && !listElements.isEmpty()) {
            listElements.getLast().append(output, listElements.size());
            return;
          }

          if (TAG_CODE.equalsIgnoreCase(tag)) {
            if (opening) startSpan(new TypefaceSpan("monospace"), output);
            else endSpan(TypefaceSpan.class, output);
          }

          if (TAG_PRE.equalsIgnoreCase(tag)) {
            output.append('\n');
            if (opening) startSpan(new TypefaceSpan("monospace"), output);
            else endSpan(TypefaceSpan.class, output);
          }

          if ((TAG_ROOT.equalsIgnoreCase(tag) || TAG_HTML.equalsIgnoreCase(tag)) && !opening) {
            // Remove leading newlines
            while (output.length() > 0 && output.charAt(0) == '\n') output.delete(0, 1);

            // Remove trailing newlines
            int last = output.length() - 1;
            while (last >= 0 && output.charAt(last) == '\n') {
              output.delete(last, last + 1);
              last = output.length() - 1;
            }

            QuoteSpan[] quoteSpans = output.getSpans(0, output.length(), QuoteSpan.class);
            for (QuoteSpan span : quoteSpans) {
              int start = output.getSpanStart(span);
              int end = output.getSpanEnd(span);
              output.removeSpan(span);
              output.setSpan(new ReplySpan(), start, end, SPAN_EXCLUSIVE_EXCLUSIVE);
            }
          }
        }
 @Override
 public void afterTextChanged(Editable s) {
   TokenImageSpan[] spans = s.getSpans(0, s.length(), TokenImageSpan.class);
   for (TokenImageSpan token : currentTokens) {
     if (!Arrays.asList(spans).contains(token)) {
       spanWatcher.onSpanRemoved(s, token, s.getSpanStart(token), s.getSpanEnd(token));
     }
   }
 }
  private void removeSpan(TokenImageSpan span) {
    Editable text = getText();
    if (text == null) return;

    // If the spanwatcher has been removed, we need to also manually trigger onSpanRemoved
    TokenSpanWatcher[] spans = text.getSpans(0, text.length(), TokenSpanWatcher.class);
    if (spans.length == 0) {
      spanWatcher.onSpanRemoved(text, span, text.getSpanStart(span), text.getSpanEnd(span));
    } else if (Build.VERSION.SDK_INT < 14) {
      // HACK: Need to manually trigger on Span removed if there is only 1 object
      // not sure if there's a cleaner way
      if (objects.size() == 1) {
        spanWatcher.onSpanRemoved(text, span, text.getSpanStart(span), text.getSpanEnd(span));
      }
    }

    // Add 1 to the end because we put a " " at the end of the spans when adding them
    text.delete(text.getSpanStart(span), text.getSpanEnd(span) + 1);
  }
Example #8
0
 private void renewColor() {
   if (DBG) {
     Log.d(LOG_TAG, "--- renewColor:");
   }
   if (mView instanceof View) {
     ImageSpan parent = getParentSpan();
     Editable text = ((EditStyledText) mView).getText();
     int start = text.getSpanStart(parent);
     ForegroundColorSpan[] spans = text.getSpans(start, start, ForegroundColorSpan.class);
     if (spans.length > 0) {
       renewColor(spans[spans.length - 1].getForegroundColor());
     }
   }
 }
Example #9
0
  /**
   * Adds {@link StrikethroughSpan} to {@literal <strike>} tag.
   *
   * <p>See android.text.HtmlToSpannedConverter#handleStartTag(java.lang.String,
   * org.xml.sax.Attributes) See android.text.HtmlToSpannedConverter#handleEndTag(java.lang.String)
   */
  private void handleStrike(boolean opening, Editable output) {
    int len = output.length();
    if (opening) {
      output.setSpan(new Strike(), len, len, Spannable.SPAN_MARK_MARK);
    } else {
      Strike strike = getLastSpan(output, Strike.class);
      int where = output.getSpanStart(strike);

      output.removeSpan(strike);

      if (where != len) {
        output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
      }
    }
  }
Example #10
0
 public void onClearStyles() {
   if (DBG) {
     Log.d(LOG_TAG, "--- onClearStyles");
   }
   Editable txt = mEST.getText();
   int len = txt.length();
   Object[] styles = txt.getSpans(0, len, Object.class);
   for (Object style : styles) {
     if (style instanceof ParagraphStyle
         || style instanceof QuoteSpan
         || style instanceof CharacterStyle) {
       if (style instanceof ImageSpan) {
         int start = txt.getSpanStart(style);
         int end = txt.getSpanEnd(style);
         txt.replace(start, end, "");
       }
       txt.removeSpan(style);
     }
   }
   mEST.setBackgroundDrawable(mEST.mDefaultBackground);
   mEST.mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
 }
  private void process(boolean opening, Editable output, CharacterStyle... spanStyles) {
    int len = output.length();
    if (opening) {
      for (CharacterStyle style : spanStyles) {
        output.setSpan(style, len, len, Spannable.SPAN_MARK_MARK);
      }
    } else {
      for (CharacterStyle style : spanStyles) {
        Object obj = getLast(output, style.getClass());
        int where = output.getSpanStart(obj);

        output.removeSpan(obj);

        if (where != len) {
          if (style instanceof VideoClickableSpan) {
            // Need to set the video url to this span.
            String url = output.subSequence(where, len).toString();
            ((VideoClickableSpan) style).setVideoUrl(url);
          }
          output.setSpan(style, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      }
    }
  }
  @Override
  protected void onSelectionChanged(int selStart, int selEnd) {
    if (hintVisible) {
      // Don't let users select the hint
      selStart = 0;
    }
    // Never let users select text
    selEnd = selStart;

    if (tokenClickStyle != null && tokenClickStyle.isSelectable()) {
      Editable text = getText();
      if (text != null) {
        clearSelections();
      }
    }

    if (prefix != null && (selStart < prefix.length() || selEnd < prefix.length())) {
      // Don't let users select the prefix
      setSelection(prefix.length());
    } else {
      Editable text = getText();
      if (text != null) {
        // Make sure if we are in a span, we select the spot 1 space after the span end
        TokenImageSpan[] spans = text.getSpans(selStart, selEnd, TokenImageSpan.class);
        for (TokenImageSpan span : spans) {
          int spanEnd = text.getSpanEnd(span);
          if (selStart <= spanEnd && text.getSpanStart(span) < selStart) {
            setSelection(spanEnd + 1);
            return;
          }
        }
      }

      super.onSelectionChanged(selStart, selEnd);
    }
  }
    @Override
    public void afterTextChanged(Editable editable) {

      modified = true; // should be unaffected by textWatcherEnabled

      if (!textWatcherEnabled) return;

      // Add style as the user types if a toggle button is enabled
      int position = Selection.getSelectionStart(DroidWriterEditText.this.getText());
      if (position < 0) {
        position = 0;
      }

      if (position > 0) {
        CharacterStyle[] appliedStyles =
            editable.getSpans(position - 1, position, CharacterStyle.class);

        StyleSpan currentBoldSpan = null;
        StyleSpan currentItalicSpan = null;
        UnderlineSpan currentUnderlineSpan = null;

        // Look for possible styles already applied to the entered text
        for (int i = 0; i < appliedStyles.length; i++) {
          if (appliedStyles[i] instanceof StyleSpan) {
            if (((StyleSpan) appliedStyles[i]).getStyle() == android.graphics.Typeface.BOLD) {
              // Bold style found
              currentBoldSpan = (StyleSpan) appliedStyles[i];
            } else if (((StyleSpan) appliedStyles[i]).getStyle()
                == android.graphics.Typeface.ITALIC) {
              // Italic style found
              currentItalicSpan = (StyleSpan) appliedStyles[i];
            }
          } else if (appliedStyles[i] instanceof UnderlineSpan) {
            // Underlined style found
            currentUnderlineSpan = (UnderlineSpan) appliedStyles[i];
          }
        }

        // Handle the bold style toggle button if it's present
        if (boldToggle != null) {
          if (boldToggle.isChecked() && currentBoldSpan == null) {
            // The user switched the bold style button on and the
            // character doesn't have any bold
            // style applied, so we start a new bold style span. The
            // span is inclusive,
            // so any new characters entered right after this one
            // will automatically get this style.
            editable.setSpan(
                new StyleSpan(android.graphics.Typeface.BOLD),
                position - 1,
                position,
                Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
          } else if (!boldToggle.isChecked() && currentBoldSpan != null) {
            // The user switched the bold style button off and the
            // character has bold style applied.
            // We need to remove the old bold style span, and define
            // a new one that end 1 position right
            // before the newly entered character.
            int boldStart = editable.getSpanStart(currentBoldSpan);
            int boldEnd = editable.getSpanEnd(currentBoldSpan);

            editable.removeSpan(currentBoldSpan);
            if (boldStart <= (position - 1)) {
              editable.setSpan(
                  new StyleSpan(android.graphics.Typeface.BOLD),
                  boldStart,
                  position - 1,
                  Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
            }

            // The old bold style span end after the current cursor
            // position, so we need to define a
            // second newly created style span too, which begins
            // after the newly entered character and
            // ends at the old span's ending position. So we split
            // the span.
            if (boldEnd > position) {
              editable.setSpan(
                  new StyleSpan(android.graphics.Typeface.BOLD),
                  position,
                  boldEnd,
                  Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
            }
          }
        }

        // Handling italics and underlined styles is the same as
        // handling bold styles.

        // Handle the italics style toggle button if it's present
        if (italicsToggle != null && italicsToggle.isChecked() && currentItalicSpan == null) {
          editable.setSpan(
              new StyleSpan(android.graphics.Typeface.ITALIC),
              position - 1,
              position,
              Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        } else if (italicsToggle != null
            && !italicsToggle.isChecked()
            && currentItalicSpan != null) {
          int italicStart = editable.getSpanStart(currentItalicSpan);
          int italicEnd = editable.getSpanEnd(currentItalicSpan);

          editable.removeSpan(currentItalicSpan);
          if (italicStart <= (position - 1)) {
            editable.setSpan(
                new StyleSpan(android.graphics.Typeface.ITALIC),
                italicStart,
                position - 1,
                Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
          }

          // Split the span
          if (italicEnd > position) {
            editable.setSpan(
                new StyleSpan(android.graphics.Typeface.ITALIC),
                position,
                italicEnd,
                Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
          }
        }

        // Handle the underlined style toggle button if it's present
        if (underlineToggle != null
            && underlineToggle.isChecked()
            && currentUnderlineSpan == null) {
          editable.setSpan(
              new UnderlineSpan(), position - 1, position, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
        } else if (underlineToggle != null
            && !underlineToggle.isChecked()
            && currentUnderlineSpan != null) {
          int underLineStart = editable.getSpanStart(currentUnderlineSpan);
          int underLineEnd = editable.getSpanEnd(currentUnderlineSpan);

          editable.removeSpan(currentUnderlineSpan);
          if (underLineStart <= (position - 1)) {
            editable.setSpan(
                new UnderlineSpan(),
                underLineStart,
                position - 1,
                Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
          }

          // We need to split the span
          if (underLineEnd > position) {
            editable.setSpan(
                new UnderlineSpan(), position, underLineEnd, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
          }
        }
      }
    }
  public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
    int selStart, selEnd;
    int pref = 0;

    if (view != null) {
      pref = TextKeyListener.getInstance().getPrefs(view.getContext());
    }

    {
      int a = Selection.getSelectionStart(content);
      int b = Selection.getSelectionEnd(content);

      selStart = Math.min(a, b);
      selEnd = Math.max(a, b);
    }

    int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
    int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);

    // now for the multitap cases...

    // Try to increment the character we were working on before
    // if we have one and it's still the same key.

    int rec =
        (content.getSpanFlags(TextKeyListener.ACTIVE) & Spannable.SPAN_USER)
            >>> Spannable.SPAN_USER_SHIFT;

    if (activeStart == selStart
        && activeEnd == selEnd
        && selEnd - selStart == 1
        && rec >= 0
        && rec < sRecs.size()) {
      if (keyCode == KeyEvent.KEYCODE_STAR) {
        char current = content.charAt(selStart);

        if (Character.isLowerCase(current)) {
          content.replace(selStart, selEnd, String.valueOf(current).toUpperCase());
          removeTimeouts(content);
          new Timeout(content); // for its side effects

          return true;
        }
        if (Character.isUpperCase(current)) {
          content.replace(selStart, selEnd, String.valueOf(current).toLowerCase());
          removeTimeouts(content);
          new Timeout(content); // for its side effects

          return true;
        }
      }

      if (sRecs.indexOfKey(keyCode) == rec) {
        String val = sRecs.valueAt(rec);
        char ch = content.charAt(selStart);
        int ix = val.indexOf(ch);

        if (ix >= 0) {
          ix = (ix + 1) % (val.length());

          content.replace(selStart, selEnd, val, ix, ix + 1);
          removeTimeouts(content);
          new Timeout(content); // for its side effects

          return true;
        }
      }

      // Is this key one we know about at all?  If so, acknowledge
      // that the selection is our fault but the key has changed
      // or the text no longer matches, so move the selection over
      // so that it inserts instead of replaces.

      rec = sRecs.indexOfKey(keyCode);

      if (rec >= 0) {
        Selection.setSelection(content, selEnd, selEnd);
        selStart = selEnd;
      }
    } else {
      rec = sRecs.indexOfKey(keyCode);
    }

    if (rec >= 0) {
      // We have a valid key.  Replace the selection or insertion point
      // with the first character for that key, and remember what
      // record it came from for next time.

      String val = sRecs.valueAt(rec);

      int off = 0;
      if ((pref & TextKeyListener.AUTO_CAP) != 0
          && TextKeyListener.shouldCap(mCapitalize, content, selStart)) {
        for (int i = 0; i < val.length(); i++) {
          if (Character.isUpperCase(val.charAt(i))) {
            off = i;
            break;
          }
        }
      }

      if (selStart != selEnd) {
        Selection.setSelection(content, selEnd);
      }

      content.setSpan(OLD_SEL_START, selStart, selStart, Spannable.SPAN_MARK_MARK);

      content.replace(selStart, selEnd, val, off, off + 1);

      int oldStart = content.getSpanStart(OLD_SEL_START);
      selEnd = Selection.getSelectionEnd(content);

      if (selEnd != oldStart) {
        Selection.setSelection(content, oldStart, selEnd);

        content.setSpan(
            TextKeyListener.LAST_TYPED, oldStart, selEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        content.setSpan(
            TextKeyListener.ACTIVE,
            oldStart,
            selEnd,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | (rec << Spannable.SPAN_USER_SHIFT));
      }

      removeTimeouts(content);
      new Timeout(content); // for its side effects

      // Set up the callback so we can remove the timeout if the
      // cursor moves.

      if (content.getSpanStart(this) < 0) {
        KeyListener[] methods = content.getSpans(0, content.length(), KeyListener.class);
        for (Object method : methods) {
          content.removeSpan(method);
        }
        content.setSpan(this, 0, content.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
      }

      return true;
    }

    return super.onKeyDown(view, content, keyCode, event);
  }
  protected void handleFocus(boolean hasFocus) {
    if (!hasFocus) {
      setSingleLine(true);

      Editable text = getText();
      if (text != null && lastLayout != null) {
        // Display +x thingy if appropriate
        int lastPosition = lastLayout.getLineVisibleEnd(0);
        TokenImageSpan[] tokens = text.getSpans(0, lastPosition, TokenImageSpan.class);
        int count = objects.size() - tokens.length;
        if (count > 0) {
          lastPosition++;
          CountSpan cs =
              new CountSpan(
                  count,
                  getContext(),
                  getCurrentTextColor(),
                  (int) getTextSize(),
                  (int) maxTextWidth());
          text.insert(lastPosition, cs.text);

          float newWidth =
              Layout.getDesiredWidth(
                  text, 0, lastPosition + cs.text.length(), lastLayout.getPaint());
          // If the +x span will be moved off screen, move it one token in
          if (newWidth > maxTextWidth()) {
            text.delete(lastPosition, lastPosition + cs.text.length());

            if (tokens.length > 0) {
              TokenImageSpan token = tokens[tokens.length - 1];
              lastPosition = text.getSpanStart(token);
              cs.setCount(count + 1);
            } else {
              lastPosition = prefix.length();
            }

            text.insert(lastPosition, cs.text);
          }

          text.setSpan(
              cs, lastPosition, lastPosition + cs.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      }

    } else {
      setSingleLine(false);
      Editable text = getText();
      if (text != null) {
        CountSpan[] counts = text.getSpans(0, text.length(), CountSpan.class);
        for (CountSpan count : counts) {
          text.delete(text.getSpanStart(count), text.getSpanEnd(count));
          text.removeSpan(count);
        }

        if (hintVisible) {
          setSelection(prefix.length());
        } else {
          setSelection(text.length());
        }

        TokenSpanWatcher[] watchers =
            getText().getSpans(0, getText().length(), TokenSpanWatcher.class);
        if (watchers.length == 0) {
          // Someone removes watchers? I'm pretty sure this isn't in this code... -mgod
          text.setSpan(spanWatcher, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }
      }
    }
  }