create changelog entry
[debian/openrocket] / android-libraries / ActionBarSherlock / src / com / actionbarsherlock / internal / widget / IcsAbsSpinner.java
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.actionbarsherlock.internal.widget;
18
19 import android.content.Context;
20 import android.database.DataSetObserver;
21 import android.graphics.Rect;
22 import android.os.Build;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.util.AttributeSet;
26 import android.util.SparseArray;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.SpinnerAdapter;
30
31 /**
32  * An abstract base class for spinner widgets. SDK users will probably not
33  * need to use this class.
34  *
35  * @attr ref android.R.styleable#AbsSpinner_entries
36  */
37 public abstract class IcsAbsSpinner extends IcsAdapterView<SpinnerAdapter> {
38     private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
39
40     SpinnerAdapter mAdapter;
41
42     int mHeightMeasureSpec;
43     int mWidthMeasureSpec;
44     boolean mBlockLayoutRequests;
45
46     int mSelectionLeftPadding = 0;
47     int mSelectionTopPadding = 0;
48     int mSelectionRightPadding = 0;
49     int mSelectionBottomPadding = 0;
50     final Rect mSpinnerPadding = new Rect();
51
52     final RecycleBin mRecycler = new RecycleBin();
53     private DataSetObserver mDataSetObserver;
54
55     /** Temporary frame to hold a child View's frame rectangle */
56     private Rect mTouchFrame;
57
58     public IcsAbsSpinner(Context context) {
59         super(context);
60         initAbsSpinner();
61     }
62
63     public IcsAbsSpinner(Context context, AttributeSet attrs) {
64         this(context, attrs, 0);
65     }
66
67     public IcsAbsSpinner(Context context, AttributeSet attrs, int defStyle) {
68         super(context, attrs, defStyle);
69         initAbsSpinner();
70
71         /*
72         TypedArray a = context.obtainStyledAttributes(attrs,
73                 com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
74
75         CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
76         if (entries != null) {
77             ArrayAdapter<CharSequence> adapter =
78                     new ArrayAdapter<CharSequence>(context,
79                             R.layout.simple_spinner_item, entries);
80             adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
81             setAdapter(adapter);
82         }
83
84         a.recycle();
85         */
86     }
87
88     /**
89      * Common code for different constructor flavors
90      */
91     private void initAbsSpinner() {
92         setFocusable(true);
93         setWillNotDraw(false);
94     }
95
96     /**
97      * The Adapter is used to provide the data which backs this Spinner.
98      * It also provides methods to transform spinner items based on their position
99      * relative to the selected item.
100      * @param adapter The SpinnerAdapter to use for this Spinner
101      */
102     @Override
103     public void setAdapter(SpinnerAdapter adapter) {
104         if (null != mAdapter) {
105             mAdapter.unregisterDataSetObserver(mDataSetObserver);
106             resetList();
107         }
108
109         mAdapter = adapter;
110
111         mOldSelectedPosition = INVALID_POSITION;
112         mOldSelectedRowId = INVALID_ROW_ID;
113
114         if (mAdapter != null) {
115             mOldItemCount = mItemCount;
116             mItemCount = mAdapter.getCount();
117             checkFocus();
118
119             mDataSetObserver = new AdapterDataSetObserver();
120             mAdapter.registerDataSetObserver(mDataSetObserver);
121
122             int position = mItemCount > 0 ? 0 : INVALID_POSITION;
123
124             setSelectedPositionInt(position);
125             setNextSelectedPositionInt(position);
126
127             if (mItemCount == 0) {
128                 // Nothing selected
129                 checkSelectionChanged();
130             }
131
132         } else {
133             checkFocus();
134             resetList();
135             // Nothing selected
136             checkSelectionChanged();
137         }
138
139         requestLayout();
140     }
141
142     /**
143      * Clear out all children from the list
144      */
145     void resetList() {
146         mDataChanged = false;
147         mNeedSync = false;
148
149         removeAllViewsInLayout();
150         mOldSelectedPosition = INVALID_POSITION;
151         mOldSelectedRowId = INVALID_ROW_ID;
152
153         setSelectedPositionInt(INVALID_POSITION);
154         setNextSelectedPositionInt(INVALID_POSITION);
155         invalidate();
156     }
157
158     /**
159      * @see android.view.View#measure(int, int)
160      *
161      * Figure out the dimensions of this Spinner. The width comes from
162      * the widthMeasureSpec as Spinnners can't have their width set to
163      * UNSPECIFIED. The height is based on the height of the selected item
164      * plus padding.
165      */
166     @Override
167     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
168         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
169         int widthSize;
170         int heightSize;
171
172         final int mPaddingLeft = getPaddingLeft();
173         final int mPaddingTop = getPaddingTop();
174         final int mPaddingRight = getPaddingRight();
175         final int mPaddingBottom = getPaddingBottom();
176
177         mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
178                 : mSelectionLeftPadding;
179         mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
180                 : mSelectionTopPadding;
181         mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
182                 : mSelectionRightPadding;
183         mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
184                 : mSelectionBottomPadding;
185
186         if (mDataChanged) {
187             handleDataChanged();
188         }
189
190         int preferredHeight = 0;
191         int preferredWidth = 0;
192         boolean needsMeasuring = true;
193
194         int selectedPosition = getSelectedItemPosition();
195         if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
196             // Try looking in the recycler. (Maybe we were measured once already)
197             View view = mRecycler.get(selectedPosition);
198             if (view == null) {
199                 // Make a new one
200                 view = mAdapter.getView(selectedPosition, null, this);
201             }
202
203             if (view != null) {
204                 // Put in recycler for re-measuring and/or layout
205                 mRecycler.put(selectedPosition, view);
206             }
207
208             if (view != null) {
209                 if (view.getLayoutParams() == null) {
210                     mBlockLayoutRequests = true;
211                     view.setLayoutParams(generateDefaultLayoutParams());
212                     mBlockLayoutRequests = false;
213                 }
214                 measureChild(view, widthMeasureSpec, heightMeasureSpec);
215
216                 preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
217                 preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
218
219                 needsMeasuring = false;
220             }
221         }
222
223         if (needsMeasuring) {
224             // No views -- just use padding
225             preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
226             if (widthMode == MeasureSpec.UNSPECIFIED) {
227                 preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
228             }
229         }
230
231         preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
232         preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
233
234         if (IS_HONEYCOMB) {
235             heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
236             widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
237         } else {
238             heightSize = resolveSize(preferredHeight, heightMeasureSpec);
239             widthSize = resolveSize(preferredWidth, widthMeasureSpec);
240         }
241
242         setMeasuredDimension(widthSize, heightSize);
243         mHeightMeasureSpec = heightMeasureSpec;
244         mWidthMeasureSpec = widthMeasureSpec;
245     }
246
247     int getChildHeight(View child) {
248         return child.getMeasuredHeight();
249     }
250
251     int getChildWidth(View child) {
252         return child.getMeasuredWidth();
253     }
254
255     @Override
256     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
257         return new ViewGroup.LayoutParams(
258                 ViewGroup.LayoutParams.MATCH_PARENT,
259                 ViewGroup.LayoutParams.WRAP_CONTENT);
260     }
261
262     void recycleAllViews() {
263         final int childCount = getChildCount();
264         final IcsAbsSpinner.RecycleBin recycleBin = mRecycler;
265         final int position = mFirstPosition;
266
267         // All views go in recycler
268         for (int i = 0; i < childCount; i++) {
269             View v = getChildAt(i);
270             int index = position + i;
271             recycleBin.put(index, v);
272         }
273     }
274
275     /**
276      * Jump directly to a specific item in the adapter data.
277      */
278     public void setSelection(int position, boolean animate) {
279         // Animate only if requested position is already on screen somewhere
280         boolean shouldAnimate = animate && mFirstPosition <= position &&
281                 position <= mFirstPosition + getChildCount() - 1;
282         setSelectionInt(position, shouldAnimate);
283     }
284
285     @Override
286     public void setSelection(int position) {
287         setNextSelectedPositionInt(position);
288         requestLayout();
289         invalidate();
290     }
291
292
293     /**
294      * Makes the item at the supplied position selected.
295      *
296      * @param position Position to select
297      * @param animate Should the transition be animated
298      *
299      */
300     void setSelectionInt(int position, boolean animate) {
301         if (position != mOldSelectedPosition) {
302             mBlockLayoutRequests = true;
303             int delta  = position - mSelectedPosition;
304             setNextSelectedPositionInt(position);
305             layout(delta, animate);
306             mBlockLayoutRequests = false;
307         }
308     }
309
310     abstract void layout(int delta, boolean animate);
311
312     @Override
313     public View getSelectedView() {
314         if (mItemCount > 0 && mSelectedPosition >= 0) {
315             return getChildAt(mSelectedPosition - mFirstPosition);
316         } else {
317             return null;
318         }
319     }
320
321     /**
322      * Override to prevent spamming ourselves with layout requests
323      * as we place views
324      *
325      * @see android.view.View#requestLayout()
326      */
327     @Override
328     public void requestLayout() {
329         if (!mBlockLayoutRequests) {
330             super.requestLayout();
331         }
332     }
333
334     @Override
335     public SpinnerAdapter getAdapter() {
336         return mAdapter;
337     }
338
339     @Override
340     public int getCount() {
341         return mItemCount;
342     }
343
344     /**
345      * Maps a point to a position in the list.
346      *
347      * @param x X in local coordinate
348      * @param y Y in local coordinate
349      * @return The position of the item which contains the specified point, or
350      *         {@link #INVALID_POSITION} if the point does not intersect an item.
351      */
352     public int pointToPosition(int x, int y) {
353         Rect frame = mTouchFrame;
354         if (frame == null) {
355             mTouchFrame = new Rect();
356             frame = mTouchFrame;
357         }
358
359         final int count = getChildCount();
360         for (int i = count - 1; i >= 0; i--) {
361             View child = getChildAt(i);
362             if (child.getVisibility() == View.VISIBLE) {
363                 child.getHitRect(frame);
364                 if (frame.contains(x, y)) {
365                     return mFirstPosition + i;
366                 }
367             }
368         }
369         return INVALID_POSITION;
370     }
371
372     static class SavedState extends BaseSavedState {
373         long selectedId;
374         int position;
375
376         /**
377          * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
378          */
379         SavedState(Parcelable superState) {
380             super(superState);
381         }
382
383         /**
384          * Constructor called from {@link #CREATOR}
385          */
386         private SavedState(Parcel in) {
387             super(in);
388             selectedId = in.readLong();
389             position = in.readInt();
390         }
391
392         @Override
393         public void writeToParcel(Parcel out, int flags) {
394             super.writeToParcel(out, flags);
395             out.writeLong(selectedId);
396             out.writeInt(position);
397         }
398
399         @Override
400         public String toString() {
401             return "AbsSpinner.SavedState{"
402                     + Integer.toHexString(System.identityHashCode(this))
403                     + " selectedId=" + selectedId
404                     + " position=" + position + "}";
405         }
406
407         public static final Parcelable.Creator<SavedState> CREATOR
408                 = new Parcelable.Creator<SavedState>() {
409             public SavedState createFromParcel(Parcel in) {
410                 return new SavedState(in);
411             }
412
413             public SavedState[] newArray(int size) {
414                 return new SavedState[size];
415             }
416         };
417     }
418
419     @Override
420     public Parcelable onSaveInstanceState() {
421         Parcelable superState = super.onSaveInstanceState();
422         SavedState ss = new SavedState(superState);
423         ss.selectedId = getSelectedItemId();
424         if (ss.selectedId >= 0) {
425             ss.position = getSelectedItemPosition();
426         } else {
427             ss.position = INVALID_POSITION;
428         }
429         return ss;
430     }
431
432     @Override
433     public void onRestoreInstanceState(Parcelable state) {
434         SavedState ss = (SavedState) state;
435
436         super.onRestoreInstanceState(ss.getSuperState());
437
438         if (ss.selectedId >= 0) {
439             mDataChanged = true;
440             mNeedSync = true;
441             mSyncRowId = ss.selectedId;
442             mSyncPosition = ss.position;
443             mSyncMode = SYNC_SELECTED_POSITION;
444             requestLayout();
445         }
446     }
447
448     class RecycleBin {
449         private final SparseArray<View> mScrapHeap = new SparseArray<View>();
450
451         public void put(int position, View v) {
452             mScrapHeap.put(position, v);
453         }
454
455         View get(int position) {
456             // System.out.print("Looking for " + position);
457             View result = mScrapHeap.get(position);
458             if (result != null) {
459                 // System.out.println(" HIT");
460                 mScrapHeap.delete(position);
461             } else {
462                 // System.out.println(" MISS");
463             }
464             return result;
465         }
466
467         void clear() {
468             final SparseArray<View> scrapHeap = mScrapHeap;
469             final int count = scrapHeap.size();
470             for (int i = 0; i < count; i++) {
471                 final View view = scrapHeap.valueAt(i);
472                 if (view != null) {
473                     removeDetachedView(view, true);
474                 }
475             }
476             scrapHeap.clear();
477         }
478     }
479 }