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