/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ex.editstyledtext; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import com.android.ex.editstyledtext.EditStyledText.EditModeActions.EditModeActionBase; import com.android.ex.editstyledtext.EditStyledText.EditStyledTextSpans.HorizontalLineSpan; import com.android.ex.editstyledtext.EditStyledText.EditStyledTextSpans.MarqueeSpan; import com.android.ex.editstyledtext.EditStyledText.EditStyledTextSpans.RescalableImageSpan; import android.R; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RectShape; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.ResultReceiver; import android.text.ClipboardManager; import android.text.Editable; import android.text.Html; import android.text.Layout; import android.text.NoCopySpan; import android.text.NoCopySpan.Concrete; import android.text.Selection; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.Html.ImageGetter; import android.text.Html.TagHandler; import android.text.method.ArrowKeyMovementMethod; import android.text.style.AbsoluteSizeSpan; import android.text.style.AlignmentSpan; import android.text.style.BackgroundColorSpan; import android.text.style.CharacterStyle; import android.text.style.DynamicDrawableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; import android.text.style.ParagraphStyle; import android.text.style.QuoteSpan; import android.text.style.UnderlineSpan; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; import android.view.Gravity; import android.view.KeyEvent; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; /** * EditStyledText extends EditText for managing the flow and status to edit the styled text. This * manages the states and flows of editing, supports inserting image, import/export HTML. */ public class EditStyledText extends EditText { private static final String TAG = "EditStyledText"; /** * DBG should be false at checking in. */ private static final boolean DBG = true; /** * Modes of editing actions. */ /** The mode that no editing action is done. */ public static final int MODE_NOTHING = 0; /** The mode of copy. */ public static final int MODE_COPY = 1; /** The mode of paste. */ public static final int MODE_PASTE = 2; /** The mode of changing size. */ public static final int MODE_SIZE = 3; /** The mode of changing color. */ public static final int MODE_COLOR = 4; /** The mode of selection. */ public static final int MODE_SELECT = 5; /** The mode of changing alignment. */ public static final int MODE_ALIGN = 6; /** The mode of changing cut. */ public static final int MODE_CUT = 7; public static final int MODE_TELOP = 8; public static final int MODE_SWING = 9; public static final int MODE_MARQUEE = 10; public static final int MODE_SELECTALL = 11; public static final int MODE_HORIZONTALLINE = 12; public static final int MODE_STOP_SELECT = 13; public static final int MODE_CLEARSTYLES = 14; public static final int MODE_IMAGE = 15; public static final int MODE_BGCOLOR = 16; public static final int MODE_PREVIEW = 17; public static final int MODE_CANCEL = 18; public static final int MODE_TEXTVIEWFUNCTION = 19; public static final int MODE_START_EDIT = 20; public static final int MODE_END_EDIT = 21; public static final int MODE_RESET = 22; public static final int MODE_SHOW_MENU = 23; /** * States of selection. */ /** The state that selection isn't started. */ public static final int STATE_SELECT_OFF = 0; /** The state that selection is started. */ public static final int STATE_SELECT_ON = 1; /** The state that selection is done, but not fixed. */ public static final int STATE_SELECTED = 2; /** The state that selection is done and not fixed. */ public static final int STATE_SELECT_FIX = 3; /** * Help message strings. */ public static final int HINT_MSG_NULL = 0; public static final int HINT_MSG_COPY_BUF_BLANK = 1; public static final int HINT_MSG_SELECT_START = 2; public static final int HINT_MSG_SELECT_END = 3; public static final int HINT_MSG_PUSH_COMPETE = 4; public static final int HINT_MSG_BIG_SIZE_ERROR = 5; public static final int HINT_MSG_END_PREVIEW = 6; public static final int HINT_MSG_END_COMPOSE = 7; /** * Fixed Values. */ public static final int DEFAULT_TRANSPARENT_COLOR = 0x00FFFFFF; public static final int DEFAULT_FOREGROUND_COLOR = 0xFF000000; public static final char ZEROWIDTHCHAR = '\u2060'; public static final char IMAGECHAR = '\uFFFC'; private static final int ID_SELECT_ALL = android.R.id.selectAll; private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText; private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText; private static final int ID_PASTE = android.R.id.paste; private static final int ID_COPY = android.R.id.copy; private static final int ID_CUT = android.R.id.cut; private static final int ID_HORIZONTALLINE = 0x00FFFF01; private static final int ID_CLEARSTYLES = 0x00FFFF02; private static final int ID_SHOWEDIT = 0x00FFFF03; private static final int ID_HIDEEDIT = 0x00FFFF04; private static final int MAXIMAGEWIDTHDIP = 300; /** * Strings for context menu. TODO: Extract the strings to strings.xml. */ private static CharSequence STR_HORIZONTALLINE; private static CharSequence STR_CLEARSTYLES; private static CharSequence STR_PASTE; private float mPaddingScale = 0; private ArrayList mESTNotifiers; private Drawable mDefaultBackground; // EditStyledTextEditorManager manages the flow and status of each function of StyledText. private EditorManager mManager; private InputConnection mInputConnection; private StyledTextConverter mConverter; private StyledTextDialog mDialog; private static final Concrete SELECTING = new NoCopySpan.Concrete(); private static final int PRESSED = Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT); /** * EditStyledText extends EditText for managing flow of each editing action. */ public EditStyledText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public EditStyledText(Context context, AttributeSet attrs) { super(context, attrs); init(); } public EditStyledText(Context context) { super(context); init(); } @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult; if (event.getAction() == MotionEvent.ACTION_UP) { cancelLongPress(); boolean editting = isEditting(); // If View is touched but not in Edit Mode, starts Edit Mode. if (!editting) { onStartEdit(); } int oldSelStart = Selection.getSelectionStart(getText()); int oldSelEnd = Selection.getSelectionEnd(getText()); superResult = super.onTouchEvent(event); if (isFocused()) { // If selection is started, don't open soft key by // touching. if (getSelectState() == STATE_SELECT_OFF) { if (editting) { mManager.showSoftKey(Selection.getSelectionStart(getText()), Selection.getSelectionEnd(getText())); } else { mManager.showSoftKey(oldSelStart, oldSelEnd); } } } mManager.onCursorMoved(); mManager.unsetTextComposingMask(); } else { superResult = super.onTouchEvent(event); } sendOnTouchEvent(event); return superResult; } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedStyledTextState ss = new SavedStyledTextState(superState); ss.mBackgroundColor = mManager.getBackgroundColor(); return ss; } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedStyledTextState)) { super.onRestoreInstanceState(state); return; } SavedStyledTextState ss = (SavedStyledTextState) state; super.onRestoreInstanceState(ss.getSuperState()); setBackgroundColor(ss.mBackgroundColor); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (mManager != null) { mManager.onRefreshStyles(); } } @Override public boolean onTextContextMenuItem(int id) { boolean selection = getSelectionStart() != getSelectionEnd(); switch (id) { case ID_SELECT_ALL: onStartSelectAll(); return true; case ID_START_SELECTING_TEXT: onStartSelect(); mManager.blockSoftKey(); break; case ID_STOP_SELECTING_TEXT: onFixSelectedItem(); break; case ID_PASTE: onStartPaste(); return true; case ID_COPY: if (selection) { onStartCopy(); } else { mManager.onStartSelectAll(false); onStartCopy(); } return true; case ID_CUT: if (selection) { onStartCut(); } else { mManager.onStartSelectAll(false); onStartCut(); } return true; case ID_HORIZONTALLINE: onInsertHorizontalLine(); return true; case ID_CLEARSTYLES: onClearStyles(); return true; case ID_SHOWEDIT: onStartEdit(); return true; case ID_HIDEEDIT: onEndEdit(); return true; } return super.onTextContextMenuItem(id); } @Override protected void onCreateContextMenu(ContextMenu menu) { super.onCreateContextMenu(menu); MenuHandler handler = new MenuHandler(); if (STR_HORIZONTALLINE != null) { menu.add(0, ID_HORIZONTALLINE, 0, STR_HORIZONTALLINE).setOnMenuItemClickListener( handler); } if (isStyledText() && STR_CLEARSTYLES != null) { menu.add(0, ID_CLEARSTYLES, 0, STR_CLEARSTYLES) .setOnMenuItemClickListener(handler); } if (mManager.canPaste()) { menu.add(0, ID_PASTE, 0, STR_PASTE) .setOnMenuItemClickListener(handler).setAlphabeticShortcut('v'); } } @Override protected void onTextChanged(CharSequence text, int start, int before, int after) { // onTextChanged will be called super's constructor. if (mManager != null) { mManager.updateSpanNextToCursor(getText(), start, before, after); mManager.updateSpanPreviousFromCursor(getText(), start, before, after); if (after > before) { mManager.setTextComposingMask(start, start + after); } else if (before < after) { mManager.unsetTextComposingMask(); } if (mManager.isWaitInput()) { if (after > before) { mManager.onCursorMoved(); onFixSelectedItem(); } else if (after < before) { mManager.onAction(MODE_RESET); } } } super.onTextChanged(text, start, before, after); } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { mInputConnection = new StyledTextInputConnection(super.onCreateInputConnection(outAttrs), this); return mInputConnection; } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); if (focused) { onStartEdit(); } else if (!isButtonsFocused()) { onEndEdit(); } } /** * Initialize members. */ private void init() { mConverter = new StyledTextConverter(this, new StyledTextHtmlStandard()); mDialog = new StyledTextDialog(this); mManager = new EditorManager(this, mDialog); setMovementMethod(new StyledTextArrowKeyMethod(mManager)); mDefaultBackground = getBackground(); requestFocus(); } public interface StyledTextHtmlConverter { public String toHtml(Spanned text); public String toHtml(Spanned text, boolean escapeNonAsciiChar); public String toHtml(Spanned text, boolean escapeNonAsciiChar, int width, float scale); public Spanned fromHtml(String string); public Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler); } public void setStyledTextHtmlConverter(StyledTextHtmlConverter html) { mConverter.setStyledTextHtmlConverter(html); } /** * EditStyledTextInterface provides functions for notifying messages to calling class. */ public interface EditStyledTextNotifier { public void sendHintMsg(int msgId); public void onStateChanged(int mode, int state); public boolean sendOnTouchEvent(MotionEvent event); public boolean isButtonsFocused(); public boolean showPreview(); public void cancelViewManager(); public boolean showInsertImageSelectAlertDialog(); public boolean showMenuAlertDialog(); } /** * Add Notifier. */ public void addEditStyledTextListener(EditStyledTextNotifier estInterface) { if (mESTNotifiers == null) { mESTNotifiers = new ArrayList(); } mESTNotifiers.add(estInterface); } /** * Remove Notifier. */ public void removeEditStyledTextListener(EditStyledTextNotifier estInterface) { if (mESTNotifiers != null) { int i = mESTNotifiers.indexOf(estInterface); if (i > 0) { mESTNotifiers.remove(i); } } } private void sendOnTouchEvent(MotionEvent event) { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { notifier.sendOnTouchEvent(event); } } } public boolean isButtonsFocused() { boolean retval = false; if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { retval |= notifier.isButtonsFocused(); } } return retval; } private void showPreview() { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { if (notifier.showPreview()) { break; } } } } private void cancelViewManagers() { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { notifier.cancelViewManager(); } } } private void showInsertImageSelectAlertDialog() { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { if (notifier.showInsertImageSelectAlertDialog()) { break; } } } } private void showMenuAlertDialog() { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { if (notifier.showMenuAlertDialog()) { break; } } } } /** * Notify hint messages what action is expected to calling class. * * @param msgId Id of the hint message. */ private void sendHintMessage(int msgId) { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { notifier.sendHintMsg(msgId); } } } /** * Notify the event that the mode and state are changed. * * @param mode Mode of the editing action. * @param state Mode of the selection state. */ private void notifyStateChanged(int mode, int state) { if (mESTNotifiers != null) { for (EditStyledTextNotifier notifier : mESTNotifiers) { notifier.onStateChanged(mode, state); } } } /** Start to edit styled text */ public void onStartEdit() { mManager.onAction(MODE_START_EDIT); } /** End of editing styled text */ public void onEndEdit() { mManager.onAction(MODE_END_EDIT); } public void onResetEdit() { mManager.onAction(MODE_RESET); } /** Start to copy styled text */ public void onStartCopy() { mManager.onAction(MODE_COPY); } /** Start to cut styled text */ public void onStartCut() { mManager.onAction(MODE_CUT); } /** Start to paste styled text */ public void onStartPaste() { mManager.onAction(MODE_PASTE); } /** Start to change size */ public void onStartSize() { mManager.onAction(MODE_SIZE); } /** Start to change color */ public void onStartColor() { mManager.onAction(MODE_COLOR); } /** Start to change background color */ public void onStartBackgroundColor() { mManager.onAction(MODE_BGCOLOR); } /** Start to change Alignment */ public void onStartAlign() { mManager.onAction(MODE_ALIGN); } public void onStartTelop() { mManager.onAction(MODE_TELOP); } public void onStartSwing() { mManager.onAction(MODE_SWING); } public void onStartMarquee() { mManager.onAction(MODE_MARQUEE); } /** Start to select a text */ public void onStartSelect() { mManager.onStartSelect(true); } /** Start to select all characters */ public void onStartSelectAll() { mManager.onStartSelectAll(true); } public void onStartShowPreview() { mManager.onAction(MODE_PREVIEW); } public void onStartShowMenuAlertDialog() { mManager.onStartShowMenuAlertDialog(); } public void onStartAction(int mode, boolean notifyStateChanged) { mManager.onAction(mode, notifyStateChanged); } /** Fix selection */ public void onFixSelectedItem() { mManager.onFixSelectedItem(); } public void onInsertImage() { mManager.onAction(MODE_IMAGE); } /** * InsertImage to TextView by using URI * * @param uri URI of the iamge inserted to TextView. */ public void onInsertImage(Uri uri) { mManager.onInsertImage(uri); } /** * InsertImage to TextView by using resource ID * * @param resId Resource ID of the iamge inserted to TextView. */ public void onInsertImage(int resId) { mManager.onInsertImage(resId); } public void onInsertHorizontalLine() { mManager.onAction(MODE_HORIZONTALLINE); } public void onClearStyles() { mManager.onClearStyles(); } public void onBlockSoftKey() { mManager.blockSoftKey(); } public void onUnblockSoftKey() { mManager.unblockSoftKey(); } public void onCancelViewManagers() { mManager.onCancelViewManagers(); } private void onRefreshStyles() { mManager.onRefreshStyles(); } private void onRefreshZeoWidthChar() { mManager.onRefreshZeoWidthChar(); } /** * Set Size of the Item. * * @param size The size of the Item. */ public void setItemSize(int size) { mManager.setItemSize(size, true); } /** * Set Color of the Item. * * @param color The color of the Item. */ public void setItemColor(int color) { mManager.setItemColor(color, true); } /** * Set Alignment of the Item. * * @param align The color of the Item. */ public void setAlignment(Layout.Alignment align) { mManager.setAlignment(align); } /** * Set Background color of View. * * @param color The background color of view. */ @Override public void setBackgroundColor(int color) { if (color != DEFAULT_TRANSPARENT_COLOR) { super.setBackgroundColor(color); } else { setBackgroundDrawable(mDefaultBackground); } mManager.setBackgroundColor(color); onRefreshStyles(); } public void setMarquee(int marquee) { mManager.setMarquee(marquee); } /** * Set html to EditStyledText. * * @param html The html to be set. */ public void setHtml(String html) { mConverter.SetHtml(html); } /** * Set Builder for AlertDialog. * * @param builder Builder for opening Alert Dialog. */ public void setBuilder(Builder builder) { mDialog.setBuilder(builder); } /** * Set Parameters for ColorAlertDialog. * * @param colortitle Title for Alert Dialog. * @param colornames List of name of selecting color. * @param colorints List of int of color. */ public void setColorAlertParams(CharSequence colortitle, CharSequence[] colornames, CharSequence[] colorints, CharSequence transparent) { mDialog.setColorAlertParams(colortitle, colornames, colorints, transparent); } /** * Set Parameters for SizeAlertDialog. * * @param sizetitle Title for Alert Dialog. * @param sizenames List of name of selecting size. * @param sizedisplayints List of int of size displayed in TextView. * @param sizesendints List of int of size exported to HTML. */ public void setSizeAlertParams(CharSequence sizetitle, CharSequence[] sizenames, CharSequence[] sizedisplayints, CharSequence[] sizesendints) { mDialog.setSizeAlertParams(sizetitle, sizenames, sizedisplayints, sizesendints); } public void setAlignAlertParams(CharSequence aligntitle, CharSequence[] alignnames) { mDialog.setAlignAlertParams(aligntitle, alignnames); } public void setMarqueeAlertParams(CharSequence marqueetitle, CharSequence[] marqueenames) { mDialog.setMarqueeAlertParams(marqueetitle, marqueenames); } public void setContextMenuStrings(CharSequence horizontalline, CharSequence clearstyles, CharSequence paste) { STR_HORIZONTALLINE = horizontalline; STR_CLEARSTYLES = clearstyles; STR_PASTE = paste; } /** * Check whether editing is started or not. * * @return Whether editing is started or not. */ public boolean isEditting() { return mManager.isEditting(); } /** * Check whether styled text or not. * * @return Whether styled text or not. */ public boolean isStyledText() { return mManager.isStyledText(); } /** * Check whether SoftKey is Blocked or not. * * @return whether SoftKey is Blocked or not. */ public boolean isSoftKeyBlocked() { return mManager.isSoftKeyBlocked(); } /** * Get the mode of the action. * * @return The mode of the action. */ public int getEditMode() { return mManager.getEditMode(); } /** * Get the state of the selection. * * @return The state of the selection. */ public int getSelectState() { return mManager.getSelectState(); } /** * Get the state of the selection. * * @return The state of the selection. */ public String getHtml() { return mConverter.getHtml(true); } public String getHtml(boolean escapeFlag) { return mConverter.getHtml(escapeFlag); } /** * Get the state of the selection. * * @param uris The array of used uris. * @return The state of the selection. */ public String getHtml(ArrayList uris, boolean escapeFlag) { mConverter.getUriArray(uris, getText()); return mConverter.getHtml(escapeFlag); } public String getPreviewHtml() { return mConverter.getPreviewHtml(); } /** * Get Background color of View. * * @return The background color of View. */ public int getBackgroundColor() { return mManager.getBackgroundColor(); } public EditorManager getEditStyledTextManager() { return mManager; } /** * Get Foreground color of View. * * @return The background color of View. */ public int getForegroundColor(int pos) { if (pos < 0 || pos > getText().length()) { return DEFAULT_FOREGROUND_COLOR; } else { ForegroundColorSpan[] spans = getText().getSpans(pos, pos, ForegroundColorSpan.class); if (spans.length > 0) { return spans[0].getForegroundColor(); } else { return DEFAULT_FOREGROUND_COLOR; } } } private void finishComposingText() { if (mInputConnection != null && !mManager.mTextIsFinishedFlag) { mInputConnection.finishComposingText(); mManager.mTextIsFinishedFlag = true; } } private float getPaddingScale() { if (mPaddingScale <= 0) { mPaddingScale = getContext().getResources().getDisplayMetrics().density; } return mPaddingScale; } /** Convert pixcel to DIP */ private int dipToPx(int dip) { if (mPaddingScale <= 0) { mPaddingScale = getContext().getResources().getDisplayMetrics().density; } return (int) ((float) dip * getPaddingScale() + 0.5); } private int getMaxImageWidthDip() { return MAXIMAGEWIDTHDIP; } private int getMaxImageWidthPx() { return dipToPx(MAXIMAGEWIDTHDIP); } public void addAction(int mode, EditModeActionBase action) { mManager.addAction(mode, action); } public void addInputExtra(boolean create, String extra) { Bundle bundle = super.getInputExtras(create); if (bundle != null) { bundle.putBoolean(extra, true); } } private static void startSelecting(View view, Spannable content) { content.setSpan(SELECTING, 0, 0, PRESSED); } private static void stopSelecting(View view, Spannable content) { content.removeSpan(SELECTING); } /** * EditorManager manages the flow and status of editing actions. */ private class EditorManager { static final private String LOG_TAG = "EditStyledText.EditorManager"; private boolean mEditFlag = false; private boolean mSoftKeyBlockFlag = false; private boolean mKeepNonLineSpan = false; private boolean mWaitInputFlag = false; private boolean mTextIsFinishedFlag = false; private int mMode = MODE_NOTHING; private int mState = STATE_SELECT_OFF; private int mCurStart = 0; private int mCurEnd = 0; private int mColorWaitInput = DEFAULT_TRANSPARENT_COLOR; private int mSizeWaitInput = 0; private int mBackgroundColor = DEFAULT_TRANSPARENT_COLOR; private BackgroundColorSpan mComposingTextMask; private EditStyledText mEST; private EditModeActions mActions; private SoftKeyReceiver mSkr; private SpannableStringBuilder mCopyBuffer; EditorManager(EditStyledText est, StyledTextDialog dialog) { mEST = est; mActions = new EditModeActions(mEST, this, dialog); mSkr = new SoftKeyReceiver(mEST); } public void addAction(int mode, EditModeActionBase action) { mActions.addAction(mode, action); } public void onAction(int mode) { onAction(mode, true); } public void onAction(int mode, boolean notifyStateChanged) { mActions.onAction(mode); if (notifyStateChanged) { mEST.notifyStateChanged(mMode, mState); } } private void startEdit() { resetEdit(); showSoftKey(); } public void onStartSelect(boolean notifyStateChanged) { if (DBG) { Log.d(LOG_TAG, "--- onClickSelect"); } mMode = MODE_SELECT; if (mState == STATE_SELECT_OFF) { mActions.onSelectAction(); } else { unsetSelect(); mActions.onSelectAction(); } if (notifyStateChanged) { mEST.notifyStateChanged(mMode, mState); } } public void onCursorMoved() { if (DBG) { Log.d(LOG_TAG, "--- onClickView"); } if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) { mActions.onSelectAction(); mEST.notifyStateChanged(mMode, mState); } } public void onStartSelectAll(boolean notifyStateChanged) { if (DBG) { Log.d(LOG_TAG, "--- onClickSelectAll"); } handleSelectAll(); if (notifyStateChanged) { mEST.notifyStateChanged(mMode, mState); } } public void onStartShowMenuAlertDialog() { mActions.onAction(MODE_SHOW_MENU); // don't call notify state changed because it have to continue // to the next action. // mEST.notifyStateChanged(mMode, mState); } public void onFixSelectedItem() { if (DBG) { Log.d(LOG_TAG, "--- onFixSelectedItem"); } fixSelectionAndDoNextAction(); mEST.notifyStateChanged(mMode, mState); } public void onInsertImage(Uri uri) { mActions.onAction(MODE_IMAGE, uri); mEST.notifyStateChanged(mMode, mState); } public void onInsertImage(int resId) { mActions.onAction(MODE_IMAGE, resId); mEST.notifyStateChanged(mMode, mState); } private void insertImageFromUri(Uri uri) { insertImageSpan(new EditStyledTextSpans.RescalableImageSpan(mEST.getContext(), uri, mEST.getMaxImageWidthPx()), mEST.getSelectionStart()); } private void insertImageFromResId(int resId) { insertImageSpan(new EditStyledTextSpans.RescalableImageSpan(mEST.getContext(), resId, mEST.getMaxImageWidthDip()), mEST.getSelectionStart()); } private void insertHorizontalLine() { if (DBG) { Log.d(LOG_TAG, "--- onInsertHorizontalLine:"); } int curpos = mEST.getSelectionStart(); if (curpos > 0 && mEST.getText().charAt(curpos - 1) != '\n') { mEST.getText().insert(curpos++, "\n"); } insertImageSpan( new HorizontalLineSpan(0xFF000000, mEST.getWidth(), mEST.getText()), curpos++); mEST.getText().insert(curpos++, "\n"); mEST.setSelection(curpos); mEST.notifyStateChanged(mMode, mState); } private void clearStyles(CharSequence txt) { if (DBG) { Log.d("EditStyledText", "--- onClearStyles"); } int len = txt.length(); if (txt instanceof Editable) { Editable editable = (Editable) txt; Object[] styles = editable.getSpans(0, len, Object.class); for (Object style : styles) { if (style instanceof ParagraphStyle || style instanceof QuoteSpan || style instanceof CharacterStyle && !(style instanceof UnderlineSpan)) { if (style instanceof ImageSpan || style instanceof HorizontalLineSpan) { int start = editable.getSpanStart(style); int end = editable.getSpanEnd(style); editable.replace(start, end, ""); } editable.removeSpan(style); } } } } public void onClearStyles() { mActions.onAction(MODE_CLEARSTYLES); } public void onCancelViewManagers() { mActions.onAction(MODE_CANCEL); } private void clearStyles() { if (DBG) { Log.d(LOG_TAG, "--- onClearStyles"); } clearStyles(mEST.getText()); mEST.setBackgroundDrawable(mEST.mDefaultBackground); mBackgroundColor = DEFAULT_TRANSPARENT_COLOR; onRefreshZeoWidthChar(); } public void onRefreshZeoWidthChar() { Editable txt = mEST.getText(); for (int i = 0; i < txt.length(); i++) { if (txt.charAt(i) == ZEROWIDTHCHAR) { txt.replace(i, i + 1, ""); i--; } } } public void onRefreshStyles() { if (DBG) { Log.d(LOG_TAG, "--- onRefreshStyles"); } Editable txt = mEST.getText(); int len = txt.length(); int width = mEST.getWidth(); HorizontalLineSpan[] lines = txt.getSpans(0, len, HorizontalLineSpan.class); for (HorizontalLineSpan line : lines) { line.resetWidth(width); } MarqueeSpan[] marquees = txt.getSpans(0, len, MarqueeSpan.class); for (MarqueeSpan marquee : marquees) { marquee.resetColor(mEST.getBackgroundColor()); } if (lines.length > 0) { // This is hack, bad needed for renewing View // by inserting new line. txt.replace(0, 1, "" + txt.charAt(0)); } } public void setBackgroundColor(int color) { mBackgroundColor = color; } public void setItemSize(int size, boolean reset) { if (DBG) { Log.d(LOG_TAG, "--- setItemSize"); } if (isWaitingNextAction()) { mSizeWaitInput = size; } else if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) { if (size > 0) { changeSizeSelectedText(size); } if (reset) { resetEdit(); } } } public void setItemColor(int color, boolean reset) { if (DBG) { Log.d(LOG_TAG, "--- setItemColor"); } if (isWaitingNextAction()) { mColorWaitInput = color; } else if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) { if (color != DEFAULT_TRANSPARENT_COLOR) { changeColorSelectedText(color); } if (reset) { resetEdit(); } } } public void setAlignment(Layout.Alignment align) { if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) { changeAlign(align); resetEdit(); } } public void setTelop() { if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) { addTelop(); resetEdit(); } } public void setSwing() { if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) { addSwing(); resetEdit(); } } public void setMarquee(int marquee) { if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) { addMarquee(marquee); resetEdit(); } } public void setTextComposingMask(int start, int end) { if (DBG) { Log.d(TAG, "--- setTextComposingMask:" + start + "," + end); } int min = Math.min(start, end); int max = Math.max(start, end); int foregroundColor; if (isWaitInput() && mColorWaitInput != DEFAULT_TRANSPARENT_COLOR) { foregroundColor = mColorWaitInput; } else { foregroundColor = mEST.getForegroundColor(min); } int backgroundColor = mEST.getBackgroundColor(); if (DBG) { Log.d(TAG, "--- fg:" + Integer.toHexString(foregroundColor) + ",bg:" + Integer.toHexString(backgroundColor) + "," + isWaitInput() + "," + "," + mMode); } if (foregroundColor == backgroundColor) { int maskColor = 0x80000000 | ~(backgroundColor | 0xFF000000); if (mComposingTextMask == null || mComposingTextMask.getBackgroundColor() != maskColor) { mComposingTextMask = new BackgroundColorSpan(maskColor); } mEST.getText().setSpan(mComposingTextMask, min, max, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } private void setEditMode(int mode) { mMode = mode; } private void setSelectState(int state) { mState = state; } public void unsetTextComposingMask() { if (DBG) { Log.d(TAG, "--- unsetTextComposingMask"); } if (mComposingTextMask != null) { mEST.getText().removeSpan(mComposingTextMask); mComposingTextMask = null; } } public boolean isEditting() { return mEditFlag; } /* If the style of the span is added, add check case for that style */ public boolean isStyledText() { Editable txt = mEST.getText(); int len = txt.length(); if (txt.getSpans(0, len, ParagraphStyle.class).length > 0 || txt.getSpans(0, len, QuoteSpan.class).length > 0 || txt.getSpans(0, len, CharacterStyle.class).length > 0 || mBackgroundColor != DEFAULT_TRANSPARENT_COLOR) { return true; } return false; } public boolean isSoftKeyBlocked() { return mSoftKeyBlockFlag; } public boolean isWaitInput() { return mWaitInputFlag; } public int getBackgroundColor() { return mBackgroundColor; } public int getEditMode() { return mMode; } public int getSelectState() { return mState; } public int getSelectionStart() { return mCurStart; } public int getSelectionEnd() { return mCurEnd; } public int getSizeWaitInput() { return mSizeWaitInput; } public int getColorWaitInput() { return mColorWaitInput; } private void setInternalSelection(int curStart, int curEnd) { mCurStart = curStart; mCurEnd = curEnd; } public void updateSpanPreviousFromCursor(Editable txt, int start, int before, int after) { if (DBG) { Log.d(LOG_TAG, "updateSpanPrevious:" + start + "," + before + "," + after); } int end = start + after; int min = Math.min(start, end); int max = Math.max(start, end); Object[] spansBefore = txt.getSpans(min, min, Object.class); for (Object span : spansBefore) { if (span instanceof ForegroundColorSpan || span instanceof AbsoluteSizeSpan || span instanceof MarqueeSpan || span instanceof AlignmentSpan) { int spanstart = txt.getSpanStart(span); int spanend = txt.getSpanEnd(span); if (DBG) { Log.d(LOG_TAG, "spantype:" + span.getClass() + "," + spanstart); } int tempmax = max; if (span instanceof MarqueeSpan || span instanceof AlignmentSpan) { // Line Span tempmax = findLineEnd(mEST.getText(), max); } else { if (mKeepNonLineSpan) { tempmax = spanend; } } if (spanend < tempmax) { if (DBG) { Log.d(LOG_TAG, "updateSpanPrevious: extend span"); } txt.setSpan(span, spanstart, tempmax, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } else if (span instanceof HorizontalLineSpan) { int spanstart = txt.getSpanStart(span); int spanend = txt.getSpanEnd(span); if (before > after) { // When text is deleted just after horizontalLineSpan, horizontalLineSpan // has to be deleted, because the charactor just after horizontalLineSpan // is '\n'. txt.replace(spanstart, spanend, ""); txt.removeSpan(span); } else { // When text is added just after horizontalLineSpan add '\n' just after // horizontalLineSpan. if (spanend == end && end < txt.length() && mEST.getText().charAt(end) != '\n') { mEST.getText().insert(end, "\n"); } } } } } public void updateSpanNextToCursor(Editable txt, int start, int before, int after) { if (DBG) { Log.d(LOG_TAG, "updateSpanNext:" + start + "," + before + "," + after); } int end = start + after; int min = Math.min(start, end); int max = Math.max(start, end); Object[] spansAfter = txt.getSpans(max, max, Object.class); for (Object span : spansAfter) { if (span instanceof MarqueeSpan || span instanceof AlignmentSpan) { int spanstart = txt.getSpanStart(span); int spanend = txt.getSpanEnd(span); if (DBG) { Log.d(LOG_TAG, "spantype:" + span.getClass() + "," + spanend); } int tempmin = min; if (span instanceof MarqueeSpan || span instanceof AlignmentSpan) { tempmin = findLineStart(mEST.getText(), min); } if (tempmin < spanstart && before > after) { txt.removeSpan(span); } else if (spanstart > min) { txt.setSpan(span, min, spanend, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } else if (span instanceof HorizontalLineSpan) { int spanstart = txt.getSpanStart(span); // Whene text is changed just before horizontalLineSpan and there is no '\n' // just before horizontalLineSpan add '\n' if (spanstart == end && end > 0 && mEST.getText().charAt(end - 1) != '\n') { mEST.getText().insert(end, "\n"); mEST.setSelection(end); } } } } /** canPaste returns true only if ClipboardManager doen't contain text. */ public boolean canPaste() { return (mCopyBuffer != null && mCopyBuffer.length() > 0 && removeImageChar( mCopyBuffer).length() == 0); } private void endEdit() { if (DBG) { Log.d(LOG_TAG, "--- handleCancel"); } mMode = MODE_NOTHING; mState = STATE_SELECT_OFF; mEditFlag = false; mColorWaitInput = DEFAULT_TRANSPARENT_COLOR; mSizeWaitInput = 0; mWaitInputFlag = false; mSoftKeyBlockFlag = false; mKeepNonLineSpan = false; mTextIsFinishedFlag = false; unsetSelect(); mEST.setOnClickListener(null); unblockSoftKey(); } private void fixSelectionAndDoNextAction() { if (DBG) { Log.d(LOG_TAG, "--- handleComplete:" + mCurStart + "," + mCurEnd); } if (!mEditFlag) { return; } if (mCurStart == mCurEnd) { if (DBG) { Log.d(LOG_TAG, "--- cancel handle complete:" + mCurStart); } resetEdit(); return; } if (mState == STATE_SELECTED) { mState = STATE_SELECT_FIX; } // When the complete button is clicked, this should do action. mActions.doNext(mMode); //MetaKeyKeyListener.stopSelecting(mEST, mEST.getText()); stopSelecting(mEST, mEST.getText()); } // Remove obj character for DynamicDrawableSpan from clipboard. private SpannableStringBuilder removeImageChar(SpannableStringBuilder text) { SpannableStringBuilder buf = new SpannableStringBuilder(text); DynamicDrawableSpan[] styles = buf.getSpans(0, buf.length(), DynamicDrawableSpan.class); for (DynamicDrawableSpan style : styles) { if (style instanceof HorizontalLineSpan || style instanceof RescalableImageSpan) { int start = buf.getSpanStart(style); int end = buf.getSpanEnd(style); buf.replace(start, end, ""); } } return buf; } private void copyToClipBoard() { int min = Math.min(getSelectionStart(), getSelectionEnd()); int max = Math.max(getSelectionStart(), getSelectionEnd()); mCopyBuffer = (SpannableStringBuilder) mEST.getText().subSequence(min, max); SpannableStringBuilder clipboardtxt = removeImageChar(mCopyBuffer); ClipboardManager clip = (ClipboardManager) getContext() .getSystemService(Context.CLIPBOARD_SERVICE); clip.setText(clipboardtxt); if (DBG) { dumpSpannableString(clipboardtxt); dumpSpannableString(mCopyBuffer); } } private void cutToClipBoard() { copyToClipBoard(); int min = Math.min(getSelectionStart(), getSelectionEnd()); int max = Math.max(getSelectionStart(), getSelectionEnd()); mEST.getText().delete(min, max); } private boolean isClipBoardChanged(CharSequence clipboardText) { if (DBG) { Log.d(TAG, "--- isClipBoardChanged:" + clipboardText); } if (mCopyBuffer == null) { return true; } int len = clipboardText.length(); CharSequence removedClipBoard = removeImageChar(mCopyBuffer); if (DBG) { Log.d(TAG, "--- clipBoard:" + len + "," + removedClipBoard + clipboardText); } if (len != removedClipBoard.length()) { return true; } for (int i = 0; i < len; ++i) { if (clipboardText.charAt(i) != removedClipBoard.charAt(i)) { return true; } } return false; } private void pasteFromClipboard() { int min = Math.min(mEST.getSelectionStart(), mEST.getSelectionEnd()); int max = Math.max(mEST.getSelectionStart(), mEST.getSelectionEnd()); // TODO: Find more smart way to set Span to Clipboard. Selection.setSelection(mEST.getText(), max); ClipboardManager clip = (ClipboardManager) getContext() .getSystemService(Context.CLIPBOARD_SERVICE); mKeepNonLineSpan = true; mEST.getText().replace(min, max, clip.getText()); if (!isClipBoardChanged(clip.getText())) { if (DBG) { Log.d(TAG, "--- handlePaste: startPasteImage"); } DynamicDrawableSpan[] styles = mCopyBuffer.getSpans(0, mCopyBuffer.length(), DynamicDrawableSpan.class); for (DynamicDrawableSpan style : styles) { int start = mCopyBuffer.getSpanStart(style); if (style instanceof HorizontalLineSpan) { insertImageSpan(new HorizontalLineSpan(0xFF000000, mEST.getWidth(), mEST.getText()), min + start); } else if (style instanceof RescalableImageSpan) { insertImageSpan( new RescalableImageSpan(mEST.getContext(), ((RescalableImageSpan) style).getContentUri(), mEST.getMaxImageWidthPx()), min + start); } } } } private void handleSelectAll() { if (!mEditFlag) { return; } mActions.onAction(MODE_SELECTALL); } private void selectAll() { Selection.selectAll(mEST.getText()); mCurStart = mEST.getSelectionStart(); mCurEnd = mEST.getSelectionEnd(); mMode = MODE_SELECT; mState = STATE_SELECT_FIX; } private void resetEdit() { endEdit(); mEditFlag = true; mEST.notifyStateChanged(mMode, mState); } private void setSelection() { if (DBG) { Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd); } if (mCurStart >= 0 && mCurStart <= mEST.getText().length() && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) { if (mCurStart < mCurEnd) { mEST.setSelection(mCurStart, mCurEnd); mState = STATE_SELECTED; } else if (mCurStart > mCurEnd) { mEST.setSelection(mCurEnd, mCurStart); mState = STATE_SELECTED; } else { mState = STATE_SELECT_ON; } } else { Log.e(LOG_TAG, "Select is on, but cursor positions are illigal.:" + mEST.getText().length() + "," + mCurStart + "," + mCurEnd); } } private void unsetSelect() { if (DBG) { Log.d(LOG_TAG, "--- offSelect"); } //MetaKeyKeyListener.stopSelecting(mEST, mEST.getText()); stopSelecting(mEST, mEST.getText()); int currpos = mEST.getSelectionStart(); mEST.setSelection(currpos, currpos); mState = STATE_SELECT_OFF; } private void setSelectStartPos() { if (DBG) { Log.d(LOG_TAG, "--- setSelectStartPos"); } mCurStart = mEST.getSelectionStart(); mState = STATE_SELECT_ON; } private void setSelectEndPos() { if (mEST.getSelectionEnd() == mCurStart) { setEndPos(mEST.getSelectionStart()); } else { setEndPos(mEST.getSelectionEnd()); } } public void setEndPos(int pos) { if (DBG) { Log.d(LOG_TAG, "--- setSelectedEndPos:" + pos); } mCurEnd = pos; setSelection(); } private boolean isWaitingNextAction() { if (DBG) { Log.d(LOG_TAG, "--- waitingNext:" + mCurStart + "," + mCurEnd + "," + mState); } if (mCurStart == mCurEnd && mState == STATE_SELECT_FIX) { waitSelection(); return true; } else { resumeSelection(); return false; } } private void waitSelection() { if (DBG) { Log.d(LOG_TAG, "--- waitSelection"); } mWaitInputFlag = true; if (mCurStart == mCurEnd) { mState = STATE_SELECT_ON; } else { mState = STATE_SELECTED; } //MetaKeyKeyListener.startSelecting(mEST, mEST.getText()); startSelecting(mEST, mEST.getText()); } private void resumeSelection() { if (DBG) { Log.d(LOG_TAG, "--- resumeSelection"); } mWaitInputFlag = false; mState = STATE_SELECT_FIX; //MetaKeyKeyListener.stopSelecting(mEST, mEST.getText()); stopSelecting(mEST, mEST.getText()); } private boolean isTextSelected() { return (mState == STATE_SELECTED || mState == STATE_SELECT_FIX); } private void setStyledTextSpan(Object span, int start, int end) { if (DBG) { Log.d(LOG_TAG, "--- setStyledTextSpan:" + mMode + "," + start + "," + end); } int min = Math.min(start, end); int max = Math.max(start, end); mEST.getText().setSpan(span, min, max, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); Selection.setSelection(mEST.getText(), max); } private void setLineStyledTextSpan(Object span) { int min = Math.min(mCurStart, mCurEnd); int max = Math.max(mCurStart, mCurEnd); int current = mEST.getSelectionStart(); int start = findLineStart(mEST.getText(), min); int end = findLineEnd(mEST.getText(), max); if (start == end) { mEST.getText().insert(end, "\n"); setStyledTextSpan(span, start, end + 1); } else { setStyledTextSpan(span, start, end); } Selection.setSelection(mEST.getText(), current); } private void changeSizeSelectedText(int size) { if (mCurStart != mCurEnd) { setStyledTextSpan(new AbsoluteSizeSpan(size), mCurStart, mCurEnd); } else { Log.e(LOG_TAG, "---changeSize: Size of the span is zero"); } } private void changeColorSelectedText(int color) { if (mCurStart != mCurEnd) { setStyledTextSpan(new ForegroundColorSpan(color), mCurStart, mCurEnd); } else { Log.e(LOG_TAG, "---changeColor: Size of the span is zero"); } } private void changeAlign(Layout.Alignment align) { setLineStyledTextSpan(new AlignmentSpan.Standard(align)); } private void addTelop() { addMarquee(MarqueeSpan.ALTERNATE); } private void addSwing() { addMarquee(MarqueeSpan.SCROLL); } private void addMarquee(int marquee) { if (DBG) { Log.d(LOG_TAG, "--- addMarquee:" + marquee); } setLineStyledTextSpan(new MarqueeSpan(marquee, mEST.getBackgroundColor())); } private void insertImageSpan(DynamicDrawableSpan span, int curpos) { if (DBG) { Log.d(LOG_TAG, "--- insertImageSpan:"); } if (span != null && span.getDrawable() != null) { mEST.getText().insert(curpos, "" + IMAGECHAR); mEST.getText().setSpan(span, curpos, curpos + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mEST.notifyStateChanged(mMode, mState); } else { Log.e(LOG_TAG, "--- insertImageSpan: null span was inserted"); mEST.sendHintMessage(HINT_MSG_BIG_SIZE_ERROR); } } private int findLineStart(Editable text, int current) { int pos = current; for (; pos > 0; pos--) { if (text.charAt(pos - 1) == '\n') { break; } } if (DBG) { Log.d(LOG_TAG, "--- findLineStart:" + current + "," + text.length() + "," + pos); } return pos; } private int findLineEnd(Editable text, int current) { int pos = current; for (; pos < text.length(); pos++) { if (text.charAt(pos) == '\n') { pos++; break; } } if (DBG) { Log.d(LOG_TAG, "--- findLineEnd:" + current + "," + text.length() + "," + pos); } return pos; } // Only for debug. private void dumpSpannableString(CharSequence txt) { if (txt instanceof Spannable) { Spannable spannable = (Spannable) txt; int len = spannable.length(); if (DBG) { Log.d(TAG, "--- dumpSpannableString, txt:" + spannable + ", len:" + len); } Object[] styles = spannable.getSpans(0, len, Object.class); for (Object style : styles) { if (DBG) { Log.d(TAG, "--- dumpSpannableString, class:" + style + "," + spannable.getSpanStart(style) + "," + spannable.getSpanEnd(style) + "," + spannable.getSpanFlags(style)); } } } } public void showSoftKey() { showSoftKey(mEST.getSelectionStart(), mEST.getSelectionEnd()); } public void showSoftKey(int oldSelStart, int oldSelEnd) { if (DBG) { Log.d(LOG_TAG, "--- showsoftkey"); } if (!mEST.isFocused() || isSoftKeyBlocked()) { return; } mSkr.mNewStart = Selection.getSelectionStart(mEST.getText()); mSkr.mNewEnd = Selection.getSelectionEnd(mEST.getText()); InputMethodManager imm = (InputMethodManager) getContext().getSystemService( Context.INPUT_METHOD_SERVICE); if (imm.showSoftInput(mEST, 0, mSkr) && mSkr != null) { Selection.setSelection(getText(), oldSelStart, oldSelEnd); } } public void hideSoftKey() { if (DBG) { Log.d(LOG_TAG, "--- hidesoftkey"); } if (!mEST.isFocused()) { return; } mSkr.mNewStart = Selection.getSelectionStart(mEST.getText()); mSkr.mNewEnd = Selection.getSelectionEnd(mEST.getText()); InputMethodManager imm = (InputMethodManager) mEST.getContext().getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mEST.getWindowToken(), 0, mSkr); } public void blockSoftKey() { if (DBG) { Log.d(LOG_TAG, "--- blockSoftKey:"); } hideSoftKey(); mSoftKeyBlockFlag = true; } public void unblockSoftKey() { if (DBG) { Log.d(LOG_TAG, "--- unblockSoftKey:"); } mSoftKeyBlockFlag = false; } } private class StyledTextHtmlStandard implements StyledTextHtmlConverter { public String toHtml(Spanned text) { return Html.toHtml(text); } public String toHtml(Spanned text, boolean escapeNonAsciiChar) { return Html.toHtml(text); } public String toHtml(Spanned text, boolean escapeNonAsciiChar, int width, float scale) { return Html.toHtml(text); } public Spanned fromHtml(String source) { return Html.fromHtml(source); } public Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) { return Html.fromHtml(source, imageGetter, tagHandler); } } private class StyledTextConverter { private EditStyledText mEST; private StyledTextHtmlConverter mHtml; public StyledTextConverter(EditStyledText est, StyledTextHtmlConverter html) { mEST = est; mHtml = html; } public void setStyledTextHtmlConverter(StyledTextHtmlConverter html) { mHtml = html; } public String getHtml(boolean escapeFlag) { mEST.clearComposingText(); mEST.onRefreshZeoWidthChar(); String htmlBody = mHtml.toHtml(mEST.getText(), escapeFlag); if (DBG) { Log.d(TAG, "--- getHtml:" + htmlBody); } return htmlBody; } public String getPreviewHtml() { mEST.clearComposingText(); mEST.onRefreshZeoWidthChar(); String html = mHtml.toHtml(mEST.getText(), true, getMaxImageWidthDip(), getPaddingScale()); int bgColor = mEST.getBackgroundColor(); html = String.format("%s", Color.red(bgColor), Color.green(bgColor), Color.blue(bgColor), html); if (DBG) { Log.d(TAG, "--- getPreviewHtml:" + html + "," + mEST.getWidth()); } return html; } public void getUriArray(ArrayList uris, Editable text) { uris.clear(); if (DBG) { Log.d(TAG, "--- getUriArray:"); } int len = text.length(); int next; for (int i = 0; i < text.length(); i = next) { next = text.nextSpanTransition(i, len, ImageSpan.class); ImageSpan[] images = text.getSpans(i, next, ImageSpan.class); for (int j = 0; j < images.length; j++) { if (DBG) { Log.d(TAG, "--- getUriArray: foundArray" + images[j].getSource()); } uris.add(Uri.parse(images[j].getSource())); } } } public void SetHtml(String html) { final Spanned spanned = mHtml.fromHtml(html, new Html.ImageGetter() { public Drawable getDrawable(String src) { Log.d(TAG, "--- sethtml: src=" + src); if (src.startsWith("content://")) { Uri uri = Uri.parse(src); try { Drawable drawable = null; Bitmap bitmap = null; System.gc(); InputStream is = mEST.getContext().getContentResolver().openInputStream(uri); BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, opt); is.close(); is = mEST.getContext().getContentResolver().openInputStream(uri); int width, height; width = opt.outWidth; height = opt.outHeight; if (opt.outWidth > getMaxImageWidthPx()) { width = getMaxImageWidthPx(); height = height * getMaxImageWidthPx() / opt.outWidth; Rect padding = new Rect(0, 0, width, height); bitmap = BitmapFactory.decodeStream(is, padding, null); } else { bitmap = BitmapFactory.decodeStream(is); } drawable = new BitmapDrawable( mEST.getContext().getResources(), bitmap); drawable.setBounds(0, 0, width, height); is.close(); return drawable; } catch (Exception e) { Log.e(TAG, "--- set html: Failed to loaded content " + uri, e); return null; } catch (OutOfMemoryError e) { Log.e(TAG, "OutOfMemoryError"); mEST.setHint(HINT_MSG_BIG_SIZE_ERROR); return null; } } return null; } }, null); mEST.setText(spanned); } } private static class SoftKeyReceiver extends ResultReceiver { int mNewStart; int mNewEnd; EditStyledText mEST; SoftKeyReceiver(EditStyledText est) { super(est.getHandler()); mEST = est; } @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode != InputMethodManager.RESULT_SHOWN) { Selection.setSelection(mEST.getText(), mNewStart, mNewEnd); } } } public static class SavedStyledTextState extends BaseSavedState { public int mBackgroundColor; SavedStyledTextState(Parcelable superState) { super(superState); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(mBackgroundColor); } @Override public String toString() { return "EditStyledText.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " bgcolor=" + mBackgroundColor + "}"; } } private static class StyledTextDialog { private static final int TYPE_FOREGROUND = 0; private static final int TYPE_BACKGROUND = 1; private Builder mBuilder; private AlertDialog mAlertDialog; private CharSequence mColorTitle; private CharSequence mSizeTitle; private CharSequence mAlignTitle; private CharSequence mMarqueeTitle; private CharSequence[] mColorNames; private CharSequence[] mColorInts; private CharSequence[] mSizeNames; private CharSequence[] mSizeDisplayInts; private CharSequence[] mSizeSendInts; private CharSequence[] mAlignNames; private CharSequence[] mMarqueeNames; private CharSequence mColorDefaultMessage; private EditStyledText mEST; public StyledTextDialog(EditStyledText est) { mEST = est; } public void setBuilder(Builder builder) { mBuilder = builder; } public void setColorAlertParams(CharSequence colortitle, CharSequence[] colornames, CharSequence[] colorInts, CharSequence defaultColorMessage) { mColorTitle = colortitle; mColorNames = colornames; mColorInts = colorInts; mColorDefaultMessage = defaultColorMessage; } public void setSizeAlertParams(CharSequence sizetitle, CharSequence[] sizenames, CharSequence[] sizedisplayints, CharSequence[] sizesendints) { mSizeTitle = sizetitle; mSizeNames = sizenames; mSizeDisplayInts = sizedisplayints; mSizeSendInts = sizesendints; } public void setAlignAlertParams(CharSequence aligntitle, CharSequence[] alignnames) { mAlignTitle = aligntitle; mAlignNames = alignnames; } public void setMarqueeAlertParams(CharSequence marqueetitle, CharSequence[] marqueenames) { mMarqueeTitle = marqueetitle; mMarqueeNames = marqueenames; } private boolean checkColorAlertParams() { if (DBG) { Log.d(TAG, "--- checkParams"); } if (mBuilder == null) { Log.e(TAG, "--- builder is null."); return false; } else if (mColorTitle == null || mColorNames == null || mColorInts == null) { Log.e(TAG, "--- color alert params are null."); return false; } else if (mColorNames.length != mColorInts.length) { Log.e(TAG, "--- the length of color alert params are " + "different."); return false; } return true; } private boolean checkSizeAlertParams() { if (DBG) { Log.d(TAG, "--- checkParams"); } if (mBuilder == null) { Log.e(TAG, "--- builder is null."); return false; } else if (mSizeTitle == null || mSizeNames == null || mSizeDisplayInts == null || mSizeSendInts == null) { Log.e(TAG, "--- size alert params are null."); return false; } else if (mSizeNames.length != mSizeDisplayInts.length && mSizeSendInts.length != mSizeDisplayInts.length) { Log.e(TAG, "--- the length of size alert params are " + "different."); return false; } return true; } private boolean checkAlignAlertParams() { if (DBG) { Log.d(TAG, "--- checkAlignAlertParams"); } if (mBuilder == null) { Log.e(TAG, "--- builder is null."); return false; } else if (mAlignTitle == null) { Log.e(TAG, "--- align alert params are null."); return false; } return true; } private boolean checkMarqueeAlertParams() { if (DBG) { Log.d(TAG, "--- checkMarqueeAlertParams"); } if (mBuilder == null) { Log.e(TAG, "--- builder is null."); return false; } else if (mMarqueeTitle == null) { Log.e(TAG, "--- Marquee alert params are null."); return false; } return true; } private void buildDialogue(CharSequence title, CharSequence[] names, DialogInterface.OnClickListener l) { mBuilder.setTitle(title); mBuilder.setIcon(0); mBuilder.setPositiveButton(null, null); mBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mEST.onStartEdit(); } }); mBuilder.setItems(names, l); mBuilder.setView(null); mBuilder.setCancelable(true); mBuilder.setOnCancelListener(new OnCancelListener() { public void onCancel(DialogInterface arg0) { if (DBG) { Log.d(TAG, "--- oncancel"); } mEST.onStartEdit(); } }); mBuilder.show(); } private void buildAndShowColorDialogue(int type, CharSequence title, int[] colors) { final int HORIZONTAL_ELEMENT_NUM = 5; final int BUTTON_SIZE = mEST.dipToPx(50); final int BUTTON_MERGIN = mEST.dipToPx(2); final int BUTTON_PADDING = mEST.dipToPx(15); mBuilder.setTitle(title); mBuilder.setIcon(0); mBuilder.setPositiveButton(null, null); mBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mEST.onStartEdit(); } }); mBuilder.setItems(null, null); LinearLayout verticalLayout = new LinearLayout(mEST.getContext()); verticalLayout.setOrientation(LinearLayout.VERTICAL); verticalLayout.setGravity(Gravity.CENTER_HORIZONTAL); verticalLayout.setPadding(BUTTON_PADDING, BUTTON_PADDING, BUTTON_PADDING, BUTTON_PADDING); LinearLayout horizontalLayout = null; for (int i = 0; i < colors.length; i++) { if (i % HORIZONTAL_ELEMENT_NUM == 0) { horizontalLayout = new LinearLayout(mEST.getContext()); verticalLayout.addView(horizontalLayout); } Button button = new Button(mEST.getContext()); button.setHeight(BUTTON_SIZE); button.setWidth(BUTTON_SIZE); ColorPaletteDrawable cp = new ColorPaletteDrawable(colors[i], BUTTON_SIZE, BUTTON_SIZE, BUTTON_MERGIN); button.setBackgroundDrawable(cp); button.setDrawingCacheBackgroundColor(colors[i]); if (type == TYPE_FOREGROUND) { button.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { mEST.setItemColor(view.getDrawingCacheBackgroundColor()); if (mAlertDialog != null) { mAlertDialog.setView(null); mAlertDialog.dismiss(); mAlertDialog = null; } else { Log.e(TAG, "--- buildAndShowColorDialogue: can't find alertDialog"); } } }); } else if (type == TYPE_BACKGROUND) { button.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { mEST.setBackgroundColor(view.getDrawingCacheBackgroundColor()); if (mAlertDialog != null) { mAlertDialog.setView(null); mAlertDialog.dismiss(); mAlertDialog = null; } else { Log.e(TAG, "--- buildAndShowColorDialogue: can't find alertDialog"); } } }); } horizontalLayout.addView(button); } if (type == TYPE_BACKGROUND) { mBuilder.setPositiveButton(mColorDefaultMessage, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mEST.setBackgroundColor(DEFAULT_TRANSPARENT_COLOR); } }); } else if (type == TYPE_FOREGROUND) { mBuilder.setPositiveButton(mColorDefaultMessage, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mEST.setItemColor(DEFAULT_FOREGROUND_COLOR); } }); } mBuilder.setView(verticalLayout); mBuilder.setCancelable(true); mBuilder.setOnCancelListener(new OnCancelListener() { public void onCancel(DialogInterface arg0) { mEST.onStartEdit(); } }); mAlertDialog = mBuilder.show(); } private void onShowForegroundColorAlertDialog() { if (DBG) { Log.d(TAG, "--- onShowForegroundColorAlertDialog"); } if (!checkColorAlertParams()) { return; } int[] colorints = new int[mColorInts.length]; for (int i = 0; i < colorints.length; i++) { colorints[i] = Integer.parseInt((String) mColorInts[i], 16) - 0x01000000; } buildAndShowColorDialogue(TYPE_FOREGROUND, mColorTitle, colorints); } private void onShowBackgroundColorAlertDialog() { if (DBG) { Log.d(TAG, "--- onShowBackgroundColorAlertDialog"); } if (!checkColorAlertParams()) { return; } int[] colorInts = new int[mColorInts.length]; for (int i = 0; i < colorInts.length; i++) { colorInts[i] = Integer.parseInt((String) mColorInts[i], 16) - 0x01000000; } buildAndShowColorDialogue(TYPE_BACKGROUND, mColorTitle, colorInts); } private void onShowSizeAlertDialog() { if (DBG) { Log.d(TAG, "--- onShowSizeAlertDialog"); } if (!checkSizeAlertParams()) { return; } buildDialogue(mSizeTitle, mSizeNames, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Log.d(TAG, "mBuilder.onclick:" + which); int size = mEST.dipToPx(Integer.parseInt((String) mSizeDisplayInts[which])); mEST.setItemSize(size); } }); } private void onShowAlignAlertDialog() { if (DBG) { Log.d(TAG, "--- onShowAlignAlertDialog"); } if (!checkAlignAlertParams()) { return; } buildDialogue(mAlignTitle, mAlignNames, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Layout.Alignment align = Layout.Alignment.ALIGN_NORMAL; switch (which) { case 0: align = Layout.Alignment.ALIGN_NORMAL; break; case 1: align = Layout.Alignment.ALIGN_CENTER; break; case 2: align = Layout.Alignment.ALIGN_OPPOSITE; break; default: Log.e(TAG, "--- onShowAlignAlertDialog: got illigal align."); break; } mEST.setAlignment(align); } }); } private void onShowMarqueeAlertDialog() { if (DBG) { Log.d(TAG, "--- onShowMarqueeAlertDialog"); } if (!checkMarqueeAlertParams()) { return; } buildDialogue(mMarqueeTitle, mMarqueeNames, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (DBG) { Log.d(TAG, "mBuilder.onclick:" + which); } mEST.setMarquee(which); } }); } } private class MenuHandler implements MenuItem.OnMenuItemClickListener { public boolean onMenuItemClick(MenuItem item) { return onTextContextMenuItem(item.getItemId()); } } private static class StyledTextArrowKeyMethod extends ArrowKeyMovementMethod { EditorManager mManager; String LOG_TAG = "StyledTextArrowKeyMethod"; StyledTextArrowKeyMethod(EditorManager manager) { super(); mManager = manager; } @Override public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { if (DBG) { Log.d(LOG_TAG, "---onkeydown:" + keyCode); } mManager.unsetTextComposingMask(); if (mManager.getSelectState() == STATE_SELECT_ON || mManager.getSelectState() == STATE_SELECTED) { return executeDown(widget, buffer, keyCode); } else { return super.onKeyDown(widget, buffer, keyCode, event); } } private int getEndPos(TextView widget) { int end; if (widget.getSelectionStart() == mManager.getSelectionStart()) { end = widget.getSelectionEnd(); } else { end = widget.getSelectionStart(); } return end; } protected boolean up(TextView widget, Spannable buffer) { if (DBG) { Log.d(LOG_TAG, "--- up:"); } Layout layout = widget.getLayout(); int end = getEndPos(widget); int line = layout.getLineForOffset(end); if (line > 0) { int to; if (layout.getParagraphDirection(line) == layout .getParagraphDirection(line - 1)) { float h = layout.getPrimaryHorizontal(end); to = layout.getOffsetForHorizontal(line - 1, h); } else { to = layout.getLineStart(line - 1); } mManager.setEndPos(to); mManager.onCursorMoved(); } return true; } protected boolean down(TextView widget, Spannable buffer) { if (DBG) { Log.d(LOG_TAG, "--- down:"); } Layout layout = widget.getLayout(); int end = getEndPos(widget); int line = layout.getLineForOffset(end); if (line < layout.getLineCount() - 1) { int to; if (layout.getParagraphDirection(line) == layout .getParagraphDirection(line + 1)) { float h = layout.getPrimaryHorizontal(end); to = layout.getOffsetForHorizontal(line + 1, h); } else { to = layout.getLineStart(line + 1); } mManager.setEndPos(to); mManager.onCursorMoved(); } return true; } protected boolean left(TextView widget, Spannable buffer) { if (DBG) { Log.d(LOG_TAG, "--- left:"); } Layout layout = widget.getLayout(); int to = layout.getOffsetToLeftOf(getEndPos(widget)); mManager.setEndPos(to); mManager.onCursorMoved(); return true; } protected boolean right(TextView widget, Spannable buffer) { if (DBG) { Log.d(LOG_TAG, "--- right:"); } Layout layout = widget.getLayout(); int to = layout.getOffsetToRightOf(getEndPos(widget)); mManager.setEndPos(to); mManager.onCursorMoved(); return true; } private boolean executeDown(TextView widget, Spannable buffer, int keyCode) { if (DBG) { Log.d(LOG_TAG, "--- executeDown: " + keyCode); } boolean handled = false; switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: handled |= up(widget, buffer); break; case KeyEvent.KEYCODE_DPAD_DOWN: handled |= down(widget, buffer); break; case KeyEvent.KEYCODE_DPAD_LEFT: handled |= left(widget, buffer); break; case KeyEvent.KEYCODE_DPAD_RIGHT: handled |= right(widget, buffer); break; case KeyEvent.KEYCODE_DPAD_CENTER: mManager.onFixSelectedItem(); handled = true; break; } return handled; } } public static class StyledTextInputConnection extends InputConnectionWrapper { EditStyledText mEST; public StyledTextInputConnection(InputConnection target, EditStyledText est) { super(target, true); mEST = est; } @Override public boolean commitText(CharSequence text, int newCursorPosition) { if (DBG) { Log.d(TAG, "--- commitText:"); } mEST.mManager.unsetTextComposingMask(); return super.commitText(text, newCursorPosition); } @Override public boolean finishComposingText() { if (DBG) { Log.d(TAG, "--- finishcomposing:"); } if (!mEST.isSoftKeyBlocked() && !mEST.isButtonsFocused() && !mEST.isEditting()) { // TODO onEndEdit isn't called temporally . mEST.onEndEdit(); } return super.finishComposingText(); } } public static class EditStyledTextSpans { private static final String LOG_TAG = "EditStyledTextSpan"; public static class HorizontalLineSpan extends DynamicDrawableSpan { HorizontalLineDrawable mDrawable; public HorizontalLineSpan(int color, int width, Spannable spannable) { super(ALIGN_BOTTOM); mDrawable = new HorizontalLineDrawable(color, width, spannable); } @Override public Drawable getDrawable() { return mDrawable; } public void resetWidth(int width) { mDrawable.renewBounds(width); } public int getColor() { return mDrawable.getPaint().getColor(); } } public static class MarqueeSpan extends CharacterStyle { public static final int SCROLL = 0; public static final int ALTERNATE = 1; public static final int NOTHING = 2; private int mType; private int mMarqueeColor; public MarqueeSpan(int type, int bgc) { mType = type; checkType(type); mMarqueeColor = getMarqueeColor(type, bgc); } public MarqueeSpan(int type) { this(type, EditStyledText.DEFAULT_TRANSPARENT_COLOR); } public int getType() { return mType; } public void resetColor(int bgc) { mMarqueeColor = getMarqueeColor(mType, bgc); } private int getMarqueeColor(int type, int bgc) { int THRESHOLD = 128; int a = Color.alpha(bgc); int r = Color.red(bgc); int g = Color.green(bgc); int b = Color.blue(bgc); if (a == 0) { a = 0x80; } switch (type) { case SCROLL: if (r > THRESHOLD) { r = r / 2; } else { r = (0XFF - r) / 2; } break; case ALTERNATE: if (g > THRESHOLD) { g = g / 2; } else { g = (0XFF - g) / 2; } break; case NOTHING: return DEFAULT_TRANSPARENT_COLOR; default: Log.e(TAG, "--- getMarqueeColor: got illigal marquee ID."); return DEFAULT_TRANSPARENT_COLOR; } return Color.argb(a, r, g, b); } private boolean checkType(int type) { if (type == SCROLL || type == ALTERNATE) { return true; } else { Log.e(LOG_TAG, "--- Invalid type of MarqueeSpan"); return false; } } @Override public void updateDrawState(TextPaint tp) { tp.bgColor = mMarqueeColor; } } public static class RescalableImageSpan extends ImageSpan { Uri mContentUri; private Drawable mDrawable; private Context mContext; public int mIntrinsicWidth = -1; public int mIntrinsicHeight = -1; private final int MAXWIDTH; public RescalableImageSpan(Context context, Uri uri, int maxwidth) { super(context, uri); mContext = context; mContentUri = uri; MAXWIDTH = maxwidth; } public RescalableImageSpan(Context context, int resourceId, int maxwidth) { super(context, resourceId); mContext = context; MAXWIDTH = maxwidth; } @Override public Drawable getDrawable() { if (mDrawable != null) { return mDrawable; } else if (mContentUri != null) { Bitmap bitmap = null; System.gc(); try { InputStream is = mContext.getContentResolver().openInputStream(mContentUri); BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, opt); is.close(); is = mContext.getContentResolver().openInputStream(mContentUri); int width, height; width = opt.outWidth; height = opt.outHeight; mIntrinsicWidth = width; mIntrinsicHeight = height; if (opt.outWidth > MAXWIDTH) { width = MAXWIDTH; height = height * MAXWIDTH / opt.outWidth; Rect padding = new Rect(0, 0, width, height); bitmap = BitmapFactory.decodeStream(is, padding, null); } else { bitmap = BitmapFactory.decodeStream(is); } mDrawable = new BitmapDrawable(mContext.getResources(), bitmap); mDrawable.setBounds(0, 0, width, height); is.close(); } catch (Exception e) { Log.e(LOG_TAG, "Failed to loaded content " + mContentUri, e); return null; } catch (OutOfMemoryError e) { Log.e(LOG_TAG, "OutOfMemoryError"); return null; } } else { mDrawable = super.getDrawable(); rescaleBigImage(mDrawable); mIntrinsicWidth = mDrawable.getIntrinsicWidth(); mIntrinsicHeight = mDrawable.getIntrinsicHeight(); } return mDrawable; } public boolean isOverSize() { return (getDrawable().getIntrinsicWidth() > MAXWIDTH); } public Uri getContentUri() { return mContentUri; } private void rescaleBigImage(Drawable image) { if (DBG) { Log.d(LOG_TAG, "--- rescaleBigImage:"); } if (MAXWIDTH < 0) { return; } int image_width = image.getIntrinsicWidth(); int image_height = image.getIntrinsicHeight(); if (DBG) { Log.d(LOG_TAG, "--- rescaleBigImage:" + image_width + "," + image_height + "," + MAXWIDTH); } if (image_width > MAXWIDTH) { image_width = MAXWIDTH; image_height = image_height * MAXWIDTH / image_width; } image.setBounds(0, 0, image_width, image_height); } } public static class HorizontalLineDrawable extends ShapeDrawable { private Spannable mSpannable; private int mWidth; private static boolean DBG_HL = false; public HorizontalLineDrawable(int color, int width, Spannable spannable) { super(new RectShape()); mSpannable = spannable; mWidth = width; renewColor(color); renewBounds(width); } @Override public void draw(Canvas canvas) { renewColor(); Rect rect = new Rect(0, 9, mWidth, 11); canvas.drawRect(rect, getPaint()); } public void renewBounds(int width) { int MARGIN = 20; int HEIGHT = 20; if (DBG_HL) { Log.d(LOG_TAG, "--- renewBounds:" + width); } if (width > MARGIN) { width -= MARGIN; } mWidth = width; setBounds(0, 0, width, HEIGHT); } private void renewColor(int color) { if (DBG_HL) { Log.d(LOG_TAG, "--- renewColor:" + color); } getPaint().setColor(color); } private void renewColor() { HorizontalLineSpan parent = getParentSpan(); Spannable text = mSpannable; int start = text.getSpanStart(parent); int end = text.getSpanEnd(parent); ForegroundColorSpan[] spans = text.getSpans(start, end, ForegroundColorSpan.class); if (DBG_HL) { Log.d(LOG_TAG, "--- renewColor:" + spans.length); } if (spans.length > 0) { renewColor(spans[spans.length - 1].getForegroundColor()); } } private HorizontalLineSpan getParentSpan() { Spannable text = mSpannable; HorizontalLineSpan[] images = text.getSpans(0, text.length(), HorizontalLineSpan.class); if (images.length > 0) { for (HorizontalLineSpan image : images) { if (image.getDrawable() == this) { return image; } } } Log.e(LOG_TAG, "---renewBounds: Couldn't find"); return null; } } } public static class ColorPaletteDrawable extends ShapeDrawable { private Rect mRect; public ColorPaletteDrawable(int color, int width, int height, int mergin) { super(new RectShape()); mRect = new Rect(mergin, mergin, width - mergin, height - mergin); getPaint().setColor(color); } @Override public void draw(Canvas canvas) { canvas.drawRect(mRect, getPaint()); } } public class EditModeActions { private static final String TAG = "EditModeActions"; private static final boolean DBG = true; private EditStyledText mEST; private EditorManager mManager; private StyledTextDialog mDialog; private int mMode = EditStyledText.MODE_NOTHING; private HashMap mActionMap = new HashMap(); private NothingAction mNothingAction = new NothingAction(); private CopyAction mCopyAction = new CopyAction(); private PasteAction mPasteAction = new PasteAction(); private SelectAction mSelectAction = new SelectAction(); private CutAction mCutAction = new CutAction(); private SelectAllAction mSelectAllAction = new SelectAllAction(); private HorizontalLineAction mHorizontalLineAction = new HorizontalLineAction(); private StopSelectionAction mStopSelectionAction = new StopSelectionAction(); private ClearStylesAction mClearStylesAction = new ClearStylesAction(); private ImageAction mImageAction = new ImageAction(); private BackgroundColorAction mBackgroundColorAction = new BackgroundColorAction(); private PreviewAction mPreviewAction = new PreviewAction(); private CancelAction mCancelEditAction = new CancelAction(); private TextViewAction mTextViewAction = new TextViewAction(); private StartEditAction mStartEditAction = new StartEditAction(); private EndEditAction mEndEditAction = new EndEditAction(); private ResetAction mResetAction = new ResetAction(); private ShowMenuAction mShowMenuAction = new ShowMenuAction(); private AlignAction mAlignAction = new AlignAction(); private TelopAction mTelopAction = new TelopAction(); private SwingAction mSwingAction = new SwingAction(); private MarqueeDialogAction mMarqueeDialogAction = new MarqueeDialogAction(); private ColorAction mColorAction = new ColorAction(); private SizeAction mSizeAction = new SizeAction(); EditModeActions(EditStyledText est, EditorManager manager, StyledTextDialog dialog) { mEST = est; mManager = manager; mDialog = dialog; mActionMap.put(EditStyledText.MODE_NOTHING, mNothingAction); mActionMap.put(EditStyledText.MODE_COPY, mCopyAction); mActionMap.put(EditStyledText.MODE_PASTE, mPasteAction); mActionMap.put(EditStyledText.MODE_SELECT, mSelectAction); mActionMap.put(EditStyledText.MODE_CUT, mCutAction); mActionMap.put(EditStyledText.MODE_SELECTALL, mSelectAllAction); mActionMap.put(EditStyledText.MODE_HORIZONTALLINE, mHorizontalLineAction); mActionMap.put(EditStyledText.MODE_STOP_SELECT, mStopSelectionAction); mActionMap.put(EditStyledText.MODE_CLEARSTYLES, mClearStylesAction); mActionMap.put(EditStyledText.MODE_IMAGE, mImageAction); mActionMap.put(EditStyledText.MODE_BGCOLOR, mBackgroundColorAction); mActionMap.put(EditStyledText.MODE_PREVIEW, mPreviewAction); mActionMap.put(EditStyledText.MODE_CANCEL, mCancelEditAction); mActionMap.put(EditStyledText.MODE_TEXTVIEWFUNCTION, mTextViewAction); mActionMap.put(EditStyledText.MODE_START_EDIT, mStartEditAction); mActionMap.put(EditStyledText.MODE_END_EDIT, mEndEditAction); mActionMap.put(EditStyledText.MODE_RESET, mResetAction); mActionMap.put(EditStyledText.MODE_SHOW_MENU, mShowMenuAction); mActionMap.put(EditStyledText.MODE_ALIGN, mAlignAction); mActionMap.put(EditStyledText.MODE_TELOP, mTelopAction); mActionMap.put(EditStyledText.MODE_SWING, mSwingAction); mActionMap.put(EditStyledText.MODE_MARQUEE, mMarqueeDialogAction); mActionMap.put(EditStyledText.MODE_COLOR, mColorAction); mActionMap.put(EditStyledText.MODE_SIZE, mSizeAction); } public void addAction(int modeId, EditModeActionBase action) { mActionMap.put(modeId, action); } public void onAction(int newMode, Object[] params) { getAction(newMode).addParams(params); mMode = newMode; doNext(newMode); } public void onAction(int newMode, Object param) { onAction(newMode, new Object[] { param }); } public void onAction(int newMode) { onAction(newMode, null); } public void onSelectAction() { doNext(EditStyledText.MODE_SELECT); } private EditModeActionBase getAction(int mode) { if (mActionMap.containsKey(mode)) { return mActionMap.get(mode); } return null; } public boolean doNext() { return doNext(mMode); } public boolean doNext(int mode) { if (DBG) { Log.d(TAG, "--- do the next action: " + mode + "," + mManager.getSelectState()); } EditModeActionBase action = getAction(mode); if (action == null) { Log.e(TAG, "--- invalid action error."); return false; } switch (mManager.getSelectState()) { case EditStyledText.STATE_SELECT_OFF: return action.doNotSelected(); case EditStyledText.STATE_SELECT_ON: return action.doStartPosIsSelected(); case EditStyledText.STATE_SELECTED: return action.doEndPosIsSelected(); case EditStyledText.STATE_SELECT_FIX: if (mManager.isWaitInput()) { return action.doSelectionIsFixedAndWaitingInput(); } else { return action.doSelectionIsFixed(); } default: return false; } } public class EditModeActionBase { private Object[] mParams; protected boolean canOverWrap() { return false; } protected boolean canSelect() { return false; } protected boolean canWaitInput() { return false; } protected boolean needSelection() { return false; } protected boolean isLine() { return false; } protected boolean doNotSelected() { return false; } protected boolean doStartPosIsSelected() { return doNotSelected(); } protected boolean doEndPosIsSelected() { return doStartPosIsSelected(); } protected boolean doSelectionIsFixed() { return doEndPosIsSelected(); } protected boolean doSelectionIsFixedAndWaitingInput() { return doEndPosIsSelected(); } protected boolean fixSelection() { mEST.finishComposingText(); mManager.setSelectState(EditStyledText.STATE_SELECT_FIX); return true; } protected void addParams(Object[] o) { mParams = o; } protected Object getParam(int num) { if (mParams == null || num > mParams.length) { if (DBG) { Log.d(TAG, "--- Number of the parameter is out of bound."); } return null; } else { return mParams[num]; } } } public class NothingAction extends EditModeActionBase { } public class TextViewActionBase extends EditModeActionBase { @Override protected boolean doNotSelected() { if (mManager.getEditMode() == EditStyledText.MODE_NOTHING || mManager.getEditMode() == EditStyledText.MODE_SELECT) { mManager.setEditMode(mMode); onSelectAction(); return true; } return false; } @Override protected boolean doEndPosIsSelected() { if (mManager.getEditMode() == EditStyledText.MODE_NOTHING || mManager.getEditMode() == EditStyledText.MODE_SELECT) { mManager.setEditMode(mMode); fixSelection(); doNext(); return true; } else if (mManager.getEditMode() != mMode) { mManager.resetEdit(); mManager.setEditMode(mMode); doNext(); return true; } return false; } } public class TextViewAction extends TextViewActionBase { @Override protected boolean doEndPosIsSelected() { if (super.doEndPosIsSelected()) { return true; } Object param = getParam(0); if (param != null && param instanceof Integer) { mEST.onTextContextMenuItem((Integer) param); } mManager.resetEdit(); return true; } } public class CopyAction extends TextViewActionBase { @Override protected boolean doEndPosIsSelected() { if (super.doEndPosIsSelected()) { return true; } mManager.copyToClipBoard(); mManager.resetEdit(); return true; } } public class CutAction extends TextViewActionBase { @Override protected boolean doEndPosIsSelected() { if (super.doEndPosIsSelected()) { return true; } mManager.cutToClipBoard(); mManager.resetEdit(); return true; } } public class SelectAction extends EditModeActionBase { @Override protected boolean doNotSelected() { if (mManager.isTextSelected()) { Log.e(TAG, "Selection is off, but selected"); } mManager.setSelectStartPos(); mEST.sendHintMessage(EditStyledText.HINT_MSG_SELECT_END); return true; } @Override protected boolean doStartPosIsSelected() { if (mManager.isTextSelected()) { Log.e(TAG, "Selection now start, but selected"); } mManager.setSelectEndPos(); mEST.sendHintMessage(EditStyledText.HINT_MSG_PUSH_COMPETE); if (mManager.getEditMode() != EditStyledText.MODE_SELECT) { doNext(mManager.getEditMode()); // doNextHandle needs edit mode in editor. } return true; } @Override protected boolean doSelectionIsFixed() { return false; } } public class PasteAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.pasteFromClipboard(); mManager.resetEdit(); return true; } } public class SelectAllAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.selectAll(); return true; } } public class HorizontalLineAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.insertHorizontalLine(); return true; } } public class ClearStylesAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.clearStyles(); return true; } } public class StopSelectionAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.fixSelectionAndDoNextAction(); return true; } } public class CancelAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mEST.cancelViewManagers(); return true; } } public class ImageAction extends EditModeActionBase { @Override protected boolean doNotSelected() { Object param = getParam(0); if (param != null) { if (param instanceof Uri) { mManager.insertImageFromUri((Uri) param); } else if (param instanceof Integer) { mManager.insertImageFromResId((Integer) param); } } else { mEST.showInsertImageSelectAlertDialog(); } return true; } } public class BackgroundColorAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mDialog.onShowBackgroundColorAlertDialog(); return true; } } public class PreviewAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mEST.showPreview(); return true; } } public class StartEditAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.startEdit(); return true; } } public class EndEditAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.endEdit(); return true; } } public class ResetAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mManager.resetEdit(); return true; } } public class ShowMenuAction extends EditModeActionBase { @Override protected boolean doNotSelected() { mEST.showMenuAlertDialog(); return true; } } public class SetSpanActionBase extends EditModeActionBase { @Override protected boolean doNotSelected() { if (mManager.getEditMode() == EditStyledText.MODE_NOTHING || mManager.getEditMode() == EditStyledText.MODE_SELECT) { mManager.setEditMode(mMode); mManager.setInternalSelection(mEST.getSelectionStart(), mEST.getSelectionEnd()); fixSelection(); doNext(); return true; } else if (mManager.getEditMode() != mMode) { Log.d(TAG, "--- setspanactionbase" + mManager.getEditMode() + "," + mMode); if (!mManager.isWaitInput()) { mManager.resetEdit(); mManager.setEditMode(mMode); } else { mManager.setEditMode(EditStyledText.MODE_NOTHING); mManager.setSelectState(EditStyledText.STATE_SELECT_OFF); } doNext(); return true; } return false; } @Override protected boolean doStartPosIsSelected() { if (mManager.getEditMode() == EditStyledText.MODE_NOTHING || mManager.getEditMode() == EditStyledText.MODE_SELECT) { mManager.setEditMode(mMode); onSelectAction(); return true; } return doNotSelected(); } @Override protected boolean doEndPosIsSelected() { if (mManager.getEditMode() == EditStyledText.MODE_NOTHING || mManager.getEditMode() == EditStyledText.MODE_SELECT) { mManager.setEditMode(mMode); fixSelection(); doNext(); return true; } return doStartPosIsSelected(); } @Override protected boolean doSelectionIsFixed() { if (doEndPosIsSelected()) { return true; } mEST.sendHintMessage(EditStyledText.HINT_MSG_NULL); return false; } } public class AlignAction extends SetSpanActionBase { @Override protected boolean doSelectionIsFixed() { if (super.doSelectionIsFixed()) { return true; } mDialog.onShowAlignAlertDialog(); return true; } } public class TelopAction extends SetSpanActionBase { @Override protected boolean doSelectionIsFixed() { if (super.doSelectionIsFixed()) { return true; } mManager.setTelop(); return true; } } public class SwingAction extends SetSpanActionBase { @Override protected boolean doSelectionIsFixed() { if (super.doSelectionIsFixed()) { return true; } mManager.setSwing(); return true; } } public class MarqueeDialogAction extends SetSpanActionBase { @Override protected boolean doSelectionIsFixed() { if (super.doSelectionIsFixed()) { return true; } mDialog.onShowMarqueeAlertDialog(); return true; } } public class ColorAction extends SetSpanActionBase { @Override protected boolean doSelectionIsFixed() { if (super.doSelectionIsFixed()) { return true; } mDialog.onShowForegroundColorAlertDialog(); return true; } @Override protected boolean doSelectionIsFixedAndWaitingInput() { if (super.doSelectionIsFixedAndWaitingInput()) { return true; } int size = mManager.getSizeWaitInput(); mManager.setItemColor(mManager.getColorWaitInput(), false); // selection was resumed if (!mManager.isWaitInput()) { mManager.setItemSize(size, false); mManager.resetEdit(); } else { fixSelection(); mDialog.onShowForegroundColorAlertDialog(); } return true; } } public class SizeAction extends SetSpanActionBase { @Override protected boolean doSelectionIsFixed() { if (super.doSelectionIsFixed()) { return true; } mDialog.onShowSizeAlertDialog(); return true; } @Override protected boolean doSelectionIsFixedAndWaitingInput() { if (super.doSelectionIsFixedAndWaitingInput()) { return true; } int color = mManager.getColorWaitInput(); mManager.setItemSize(mManager.getSizeWaitInput(), false); if (!mManager.isWaitInput()) { mManager.setItemColor(color, false); mManager.resetEdit(); } else { fixSelection(); mDialog.onShowSizeAlertDialog(); } return true; } } } }