blob: 45bbf174d7391046d35b7bf23569502417c993f7 [file] [log] [blame]
/*
* 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<EditStyledTextNotifier> 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<EditStyledTextNotifier>();
}
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<Uri> 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("<body bgcolor=\"#%02X%02X%02X\">%s</body>",
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<Uri> 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<Integer, EditModeActionBase> mActionMap =
new HashMap<Integer, EditModeActionBase>();
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;
}
}
}
}