Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / android-libraries / ActionBarSherlock / src / com / actionbarsherlock / internal / widget / IcsSpinner.java
diff --git a/android-libraries/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsSpinner.java b/android-libraries/ActionBarSherlock/src/com/actionbarsherlock/internal/widget/IcsSpinner.java
new file mode 100644 (file)
index 0000000..038d1e0
--- /dev/null
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2007 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 static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import com.actionbarsherlock.R;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.SpinnerAdapter;
+
+
+/**
+ * A view that displays one child at a time and lets the user pick among them.
+ * The items in the Spinner come from the {@link Adapter} associated with
+ * this view.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
+ * tutorial</a>.</p>
+ *
+ * @attr ref android.R.styleable#Spinner_prompt
+ */
+public class IcsSpinner extends IcsAbsSpinner implements OnClickListener {
+    //private static final String TAG = "Spinner";
+
+    // Only measure this many items to get a decent max width.
+    private static final int MAX_ITEMS_MEASURED = 15;
+
+    /**
+     * Use a dialog window for selecting spinner options.
+     */
+    //public static final int MODE_DIALOG = 0;
+
+    /**
+     * Use a dropdown anchored to the Spinner for selecting spinner options.
+     */
+    public static final int MODE_DROPDOWN = 1;
+
+    /**
+     * Use the theme-supplied value to select the dropdown mode.
+     */
+    //private static final int MODE_THEME = -1;
+
+    private SpinnerPopup mPopup;
+    private DropDownAdapter mTempAdapter;
+    int mDropDownWidth;
+
+    private int mGravity;
+    private boolean mDisableChildrenWhenDisabled;
+
+    private Rect mTempRect = new Rect();
+
+    public IcsSpinner(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.actionDropDownStyle);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme, the supplied attribute set,
+     * and default style.
+     *
+     * @param context The Context the view is running in, through which it can
+     *        access the current theme, resources, etc.
+     * @param attrs The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style
+     *        will be applied (beyond what is included in the theme). This may
+     *        either be an attribute resource, whose value will be retrieved
+     *        from the current theme, or an explicit style resource.
+     */
+    public IcsSpinner(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.SherlockSpinner, defStyle, 0);
+
+
+        DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
+
+        mDropDownWidth = a.getLayoutDimension(
+                R.styleable.SherlockSpinner_android_dropDownWidth,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        popup.setBackgroundDrawable(a.getDrawable(
+                R.styleable.SherlockSpinner_android_popupBackground));
+        final int verticalOffset = a.getDimensionPixelOffset(
+                R.styleable.SherlockSpinner_android_dropDownVerticalOffset, 0);
+        if (verticalOffset != 0) {
+            popup.setVerticalOffset(verticalOffset);
+        }
+
+        final int horizontalOffset = a.getDimensionPixelOffset(
+                R.styleable.SherlockSpinner_android_dropDownHorizontalOffset, 0);
+        if (horizontalOffset != 0) {
+            popup.setHorizontalOffset(horizontalOffset);
+        }
+
+        mPopup = popup;
+
+        mGravity = a.getInt(R.styleable.SherlockSpinner_android_gravity, Gravity.CENTER);
+
+        mPopup.setPromptText(a.getString(R.styleable.SherlockSpinner_android_prompt));
+
+        mDisableChildrenWhenDisabled = true;
+
+        a.recycle();
+
+        // Base constructor can call setAdapter before we initialize mPopup.
+        // Finish setting things up if this happened.
+        if (mTempAdapter != null) {
+            mPopup.setAdapter(mTempAdapter);
+            mTempAdapter = null;
+        }
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        if (mDisableChildrenWhenDisabled) {
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                getChildAt(i).setEnabled(enabled);
+            }
+        }
+    }
+
+    /**
+     * Describes how the selected item view is positioned. Currently only the horizontal component
+     * is used. The default is determined by the current theme.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     *
+     * @attr ref android.R.styleable#Spinner_gravity
+     */
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.LEFT;
+            }
+            mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    @Override
+    public void setAdapter(SpinnerAdapter adapter) {
+        super.setAdapter(adapter);
+
+        if (mPopup != null) {
+            mPopup.setAdapter(new DropDownAdapter(adapter));
+        } else {
+            mTempAdapter = new DropDownAdapter(adapter);
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        View child = null;
+
+        if (getChildCount() > 0) {
+            child = getChildAt(0);
+        } else if (mAdapter != null && mAdapter.getCount() > 0) {
+            child = makeAndAddView(0);
+            mRecycler.put(0, child);
+            removeAllViewsInLayout();
+        }
+
+        if (child != null) {
+            final int childBaseline = child.getBaseline();
+            return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mPopup != null && mPopup.isShowing()) {
+            mPopup.dismiss();
+        }
+    }
+
+    /**
+     * <p>A spinner does not support item click events. Calling this method
+     * will raise an exception.</p>
+     *
+     * @param l this listener will be ignored
+     */
+    @Override
+    public void setOnItemClickListener(OnItemClickListener l) {
+        throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
+            final int measuredWidth = getMeasuredWidth();
+            setMeasuredDimension(Math.min(Math.max(measuredWidth,
+                    measureContentWidth(getAdapter(), getBackground())),
+                    MeasureSpec.getSize(widthMeasureSpec)),
+                    getMeasuredHeight());
+        }
+    }
+
+    /**
+     * @see android.view.View#onLayout(boolean,int,int,int,int)
+     *
+     * Creates and positions all views
+     *
+     */
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mInLayout = true;
+        layout(0, false);
+        mInLayout = false;
+    }
+
+    /**
+     * Creates and positions all views for this Spinner.
+     *
+     * @param delta Change in the selected position. +1 moves selection is moving to the right,
+     * so views are scrolling to the left. -1 means selection is moving to the left.
+     */
+    @Override
+    void layout(int delta, boolean animate) {
+        int childrenLeft = mSpinnerPadding.left;
+        int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+
+        // Handle the empty set by removing all views
+        if (mItemCount == 0) {
+            resetList();
+            return;
+        }
+
+        if (mNextSelectedPosition >= 0) {
+            setSelectedPositionInt(mNextSelectedPosition);
+        }
+
+        recycleAllViews();
+
+        // Clear out old views
+        removeAllViewsInLayout();
+
+        // Make selected view and position it
+        mFirstPosition = mSelectedPosition;
+        View sel = makeAndAddView(mSelectedPosition);
+        int width = sel.getMeasuredWidth();
+        int selectedOffset = childrenLeft;
+        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+            case Gravity.CENTER_HORIZONTAL:
+                selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
+                break;
+            case Gravity.RIGHT:
+                selectedOffset = childrenLeft + childrenWidth - width;
+                break;
+        }
+        sel.offsetLeftAndRight(selectedOffset);
+
+        // Flush any cached views that did not get reused above
+        mRecycler.clear();
+
+        invalidate();
+
+        checkSelectionChanged();
+
+        mDataChanged = false;
+        mNeedSync = false;
+        setNextSelectedPositionInt(mSelectedPosition);
+    }
+
+    /**
+     * Obtain a view, either by pulling an existing view from the recycler or
+     * by getting a new one from the adapter. If we are animating, make sure
+     * there is enough information in the view's layout parameters to animate
+     * from the old to new positions.
+     *
+     * @param position Position in the spinner for the view to obtain
+     * @return A view that has been added to the spinner
+     */
+    private View makeAndAddView(int position) {
+
+        View child;
+
+        if (!mDataChanged) {
+            child = mRecycler.get(position);
+            if (child != null) {
+                // Position the view
+                setUpChild(child);
+
+                return child;
+            }
+        }
+
+        // Nothing found in the recycler -- ask the adapter for a view
+        child = mAdapter.getView(position, null, this);
+
+        // Position the view
+        setUpChild(child);
+
+        return child;
+    }
+
+    /**
+     * Helper for makeAndAddView to set the position of a view
+     * and fill out its layout paramters.
+     *
+     * @param child The view to position
+     */
+    private void setUpChild(View child) {
+
+        // Respect layout params that are already in the view. Otherwise
+        // make some up...
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+        if (lp == null) {
+            lp = generateDefaultLayoutParams();
+        }
+
+        addViewInLayout(child, 0, lp);
+
+        child.setSelected(hasFocus());
+        if (mDisableChildrenWhenDisabled) {
+            child.setEnabled(isEnabled());
+        }
+
+        // Get measure specs
+        int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
+                mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
+        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+                mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
+
+        // Measure child
+        child.measure(childWidthSpec, childHeightSpec);
+
+        int childLeft;
+        int childRight;
+
+        // Position vertically based on gravity setting
+        int childTop = mSpinnerPadding.top
+                + ((getMeasuredHeight() - mSpinnerPadding.bottom -
+                        mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
+        int childBottom = childTop + child.getMeasuredHeight();
+
+        int width = child.getMeasuredWidth();
+        childLeft = 0;
+        childRight = childLeft + width;
+
+        child.layout(childLeft, childTop, childRight, childBottom);
+    }
+
+    @Override
+    public boolean performClick() {
+        boolean handled = super.performClick();
+
+        if (!handled) {
+            handled = true;
+
+            if (!mPopup.isShowing()) {
+                mPopup.show();
+            }
+        }
+
+        return handled;
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        setSelection(which);
+        dialog.dismiss();
+    }
+
+    /**
+     * Sets the prompt to display when the dialog is shown.
+     * @param prompt the prompt to set
+     */
+    public void setPrompt(CharSequence prompt) {
+        mPopup.setPromptText(prompt);
+    }
+
+    /**
+     * Sets the prompt to display when the dialog is shown.
+     * @param promptId the resource ID of the prompt to display when the dialog is shown
+     */
+    public void setPromptId(int promptId) {
+        setPrompt(getContext().getText(promptId));
+    }
+
+    /**
+     * @return The prompt to display when the dialog is shown
+     */
+    public CharSequence getPrompt() {
+        return mPopup.getHintText();
+    }
+
+    int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
+        if (adapter == null) {
+            return 0;
+        }
+
+        int width = 0;
+        View itemView = null;
+        int itemType = 0;
+        final int widthMeasureSpec =
+            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int heightMeasureSpec =
+            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+        // Make sure the number of items we'll measure is capped. If it's a huge data set
+        // with wildly varying sizes, oh well.
+        int start = Math.max(0, getSelectedItemPosition());
+        final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
+        final int count = end - start;
+        start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
+        for (int i = start; i < end; i++) {
+            final int positionType = adapter.getItemViewType(i);
+            if (positionType != itemType) {
+                itemType = positionType;
+                itemView = null;
+            }
+            itemView = adapter.getView(i, itemView, this);
+            if (itemView.getLayoutParams() == null) {
+                itemView.setLayoutParams(new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
+            }
+            itemView.measure(widthMeasureSpec, heightMeasureSpec);
+            width = Math.max(width, itemView.getMeasuredWidth());
+        }
+
+        // Add background padding to measured width
+        if (background != null) {
+            background.getPadding(mTempRect);
+            width += mTempRect.left + mTempRect.right;
+        }
+
+        return width;
+    }
+
+    /**
+     * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
+     * into a ListAdapter.</p>
+     */
+    private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
+        private SpinnerAdapter mAdapter;
+        private ListAdapter mListAdapter;
+
+        /**
+         * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
+         *
+         * @param adapter the Adapter to transform into a ListAdapter
+         */
+        public DropDownAdapter(SpinnerAdapter adapter) {
+            this.mAdapter = adapter;
+            if (adapter instanceof ListAdapter) {
+                this.mListAdapter = (ListAdapter) adapter;
+            }
+        }
+
+        public int getCount() {
+            return mAdapter == null ? 0 : mAdapter.getCount();
+        }
+
+        public Object getItem(int position) {
+            return mAdapter == null ? null : mAdapter.getItem(position);
+        }
+
+        public long getItemId(int position) {
+            return mAdapter == null ? -1 : mAdapter.getItemId(position);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return getDropDownView(position, convertView, parent);
+        }
+
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            return mAdapter == null ? null :
+                    mAdapter.getDropDownView(position, convertView, parent);
+        }
+
+        public boolean hasStableIds() {
+            return mAdapter != null && mAdapter.hasStableIds();
+        }
+
+        public void registerDataSetObserver(DataSetObserver observer) {
+            if (mAdapter != null) {
+                mAdapter.registerDataSetObserver(observer);
+            }
+        }
+
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            if (mAdapter != null) {
+                mAdapter.unregisterDataSetObserver(observer);
+            }
+        }
+
+        /**
+         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
+         * Otherwise, return true.
+         */
+        public boolean areAllItemsEnabled() {
+            final ListAdapter adapter = mListAdapter;
+            if (adapter != null) {
+                return adapter.areAllItemsEnabled();
+            } else {
+                return true;
+            }
+        }
+
+        /**
+         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
+         * Otherwise, return true.
+         */
+        public boolean isEnabled(int position) {
+            final ListAdapter adapter = mListAdapter;
+            if (adapter != null) {
+                return adapter.isEnabled(position);
+            } else {
+                return true;
+            }
+        }
+
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        public boolean isEmpty() {
+            return getCount() == 0;
+        }
+    }
+
+    /**
+     * Implements some sort of popup selection interface for selecting a spinner option.
+     * Allows for different spinner modes.
+     */
+    private interface SpinnerPopup {
+        public void setAdapter(ListAdapter adapter);
+
+        /**
+         * Show the popup
+         */
+        public void show();
+
+        /**
+         * Dismiss the popup
+         */
+        public void dismiss();
+
+        /**
+         * @return true if the popup is showing, false otherwise.
+         */
+        public boolean isShowing();
+
+        /**
+         * Set hint text to be displayed to the user. This should provide
+         * a description of the choice being made.
+         * @param hintText Hint text to set.
+         */
+        public void setPromptText(CharSequence hintText);
+        public CharSequence getHintText();
+    }
+
+    /*
+    private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
+        private AlertDialog mPopup;
+        private ListAdapter mListAdapter;
+        private CharSequence mPrompt;
+
+        public void dismiss() {
+            mPopup.dismiss();
+            mPopup = null;
+        }
+
+        public boolean isShowing() {
+            return mPopup != null ? mPopup.isShowing() : false;
+        }
+
+        public void setAdapter(ListAdapter adapter) {
+            mListAdapter = adapter;
+        }
+
+        public void setPromptText(CharSequence hintText) {
+            mPrompt = hintText;
+        }
+
+        public CharSequence getHintText() {
+            return mPrompt;
+        }
+
+        public void show() {
+            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+            if (mPrompt != null) {
+                builder.setTitle(mPrompt);
+            }
+            mPopup = builder.setSingleChoiceItems(mListAdapter,
+                    getSelectedItemPosition(), this).show();
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            setSelection(which);
+            dismiss();
+        }
+    }
+    */
+
+    private class DropdownPopup extends IcsListPopupWindow implements SpinnerPopup {
+        private CharSequence mHintText;
+        private ListAdapter mAdapter;
+
+        public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) {
+            super(context, attrs, 0, defStyleRes);
+
+            setAnchorView(IcsSpinner.this);
+            setModal(true);
+            setPromptPosition(POSITION_PROMPT_ABOVE);
+            setOnItemClickListener(new OnItemClickListener() {
+                @SuppressWarnings("rawtypes")
+                public void onItemClick(AdapterView parent, View v, int position, long id) {
+                    IcsSpinner.this.setSelection(position);
+                    dismiss();
+                }
+            });
+        }
+
+        @Override
+        public void setAdapter(ListAdapter adapter) {
+            super.setAdapter(adapter);
+            mAdapter = adapter;
+        }
+
+        public CharSequence getHintText() {
+            return mHintText;
+        }
+
+        public void setPromptText(CharSequence hintText) {
+            // Hint text is ignored for dropdowns, but maintain it here.
+            mHintText = hintText;
+        }
+
+        @Override
+        public void show() {
+            final int spinnerPaddingLeft = IcsSpinner.this.getPaddingLeft();
+            if (mDropDownWidth == WRAP_CONTENT) {
+                final int spinnerWidth = IcsSpinner.this.getWidth();
+                final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight();
+                setContentWidth(Math.max(
+                        measureContentWidth((SpinnerAdapter) mAdapter, getBackground()),
+                        spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
+            } else if (mDropDownWidth == MATCH_PARENT) {
+                final int spinnerWidth = IcsSpinner.this.getWidth();
+                final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight();
+                setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
+            } else {
+                setContentWidth(mDropDownWidth);
+            }
+            final Drawable background = getBackground();
+            int bgOffset = 0;
+            if (background != null) {
+                background.getPadding(mTempRect);
+                bgOffset = -mTempRect.left;
+            }
+            setHorizontalOffset(bgOffset + spinnerPaddingLeft);
+            setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+            super.show();
+            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            setSelection(IcsSpinner.this.getSelectedItemPosition());
+        }
+    }
+}