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