1/*
2 * Copyright (C) 2013 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.colorpicker;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.util.AttributeSet;
22import android.view.View;
23import android.view.ViewGroup;
24import android.widget.ImageView;
25import android.widget.TableLayout;
26import android.widget.TableRow;
27
28import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
29
30/**
31 * A color picker custom view which creates an grid of color squares.  The number of squares per
32 * row (and the padding between the squares) is determined by the user.
33 */
34public class ColorPickerPalette extends TableLayout {
35
36    public OnColorSelectedListener mOnColorSelectedListener;
37
38    private String mDescription;
39    private String mDescriptionSelected;
40
41    private int mSwatchLength;
42    private int mMarginSize;
43    private int mNumColumns;
44
45    public ColorPickerPalette(Context context, AttributeSet attrs) {
46        super(context, attrs);
47    }
48
49    public ColorPickerPalette(Context context) {
50        super(context);
51    }
52
53    /**
54     * Initialize the size, columns, and listener.  Size should be a pre-defined size (SIZE_LARGE
55     * or SIZE_SMALL) from ColorPickerDialogFragment.
56     */
57    public void init(int size, int columns, OnColorSelectedListener listener) {
58        mNumColumns = columns;
59        Resources res = getResources();
60        if (size == ColorPickerDialog.SIZE_LARGE) {
61            mSwatchLength = res.getDimensionPixelSize(R.dimen.color_swatch_large);
62            mMarginSize = res.getDimensionPixelSize(R.dimen.color_swatch_margins_large);
63        } else {
64            mSwatchLength = res.getDimensionPixelSize(R.dimen.color_swatch_small);
65            mMarginSize = res.getDimensionPixelSize(R.dimen.color_swatch_margins_small);
66        }
67        mOnColorSelectedListener = listener;
68
69        mDescription = res.getString(R.string.color_swatch_description);
70        mDescriptionSelected = res.getString(R.string.color_swatch_description_selected);
71    }
72
73    private TableRow createTableRow() {
74        TableRow row = new TableRow(getContext());
75        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,
76                LayoutParams.WRAP_CONTENT);
77        row.setLayoutParams(params);
78        return row;
79    }
80
81    /**
82     * Adds swatches to table in a serpentine format.
83     */
84    public void drawPalette(int[] colors, int selectedColor) {
85        drawPalette(colors, selectedColor, null);
86    }
87
88    /**
89     * Adds swatches to table in a serpentine format.
90     */
91    public void drawPalette(int[] colors, int selectedColor, String[] colorContentDescriptions) {
92        if (colors == null) {
93            return;
94        }
95
96        this.removeAllViews();
97        int tableElements = 0;
98        int rowElements = 0;
99        int rowNumber = 0;
100
101        // Fills the table with swatches based on the array of colors.
102        TableRow row = createTableRow();
103        for (int color : colors) {
104            View colorSwatch = createColorSwatch(color, selectedColor);
105            setSwatchDescription(rowNumber, tableElements, rowElements, color == selectedColor,
106                    colorSwatch, colorContentDescriptions);
107            addSwatchToRow(row, colorSwatch, rowNumber);
108
109            tableElements++;
110            rowElements++;
111            if (rowElements == mNumColumns) {
112                addView(row);
113                row = createTableRow();
114                rowElements = 0;
115                rowNumber++;
116            }
117        }
118
119        // Create blank views to fill the row if the last row has not been filled.
120        if (rowElements > 0) {
121            while (rowElements != mNumColumns) {
122                addSwatchToRow(row, createBlankSpace(), rowNumber);
123                rowElements++;
124            }
125            addView(row);
126        }
127    }
128
129    /**
130     * Appends a swatch to the end of the row for even-numbered rows (starting with row 0),
131     * to the beginning of a row for odd-numbered rows.
132     */
133    private static void addSwatchToRow(TableRow row, View swatch, int rowNumber) {
134        if (rowNumber % 2 == 0) {
135            row.addView(swatch);
136        } else {
137            row.addView(swatch, 0);
138        }
139    }
140
141    /**
142     * Add a content description to the specified swatch view. Because the colors get added in a
143     * snaking form, every other row will need to compensate for the fact that the colors are added
144     * in an opposite direction from their left->right/top->bottom order, which is how the system
145     * will arrange them for accessibility purposes.
146     */
147    private void setSwatchDescription(int rowNumber, int index, int rowElements, boolean selected,
148            View swatch, String[] contentDescriptions) {
149        String description;
150        if (contentDescriptions != null && contentDescriptions.length > index) {
151            description = contentDescriptions[index];
152        } else {
153            int accessibilityIndex;
154            if (rowNumber % 2 == 0) {
155                // We're in a regular-ordered row
156                accessibilityIndex = index + 1;
157            } else {
158                // We're in a backwards-ordered row.
159                int rowMax = ((rowNumber + 1) * mNumColumns);
160                accessibilityIndex = rowMax - rowElements;
161            }
162
163            if (selected) {
164                description = String.format(mDescriptionSelected, accessibilityIndex);
165            } else {
166                description = String.format(mDescription, accessibilityIndex);
167            }
168        }
169        swatch.setContentDescription(description);
170    }
171
172    /**
173     * Creates a blank space to fill the row.
174     */
175    private ImageView createBlankSpace() {
176        ImageView view = new ImageView(getContext());
177        TableRow.LayoutParams params = new TableRow.LayoutParams(mSwatchLength, mSwatchLength);
178        params.setMargins(mMarginSize, mMarginSize, mMarginSize, mMarginSize);
179        view.setLayoutParams(params);
180        return view;
181    }
182
183    /**
184     * Creates a color swatch.
185     */
186    private ColorPickerSwatch createColorSwatch(int color, int selectedColor) {
187        ColorPickerSwatch view = new ColorPickerSwatch(getContext(), color,
188                color == selectedColor, mOnColorSelectedListener);
189        TableRow.LayoutParams params = new TableRow.LayoutParams(mSwatchLength, mSwatchLength);
190        params.setMargins(mMarginSize, mMarginSize, mMarginSize, mMarginSize);
191        view.setLayoutParams(params);
192        return view;
193    }
194}
195