MonthWeekEventsView.java revision c18dd7aea8c8ee06dd34f268ea9e0e9e7c235b7f
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
19import com.android.calendar.Event;
20import com.android.calendar.R;
21import com.android.calendar.Utils;
22import com.android.calendar.Utils.BusyBitsSegment;
23
24import android.content.Context;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.Canvas;
28import android.graphics.Paint;
29import android.graphics.Paint.Align;
30import android.graphics.Paint.Style;
31import android.graphics.Typeface;
32import android.graphics.drawable.Drawable;
33import android.text.TextPaint;
34import android.text.TextUtils;
35import android.text.format.DateUtils;
36import android.text.format.Time;
37import android.util.Log;
38
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Formatter;
42import java.util.HashMap;
43import java.util.Iterator;
44import java.util.List;
45import java.util.Locale;
46
47public class MonthWeekEventsView extends SimpleWeekView {
48    private static final String TAG = "MonthView";
49
50    public static final String VIEW_PARAMS_ORIENTATION = "orientation";
51
52    private static int TEXT_SIZE_MONTH_NUMBER = 32;
53    private static int TEXT_SIZE_EVENT = 14;
54    private static int TEXT_SIZE_MORE_EVENTS = 12;
55    private static int TEXT_SIZE_MONTH_NAME = 14;
56    private static int TEXT_SIZE_WEEK_NUM = 12;
57
58    private static final int DEFAULT_EDGE_SPACING = 4;
59    private static int PADDING_MONTH_NUMBER = 4;
60    private static int PADDING_WEEK_NUMBER = 16;
61    private static int DAY_SEPARATOR_OUTER_WIDTH = 5;
62    private static int DAY_SEPARATOR_INNER_WIDTH = 1;
63    private static int DAY_SEPARATOR_VERTICAL_LENGTH = 53;
64    private static int DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT = 64;
65
66    private static int EVENT_X_OFFSET_LANDSCAPE = 44;
67    private static int EVENT_Y_OFFSET_LANDSCAPE = 11;
68    private static int EVENT_Y_OFFSET_PORTRAIT = 18;
69    private static int EVENT_SQUARE_WIDTH = 10;
70    private static int EVENT_SQUARE_BORDER = 1;
71    private static int EVENT_LINE_PADDING = 4;
72    private static int EVENT_RIGHT_PADDING = 4;
73    private static int EVENT_BOTTOM_PADDING = 15;
74
75    private static int BUSY_BITS_MARGIN = 2;
76    private static int BUSY_BITS_WIDTH = 8;
77
78
79    private static int SPACING_WEEK_NUMBER = 19;
80    private static boolean mScaled = false;
81    private boolean mShowDetailsInMonth;
82
83    protected Time mToday = new Time();
84    protected boolean mHasToday = false;
85    protected int mTodayIndex = -1;
86    protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE;
87    protected List<ArrayList<Event>> mEvents = null;
88    // This is for drawing the outlines around event chips and supports up to 10
89    // events being drawn on each day. The code will expand this if necessary.
90    protected FloatRef mEventOutlines = new FloatRef(10 * 4 * 4 * 7);
91
92    protected static StringBuilder mStringBuilder = new StringBuilder(50);
93    // TODO recreate formatter when locale changes
94    protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
95
96    protected Paint mMonthNamePaint;
97    protected TextPaint mEventPaint;
98    protected TextPaint mEventExtrasPaint;
99    protected Paint mWeekNumPaint;
100
101    protected Drawable mTodayDrawable;
102
103    protected int mMonthNumHeight;
104    protected int mEventHeight;
105    protected int mExtrasHeight;
106    protected int mWeekNumHeight;
107
108    protected int mMonthNumColor;
109    protected int mMonthNumOtherColor;
110    protected int mMonthNumTodayColor;
111    protected int mMonthNameColor;
112    protected int mMonthNameOtherColor;
113    protected int mMonthEventColor;
114    protected int mMonthEventExtraColor;
115    protected int mMonthEventOtherColor;
116    protected int mMonthEventExtraOtherColor;
117    protected int mMonthWeekNumColor;
118    protected int mMonthBusyBitsBgColor;
119    protected int mMonthBusyBitsFgColor;
120
121    protected int mEventChipOutlineColor = 0xFFFFFFFF;
122    protected int mDaySeparatorOuterColor = 0x33FFFFFF;
123    protected int mDaySeparatorInnerColor = 0x1A000000;
124
125
126    /**
127     * This provides a reference to a float array which allows for easy size
128     * checking and reallocation. Used for drawing lines.
129     */
130    private class FloatRef {
131        float[] array;
132
133        public FloatRef(int size) {
134            array = new float[size];
135        }
136
137        public void ensureSize(int newSize) {
138            if (newSize >= array.length) {
139                // Add enough space for 7 more boxes to be drawn
140                array = Arrays.copyOf(array, newSize + 16 * 7);
141            }
142        }
143    }
144
145
146    /**
147     * @param context
148     */
149    public MonthWeekEventsView(Context context) {
150        super(context);
151
152        Resources resources = context.getResources();
153        TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number);
154
155
156        mPadding = DEFAULT_EDGE_SPACING;
157        if (mScale != 1 && !mScaled) {
158            PADDING_MONTH_NUMBER *= mScale;
159            PADDING_WEEK_NUMBER *= mScale;
160            SPACING_WEEK_NUMBER *= mScale;
161            TEXT_SIZE_MONTH_NUMBER *= mScale;
162            TEXT_SIZE_EVENT *= mScale;
163            TEXT_SIZE_MORE_EVENTS *= mScale;
164            TEXT_SIZE_MONTH_NAME *= mScale;
165            TEXT_SIZE_WEEK_NUM *= mScale;
166            DAY_SEPARATOR_OUTER_WIDTH *= mScale;
167            DAY_SEPARATOR_INNER_WIDTH *= mScale;
168            DAY_SEPARATOR_VERTICAL_LENGTH *= mScale;
169            DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT *= mScale;
170            EVENT_X_OFFSET_LANDSCAPE *= mScale;
171            EVENT_Y_OFFSET_LANDSCAPE *= mScale;
172            EVENT_Y_OFFSET_PORTRAIT *= mScale;
173            EVENT_SQUARE_WIDTH *= mScale;
174            EVENT_LINE_PADDING *= mScale;
175            EVENT_BOTTOM_PADDING *= mScale;
176            EVENT_RIGHT_PADDING *= mScale;
177            BUSY_BITS_MARGIN *= mScale;
178            BUSY_BITS_WIDTH *= mScale;
179
180            mPadding = (int) (DEFAULT_EDGE_SPACING * mScale);
181            mScaled = true;
182        }
183        mShowDetailsInMonth = Utils.getConfigBool(context, R.bool.show_details_in_month);
184    }
185
186    public void setEvents(List<ArrayList<Event>> sortedEvents) {
187        mEvents = sortedEvents;
188        if (sortedEvents == null) {
189            return;
190        }
191        if (sortedEvents.size() != mNumDays) {
192            if (Log.isLoggable(TAG, Log.ERROR)) {
193                Log.wtf(TAG, "Events size must be same as days displayed: size="
194                        + sortedEvents.size() + " days=" + mNumDays);
195            }
196            mEvents = null;
197            return;
198        }
199    }
200
201    protected void loadColors(Context context) {
202        Resources res = context.getResources();
203        mMonthWeekNumColor = res.getColor(R.color.month_week_num_color);
204        mMonthNumColor = res.getColor(R.color.month_day_number);
205        mMonthNumOtherColor = res.getColor(R.color.month_day_number_other);
206        mMonthNumTodayColor = res.getColor(R.color.month_today_number);
207        mMonthNameColor = mMonthNumColor;
208        mMonthNameOtherColor = mMonthNumOtherColor;
209        mMonthEventColor = res.getColor(R.color.month_event_color);
210        mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color);
211        mMonthEventOtherColor = res.getColor(R.color.month_event_other_color);
212        mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color);
213        mMonthBusyBitsBgColor = res.getColor(R.color.month_busybits_backgound_color);
214        mMonthBusyBitsFgColor = res.getColor(R.color.month_busybits_foregound_color);
215
216        mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light);
217    }
218
219    /**
220     * Sets up the text and style properties for painting. Override this if you
221     * want to use a different paint.
222     */
223    @Override
224    protected void setPaintProperties() {
225        loadColors(mContext);
226        // TODO modify paint properties depending on isMini
227        p.setStyle(Style.FILL);
228
229        mMonthNumPaint = new Paint();
230        mMonthNumPaint.setFakeBoldText(false);
231        mMonthNumPaint.setAntiAlias(true);
232        mMonthNumPaint.setTextSize(TEXT_SIZE_MONTH_NUMBER);
233        mMonthNumPaint.setColor(mMonthNumColor);
234        mMonthNumPaint.setStyle(Style.FILL);
235        mMonthNumPaint.setTextAlign(Align.LEFT);
236        mMonthNumPaint.setTypeface(Typeface.DEFAULT_BOLD);
237
238        mMonthNumHeight = (int) (-mMonthNumPaint.ascent());
239
240        mEventPaint = new TextPaint();
241        mEventPaint.setFakeBoldText(false);
242        mEventPaint.setAntiAlias(true);
243        mEventPaint.setTextSize(TEXT_SIZE_EVENT);
244        mEventPaint.setColor(mMonthEventColor);
245
246        mEventHeight = (int) (-mEventPaint.ascent());
247
248        mEventExtrasPaint = new TextPaint();
249        mEventExtrasPaint.setFakeBoldText(false);
250        mEventExtrasPaint.setAntiAlias(true);
251        mEventExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER);
252        mEventExtrasPaint.setTextSize(TEXT_SIZE_EVENT);
253        mEventExtrasPaint.setColor(mMonthEventExtraColor);
254        mEventExtrasPaint.setStyle(Style.FILL);
255        mEventExtrasPaint.setTextAlign(Align.LEFT);
256
257        mWeekNumPaint = new Paint();
258        mWeekNumPaint.setFakeBoldText(false);
259        mWeekNumPaint.setAntiAlias(true);
260        mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM);
261        mWeekNumPaint.setColor(mWeekNumColor);
262        mWeekNumPaint.setStyle(Style.FILL);
263        mWeekNumPaint.setTextAlign(Align.RIGHT);
264
265        mWeekNumHeight = (int) (-mWeekNumPaint.ascent());
266    }
267
268    @Override
269    public void setWeekParams(HashMap<String, Integer> params, String tz) {
270        super.setWeekParams(params, tz);
271
272        if (params.containsKey(VIEW_PARAMS_ORIENTATION)) {
273            mOrientation = params.get(VIEW_PARAMS_ORIENTATION);
274        }
275
276        mToday.timezone = tz;
277        mToday.setToNow();
278        mToday.normalize(true);
279        int julianToday = Time.getJulianDay(mToday.toMillis(false), mToday.gmtoff);
280        if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) {
281            mHasToday = true;
282            mTodayIndex = julianToday - mFirstJulianDay;
283        } else {
284            mHasToday = false;
285            mTodayIndex = -1;
286        }
287        mNumCells = mNumDays + 1;
288    }
289
290    @Override
291    protected void onDraw(Canvas canvas) {
292        drawBackground(canvas);
293        drawWeekNums(canvas);
294        drawDaySeparators(canvas);
295        if (mShowDetailsInMonth) {
296            drawEvents(canvas);
297        } else {
298            drawBusyBits(canvas);
299        }
300    }
301
302    @Override
303    protected void drawDaySeparators(Canvas canvas) {
304        // mDaySeparatorOuterColor
305        float lines[] = new float[8 * 4];
306        int count = 7 * 4;
307        int wkNumOffset = 0;
308        int effectiveWidth = mWidth - mPadding * 2;
309        count += 4;
310        wkNumOffset = 1;
311        effectiveWidth -= SPACING_WEEK_NUMBER;
312        lines[0] = mPadding;
313        lines[1] = DAY_SEPARATOR_OUTER_WIDTH / 2 + 1;
314        lines[2] = mWidth - mPadding;
315        lines[3] = lines[1];
316        int y0 = DAY_SEPARATOR_OUTER_WIDTH / 2 + DAY_SEPARATOR_INNER_WIDTH;
317        int y1;
318        if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
319            y1 = y0 + DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT;
320        } else {
321            y1 = y0 + DAY_SEPARATOR_VERTICAL_LENGTH;
322        }
323
324        for (int i = 4; i < count;) {
325            int x = (i / 4 - wkNumOffset) * effectiveWidth / (mNumDays) + mPadding
326                    + (SPACING_WEEK_NUMBER * wkNumOffset);
327            lines[i++] = x;
328            lines[i++] = y0;
329            lines[i++] = x;
330            lines[i++] = y1;
331        }
332        p.setColor(mDaySeparatorOuterColor);
333        p.setStrokeWidth(DAY_SEPARATOR_OUTER_WIDTH);
334        canvas.drawLines(lines, 0, count, p);
335        p.setColor(mDaySeparatorInnerColor);
336        p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH);
337        canvas.drawLines(lines, 0, count, p);
338    }
339
340    @Override
341    protected void drawBackground(Canvas canvas) {
342        if (mHasToday) {
343            p.setColor(mSelectedWeekBGColor);
344        } else {
345            return;
346        }
347        int wkNumOffset = 0;
348        int effectiveWidth = mWidth - mPadding * 2;
349        wkNumOffset = 1;
350        effectiveWidth -= SPACING_WEEK_NUMBER;
351        r.top = DAY_SEPARATOR_OUTER_WIDTH + 1;
352        r.bottom = mHeight;
353        r.left = (mTodayIndex) * effectiveWidth / (mNumDays) + mPadding
354                + (SPACING_WEEK_NUMBER * wkNumOffset) + DAY_SEPARATOR_OUTER_WIDTH / 2 + 1;
355        r.right = (mTodayIndex + 1) * effectiveWidth / (mNumDays) + mPadding
356                + (SPACING_WEEK_NUMBER * wkNumOffset) - DAY_SEPARATOR_OUTER_WIDTH / 2;
357        mTodayDrawable.setBounds(r);
358        mTodayDrawable.draw(canvas);
359    }
360
361    @Override
362    protected void drawWeekNums(Canvas canvas) {
363        int y;
364
365        int i = 0;
366        int offset = 0;
367        int effectiveWidth = mWidth - mPadding * 2;
368        int todayIndex = mTodayIndex;
369        int x = PADDING_WEEK_NUMBER + mPadding;
370        int numCount = mNumDays;
371        y = mWeekNumHeight + PADDING_MONTH_NUMBER;
372        if (mShowWeekNum) {
373            canvas.drawText(mDayNumbers[0], x, y, mWeekNumPaint);
374            numCount++;
375            i++;
376            todayIndex++;
377            offset++;
378        }
379        effectiveWidth -= SPACING_WEEK_NUMBER;
380
381        y = (mMonthNumHeight + PADDING_MONTH_NUMBER);
382
383        boolean isFocusMonth = mFocusDay[i];
384        mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor);
385        for (; i < numCount; i++) {
386            if (mHasToday && todayIndex == i) {
387                mMonthNumPaint.setColor(mMonthNumTodayColor);
388                if (i + 1 < numCount) {
389                    // Make sure the color will be set back on the next
390                    // iteration
391                    isFocusMonth = !mFocusDay[i + 1];
392                }
393            } else if (mFocusDay[i] != isFocusMonth) {
394                isFocusMonth = mFocusDay[i];
395                mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor);
396            }
397            x = (i - offset) * effectiveWidth / (mNumDays) + mPadding + PADDING_MONTH_NUMBER
398                    + SPACING_WEEK_NUMBER;
399            canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
400
401        }
402    }
403
404    protected void drawEvents(Canvas canvas) {
405        if (mEvents == null) {
406            return;
407        }
408        int wkNumOffset = 0;
409        int effectiveWidth = mWidth - mPadding * 2;
410        wkNumOffset = 1;
411        effectiveWidth -= SPACING_WEEK_NUMBER;
412
413        int day = -1;
414        int outlineCount = 0;
415        for (ArrayList<Event> eventDay : mEvents) {
416            day++;
417            if (eventDay == null || eventDay.size() == 0) {
418                continue;
419            }
420            int ySquare;
421            int xSquare = day * effectiveWidth / (mNumDays) + mPadding
422                    + (SPACING_WEEK_NUMBER * wkNumOffset);
423            if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
424                ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + PADDING_MONTH_NUMBER;
425                xSquare += PADDING_MONTH_NUMBER + 1;
426            } else {
427                ySquare = EVENT_Y_OFFSET_LANDSCAPE;
428                xSquare += EVENT_X_OFFSET_LANDSCAPE;
429            }
430            int rightEdge = (day + 1) * effectiveWidth / (mNumDays) + mPadding
431                    + (SPACING_WEEK_NUMBER * wkNumOffset) - EVENT_RIGHT_PADDING;
432            int eventCount = 0;
433            Iterator<Event> iter = eventDay.iterator();
434            while (iter.hasNext()) {
435                Event event = iter.next();
436                int newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext());
437                if (newY == ySquare) {
438                    break;
439                }
440                outlineCount = addChipOutline(mEventOutlines, outlineCount, xSquare, ySquare);
441                eventCount++;
442                ySquare = newY;
443            }
444
445            int remaining = eventDay.size() - eventCount;
446            if (remaining > 0) {
447                drawMoreEvents(canvas, remaining, xSquare);
448            }
449        }
450        if (outlineCount > 0) {
451            p.setColor(mEventChipOutlineColor);
452            p.setStrokeWidth(EVENT_SQUARE_BORDER);
453            canvas.drawLines(mEventOutlines.array, 0, outlineCount, p);
454        }
455    }
456
457    protected int addChipOutline(FloatRef lines, int count, int x, int y) {
458        lines.ensureSize(count + 16);
459        // top of box
460        lines.array[count++] = x;
461        lines.array[count++] = y;
462        lines.array[count++] = x + EVENT_SQUARE_WIDTH;
463        lines.array[count++] = y;
464        // right side of box
465        lines.array[count++] = x + EVENT_SQUARE_WIDTH;
466        lines.array[count++] = y;
467        lines.array[count++] = x + EVENT_SQUARE_WIDTH;
468        lines.array[count++] = y + EVENT_SQUARE_WIDTH;
469        // left side of box
470        lines.array[count++] = x;
471        lines.array[count++] = y;
472        lines.array[count++] = x;
473        lines.array[count++] = y + EVENT_SQUARE_WIDTH + 1;
474        // bottom of box
475        lines.array[count++] = x;
476        lines.array[count++] = y + EVENT_SQUARE_WIDTH;
477        lines.array[count++] = x + EVENT_SQUARE_WIDTH + 1;
478        lines.array[count++] = y + EVENT_SQUARE_WIDTH;
479
480        return count;
481    }
482
483    /**
484     * Attempts to draw the given event. Returns the y for the next event or the
485     * original y if the event will not fit. An event is considered to not fit
486     * if the event and its extras won't fit or if there are more events and the
487     * more events line would not fit after drawing this event.
488     *
489     * @param event the event to draw
490     * @param x the top left corner for this event's color chip
491     * @param y the top left corner for this event's color chip
492     * @return the y for the next event or the original y if it won't fit
493     */
494    protected int drawEvent(
495            Canvas canvas, Event event, int x, int y, int rightEdge, boolean moreEvents) {
496        int requiredSpace = EVENT_LINE_PADDING + mEventHeight;
497        int multiplier = 1;
498        if (moreEvents) {
499            multiplier++;
500        }
501        if (!event.allDay) {
502            multiplier++;
503        }
504        requiredSpace *= multiplier;
505        if (requiredSpace + y >= mHeight - EVENT_BOTTOM_PADDING) {
506            // Not enough space, return
507            return y;
508        }
509        r.left = x;
510        r.right = x + EVENT_SQUARE_WIDTH;
511        r.top = y;
512        r.bottom = y + EVENT_SQUARE_WIDTH;
513        p.setColor(event.color);
514        canvas.drawRect(r, p);
515
516        int textX = x + EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING;
517        int textY = y + mEventHeight - EVENT_LINE_PADDING / 2;
518        float avail = rightEdge - textX;
519        CharSequence text = TextUtils.ellipsize(
520                event.title, mEventPaint, avail, TextUtils.TruncateAt.END);
521        canvas.drawText(text.toString(), textX, textY, mEventPaint);
522        if (!event.allDay) {
523            textY += mEventHeight + EVENT_LINE_PADDING;
524            mStringBuilder.setLength(0);
525            text = DateUtils.formatDateRange(mContext, mFormatter, event.startMillis,
526                    event.endMillis, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL,
527                    Utils.getTimeZone(mContext, null)).toString();
528            text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END);
529            canvas.drawText(text.toString(), textX, textY, mEventExtrasPaint);
530        }
531
532        return textY + EVENT_LINE_PADDING;
533    }
534
535    protected void drawMoreEvents(Canvas canvas, int remainingEvents, int x) {
536        FloatRef lines = new FloatRef(4 * 4);
537        int y = mHeight - EVENT_BOTTOM_PADDING + EVENT_LINE_PADDING / 2 - mEventHeight;
538        addChipOutline(lines, 0, x, y);
539        canvas.drawLines(lines.array, mEventExtrasPaint);
540        String text = mContext.getResources().getQuantityString(
541                R.plurals.month_more_events, remainingEvents);
542        y = mHeight - EVENT_BOTTOM_PADDING;
543        x += EVENT_SQUARE_WIDTH + EVENT_LINE_PADDING;
544        mEventExtrasPaint.setFakeBoldText(true);
545        canvas.drawText(String.format(text, remainingEvents), x, y, mEventExtrasPaint);
546        mEventExtrasPaint.setFakeBoldText(false);
547    }
548
549    // Draws the DNA view of events
550
551    protected void drawBusyBits(Canvas canvas) {
552
553        int wkNumOffset = 1;
554        int effectiveWidth = mWidth - mPadding * 2 - SPACING_WEEK_NUMBER;
555        int top = DAY_SEPARATOR_OUTER_WIDTH + BUSY_BITS_MARGIN;
556        int bottom = mHeight - BUSY_BITS_MARGIN;
557
558        // Draw background for all days first since even if there are not
559        // events, we still need to show the background bar
560
561        for (int i = 1; i <= mNumDays; i++) {
562            r.top = top;
563            r.bottom = bottom;
564            r.right = i * effectiveWidth / (mNumDays) + mPadding
565                    + (SPACING_WEEK_NUMBER * wkNumOffset) - DAY_SEPARATOR_OUTER_WIDTH / 2;
566            r.left = r.right - BUSY_BITS_WIDTH;
567            p.setColor(mMonthBusyBitsBgColor);
568            p.setStyle(Style.FILL);
569            canvas.drawRect(r, p);
570        }
571
572        if (mEvents != null) {
573            p.setColor(mMonthBusyBitsFgColor);
574            p.setStyle(Style.FILL);
575            int day = 0;
576
577            // For each day draw events
578
579            for (ArrayList<Event> eventDay : mEvents) {
580                // Create a list of segments that correspond to busy times
581                ArrayList<BusyBitsSegment> segments = Utils.createBusyBitSegments(top, bottom, 0,
582                        24 * 60, mFirstJulianDay + day, eventDay);
583                day++;
584                if (segments == null) {
585                    continue;
586                }
587                // iterate and draw each segment
588                for (BusyBitsSegment s : segments) {
589                    r.right = day * effectiveWidth / (mNumDays) + mPadding
590                            + (SPACING_WEEK_NUMBER * wkNumOffset) - DAY_SEPARATOR_OUTER_WIDTH / 2;
591                    r.left = r.right - BUSY_BITS_WIDTH;
592                    r.top = s.start;
593                    r.bottom = s.end;
594                    canvas.drawRect(r, p);
595                }
596            }
597        }
598    }
599
600    @Override
601    protected void updateSelectionPositions() {
602        if (mHasSelectedDay) {
603            int selectedPosition = mSelectedDay - mWeekStart;
604            if (selectedPosition < 0) {
605                selectedPosition += 7;
606            }
607            int effectiveWidth = mWidth - mPadding * 2;
608            effectiveWidth -= SPACING_WEEK_NUMBER;
609            mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding;
610            mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding;
611            mSelectedLeft += SPACING_WEEK_NUMBER;
612            mSelectedRight += SPACING_WEEK_NUMBER;
613        }
614    }
615
616    @Override
617    public Time getDayFromLocation(float x) {
618        int dayStart = SPACING_WEEK_NUMBER + mPadding;
619        if (x < dayStart || x > mWidth - mPadding) {
620            return null;
621        }
622        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
623        int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
624        int day = mFirstJulianDay + dayPosition;
625
626        Time time = new Time(mTimeZone);
627        if (mWeek == 0) {
628            // This week is weird...
629            if (day < Time.EPOCH_JULIAN_DAY) {
630                day++;
631            } else if (day == Time.EPOCH_JULIAN_DAY) {
632                time.set(1, 0, 1970);
633                time.normalize(true);
634                return time;
635            }
636        }
637
638        time.setJulianDay(day);
639        return time;
640    }
641
642}
643