1c046e4a310462aa6998a34ff50439eb5e2508d85mindyp/*
2c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Copyright (C) 2013 Google Inc.
3c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Licensed to The Android Open Source Project.
4c046e4a310462aa6998a34ff50439eb5e2508d85mindyp *
5c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Licensed under the Apache License, Version 2.0 (the "License");
6c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * you may not use this file except in compliance with the License.
7c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * You may obtain a copy of the License at
8c046e4a310462aa6998a34ff50439eb5e2508d85mindyp *
9c046e4a310462aa6998a34ff50439eb5e2508d85mindyp *      http://www.apache.org/licenses/LICENSE-2.0
10c046e4a310462aa6998a34ff50439eb5e2508d85mindyp *
11c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Unless required by applicable law or agreed to in writing, software
12c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * distributed under the License is distributed on an "AS IS" BASIS,
13c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * See the License for the specific language governing permissions and
15c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * limitations under the License.
16c046e4a310462aa6998a34ff50439eb5e2508d85mindyp */
17c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
18c046e4a310462aa6998a34ff50439eb5e2508d85mindyppackage com.android.mail.ui;
19c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
20c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.content.Context;
21c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.text.Layout;
22ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huangimport android.text.Layout.Alignment;
23c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.text.SpannableStringBuilder;
245d6773c773401a259e6b6db957c34f36c508816bAndy Huangimport android.text.Spanned;
25ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huangimport android.text.StaticLayout;
26c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.text.TextUtils;
27c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.util.AttributeSet;
28c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.widget.TextView;
29c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
30c046e4a310462aa6998a34ff50439eb5e2508d85mindyp/**
31c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * A special MultiLine TextView that will apply ellipsize logic to only the last
32c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * line of text, such that the last line may be shorter than any previous lines.
33c046e4a310462aa6998a34ff50439eb5e2508d85mindyp */
34c046e4a310462aa6998a34ff50439eb5e2508d85mindyppublic class EllipsizedMultilineTextView extends TextView {
35c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
36c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    public static final int ALL_AVAILABLE = -1;
37c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    private int mMaxLines;
38c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
39c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    public EllipsizedMultilineTextView(Context context) {
40c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        this(context, null);
41c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    }
42c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
43c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    public EllipsizedMultilineTextView(Context context, AttributeSet attrs) {
44c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        super(context, attrs);
45c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    }
46c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
47c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    @Override
48c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    public void setMaxLines(int maxlines) {
49c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        super.setMaxLines(maxlines);
50c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        mMaxLines = maxlines;
51c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    }
52c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
53c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    /**
54c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     * Ellipsize just the last line of text in this view and set the text to the
55c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     * new ellipsized value.
56c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     * @param text Text to set and ellipsize
57c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     * @param avail available width in pixels for the last line
58c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     * @param paint Paint that has the proper properties set to measure the text
59c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     *            for this view
60c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy     * @return the {@link CharSequence} that was set on the {@link TextView}
61c046e4a310462aa6998a34ff50439eb5e2508d85mindyp     */
62c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy    public CharSequence setText(final CharSequence text, int avail) {
63c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        if (text == null || text.length() == 0) {
64c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy            return text;
65c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        }
66c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
67c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        setEllipsize(null);
685d6773c773401a259e6b6db957c34f36c508816bAndy Huang        setText(text);
69c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
70c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        if (avail == ALL_AVAILABLE) {
71c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy            return text;
72c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        }
73c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        Layout layout = getLayout();
74c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
75c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        if (layout == null) {
76ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang            final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
77ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang            layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL,
78ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang                    1.0f, 0f, false);
79c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        }
80c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
815d6773c773401a259e6b6db957c34f36c508816bAndy Huang        // find the last line of text and chop it according to available space
825d6773c773401a259e6b6db957c34f36c508816bAndy Huang        final int lastLineStart = layout.getLineStart(mMaxLines - 1);
835d6773c773401a259e6b6db957c34f36c508816bAndy Huang        final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart,
845d6773c773401a259e6b6db957c34f36c508816bAndy Huang                text.length()), getPaint(), avail, TextUtils.TruncateAt.END);
853a9238b703bc1f348d6c06b75617d346ea2bdb1eScott Kennedy
865d6773c773401a259e6b6db957c34f36c508816bAndy Huang        // assemble just the text portion, without spans
875d6773c773401a259e6b6db957c34f36c508816bAndy Huang        final SpannableStringBuilder builder = new SpannableStringBuilder();
885d6773c773401a259e6b6db957c34f36c508816bAndy Huang
895d6773c773401a259e6b6db957c34f36c508816bAndy Huang        builder.append(text.toString(), 0, lastLineStart);
90c046e4a310462aa6998a34ff50439eb5e2508d85mindyp
9120347e439dc5e2c86bc105d432c64f6e7f69c7ccAndy Huang        if (!TextUtils.isEmpty(remainder)) {
925d6773c773401a259e6b6db957c34f36c508816bAndy Huang            builder.append(remainder.toString());
935d6773c773401a259e6b6db957c34f36c508816bAndy Huang        }
945d6773c773401a259e6b6db957c34f36c508816bAndy Huang
955d6773c773401a259e6b6db957c34f36c508816bAndy Huang        // Now copy the original spans into the assembled string, modified for any ellipsizing.
965d6773c773401a259e6b6db957c34f36c508816bAndy Huang        //
975d6773c773401a259e6b6db957c34f36c508816bAndy Huang        // Merely assembling the Spanned pieces together would result in duplicate CharacterStyle
985d6773c773401a259e6b6db957c34f36c508816bAndy Huang        // spans in the assembled version if a CharacterStyle spanned across the lastLineStart
995d6773c773401a259e6b6db957c34f36c508816bAndy Huang        // offset.
1005d6773c773401a259e6b6db957c34f36c508816bAndy Huang        if (text instanceof Spanned) {
1015d6773c773401a259e6b6db957c34f36c508816bAndy Huang            final Spanned s = (Spanned) text;
1025d6773c773401a259e6b6db957c34f36c508816bAndy Huang            final Object[] spans = s.getSpans(0, s.length(), Object.class);
1035d6773c773401a259e6b6db957c34f36c508816bAndy Huang            final int destLen = builder.length();
1045d6773c773401a259e6b6db957c34f36c508816bAndy Huang            for (int i = 0; i < spans.length; i++) {
1055d6773c773401a259e6b6db957c34f36c508816bAndy Huang                final Object span = spans[i];
1065d6773c773401a259e6b6db957c34f36c508816bAndy Huang                final int start = s.getSpanStart(span);
1075d6773c773401a259e6b6db957c34f36c508816bAndy Huang                final int end = s.getSpanEnd(span);
1085d6773c773401a259e6b6db957c34f36c508816bAndy Huang                final int flags = s.getSpanFlags(span);
1095d6773c773401a259e6b6db957c34f36c508816bAndy Huang                if (start <= destLen) {
1105d6773c773401a259e6b6db957c34f36c508816bAndy Huang                    builder.setSpan(span, start, Math.min(end, destLen), flags);
1115d6773c773401a259e6b6db957c34f36c508816bAndy Huang                }
1125d6773c773401a259e6b6db957c34f36c508816bAndy Huang            }
113c046e4a310462aa6998a34ff50439eb5e2508d85mindyp        }
1145d6773c773401a259e6b6db957c34f36c508816bAndy Huang
1155d6773c773401a259e6b6db957c34f36c508816bAndy Huang        setText(builder);
116c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy
117c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy        return builder;
118c046e4a310462aa6998a34ff50439eb5e2508d85mindyp    }
119c046e4a310462aa6998a34ff50439eb5e2508d85mindyp}