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