1/*
2 * Copyright (C) 2010 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.ex.editstyledtext;
18
19import java.io.InputStream;
20import java.util.ArrayList;
21import java.util.HashMap;
22
23import com.android.ex.editstyledtext.EditStyledText.EditModeActions.EditModeActionBase;
24import com.android.ex.editstyledtext.EditStyledText.EditStyledTextSpans.HorizontalLineSpan;
25import com.android.ex.editstyledtext.EditStyledText.EditStyledTextSpans.MarqueeSpan;
26import com.android.ex.editstyledtext.EditStyledText.EditStyledTextSpans.RescalableImageSpan;
27
28import android.R;
29import android.app.AlertDialog;
30import android.app.AlertDialog.Builder;
31import android.content.Context;
32import android.content.DialogInterface;
33import android.content.DialogInterface.OnCancelListener;
34import android.graphics.Bitmap;
35import android.graphics.BitmapFactory;
36import android.graphics.Canvas;
37import android.graphics.Color;
38import android.graphics.Rect;
39import android.graphics.drawable.BitmapDrawable;
40import android.graphics.drawable.Drawable;
41import android.graphics.drawable.ShapeDrawable;
42import android.graphics.drawable.shapes.RectShape;
43import android.net.Uri;
44import android.os.Bundle;
45import android.os.Parcel;
46import android.os.Parcelable;
47import android.os.ResultReceiver;
48import android.text.ClipboardManager;
49import android.text.Editable;
50import android.text.Html;
51import android.text.Layout;
52import android.text.NoCopySpan;
53import android.text.NoCopySpan.Concrete;
54import android.text.Selection;
55import android.text.Spannable;
56import android.text.SpannableStringBuilder;
57import android.text.Spanned;
58import android.text.TextPaint;
59import android.text.Html.ImageGetter;
60import android.text.Html.TagHandler;
61import android.text.method.ArrowKeyMovementMethod;
62import android.text.style.AbsoluteSizeSpan;
63import android.text.style.AlignmentSpan;
64import android.text.style.BackgroundColorSpan;
65import android.text.style.CharacterStyle;
66import android.text.style.DynamicDrawableSpan;
67import android.text.style.ForegroundColorSpan;
68import android.text.style.ImageSpan;
69import android.text.style.ParagraphStyle;
70import android.text.style.QuoteSpan;
71import android.text.style.UnderlineSpan;
72import android.util.AttributeSet;
73import android.util.Log;
74import android.view.ContextMenu;
75import android.view.Gravity;
76import android.view.KeyEvent;
77import android.view.MenuItem;
78import android.view.MotionEvent;
79import android.view.View;
80import android.view.inputmethod.EditorInfo;
81import android.view.inputmethod.InputConnection;
82import android.view.inputmethod.InputConnectionWrapper;
83import android.view.inputmethod.InputMethodManager;
84import android.widget.Button;
85import android.widget.EditText;
86import android.widget.LinearLayout;
87import android.widget.TextView;
88
89/**
90 * EditStyledText extends EditText for managing the flow and status to edit the styled text. This
91 * manages the states and flows of editing, supports inserting image, import/export HTML.
92 */
93public class EditStyledText extends EditText {
94
95    private static final String TAG = "EditStyledText";
96    /**
97     * DBG should be false at checking in.
98     */
99    private static final boolean DBG = true;
100
101    /**
102     * Modes of editing actions.
103     */
104    /** The mode that no editing action is done. */
105    public static final int MODE_NOTHING = 0;
106    /** The mode of copy. */
107    public static final int MODE_COPY = 1;
108    /** The mode of paste. */
109    public static final int MODE_PASTE = 2;
110    /** The mode of changing size. */
111    public static final int MODE_SIZE = 3;
112    /** The mode of changing color. */
113    public static final int MODE_COLOR = 4;
114    /** The mode of selection. */
115    public static final int MODE_SELECT = 5;
116    /** The mode of changing alignment. */
117    public static final int MODE_ALIGN = 6;
118    /** The mode of changing cut. */
119    public static final int MODE_CUT = 7;
120    public static final int MODE_TELOP = 8;
121    public static final int MODE_SWING = 9;
122    public static final int MODE_MARQUEE = 10;
123    public static final int MODE_SELECTALL = 11;
124    public static final int MODE_HORIZONTALLINE = 12;
125    public static final int MODE_STOP_SELECT = 13;
126    public static final int MODE_CLEARSTYLES = 14;
127    public static final int MODE_IMAGE = 15;
128    public static final int MODE_BGCOLOR = 16;
129    public static final int MODE_PREVIEW = 17;
130    public static final int MODE_CANCEL = 18;
131    public static final int MODE_TEXTVIEWFUNCTION = 19;
132    public static final int MODE_START_EDIT = 20;
133    public static final int MODE_END_EDIT = 21;
134    public static final int MODE_RESET = 22;
135    public static final int MODE_SHOW_MENU = 23;
136
137    /**
138     * States of selection.
139     */
140    /** The state that selection isn't started. */
141    public static final int STATE_SELECT_OFF = 0;
142    /** The state that selection is started. */
143    public static final int STATE_SELECT_ON = 1;
144    /** The state that selection is done, but not fixed. */
145    public static final int STATE_SELECTED = 2;
146    /** The state that selection is done and not fixed. */
147    public static final int STATE_SELECT_FIX = 3;
148
149    /**
150     * Help message strings.
151     */
152    public static final int HINT_MSG_NULL = 0;
153    public static final int HINT_MSG_COPY_BUF_BLANK = 1;
154    public static final int HINT_MSG_SELECT_START = 2;
155    public static final int HINT_MSG_SELECT_END = 3;
156    public static final int HINT_MSG_PUSH_COMPETE = 4;
157    public static final int HINT_MSG_BIG_SIZE_ERROR = 5;
158    public static final int HINT_MSG_END_PREVIEW = 6;
159    public static final int HINT_MSG_END_COMPOSE = 7;
160
161    /**
162     * Fixed Values.
163     */
164    public static final int DEFAULT_TRANSPARENT_COLOR = 0x00FFFFFF;
165    public static final int DEFAULT_FOREGROUND_COLOR = 0xFF000000;
166    public static final char ZEROWIDTHCHAR = '\u2060';
167    public static final char IMAGECHAR = '\uFFFC';
168    private static final int ID_SELECT_ALL = android.R.id.selectAll;
169    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
170    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
171    private static final int ID_PASTE = android.R.id.paste;
172    private static final int ID_COPY = android.R.id.copy;
173    private static final int ID_CUT = android.R.id.cut;
174    private static final int ID_HORIZONTALLINE = 0x00FFFF01;
175    private static final int ID_CLEARSTYLES = 0x00FFFF02;
176    private static final int ID_SHOWEDIT = 0x00FFFF03;
177    private static final int ID_HIDEEDIT = 0x00FFFF04;
178    private static final int MAXIMAGEWIDTHDIP = 300;
179
180    /**
181     * Strings for context menu. TODO: Extract the strings to strings.xml.
182     */
183    private static CharSequence STR_HORIZONTALLINE;
184    private static CharSequence STR_CLEARSTYLES;
185    private static CharSequence STR_PASTE;
186
187    private float mPaddingScale = 0;
188    private ArrayList<EditStyledTextNotifier> mESTNotifiers;
189    private Drawable mDefaultBackground;
190    // EditStyledTextEditorManager manages the flow and status of each function of StyledText.
191    private EditorManager mManager;
192    private InputConnection mInputConnection;
193    private StyledTextConverter mConverter;
194    private StyledTextDialog mDialog;
195
196    private static final Concrete SELECTING = new NoCopySpan.Concrete();
197    private static final int PRESSED = Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
198
199    /**
200     * EditStyledText extends EditText for managing flow of each editing action.
201     */
202    public EditStyledText(Context context, AttributeSet attrs, int defStyle) {
203        super(context, attrs, defStyle);
204        init();
205    }
206
207    public EditStyledText(Context context, AttributeSet attrs) {
208        super(context, attrs);
209        init();
210    }
211
212    public EditStyledText(Context context) {
213        super(context);
214        init();
215    }
216
217    @Override
218    public boolean onTouchEvent(MotionEvent event) {
219        boolean superResult;
220        if (event.getAction() == MotionEvent.ACTION_UP) {
221            cancelLongPress();
222            boolean editting = isEditting();
223            // If View is touched but not in Edit Mode, starts Edit Mode.
224            if (!editting) {
225                onStartEdit();
226            }
227            int oldSelStart = Selection.getSelectionStart(getText());
228            int oldSelEnd = Selection.getSelectionEnd(getText());
229            superResult = super.onTouchEvent(event);
230            if (isFocused()) {
231                // If selection is started, don't open soft key by
232                // touching.
233                if (getSelectState() == STATE_SELECT_OFF) {
234                    if (editting) {
235                        mManager.showSoftKey(Selection.getSelectionStart(getText()),
236                                Selection.getSelectionEnd(getText()));
237                    } else {
238                        mManager.showSoftKey(oldSelStart, oldSelEnd);
239                    }
240                }
241            }
242            mManager.onCursorMoved();
243            mManager.unsetTextComposingMask();
244        } else {
245            superResult = super.onTouchEvent(event);
246        }
247        sendOnTouchEvent(event);
248        return superResult;
249    }
250
251    @Override
252    public Parcelable onSaveInstanceState() {
253        Parcelable superState = super.onSaveInstanceState();
254        SavedStyledTextState ss = new SavedStyledTextState(superState);
255        ss.mBackgroundColor = mManager.getBackgroundColor();
256        return ss;
257    }
258
259    @Override
260    public void onRestoreInstanceState(Parcelable state) {
261        if (!(state instanceof SavedStyledTextState)) {
262            super.onRestoreInstanceState(state);
263            return;
264        }
265        SavedStyledTextState ss = (SavedStyledTextState) state;
266        super.onRestoreInstanceState(ss.getSuperState());
267        setBackgroundColor(ss.mBackgroundColor);
268    }
269
270    @Override
271    protected void drawableStateChanged() {
272        super.drawableStateChanged();
273        if (mManager != null) {
274            mManager.onRefreshStyles();
275        }
276    }
277
278    @Override
279    public boolean onTextContextMenuItem(int id) {
280        boolean selection = getSelectionStart() != getSelectionEnd();
281        switch (id) {
282            case ID_SELECT_ALL:
283                onStartSelectAll();
284                return true;
285            case ID_START_SELECTING_TEXT:
286                onStartSelect();
287                mManager.blockSoftKey();
288                break;
289            case ID_STOP_SELECTING_TEXT:
290                onFixSelectedItem();
291                break;
292            case ID_PASTE:
293                onStartPaste();
294                return true;
295            case ID_COPY:
296                if (selection) {
297                    onStartCopy();
298                } else {
299                    mManager.onStartSelectAll(false);
300                    onStartCopy();
301                }
302                return true;
303            case ID_CUT:
304                if (selection) {
305                    onStartCut();
306                } else {
307                    mManager.onStartSelectAll(false);
308                    onStartCut();
309                }
310                return true;
311            case ID_HORIZONTALLINE:
312                onInsertHorizontalLine();
313                return true;
314            case ID_CLEARSTYLES:
315                onClearStyles();
316                return true;
317            case ID_SHOWEDIT:
318                onStartEdit();
319                return true;
320            case ID_HIDEEDIT:
321                onEndEdit();
322                return true;
323        }
324        return super.onTextContextMenuItem(id);
325    }
326
327    @Override
328    protected void onCreateContextMenu(ContextMenu menu) {
329        super.onCreateContextMenu(menu);
330        MenuHandler handler = new MenuHandler();
331        if (STR_HORIZONTALLINE != null) {
332            menu.add(0, ID_HORIZONTALLINE, 0, STR_HORIZONTALLINE).setOnMenuItemClickListener(
333                    handler);
334        }
335        if (isStyledText() && STR_CLEARSTYLES != null) {
336            menu.add(0, ID_CLEARSTYLES, 0, STR_CLEARSTYLES)
337                    .setOnMenuItemClickListener(handler);
338        }
339        if (mManager.canPaste()) {
340            menu.add(0, ID_PASTE, 0, STR_PASTE)
341                    .setOnMenuItemClickListener(handler).setAlphabeticShortcut('v');
342        }
343    }
344
345    @Override
346    protected void onTextChanged(CharSequence text, int start, int before, int after) {
347        // onTextChanged will be called super's constructor.
348        if (mManager != null) {
349            mManager.updateSpanNextToCursor(getText(), start, before, after);
350            mManager.updateSpanPreviousFromCursor(getText(), start, before, after);
351            if (after > before) {
352                mManager.setTextComposingMask(start, start + after);
353            } else if (before < after) {
354                mManager.unsetTextComposingMask();
355            }
356            if (mManager.isWaitInput()) {
357                if (after > before) {
358                    mManager.onCursorMoved();
359                    onFixSelectedItem();
360                } else if (after < before) {
361                    mManager.onAction(MODE_RESET);
362                }
363            }
364        }
365        super.onTextChanged(text, start, before, after);
366    }
367
368    @Override
369    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
370        mInputConnection =
371                new StyledTextInputConnection(super.onCreateInputConnection(outAttrs), this);
372        return mInputConnection;
373    }
374
375    @Override
376    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
377        super.onFocusChanged(focused, direction, previouslyFocusedRect);
378        if (focused) {
379            onStartEdit();
380        } else if (!isButtonsFocused()) {
381            onEndEdit();
382        }
383    }
384
385    /**
386     * Initialize members.
387     */
388    private void init() {
389        mConverter = new StyledTextConverter(this, new StyledTextHtmlStandard());
390        mDialog = new StyledTextDialog(this);
391        mManager = new EditorManager(this, mDialog);
392        setMovementMethod(new StyledTextArrowKeyMethod(mManager));
393        mDefaultBackground = getBackground();
394        requestFocus();
395    }
396
397    public interface StyledTextHtmlConverter {
398        public String toHtml(Spanned text);
399
400        public String toHtml(Spanned text, boolean escapeNonAsciiChar);
401
402        public String toHtml(Spanned text, boolean escapeNonAsciiChar, int width, float scale);
403
404        public Spanned fromHtml(String string);
405
406        public Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler);
407    }
408
409    public void setStyledTextHtmlConverter(StyledTextHtmlConverter html) {
410        mConverter.setStyledTextHtmlConverter(html);
411    }
412
413    /**
414     * EditStyledTextInterface provides functions for notifying messages to calling class.
415     */
416    public interface EditStyledTextNotifier {
417        public void sendHintMsg(int msgId);
418
419        public void onStateChanged(int mode, int state);
420
421        public boolean sendOnTouchEvent(MotionEvent event);
422
423        public boolean isButtonsFocused();
424
425        public boolean showPreview();
426
427        public void cancelViewManager();
428
429        public boolean showInsertImageSelectAlertDialog();
430
431        public boolean showMenuAlertDialog();
432    }
433
434    /**
435     * Add Notifier.
436     */
437    public void addEditStyledTextListener(EditStyledTextNotifier estInterface) {
438        if (mESTNotifiers == null) {
439            mESTNotifiers = new ArrayList<EditStyledTextNotifier>();
440        }
441        mESTNotifiers.add(estInterface);
442    }
443
444    /**
445     * Remove Notifier.
446     */
447    public void removeEditStyledTextListener(EditStyledTextNotifier estInterface) {
448        if (mESTNotifiers != null) {
449            int i = mESTNotifiers.indexOf(estInterface);
450
451            if (i > 0) {
452                mESTNotifiers.remove(i);
453            }
454        }
455    }
456
457    private void sendOnTouchEvent(MotionEvent event) {
458        if (mESTNotifiers != null) {
459            for (EditStyledTextNotifier notifier : mESTNotifiers) {
460                notifier.sendOnTouchEvent(event);
461            }
462        }
463    }
464
465    public boolean isButtonsFocused() {
466        boolean retval = false;
467        if (mESTNotifiers != null) {
468            for (EditStyledTextNotifier notifier : mESTNotifiers) {
469                retval |= notifier.isButtonsFocused();
470            }
471        }
472        return retval;
473    }
474
475    private void showPreview() {
476        if (mESTNotifiers != null) {
477            for (EditStyledTextNotifier notifier : mESTNotifiers) {
478                if (notifier.showPreview()) {
479                    break;
480                }
481            }
482        }
483    }
484
485    private void cancelViewManagers() {
486        if (mESTNotifiers != null) {
487            for (EditStyledTextNotifier notifier : mESTNotifiers) {
488                notifier.cancelViewManager();
489            }
490        }
491    }
492
493    private void showInsertImageSelectAlertDialog() {
494        if (mESTNotifiers != null) {
495            for (EditStyledTextNotifier notifier : mESTNotifiers) {
496                if (notifier.showInsertImageSelectAlertDialog()) {
497                    break;
498                }
499            }
500        }
501    }
502
503    private void showMenuAlertDialog() {
504        if (mESTNotifiers != null) {
505            for (EditStyledTextNotifier notifier : mESTNotifiers) {
506                if (notifier.showMenuAlertDialog()) {
507                    break;
508                }
509            }
510        }
511    }
512
513    /**
514     * Notify hint messages what action is expected to calling class.
515     *
516     * @param msgId Id of the hint message.
517     */
518    private void sendHintMessage(int msgId) {
519        if (mESTNotifiers != null) {
520            for (EditStyledTextNotifier notifier : mESTNotifiers) {
521                notifier.sendHintMsg(msgId);
522            }
523        }
524    }
525
526    /**
527     * Notify the event that the mode and state are changed.
528     *
529     * @param mode Mode of the editing action.
530     * @param state Mode of the selection state.
531     */
532    private void notifyStateChanged(int mode, int state) {
533        if (mESTNotifiers != null) {
534            for (EditStyledTextNotifier notifier : mESTNotifiers) {
535                notifier.onStateChanged(mode, state);
536            }
537        }
538    }
539
540    /** Start to edit styled text */
541    public void onStartEdit() {
542        mManager.onAction(MODE_START_EDIT);
543    }
544
545    /** End of editing styled text */
546    public void onEndEdit() {
547        mManager.onAction(MODE_END_EDIT);
548    }
549
550    public void onResetEdit() {
551        mManager.onAction(MODE_RESET);
552    }
553
554    /** Start to copy styled text */
555    public void onStartCopy() {
556        mManager.onAction(MODE_COPY);
557    }
558
559    /** Start to cut styled text */
560    public void onStartCut() {
561        mManager.onAction(MODE_CUT);
562    }
563
564    /** Start to paste styled text */
565    public void onStartPaste() {
566        mManager.onAction(MODE_PASTE);
567    }
568
569    /** Start to change size */
570    public void onStartSize() {
571        mManager.onAction(MODE_SIZE);
572    }
573
574    /** Start to change color */
575    public void onStartColor() {
576        mManager.onAction(MODE_COLOR);
577    }
578
579    /** Start to change background color */
580    public void onStartBackgroundColor() {
581        mManager.onAction(MODE_BGCOLOR);
582    }
583
584    /** Start to change Alignment */
585    public void onStartAlign() {
586        mManager.onAction(MODE_ALIGN);
587    }
588
589    public void onStartTelop() {
590        mManager.onAction(MODE_TELOP);
591    }
592
593    public void onStartSwing() {
594        mManager.onAction(MODE_SWING);
595    }
596
597    public void onStartMarquee() {
598        mManager.onAction(MODE_MARQUEE);
599    }
600
601    /** Start to select a text */
602    public void onStartSelect() {
603        mManager.onStartSelect(true);
604    }
605
606    /** Start to select all characters */
607    public void onStartSelectAll() {
608        mManager.onStartSelectAll(true);
609    }
610
611    public void onStartShowPreview() {
612        mManager.onAction(MODE_PREVIEW);
613    }
614
615    public void onStartShowMenuAlertDialog() {
616        mManager.onStartShowMenuAlertDialog();
617    }
618
619    public void onStartAction(int mode, boolean notifyStateChanged) {
620        mManager.onAction(mode, notifyStateChanged);
621    }
622
623    /** Fix selection */
624    public void onFixSelectedItem() {
625        mManager.onFixSelectedItem();
626    }
627
628    public void onInsertImage() {
629        mManager.onAction(MODE_IMAGE);
630    }
631
632    /**
633     * InsertImage to TextView by using URI
634     *
635     * @param uri URI of the iamge inserted to TextView.
636     */
637    public void onInsertImage(Uri uri) {
638        mManager.onInsertImage(uri);
639    }
640
641    /**
642     * InsertImage to TextView by using resource ID
643     *
644     * @param resId Resource ID of the iamge inserted to TextView.
645     */
646    public void onInsertImage(int resId) {
647        mManager.onInsertImage(resId);
648    }
649
650    public void onInsertHorizontalLine() {
651        mManager.onAction(MODE_HORIZONTALLINE);
652    }
653
654    public void onClearStyles() {
655        mManager.onClearStyles();
656    }
657
658    public void onBlockSoftKey() {
659        mManager.blockSoftKey();
660    }
661
662    public void onUnblockSoftKey() {
663        mManager.unblockSoftKey();
664    }
665
666    public void onCancelViewManagers() {
667        mManager.onCancelViewManagers();
668    }
669
670    private void onRefreshStyles() {
671        mManager.onRefreshStyles();
672    }
673
674    private void onRefreshZeoWidthChar() {
675        mManager.onRefreshZeoWidthChar();
676    }
677
678    /**
679     * Set Size of the Item.
680     *
681     * @param size The size of the Item.
682     */
683    public void setItemSize(int size) {
684        mManager.setItemSize(size, true);
685    }
686
687    /**
688     * Set Color of the Item.
689     *
690     * @param color The color of the Item.
691     */
692    public void setItemColor(int color) {
693        mManager.setItemColor(color, true);
694    }
695
696    /**
697     * Set Alignment of the Item.
698     *
699     * @param align The color of the Item.
700     */
701    public void setAlignment(Layout.Alignment align) {
702        mManager.setAlignment(align);
703    }
704
705    /**
706     * Set Background color of View.
707     *
708     * @param color The background color of view.
709     */
710    @Override
711    public void setBackgroundColor(int color) {
712        if (color != DEFAULT_TRANSPARENT_COLOR) {
713            super.setBackgroundColor(color);
714        } else {
715            setBackgroundDrawable(mDefaultBackground);
716        }
717        mManager.setBackgroundColor(color);
718        onRefreshStyles();
719    }
720
721    public void setMarquee(int marquee) {
722        mManager.setMarquee(marquee);
723    }
724
725    /**
726     * Set html to EditStyledText.
727     *
728     * @param html The html to be set.
729     */
730    public void setHtml(String html) {
731        mConverter.SetHtml(html);
732    }
733
734    /**
735     * Set Builder for AlertDialog.
736     *
737     * @param builder Builder for opening Alert Dialog.
738     */
739    public void setBuilder(Builder builder) {
740        mDialog.setBuilder(builder);
741    }
742
743    /**
744     * Set Parameters for ColorAlertDialog.
745     *
746     * @param colortitle Title for Alert Dialog.
747     * @param colornames List of name of selecting color.
748     * @param colorints List of int of color.
749     */
750    public void setColorAlertParams(CharSequence colortitle, CharSequence[] colornames,
751            CharSequence[] colorints, CharSequence transparent) {
752        mDialog.setColorAlertParams(colortitle, colornames, colorints, transparent);
753    }
754
755    /**
756     * Set Parameters for SizeAlertDialog.
757     *
758     * @param sizetitle Title for Alert Dialog.
759     * @param sizenames List of name of selecting size.
760     * @param sizedisplayints List of int of size displayed in TextView.
761     * @param sizesendints List of int of size exported to HTML.
762     */
763    public void setSizeAlertParams(CharSequence sizetitle, CharSequence[] sizenames,
764            CharSequence[] sizedisplayints, CharSequence[] sizesendints) {
765        mDialog.setSizeAlertParams(sizetitle, sizenames, sizedisplayints, sizesendints);
766    }
767
768    public void setAlignAlertParams(CharSequence aligntitle, CharSequence[] alignnames) {
769        mDialog.setAlignAlertParams(aligntitle, alignnames);
770    }
771
772    public void setMarqueeAlertParams(CharSequence marqueetitle, CharSequence[] marqueenames) {
773        mDialog.setMarqueeAlertParams(marqueetitle, marqueenames);
774    }
775
776    public void setContextMenuStrings(CharSequence horizontalline, CharSequence clearstyles,
777            CharSequence paste) {
778        STR_HORIZONTALLINE = horizontalline;
779        STR_CLEARSTYLES = clearstyles;
780        STR_PASTE = paste;
781    }
782
783    /**
784     * Check whether editing is started or not.
785     *
786     * @return Whether editing is started or not.
787     */
788    public boolean isEditting() {
789        return mManager.isEditting();
790    }
791
792    /**
793     * Check whether styled text or not.
794     *
795     * @return Whether styled text or not.
796     */
797    public boolean isStyledText() {
798        return mManager.isStyledText();
799    }
800
801    /**
802     * Check whether SoftKey is Blocked or not.
803     *
804     * @return whether SoftKey is Blocked or not.
805     */
806    public boolean isSoftKeyBlocked() {
807        return mManager.isSoftKeyBlocked();
808    }
809
810    /**
811     * Get the mode of the action.
812     *
813     * @return The mode of the action.
814     */
815    public int getEditMode() {
816        return mManager.getEditMode();
817    }
818
819    /**
820     * Get the state of the selection.
821     *
822     * @return The state of the selection.
823     */
824    public int getSelectState() {
825        return mManager.getSelectState();
826    }
827
828    /**
829     * Get the state of the selection.
830     *
831     * @return The state of the selection.
832     */
833    public String getHtml() {
834        return mConverter.getHtml(true);
835    }
836
837    public String getHtml(boolean escapeFlag) {
838        return mConverter.getHtml(escapeFlag);
839    }
840
841    /**
842     * Get the state of the selection.
843     *
844     * @param uris The array of used uris.
845     * @return The state of the selection.
846     */
847    public String getHtml(ArrayList<Uri> uris, boolean escapeFlag) {
848        mConverter.getUriArray(uris, getText());
849        return mConverter.getHtml(escapeFlag);
850    }
851
852    public String getPreviewHtml() {
853        return mConverter.getPreviewHtml();
854    }
855
856    /**
857     * Get Background color of View.
858     *
859     * @return The background color of View.
860     */
861    public int getBackgroundColor() {
862        return mManager.getBackgroundColor();
863    }
864
865    public EditorManager getEditStyledTextManager() {
866        return mManager;
867    }
868
869    /**
870     * Get Foreground color of View.
871     *
872     * @return The background color of View.
873     */
874    public int getForegroundColor(int pos) {
875        if (pos < 0 || pos > getText().length()) {
876            return DEFAULT_FOREGROUND_COLOR;
877        } else {
878            ForegroundColorSpan[] spans =
879                    getText().getSpans(pos, pos, ForegroundColorSpan.class);
880            if (spans.length > 0) {
881                return spans[0].getForegroundColor();
882            } else {
883                return DEFAULT_FOREGROUND_COLOR;
884            }
885        }
886    }
887
888    private void finishComposingText() {
889        if (mInputConnection != null && !mManager.mTextIsFinishedFlag) {
890            mInputConnection.finishComposingText();
891            mManager.mTextIsFinishedFlag = true;
892        }
893    }
894
895    private float getPaddingScale() {
896        if (mPaddingScale <= 0) {
897            mPaddingScale = getContext().getResources().getDisplayMetrics().density;
898        }
899        return mPaddingScale;
900    }
901
902    /** Convert pixcel to DIP */
903    private int dipToPx(int dip) {
904        if (mPaddingScale <= 0) {
905            mPaddingScale = getContext().getResources().getDisplayMetrics().density;
906        }
907        return (int) ((float) dip * getPaddingScale() + 0.5);
908    }
909
910    private int getMaxImageWidthDip() {
911        return MAXIMAGEWIDTHDIP;
912    }
913
914    private int getMaxImageWidthPx() {
915        return dipToPx(MAXIMAGEWIDTHDIP);
916    }
917
918    public void addAction(int mode, EditModeActionBase action) {
919        mManager.addAction(mode, action);
920    }
921
922    public void addInputExtra(boolean create, String extra) {
923        Bundle bundle = super.getInputExtras(create);
924        if (bundle != null) {
925            bundle.putBoolean(extra, true);
926        }
927    }
928
929    private static void startSelecting(View view, Spannable content) {
930        content.setSpan(SELECTING, 0, 0, PRESSED);
931    }
932
933    private static void stopSelecting(View view, Spannable content) {
934        content.removeSpan(SELECTING);
935    }
936
937    /**
938     * EditorManager manages the flow and status of editing actions.
939     */
940    private class EditorManager {
941
942        static final private String LOG_TAG = "EditStyledText.EditorManager";
943
944        private boolean mEditFlag = false;
945        private boolean mSoftKeyBlockFlag = false;
946        private boolean mKeepNonLineSpan = false;
947        private boolean mWaitInputFlag = false;
948        private boolean mTextIsFinishedFlag = false;
949        private int mMode = MODE_NOTHING;
950        private int mState = STATE_SELECT_OFF;
951        private int mCurStart = 0;
952        private int mCurEnd = 0;
953        private int mColorWaitInput = DEFAULT_TRANSPARENT_COLOR;
954        private int mSizeWaitInput = 0;
955        private int mBackgroundColor = DEFAULT_TRANSPARENT_COLOR;
956
957        private BackgroundColorSpan mComposingTextMask;
958        private EditStyledText mEST;
959        private EditModeActions mActions;
960        private SoftKeyReceiver mSkr;
961        private SpannableStringBuilder mCopyBuffer;
962
963        EditorManager(EditStyledText est, StyledTextDialog dialog) {
964            mEST = est;
965            mActions = new EditModeActions(mEST, this, dialog);
966            mSkr = new SoftKeyReceiver(mEST);
967        }
968
969        public void addAction(int mode, EditModeActionBase action) {
970            mActions.addAction(mode, action);
971        }
972
973        public void onAction(int mode) {
974            onAction(mode, true);
975        }
976
977        public void onAction(int mode, boolean notifyStateChanged) {
978            mActions.onAction(mode);
979            if (notifyStateChanged) {
980                mEST.notifyStateChanged(mMode, mState);
981            }
982        }
983
984        private void startEdit() {
985            resetEdit();
986            showSoftKey();
987        }
988
989        public void onStartSelect(boolean notifyStateChanged) {
990            if (DBG) {
991                Log.d(LOG_TAG, "--- onClickSelect");
992            }
993            mMode = MODE_SELECT;
994            if (mState == STATE_SELECT_OFF) {
995                mActions.onSelectAction();
996            } else {
997                unsetSelect();
998                mActions.onSelectAction();
999            }
1000            if (notifyStateChanged) {
1001                mEST.notifyStateChanged(mMode, mState);
1002            }
1003        }
1004
1005        public void onCursorMoved() {
1006            if (DBG) {
1007                Log.d(LOG_TAG, "--- onClickView");
1008            }
1009            if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) {
1010                mActions.onSelectAction();
1011                mEST.notifyStateChanged(mMode, mState);
1012            }
1013        }
1014
1015        public void onStartSelectAll(boolean notifyStateChanged) {
1016            if (DBG) {
1017                Log.d(LOG_TAG, "--- onClickSelectAll");
1018            }
1019            handleSelectAll();
1020            if (notifyStateChanged) {
1021                mEST.notifyStateChanged(mMode, mState);
1022            }
1023        }
1024
1025        public void onStartShowMenuAlertDialog() {
1026            mActions.onAction(MODE_SHOW_MENU);
1027            // don't call notify state changed because it have to continue
1028            // to the next action.
1029            // mEST.notifyStateChanged(mMode, mState);
1030        }
1031
1032        public void onFixSelectedItem() {
1033            if (DBG) {
1034                Log.d(LOG_TAG, "--- onFixSelectedItem");
1035            }
1036            fixSelectionAndDoNextAction();
1037            mEST.notifyStateChanged(mMode, mState);
1038        }
1039
1040        public void onInsertImage(Uri uri) {
1041            mActions.onAction(MODE_IMAGE, uri);
1042            mEST.notifyStateChanged(mMode, mState);
1043        }
1044
1045        public void onInsertImage(int resId) {
1046            mActions.onAction(MODE_IMAGE, resId);
1047            mEST.notifyStateChanged(mMode, mState);
1048        }
1049
1050        private void insertImageFromUri(Uri uri) {
1051            insertImageSpan(new EditStyledTextSpans.RescalableImageSpan(mEST.getContext(),
1052                    uri, mEST.getMaxImageWidthPx()), mEST.getSelectionStart());
1053        }
1054
1055        private void insertImageFromResId(int resId) {
1056            insertImageSpan(new EditStyledTextSpans.RescalableImageSpan(mEST.getContext(),
1057                    resId, mEST.getMaxImageWidthDip()), mEST.getSelectionStart());
1058        }
1059
1060        private void insertHorizontalLine() {
1061            if (DBG) {
1062                Log.d(LOG_TAG, "--- onInsertHorizontalLine:");
1063            }
1064            int curpos = mEST.getSelectionStart();
1065            if (curpos > 0 && mEST.getText().charAt(curpos - 1) != '\n') {
1066                mEST.getText().insert(curpos++, "\n");
1067            }
1068            insertImageSpan(
1069                    new HorizontalLineSpan(0xFF000000, mEST.getWidth(), mEST.getText()),
1070                    curpos++);
1071            mEST.getText().insert(curpos++, "\n");
1072            mEST.setSelection(curpos);
1073            mEST.notifyStateChanged(mMode, mState);
1074        }
1075
1076        private void clearStyles(CharSequence txt) {
1077            if (DBG) {
1078                Log.d("EditStyledText", "--- onClearStyles");
1079            }
1080            int len = txt.length();
1081            if (txt instanceof Editable) {
1082                Editable editable = (Editable) txt;
1083                Object[] styles = editable.getSpans(0, len, Object.class);
1084                for (Object style : styles) {
1085                    if (style instanceof ParagraphStyle || style instanceof QuoteSpan
1086                            || style instanceof CharacterStyle
1087                            && !(style instanceof UnderlineSpan)) {
1088                        if (style instanceof ImageSpan || style instanceof HorizontalLineSpan) {
1089                            int start = editable.getSpanStart(style);
1090                            int end = editable.getSpanEnd(style);
1091                            editable.replace(start, end, "");
1092                        }
1093                        editable.removeSpan(style);
1094                    }
1095                }
1096            }
1097        }
1098
1099        public void onClearStyles() {
1100            mActions.onAction(MODE_CLEARSTYLES);
1101        }
1102
1103        public void onCancelViewManagers() {
1104            mActions.onAction(MODE_CANCEL);
1105        }
1106
1107        private void clearStyles() {
1108            if (DBG) {
1109                Log.d(LOG_TAG, "--- onClearStyles");
1110            }
1111            clearStyles(mEST.getText());
1112            mEST.setBackgroundDrawable(mEST.mDefaultBackground);
1113            mBackgroundColor = DEFAULT_TRANSPARENT_COLOR;
1114            onRefreshZeoWidthChar();
1115        }
1116
1117        public void onRefreshZeoWidthChar() {
1118            Editable txt = mEST.getText();
1119            for (int i = 0; i < txt.length(); i++) {
1120                if (txt.charAt(i) == ZEROWIDTHCHAR) {
1121                    txt.replace(i, i + 1, "");
1122                    i--;
1123                }
1124            }
1125        }
1126
1127        public void onRefreshStyles() {
1128            if (DBG) {
1129                Log.d(LOG_TAG, "--- onRefreshStyles");
1130            }
1131            Editable txt = mEST.getText();
1132            int len = txt.length();
1133            int width = mEST.getWidth();
1134            HorizontalLineSpan[] lines = txt.getSpans(0, len, HorizontalLineSpan.class);
1135            for (HorizontalLineSpan line : lines) {
1136                line.resetWidth(width);
1137            }
1138            MarqueeSpan[] marquees = txt.getSpans(0, len, MarqueeSpan.class);
1139            for (MarqueeSpan marquee : marquees) {
1140                marquee.resetColor(mEST.getBackgroundColor());
1141            }
1142
1143            if (lines.length > 0) {
1144                // This is hack, bad needed for renewing View
1145                // by inserting new line.
1146                txt.replace(0, 1, "" + txt.charAt(0));
1147            }
1148        }
1149
1150        public void setBackgroundColor(int color) {
1151            mBackgroundColor = color;
1152        }
1153
1154        public void setItemSize(int size, boolean reset) {
1155            if (DBG) {
1156                Log.d(LOG_TAG, "--- setItemSize");
1157            }
1158            if (isWaitingNextAction()) {
1159                mSizeWaitInput = size;
1160            } else if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
1161                if (size > 0) {
1162                    changeSizeSelectedText(size);
1163                }
1164                if (reset) {
1165                    resetEdit();
1166                }
1167            }
1168        }
1169
1170        public void setItemColor(int color, boolean reset) {
1171            if (DBG) {
1172                Log.d(LOG_TAG, "--- setItemColor");
1173            }
1174            if (isWaitingNextAction()) {
1175                mColorWaitInput = color;
1176            } else if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
1177                if (color != DEFAULT_TRANSPARENT_COLOR) {
1178                    changeColorSelectedText(color);
1179                }
1180                if (reset) {
1181                    resetEdit();
1182                }
1183            }
1184        }
1185
1186        public void setAlignment(Layout.Alignment align) {
1187            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
1188                changeAlign(align);
1189                resetEdit();
1190            }
1191        }
1192
1193        public void setTelop() {
1194            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
1195                addTelop();
1196                resetEdit();
1197            }
1198        }
1199
1200        public void setSwing() {
1201            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
1202                addSwing();
1203                resetEdit();
1204            }
1205        }
1206
1207        public void setMarquee(int marquee) {
1208            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
1209                addMarquee(marquee);
1210                resetEdit();
1211            }
1212        }
1213
1214        public void setTextComposingMask(int start, int end) {
1215            if (DBG) {
1216                Log.d(TAG, "--- setTextComposingMask:" + start + "," + end);
1217            }
1218            int min = Math.min(start, end);
1219            int max = Math.max(start, end);
1220            int foregroundColor;
1221            if (isWaitInput() && mColorWaitInput != DEFAULT_TRANSPARENT_COLOR) {
1222                foregroundColor = mColorWaitInput;
1223            } else {
1224                foregroundColor = mEST.getForegroundColor(min);
1225            }
1226            int backgroundColor = mEST.getBackgroundColor();
1227            if (DBG) {
1228                Log.d(TAG,
1229                        "--- fg:" + Integer.toHexString(foregroundColor) + ",bg:"
1230                                + Integer.toHexString(backgroundColor) + "," + isWaitInput()
1231                                + "," + "," + mMode);
1232            }
1233            if (foregroundColor == backgroundColor) {
1234                int maskColor = 0x80000000 | ~(backgroundColor | 0xFF000000);
1235                if (mComposingTextMask == null
1236                        || mComposingTextMask.getBackgroundColor() != maskColor) {
1237                    mComposingTextMask = new BackgroundColorSpan(maskColor);
1238                }
1239                mEST.getText().setSpan(mComposingTextMask, min, max,
1240                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1241            }
1242        }
1243
1244        private void setEditMode(int mode) {
1245            mMode = mode;
1246        }
1247
1248        private void setSelectState(int state) {
1249            mState = state;
1250        }
1251
1252        public void unsetTextComposingMask() {
1253            if (DBG) {
1254                Log.d(TAG, "--- unsetTextComposingMask");
1255            }
1256            if (mComposingTextMask != null) {
1257                mEST.getText().removeSpan(mComposingTextMask);
1258                mComposingTextMask = null;
1259            }
1260        }
1261
1262        public boolean isEditting() {
1263            return mEditFlag;
1264        }
1265
1266        /* If the style of the span is added, add check case for that style */
1267        public boolean isStyledText() {
1268            Editable txt = mEST.getText();
1269            int len = txt.length();
1270            if (txt.getSpans(0, len, ParagraphStyle.class).length > 0
1271                    || txt.getSpans(0, len, QuoteSpan.class).length > 0
1272                    || txt.getSpans(0, len, CharacterStyle.class).length > 0
1273                    || mBackgroundColor != DEFAULT_TRANSPARENT_COLOR) {
1274                return true;
1275            }
1276            return false;
1277        }
1278
1279        public boolean isSoftKeyBlocked() {
1280            return mSoftKeyBlockFlag;
1281        }
1282
1283        public boolean isWaitInput() {
1284            return mWaitInputFlag;
1285        }
1286
1287        public int getBackgroundColor() {
1288            return mBackgroundColor;
1289        }
1290
1291        public int getEditMode() {
1292            return mMode;
1293        }
1294
1295        public int getSelectState() {
1296            return mState;
1297        }
1298
1299        public int getSelectionStart() {
1300            return mCurStart;
1301        }
1302
1303        public int getSelectionEnd() {
1304            return mCurEnd;
1305        }
1306
1307        public int getSizeWaitInput() {
1308            return mSizeWaitInput;
1309        }
1310
1311        public int getColorWaitInput() {
1312            return mColorWaitInput;
1313        }
1314
1315        private void setInternalSelection(int curStart, int curEnd) {
1316            mCurStart = curStart;
1317            mCurEnd = curEnd;
1318        }
1319
1320        public void
1321                updateSpanPreviousFromCursor(Editable txt, int start, int before, int after) {
1322            if (DBG) {
1323                Log.d(LOG_TAG, "updateSpanPrevious:" + start + "," + before + "," + after);
1324            }
1325            int end = start + after;
1326            int min = Math.min(start, end);
1327            int max = Math.max(start, end);
1328            Object[] spansBefore = txt.getSpans(min, min, Object.class);
1329            for (Object span : spansBefore) {
1330                if (span instanceof ForegroundColorSpan || span instanceof AbsoluteSizeSpan
1331                        || span instanceof MarqueeSpan || span instanceof AlignmentSpan) {
1332                    int spanstart = txt.getSpanStart(span);
1333                    int spanend = txt.getSpanEnd(span);
1334                    if (DBG) {
1335                        Log.d(LOG_TAG, "spantype:" + span.getClass() + "," + spanstart);
1336                    }
1337                    int tempmax = max;
1338                    if (span instanceof MarqueeSpan || span instanceof AlignmentSpan) {
1339                        // Line Span
1340                        tempmax = findLineEnd(mEST.getText(), max);
1341                    } else {
1342                        if (mKeepNonLineSpan) {
1343                            tempmax = spanend;
1344                        }
1345                    }
1346                    if (spanend < tempmax) {
1347                        if (DBG) {
1348                            Log.d(LOG_TAG, "updateSpanPrevious: extend span");
1349                        }
1350                        txt.setSpan(span, spanstart, tempmax,
1351                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1352                    }
1353                } else if (span instanceof HorizontalLineSpan) {
1354                    int spanstart = txt.getSpanStart(span);
1355                    int spanend = txt.getSpanEnd(span);
1356                    if (before > after) {
1357                        // When text is deleted just after horizontalLineSpan, horizontalLineSpan
1358                        // has to be deleted, because the charactor just after horizontalLineSpan
1359                        // is '\n'.
1360                        txt.replace(spanstart, spanend, "");
1361                        txt.removeSpan(span);
1362                    } else {
1363                        // When text is added just after horizontalLineSpan add '\n' just after
1364                        // horizontalLineSpan.
1365                        if (spanend == end && end < txt.length()
1366                                && mEST.getText().charAt(end) != '\n') {
1367                            mEST.getText().insert(end, "\n");
1368                        }
1369                    }
1370                }
1371            }
1372        }
1373
1374        public void updateSpanNextToCursor(Editable txt, int start, int before, int after) {
1375            if (DBG) {
1376                Log.d(LOG_TAG, "updateSpanNext:" + start + "," + before + "," + after);
1377            }
1378            int end = start + after;
1379            int min = Math.min(start, end);
1380            int max = Math.max(start, end);
1381            Object[] spansAfter = txt.getSpans(max, max, Object.class);
1382            for (Object span : spansAfter) {
1383                if (span instanceof MarqueeSpan || span instanceof AlignmentSpan) {
1384                    int spanstart = txt.getSpanStart(span);
1385                    int spanend = txt.getSpanEnd(span);
1386                    if (DBG) {
1387                        Log.d(LOG_TAG, "spantype:" + span.getClass() + "," + spanend);
1388                    }
1389                    int tempmin = min;
1390                    if (span instanceof MarqueeSpan || span instanceof AlignmentSpan) {
1391                        tempmin = findLineStart(mEST.getText(), min);
1392                    }
1393                    if (tempmin < spanstart && before > after) {
1394                        txt.removeSpan(span);
1395                    } else if (spanstart > min) {
1396                        txt.setSpan(span, min, spanend, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1397                    }
1398                } else if (span instanceof HorizontalLineSpan) {
1399                    int spanstart = txt.getSpanStart(span);
1400                    // Whene text is changed just before horizontalLineSpan and there is no '\n'
1401                    // just before horizontalLineSpan add '\n'
1402                    if (spanstart == end && end > 0 && mEST.getText().charAt(end - 1) != '\n') {
1403                        mEST.getText().insert(end, "\n");
1404                        mEST.setSelection(end);
1405                    }
1406                }
1407            }
1408        }
1409
1410        /** canPaste returns true only if ClipboardManager doen't contain text. */
1411        public boolean canPaste() {
1412            return (mCopyBuffer != null && mCopyBuffer.length() > 0 && removeImageChar(
1413                    mCopyBuffer).length() == 0);
1414        }
1415
1416        private void endEdit() {
1417            if (DBG) {
1418                Log.d(LOG_TAG, "--- handleCancel");
1419            }
1420            mMode = MODE_NOTHING;
1421            mState = STATE_SELECT_OFF;
1422            mEditFlag = false;
1423            mColorWaitInput = DEFAULT_TRANSPARENT_COLOR;
1424            mSizeWaitInput = 0;
1425            mWaitInputFlag = false;
1426            mSoftKeyBlockFlag = false;
1427            mKeepNonLineSpan = false;
1428            mTextIsFinishedFlag = false;
1429            unsetSelect();
1430            mEST.setOnClickListener(null);
1431            unblockSoftKey();
1432        }
1433
1434        private void fixSelectionAndDoNextAction() {
1435            if (DBG) {
1436                Log.d(LOG_TAG, "--- handleComplete:" + mCurStart + "," + mCurEnd);
1437            }
1438            if (!mEditFlag) {
1439                return;
1440            }
1441            if (mCurStart == mCurEnd) {
1442                if (DBG) {
1443                    Log.d(LOG_TAG, "--- cancel handle complete:" + mCurStart);
1444                }
1445                resetEdit();
1446                return;
1447            }
1448            if (mState == STATE_SELECTED) {
1449                mState = STATE_SELECT_FIX;
1450            }
1451            // When the complete button is clicked, this should do action.
1452            mActions.doNext(mMode);
1453            //MetaKeyKeyListener.stopSelecting(mEST, mEST.getText());
1454            stopSelecting(mEST, mEST.getText());
1455        }
1456
1457        // Remove obj character for DynamicDrawableSpan from clipboard.
1458        private SpannableStringBuilder removeImageChar(SpannableStringBuilder text) {
1459            SpannableStringBuilder buf = new SpannableStringBuilder(text);
1460            DynamicDrawableSpan[] styles =
1461                    buf.getSpans(0, buf.length(), DynamicDrawableSpan.class);
1462            for (DynamicDrawableSpan style : styles) {
1463                if (style instanceof HorizontalLineSpan
1464                        || style instanceof RescalableImageSpan) {
1465                    int start = buf.getSpanStart(style);
1466                    int end = buf.getSpanEnd(style);
1467                    buf.replace(start, end, "");
1468                }
1469            }
1470            return buf;
1471        }
1472
1473        private void copyToClipBoard() {
1474            int min = Math.min(getSelectionStart(), getSelectionEnd());
1475            int max = Math.max(getSelectionStart(), getSelectionEnd());
1476            mCopyBuffer = (SpannableStringBuilder) mEST.getText().subSequence(min, max);
1477            SpannableStringBuilder clipboardtxt = removeImageChar(mCopyBuffer);
1478            ClipboardManager clip =
1479                    (ClipboardManager) getContext()
1480                            .getSystemService(Context.CLIPBOARD_SERVICE);
1481            clip.setText(clipboardtxt);
1482            if (DBG) {
1483                dumpSpannableString(clipboardtxt);
1484                dumpSpannableString(mCopyBuffer);
1485            }
1486        }
1487
1488        private void cutToClipBoard() {
1489            copyToClipBoard();
1490            int min = Math.min(getSelectionStart(), getSelectionEnd());
1491            int max = Math.max(getSelectionStart(), getSelectionEnd());
1492            mEST.getText().delete(min, max);
1493        }
1494
1495        private boolean isClipBoardChanged(CharSequence clipboardText) {
1496            if (DBG) {
1497                Log.d(TAG, "--- isClipBoardChanged:" + clipboardText);
1498            }
1499            if (mCopyBuffer == null) {
1500                return true;
1501            }
1502            int len = clipboardText.length();
1503            CharSequence removedClipBoard = removeImageChar(mCopyBuffer);
1504            if (DBG) {
1505                Log.d(TAG, "--- clipBoard:" + len + "," + removedClipBoard + clipboardText);
1506            }
1507            if (len != removedClipBoard.length()) {
1508                return true;
1509            }
1510            for (int i = 0; i < len; ++i) {
1511                if (clipboardText.charAt(i) != removedClipBoard.charAt(i)) {
1512                    return true;
1513                }
1514            }
1515            return false;
1516        }
1517
1518        private void pasteFromClipboard() {
1519            int min = Math.min(mEST.getSelectionStart(), mEST.getSelectionEnd());
1520            int max = Math.max(mEST.getSelectionStart(), mEST.getSelectionEnd());
1521            // TODO: Find more smart way to set Span to Clipboard.
1522            Selection.setSelection(mEST.getText(), max);
1523            ClipboardManager clip =
1524                    (ClipboardManager) getContext()
1525                            .getSystemService(Context.CLIPBOARD_SERVICE);
1526            mKeepNonLineSpan = true;
1527            mEST.getText().replace(min, max, clip.getText());
1528            if (!isClipBoardChanged(clip.getText())) {
1529                if (DBG) {
1530                    Log.d(TAG, "--- handlePaste: startPasteImage");
1531                }
1532                DynamicDrawableSpan[] styles =
1533                        mCopyBuffer.getSpans(0, mCopyBuffer.length(),
1534                                DynamicDrawableSpan.class);
1535                for (DynamicDrawableSpan style : styles) {
1536                    int start = mCopyBuffer.getSpanStart(style);
1537                    if (style instanceof HorizontalLineSpan) {
1538                        insertImageSpan(new HorizontalLineSpan(0xFF000000, mEST.getWidth(),
1539                                mEST.getText()), min + start);
1540                    } else if (style instanceof RescalableImageSpan) {
1541                        insertImageSpan(
1542                                new RescalableImageSpan(mEST.getContext(),
1543                                        ((RescalableImageSpan) style).getContentUri(),
1544                                        mEST.getMaxImageWidthPx()), min + start);
1545                    }
1546                }
1547            }
1548        }
1549
1550        private void handleSelectAll() {
1551            if (!mEditFlag) {
1552                return;
1553            }
1554            mActions.onAction(MODE_SELECTALL);
1555        }
1556
1557        private void selectAll() {
1558            Selection.selectAll(mEST.getText());
1559            mCurStart = mEST.getSelectionStart();
1560            mCurEnd = mEST.getSelectionEnd();
1561            mMode = MODE_SELECT;
1562            mState = STATE_SELECT_FIX;
1563        }
1564
1565        private void resetEdit() {
1566            endEdit();
1567            mEditFlag = true;
1568            mEST.notifyStateChanged(mMode, mState);
1569        }
1570
1571        private void setSelection() {
1572            if (DBG) {
1573                Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd);
1574            }
1575            if (mCurStart >= 0 && mCurStart <= mEST.getText().length() && mCurEnd >= 0
1576                    && mCurEnd <= mEST.getText().length()) {
1577                if (mCurStart < mCurEnd) {
1578                    mEST.setSelection(mCurStart, mCurEnd);
1579                    mState = STATE_SELECTED;
1580                } else if (mCurStart > mCurEnd) {
1581                    mEST.setSelection(mCurEnd, mCurStart);
1582                    mState = STATE_SELECTED;
1583                } else {
1584                    mState = STATE_SELECT_ON;
1585                }
1586            } else {
1587                Log.e(LOG_TAG, "Select is on, but cursor positions are illigal.:"
1588                        + mEST.getText().length() + "," + mCurStart + "," + mCurEnd);
1589            }
1590        }
1591
1592        private void unsetSelect() {
1593            if (DBG) {
1594                Log.d(LOG_TAG, "--- offSelect");
1595            }
1596            //MetaKeyKeyListener.stopSelecting(mEST, mEST.getText());
1597            stopSelecting(mEST, mEST.getText());
1598            int currpos = mEST.getSelectionStart();
1599            mEST.setSelection(currpos, currpos);
1600            mState = STATE_SELECT_OFF;
1601        }
1602
1603        private void setSelectStartPos() {
1604            if (DBG) {
1605                Log.d(LOG_TAG, "--- setSelectStartPos");
1606            }
1607            mCurStart = mEST.getSelectionStart();
1608            mState = STATE_SELECT_ON;
1609        }
1610
1611        private void setSelectEndPos() {
1612            if (mEST.getSelectionEnd() == mCurStart) {
1613                setEndPos(mEST.getSelectionStart());
1614            } else {
1615                setEndPos(mEST.getSelectionEnd());
1616            }
1617        }
1618
1619        public void setEndPos(int pos) {
1620            if (DBG) {
1621                Log.d(LOG_TAG, "--- setSelectedEndPos:" + pos);
1622            }
1623            mCurEnd = pos;
1624            setSelection();
1625        }
1626
1627        private boolean isWaitingNextAction() {
1628            if (DBG) {
1629                Log.d(LOG_TAG, "--- waitingNext:" + mCurStart + "," + mCurEnd + "," + mState);
1630            }
1631            if (mCurStart == mCurEnd && mState == STATE_SELECT_FIX) {
1632                waitSelection();
1633                return true;
1634            } else {
1635                resumeSelection();
1636                return false;
1637            }
1638        }
1639
1640        private void waitSelection() {
1641            if (DBG) {
1642                Log.d(LOG_TAG, "--- waitSelection");
1643            }
1644            mWaitInputFlag = true;
1645            if (mCurStart == mCurEnd) {
1646                mState = STATE_SELECT_ON;
1647            } else {
1648                mState = STATE_SELECTED;
1649            }
1650            //MetaKeyKeyListener.startSelecting(mEST, mEST.getText());
1651            startSelecting(mEST, mEST.getText());
1652        }
1653
1654        private void resumeSelection() {
1655            if (DBG) {
1656                Log.d(LOG_TAG, "--- resumeSelection");
1657            }
1658            mWaitInputFlag = false;
1659            mState = STATE_SELECT_FIX;
1660            //MetaKeyKeyListener.stopSelecting(mEST, mEST.getText());
1661            stopSelecting(mEST, mEST.getText());
1662        }
1663
1664        private boolean isTextSelected() {
1665            return (mState == STATE_SELECTED || mState == STATE_SELECT_FIX);
1666        }
1667
1668        private void setStyledTextSpan(Object span, int start, int end) {
1669            if (DBG) {
1670                Log.d(LOG_TAG, "--- setStyledTextSpan:" + mMode + "," + start + "," + end);
1671            }
1672            int min = Math.min(start, end);
1673            int max = Math.max(start, end);
1674            mEST.getText().setSpan(span, min, max, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1675            Selection.setSelection(mEST.getText(), max);
1676        }
1677
1678        private void setLineStyledTextSpan(Object span) {
1679            int min = Math.min(mCurStart, mCurEnd);
1680            int max = Math.max(mCurStart, mCurEnd);
1681            int current = mEST.getSelectionStart();
1682            int start = findLineStart(mEST.getText(), min);
1683            int end = findLineEnd(mEST.getText(), max);
1684            if (start == end) {
1685                mEST.getText().insert(end, "\n");
1686                setStyledTextSpan(span, start, end + 1);
1687            } else {
1688                setStyledTextSpan(span, start, end);
1689            }
1690            Selection.setSelection(mEST.getText(), current);
1691        }
1692
1693        private void changeSizeSelectedText(int size) {
1694            if (mCurStart != mCurEnd) {
1695                setStyledTextSpan(new AbsoluteSizeSpan(size), mCurStart, mCurEnd);
1696            } else {
1697                Log.e(LOG_TAG, "---changeSize: Size of the span is zero");
1698            }
1699        }
1700
1701        private void changeColorSelectedText(int color) {
1702            if (mCurStart != mCurEnd) {
1703                setStyledTextSpan(new ForegroundColorSpan(color), mCurStart, mCurEnd);
1704            } else {
1705                Log.e(LOG_TAG, "---changeColor: Size of the span is zero");
1706            }
1707        }
1708
1709        private void changeAlign(Layout.Alignment align) {
1710            setLineStyledTextSpan(new AlignmentSpan.Standard(align));
1711        }
1712
1713        private void addTelop() {
1714            addMarquee(MarqueeSpan.ALTERNATE);
1715        }
1716
1717        private void addSwing() {
1718            addMarquee(MarqueeSpan.SCROLL);
1719        }
1720
1721        private void addMarquee(int marquee) {
1722            if (DBG) {
1723                Log.d(LOG_TAG, "--- addMarquee:" + marquee);
1724            }
1725            setLineStyledTextSpan(new MarqueeSpan(marquee, mEST.getBackgroundColor()));
1726        }
1727
1728        private void insertImageSpan(DynamicDrawableSpan span, int curpos) {
1729            if (DBG) {
1730                Log.d(LOG_TAG, "--- insertImageSpan:");
1731            }
1732            if (span != null && span.getDrawable() != null) {
1733                mEST.getText().insert(curpos, "" + IMAGECHAR);
1734                mEST.getText().setSpan(span, curpos, curpos + 1,
1735                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1736                mEST.notifyStateChanged(mMode, mState);
1737            } else {
1738                Log.e(LOG_TAG, "--- insertImageSpan: null span was inserted");
1739                mEST.sendHintMessage(HINT_MSG_BIG_SIZE_ERROR);
1740            }
1741        }
1742
1743        private int findLineStart(Editable text, int current) {
1744            int pos = current;
1745            for (; pos > 0; pos--) {
1746                if (text.charAt(pos - 1) == '\n') {
1747                    break;
1748                }
1749            }
1750            if (DBG) {
1751                Log.d(LOG_TAG, "--- findLineStart:" + current + "," + text.length() + ","
1752                        + pos);
1753            }
1754            return pos;
1755        }
1756
1757        private int findLineEnd(Editable text, int current) {
1758            int pos = current;
1759            for (; pos < text.length(); pos++) {
1760                if (text.charAt(pos) == '\n') {
1761                    pos++;
1762                    break;
1763                }
1764            }
1765            if (DBG) {
1766                Log.d(LOG_TAG, "--- findLineEnd:" + current + "," + text.length() + "," + pos);
1767            }
1768            return pos;
1769        }
1770
1771        // Only for debug.
1772        private void dumpSpannableString(CharSequence txt) {
1773            if (txt instanceof Spannable) {
1774                Spannable spannable = (Spannable) txt;
1775                int len = spannable.length();
1776                if (DBG) {
1777                    Log.d(TAG, "--- dumpSpannableString, txt:" + spannable + ", len:" + len);
1778                }
1779                Object[] styles = spannable.getSpans(0, len, Object.class);
1780                for (Object style : styles) {
1781                    if (DBG) {
1782                        Log.d(TAG,
1783                                "--- dumpSpannableString, class:" + style + ","
1784                                        + spannable.getSpanStart(style) + ","
1785                                        + spannable.getSpanEnd(style) + ","
1786                                        + spannable.getSpanFlags(style));
1787                    }
1788                }
1789            }
1790        }
1791
1792        public void showSoftKey() {
1793            showSoftKey(mEST.getSelectionStart(), mEST.getSelectionEnd());
1794        }
1795
1796        public void showSoftKey(int oldSelStart, int oldSelEnd) {
1797            if (DBG) {
1798                Log.d(LOG_TAG, "--- showsoftkey");
1799            }
1800            if (!mEST.isFocused() || isSoftKeyBlocked()) {
1801                return;
1802            }
1803            mSkr.mNewStart = Selection.getSelectionStart(mEST.getText());
1804            mSkr.mNewEnd = Selection.getSelectionEnd(mEST.getText());
1805            InputMethodManager imm =
1806                    (InputMethodManager) getContext().getSystemService(
1807                            Context.INPUT_METHOD_SERVICE);
1808            if (imm.showSoftInput(mEST, 0, mSkr) && mSkr != null) {
1809                Selection.setSelection(getText(), oldSelStart, oldSelEnd);
1810            }
1811        }
1812
1813        public void hideSoftKey() {
1814            if (DBG) {
1815                Log.d(LOG_TAG, "--- hidesoftkey");
1816            }
1817            if (!mEST.isFocused()) {
1818                return;
1819            }
1820            mSkr.mNewStart = Selection.getSelectionStart(mEST.getText());
1821            mSkr.mNewEnd = Selection.getSelectionEnd(mEST.getText());
1822            InputMethodManager imm =
1823                    (InputMethodManager) mEST.getContext().getSystemService(
1824                            Context.INPUT_METHOD_SERVICE);
1825            imm.hideSoftInputFromWindow(mEST.getWindowToken(), 0, mSkr);
1826        }
1827
1828        public void blockSoftKey() {
1829            if (DBG) {
1830                Log.d(LOG_TAG, "--- blockSoftKey:");
1831            }
1832            hideSoftKey();
1833            mSoftKeyBlockFlag = true;
1834        }
1835
1836        public void unblockSoftKey() {
1837            if (DBG) {
1838                Log.d(LOG_TAG, "--- unblockSoftKey:");
1839            }
1840            mSoftKeyBlockFlag = false;
1841        }
1842    }
1843
1844    private class StyledTextHtmlStandard implements StyledTextHtmlConverter {
1845        public String toHtml(Spanned text) {
1846            return Html.toHtml(text);
1847        }
1848
1849        public String toHtml(Spanned text, boolean escapeNonAsciiChar) {
1850            return Html.toHtml(text);
1851        }
1852
1853        public String toHtml(Spanned text, boolean escapeNonAsciiChar, int width, float scale) {
1854            return Html.toHtml(text);
1855        }
1856
1857        public Spanned fromHtml(String source) {
1858            return Html.fromHtml(source);
1859        }
1860
1861        public Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler) {
1862            return Html.fromHtml(source, imageGetter, tagHandler);
1863        }
1864    }
1865
1866    private class StyledTextConverter {
1867        private EditStyledText mEST;
1868        private StyledTextHtmlConverter mHtml;
1869
1870        public StyledTextConverter(EditStyledText est, StyledTextHtmlConverter html) {
1871            mEST = est;
1872            mHtml = html;
1873        }
1874
1875        public void setStyledTextHtmlConverter(StyledTextHtmlConverter html) {
1876            mHtml = html;
1877        }
1878
1879        public String getHtml(boolean escapeFlag) {
1880            mEST.clearComposingText();
1881            mEST.onRefreshZeoWidthChar();
1882            String htmlBody = mHtml.toHtml(mEST.getText(), escapeFlag);
1883            if (DBG) {
1884                Log.d(TAG, "--- getHtml:" + htmlBody);
1885            }
1886            return htmlBody;
1887        }
1888
1889        public String getPreviewHtml() {
1890            mEST.clearComposingText();
1891            mEST.onRefreshZeoWidthChar();
1892            String html =
1893                    mHtml.toHtml(mEST.getText(), true, getMaxImageWidthDip(),
1894                            getPaddingScale());
1895            int bgColor = mEST.getBackgroundColor();
1896            html =
1897                    String.format("<body bgcolor=\"#%02X%02X%02X\">%s</body>",
1898                            Color.red(bgColor), Color.green(bgColor), Color.blue(bgColor),
1899                            html);
1900            if (DBG) {
1901                Log.d(TAG, "--- getPreviewHtml:" + html + "," + mEST.getWidth());
1902            }
1903            return html;
1904        }
1905
1906        public void getUriArray(ArrayList<Uri> uris, Editable text) {
1907            uris.clear();
1908            if (DBG) {
1909                Log.d(TAG, "--- getUriArray:");
1910            }
1911            int len = text.length();
1912            int next;
1913            for (int i = 0; i < text.length(); i = next) {
1914                next = text.nextSpanTransition(i, len, ImageSpan.class);
1915                ImageSpan[] images = text.getSpans(i, next, ImageSpan.class);
1916                for (int j = 0; j < images.length; j++) {
1917                    if (DBG) {
1918                        Log.d(TAG, "--- getUriArray: foundArray" + images[j].getSource());
1919                    }
1920                    uris.add(Uri.parse(images[j].getSource()));
1921                }
1922            }
1923        }
1924
1925        public void SetHtml(String html) {
1926            final Spanned spanned = mHtml.fromHtml(html, new Html.ImageGetter() {
1927                public Drawable getDrawable(String src) {
1928                    Log.d(TAG, "--- sethtml: src=" + src);
1929                    if (src.startsWith("content://")) {
1930                        Uri uri = Uri.parse(src);
1931                        try {
1932                            Drawable drawable = null;
1933                            Bitmap bitmap = null;
1934                            System.gc();
1935                            InputStream is =
1936                                    mEST.getContext().getContentResolver().openInputStream(uri);
1937                            BitmapFactory.Options opt = new BitmapFactory.Options();
1938                            opt.inJustDecodeBounds = true;
1939                            BitmapFactory.decodeStream(is, null, opt);
1940                            is.close();
1941                            is =  mEST.getContext().getContentResolver().openInputStream(uri);
1942                            int width, height;
1943                            width = opt.outWidth;
1944                            height = opt.outHeight;
1945                            if (opt.outWidth > getMaxImageWidthPx()) {
1946                                width = getMaxImageWidthPx();
1947                                height = height * getMaxImageWidthPx() / opt.outWidth;
1948                                Rect padding = new Rect(0, 0, width, height);
1949                                bitmap = BitmapFactory.decodeStream(is, padding, null);
1950                            } else {
1951                                bitmap = BitmapFactory.decodeStream(is);
1952                            }
1953                            drawable = new BitmapDrawable(
1954                                    mEST.getContext().getResources(), bitmap);
1955                            drawable.setBounds(0, 0, width, height);
1956                            is.close();
1957                            return drawable;
1958                        } catch (Exception e) {
1959                            Log.e(TAG, "--- set html: Failed to loaded content " + uri, e);
1960                            return null;
1961                        } catch (OutOfMemoryError e) {
1962                            Log.e(TAG, "OutOfMemoryError");
1963                            mEST.setHint(HINT_MSG_BIG_SIZE_ERROR);
1964
1965                            return null;
1966                        }
1967                    }
1968                    return null;
1969                }
1970            }, null);
1971            mEST.setText(spanned);
1972        }
1973    }
1974
1975    private static class SoftKeyReceiver extends ResultReceiver {
1976        int mNewStart;
1977        int mNewEnd;
1978        EditStyledText mEST;
1979
1980        SoftKeyReceiver(EditStyledText est) {
1981            super(est.getHandler());
1982            mEST = est;
1983        }
1984
1985        @Override
1986        protected void onReceiveResult(int resultCode, Bundle resultData) {
1987            if (resultCode != InputMethodManager.RESULT_SHOWN) {
1988                Selection.setSelection(mEST.getText(), mNewStart, mNewEnd);
1989            }
1990        }
1991    }
1992
1993    public static class SavedStyledTextState extends BaseSavedState {
1994        public int mBackgroundColor;
1995
1996        SavedStyledTextState(Parcelable superState) {
1997            super(superState);
1998        }
1999
2000        @Override
2001        public void writeToParcel(Parcel out, int flags) {
2002            super.writeToParcel(out, flags);
2003            out.writeInt(mBackgroundColor);
2004        }
2005
2006        @Override
2007        public String toString() {
2008            return "EditStyledText.SavedState{"
2009                    + Integer.toHexString(System.identityHashCode(this)) + " bgcolor="
2010                    + mBackgroundColor + "}";
2011        }
2012    }
2013
2014    private static class StyledTextDialog {
2015        private static final int TYPE_FOREGROUND = 0;
2016        private static final int TYPE_BACKGROUND = 1;
2017        private Builder mBuilder;
2018        private AlertDialog mAlertDialog;
2019        private CharSequence mColorTitle;
2020        private CharSequence mSizeTitle;
2021        private CharSequence mAlignTitle;
2022        private CharSequence mMarqueeTitle;
2023        private CharSequence[] mColorNames;
2024        private CharSequence[] mColorInts;
2025        private CharSequence[] mSizeNames;
2026        private CharSequence[] mSizeDisplayInts;
2027        private CharSequence[] mSizeSendInts;
2028        private CharSequence[] mAlignNames;
2029        private CharSequence[] mMarqueeNames;
2030        private CharSequence mColorDefaultMessage;
2031        private EditStyledText mEST;
2032
2033        public StyledTextDialog(EditStyledText est) {
2034            mEST = est;
2035        }
2036
2037        public void setBuilder(Builder builder) {
2038            mBuilder = builder;
2039        }
2040
2041        public void setColorAlertParams(CharSequence colortitle, CharSequence[] colornames,
2042                CharSequence[] colorInts, CharSequence defaultColorMessage) {
2043            mColorTitle = colortitle;
2044            mColorNames = colornames;
2045            mColorInts = colorInts;
2046            mColorDefaultMessage = defaultColorMessage;
2047        }
2048
2049        public void setSizeAlertParams(CharSequence sizetitle, CharSequence[] sizenames,
2050                CharSequence[] sizedisplayints, CharSequence[] sizesendints) {
2051            mSizeTitle = sizetitle;
2052            mSizeNames = sizenames;
2053            mSizeDisplayInts = sizedisplayints;
2054            mSizeSendInts = sizesendints;
2055        }
2056
2057        public void setAlignAlertParams(CharSequence aligntitle, CharSequence[] alignnames) {
2058            mAlignTitle = aligntitle;
2059            mAlignNames = alignnames;
2060        }
2061
2062        public void setMarqueeAlertParams(CharSequence marqueetitle,
2063                CharSequence[] marqueenames) {
2064            mMarqueeTitle = marqueetitle;
2065            mMarqueeNames = marqueenames;
2066        }
2067
2068        private boolean checkColorAlertParams() {
2069            if (DBG) {
2070                Log.d(TAG, "--- checkParams");
2071            }
2072            if (mBuilder == null) {
2073                Log.e(TAG, "--- builder is null.");
2074                return false;
2075            } else if (mColorTitle == null || mColorNames == null || mColorInts == null) {
2076                Log.e(TAG, "--- color alert params are null.");
2077                return false;
2078            } else if (mColorNames.length != mColorInts.length) {
2079                Log.e(TAG, "--- the length of color alert params are " + "different.");
2080                return false;
2081            }
2082            return true;
2083        }
2084
2085        private boolean checkSizeAlertParams() {
2086            if (DBG) {
2087                Log.d(TAG, "--- checkParams");
2088            }
2089            if (mBuilder == null) {
2090                Log.e(TAG, "--- builder is null.");
2091                return false;
2092            } else if (mSizeTitle == null || mSizeNames == null || mSizeDisplayInts == null
2093                    || mSizeSendInts == null) {
2094                Log.e(TAG, "--- size alert params are null.");
2095                return false;
2096            } else if (mSizeNames.length != mSizeDisplayInts.length
2097                    && mSizeSendInts.length != mSizeDisplayInts.length) {
2098                Log.e(TAG, "--- the length of size alert params are " + "different.");
2099                return false;
2100            }
2101            return true;
2102        }
2103
2104        private boolean checkAlignAlertParams() {
2105            if (DBG) {
2106                Log.d(TAG, "--- checkAlignAlertParams");
2107            }
2108            if (mBuilder == null) {
2109                Log.e(TAG, "--- builder is null.");
2110                return false;
2111            } else if (mAlignTitle == null) {
2112                Log.e(TAG, "--- align alert params are null.");
2113                return false;
2114            }
2115            return true;
2116        }
2117
2118        private boolean checkMarqueeAlertParams() {
2119            if (DBG) {
2120                Log.d(TAG, "--- checkMarqueeAlertParams");
2121            }
2122            if (mBuilder == null) {
2123                Log.e(TAG, "--- builder is null.");
2124                return false;
2125            } else if (mMarqueeTitle == null) {
2126                Log.e(TAG, "--- Marquee alert params are null.");
2127                return false;
2128            }
2129            return true;
2130        }
2131
2132        private void buildDialogue(CharSequence title, CharSequence[] names,
2133                DialogInterface.OnClickListener l) {
2134            mBuilder.setTitle(title);
2135            mBuilder.setIcon(0);
2136            mBuilder.setPositiveButton(null, null);
2137            mBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2138                public void onClick(DialogInterface dialog, int which) {
2139                    mEST.onStartEdit();
2140                }
2141            });
2142            mBuilder.setItems(names, l);
2143            mBuilder.setView(null);
2144            mBuilder.setCancelable(true);
2145            mBuilder.setOnCancelListener(new OnCancelListener() {
2146                public void onCancel(DialogInterface arg0) {
2147                    if (DBG) {
2148                        Log.d(TAG, "--- oncancel");
2149                    }
2150                    mEST.onStartEdit();
2151                }
2152            });
2153            mBuilder.show();
2154        }
2155
2156        private void buildAndShowColorDialogue(int type, CharSequence title, int[] colors) {
2157            final int HORIZONTAL_ELEMENT_NUM = 5;
2158            final int BUTTON_SIZE = mEST.dipToPx(50);
2159            final int BUTTON_MERGIN = mEST.dipToPx(2);
2160            final int BUTTON_PADDING = mEST.dipToPx(15);
2161            mBuilder.setTitle(title);
2162            mBuilder.setIcon(0);
2163            mBuilder.setPositiveButton(null, null);
2164            mBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2165                public void onClick(DialogInterface dialog, int which) {
2166                    mEST.onStartEdit();
2167                }
2168            });
2169            mBuilder.setItems(null, null);
2170            LinearLayout verticalLayout = new LinearLayout(mEST.getContext());
2171            verticalLayout.setOrientation(LinearLayout.VERTICAL);
2172            verticalLayout.setGravity(Gravity.CENTER_HORIZONTAL);
2173            verticalLayout.setPadding(BUTTON_PADDING, BUTTON_PADDING, BUTTON_PADDING,
2174                    BUTTON_PADDING);
2175            LinearLayout horizontalLayout = null;
2176            for (int i = 0; i < colors.length; i++) {
2177                if (i % HORIZONTAL_ELEMENT_NUM == 0) {
2178                    horizontalLayout = new LinearLayout(mEST.getContext());
2179                    verticalLayout.addView(horizontalLayout);
2180                }
2181                Button button = new Button(mEST.getContext());
2182                button.setHeight(BUTTON_SIZE);
2183                button.setWidth(BUTTON_SIZE);
2184                ColorPaletteDrawable cp =
2185                        new ColorPaletteDrawable(colors[i], BUTTON_SIZE, BUTTON_SIZE,
2186                                BUTTON_MERGIN);
2187                button.setBackgroundDrawable(cp);
2188                button.setDrawingCacheBackgroundColor(colors[i]);
2189                if (type == TYPE_FOREGROUND) {
2190                    button.setOnClickListener(new View.OnClickListener() {
2191                        public void onClick(View view) {
2192                            mEST.setItemColor(view.getDrawingCacheBackgroundColor());
2193                            if (mAlertDialog != null) {
2194                                mAlertDialog.setView(null);
2195                                mAlertDialog.dismiss();
2196                                mAlertDialog = null;
2197                            } else {
2198                                Log.e(TAG,
2199                                        "--- buildAndShowColorDialogue: can't find alertDialog");
2200                            }
2201                        }
2202                    });
2203                } else if (type == TYPE_BACKGROUND) {
2204                    button.setOnClickListener(new View.OnClickListener() {
2205                        public void onClick(View view) {
2206                            mEST.setBackgroundColor(view.getDrawingCacheBackgroundColor());
2207                            if (mAlertDialog != null) {
2208                                mAlertDialog.setView(null);
2209                                mAlertDialog.dismiss();
2210                                mAlertDialog = null;
2211                            } else {
2212                                Log.e(TAG,
2213                                        "--- buildAndShowColorDialogue: can't find alertDialog");
2214                            }
2215                        }
2216                    });
2217                }
2218                horizontalLayout.addView(button);
2219            }
2220
2221            if (type == TYPE_BACKGROUND) {
2222                mBuilder.setPositiveButton(mColorDefaultMessage,
2223                        new DialogInterface.OnClickListener() {
2224                            public void onClick(DialogInterface dialog, int which) {
2225                                mEST.setBackgroundColor(DEFAULT_TRANSPARENT_COLOR);
2226                            }
2227                        });
2228            } else if (type == TYPE_FOREGROUND) {
2229                mBuilder.setPositiveButton(mColorDefaultMessage,
2230                        new DialogInterface.OnClickListener() {
2231                            public void onClick(DialogInterface dialog, int which) {
2232                                mEST.setItemColor(DEFAULT_FOREGROUND_COLOR);
2233                            }
2234                        });
2235            }
2236
2237            mBuilder.setView(verticalLayout);
2238            mBuilder.setCancelable(true);
2239            mBuilder.setOnCancelListener(new OnCancelListener() {
2240                public void onCancel(DialogInterface arg0) {
2241                    mEST.onStartEdit();
2242                }
2243            });
2244            mAlertDialog = mBuilder.show();
2245        }
2246
2247        private void onShowForegroundColorAlertDialog() {
2248            if (DBG) {
2249                Log.d(TAG, "--- onShowForegroundColorAlertDialog");
2250            }
2251            if (!checkColorAlertParams()) {
2252                return;
2253            }
2254            int[] colorints = new int[mColorInts.length];
2255            for (int i = 0; i < colorints.length; i++) {
2256                colorints[i] = Integer.parseInt((String) mColorInts[i], 16) - 0x01000000;
2257            }
2258            buildAndShowColorDialogue(TYPE_FOREGROUND, mColorTitle, colorints);
2259        }
2260
2261        private void onShowBackgroundColorAlertDialog() {
2262            if (DBG) {
2263                Log.d(TAG, "--- onShowBackgroundColorAlertDialog");
2264            }
2265            if (!checkColorAlertParams()) {
2266                return;
2267            }
2268            int[] colorInts = new int[mColorInts.length];
2269            for (int i = 0; i < colorInts.length; i++) {
2270                colorInts[i] = Integer.parseInt((String) mColorInts[i], 16) - 0x01000000;
2271            }
2272            buildAndShowColorDialogue(TYPE_BACKGROUND, mColorTitle, colorInts);
2273        }
2274
2275        private void onShowSizeAlertDialog() {
2276            if (DBG) {
2277                Log.d(TAG, "--- onShowSizeAlertDialog");
2278            }
2279            if (!checkSizeAlertParams()) {
2280                return;
2281            }
2282            buildDialogue(mSizeTitle, mSizeNames, new DialogInterface.OnClickListener() {
2283                public void onClick(DialogInterface dialog, int which) {
2284                    Log.d(TAG, "mBuilder.onclick:" + which);
2285                    int size =
2286                            mEST.dipToPx(Integer.parseInt((String) mSizeDisplayInts[which]));
2287                    mEST.setItemSize(size);
2288                }
2289            });
2290        }
2291
2292        private void onShowAlignAlertDialog() {
2293            if (DBG) {
2294                Log.d(TAG, "--- onShowAlignAlertDialog");
2295            }
2296            if (!checkAlignAlertParams()) {
2297                return;
2298            }
2299            buildDialogue(mAlignTitle, mAlignNames, new DialogInterface.OnClickListener() {
2300                public void onClick(DialogInterface dialog, int which) {
2301                    Layout.Alignment align = Layout.Alignment.ALIGN_NORMAL;
2302                    switch (which) {
2303                        case 0:
2304                            align = Layout.Alignment.ALIGN_NORMAL;
2305                            break;
2306                        case 1:
2307                            align = Layout.Alignment.ALIGN_CENTER;
2308                            break;
2309                        case 2:
2310                            align = Layout.Alignment.ALIGN_OPPOSITE;
2311                            break;
2312                        default:
2313                            Log.e(TAG, "--- onShowAlignAlertDialog: got illigal align.");
2314                            break;
2315                    }
2316                    mEST.setAlignment(align);
2317                }
2318            });
2319        }
2320
2321        private void onShowMarqueeAlertDialog() {
2322            if (DBG) {
2323                Log.d(TAG, "--- onShowMarqueeAlertDialog");
2324            }
2325            if (!checkMarqueeAlertParams()) {
2326                return;
2327            }
2328            buildDialogue(mMarqueeTitle, mMarqueeNames, new DialogInterface.OnClickListener() {
2329                public void onClick(DialogInterface dialog, int which) {
2330                    if (DBG) {
2331                        Log.d(TAG, "mBuilder.onclick:" + which);
2332                    }
2333                    mEST.setMarquee(which);
2334                }
2335            });
2336        }
2337    }
2338
2339    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
2340        public boolean onMenuItemClick(MenuItem item) {
2341            return onTextContextMenuItem(item.getItemId());
2342        }
2343    }
2344
2345    private static class StyledTextArrowKeyMethod extends ArrowKeyMovementMethod {
2346        EditorManager mManager;
2347        String LOG_TAG = "StyledTextArrowKeyMethod";
2348
2349        StyledTextArrowKeyMethod(EditorManager manager) {
2350            super();
2351            mManager = manager;
2352        }
2353
2354        @Override
2355        public boolean
2356                onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
2357            if (DBG) {
2358                Log.d(LOG_TAG, "---onkeydown:" + keyCode);
2359            }
2360            mManager.unsetTextComposingMask();
2361            if (mManager.getSelectState() == STATE_SELECT_ON
2362                    || mManager.getSelectState() == STATE_SELECTED) {
2363                return executeDown(widget, buffer, keyCode);
2364            } else {
2365                return super.onKeyDown(widget, buffer, keyCode, event);
2366            }
2367        }
2368
2369        private int getEndPos(TextView widget) {
2370            int end;
2371            if (widget.getSelectionStart() == mManager.getSelectionStart()) {
2372                end = widget.getSelectionEnd();
2373            } else {
2374                end = widget.getSelectionStart();
2375            }
2376            return end;
2377        }
2378
2379        protected boolean up(TextView widget, Spannable buffer) {
2380            if (DBG) {
2381                Log.d(LOG_TAG, "--- up:");
2382            }
2383            Layout layout = widget.getLayout();
2384            int end = getEndPos(widget);
2385            int line = layout.getLineForOffset(end);
2386            if (line > 0) {
2387                int to;
2388                if (layout.getParagraphDirection(line) == layout
2389                        .getParagraphDirection(line - 1)) {
2390                    float h = layout.getPrimaryHorizontal(end);
2391                    to = layout.getOffsetForHorizontal(line - 1, h);
2392                } else {
2393                    to = layout.getLineStart(line - 1);
2394                }
2395                mManager.setEndPos(to);
2396                mManager.onCursorMoved();
2397            }
2398            return true;
2399        }
2400
2401        protected boolean down(TextView widget, Spannable buffer) {
2402            if (DBG) {
2403                Log.d(LOG_TAG, "--- down:");
2404            }
2405            Layout layout = widget.getLayout();
2406            int end = getEndPos(widget);
2407            int line = layout.getLineForOffset(end);
2408            if (line < layout.getLineCount() - 1) {
2409                int to;
2410                if (layout.getParagraphDirection(line) == layout
2411                        .getParagraphDirection(line + 1)) {
2412                    float h = layout.getPrimaryHorizontal(end);
2413                    to = layout.getOffsetForHorizontal(line + 1, h);
2414                } else {
2415                    to = layout.getLineStart(line + 1);
2416                }
2417                mManager.setEndPos(to);
2418                mManager.onCursorMoved();
2419            }
2420            return true;
2421        }
2422
2423        protected boolean left(TextView widget, Spannable buffer) {
2424            if (DBG) {
2425                Log.d(LOG_TAG, "--- left:");
2426            }
2427            Layout layout = widget.getLayout();
2428            int to = layout.getOffsetToLeftOf(getEndPos(widget));
2429            mManager.setEndPos(to);
2430            mManager.onCursorMoved();
2431            return true;
2432        }
2433
2434        protected boolean right(TextView widget, Spannable buffer) {
2435            if (DBG) {
2436                Log.d(LOG_TAG, "--- right:");
2437            }
2438            Layout layout = widget.getLayout();
2439            int to = layout.getOffsetToRightOf(getEndPos(widget));
2440            mManager.setEndPos(to);
2441            mManager.onCursorMoved();
2442            return true;
2443        }
2444
2445        private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
2446            if (DBG) {
2447                Log.d(LOG_TAG, "--- executeDown: " + keyCode);
2448            }
2449            boolean handled = false;
2450
2451            switch (keyCode) {
2452                case KeyEvent.KEYCODE_DPAD_UP:
2453                    handled |= up(widget, buffer);
2454                    break;
2455                case KeyEvent.KEYCODE_DPAD_DOWN:
2456                    handled |= down(widget, buffer);
2457                    break;
2458                case KeyEvent.KEYCODE_DPAD_LEFT:
2459                    handled |= left(widget, buffer);
2460                    break;
2461                case KeyEvent.KEYCODE_DPAD_RIGHT:
2462                    handled |= right(widget, buffer);
2463                    break;
2464                case KeyEvent.KEYCODE_DPAD_CENTER:
2465                    mManager.onFixSelectedItem();
2466                    handled = true;
2467                    break;
2468            }
2469            return handled;
2470        }
2471    }
2472
2473    public static class StyledTextInputConnection extends InputConnectionWrapper {
2474        EditStyledText mEST;
2475
2476        public StyledTextInputConnection(InputConnection target, EditStyledText est) {
2477            super(target, true);
2478            mEST = est;
2479        }
2480
2481        @Override
2482        public boolean commitText(CharSequence text, int newCursorPosition) {
2483            if (DBG) {
2484                Log.d(TAG, "--- commitText:");
2485            }
2486            mEST.mManager.unsetTextComposingMask();
2487            return super.commitText(text, newCursorPosition);
2488        }
2489
2490        @Override
2491        public boolean finishComposingText() {
2492            if (DBG) {
2493                Log.d(TAG, "--- finishcomposing:");
2494            }
2495            if (!mEST.isSoftKeyBlocked() && !mEST.isButtonsFocused() && !mEST.isEditting()) {
2496                // TODO onEndEdit isn't called temporally .
2497                mEST.onEndEdit();
2498            }
2499            return super.finishComposingText();
2500        }
2501    }
2502
2503    public static class EditStyledTextSpans {
2504        private static final String LOG_TAG = "EditStyledTextSpan";
2505
2506        public static class HorizontalLineSpan extends DynamicDrawableSpan {
2507            HorizontalLineDrawable mDrawable;
2508
2509            public HorizontalLineSpan(int color, int width, Spannable spannable) {
2510                super(ALIGN_BOTTOM);
2511                mDrawable = new HorizontalLineDrawable(color, width, spannable);
2512            }
2513
2514            @Override
2515            public Drawable getDrawable() {
2516                return mDrawable;
2517            }
2518
2519            public void resetWidth(int width) {
2520                mDrawable.renewBounds(width);
2521            }
2522
2523            public int getColor() {
2524                return mDrawable.getPaint().getColor();
2525            }
2526        }
2527
2528        public static class MarqueeSpan extends CharacterStyle {
2529            public static final int SCROLL = 0;
2530            public static final int ALTERNATE = 1;
2531            public static final int NOTHING = 2;
2532            private int mType;
2533            private int mMarqueeColor;
2534
2535            public MarqueeSpan(int type, int bgc) {
2536                mType = type;
2537                checkType(type);
2538                mMarqueeColor = getMarqueeColor(type, bgc);
2539            }
2540
2541            public MarqueeSpan(int type) {
2542                this(type, EditStyledText.DEFAULT_TRANSPARENT_COLOR);
2543            }
2544
2545            public int getType() {
2546                return mType;
2547            }
2548
2549            public void resetColor(int bgc) {
2550                mMarqueeColor = getMarqueeColor(mType, bgc);
2551            }
2552
2553            private int getMarqueeColor(int type, int bgc) {
2554                int THRESHOLD = 128;
2555                int a = Color.alpha(bgc);
2556                int r = Color.red(bgc);
2557                int g = Color.green(bgc);
2558                int b = Color.blue(bgc);
2559                if (a == 0) {
2560                    a = 0x80;
2561                }
2562                switch (type) {
2563                    case SCROLL:
2564                        if (r > THRESHOLD) {
2565                            r = r / 2;
2566                        } else {
2567                            r = (0XFF - r) / 2;
2568                        }
2569                        break;
2570                    case ALTERNATE:
2571                        if (g > THRESHOLD) {
2572                            g = g / 2;
2573                        } else {
2574                            g = (0XFF - g) / 2;
2575                        }
2576                        break;
2577                    case NOTHING:
2578                        return DEFAULT_TRANSPARENT_COLOR;
2579                    default:
2580                        Log.e(TAG, "--- getMarqueeColor: got illigal marquee ID.");
2581                        return DEFAULT_TRANSPARENT_COLOR;
2582                }
2583                return Color.argb(a, r, g, b);
2584            }
2585
2586            private boolean checkType(int type) {
2587                if (type == SCROLL || type == ALTERNATE) {
2588                    return true;
2589                } else {
2590                    Log.e(LOG_TAG, "--- Invalid type of MarqueeSpan");
2591                    return false;
2592                }
2593            }
2594
2595            @Override
2596            public void updateDrawState(TextPaint tp) {
2597                tp.bgColor = mMarqueeColor;
2598            }
2599        }
2600
2601        public static class RescalableImageSpan extends ImageSpan {
2602            Uri mContentUri;
2603            private Drawable mDrawable;
2604            private Context mContext;
2605            public int mIntrinsicWidth = -1;
2606            public int mIntrinsicHeight = -1;
2607            private final int MAXWIDTH;
2608
2609            public RescalableImageSpan(Context context, Uri uri, int maxwidth) {
2610                super(context, uri);
2611                mContext = context;
2612                mContentUri = uri;
2613                MAXWIDTH = maxwidth;
2614            }
2615
2616            public RescalableImageSpan(Context context, int resourceId, int maxwidth) {
2617                super(context, resourceId);
2618                mContext = context;
2619                MAXWIDTH = maxwidth;
2620            }
2621
2622            @Override
2623            public Drawable getDrawable() {
2624                if (mDrawable != null) {
2625                    return mDrawable;
2626                } else if (mContentUri != null) {
2627                    Bitmap bitmap = null;
2628                    System.gc();
2629                    try {
2630                        InputStream is =
2631                                mContext.getContentResolver().openInputStream(mContentUri);
2632                        BitmapFactory.Options opt = new BitmapFactory.Options();
2633                        opt.inJustDecodeBounds = true;
2634                        BitmapFactory.decodeStream(is, null, opt);
2635                        is.close();
2636                        is = mContext.getContentResolver().openInputStream(mContentUri);
2637                        int width, height;
2638                        width = opt.outWidth;
2639                        height = opt.outHeight;
2640                        mIntrinsicWidth = width;
2641                        mIntrinsicHeight = height;
2642                        if (opt.outWidth > MAXWIDTH) {
2643                            width = MAXWIDTH;
2644                            height = height * MAXWIDTH / opt.outWidth;
2645                            Rect padding = new Rect(0, 0, width, height);
2646                            bitmap = BitmapFactory.decodeStream(is, padding, null);
2647                        } else {
2648                            bitmap = BitmapFactory.decodeStream(is);
2649                        }
2650                        mDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
2651                        mDrawable.setBounds(0, 0, width, height);
2652                        is.close();
2653                    } catch (Exception e) {
2654                        Log.e(LOG_TAG, "Failed to loaded content " + mContentUri, e);
2655                        return null;
2656                    } catch (OutOfMemoryError e) {
2657                        Log.e(LOG_TAG, "OutOfMemoryError");
2658                        return null;
2659                    }
2660                } else {
2661                    mDrawable = super.getDrawable();
2662                    rescaleBigImage(mDrawable);
2663                    mIntrinsicWidth = mDrawable.getIntrinsicWidth();
2664                    mIntrinsicHeight = mDrawable.getIntrinsicHeight();
2665                }
2666                return mDrawable;
2667            }
2668
2669            public boolean isOverSize() {
2670                return (getDrawable().getIntrinsicWidth() > MAXWIDTH);
2671            }
2672
2673            public Uri getContentUri() {
2674                return mContentUri;
2675            }
2676
2677            private void rescaleBigImage(Drawable image) {
2678                if (DBG) {
2679                    Log.d(LOG_TAG, "--- rescaleBigImage:");
2680                }
2681                if (MAXWIDTH < 0) {
2682                    return;
2683                }
2684                int image_width = image.getIntrinsicWidth();
2685                int image_height = image.getIntrinsicHeight();
2686                if (DBG) {
2687                    Log.d(LOG_TAG, "--- rescaleBigImage:" + image_width + "," + image_height
2688                            + "," + MAXWIDTH);
2689                }
2690                if (image_width > MAXWIDTH) {
2691                    image_width = MAXWIDTH;
2692                    image_height = image_height * MAXWIDTH / image_width;
2693                }
2694                image.setBounds(0, 0, image_width, image_height);
2695            }
2696        }
2697
2698        public static class HorizontalLineDrawable extends ShapeDrawable {
2699            private Spannable mSpannable;
2700            private int mWidth;
2701            private static boolean DBG_HL = false;
2702
2703            public HorizontalLineDrawable(int color, int width, Spannable spannable) {
2704                super(new RectShape());
2705                mSpannable = spannable;
2706                mWidth = width;
2707                renewColor(color);
2708                renewBounds(width);
2709            }
2710
2711            @Override
2712            public void draw(Canvas canvas) {
2713                renewColor();
2714                Rect rect = new Rect(0, 9, mWidth, 11);
2715                canvas.drawRect(rect, getPaint());
2716            }
2717
2718            public void renewBounds(int width) {
2719                int MARGIN = 20;
2720                int HEIGHT = 20;
2721                if (DBG_HL) {
2722                    Log.d(LOG_TAG, "--- renewBounds:" + width);
2723                }
2724                if (width > MARGIN) {
2725                    width -= MARGIN;
2726                }
2727                mWidth = width;
2728                setBounds(0, 0, width, HEIGHT);
2729            }
2730
2731            private void renewColor(int color) {
2732                if (DBG_HL) {
2733                    Log.d(LOG_TAG, "--- renewColor:" + color);
2734                }
2735                getPaint().setColor(color);
2736            }
2737
2738            private void renewColor() {
2739                HorizontalLineSpan parent = getParentSpan();
2740                Spannable text = mSpannable;
2741                int start = text.getSpanStart(parent);
2742                int end = text.getSpanEnd(parent);
2743                ForegroundColorSpan[] spans =
2744                        text.getSpans(start, end, ForegroundColorSpan.class);
2745                if (DBG_HL) {
2746                    Log.d(LOG_TAG, "--- renewColor:" + spans.length);
2747                }
2748                if (spans.length > 0) {
2749                    renewColor(spans[spans.length - 1].getForegroundColor());
2750                }
2751            }
2752
2753            private HorizontalLineSpan getParentSpan() {
2754                Spannable text = mSpannable;
2755                HorizontalLineSpan[] images =
2756                        text.getSpans(0, text.length(), HorizontalLineSpan.class);
2757                if (images.length > 0) {
2758                    for (HorizontalLineSpan image : images) {
2759                        if (image.getDrawable() == this) {
2760                            return image;
2761                        }
2762                    }
2763                }
2764                Log.e(LOG_TAG, "---renewBounds: Couldn't find");
2765                return null;
2766            }
2767        }
2768    }
2769
2770    public static class ColorPaletteDrawable extends ShapeDrawable {
2771        private Rect mRect;
2772
2773        public ColorPaletteDrawable(int color, int width, int height, int mergin) {
2774            super(new RectShape());
2775            mRect = new Rect(mergin, mergin, width - mergin, height - mergin);
2776            getPaint().setColor(color);
2777        }
2778
2779        @Override
2780        public void draw(Canvas canvas) {
2781            canvas.drawRect(mRect, getPaint());
2782        }
2783    }
2784
2785    public class EditModeActions {
2786
2787        private static final String TAG = "EditModeActions";
2788        private static final boolean DBG = true;
2789        private EditStyledText mEST;
2790        private EditorManager mManager;
2791        private StyledTextDialog mDialog;
2792
2793        private int mMode = EditStyledText.MODE_NOTHING;
2794
2795        private HashMap<Integer, EditModeActionBase> mActionMap =
2796                new HashMap<Integer, EditModeActionBase>();
2797
2798        private NothingAction mNothingAction = new NothingAction();
2799        private CopyAction mCopyAction = new CopyAction();
2800        private PasteAction mPasteAction = new PasteAction();
2801        private SelectAction mSelectAction = new SelectAction();
2802        private CutAction mCutAction = new CutAction();
2803        private SelectAllAction mSelectAllAction = new SelectAllAction();
2804        private HorizontalLineAction mHorizontalLineAction = new HorizontalLineAction();
2805        private StopSelectionAction mStopSelectionAction = new StopSelectionAction();
2806        private ClearStylesAction mClearStylesAction = new ClearStylesAction();
2807        private ImageAction mImageAction = new ImageAction();
2808        private BackgroundColorAction mBackgroundColorAction = new BackgroundColorAction();
2809        private PreviewAction mPreviewAction = new PreviewAction();
2810        private CancelAction mCancelEditAction = new CancelAction();
2811        private TextViewAction mTextViewAction = new TextViewAction();
2812        private StartEditAction mStartEditAction = new StartEditAction();
2813        private EndEditAction mEndEditAction = new EndEditAction();
2814        private ResetAction mResetAction = new ResetAction();
2815        private ShowMenuAction mShowMenuAction = new ShowMenuAction();
2816        private AlignAction mAlignAction = new AlignAction();
2817        private TelopAction mTelopAction = new TelopAction();
2818        private SwingAction mSwingAction = new SwingAction();
2819        private MarqueeDialogAction mMarqueeDialogAction = new MarqueeDialogAction();
2820        private ColorAction mColorAction = new ColorAction();
2821        private SizeAction mSizeAction = new SizeAction();
2822
2823        EditModeActions(EditStyledText est, EditorManager manager, StyledTextDialog dialog) {
2824            mEST = est;
2825            mManager = manager;
2826            mDialog = dialog;
2827            mActionMap.put(EditStyledText.MODE_NOTHING, mNothingAction);
2828            mActionMap.put(EditStyledText.MODE_COPY, mCopyAction);
2829            mActionMap.put(EditStyledText.MODE_PASTE, mPasteAction);
2830            mActionMap.put(EditStyledText.MODE_SELECT, mSelectAction);
2831            mActionMap.put(EditStyledText.MODE_CUT, mCutAction);
2832            mActionMap.put(EditStyledText.MODE_SELECTALL, mSelectAllAction);
2833            mActionMap.put(EditStyledText.MODE_HORIZONTALLINE, mHorizontalLineAction);
2834            mActionMap.put(EditStyledText.MODE_STOP_SELECT, mStopSelectionAction);
2835            mActionMap.put(EditStyledText.MODE_CLEARSTYLES, mClearStylesAction);
2836            mActionMap.put(EditStyledText.MODE_IMAGE, mImageAction);
2837            mActionMap.put(EditStyledText.MODE_BGCOLOR, mBackgroundColorAction);
2838            mActionMap.put(EditStyledText.MODE_PREVIEW, mPreviewAction);
2839            mActionMap.put(EditStyledText.MODE_CANCEL, mCancelEditAction);
2840            mActionMap.put(EditStyledText.MODE_TEXTVIEWFUNCTION, mTextViewAction);
2841            mActionMap.put(EditStyledText.MODE_START_EDIT, mStartEditAction);
2842            mActionMap.put(EditStyledText.MODE_END_EDIT, mEndEditAction);
2843            mActionMap.put(EditStyledText.MODE_RESET, mResetAction);
2844            mActionMap.put(EditStyledText.MODE_SHOW_MENU, mShowMenuAction);
2845            mActionMap.put(EditStyledText.MODE_ALIGN, mAlignAction);
2846            mActionMap.put(EditStyledText.MODE_TELOP, mTelopAction);
2847            mActionMap.put(EditStyledText.MODE_SWING, mSwingAction);
2848            mActionMap.put(EditStyledText.MODE_MARQUEE, mMarqueeDialogAction);
2849            mActionMap.put(EditStyledText.MODE_COLOR, mColorAction);
2850            mActionMap.put(EditStyledText.MODE_SIZE, mSizeAction);
2851        }
2852
2853        public void addAction(int modeId, EditModeActionBase action) {
2854            mActionMap.put(modeId, action);
2855        }
2856
2857        public void onAction(int newMode, Object[] params) {
2858            getAction(newMode).addParams(params);
2859            mMode = newMode;
2860            doNext(newMode);
2861        }
2862
2863        public void onAction(int newMode, Object param) {
2864            onAction(newMode, new Object[] { param });
2865        }
2866
2867        public void onAction(int newMode) {
2868            onAction(newMode, null);
2869        }
2870
2871        public void onSelectAction() {
2872            doNext(EditStyledText.MODE_SELECT);
2873        }
2874
2875        private EditModeActionBase getAction(int mode) {
2876            if (mActionMap.containsKey(mode)) {
2877                return mActionMap.get(mode);
2878            }
2879            return null;
2880        }
2881
2882        public boolean doNext() {
2883            return doNext(mMode);
2884        }
2885
2886        public boolean doNext(int mode) {
2887            if (DBG) {
2888                Log.d(TAG, "--- do the next action: " + mode + "," + mManager.getSelectState());
2889            }
2890            EditModeActionBase action = getAction(mode);
2891            if (action == null) {
2892                Log.e(TAG, "--- invalid action error.");
2893                return false;
2894            }
2895            switch (mManager.getSelectState()) {
2896                case EditStyledText.STATE_SELECT_OFF:
2897                    return action.doNotSelected();
2898                case EditStyledText.STATE_SELECT_ON:
2899                    return action.doStartPosIsSelected();
2900                case EditStyledText.STATE_SELECTED:
2901                    return action.doEndPosIsSelected();
2902                case EditStyledText.STATE_SELECT_FIX:
2903                    if (mManager.isWaitInput()) {
2904                        return action.doSelectionIsFixedAndWaitingInput();
2905                    } else {
2906                        return action.doSelectionIsFixed();
2907                    }
2908                default:
2909                    return false;
2910            }
2911        }
2912
2913        public class EditModeActionBase {
2914            private Object[] mParams;
2915
2916            protected boolean canOverWrap() {
2917                return false;
2918            }
2919
2920            protected boolean canSelect() {
2921                return false;
2922            }
2923
2924            protected boolean canWaitInput() {
2925                return false;
2926            }
2927
2928            protected boolean needSelection() {
2929                return false;
2930            }
2931
2932            protected boolean isLine() {
2933                return false;
2934            }
2935
2936            protected boolean doNotSelected() {
2937                return false;
2938            }
2939
2940            protected boolean doStartPosIsSelected() {
2941                return doNotSelected();
2942            }
2943
2944            protected boolean doEndPosIsSelected() {
2945                return doStartPosIsSelected();
2946            }
2947
2948            protected boolean doSelectionIsFixed() {
2949                return doEndPosIsSelected();
2950            }
2951
2952            protected boolean doSelectionIsFixedAndWaitingInput() {
2953                return doEndPosIsSelected();
2954            }
2955
2956            protected boolean fixSelection() {
2957                mEST.finishComposingText();
2958                mManager.setSelectState(EditStyledText.STATE_SELECT_FIX);
2959                return true;
2960            }
2961
2962            protected void addParams(Object[] o) {
2963                mParams = o;
2964            }
2965
2966            protected Object getParam(int num) {
2967                if (mParams == null || num > mParams.length) {
2968                    if (DBG) {
2969                        Log.d(TAG, "--- Number of the parameter is out of bound.");
2970                    }
2971                    return null;
2972                } else {
2973                    return mParams[num];
2974                }
2975            }
2976        }
2977
2978        public class NothingAction extends EditModeActionBase {
2979        }
2980
2981        public class TextViewActionBase extends EditModeActionBase {
2982            @Override
2983            protected boolean doNotSelected() {
2984                if (mManager.getEditMode() == EditStyledText.MODE_NOTHING
2985                        || mManager.getEditMode() == EditStyledText.MODE_SELECT) {
2986                    mManager.setEditMode(mMode);
2987                    onSelectAction();
2988                    return true;
2989                }
2990                return false;
2991            }
2992
2993            @Override
2994            protected boolean doEndPosIsSelected() {
2995                if (mManager.getEditMode() == EditStyledText.MODE_NOTHING
2996                        || mManager.getEditMode() == EditStyledText.MODE_SELECT) {
2997                    mManager.setEditMode(mMode);
2998                    fixSelection();
2999                    doNext();
3000                    return true;
3001                } else if (mManager.getEditMode() != mMode) {
3002                    mManager.resetEdit();
3003                    mManager.setEditMode(mMode);
3004                    doNext();
3005                    return true;
3006                }
3007                return false;
3008            }
3009        }
3010
3011        public class TextViewAction extends TextViewActionBase {
3012            @Override
3013            protected boolean doEndPosIsSelected() {
3014                if (super.doEndPosIsSelected()) {
3015                    return true;
3016                }
3017                Object param = getParam(0);
3018                if (param != null && param instanceof Integer) {
3019                    mEST.onTextContextMenuItem((Integer) param);
3020                }
3021                mManager.resetEdit();
3022                return true;
3023            }
3024        }
3025
3026        public class CopyAction extends TextViewActionBase {
3027            @Override
3028            protected boolean doEndPosIsSelected() {
3029                if (super.doEndPosIsSelected()) {
3030                    return true;
3031                }
3032                mManager.copyToClipBoard();
3033                mManager.resetEdit();
3034                return true;
3035            }
3036        }
3037
3038        public class CutAction extends TextViewActionBase {
3039            @Override
3040            protected boolean doEndPosIsSelected() {
3041                if (super.doEndPosIsSelected()) {
3042                    return true;
3043                }
3044                mManager.cutToClipBoard();
3045                mManager.resetEdit();
3046                return true;
3047            }
3048        }
3049
3050        public class SelectAction extends EditModeActionBase {
3051            @Override
3052            protected boolean doNotSelected() {
3053                if (mManager.isTextSelected()) {
3054                    Log.e(TAG, "Selection is off, but selected");
3055                }
3056                mManager.setSelectStartPos();
3057                mEST.sendHintMessage(EditStyledText.HINT_MSG_SELECT_END);
3058                return true;
3059            }
3060
3061            @Override
3062            protected boolean doStartPosIsSelected() {
3063                if (mManager.isTextSelected()) {
3064                    Log.e(TAG, "Selection now start, but selected");
3065                }
3066                mManager.setSelectEndPos();
3067                mEST.sendHintMessage(EditStyledText.HINT_MSG_PUSH_COMPETE);
3068                if (mManager.getEditMode() != EditStyledText.MODE_SELECT) {
3069                    doNext(mManager.getEditMode()); // doNextHandle needs edit mode in editor.
3070                }
3071                return true;
3072            }
3073
3074            @Override
3075            protected boolean doSelectionIsFixed() {
3076                return false;
3077            }
3078        }
3079
3080        public class PasteAction extends EditModeActionBase {
3081            @Override
3082            protected boolean doNotSelected() {
3083                mManager.pasteFromClipboard();
3084                mManager.resetEdit();
3085                return true;
3086            }
3087        }
3088
3089        public class SelectAllAction extends EditModeActionBase {
3090            @Override
3091            protected boolean doNotSelected() {
3092                mManager.selectAll();
3093                return true;
3094            }
3095        }
3096
3097        public class HorizontalLineAction extends EditModeActionBase {
3098            @Override
3099            protected boolean doNotSelected() {
3100                mManager.insertHorizontalLine();
3101                return true;
3102            }
3103        }
3104
3105        public class ClearStylesAction extends EditModeActionBase {
3106            @Override
3107            protected boolean doNotSelected() {
3108                mManager.clearStyles();
3109                return true;
3110            }
3111        }
3112
3113        public class StopSelectionAction extends EditModeActionBase {
3114            @Override
3115            protected boolean doNotSelected() {
3116                mManager.fixSelectionAndDoNextAction();
3117                return true;
3118            }
3119        }
3120
3121        public class CancelAction extends EditModeActionBase {
3122            @Override
3123            protected boolean doNotSelected() {
3124                mEST.cancelViewManagers();
3125                return true;
3126            }
3127        }
3128
3129        public class ImageAction extends EditModeActionBase {
3130            @Override
3131            protected boolean doNotSelected() {
3132                Object param = getParam(0);
3133                if (param != null) {
3134                    if (param instanceof Uri) {
3135                        mManager.insertImageFromUri((Uri) param);
3136                    } else if (param instanceof Integer) {
3137                        mManager.insertImageFromResId((Integer) param);
3138                    }
3139                } else {
3140                    mEST.showInsertImageSelectAlertDialog();
3141                }
3142                return true;
3143            }
3144        }
3145
3146        public class BackgroundColorAction extends EditModeActionBase {
3147            @Override
3148            protected boolean doNotSelected() {
3149                mDialog.onShowBackgroundColorAlertDialog();
3150                return true;
3151            }
3152        }
3153
3154        public class PreviewAction extends EditModeActionBase {
3155            @Override
3156            protected boolean doNotSelected() {
3157                mEST.showPreview();
3158                return true;
3159            }
3160        }
3161
3162        public class StartEditAction extends EditModeActionBase {
3163            @Override
3164            protected boolean doNotSelected() {
3165                mManager.startEdit();
3166                return true;
3167            }
3168        }
3169
3170        public class EndEditAction extends EditModeActionBase {
3171            @Override
3172            protected boolean doNotSelected() {
3173                mManager.endEdit();
3174                return true;
3175            }
3176        }
3177
3178        public class ResetAction extends EditModeActionBase {
3179            @Override
3180            protected boolean doNotSelected() {
3181                mManager.resetEdit();
3182                return true;
3183            }
3184        }
3185
3186        public class ShowMenuAction extends EditModeActionBase {
3187            @Override
3188            protected boolean doNotSelected() {
3189                mEST.showMenuAlertDialog();
3190                return true;
3191            }
3192        }
3193
3194        public class SetSpanActionBase extends EditModeActionBase {
3195            @Override
3196            protected boolean doNotSelected() {
3197                if (mManager.getEditMode() == EditStyledText.MODE_NOTHING
3198                        || mManager.getEditMode() == EditStyledText.MODE_SELECT) {
3199                    mManager.setEditMode(mMode);
3200                    mManager.setInternalSelection(mEST.getSelectionStart(),
3201                            mEST.getSelectionEnd());
3202                    fixSelection();
3203                    doNext();
3204                    return true;
3205                } else if (mManager.getEditMode() != mMode) {
3206                    Log.d(TAG, "--- setspanactionbase" + mManager.getEditMode() + "," + mMode);
3207                    if (!mManager.isWaitInput()) {
3208                        mManager.resetEdit();
3209                        mManager.setEditMode(mMode);
3210                    } else {
3211                        mManager.setEditMode(EditStyledText.MODE_NOTHING);
3212                        mManager.setSelectState(EditStyledText.STATE_SELECT_OFF);
3213                    }
3214                    doNext();
3215                    return true;
3216                }
3217                return false;
3218            }
3219
3220            @Override
3221            protected boolean doStartPosIsSelected() {
3222                if (mManager.getEditMode() == EditStyledText.MODE_NOTHING
3223                        || mManager.getEditMode() == EditStyledText.MODE_SELECT) {
3224                    mManager.setEditMode(mMode);
3225                    onSelectAction();
3226                    return true;
3227                }
3228                return doNotSelected();
3229            }
3230
3231            @Override
3232            protected boolean doEndPosIsSelected() {
3233                if (mManager.getEditMode() == EditStyledText.MODE_NOTHING
3234                        || mManager.getEditMode() == EditStyledText.MODE_SELECT) {
3235                    mManager.setEditMode(mMode);
3236                    fixSelection();
3237                    doNext();
3238                    return true;
3239                }
3240                return doStartPosIsSelected();
3241            }
3242
3243            @Override
3244            protected boolean doSelectionIsFixed() {
3245                if (doEndPosIsSelected()) {
3246                    return true;
3247                }
3248                mEST.sendHintMessage(EditStyledText.HINT_MSG_NULL);
3249
3250                return false;
3251            }
3252        }
3253
3254        public class AlignAction extends SetSpanActionBase {
3255            @Override
3256            protected boolean doSelectionIsFixed() {
3257                if (super.doSelectionIsFixed()) {
3258                    return true;
3259                }
3260                mDialog.onShowAlignAlertDialog();
3261                return true;
3262            }
3263        }
3264
3265        public class TelopAction extends SetSpanActionBase {
3266            @Override
3267            protected boolean doSelectionIsFixed() {
3268                if (super.doSelectionIsFixed()) {
3269                    return true;
3270                }
3271                mManager.setTelop();
3272                return true;
3273            }
3274        }
3275
3276        public class SwingAction extends SetSpanActionBase {
3277            @Override
3278            protected boolean doSelectionIsFixed() {
3279                if (super.doSelectionIsFixed()) {
3280                    return true;
3281                }
3282                mManager.setSwing();
3283                return true;
3284            }
3285        }
3286
3287        public class MarqueeDialogAction extends SetSpanActionBase {
3288            @Override
3289            protected boolean doSelectionIsFixed() {
3290                if (super.doSelectionIsFixed()) {
3291                    return true;
3292                }
3293                mDialog.onShowMarqueeAlertDialog();
3294                return true;
3295            }
3296        }
3297
3298        public class ColorAction extends SetSpanActionBase {
3299            @Override
3300            protected boolean doSelectionIsFixed() {
3301                if (super.doSelectionIsFixed()) {
3302                    return true;
3303                }
3304                mDialog.onShowForegroundColorAlertDialog();
3305                return true;
3306            }
3307
3308            @Override
3309            protected boolean doSelectionIsFixedAndWaitingInput() {
3310                if (super.doSelectionIsFixedAndWaitingInput()) {
3311                    return true;
3312                }
3313                int size = mManager.getSizeWaitInput();
3314                mManager.setItemColor(mManager.getColorWaitInput(), false);
3315                // selection was resumed
3316                if (!mManager.isWaitInput()) {
3317                    mManager.setItemSize(size, false);
3318                    mManager.resetEdit();
3319                } else {
3320                    fixSelection();
3321                    mDialog.onShowForegroundColorAlertDialog();
3322                }
3323                return true;
3324            }
3325        }
3326
3327        public class SizeAction extends SetSpanActionBase {
3328            @Override
3329            protected boolean doSelectionIsFixed() {
3330                if (super.doSelectionIsFixed()) {
3331                    return true;
3332                }
3333                mDialog.onShowSizeAlertDialog();
3334                return true;
3335            }
3336
3337            @Override
3338            protected boolean doSelectionIsFixedAndWaitingInput() {
3339                if (super.doSelectionIsFixedAndWaitingInput()) {
3340                    return true;
3341                }
3342                int color = mManager.getColorWaitInput();
3343                mManager.setItemSize(mManager.getSizeWaitInput(), false);
3344                if (!mManager.isWaitInput()) {
3345                    mManager.setItemColor(color, false);
3346                    mManager.resetEdit();
3347                } else {
3348                    fixSelection();
3349                    mDialog.onShowSizeAlertDialog();
3350                }
3351                return true;
3352            }
3353        }
3354    }
3355}
3356