com.coreform.open.android.formidablevalidation.SetErrorHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.coreform.open.android.formidablevalidation.SetErrorHandler.java

Source

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.coreform.open.android.formidablevalidation;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;

public class SetErrorHandler {
    private static final boolean DEBUG = true;
    private static final String TAG = "SetErrorHandler";

    private View mView;
    private int mTop;
    private int mBottom;
    private int mLeft;
    private int mRight;
    private int mPaddingTop;
    private int mPaddingBottom;
    private int mPaddingLeft;
    private int mPaddingRight;
    private int mErrorPopupPaddingTop;
    private int mErrorPopupPaddingBottom;
    private int mErrorPopupPaddingLeft;
    private int mErrorPopupPaddingRight;

    class Drawables {
        final Rect mCompoundRect = new Rect();
        Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
        int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
        int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
        int mDrawablePadding;
    }

    private Context mContext;
    private Drawables mDrawables;
    public CharSequence mError;
    private boolean mErrorWasChanged;
    private ErrorPopup mPopup;
    private boolean mShowErrorAfterAttach;

    public SetErrorHandler(Context context, View view) {
        mContext = context;
        mView = view;
        setupCustomView();
    }

    //necessary for XML inflation (of a View...this isn't a View class anymore)
    /*
    public SetErrorAble(Context context, AttributeSet attrs) {
       super(context, attrs);
       mContext = context;
       setupCustomView();
    }
    */

    private void setupCustomView() {
        mTop = mView.getTop();
        mBottom = mView.getBottom();
        mLeft = mView.getLeft();
        mRight = mView.getRight();
        mPaddingTop = mView.getPaddingTop();
        mPaddingBottom = mView.getPaddingBottom();
        mPaddingLeft = mView.getPaddingLeft();
        mPaddingRight = mView.getPaddingRight();
    }

    //LD - custom..TODO: remove this
    public void setErrorPopupPadding(int left, int top, int right, int bottom) {
        mErrorPopupPaddingTop = top;
        mErrorPopupPaddingBottom = bottom;
        mErrorPopupPaddingLeft = left;
        mErrorPopupPaddingRight = right;
    }

    /**
      * Sets the right-hand compound drawable of the TextView to the "error"
      * icon and sets an error message that will be displayed in a popup when
      * the TextView has focus.  The icon and error message will be reset to
      * null when any key events cause changes to the TextView's text.  If the
      * <code>error</code> is <code>null</code>, the error message and icon
      * will be cleared.
      */
    public void setError(CharSequence error) {
        if (DEBUG)
            Log.d(TAG, ".setError(error)...");
        if (error == null) {
            setError(null, null, true, true);
        } else {
            /*
             Drawable dr = getContext().getResources().getDrawable(R.drawable.indicator_input_error);
                
             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
             setError(error, dr);
             */
            setError(error, null, true, true);
        }
    }

    public void setError(CharSequence error, boolean showError) {
        setError(error, null, showError, true);
    }

    public void setError(CharSequence error, Drawable icon) {
        setError(error, icon, true, true);
    }

    public void setError(CharSequence error, Drawable icon, boolean showError) {
        setError(error, icon, showError, true);
    }

    public void setError(CharSequence error, Drawable icon, boolean showError,
            boolean showCompoundDrawableOnRight) {
        if (DEBUG)
            Log.d(TAG, ".setError(error, icon, showError, showCompoundDrawableOnRight)...");
        if (icon != null) {
            if (DEBUG)
                Log.d(TAG, "...icon is not null...");
            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
        }
        error = TextUtils.stringOrSpannedString(error);
        mErrorWasChanged = true;

        mError = error;
        mErrorWasChanged = true;

        final Drawables dr = mDrawables;
        if (mView instanceof TextView && error != null) {
            if (true || dr != null) {
                if (showCompoundDrawableOnRight) {
                    if (DEBUG)
                        Log.d(TAG, "...showing CompoundDrawable on right)...");
                    //((TextView) mView).setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, dr.mDrawableBottom);
                    ((TextView) mView).setCompoundDrawables(null, null, icon, null);
                } else {
                    //((TextView) mView).setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom);
                    ((TextView) mView).setCompoundDrawables(icon, null, null, null);
                }
            }
        }

        if (error == null) {
            if (mPopup != null) {
                if (mPopup.isShowing()) {
                    mPopup.dismiss();
                }
                if (mView instanceof TextView) {
                    ((TextView) mView).setCompoundDrawables(null, null, null, null);
                }
                mPopup = null;
            }
        } else if (showError) {
            //LD - EditTexts use isFocused to show only the focused one, other Views may not be focusable
            //if (isFocused()) {
            showError();
            //}
        }
    }

