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
17package android.widget;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.net.Uri;
22import android.view.View;
23
24/**
25 * An easy adapter to map columns from a cursor to TextViews or ImageViews
26 * defined in an XML file. You can specify which columns you want, which views
27 * you want to display the columns, and the XML file that defines the appearance
28 * of these views. Separate XML files for child and groups are possible.
29 *
30 * Binding occurs in two phases. First, if a
31 * {@link android.widget.SimpleCursorTreeAdapter.ViewBinder} is available,
32 * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
33 * is invoked. If the returned value is true, binding has occurred. If the
34 * returned value is false and the view to bind is a TextView,
35 * {@link #setViewText(TextView, String)} is invoked. If the returned value
36 * is false and the view to bind is an ImageView,
37 * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
38 * binding can be found, an {@link IllegalStateException} is thrown.
39 */
40public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter {
41    /** The indices of columns that contain data to display for a group. */
42    private int[] mGroupFrom;
43    /**
44     * The View IDs that will display a group's data fetched from the
45     * corresponding column.
46     */
47    private int[] mGroupTo;
48
49    /** The indices of columns that contain data to display for a child. */
50    private int[] mChildFrom;
51    /**
52     * The View IDs that will display a child's data fetched from the
53     * corresponding column.
54     */
55    private int[] mChildTo;
56
57    /**
58     * View binder, if supplied
59     */
60    private ViewBinder mViewBinder;
61
62    /**
63     * Constructor.
64     *
65     * @param context The context where the {@link ExpandableListView}
66     *            associated with this {@link SimpleCursorTreeAdapter} is
67     *            running
68     * @param cursor The database cursor
69     * @param collapsedGroupLayout The resource identifier of a layout file that
70     *            defines the views for a collapsed group. The layout file
71     *            should include at least those named views defined in groupTo.
72     * @param expandedGroupLayout The resource identifier of a layout file that
73     *            defines the views for an expanded group. The layout file
74     *            should include at least those named views defined in groupTo.
75     * @param groupFrom A list of column names that will be used to display the
76     *            data for a group.
77     * @param groupTo The group views (from the group layouts) that should
78     *            display column in the "from" parameter. These should all be
79     *            TextViews or ImageViews. The first N views in this list are
80     *            given the values of the first N columns in the from parameter.
81     * @param childLayout The resource identifier of a layout file that defines
82     *            the views for a child (except the last). The layout file
83     *            should include at least those named views defined in childTo.
84     * @param lastChildLayout The resource identifier of a layout file that
85     *            defines the views for the last child within a group. The
86     *            layout file should include at least those named views defined
87     *            in childTo.
88     * @param childFrom A list of column names that will be used to display the
89     *            data for a child.
90     * @param childTo The child views (from the child layouts) that should
91     *            display column in the "from" parameter. These should all be
92     *            TextViews or ImageViews. The first N views in this list are
93     *            given the values of the first N columns in the from parameter.
94     */
95    public SimpleCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
96            int expandedGroupLayout, String[] groupFrom, int[] groupTo, int childLayout,
97            int lastChildLayout, String[] childFrom, int[] childTo) {
98        super(context, cursor, collapsedGroupLayout, expandedGroupLayout, childLayout,
99                lastChildLayout);
100        init(groupFrom, groupTo, childFrom, childTo);
101    }
102
103    /**
104     * Constructor.
105     *
106     * @param context The context where the {@link ExpandableListView}
107     *            associated with this {@link SimpleCursorTreeAdapter} is
108     *            running
109     * @param cursor The database cursor
110     * @param collapsedGroupLayout The resource identifier of a layout file that
111     *            defines the views for a collapsed group. The layout file
112     *            should include at least those named views defined in groupTo.
113     * @param expandedGroupLayout The resource identifier of a layout file that
114     *            defines the views for an expanded group. The layout file
115     *            should include at least those named views defined in groupTo.
116     * @param groupFrom A list of column names that will be used to display the
117     *            data for a group.
118     * @param groupTo The group views (from the group layouts) that should
119     *            display column in the "from" parameter. These should all be
120     *            TextViews or ImageViews. The first N views in this list are
121     *            given the values of the first N columns in the from parameter.
122     * @param childLayout The resource identifier of a layout file that defines
123     *            the views for a child. The layout file
124     *            should include at least those named views defined in childTo.
125     * @param childFrom A list of column names that will be used to display the
126     *            data for a child.
127     * @param childTo The child views (from the child layouts) that should
128     *            display column in the "from" parameter. These should all be
129     *            TextViews or ImageViews. The first N views in this list are
130     *            given the values of the first N columns in the from parameter.
131     */
132    public SimpleCursorTreeAdapter(Context context, Cursor cursor, int collapsedGroupLayout,
133            int expandedGroupLayout, String[] groupFrom, int[] groupTo,
134            int childLayout, String[] childFrom, int[] childTo) {
135        super(context, cursor, collapsedGroupLayout, expandedGroupLayout, childLayout);
136        init(groupFrom, groupTo, childFrom, childTo);
137    }
138
139    /**
140     * Constructor.
141     *
142     * @param context The context where the {@link ExpandableListView}
143     *            associated with this {@link SimpleCursorTreeAdapter} is
144     *            running
145     * @param cursor The database cursor
146     * @param groupLayout The resource identifier of a layout file that defines
147     *            the views for a group. The layout file should include at least
148     *            those named views defined in groupTo.
149     * @param groupFrom A list of column names that will be used to display the
150     *            data for a group.
151     * @param groupTo The group views (from the group layouts) that should
152     *            display column in the "from" parameter. These should all be
153     *            TextViews or ImageViews. The first N views in this list are
154     *            given the values of the first N columns in the from parameter.
155     * @param childLayout The resource identifier of a layout file that defines
156     *            the views for a child. The layout file should include at least
157     *            those named views defined in childTo.
158     * @param childFrom A list of column names that will be used to display the
159     *            data for a child.
160     * @param childTo The child views (from the child layouts) that should
161     *            display column in the "from" parameter. These should all be
162     *            TextViews or ImageViews. The first N views in this list are
163     *            given the values of the first N columns in the from parameter.
164     */
165    public SimpleCursorTreeAdapter(Context context, Cursor cursor, int groupLayout,
166            String[] groupFrom, int[] groupTo, int childLayout, String[] childFrom,
167            int[] childTo) {
168        super(context, cursor, groupLayout, childLayout);
169        init(groupFrom, groupTo, childFrom, childTo);
170    }
171
172    private void init(String[] groupFromNames, int[] groupTo, String[] childFromNames,
173            int[] childTo) {
174        mGroupTo = groupTo;
175
176        mChildTo = childTo;
177
178        // Get the group cursor column indices, the child cursor column indices will come
179        // when needed
180        initGroupFromColumns(groupFromNames);
181
182        // Get a temporary child cursor to init the column indices
183        if (getGroupCount() > 0) {
184            MyCursorHelper tmpCursorHelper = getChildrenCursorHelper(0, true);
185            if (tmpCursorHelper != null) {
186                initChildrenFromColumns(childFromNames, tmpCursorHelper.getCursor());
187                deactivateChildrenCursorHelper(0);
188            }
189        }
190    }
191
192    private void initFromColumns(Cursor cursor, String[] fromColumnNames, int[] fromColumns) {
193        for (int i = fromColumnNames.length - 1; i >= 0; i--) {
194            fromColumns[i] = cursor.getColumnIndexOrThrow(fromColumnNames[i]);
195        }
196    }
197
198    private void initGroupFromColumns(String[] groupFromNames) {
199        mGroupFrom = new int[groupFromNames.length];
200        initFromColumns(mGroupCursorHelper.getCursor(), groupFromNames, mGroupFrom);
201    }
202
203    private void initChildrenFromColumns(String[] childFromNames, Cursor childCursor) {
204        mChildFrom = new int[childFromNames.length];
205        initFromColumns(childCursor, childFromNames, mChildFrom);
206    }
207
208    /**
209     * Returns the {@link ViewBinder} used to bind data to views.
210     *
211     * @return a ViewBinder or null if the binder does not exist
212     *
213     * @see #setViewBinder(android.widget.SimpleCursorTreeAdapter.ViewBinder)
214     */
215    public ViewBinder getViewBinder() {
216        return mViewBinder;
217    }
218
219    /**
220     * Sets the binder used to bind data to views.
221     *
222     * @param viewBinder the binder used to bind data to views, can be null to
223     *        remove the existing binder
224     *
225     * @see #getViewBinder()
226     */
227    public void setViewBinder(ViewBinder viewBinder) {
228        mViewBinder = viewBinder;
229    }
230
231    private void bindView(View view, Context context, Cursor cursor, int[] from, int[] to) {
232        final ViewBinder binder = mViewBinder;
233
234        for (int i = 0; i < to.length; i++) {
235            View v = view.findViewById(to[i]);
236            if (v != null) {
237                boolean bound = false;
238                if (binder != null) {
239                    bound = binder.setViewValue(v, cursor, from[i]);
240                }
241
242                if (!bound) {
243                    String text = cursor.getString(from[i]);
244                    if (text == null) {
245                        text = "";
246                    }
247                    if (v instanceof TextView) {
248                        setViewText((TextView) v, text);
249                    } else if (v instanceof ImageView) {
250                        setViewImage((ImageView) v, text);
251                    } else {
252                        throw new IllegalStateException("SimpleCursorTreeAdapter can bind values" +
253                                " only to TextView and ImageView!");
254                    }
255                }
256            }
257        }
258    }
259
260    @Override
261    protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
262        bindView(view, context, cursor, mChildFrom, mChildTo);
263    }
264
265    @Override
266    protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
267        bindView(view, context, cursor, mGroupFrom, mGroupTo);
268    }
269
270    /**
271     * Called by bindView() to set the image for an ImageView. By default, the
272     * value will be treated as a Uri. Intended to be overridden by Adapters
273     * that need to filter strings retrieved from the database.
274     *
275     * @param v ImageView to receive an image
276     * @param value the value retrieved from the cursor
277     */
278    protected void setViewImage(ImageView v, String value) {
279        try {
280            v.setImageResource(Integer.parseInt(value));
281        } catch (NumberFormatException nfe) {
282            v.setImageURI(Uri.parse(value));
283        }
284    }
285
286    /**
287     * Called by bindView() to set the text for a TextView but only if
288     * there is no existing ViewBinder or if the existing ViewBinder cannot
289     * handle binding to an TextView.
290     *
291     * Intended to be overridden by Adapters that need to filter strings
292     * retrieved from the database.
293     *
294     * @param v TextView to receive text
295     * @param text the text to be set for the TextView
296     */
297    public void setViewText(TextView v, String text) {
298        v.setText(text);
299    }
300
301    /**
302     * This class can be used by external clients of SimpleCursorTreeAdapter
303     * to bind values from the Cursor to views.
304     *
305     * You should use this class to bind values from the Cursor to views
306     * that are not directly supported by SimpleCursorTreeAdapter or to
307     * change the way binding occurs for views supported by
308     * SimpleCursorTreeAdapter.
309     *
310     * @see SimpleCursorTreeAdapter#setViewImage(ImageView, String)
311     * @see SimpleCursorTreeAdapter#setViewText(TextView, String)
312     */
313    public static interface ViewBinder {
314        /**
315         * Binds the Cursor column defined by the specified index to the specified view.
316         *
317         * When binding is handled by this ViewBinder, this method must return true.
318         * If this method returns false, SimpleCursorTreeAdapter will attempts to handle
319         * the binding on its own.
320         *
321         * @param view the view to bind the data to
322         * @param cursor the cursor to get the data from
323         * @param columnIndex the column at which the data can be found in the cursor
324         *
325         * @return true if the data was bound to the view, false otherwise
326         */
327        boolean setViewValue(View view, Cursor cursor, int columnIndex);
328    }
329}
330