1/*
2 * Copyright (C) 2007 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
17package android.util;
18
19import com.android.frameworks.coretests.R;
20
21import android.view.View;
22import android.view.KeyEvent;
23import android.content.Context;
24import android.content.res.TypedArray;
25import android.graphics.Paint;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.graphics.Color;
29import android.util.AttributeSet;
30
31
32
33/**
34 * A view that has a known number of selectable rows, and maintains a notion of which
35 * row is selected. The rows take up the
36 * entire width of the view.  The height of the view is divided evenly among
37 * the rows.
38 *
39 * Note: If the height of the view does not divide exactly to the number of rows,
40 *       the last row's height is inflated with the remainder. For example, if the
41 *       view height is 22 and there are two rows, the height of the first row is
42 *       10 and the second 22.
43 *
44 * Notice what this view does to be a good citizen w.r.t its internal selection:
45 * 1) calls {@link View#requestRectangleOnScreen} each time the selection changes due to
46 *    internal navigation.
47 * 2) implements {@link View#getFocusedRect} by filling in the rectangle of the currently
48 *    selected row
49 * 3) overrides {@link View#onFocusChanged} and sets selection appropriately according to
50 *    the previously focused rectangle.
51 */
52public class InternalSelectionView extends View {
53
54    private Paint mPainter = new Paint();
55    private Paint mTextPaint = new Paint();
56    private Rect mTempRect = new Rect();
57
58    private int mNumRows = 5;
59    private int mSelectedRow = 0;
60    private final int mEstimatedPixelHeight = 10;
61
62    private Integer mDesiredHeight = null;
63    private String mLabel = null;
64
65    public InternalSelectionView(Context context, int numRows, String label) {
66        super(context);
67        mNumRows = numRows;
68        mLabel = label;
69        init();
70    }
71
72    public InternalSelectionView(Context context, AttributeSet attrs) {
73        super(context, attrs);
74        TypedArray a =
75                context.obtainStyledAttributes(
76                        attrs, R.styleable.SelectableRowView);
77        mNumRows = a.getInt(R.styleable.SelectableRowView_numRows, 5);
78        init();
79    }
80
81    private void init() {
82        setFocusable(true);
83        mTextPaint.setAntiAlias(true);
84        mTextPaint.setTextSize(10);
85        mTextPaint.setColor(Color.WHITE);
86    }
87
88    public int getNumRows() {
89        return mNumRows;
90    }
91
92    public int getSelectedRow() {
93        return mSelectedRow;
94    }
95
96    public void setDesiredHeight(int desiredHeight) {
97        mDesiredHeight = desiredHeight;
98    }
99
100    public String getLabel() {
101        return mLabel;
102    }
103
104    @Override
105    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
106        setMeasuredDimension(
107            measureWidth(widthMeasureSpec),
108            measureHeight(heightMeasureSpec));
109    }
110
111    private int measureWidth(int measureSpec) {
112        int specMode = MeasureSpec.getMode(measureSpec);
113        int specSize = MeasureSpec.getSize(measureSpec);
114
115        int desiredWidth = 300 + mPaddingLeft + mPaddingRight;
116        if (specMode == MeasureSpec.EXACTLY) {
117            // We were told how big to be
118            return specSize;
119        } else if (specMode == MeasureSpec.AT_MOST) {
120            return desiredWidth < specSize ? desiredWidth : specSize;
121        } else {
122            return desiredWidth;
123        }
124    }
125
126    private int measureHeight(int measureSpec) {
127        int specMode = MeasureSpec.getMode(measureSpec);
128        int specSize = MeasureSpec.getSize(measureSpec);
129
130        int desiredHeight = mDesiredHeight != null ?
131                mDesiredHeight :
132                mNumRows * mEstimatedPixelHeight + mPaddingTop + mPaddingBottom;
133        if (specMode == MeasureSpec.EXACTLY) {
134            // We were told how big to be
135            return specSize;
136        } else if (specMode == MeasureSpec.AT_MOST) {
137            return desiredHeight < specSize ? desiredHeight : specSize;
138        } else {
139            return desiredHeight;
140        }
141    }
142
143
144    @Override
145    protected void onDraw(Canvas canvas) {
146        int rectTop = mPaddingTop;
147        int rectLeft = mPaddingLeft;
148        int rectRight = getWidth() - mPaddingRight;
149        for (int i = 0; i < mNumRows; i++) {
150
151            mPainter.setColor(Color.BLACK);
152            mPainter.setAlpha(0x20);
153
154            int rowHeight = getRowHeight(i);
155
156            // draw background rect
157            mTempRect.set(rectLeft, rectTop, rectRight, rectTop + rowHeight);
158            canvas.drawRect(mTempRect, mPainter);
159
160            // draw forground rect
161            if (i == mSelectedRow && hasFocus()) {
162                mPainter.setColor(Color.RED);
163                mPainter.setAlpha(0xF0);
164                mTextPaint.setAlpha(0xFF);
165            } else {
166                mPainter.setColor(Color.BLACK);
167                mPainter.setAlpha(0x40);
168                mTextPaint.setAlpha(0xF0);
169            }
170            mTempRect.set(rectLeft + 2, rectTop + 2,
171                    rectRight - 2, rectTop + rowHeight - 2);
172            canvas.drawRect(mTempRect, mPainter);
173
174            // draw text to help when visually inspecting
175            canvas.drawText(
176                    Integer.toString(i),
177                    rectLeft + 2,
178                    rectTop + 2 - (int) mTextPaint.ascent(),
179                    mTextPaint);
180
181            rectTop += rowHeight;
182        }
183    }
184
185    private int getRowHeight(int row) {
186        final int availableHeight = getHeight() - mPaddingTop - mPaddingBottom;
187        final int desiredRowHeight = availableHeight / mNumRows;
188        if (row < mNumRows - 1) {
189            return desiredRowHeight;
190        } else {
191            final int residualHeight = availableHeight % mNumRows;
192            return desiredRowHeight + residualHeight;
193        }
194    }
195
196    public void getRectForRow(Rect rect, int row) {
197        final int rowHeight = getRowHeight(row);
198        final int top = mPaddingTop + row * rowHeight;
199        rect.set(mPaddingLeft,
200                top,
201                getWidth() - mPaddingRight,
202                top + rowHeight);
203    }
204
205
206    void ensureRectVisible() {
207        getRectForRow(mTempRect, mSelectedRow);
208        requestRectangleOnScreen(mTempRect);
209    }
210
211    @Override
212    public boolean onKeyDown(int keyCode, KeyEvent event) {
213        switch(event.getKeyCode()) {
214            case KeyEvent.KEYCODE_DPAD_UP:
215                if (mSelectedRow > 0) {
216                    mSelectedRow--;
217                    invalidate();
218                    ensureRectVisible();
219                    return true;
220                }
221                break;
222            case KeyEvent.KEYCODE_DPAD_DOWN:
223                if (mSelectedRow < (mNumRows - 1)) {
224                    mSelectedRow++;
225                    invalidate();
226                    ensureRectVisible();
227                    return true;
228                }
229                break;
230        }
231        return false;
232    }
233
234
235    @Override
236    public void getFocusedRect(Rect r) {
237        getRectForRow(r, mSelectedRow);
238    }
239
240    @Override
241    protected void onFocusChanged(boolean focused, int direction,
242            Rect previouslyFocusedRect) {
243        super.onFocusChanged(focused, direction, previouslyFocusedRect);
244
245        if (focused) {
246            switch (direction) {
247                case View.FOCUS_DOWN:
248                    mSelectedRow = 0;
249                    break;
250                case View.FOCUS_UP:
251                    mSelectedRow = mNumRows - 1;
252                    break;
253                case View.FOCUS_LEFT:  // fall through
254                case View.FOCUS_RIGHT:
255                    // set the row that is closest to the rect
256                    if (previouslyFocusedRect != null) {
257                        int y = previouslyFocusedRect.top
258                                + (previouslyFocusedRect.height() / 2);
259                        int yPerRow = getHeight() / mNumRows;
260                        mSelectedRow = y / yPerRow;
261                    } else {
262                        mSelectedRow = 0;
263                    }
264                    break;
265                default:
266                    // can't gleam any useful information about what internal
267                    // selection should be...
268                    return;
269            }
270            invalidate();
271        }
272    }
273
274    @Override
275    public String toString() {
276        if (mLabel != null) {
277            return mLabel;
278        }
279        return super.toString();
280    }
281}
282