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