    public void showError() {
        if (DEBUG)
            Log.d(TAG, ".showError()...");
        if (mView.getWindowToken() == null) {
            mShowErrorAfterAttach = true;
            return;
        }

        if (mPopup == null) {
            LayoutInflater inflater = LayoutInflater.from(mView.getContext());
            final TextView err = (TextView) inflater.inflate(R.layout.textview_hint, null);
            err.setText(mError);

            final float scale = mContext.getResources().getDisplayMetrics().density;
            mPopup = new ErrorPopup(mContext, err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
            mPopup.setFocusable(false);
            //LD - non EditTexts may not require this:
            // The user is entering text, so the input method is needed.  We
            // don't want the popup to be displayed on top of it.
            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
        }

        if (DEBUG)
            Log.d(TAG, "...error: " + mError);
        TextView tv = (TextView) mPopup.getContentView();
        //tv.setPadding(mErrorPopupPaddingLeft, mErrorPopupPaddingTop, mErrorPopupPaddingRight, mErrorPopupPaddingBottom);
        chooseSize(mPopup, mError, tv);
        tv.setText(mError);

        mPopup.showAsDropDown(mView, getErrorX(), getErrorY());
        mPopup.fixDirection(mPopup.isAboveAnchor());
    }

    public void hideError() {
        if (mPopup != null) {
            if (mPopup.isShowing()) {
                mPopup.dismiss();
            }
        }

        mShowErrorAfterAttach = false;
    }

    private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
        int wid = tv.getPaddingLeft() + tv.getPaddingRight();
        int ht = tv.getPaddingTop() + tv.getPaddingBottom();

        //com.android.internal.R.dimen.textview_error_popup_default_width introduced after Gingerbread, only has one variant (240dip)
        int defaultWidthInPixels = mContext.getResources()
                .getDimensionPixelSize(R.dimen.textview_error_popup_default_width);
        Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels, Layout.Alignment.ALIGN_NORMAL, 1, 0,
                true);

        float max = 0;
        for (int i = 0; i < l.getLineCount(); i++) {
            max = Math.max(max, l.getLineWidth(i));
        }

        if (DEBUG)
            Log.d(TAG, "max: " + max + ", height: " + l.getHeight());

