1/*
2 * Copyright (C) 2010 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.calendar.month;
18
19// TODO Remove calendar imports when the required methods have been
20// refactored into the public api
21import com.android.calendar.CalendarController;
22import com.android.calendar.Utils;
23
24import android.content.Context;
25import android.text.format.Time;
26import android.util.Log;
27import android.view.GestureDetector;
28import android.view.MotionEvent;
29import android.view.View;
30import android.view.View.OnTouchListener;
31import android.view.ViewGroup;
32import android.widget.AbsListView.LayoutParams;
33import android.widget.BaseAdapter;
34import android.widget.ListView;
35
36import java.util.Calendar;
37import java.util.HashMap;
38import java.util.Locale;
39
40/**
41 * <p>
42 * This is a specialized adapter for creating a list of weeks with selectable
43 * days. It can be configured to display the week number, start the week on a
44 * given day, show a reduced number of days, or display an arbitrary number of
45 * weeks at a time. See {@link SimpleDayPickerFragment} for usage.
46 * </p>
47 */
48public class SimpleWeeksAdapter extends BaseAdapter implements OnTouchListener {
49
50    private static final String TAG = "MonthByWeek";
51
52    /**
53     * The number of weeks to display at a time.
54     */
55    public static final String WEEK_PARAMS_NUM_WEEKS = "num_weeks";
56    /**
57     * Which month should be in focus currently.
58     */
59    public static final String WEEK_PARAMS_FOCUS_MONTH = "focus_month";
60    /**
61     * Whether the week number should be shown. Non-zero to show them.
62     */
63    public static final String WEEK_PARAMS_SHOW_WEEK = "week_numbers";
64    /**
65     * Which day the week should start on. {@link Time#SUNDAY} through
66     * {@link Time#SATURDAY}.
67     */
68    public static final String WEEK_PARAMS_WEEK_START = "week_start";
69    /**
70     * The Julian day to highlight as selected.
71     */
72    public static final String WEEK_PARAMS_JULIAN_DAY = "selected_day";
73    /**
74     * How many days of the week to display [1-7].
75     */
76    public static final String WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week";
77
78    protected static final int WEEK_COUNT = CalendarController.MAX_CALENDAR_WEEK
79            - CalendarController.MIN_CALENDAR_WEEK;
80    protected static int DEFAULT_NUM_WEEKS = 6;
81    protected static int DEFAULT_MONTH_FOCUS = 0;
82    protected static int DEFAULT_DAYS_PER_WEEK = 7;
83    protected static int DEFAULT_WEEK_HEIGHT = 32;
84    protected static int WEEK_7_OVERHANG_HEIGHT = 7;
85
86    protected static float mScale = 0;
87    protected Context mContext;
88    // The day to highlight as selected
89    protected Time mSelectedDay;
90    // The week since 1970 that the selected day is in
91    protected int mSelectedWeek;
92    // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
93    protected int mFirstDayOfWeek;
94    protected boolean mShowWeekNumber = false;
95    protected GestureDetector mGestureDetector;
96    protected int mNumWeeks = DEFAULT_NUM_WEEKS;
97    protected int mDaysPerWeek = DEFAULT_DAYS_PER_WEEK;
98    protected int mFocusMonth = DEFAULT_MONTH_FOCUS;
99
100    public SimpleWeeksAdapter(Context context, HashMap<String, Integer> params) {
101        mContext = context;
102
103        // Get default week start based on locale, subtracting one for use with android Time.
104        Calendar cal = Calendar.getInstance(Locale.getDefault());
105        mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1;
106
107        if (mScale == 0) {
108            mScale = context.getResources().getDisplayMetrics().density;
109            if (mScale != 1) {
110                WEEK_7_OVERHANG_HEIGHT *= mScale;
111            }
112        }
113        init();
114        updateParams(params);
115    }
116
117    /**
118     * Set up the gesture detector and selected time
119     */
120    protected void init() {
121        mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
122        mSelectedDay = new Time();
123        mSelectedDay.setToNow();
124    }
125
126    /**
127     * Parse the parameters and set any necessary fields. See
128     * {@link #WEEK_PARAMS_NUM_WEEKS} for parameter details.
129     *
130     * @param params A list of parameters for this adapter
131     */
132    public void updateParams(HashMap<String, Integer> params) {
133        if (params == null) {
134            Log.e(TAG, "WeekParameters are null! Cannot update adapter.");
135            return;
136        }
137        if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
138            mFocusMonth = params.get(WEEK_PARAMS_FOCUS_MONTH);
139        }
140        if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
141            mNumWeeks = params.get(WEEK_PARAMS_NUM_WEEKS);
142        }
143        if (params.containsKey(WEEK_PARAMS_SHOW_WEEK)) {
144            mShowWeekNumber = params.get(WEEK_PARAMS_SHOW_WEEK) != 0;
145        }
146        if (params.containsKey(WEEK_PARAMS_WEEK_START)) {
147            mFirstDayOfWeek = params.get(WEEK_PARAMS_WEEK_START);
148        }
149        if (params.containsKey(WEEK_PARAMS_JULIAN_DAY)) {
150            int julianDay = params.get(WEEK_PARAMS_JULIAN_DAY);
151            mSelectedDay.setJulianDay(julianDay);
152            mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(julianDay, mFirstDayOfWeek);
153        }
154        if (params.containsKey(WEEK_PARAMS_DAYS_PER_WEEK)) {
155            mDaysPerWeek = params.get(WEEK_PARAMS_DAYS_PER_WEEK);
156        }
157        refresh();
158    }
159
160    /**
161     * Updates the selected day and related parameters.
162     *
163     * @param selectedTime The time to highlight
164     */
165    public void setSelectedDay(Time selectedTime) {
166        mSelectedDay.set(selectedTime);
167        long millis = mSelectedDay.normalize(true);
168        mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
169                Time.getJulianDay(millis, mSelectedDay.gmtoff), mFirstDayOfWeek);
170        notifyDataSetChanged();
171    }
172
173    /**
174     * Returns the currently highlighted day
175     *
176     * @return
177     */
178    public Time getSelectedDay() {
179        return mSelectedDay;
180    }
181
182    /**
183     * updates any config options that may have changed and refreshes the view
184     */
185    protected void refresh() {
186        notifyDataSetChanged();
187    }
188
189    @Override
190    public int getCount() {
191        return WEEK_COUNT;
192    }
193
194    @Override
195    public Object getItem(int position) {
196        return null;
197    }
198
199    @Override
200    public long getItemId(int position) {
201        return position;
202    }
203
204    @SuppressWarnings("unchecked")
205    @Override
206    public View getView(int position, View convertView, ViewGroup parent) {
207        SimpleWeekView v;
208        HashMap<String, Integer> drawingParams = null;
209        if (convertView != null) {
210            v = (SimpleWeekView) convertView;
211            // We store the drawing parameters in the view so it can be recycled
212            drawingParams = (HashMap<String, Integer>) v.getTag();
213        } else {
214            v = new SimpleWeekView(mContext);
215            // Set up the new view
216            LayoutParams params = new LayoutParams(
217                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
218            v.setLayoutParams(params);
219            v.setClickable(true);
220            v.setOnTouchListener(this);
221        }
222        if (drawingParams == null) {
223            drawingParams = new HashMap<String, Integer>();
224        }
225        drawingParams.clear();
226
227        int selectedDay = -1;
228        if (mSelectedWeek == position) {
229            selectedDay = mSelectedDay.weekDay;
230        }
231
232        // pass in all the view parameters
233        drawingParams.put(SimpleWeekView.VIEW_PARAMS_HEIGHT,
234                (parent.getHeight() - WEEK_7_OVERHANG_HEIGHT) / mNumWeeks);
235        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
236        drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, mShowWeekNumber ? 1 : 0);
237        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek);
238        drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek);
239        drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position);
240        drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth);
241        v.setWeekParams(drawingParams, mSelectedDay.timezone);
242        v.invalidate();
243
244        return v;
245    }
246
247    /**
248     * Changes which month is in focus and updates the view.
249     *
250     * @param month The month to show as in focus [0-11]
251     */
252    public void updateFocusMonth(int month) {
253        mFocusMonth = month;
254        notifyDataSetChanged();
255    }
256
257    @Override
258    public boolean onTouch(View v, MotionEvent event) {
259        if (mGestureDetector.onTouchEvent(event)) {
260            SimpleWeekView view = (SimpleWeekView) v;
261            Time day = ((SimpleWeekView)v).getDayFromLocation(event.getX());
262            if (Log.isLoggable(TAG, Log.DEBUG)) {
263                Log.d(TAG, "Touched day at Row=" + view.mWeek + " day=" + day.toString());
264            }
265            if (day != null) {
266                onDayTapped(day);
267            }
268            return true;
269        }
270        return false;
271    }
272
273    /**
274     * Maintains the same hour/min/sec but moves the day to the tapped day.
275     *
276     * @param day The day that was tapped
277     */
278    protected void onDayTapped(Time day) {
279        day.hour = mSelectedDay.hour;
280        day.minute = mSelectedDay.minute;
281        day.second = mSelectedDay.second;
282        setSelectedDay(day);
283    }
284
285
286    /**
287     * This is here so we can identify single tap events and set the selected
288     * day correctly
289     */
290    protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
291        @Override
292        public boolean onSingleTapUp(MotionEvent e) {
293            return true;
294        }
295    }
296
297    ListView mListView;
298
299    public void setListView(ListView lv) {
300        mListView = lv;
301    }
302}
303