1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
19import java.io.InputStream;
20import java.util.ArrayList;
21
22import android.app.AlertDialog.Builder;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
27import android.graphics.Canvas;
28import android.graphics.drawable.BitmapDrawable;
29import android.graphics.drawable.Drawable;
30import android.graphics.drawable.ShapeDrawable;
31import android.graphics.drawable.shapes.RectShape;
32import android.net.Uri;
33import android.os.Bundle;
34import android.text.Editable;
35import android.text.Html;
36import android.text.Layout;
37import android.text.Spannable;
38import android.text.Spanned;
39import android.text.method.ArrowKeyMovementMethod;
40import android.text.style.AbsoluteSizeSpan;
41import android.text.style.AlignmentSpan;
42import android.text.style.CharacterStyle;
43import android.text.style.ForegroundColorSpan;
44import android.text.style.ImageSpan;
45import android.text.style.ParagraphStyle;
46import android.text.style.QuoteSpan;
47import android.util.AttributeSet;
48import android.util.Log;
49import android.view.KeyEvent;
50import android.view.MotionEvent;
51import android.view.View;
52import android.view.inputmethod.InputMethodManager;
53import android.widget.EditText;
54import android.widget.TextView;
55
56/**
57 * EditStyledText extends EditText for managing the flow and status to edit
58 * the styled text. This manages the states and flows of editing, supports
59 * inserting image, import/export HTML.
60 */
61public class EditStyledText extends EditText {
62
63    private static final String LOG_TAG = "EditStyledText";
64    private static final boolean DBG = false;
65
66    /**
67     * The modes of editing actions.
68     */
69    /** The mode that no editing action is done. */
70    public static final int MODE_NOTHING = 0;
71    /** The mode of copy. */
72    public static final int MODE_COPY = 1;
73    /** The mode of paste. */
74    public static final int MODE_PASTE = 2;
75    /** The mode of changing size. */
76    public static final int MODE_SIZE = 3;
77    /** The mode of changing color. */
78    public static final int MODE_COLOR = 4;
79    /** The mode of selection. */
80    public static final int MODE_SELECT = 5;
81    /** The mode of changing alignment. */
82    public static final int MODE_ALIGN = 6;
83    /** The mode of changing cut. */
84    public static final int MODE_CUT = 7;
85
86    /**
87     * The state of selection.
88     */
89    /** The state that selection isn't started. */
90    public static final int STATE_SELECT_OFF = 0;
91    /** The state that selection is started. */
92    public static final int STATE_SELECT_ON = 1;
93    /** The state that selection is done, but not fixed. */
94    public static final int STATE_SELECTED = 2;
95    /** The state that selection is done and not fixed. */
96    public static final int STATE_SELECT_FIX = 3;
97
98    /**
99     * The help message strings.
100     */
101    public static final int HINT_MSG_NULL = 0;
102    public static final int HINT_MSG_COPY_BUF_BLANK = 1;
103    public static final int HINT_MSG_SELECT_START = 2;
104    public static final int HINT_MSG_SELECT_END = 3;
105    public static final int HINT_MSG_PUSH_COMPETE = 4;
106
107
108    /**
109     * The help message strings.
110     */
111    public static final int DEFAULT_BACKGROUND_COLOR = 0x00FFFFFF;
112
113    /**
114     * EditStyledTextInterface provides functions for notifying messages to
115     * calling class.
116     */
117    public interface EditStyledTextNotifier {
118        public void notifyHintMsg(int msgId);
119        public void notifyStateChanged(int mode, int state);
120    }
121
122    private EditStyledTextNotifier mESTInterface;
123
124    /**
125     * EditStyledTextEditorManager manages the flow and status of each
126     * function for editing styled text.
127     */
128    private EditorManager mManager;
129    private StyledTextConverter mConverter;
130    private StyledTextDialog mDialog;
131    private Drawable mDefaultBackground;
132    private int mBackgroundColor;
133
134    /**
135     * EditStyledText extends EditText for managing flow of each editing
136     * action.
137     */
138    public EditStyledText(Context context, AttributeSet attrs, int defStyle) {
139        super(context, attrs, defStyle);
140        init();
141    }
142
143    public EditStyledText(Context context, AttributeSet attrs) {
144        super(context, attrs);
145        init();
146    }
147
148    public EditStyledText(Context context) {
149        super(context);
150        init();
151    }
152
153    /**
154     * Set Notifier.
155     */
156    public void setNotifier(EditStyledTextNotifier estInterface) {
157        mESTInterface = estInterface;
158    }
159
160    /**
161     * Set Builder for AlertDialog.
162     *
163     * @param builder
164     *            Builder for opening Alert Dialog.
165     */
166    public void setBuilder(Builder builder) {
167        mDialog.setBuilder(builder);
168    }
169
170    /**
171     * Set Parameters for ColorAlertDialog.
172     *
173     * @param colortitle
174     *            Title for Alert Dialog.
175     * @param colornames
176     *            List of name of selecting color.
177     * @param colorints
178     *            List of int of color.
179     */
180    public void setColorAlertParams(CharSequence colortitle,
181            CharSequence[] colornames, CharSequence[] colorints) {
182        mDialog.setColorAlertParams(colortitle, colornames, colorints);
183    }
184
185    /**
186     * Set Parameters for SizeAlertDialog.
187     *
188     * @param sizetitle
189     *            Title for Alert Dialog.
190     * @param sizenames
191     *            List of name of selecting size.
192     * @param sizedisplayints
193     *            List of int of size displayed in TextView.
194     * @param sizesendints
195     *            List of int of size exported to HTML.
196     */
197    public void setSizeAlertParams(CharSequence sizetitle,
198            CharSequence[] sizenames, CharSequence[] sizedisplayints,
199            CharSequence[] sizesendints) {
200        mDialog.setSizeAlertParams(sizetitle, sizenames, sizedisplayints,
201                sizesendints);
202    }
203
204    public void setAlignAlertParams(CharSequence aligntitle,
205            CharSequence[] alignnames) {
206        mDialog.setAlignAlertParams(aligntitle, alignnames);
207    }
208
209    @Override
210    public boolean onTouchEvent(MotionEvent event) {
211        if (mManager.isSoftKeyBlocked() &&
212                event.getAction() == MotionEvent.ACTION_UP) {
213            cancelLongPress();
214        }
215        final boolean superResult = super.onTouchEvent(event);
216        if (event.getAction() == MotionEvent.ACTION_UP) {
217            if (DBG) {
218                Log.d(LOG_TAG, "--- onTouchEvent");
219            }
220            mManager.onCursorMoved();
221        }
222        return superResult;
223    }
224
225    /**
226     * Start editing. This function have to be called before other editing
227     * actions.
228     */
229    public void onStartEdit() {
230        mManager.onStartEdit();
231    }
232
233    /**
234     * End editing.
235     */
236    public void onEndEdit() {
237        mManager.onEndEdit();
238    }
239
240    /**
241     * Start "Copy" action.
242     */
243    public void onStartCopy() {
244        mManager.onStartCopy();
245    }
246
247    /**
248     * Start "Cut" action.
249     */
250    public void onStartCut() {
251        mManager.onStartCut();
252    }
253
254    /**
255     * Start "Paste" action.
256     */
257    public void onStartPaste() {
258        mManager.onStartPaste();
259    }
260
261    /**
262     * Start changing "Size" action.
263     */
264    public void onStartSize() {
265        mManager.onStartSize();
266    }
267
268    /**
269     * Start changing "Color" action.
270     */
271    public void onStartColor() {
272        mManager.onStartColor();
273    }
274
275    /**
276     * Start changing "BackgroundColor" action.
277     */
278    public void onStartBackgroundColor() {
279        mManager.onStartBackgroundColor();
280    }
281
282    /**
283     * Start changing "Alignment" action.
284     */
285    public void onStartAlign() {
286        mManager.onStartAlign();
287    }
288
289    /**
290     * Start "Select" action.
291     */
292    public void onStartSelect() {
293        mManager.onStartSelect();
294    }
295
296    /**
297     * Start "SelectAll" action.
298     */
299    public void onStartSelectAll() {
300        mManager.onStartSelectAll();
301    }
302
303    /**
304     * Fix Selected Item.
305     */
306    public void onFixSelectedItem() {
307        mManager.onFixSelectedItem();
308    }
309
310    /**
311     * InsertImage to TextView by using URI
312     *
313     * @param uri
314     *            URI of the iamge inserted to TextView.
315     */
316    public void onInsertImage(Uri uri) {
317        mManager.onInsertImage(uri);
318    }
319
320    /**
321     * InsertImage to TextView by using resource ID
322     *
323     * @param resId
324     *            Resource ID of the iamge inserted to TextView.
325     */
326    public void onInsertImage(int resId) {
327        mManager.onInsertImage(resId);
328    }
329
330    public void onInsertHorizontalLine() {
331        mManager.onInsertHorizontalLine();
332    }
333
334    public void onClearStyles() {
335        mManager.onClearStyles();
336    }
337    /**
338     * Set Size of the Item.
339     *
340     * @param size
341     *            The size of the Item.
342     */
343    public void setItemSize(int size) {
344        mManager.setItemSize(size);
345    }
346
347    /**
348     * Set Color of the Item.
349     *
350     * @param color
351     *            The color of the Item.
352     */
353    public void setItemColor(int color) {
354        mManager.setItemColor(color);
355    }
356
357    /**
358     * Set Alignment of the Item.
359     *
360     * @param color
361     *            The color of the Item.
362     */
363    public void setAlignment(Layout.Alignment align) {
364        mManager.setAlignment(align);
365    }
366
367    /**
368     * Set Background color of View.
369     *
370     * @param color
371     *            The background color of view.
372     */
373    @Override
374    public void setBackgroundColor(int color) {
375        super.setBackgroundColor(color);
376        mBackgroundColor = color;
377    }
378
379    /**
380     * Set html to EditStyledText.
381     *
382     * @param html
383     *            The html to be set.
384     */
385    public void setHtml(String html) {
386        mConverter.SetHtml(html);
387    }
388    /**
389     * Check whether editing is started or not.
390     *
391     * @return Whether editing is started or not.
392     */
393    public boolean isEditting() {
394        return mManager.isEditting();
395    }
396
397    /**
398     * Check whether styled text or not.
399     *
400     * @return Whether styled text or not.
401     */
402    public boolean isStyledText() {
403        return mManager.isStyledText();
404    }
405    /**
406     * Check whether SoftKey is Blocked or not.
407     *
408     * @return whether SoftKey is Blocked or not.
409     */
410    public boolean isSoftKeyBlocked() {
411        return mManager.isSoftKeyBlocked();
412    }
413
414    /**
415     * Get the mode of the action.
416     *
417     * @return The mode of the action.
418     */
419    public int getEditMode() {
420        return mManager.getEditMode();
421    }
422
423    /**
424     * Get the state of the selection.
425     *
426     * @return The state of the selection.
427     */
428    public int getSelectState() {
429        return mManager.getSelectState();
430    }
431
432    @Override
433    public Bundle getInputExtras(boolean create) {
434        if (DBG) {
435            Log.d(LOG_TAG, "---getInputExtras");
436        }
437        Bundle bundle = super.getInputExtras(create);
438        if (bundle != null) {
439            bundle = new Bundle();
440        }
441        bundle.putBoolean("allowEmoji", true);
442        return bundle;
443    }
444
445    /**
446     * Get the state of the selection.
447     *
448     * @return The state of the selection.
449     */
450    public String getHtml() {
451        return mConverter.getHtml();
452    }
453
454    /**
455     * Get the state of the selection.
456     *
457     * @param uris
458     *            The array of used uris.
459     * @return The state of the selection.
460     */
461    public String getHtml(ArrayList<Uri> uris) {
462        mConverter.getUriArray(uris, getText());
463        return mConverter.getHtml();
464    }
465
466    /**
467     * Get Background color of View.
468     *
469     * @return The background color of View.
470     */
471    public int getBackgroundColor() {
472        return mBackgroundColor;
473    }
474
475    /**
476     * Get Foreground color of View.
477     *
478     * @return The background color of View.
479     */
480    public int getForeGroundColor(int pos) {
481        if (DBG) {
482            Log.d(LOG_TAG, "---getForeGroundColor: " + pos);
483        }
484        if (pos < 0 || pos > getText().length()) {
485            Log.e(LOG_TAG, "---getForeGroundColor: Illigal position.");
486            return DEFAULT_BACKGROUND_COLOR;
487        } else {
488            ForegroundColorSpan[] spans =
489                getText().getSpans(pos, pos, ForegroundColorSpan.class);
490            if (spans.length > 0) {
491                return spans[0].getForegroundColor();
492            } else {
493                return DEFAULT_BACKGROUND_COLOR;
494            }
495        }
496    }
497
498    /**
499     * Initialize members.
500     */
501    private void init() {
502        if (DBG) {
503            Log.d(LOG_TAG, "--- init");
504        }
505        requestFocus();
506        mDefaultBackground = getBackground();
507        mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
508        mManager = new EditorManager(this);
509        mConverter = new StyledTextConverter(this);
510        mDialog = new StyledTextDialog(this);
511        setMovementMethod(new StyledTextArrowKeyMethod(mManager));
512        mManager.blockSoftKey();
513        mManager.unblockSoftKey();
514    }
515
516    /**
517     * Show Foreground Color Selecting Dialog.
518     */
519    private void onShowForegroundColorAlert() {
520        mDialog.onShowForegroundColorAlertDialog();
521    }
522
523    /**
524     * Show Background Color Selecting Dialog.
525     */
526    private void onShowBackgroundColorAlert() {
527        mDialog.onShowBackgroundColorAlertDialog();
528    }
529
530    /**
531     * Show Size Selecting Dialog.
532     */
533    private void onShowSizeAlert() {
534        mDialog.onShowSizeAlertDialog();
535    }
536
537    /**
538     * Show Alignment Selecting Dialog.
539     */
540    private void onShowAlignAlert() {
541        mDialog.onShowAlignAlertDialog();
542    }
543
544    /**
545     * Notify hint messages what action is expected to calling class.
546     *
547     * @param msgId
548     *            Id of the hint message.
549     */
550    private void setHintMessage(int msgId) {
551        if (mESTInterface != null) {
552            mESTInterface.notifyHintMsg(msgId);
553        }
554    }
555
556    /**
557     * Notify the event that the mode and state are changed.
558     *
559     * @param mode
560     *            Mode of the editing action.
561     * @param state
562     *            Mode of the selection state.
563     */
564    private void notifyStateChanged(int mode, int state) {
565        if (mESTInterface != null) {
566            mESTInterface.notifyStateChanged(mode, state);
567        }
568    }
569
570    /**
571     * EditorManager manages the flow and status of editing actions.
572     */
573    private class EditorManager {
574        private boolean mEditFlag = false;
575        private boolean mSoftKeyBlockFlag = false;
576        private int mMode = 0;
577        private int mState = 0;
578        private int mCurStart = 0;
579        private int mCurEnd = 0;
580        private EditStyledText mEST;
581
582        EditorManager(EditStyledText est) {
583            mEST = est;
584        }
585
586        public void onStartEdit() {
587            if (DBG) {
588                Log.d(LOG_TAG, "--- onStartEdit");
589            }
590            Log.d(LOG_TAG, "--- onstartedit:");
591            handleResetEdit();
592            mEST.notifyStateChanged(mMode, mState);
593        }
594
595        public void onEndEdit() {
596            if (DBG) {
597                Log.d(LOG_TAG, "--- onEndEdit");
598            }
599            handleCancel();
600            mEST.notifyStateChanged(mMode, mState);
601        }
602
603        public void onStartCopy() {
604            if (DBG) {
605                Log.d(LOG_TAG, "--- onStartCopy");
606            }
607            handleCopy();
608            mEST.notifyStateChanged(mMode, mState);
609        }
610
611        public void onStartCut() {
612            if (DBG) {
613                Log.d(LOG_TAG, "--- onStartCut");
614            }
615            handleCut();
616            mEST.notifyStateChanged(mMode, mState);
617        }
618
619        public void onStartPaste() {
620            if (DBG) {
621                Log.d(LOG_TAG, "--- onStartPaste");
622            }
623            handlePaste();
624            mEST.notifyStateChanged(mMode, mState);
625        }
626
627        public void onStartSize() {
628            if (DBG) {
629                Log.d(LOG_TAG, "--- onStartSize");
630            }
631            handleSize();
632            mEST.notifyStateChanged(mMode, mState);
633        }
634
635        public void onStartAlign() {
636            if (DBG) {
637                Log.d(LOG_TAG, "--- onStartAlignRight");
638            }
639            handleAlign();
640            mEST.notifyStateChanged(mMode, mState);
641        }
642
643        public void onStartColor() {
644            if (DBG) {
645                Log.d(LOG_TAG, "--- onClickColor");
646            }
647            handleColor();
648            mEST.notifyStateChanged(mMode, mState);
649        }
650
651        public void onStartBackgroundColor() {
652            if (DBG) {
653                Log.d(LOG_TAG, "--- onClickColor");
654            }
655            mEST.onShowBackgroundColorAlert();
656            mEST.notifyStateChanged(mMode, mState);
657        }
658
659        public void onStartSelect() {
660            if (DBG) {
661                Log.d(LOG_TAG, "--- onClickSelect");
662            }
663            mMode = MODE_SELECT;
664            if (mState == STATE_SELECT_OFF) {
665                handleSelect();
666            } else {
667                unsetSelect();
668                handleSelect();
669            }
670            mEST.notifyStateChanged(mMode, mState);
671        }
672
673        public void onCursorMoved() {
674            if (DBG) {
675                Log.d(LOG_TAG, "--- onClickView");
676            }
677            if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) {
678                handleSelect();
679                mEST.notifyStateChanged(mMode, mState);
680            }
681        }
682
683        public void onStartSelectAll() {
684            if (DBG) {
685                Log.d(LOG_TAG, "--- onClickSelectAll");
686            }
687            handleSelectAll();
688            mEST.notifyStateChanged(mMode, mState);
689        }
690
691        public void onFixSelectedItem() {
692            if (DBG) {
693                Log.d(LOG_TAG, "--- onClickComplete");
694            }
695            handleComplete();
696            mEST.notifyStateChanged(mMode, mState);
697        }
698
699        public void onInsertImage(Uri uri) {
700            if (DBG) {
701                Log.d(LOG_TAG, "--- onInsertImage by URI: " + uri.getPath()
702                        + "," + uri.toString());
703            }
704            insertImageSpan(new ImageSpan(mEST.getContext(), uri));
705            mEST.notifyStateChanged(mMode, mState);
706        }
707
708        public void onInsertImage(int resID) {
709            if (DBG) {
710                Log.d(LOG_TAG, "--- onInsertImage by resID");
711            }
712            insertImageSpan(new ImageSpan(mEST.getContext(), resID));
713            mEST.notifyStateChanged(mMode, mState);
714        }
715
716        public void onInsertHorizontalLine() {
717            if (DBG) {
718                Log.d(LOG_TAG, "--- onInsertHorizontalLine:");
719            }
720            insertImageSpan(new HorizontalLineSpan(0xFF000000, mEST));
721            mEST.notifyStateChanged(mMode, mState);
722        }
723
724        public void onClearStyles() {
725            if (DBG) {
726                Log.d(LOG_TAG, "--- onClearStyles");
727            }
728            Editable txt = mEST.getText();
729            int len = txt.length();
730            Object[] styles = txt.getSpans(0, len, Object.class);
731            for (Object style : styles) {
732                if (style instanceof ParagraphStyle ||
733                        style instanceof QuoteSpan ||
734                        style instanceof CharacterStyle) {
735                    if (style instanceof ImageSpan) {
736                        int start = txt.getSpanStart(style);
737                        int end = txt.getSpanEnd(style);
738                        txt.replace(start, end, "");
739                    }
740                    txt.removeSpan(style);
741                }
742            }
743            mEST.setBackgroundDrawable(mEST.mDefaultBackground);
744            mEST.mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
745        }
746
747        public void setItemSize(int size) {
748            if (DBG) {
749                Log.d(LOG_TAG, "--- onClickSizeItem");
750            }
751            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
752                changeSizeSelectedText(size);
753                handleResetEdit();
754            }
755        }
756
757        public void setItemColor(int color) {
758            if (DBG) {
759                Log.d(LOG_TAG, "--- onClickColorItem");
760            }
761            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
762                changeColorSelectedText(color);
763                handleResetEdit();
764            }
765        }
766
767        public void setAlignment(Layout.Alignment align) {
768            if (DBG) {
769                Log.d(LOG_TAG, "--- onClickColorItem");
770            }
771            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
772                changeAlign(align);
773                handleResetEdit();
774            }
775        }
776
777        public boolean isEditting() {
778            return mEditFlag;
779        }
780
781        /* If the style of the span is added, add check case for that style */
782        public boolean isStyledText() {
783            Editable txt = mEST.getText();
784            int len = txt.length();
785            if (txt.getSpans(0, len -1, ParagraphStyle.class).length > 0 ||
786                    txt.getSpans(0, len -1, QuoteSpan.class).length > 0 ||
787                    txt.getSpans(0, len -1, CharacterStyle.class).length > 0 ||
788                    mEST.mBackgroundColor != DEFAULT_BACKGROUND_COLOR) {
789                return true;
790            }
791            return false;
792        }
793
794        public boolean isSoftKeyBlocked() {
795            return mSoftKeyBlockFlag;
796        }
797
798        public int getEditMode() {
799            return mMode;
800        }
801
802        public int getSelectState() {
803            return mState;
804        }
805
806        public int getSelectionStart() {
807            return mCurStart;
808        }
809
810        public int getSelectionEnd() {
811            return mCurEnd;
812        }
813
814        private void doNextHandle() {
815            if (DBG) {
816                Log.d(LOG_TAG, "--- doNextHandle: " + mMode + "," + mState);
817            }
818            switch (mMode) {
819            case MODE_COPY:
820                handleCopy();
821                break;
822            case MODE_CUT:
823                handleCut();
824                break;
825            case MODE_PASTE:
826                handlePaste();
827                break;
828            case MODE_SIZE:
829                handleSize();
830                break;
831            case MODE_COLOR:
832                handleColor();
833                break;
834            case MODE_ALIGN:
835                handleAlign();
836                break;
837            default:
838                break;
839            }
840        }
841
842        private void handleCancel() {
843            if (DBG) {
844                Log.d(LOG_TAG, "--- handleCancel");
845            }
846            mMode = MODE_NOTHING;
847            mState = STATE_SELECT_OFF;
848            mEditFlag = false;
849            Log.d(LOG_TAG, "--- handleCancel:" + mEST.getInputType());
850            unblockSoftKey();
851            unsetSelect();
852        }
853
854        private void handleComplete() {
855            if (DBG) {
856                Log.d(LOG_TAG, "--- handleComplete");
857            }
858            if (!mEditFlag) {
859                return;
860            }
861            if (mState == STATE_SELECTED) {
862                mState = STATE_SELECT_FIX;
863            }
864            doNextHandle();
865        }
866
867        private void handleTextViewFunc(int mode, int id) {
868            if (DBG) {
869                Log.d(LOG_TAG, "--- handleTextView: " + mMode + "," + mState +
870                        "," + id);
871            }
872            if (!mEditFlag) {
873                return;
874            }
875            if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
876                mMode = mode;
877                if (mState == STATE_SELECTED) {
878                    mState = STATE_SELECT_FIX;
879                    handleTextViewFunc(mode, id);
880                } else {
881                    handleSelect();
882                }
883            } else if (mMode != mode) {
884                handleCancel();
885                mMode = mode;
886                handleTextViewFunc(mode, id);
887            } else if (mState == STATE_SELECT_FIX) {
888                mEST.onTextContextMenuItem(id);
889                handleResetEdit();
890            }
891        }
892
893        private void handleCopy() {
894            if (DBG) {
895                Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
896            }
897            handleTextViewFunc(MODE_COPY, android.R.id.copy);
898        }
899
900        private void handleCut() {
901            if (DBG) {
902                Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
903            }
904            handleTextViewFunc(MODE_CUT, android.R.id.cut);
905        }
906
907        private void handlePaste() {
908            if (DBG) {
909                Log.d(LOG_TAG, "--- handlePaste");
910            }
911            if (!mEditFlag) {
912                return;
913            }
914            mEST.onTextContextMenuItem(android.R.id.paste);
915        }
916
917        private void handleSetSpan(int mode) {
918            if (DBG) {
919                Log.d(LOG_TAG, "--- handleSetSpan:" + mEditFlag + ","
920                        + mState + ',' + mMode);
921            }
922            if (!mEditFlag) {
923                Log.e(LOG_TAG, "--- handleSetSpan: Editing is not started.");
924                return;
925            }
926            if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
927                mMode = mode;
928                if (mState == STATE_SELECTED) {
929                    mState = STATE_SELECT_FIX;
930                    handleSetSpan(mode);
931                } else {
932                    handleSelect();
933                }
934            } else if (mMode != mode) {
935                handleCancel();
936                mMode = mode;
937                handleSetSpan(mode);
938            } else {
939                if (mState == STATE_SELECT_FIX) {
940                    mEST.setHintMessage(HINT_MSG_NULL);
941                    switch (mode) {
942                    case MODE_COLOR:
943                        mEST.onShowForegroundColorAlert();
944                        break;
945                    case MODE_SIZE:
946                        mEST.onShowSizeAlert();
947                        break;
948                    case MODE_ALIGN:
949                        mEST.onShowAlignAlert();
950                        break;
951                    default:
952                        Log.e(LOG_TAG, "--- handleSetSpan: invalid mode.");
953                        break;
954                    }
955                } else {
956                    Log.d(LOG_TAG, "--- handleSetSpan: do nothing.");
957                }
958            }
959        }
960
961        private void handleSize() {
962            handleSetSpan(MODE_SIZE);
963        }
964
965        private void handleColor() {
966            handleSetSpan(MODE_COLOR);
967        }
968
969        private void handleAlign() {
970            handleSetSpan(MODE_ALIGN);
971        }
972
973        private void handleSelect() {
974            if (DBG) {
975                Log.d(LOG_TAG, "--- handleSelect:" + mEditFlag + "," + mState);
976            }
977            if (!mEditFlag) {
978                return;
979            }
980            if (mState == STATE_SELECT_OFF) {
981                if (isTextSelected()) {
982                    Log.e(LOG_TAG, "Selection is off, but selected");
983                }
984                setSelectStartPos();
985                blockSoftKey();
986                mEST.setHintMessage(HINT_MSG_SELECT_END);
987            } else if (mState == STATE_SELECT_ON) {
988                if (isTextSelected()) {
989                    Log.e(LOG_TAG, "Selection now start, but selected");
990                }
991                setSelectedEndPos();
992                mEST.setHintMessage(HINT_MSG_PUSH_COMPETE);
993                doNextHandle();
994            } else if (mState == STATE_SELECTED) {
995                if (!isTextSelected()) {
996                    Log.e(LOG_TAG, "Selection is done, but not selected");
997                }
998                setSelectedEndPos();
999                doNextHandle();
1000            }
1001        }
1002
1003        private void handleSelectAll() {
1004            if (DBG) {
1005                Log.d(LOG_TAG, "--- handleSelectAll");
1006            }
1007            if (!mEditFlag) {
1008                return;
1009            }
1010            mEST.selectAll();
1011            mState = STATE_SELECTED;
1012        }
1013
1014        private void handleResetEdit() {
1015            if (DBG) {
1016                Log.d(LOG_TAG, "Reset Editor");
1017            }
1018            blockSoftKey();
1019            handleCancel();
1020            mEditFlag = true;
1021            mEST.setHintMessage(HINT_MSG_SELECT_START);
1022        }
1023
1024        private void setSelection() {
1025            if (DBG) {
1026                Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd);
1027            }
1028            if (mCurStart >= 0 && mCurStart <= mEST.getText().length()
1029                    && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) {
1030                if (mCurStart < mCurEnd) {
1031                    mEST.setSelection(mCurStart, mCurEnd);
1032                } else {
1033                    mEST.setSelection(mCurEnd, mCurStart);
1034                }
1035                mState = STATE_SELECTED;
1036            } else {
1037                Log.e(LOG_TAG,
1038                        "Select is on, but cursor positions are illigal.:"
1039                                + mEST.getText().length() + "," + mCurStart
1040                                + "," + mCurEnd);
1041            }
1042        }
1043
1044        private void unsetSelect() {
1045            if (DBG) {
1046                Log.d(LOG_TAG, "--- offSelect");
1047            }
1048            int currpos = mEST.getSelectionStart();
1049            mEST.setSelection(currpos, currpos);
1050            mState = STATE_SELECT_OFF;
1051        }
1052
1053        private void setSelectStartPos() {
1054            if (DBG) {
1055                Log.d(LOG_TAG, "--- setSelectStartPos");
1056            }
1057            mCurStart = mEST.getSelectionStart();
1058            mState = STATE_SELECT_ON;
1059        }
1060
1061        private void setSelectedEndPos() {
1062            if (DBG) {
1063                Log.d(LOG_TAG, "--- setSelectEndPos:");
1064            }
1065            if (mEST.getSelectionStart() == mCurStart) {
1066                setSelectedEndPos(mEST.getSelectionEnd());
1067            } else {
1068                setSelectedEndPos(mEST.getSelectionStart());
1069            }
1070        }
1071
1072        public void setSelectedEndPos(int pos) {
1073            if (DBG) {
1074                Log.d(LOG_TAG, "--- setSelectedEndPos:");
1075            }
1076            mCurEnd = pos;
1077            setSelection();
1078        }
1079
1080        private boolean isTextSelected() {
1081            if (DBG) {
1082                Log.d(LOG_TAG, "--- isTextSelected:" + mCurStart + ","
1083                        + mCurEnd);
1084            }
1085            return (mCurStart != mCurEnd)
1086                    && (mState == STATE_SELECTED ||
1087                            mState == STATE_SELECT_FIX);
1088        }
1089
1090        private void setStyledTextSpan(Object span, int start, int end) {
1091            if (DBG) {
1092                Log.d(LOG_TAG, "--- setStyledTextSpan:" + mMode + ","
1093                        + start + "," + end);
1094            }
1095            if (start < end) {
1096                mEST.getText().setSpan(span, start, end,
1097                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1098            } else {
1099                mEST.getText().setSpan(span, end, start,
1100                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1101            }
1102        }
1103
1104        private void changeSizeSelectedText(int size) {
1105            if (DBG) {
1106                Log.d(LOG_TAG, "--- changeSize:" + size);
1107            }
1108            setStyledTextSpan(new AbsoluteSizeSpan(size),
1109                mCurStart, mCurEnd);
1110        }
1111
1112        private void changeColorSelectedText(int color) {
1113            if (DBG) {
1114                Log.d(LOG_TAG, "--- changeColor:" + color);
1115            }
1116            setStyledTextSpan(new ForegroundColorSpan(color),
1117                mCurStart, mCurEnd);
1118        }
1119
1120        private void changeAlign(Layout.Alignment align) {
1121            if (DBG) {
1122                Log.d(LOG_TAG, "--- changeAlign:" + align);
1123            }
1124            setStyledTextSpan(new AlignmentSpan.Standard(align),
1125                    findLineStart(mEST.getText(), mCurStart),
1126                    findLineEnd(mEST.getText(), mCurEnd));
1127        }
1128
1129        private int findLineStart(Editable text, int current) {
1130            if (DBG) {
1131                Log.d(LOG_TAG, "--- findLineStart: curr:" + current +
1132                        ", length:" + text.length());
1133            }
1134            int pos = current;
1135            for (; pos > 0; pos--) {
1136                if (text.charAt(pos - 1) == '\n') {
1137                    break;
1138                }
1139            }
1140            return pos;
1141        }
1142
1143        private void insertImageSpan(ImageSpan span) {
1144            if (DBG) {
1145                Log.d(LOG_TAG, "--- insertImageSpan");
1146            }
1147            if (span != null) {
1148                Log.d(LOG_TAG, "--- insertimagespan:" + span.getDrawable().getIntrinsicHeight() + "," + span.getDrawable().getIntrinsicWidth());
1149                Log.d(LOG_TAG, "--- insertimagespan:" + span.getDrawable().getClass());
1150                int curpos = mEST.getSelectionStart();
1151                mEST.getText().insert(curpos, "\uFFFC");
1152                mEST.getText().setSpan(span, curpos, curpos + 1,
1153                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1154                mEST.notifyStateChanged(mMode, mState);
1155            } else {
1156                Log.e(LOG_TAG, "--- insertImageSpan: null span was inserted");
1157            }
1158        }
1159
1160        private int findLineEnd(Editable text, int current) {
1161            if (DBG) {
1162                Log.d(LOG_TAG, "--- findLineEnd: curr:" + current +
1163                        ", length:" + text.length());
1164            }
1165            int pos = current;
1166            for (; pos < text.length(); pos++) {
1167                if (pos > 0 && text.charAt(pos - 1) == '\n') {
1168                    break;
1169                }
1170            }
1171            return pos;
1172        }
1173
1174        private void blockSoftKey() {
1175            if (DBG) {
1176                Log.d(LOG_TAG, "--- blockSoftKey:");
1177            }
1178            InputMethodManager imm = (InputMethodManager) mEST.getContext().
1179            getSystemService(Context.INPUT_METHOD_SERVICE);
1180            imm.hideSoftInputFromWindow(mEST.getWindowToken(), 0);
1181            mEST.setOnClickListener(
1182                    new OnClickListener() {
1183                        public void onClick(View v) {
1184                            Log.d(LOG_TAG, "--- ontrackballclick:");
1185                            onFixSelectedItem();
1186                        }
1187            });
1188            mSoftKeyBlockFlag = true;
1189        }
1190
1191        private void unblockSoftKey() {
1192            if (DBG) {
1193                Log.d(LOG_TAG, "--- unblockSoftKey:");
1194            }
1195            mEST.setOnClickListener(null);
1196            mSoftKeyBlockFlag = false;
1197        }
1198    }
1199
1200    private class StyledTextConverter {
1201        private EditStyledText mEST;
1202
1203        public StyledTextConverter(EditStyledText est) {
1204            mEST = est;
1205        }
1206
1207        public String getHtml() {
1208            String htmlBody = Html.toHtml(mEST.getText());
1209            if (DBG) {
1210                Log.d(LOG_TAG, "--- getConvertedBody:" + htmlBody);
1211            }
1212            return htmlBody;
1213        }
1214
1215        public void getUriArray(ArrayList<Uri> uris, Editable text) {
1216            uris.clear();
1217            if (DBG) {
1218                Log.d(LOG_TAG, "--- getUriArray:");
1219            }
1220            int len = text.length();
1221            int next;
1222            for (int i = 0; i < text.length(); i = next) {
1223                next = text.nextSpanTransition(i, len, ImageSpan.class);
1224                ImageSpan[] images = text.getSpans(i, next, ImageSpan.class);
1225                for (int j = 0; j < images.length; j++) {
1226                    if (DBG) {
1227                        Log.d(LOG_TAG, "--- getUriArray: foundArray" +
1228                                ((ImageSpan) images[j]).getSource());
1229                    }
1230                    uris.add(Uri.parse(
1231                            ((ImageSpan) images[j]).getSource()));
1232                }
1233            }
1234        }
1235
1236        public void SetHtml (String html) {
1237            final Spanned spanned = Html.fromHtml(html, new Html.ImageGetter() {
1238                public Drawable getDrawable(String src) {
1239                    Log.d(LOG_TAG, "--- sethtml: src="+src);
1240                    if (src.startsWith("content://")) {
1241                        Uri uri = Uri.parse(src);
1242                        try {
1243                            InputStream is = mEST.getContext().getContentResolver().openInputStream(uri);
1244                            Bitmap bitmap = BitmapFactory.decodeStream(is);
1245                            Drawable drawable = new BitmapDrawable(
1246                                    getContext().getResources(), bitmap);
1247                            drawable.setBounds(0, 0,
1248                                    drawable.getIntrinsicWidth(),
1249                                    drawable.getIntrinsicHeight());
1250                            is.close();
1251                            return drawable;
1252                        } catch (Exception e) {
1253                            Log.e(LOG_TAG, "--- set html: Failed to loaded content " + uri, e);
1254                            return null;
1255                        }
1256                    }
1257                    Log.d(LOG_TAG, "  unknown src="+src);
1258                    return null;
1259                }
1260            }, null);
1261            mEST.setText(spanned);
1262        }
1263    }
1264
1265    private class StyledTextDialog {
1266        Builder mBuilder;
1267        CharSequence mColorTitle;
1268        CharSequence mSizeTitle;
1269        CharSequence mAlignTitle;
1270        CharSequence[] mColorNames;
1271        CharSequence[] mColorInts;
1272        CharSequence[] mSizeNames;
1273        CharSequence[] mSizeDisplayInts;
1274        CharSequence[] mSizeSendInts;
1275        CharSequence[] mAlignNames;
1276        EditStyledText mEST;
1277
1278        public StyledTextDialog(EditStyledText est) {
1279            mEST = est;
1280        }
1281
1282        public void setBuilder(Builder builder) {
1283            mBuilder = builder;
1284        }
1285
1286        public void setColorAlertParams(CharSequence colortitle,
1287                CharSequence[] colornames, CharSequence[] colorints) {
1288            mColorTitle = colortitle;
1289            mColorNames = colornames;
1290            mColorInts = colorints;
1291        }
1292
1293        public void setSizeAlertParams(CharSequence sizetitle,
1294                CharSequence[] sizenames, CharSequence[] sizedisplayints,
1295                CharSequence[] sizesendints) {
1296            mSizeTitle = sizetitle;
1297            mSizeNames = sizenames;
1298            mSizeDisplayInts = sizedisplayints;
1299            mSizeSendInts = sizesendints;
1300        }
1301
1302        public void setAlignAlertParams(CharSequence aligntitle,
1303                CharSequence[] alignnames) {
1304            mAlignTitle = aligntitle;
1305            mAlignNames = alignnames;
1306        }
1307
1308        private boolean checkColorAlertParams() {
1309            if (DBG) {
1310                Log.d(LOG_TAG, "--- checkParams");
1311            }
1312            if (mBuilder == null) {
1313                Log.e(LOG_TAG, "--- builder is null.");
1314                return false;
1315            } else if (mColorTitle == null || mColorNames == null
1316                    || mColorInts == null) {
1317                Log.e(LOG_TAG, "--- color alert params are null.");
1318                return false;
1319            } else if (mColorNames.length != mColorInts.length) {
1320                Log.e(LOG_TAG, "--- the length of color alert params are "
1321                        + "different.");
1322                return false;
1323            }
1324            return true;
1325        }
1326
1327        private boolean checkSizeAlertParams() {
1328            if (DBG) {
1329                Log.d(LOG_TAG, "--- checkParams");
1330            }
1331            if (mBuilder == null) {
1332                Log.e(LOG_TAG, "--- builder is null.");
1333                return false;
1334            } else if (mSizeTitle == null || mSizeNames == null
1335                    || mSizeDisplayInts == null || mSizeSendInts == null) {
1336                Log.e(LOG_TAG, "--- size alert params are null.");
1337                return false;
1338            } else if (mSizeNames.length != mSizeDisplayInts.length
1339                    && mSizeSendInts.length != mSizeDisplayInts.length) {
1340                Log.e(LOG_TAG, "--- the length of size alert params are "
1341                        + "different.");
1342                return false;
1343            }
1344            return true;
1345        }
1346
1347        private boolean checkAlignAlertParams() {
1348            if (DBG) {
1349                Log.d(LOG_TAG, "--- checkAlignAlertParams");
1350            }
1351            if (mBuilder == null) {
1352                Log.e(LOG_TAG, "--- builder is null.");
1353                return false;
1354            } else if (mAlignTitle == null) {
1355                Log.e(LOG_TAG, "--- align alert params are null.");
1356                return false;
1357            }
1358            return true;
1359        }
1360
1361        private void onShowForegroundColorAlertDialog() {
1362            if (DBG) {
1363                Log.d(LOG_TAG, "--- onShowForegroundColorAlertDialog");
1364            }
1365            if (!checkColorAlertParams()) {
1366                return;
1367            }
1368            mBuilder.setTitle(mColorTitle);
1369            mBuilder.setIcon(0);
1370            mBuilder.
1371            setItems(mColorNames,
1372                    new DialogInterface.OnClickListener() {
1373                public void onClick(DialogInterface dialog, int which) {
1374                    Log.d("EETVM", "mBuilder.onclick:" + which);
1375                    int color = Integer.parseInt(
1376                            (String) mColorInts[which], 16) - 0x01000000;
1377                    mEST.setItemColor(color);
1378                }
1379            });
1380            mBuilder.show();
1381        }
1382
1383        private void onShowBackgroundColorAlertDialog() {
1384            if (DBG) {
1385                Log.d(LOG_TAG, "--- onShowBackgroundColorAlertDialog");
1386            }
1387            if (!checkColorAlertParams()) {
1388                return;
1389            }
1390            mBuilder.setTitle(mColorTitle);
1391            mBuilder.setIcon(0);
1392            mBuilder.
1393            setItems(mColorNames,
1394                    new DialogInterface.OnClickListener() {
1395                public void onClick(DialogInterface dialog, int which) {
1396                    Log.d("EETVM", "mBuilder.onclick:" + which);
1397                    int color = Integer.parseInt(
1398                            (String) mColorInts[which], 16) - 0x01000000;
1399                    mEST.setBackgroundColor(color);
1400                }
1401            });
1402            mBuilder.show();
1403        }
1404
1405        private void onShowSizeAlertDialog() {
1406            if (DBG) {
1407                Log.d(LOG_TAG, "--- onShowSizeAlertDialog");
1408            }
1409            if (!checkSizeAlertParams()) {
1410                return;
1411            }
1412            mBuilder.setTitle(mSizeTitle);
1413            mBuilder.setIcon(0);
1414            mBuilder.
1415            setItems(mSizeNames,
1416                    new DialogInterface.OnClickListener() {
1417                public void onClick(DialogInterface dialog, int which) {
1418                    Log.d(LOG_TAG, "mBuilder.onclick:" + which);
1419                    int size = Integer
1420                    .parseInt((String) mSizeDisplayInts[which]);
1421                    mEST.setItemSize(size);
1422                }
1423            });
1424            mBuilder.show();
1425        }
1426
1427        private void onShowAlignAlertDialog() {
1428            if (DBG) {
1429                Log.d(LOG_TAG, "--- onShowAlignAlertDialog");
1430            }
1431            if (!checkAlignAlertParams()) {
1432                return;
1433            }
1434            mBuilder.setTitle(mAlignTitle);
1435            mBuilder.setIcon(0);
1436            mBuilder.
1437            setItems(mAlignNames,
1438                    new DialogInterface.OnClickListener() {
1439                public void onClick(DialogInterface dialog, int which) {
1440                    Log.d(LOG_TAG, "mBuilder.onclick:" + which);
1441                    Layout.Alignment align = Layout.Alignment.ALIGN_NORMAL;
1442                    switch (which) {
1443                    case 0:
1444                        align = Layout.Alignment.ALIGN_NORMAL;
1445                        break;
1446                    case 1:
1447                        align = Layout.Alignment.ALIGN_CENTER;
1448                        break;
1449                    case 2:
1450                        align = Layout.Alignment.ALIGN_OPPOSITE;
1451                        break;
1452                    default:
1453                        break;
1454                    }
1455                    mEST.setAlignment(align);
1456                }
1457            });
1458            mBuilder.show();
1459        }
1460    }
1461
1462    private class StyledTextArrowKeyMethod extends ArrowKeyMovementMethod {
1463        EditorManager mManager;
1464        StyledTextArrowKeyMethod(EditorManager manager) {
1465            super();
1466            mManager = manager;
1467        }
1468
1469        @Override
1470        public boolean onKeyDown(TextView widget, Spannable buffer,
1471                int keyCode, KeyEvent event) {
1472            if (!mManager.isSoftKeyBlocked()) {
1473                return super.onKeyDown(widget, buffer, keyCode, event);
1474            }
1475            if (executeDown(widget, buffer, keyCode)) {
1476                return true;
1477            }
1478            return false;
1479        }
1480
1481        private int getEndPos(TextView widget) {
1482            int end;
1483            if (widget.getSelectionStart() == mManager.getSelectionStart()) {
1484                end = widget.getSelectionEnd();
1485            } else {
1486                end = widget.getSelectionStart();
1487            }
1488            return end;
1489        }
1490
1491        private boolean up(TextView widget, Spannable buffer) {
1492            if (DBG) {
1493                Log.d(LOG_TAG, "--- up:");
1494            }
1495            Layout layout = widget.getLayout();
1496            int end = getEndPos(widget);
1497            int line = layout.getLineForOffset(end);
1498            if (line > 0) {
1499                int to;
1500                if (layout.getParagraphDirection(line) ==
1501                    layout.getParagraphDirection(line - 1)) {
1502                    float h = layout.getPrimaryHorizontal(end);
1503                    to = layout.getOffsetForHorizontal(line - 1, h);
1504                } else {
1505                    to = layout.getLineStart(line - 1);
1506                }
1507                mManager.setSelectedEndPos(to);
1508                mManager.onCursorMoved();
1509                return true;
1510            }
1511            return false;
1512        }
1513
1514        private boolean down(TextView widget, Spannable buffer) {
1515            if (DBG) {
1516                Log.d(LOG_TAG, "--- down:");
1517            }
1518            Layout layout = widget.getLayout();
1519            int end = getEndPos(widget);
1520            int line = layout.getLineForOffset(end);
1521            if (line < layout.getLineCount() - 1) {
1522                int to;
1523                if (layout.getParagraphDirection(line) ==
1524                    layout.getParagraphDirection(line + 1)) {
1525                    float h = layout.getPrimaryHorizontal(end);
1526                    to = layout.getOffsetForHorizontal(line + 1, h);
1527                } else {
1528                    to = layout.getLineStart(line + 1);
1529                }
1530                mManager.setSelectedEndPos(to);
1531                mManager.onCursorMoved();
1532                return true;
1533            }
1534            return false;
1535        }
1536
1537        private boolean left(TextView widget, Spannable buffer) {
1538            if (DBG) {
1539                Log.d(LOG_TAG, "--- left:");
1540            }
1541            Layout layout = widget.getLayout();
1542            int to = layout.getOffsetToLeftOf(getEndPos(widget));
1543            mManager.setSelectedEndPos(to);
1544            mManager.onCursorMoved();
1545            return true;
1546        }
1547
1548        private boolean right(TextView widget, Spannable buffer) {
1549            if (DBG) {
1550                Log.d(LOG_TAG, "--- right:");
1551            }
1552            Layout layout = widget.getLayout();
1553            int to = layout.getOffsetToRightOf(getEndPos(widget));
1554            mManager.setSelectedEndPos(to);
1555            mManager.onCursorMoved();
1556            return true;
1557        }
1558
1559        private boolean executeDown(TextView widget, Spannable buffer,
1560                int keyCode) {
1561            if (DBG) {
1562                Log.d(LOG_TAG, "--- executeDown: " + keyCode);
1563            }
1564            boolean handled = false;
1565
1566            switch (keyCode) {
1567            case KeyEvent.KEYCODE_DPAD_UP:
1568                handled |= up(widget, buffer);
1569                break;
1570            case KeyEvent.KEYCODE_DPAD_DOWN:
1571                handled |= down(widget, buffer);
1572                break;
1573            case KeyEvent.KEYCODE_DPAD_LEFT:
1574                handled |= left(widget, buffer);
1575                break;
1576            case KeyEvent.KEYCODE_DPAD_RIGHT:
1577                handled |= right(widget, buffer);
1578                break;
1579                case KeyEvent.KEYCODE_DPAD_CENTER:
1580                    mManager.onFixSelectedItem();
1581                    handled = true;
1582                    break;
1583            }
1584            return handled;
1585        }
1586    }
1587
1588    public class HorizontalLineSpan extends ImageSpan {
1589        public HorizontalLineSpan(int color, View view) {
1590            super(new HorizontalLineDrawable(color, view));
1591        }
1592    }
1593    public class HorizontalLineDrawable extends ShapeDrawable {
1594        private View mView;
1595        public HorizontalLineDrawable(int color, View view) {
1596            super(new RectShape());
1597            mView = view;
1598            renewColor(color);
1599            renewBounds(view);
1600        }
1601        @Override
1602        public void draw(Canvas canvas) {
1603            if (DBG) {
1604                Log.d(LOG_TAG, "--- draw:");
1605            }
1606            renewColor();
1607            renewBounds(mView);
1608            super.draw(canvas);
1609        }
1610
1611        private void renewBounds(View view) {
1612            if (DBG) {
1613                int width = mView.getBackground().getBounds().width();
1614                int height = mView.getBackground().getBounds().height();
1615                Log.d(LOG_TAG, "--- renewBounds:" + width + "," + height);
1616                Log.d(LOG_TAG, "--- renewBounds:" + mView.getClass());
1617            }
1618            int width = mView.getWidth();
1619            if (width > 20) {
1620                width -= 20;
1621            }
1622            setBounds(0, 0, width, 2);
1623        }
1624        private void renewColor(int color) {
1625            if (DBG) {
1626                Log.d(LOG_TAG, "--- renewColor:" + color);
1627            }
1628            getPaint().setColor(color);
1629        }
1630        private void renewColor() {
1631            if (DBG) {
1632                Log.d(LOG_TAG, "--- renewColor:");
1633            }
1634            if (mView instanceof View) {
1635                ImageSpan parent = getParentSpan();
1636                Editable text = ((EditStyledText)mView).getText();
1637                int start = text.getSpanStart(parent);
1638                ForegroundColorSpan[] spans = text.getSpans(start, start, ForegroundColorSpan.class);
1639                if (spans.length > 0) {
1640                    renewColor(spans[spans.length - 1].getForegroundColor());
1641                }
1642            }
1643        }
1644        private ImageSpan getParentSpan() {
1645            if (DBG) {
1646                Log.d(LOG_TAG, "--- getParentSpan:");
1647            }
1648            if (mView instanceof EditStyledText) {
1649                Editable text = ((EditStyledText)mView).getText();
1650                ImageSpan[] images = text.getSpans(0, text.length(), ImageSpan.class);
1651                if (images.length > 0) {
1652                    for (ImageSpan image: images) {
1653                        if (image.getDrawable() == this) {
1654                            return image;
1655                        }
1656                    }
1657                }
1658            }
1659            Log.e(LOG_TAG, "---renewBounds: Couldn't find");
1660            return null;
1661        }
1662    }
1663}
1664