        /*
         * Now set the popup size to be big enough for the text plus the border.
         */
        pop.setWidth(wid + (int) Math.ceil(max));
        pop.setHeight(ht + l.getHeight()); //TODO: buggy (the 2 shouldnt need to be there)
    }

    /**
      * Returns the Y offset to make the pointy top of the error point
      * at the middle of the error icon.
      */
    private int getErrorX() {
        /*
         * The "25" is the distance between the point and the right edge
         * of the background
         */

        final Drawables dr = mDrawables;
        return mView.getWidth() - mPopup.getWidth();// - getPaddingRight() - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 0;
    }

    /**
     * Returns the Y offset to make the pointy top of the error point
     * at the bottom of the error icon.
     */
    private int getErrorY() {
        /*
         * Compound, not extended, because the icon is not clipped
         * if the text height is smaller.
         */
        int vspace = mBottom - mTop - getCompoundPaddingBottom() - getCompoundPaddingTop();

        final Drawables dr = mDrawables;
        int icontop = getCompoundPaddingTop() + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;

        /*
         * The "2" is the distance between the point and the top edge
         * of the background.
         */
        //return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - getHeight() - 2;
        return 0 - mView.getHeight() / 2;
    }

    /**
     * Returns the top padding of the view, plus space for the top
     * Drawable if any.
     */
    public int getCompoundPaddingTop() {
        final Drawables dr = mDrawables;
        if (dr == null || dr.mDrawableTop == null) {
            return mPaddingTop;
        } else {
            return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
        }
    }

    /**
     * Returns the bottom padding of the view, plus space for the bottom
     * Drawable if any.
     */
    public int getCompoundPaddingBottom() {
        final Drawables dr = mDrawables;
        if (dr == null || dr.mDrawableBottom == null) {
            return mPaddingBottom;
        } else {
            return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
        }
    }

    /**
     * Returns the left padding of the view, plus space for the left
     * Drawable if any.
     */
    public int getCompoundPaddingLeft() {
        final Drawables dr = mDrawables;
        if (dr == null || dr.mDrawableLeft == null) {
            return mPaddingLeft;
        } else {
            return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
        }
    }

    /**
     * Returns the right padding of the view, plus space for the right
     * Drawable if any.
     */
    public int getCompoundPaddingRight() {
        final Drawables dr = mDrawables;
        if (dr == null || dr.mDrawableRight == null) {
            return mPaddingRight;
        } else {
            return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
        }
    }

    /**
      * Sets the Drawables (if any) to appear to the left of, above,
      * to the right of, and below the text.  Use null if you do not
      * want a Drawable there.  The Drawables must already have had
      * {@link Drawable#setBounds} called.
      *
      * @attr ref android.R.styleable#TextView_drawableLeft
      * @attr ref android.R.styleable#TextView_drawableTop
      * @attr ref android.R.styleable#TextView_drawableRight
      * @attr ref android.R.styleable#TextView_drawableBottom
      */
    public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
        Drawables dr = mDrawables;

        final boolean drawables = left != null || top != null || right != null || bottom != null;

        if (!drawables) {
            // Clearing drawables...  can we free the data structure?
            if (dr != null) {
                if (dr.mDrawablePadding == 0) {
                    mDrawables = null;
                } else {
                    // We need to retain the last set padding, so just clear
                    // out all of the fields in the existing structure.
                    dr.mDrawableLeft = null;
                    dr.mDrawableTop = null;
                    dr.mDrawableRight = null;
                    dr.mDrawableBottom = null;
                    dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
                    dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
                    dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
                    dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
                }
            }
        } else {
            if (dr == null) {
                mDrawables = dr = new Drawables();
            }

            dr.mDrawableLeft = left;
            dr.mDrawableTop = top;
            dr.mDrawableRight = right;
            dr.mDrawableBottom = bottom;

            final Rect compoundRect = dr.mCompoundRect;
            int[] state = null;

            state = mView.getDrawableState();

            if (left != null) {
                left.setState(state);
                left.copyBounds(compoundRect);
                dr.mDrawableSizeLeft = compoundRect.width();
                dr.mDrawableHeightLeft = compoundRect.height();
            } else {
                dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
            }

            if (right != null) {
                right.setState(state);
                right.copyBounds(compoundRect);
                dr.mDrawableSizeRight = compoundRect.width();
                dr.mDrawableHeightRight = compoundRect.height();
            } else {
                dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
            }

            if (top != null) {
                top.setState(state);
                top.copyBounds(compoundRect);
                dr.mDrawableSizeTop = compoundRect.height();
                dr.mDrawableWidthTop = compoundRect.width();
            } else {
                dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
            }

            if (bottom != null) {
                bottom.setState(state);
                bottom.copyBounds(compoundRect);
                dr.mDrawableSizeBottom = compoundRect.height();
                dr.mDrawableWidthBottom = compoundRect.width();
            } else {
                dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
            }
        }

        mView.invalidate();
        mView.requestLayout();
    }

    public void hideErrorIfUnchanged() {
        if (mError != null && !mErrorWasChanged) {
            setError(null, null);
        }
    }

    /**
     * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
     * can be recorded.
     * @hide
     */
    public void resetErrorChangedFlag() {
        /*
         * Keep track of what the error was before doing the input
         * so that if an input filter changed the error, we leave
         * that error showing.  Otherwise, we take down whatever
         * error was showing when the user types something.
         */
        mErrorWasChanged = false;
    }

    /*
     * INNER CLASSES
     */

    /**
     * Ripped from android.Widget.TextView.
     * Modified to utilise end-developer-App's local resources.
     *
     */
    private static class ErrorPopup extends PopupWindow {
        private boolean mAbove = false;
        private TextView mView;
        private int mPopupInlineErrorBackgroundId = 0;
        private int mPopupInlineErrorAboveBackgroundId = 0;

        ErrorPopup(Context mContext, TextView v, int width, int height) {
            super(v, width, height);
            mView = v;
            // Make sure the TextView has a background set as it will be used the first time it is
            // shown and positioned. Initialised with below background, which should have
            // dimensions identical to the above version for this to work (and is more likely).
            //following is a tweak on the ICS take...
            TypedArray array = mContext.obtainStyledAttributes(R.styleable.Theme);
            mPopupInlineErrorBackgroundId = array.getResourceId(R.styleable.Theme_errorMessageBackground, 0);
            mPopupInlineErrorAboveBackgroundId = array.getResourceId(R.styleable.Theme_errorMessageAboveBackground,
                    0);
            //if(DEBUG) Log.d(TAG, "popupInlineErrorBackgroundId: "+mPopupInlineErrorBackgroundId);
            //if(DEBUG) Log.d(TAG, "popupInlineErrorAboveBackgroundId: "+mPopupInlineErrorAboveBackgroundId);
            mView.setBackgroundResource(mPopupInlineErrorBackgroundId);

        }

        void fixDirection(boolean above) {
            mAbove = above;

            if (above) {
                mView.setBackgroundResource(mPopupInlineErrorAboveBackgroundId);
            } else {
                mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
            }
        }

        @Override
        public void update(int x, int y, int w, int h, boolean force) {
            super.update(x, y, w, h, force);

            boolean above = isAboveAnchor();
            if (above != mAbove) {
                fixDirection(above);
            }
        }
    }
}