1de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov/*
24b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * Copyright (C) 2014 The Android Open Source Project
3de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov *
4de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * Licensed under the Apache License, Version 2.0 (the "License");
5de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * you may not use this file except in compliance with the License.
6de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * You may obtain a copy of the License at
7de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov *
84b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen *   http://www.apache.org/licenses/LICENSE-2.0
9de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov *
10de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * Unless required by applicable law or agreed to in writing, software
11de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * distributed under the License is distributed on an "AS IS" BASIS,
12de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * See the License for the specific language governing permissions and
14de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov * limitations under the License.
15de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov */
16de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov
17de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikovpackage com.android.calculator2;
18de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov
19de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikovimport android.content.Context;
204b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.content.res.TypedArray;
21245925ee00435ac6d63d3af409366ae10c79127dHongwei Wangimport android.graphics.Paint;
224b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.graphics.Paint.FontMetricsInt;
234b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.graphics.Rect;
24bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassenimport android.os.Parcelable;
254b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.text.method.ScrollingMovementMethod;
264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.text.TextPaint;
27de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikovimport android.util.AttributeSet;
28245925ee00435ac6d63d3af409366ae10c79127dHongwei Wangimport android.util.TypedValue;
29f57b8b40c633401775b81aa008e49201ac948140Gilles Debunneimport android.view.ActionMode;
30f57b8b40c633401775b81aa008e49201ac948140Gilles Debunneimport android.view.Menu;
31f57b8b40c633401775b81aa008e49201ac948140Gilles Debunneimport android.view.MenuItem;
3279525c3b333ea3623d978674e5d8689e78b7ed16Gilles Debunneimport android.view.MotionEvent;
33de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikovimport android.widget.EditText;
34fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassenimport android.widget.TextView;
35de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov
364b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenpublic class CalculatorEditText extends EditText {
3788a84db210d5e3ad5ea8c1f75c55c3175afcf46cMindy Pereira
38bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen    private final static ActionMode.Callback NO_SELECTION_ACTION_MODE_CALLBACK =
39bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen            new ActionMode.Callback() {
404b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        @Override
414b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
424b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            return false;
434b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
44de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov
454b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        @Override
464b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
474b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            // Prevents the selection action mode on double tap.
484b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            return false;
494b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
504b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        @Override
524b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        public void onDestroyActionMode(ActionMode mode) {
534b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
54461992df853ace2b0624aac4e372d3ed8efc7059Alan Viverette
554b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        @Override
564b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
574b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            return false;
584b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
594b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    };
60461992df853ace2b0624aac4e372d3ed8efc7059Alan Viverette
614b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private final float mMaximumTextSize;
624b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private final float mMinimumTextSize;
634b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private final float mStepTextSize;
64245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
652be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen    // Temporary objects for use in layout methods.
662be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen    private final Paint mTempPaint = new TextPaint();
672be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen    private final Rect mTempRect = new Rect();
682be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen
69245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    private int mWidthConstraint = -1;
70fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    private OnTextSizeChangeListener mOnTextSizeChangeListener;
71245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
728bea49415c6de57f7e14517bf64769d24e3f099dAlan Viverette    public CalculatorEditText(Context context) {
738bea49415c6de57f7e14517bf64769d24e3f099dAlan Viverette        this(context, null);
748bea49415c6de57f7e14517bf64769d24e3f099dAlan Viverette    }
758bea49415c6de57f7e14517bf64769d24e3f099dAlan Viverette
76de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov    public CalculatorEditText(Context context, AttributeSet attrs) {
774b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        this(context, attrs, 0);
7879525c3b333ea3623d978674e5d8689e78b7ed16Gilles Debunne    }
7979525c3b333ea3623d978674e5d8689e78b7ed16Gilles Debunne
804b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public CalculatorEditText(Context context, AttributeSet attrs, int defStyle) {
814b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        super(context, attrs, defStyle);
82461992df853ace2b0624aac4e372d3ed8efc7059Alan Viverette
834b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        final TypedArray a = context.obtainStyledAttributes(
844b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                attrs, R.styleable.CalculatorEditText, defStyle, 0);
854b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mMaximumTextSize = a.getDimension(
864b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                R.styleable.CalculatorEditText_maxTextSize, getTextSize());
874b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mMinimumTextSize = a.getDimension(
884b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                R.styleable.CalculatorEditText_minTextSize, getTextSize());
894b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mStepTextSize = a.getDimension(R.styleable.CalculatorEditText_stepTextSize,
904b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                (mMaximumTextSize - mMinimumTextSize) / 3);
9179525c3b333ea3623d978674e5d8689e78b7ed16Gilles Debunne
924b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        a.recycle();
93461992df853ace2b0624aac4e372d3ed8efc7059Alan Viverette
94bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        setCustomSelectionActionModeCallback(NO_SELECTION_ACTION_MODE_CALLBACK);
95ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        if (isFocusable()) {
96ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro            setMovementMethod(ScrollingMovementMethod.getInstance());
97ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        }
984b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
994b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
100220cf991106c6b6351673111076c91333c93cc7dMindy Pereira    }
101220cf991106c6b6351673111076c91333c93cc7dMindy Pereira
10288a84db210d5e3ad5ea8c1f75c55c3175afcf46cMindy Pereira    @Override
1034b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public boolean onTouchEvent(MotionEvent event) {
1044b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1054b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            // Hack to prevent keyboard and insertion handle from showing.
1064b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            cancelLongPress();
1074b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
1084b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        return super.onTouchEvent(event);
10988a84db210d5e3ad5ea8c1f75c55c3175afcf46cMindy Pereira    }
11088a84db210d5e3ad5ea8c1f75c55c3175afcf46cMindy Pereira
111245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    @Override
112245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
113245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
114245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
115245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang        mWidthConstraint =
116245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang                MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
1174b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
118245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    }
119245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
120245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    @Override
121bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen    public Parcelable onSaveInstanceState() {
122bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        super.onSaveInstanceState();
123bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen
124bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        // EditText will freeze any text with a selection regardless of getFreezesText() ->
125bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        // return null to prevent any state from being preserved at the instance level.
126bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        return null;
127bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen    }
128bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen
129bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen    @Override
130245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
131245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang        super.onTextChanged(text, start, lengthBefore, lengthAfter);
132bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen
133bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        final int textLength = text.length();
134bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        if (getSelectionStart() != textLength || getSelectionEnd() != textLength) {
135bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen            // Pin the selection to the end of the current text.
136bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen            setSelection(textLength);
137bfc4e4d88011b33b4cf142a89b9125bd07393ab5Justin Klaassen        }
1384b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
139245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang    }
140245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
141fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    @Override
142fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    public void setTextSize(int unit, float size) {
143fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        final float oldTextSize = getTextSize();
144fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        super.setTextSize(unit, size);
145fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
146fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
147fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen            mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
148fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        }
149fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    }
150fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
151fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    public void setOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
152fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        mOnTextSizeChangeListener = listener;
153fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    }
154fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
1554b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public float getVariableTextSize(String text) {
1564b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
157245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang            // Not measured, bail early.
1584b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            return getTextSize();
159245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang        }
160245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
1612be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen        // Capture current paint state.
1622be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen        mTempPaint.set(getPaint());
1632be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen
1642be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen        // Step through increasing text sizes until the text would no longer fit.
1654b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        float lastFitTextSize = mMinimumTextSize;
166245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang        while (lastFitTextSize < mMaximumTextSize) {
1674b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
1682be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen            mTempPaint.setTextSize(nextSize);
1692be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen            if (mTempPaint.measureText(text) > mWidthConstraint) {
170245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang                break;
171245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang            } else {
172245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang                lastFitTextSize = nextSize;
173245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang            }
174245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang        }
175245925ee00435ac6d63d3af409366ae10c79127dHongwei Wang
1764b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        return lastFitTextSize;
177220cf991106c6b6351673111076c91333c93cc7dMindy Pereira    }
178220cf991106c6b6351673111076c91333c93cc7dMindy Pereira
179220cf991106c6b6351673111076c91333c93cc7dMindy Pereira    @Override
1804b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public int getCompoundPaddingTop() {
1814b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        // Measure the top padding from the capital letter height of the text instead of the top,
1824b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        // but don't remove more than the available top padding otherwise clipping may occur.
1832be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen        getPaint().getTextBounds("H", 0, 1, mTempRect);
184220cf991106c6b6351673111076c91333c93cc7dMindy Pereira
1854b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
1862be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen        final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
1874b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
188461992df853ace2b0624aac4e372d3ed8efc7059Alan Viverette    }
189461992df853ace2b0624aac4e372d3ed8efc7059Alan Viverette
1904b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    @Override
1914b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public int getCompoundPaddingBottom() {
1924b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
1934b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        // remove more than the available bottom padding otherwise clipping may occur.
1944b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
1954b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
196de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov    }
197fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
198fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    public interface OnTextSizeChangeListener {
199fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        void onTextSizeChanged(TextView textView, float oldSize);
200fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    }
201de3eec25069ffdd5b83fbbca90277d4a1f0d232bDmitri Plotnikov}
202