Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / android-libraries / ActionBarSherlock / src / com / actionbarsherlock / internal / widget / IcsAbsSpinner.java
diff --git a/android-libraries/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsAbsSpinner.java b/android-libraries/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsAbsSpinner.java
new file mode 100644 (file)
index 0000000..ce0cb3b
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * 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.actionbarsherlock.internal.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SpinnerAdapter;
+
+/**
+ * An abstract base class for spinner widgets. SDK users will probably not
+ * need to use this class.
+ *
+ * @attr ref android.R.styleable#AbsSpinner_entries
+ */
+public abstract class IcsAbsSpinner extends IcsAdapterView<SpinnerAdapter> {
+    private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+
+    SpinnerAdapter mAdapter;
+
+    int mHeightMeasureSpec;
+    int mWidthMeasureSpec;
+    boolean mBlockLayoutRequests;
+
+    int mSelectionLeftPadding = 0;
+    int mSelectionTopPadding = 0;
+    int mSelectionRightPadding = 0;
+    int mSelectionBottomPadding = 0;
+    final Rect mSpinnerPadding = new Rect();
+
+    final RecycleBin mRecycler = new RecycleBin();
+    private DataSetObserver mDataSetObserver;
+
+    /** Temporary frame to hold a child View's frame rectangle */
+    private Rect mTouchFrame;
+
+    public IcsAbsSpinner(Context context) {
+        super(context);
+        initAbsSpinner();
+    }
+
+    public IcsAbsSpinner(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public IcsAbsSpinner(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initAbsSpinner();
+
+        /*
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
+
+        CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
+        if (entries != null) {
+            ArrayAdapter<CharSequence> adapter =
+                    new ArrayAdapter<CharSequence>(context,
+                            R.layout.simple_spinner_item, entries);
+            adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
+            setAdapter(adapter);
+        }
+
+        a.recycle();
+        */
+    }
+
+    /**
+     * Common code for different constructor flavors
+     */
+    private void initAbsSpinner() {
+        setFocusable(true);
+        setWillNotDraw(false);
+    }
+
+    /**
+     * The Adapter is used to provide the data which backs this Spinner.
+     * It also provides methods to transform spinner items based on their position
+     * relative to the selected item.
+     * @param adapter The SpinnerAdapter to use for this Spinner
+     */
+    @Override
+    public void setAdapter(SpinnerAdapter adapter) {
+        if (null != mAdapter) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+            resetList();
+        }
+
+        mAdapter = adapter;
+
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+
+        if (mAdapter != null) {
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
+            checkFocus();
+
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+
+            int position = mItemCount > 0 ? 0 : INVALID_POSITION;
+
+            setSelectedPositionInt(position);
+            setNextSelectedPositionInt(position);
+
+            if (mItemCount == 0) {
+                // Nothing selected
+                checkSelectionChanged();
+            }
+
+        } else {
+            checkFocus();
+            resetList();
+            // Nothing selected
+            checkSelectionChanged();
+        }
+
+        requestLayout();
+    }
+
+    /**
+     * Clear out all children from the list
+     */
+    void resetList() {
+        mDataChanged = false;
+        mNeedSync = false;
+
+        removeAllViewsInLayout();
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+
+        setSelectedPositionInt(INVALID_POSITION);
+        setNextSelectedPositionInt(INVALID_POSITION);
+        invalidate();
+    }
+
+    /**
+     * @see android.view.View#measure(int, int)
+     *
+     * Figure out the dimensions of this Spinner. The width comes from
+     * the widthMeasureSpec as Spinnners can't have their width set to
+     * UNSPECIFIED. The height is based on the height of the selected item
+     * plus padding.
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize;
+        int heightSize;
+
+        final int mPaddingLeft = getPaddingLeft();
+        final int mPaddingTop = getPaddingTop();
+        final int mPaddingRight = getPaddingRight();
+        final int mPaddingBottom = getPaddingBottom();
+
+        mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
+                : mSelectionLeftPadding;
+        mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
+                : mSelectionTopPadding;
+        mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
+                : mSelectionRightPadding;
+        mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
+                : mSelectionBottomPadding;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+
+        int preferredHeight = 0;
+        int preferredWidth = 0;
+        boolean needsMeasuring = true;
+
+        int selectedPosition = getSelectedItemPosition();
+        if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
+            // Try looking in the recycler. (Maybe we were measured once already)
+            View view = mRecycler.get(selectedPosition);
+            if (view == null) {
+                // Make a new one
+                view = mAdapter.getView(selectedPosition, null, this);
+            }
+
+            if (view != null) {
+                // Put in recycler for re-measuring and/or layout
+                mRecycler.put(selectedPosition, view);
+            }
+
+            if (view != null) {
+                if (view.getLayoutParams() == null) {
+                    mBlockLayoutRequests = true;
+                    view.setLayoutParams(generateDefaultLayoutParams());
+                    mBlockLayoutRequests = false;
+                }
+                measureChild(view, widthMeasureSpec, heightMeasureSpec);
+
+                preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
+                preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
+
+                needsMeasuring = false;
+            }
+        }
+
+        if (needsMeasuring) {
+            // No views -- just use padding
+            preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
+            if (widthMode == MeasureSpec.UNSPECIFIED) {
+                preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
+            }
+        }
+
+        preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
+        preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
+
+        if (IS_HONEYCOMB) {
+            heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
+            widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
+        } else {
+            heightSize = resolveSize(preferredHeight, heightMeasureSpec);
+            widthSize = resolveSize(preferredWidth, widthMeasureSpec);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+        mHeightMeasureSpec = heightMeasureSpec;
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    int getChildHeight(View child) {
+        return child.getMeasuredHeight();
+    }
+
+    int getChildWidth(View child) {
+        return child.getMeasuredWidth();
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    void recycleAllViews() {
+        final int childCount = getChildCount();
+        final IcsAbsSpinner.RecycleBin recycleBin = mRecycler;
+        final int position = mFirstPosition;
+
+        // All views go in recycler
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            int index = position + i;
+            recycleBin.put(index, v);
+        }
+    }
+
+    /**
+     * Jump directly to a specific item in the adapter data.
+     */
+    public void setSelection(int position, boolean animate) {
+        // Animate only if requested position is already on screen somewhere
+        boolean shouldAnimate = animate && mFirstPosition <= position &&
+                position <= mFirstPosition + getChildCount() - 1;
+        setSelectionInt(position, shouldAnimate);
+    }
+
+    @Override
+    public void setSelection(int position) {
+        setNextSelectedPositionInt(position);
+        requestLayout();
+        invalidate();
+    }
+
+
+    /**
+     * Makes the item at the supplied position selected.
+     *
+     * @param position Position to select
+     * @param animate Should the transition be animated
+     *
+     */
+    void setSelectionInt(int position, boolean animate) {
+        if (position != mOldSelectedPosition) {
+            mBlockLayoutRequests = true;
+            int delta  = position - mSelectedPosition;
+            setNextSelectedPositionInt(position);
+            layout(delta, animate);
+            mBlockLayoutRequests = false;
+        }
+    }
+
+    abstract void layout(int delta, boolean animate);
+
+    @Override
+    public View getSelectedView() {
+        if (mItemCount > 0 && mSelectedPosition >= 0) {
+            return getChildAt(mSelectedPosition - mFirstPosition);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Override to prevent spamming ourselves with layout requests
+     * as we place views
+     *
+     * @see android.view.View#requestLayout()
+     */
+    @Override
+    public void requestLayout() {
+        if (!mBlockLayoutRequests) {
+            super.requestLayout();
+        }
+    }
+
+    @Override
+    public SpinnerAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    @Override
+    public int getCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Maps a point to a position in the list.
+     *
+     * @param x X in local coordinate
+     * @param y Y in local coordinate
+     * @return The position of the item which contains the specified point, or
+     *         {@link #INVALID_POSITION} if the point does not intersect an item.
+     */
+    public int pointToPosition(int x, int y) {
+        Rect frame = mTouchFrame;
+        if (frame == null) {
+            mTouchFrame = new Rect();
+            frame = mTouchFrame;
+        }
+
+        final int count = getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                child.getHitRect(frame);
+                if (frame.contains(x, y)) {
+                    return mFirstPosition + i;
+                }
+            }
+        }
+        return INVALID_POSITION;
+    }
+
+    static class SavedState extends BaseSavedState {
+        long selectedId;
+        int position;
+
+        /**
+         * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            selectedId = in.readLong();
+            position = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeLong(selectedId);
+            out.writeInt(position);
+        }
+
+        @Override
+        public String toString() {
+            return "AbsSpinner.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " selectedId=" + selectedId
+                    + " position=" + position + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.selectedId = getSelectedItemId();
+        if (ss.selectedId >= 0) {
+            ss.position = getSelectedItemPosition();
+        } else {
+            ss.position = INVALID_POSITION;
+        }
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.selectedId >= 0) {
+            mDataChanged = true;
+            mNeedSync = true;
+            mSyncRowId = ss.selectedId;
+            mSyncPosition = ss.position;
+            mSyncMode = SYNC_SELECTED_POSITION;
+            requestLayout();
+        }
+    }
+
+    class RecycleBin {
+        private final SparseArray<View> mScrapHeap = new SparseArray<View>();
+
+        public void put(int position, View v) {
+            mScrapHeap.put(position, v);
+        }
+
+        View get(int position) {
+            // System.out.print("Looking for " + position);
+            View result = mScrapHeap.get(position);
+            if (result != null) {
+                // System.out.println(" HIT");
+                mScrapHeap.delete(position);
+            } else {
+                // System.out.println(" MISS");
+            }
+            return result;
+        }
+
+        void clear() {
+            final SparseArray<View> scrapHeap = mScrapHeap;
+            final int count = scrapHeap.size();
+            for (int i = 0; i < count; i++) {
+                final View view = scrapHeap.valueAt(i);
+                if (view != null) {
+                    removeDetachedView(view, true);
+                }
+            }
+            scrapHeap.clear();
+        }
+    }
+}