content: /*
   * 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();
          }
      }
  }
  