| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.calendar.month; |
| |
| import com.android.calendar.Event; |
| import com.android.calendar.R; |
| import com.android.calendar.Utils; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ObjectAnimator; |
| import android.app.Service; |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Paint.Align; |
| import android.graphics.Paint.Style; |
| import android.graphics.Typeface; |
| import android.graphics.drawable.Drawable; |
| import android.provider.CalendarContract.Attendees; |
| import android.text.TextPaint; |
| import android.text.TextUtils; |
| import android.text.format.DateFormat; |
| import android.text.format.DateUtils; |
| import android.text.format.Time; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityManager; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Formatter; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| |
| public class MonthWeekEventsView extends SimpleWeekView { |
| |
| private static final String TAG = "MonthView"; |
| |
| private static final boolean DEBUG_LAYOUT = false; |
| |
| public static final String VIEW_PARAMS_ORIENTATION = "orientation"; |
| public static final String VIEW_PARAMS_ANIMATE_TODAY = "animate_today"; |
| |
| /* NOTE: these are not constants, and may be multiplied by a scale factor */ |
| private static int TEXT_SIZE_MONTH_NUMBER = 32; |
| private static int TEXT_SIZE_EVENT = 12; |
| private static int TEXT_SIZE_EVENT_TITLE = 14; |
| private static int TEXT_SIZE_MORE_EVENTS = 12; |
| private static int TEXT_SIZE_MONTH_NAME = 14; |
| private static int TEXT_SIZE_WEEK_NUM = 12; |
| |
| private static int DNA_MARGIN = 4; |
| private static int DNA_ALL_DAY_HEIGHT = 4; |
| private static int DNA_MIN_SEGMENT_HEIGHT = 4; |
| private static int DNA_WIDTH = 8; |
| private static int DNA_ALL_DAY_WIDTH = 32; |
| private static int DNA_SIDE_PADDING = 6; |
| private static int CONFLICT_COLOR = Color.BLACK; |
| private static int EVENT_TEXT_COLOR = Color.WHITE; |
| |
| private static int DEFAULT_EDGE_SPACING = 0; |
| private static int SIDE_PADDING_MONTH_NUMBER = 4; |
| private static int TOP_PADDING_MONTH_NUMBER = 4; |
| private static int TOP_PADDING_WEEK_NUMBER = 4; |
| private static int SIDE_PADDING_WEEK_NUMBER = 20; |
| private static int DAY_SEPARATOR_OUTER_WIDTH = 0; |
| private static int DAY_SEPARATOR_INNER_WIDTH = 1; |
| private static int DAY_SEPARATOR_VERTICAL_LENGTH = 53; |
| private static int DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT = 64; |
| private static int MIN_WEEK_WIDTH = 50; |
| |
| private static int EVENT_X_OFFSET_LANDSCAPE = 38; |
| private static int EVENT_Y_OFFSET_LANDSCAPE = 8; |
| private static int EVENT_Y_OFFSET_PORTRAIT = 7; |
| private static int EVENT_SQUARE_WIDTH = 10; |
| private static int EVENT_SQUARE_BORDER = 2; |
| private static int EVENT_LINE_PADDING = 2; |
| private static int EVENT_RIGHT_PADDING = 4; |
| private static int EVENT_BOTTOM_PADDING = 3; |
| |
| private static int TODAY_HIGHLIGHT_WIDTH = 2; |
| |
| private static int SPACING_WEEK_NUMBER = 24; |
| private static boolean mInitialized = false; |
| private static boolean mShowDetailsInMonth; |
| |
| protected Time mToday = new Time(); |
| protected boolean mHasToday = false; |
| protected int mTodayIndex = -1; |
| protected int mOrientation = Configuration.ORIENTATION_LANDSCAPE; |
| protected List<ArrayList<Event>> mEvents = null; |
| protected ArrayList<Event> mUnsortedEvents = null; |
| HashMap<Integer, Utils.DNAStrand> mDna = null; |
| // This is for drawing the outlines around event chips and supports up to 10 |
| // events being drawn on each day. The code will expand this if necessary. |
| protected FloatRef mEventOutlines = new FloatRef(10 * 4 * 4 * 7); |
| |
| |
| |
| protected static StringBuilder mStringBuilder = new StringBuilder(50); |
| // TODO recreate formatter when locale changes |
| protected static Formatter mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); |
| |
| protected Paint mMonthNamePaint; |
| protected TextPaint mEventPaint; |
| protected TextPaint mSolidBackgroundEventPaint; |
| protected TextPaint mFramedEventPaint; |
| protected TextPaint mDeclinedEventPaint; |
| protected TextPaint mEventExtrasPaint; |
| protected TextPaint mEventDeclinedExtrasPaint; |
| protected Paint mWeekNumPaint; |
| protected Paint mDNAAllDayPaint; |
| protected Paint mDNATimePaint; |
| protected Paint mEventSquarePaint; |
| |
| |
| protected Drawable mTodayDrawable; |
| |
| protected int mMonthNumHeight; |
| protected int mMonthNumAscentHeight; |
| protected int mEventHeight; |
| protected int mEventAscentHeight; |
| protected int mExtrasHeight; |
| protected int mExtrasAscentHeight; |
| protected int mExtrasDescent; |
| protected int mWeekNumAscentHeight; |
| |
| protected int mMonthBGColor; |
| protected int mMonthBGOtherColor; |
| protected int mMonthBGTodayColor; |
| protected int mMonthNumColor; |
| protected int mMonthNumOtherColor; |
| protected int mMonthNumTodayColor; |
| protected int mMonthNameColor; |
| protected int mMonthNameOtherColor; |
| protected int mMonthEventColor; |
| protected int mMonthDeclinedEventColor; |
| protected int mMonthDeclinedExtrasColor; |
| protected int mMonthEventExtraColor; |
| protected int mMonthEventOtherColor; |
| protected int mMonthEventExtraOtherColor; |
| protected int mMonthWeekNumColor; |
| protected int mMonthBusyBitsBgColor; |
| protected int mMonthBusyBitsBusyTimeColor; |
| protected int mMonthBusyBitsConflictTimeColor; |
| private int mClickedDayIndex = -1; |
| private int mClickedDayColor; |
| private static final int mClickedAlpha = 128; |
| |
| protected int mEventChipOutlineColor = 0xFFFFFFFF; |
| protected int mDaySeparatorInnerColor; |
| protected int mTodayAnimateColor; |
| |
| private boolean mAnimateToday; |
| private int mAnimateTodayAlpha = 0; |
| private ObjectAnimator mTodayAnimator = null; |
| |
| private final TodayAnimatorListener mAnimatorListener = new TodayAnimatorListener(); |
| |
| class TodayAnimatorListener extends AnimatorListenerAdapter { |
| private volatile Animator mAnimator = null; |
| private volatile boolean mFadingIn = false; |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| synchronized (this) { |
| if (mAnimator != animation) { |
| animation.removeAllListeners(); |
| animation.cancel(); |
| return; |
| } |
| if (mFadingIn) { |
| if (mTodayAnimator != null) { |
| mTodayAnimator.removeAllListeners(); |
| mTodayAnimator.cancel(); |
| } |
| mTodayAnimator = ObjectAnimator.ofInt(MonthWeekEventsView.this, |
| "animateTodayAlpha", 255, 0); |
| mAnimator = mTodayAnimator; |
| mFadingIn = false; |
| mTodayAnimator.addListener(this); |
| mTodayAnimator.setDuration(600); |
| mTodayAnimator.start(); |
| } else { |
| mAnimateToday = false; |
| mAnimateTodayAlpha = 0; |
| mAnimator.removeAllListeners(); |
| mAnimator = null; |
| mTodayAnimator = null; |
| invalidate(); |
| } |
| } |
| } |
| |
| public void setAnimator(Animator animation) { |
| mAnimator = animation; |
| } |
| |
| public void setFadingIn(boolean fadingIn) { |
| mFadingIn = fadingIn; |
| } |
| |
| } |
| |
| private int[] mDayXs; |
| |
| /** |
| * This provides a reference to a float array which allows for easy size |
| * checking and reallocation. Used for drawing lines. |
| */ |
| private class FloatRef { |
| float[] array; |
| |
| public FloatRef(int size) { |
| array = new float[size]; |
| } |
| |
| public void ensureSize(int newSize) { |
| if (newSize >= array.length) { |
| // Add enough space for 7 more boxes to be drawn |
| array = Arrays.copyOf(array, newSize + 16 * 7); |
| } |
| } |
| } |
| |
| /** |
| * Shows up as an error if we don't include this. |
| */ |
| public MonthWeekEventsView(Context context) { |
| super(context); |
| } |
| |
| // Sets the list of events for this week. Takes a sorted list of arrays |
| // divided up by day for generating the large month version and the full |
| // arraylist sorted by start time to generate the dna version. |
| public void setEvents(List<ArrayList<Event>> sortedEvents, ArrayList<Event> unsortedEvents) { |
| setEvents(sortedEvents); |
| // The MIN_WEEK_WIDTH is a hack to prevent the view from trying to |
| // generate dna bits before its width has been fixed. |
| createDna(unsortedEvents); |
| } |
| |
| /** |
| * Sets up the dna bits for the view. This will return early if the view |
| * isn't in a state that will create a valid set of dna yet (such as the |
| * views width not being set correctly yet). |
| */ |
| public void createDna(ArrayList<Event> unsortedEvents) { |
| if (unsortedEvents == null || mWidth <= MIN_WEEK_WIDTH || getContext() == null) { |
| // Stash the list of events for use when this view is ready, or |
| // just clear it if a null set has been passed to this view |
| mUnsortedEvents = unsortedEvents; |
| mDna = null; |
| return; |
| } else { |
| // clear the cached set of events since we're ready to build it now |
| mUnsortedEvents = null; |
| } |
| // Create the drawing coordinates for dna |
| if (!mShowDetailsInMonth) { |
| int numDays = mEvents.size(); |
| int effectiveWidth = mWidth - mPadding * 2; |
| if (mShowWeekNum) { |
| effectiveWidth -= SPACING_WEEK_NUMBER; |
| } |
| DNA_ALL_DAY_WIDTH = effectiveWidth / numDays - 2 * DNA_SIDE_PADDING; |
| mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH); |
| mDayXs = new int[numDays]; |
| for (int day = 0; day < numDays; day++) { |
| mDayXs[day] = computeDayLeftPosition(day) + DNA_WIDTH / 2 + DNA_SIDE_PADDING; |
| |
| } |
| |
| int top = DAY_SEPARATOR_INNER_WIDTH + DNA_MARGIN + DNA_ALL_DAY_HEIGHT + 1; |
| int bottom = mHeight - DNA_MARGIN; |
| mDna = Utils.createDNAStrands(mFirstJulianDay, unsortedEvents, top, bottom, |
| DNA_MIN_SEGMENT_HEIGHT, mDayXs, getContext()); |
| } |
| } |
| |
| public void setEvents(List<ArrayList<Event>> sortedEvents) { |
| mEvents = sortedEvents; |
| if (sortedEvents == null) { |
| return; |
| } |
| if (sortedEvents.size() != mNumDays) { |
| if (Log.isLoggable(TAG, Log.ERROR)) { |
| Log.wtf(TAG, "Events size must be same as days displayed: size=" |
| + sortedEvents.size() + " days=" + mNumDays); |
| } |
| mEvents = null; |
| return; |
| } |
| } |
| |
| protected void loadColors(Context context) { |
| Resources res = context.getResources(); |
| mMonthWeekNumColor = res.getColor(R.color.month_week_num_color); |
| mMonthNumColor = res.getColor(R.color.month_day_number); |
| mMonthNumOtherColor = res.getColor(R.color.month_day_number_other); |
| mMonthNumTodayColor = res.getColor(R.color.month_today_number); |
| mMonthNameColor = mMonthNumColor; |
| mMonthNameOtherColor = mMonthNumOtherColor; |
| mMonthEventColor = res.getColor(R.color.month_event_color); |
| mMonthDeclinedEventColor = res.getColor(R.color.agenda_item_declined_color); |
| mMonthDeclinedExtrasColor = res.getColor(R.color.agenda_item_where_declined_text_color); |
| mMonthEventExtraColor = res.getColor(R.color.month_event_extra_color); |
| mMonthEventOtherColor = res.getColor(R.color.month_event_other_color); |
| mMonthEventExtraOtherColor = res.getColor(R.color.month_event_extra_other_color); |
| mMonthBGTodayColor = res.getColor(R.color.month_today_bgcolor); |
| mMonthBGOtherColor = res.getColor(R.color.month_other_bgcolor); |
| mMonthBGColor = res.getColor(R.color.month_bgcolor); |
| mDaySeparatorInnerColor = res.getColor(R.color.month_grid_lines); |
| mTodayAnimateColor = res.getColor(R.color.today_highlight_color); |
| mClickedDayColor = res.getColor(R.color.day_clicked_background_color); |
| mTodayDrawable = res.getDrawable(R.drawable.today_blue_week_holo_light); |
| } |
| |
| /** |
| * Sets up the text and style properties for painting. Override this if you |
| * want to use a different paint. |
| */ |
| @Override |
| protected void initView() { |
| super.initView(); |
| |
| if (!mInitialized) { |
| Resources resources = getContext().getResources(); |
| mShowDetailsInMonth = Utils.getConfigBool(getContext(), R.bool.show_details_in_month); |
| TEXT_SIZE_EVENT_TITLE = resources.getInteger(R.integer.text_size_event_title); |
| TEXT_SIZE_MONTH_NUMBER = resources.getInteger(R.integer.text_size_month_number); |
| SIDE_PADDING_MONTH_NUMBER = resources.getInteger(R.integer.month_day_number_margin); |
| CONFLICT_COLOR = resources.getColor(R.color.month_dna_conflict_time_color); |
| EVENT_TEXT_COLOR = resources.getColor(R.color.calendar_event_text_color); |
| if (mScale != 1) { |
| TOP_PADDING_MONTH_NUMBER *= mScale; |
| TOP_PADDING_WEEK_NUMBER *= mScale; |
| SIDE_PADDING_MONTH_NUMBER *= mScale; |
| SIDE_PADDING_WEEK_NUMBER *= mScale; |
| SPACING_WEEK_NUMBER *= mScale; |
| TEXT_SIZE_MONTH_NUMBER *= mScale; |
| TEXT_SIZE_EVENT *= mScale; |
| TEXT_SIZE_EVENT_TITLE *= mScale; |
| TEXT_SIZE_MORE_EVENTS *= mScale; |
| TEXT_SIZE_MONTH_NAME *= mScale; |
| TEXT_SIZE_WEEK_NUM *= mScale; |
| DAY_SEPARATOR_OUTER_WIDTH *= mScale; |
| DAY_SEPARATOR_INNER_WIDTH *= mScale; |
| DAY_SEPARATOR_VERTICAL_LENGTH *= mScale; |
| DAY_SEPARATOR_VERTICAL_LENGHT_PORTRAIT *= mScale; |
| EVENT_X_OFFSET_LANDSCAPE *= mScale; |
| EVENT_Y_OFFSET_LANDSCAPE *= mScale; |
| EVENT_Y_OFFSET_PORTRAIT *= mScale; |
| EVENT_SQUARE_WIDTH *= mScale; |
| EVENT_SQUARE_BORDER *= mScale; |
| EVENT_LINE_PADDING *= mScale; |
| EVENT_BOTTOM_PADDING *= mScale; |
| EVENT_RIGHT_PADDING *= mScale; |
| DNA_MARGIN *= mScale; |
| DNA_WIDTH *= mScale; |
| DNA_ALL_DAY_HEIGHT *= mScale; |
| DNA_MIN_SEGMENT_HEIGHT *= mScale; |
| DNA_SIDE_PADDING *= mScale; |
| DEFAULT_EDGE_SPACING *= mScale; |
| DNA_ALL_DAY_WIDTH *= mScale; |
| TODAY_HIGHLIGHT_WIDTH *= mScale; |
| } |
| if (!mShowDetailsInMonth) { |
| TOP_PADDING_MONTH_NUMBER += DNA_ALL_DAY_HEIGHT + DNA_MARGIN; |
| } |
| mInitialized = true; |
| } |
| mPadding = DEFAULT_EDGE_SPACING; |
| loadColors(getContext()); |
| // TODO modify paint properties depending on isMini |
| |
| mMonthNumPaint = new Paint(); |
| mMonthNumPaint.setFakeBoldText(false); |
| mMonthNumPaint.setAntiAlias(true); |
| mMonthNumPaint.setTextSize(TEXT_SIZE_MONTH_NUMBER); |
| mMonthNumPaint.setColor(mMonthNumColor); |
| mMonthNumPaint.setStyle(Style.FILL); |
| mMonthNumPaint.setTextAlign(Align.RIGHT); |
| mMonthNumPaint.setTypeface(Typeface.DEFAULT); |
| |
| mMonthNumAscentHeight = (int) (-mMonthNumPaint.ascent() + 0.5f); |
| mMonthNumHeight = (int) (mMonthNumPaint.descent() - mMonthNumPaint.ascent() + 0.5f); |
| |
| mEventPaint = new TextPaint(); |
| mEventPaint.setFakeBoldText(true); |
| mEventPaint.setAntiAlias(true); |
| mEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE); |
| mEventPaint.setColor(mMonthEventColor); |
| |
| mSolidBackgroundEventPaint = new TextPaint(mEventPaint); |
| mSolidBackgroundEventPaint.setColor(EVENT_TEXT_COLOR); |
| mFramedEventPaint = new TextPaint(mSolidBackgroundEventPaint); |
| |
| mDeclinedEventPaint = new TextPaint(); |
| mDeclinedEventPaint.setFakeBoldText(true); |
| mDeclinedEventPaint.setAntiAlias(true); |
| mDeclinedEventPaint.setTextSize(TEXT_SIZE_EVENT_TITLE); |
| mDeclinedEventPaint.setColor(mMonthDeclinedEventColor); |
| |
| mEventAscentHeight = (int) (-mEventPaint.ascent() + 0.5f); |
| mEventHeight = (int) (mEventPaint.descent() - mEventPaint.ascent() + 0.5f); |
| |
| mEventExtrasPaint = new TextPaint(); |
| mEventExtrasPaint.setFakeBoldText(false); |
| mEventExtrasPaint.setAntiAlias(true); |
| mEventExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER); |
| mEventExtrasPaint.setTextSize(TEXT_SIZE_EVENT); |
| mEventExtrasPaint.setColor(mMonthEventExtraColor); |
| mEventExtrasPaint.setStyle(Style.FILL); |
| mEventExtrasPaint.setTextAlign(Align.LEFT); |
| mExtrasHeight = (int)(mEventExtrasPaint.descent() - mEventExtrasPaint.ascent() + 0.5f); |
| mExtrasAscentHeight = (int)(-mEventExtrasPaint.ascent() + 0.5f); |
| mExtrasDescent = (int)(mEventExtrasPaint.descent() + 0.5f); |
| |
| mEventDeclinedExtrasPaint = new TextPaint(); |
| mEventDeclinedExtrasPaint.setFakeBoldText(false); |
| mEventDeclinedExtrasPaint.setAntiAlias(true); |
| mEventDeclinedExtrasPaint.setStrokeWidth(EVENT_SQUARE_BORDER); |
| mEventDeclinedExtrasPaint.setTextSize(TEXT_SIZE_EVENT); |
| mEventDeclinedExtrasPaint.setColor(mMonthDeclinedExtrasColor); |
| mEventDeclinedExtrasPaint.setStyle(Style.FILL); |
| mEventDeclinedExtrasPaint.setTextAlign(Align.LEFT); |
| |
| mWeekNumPaint = new Paint(); |
| mWeekNumPaint.setFakeBoldText(false); |
| mWeekNumPaint.setAntiAlias(true); |
| mWeekNumPaint.setTextSize(TEXT_SIZE_WEEK_NUM); |
| mWeekNumPaint.setColor(mWeekNumColor); |
| mWeekNumPaint.setStyle(Style.FILL); |
| mWeekNumPaint.setTextAlign(Align.RIGHT); |
| |
| mWeekNumAscentHeight = (int) (-mWeekNumPaint.ascent() + 0.5f); |
| |
| mDNAAllDayPaint = new Paint(); |
| mDNATimePaint = new Paint(); |
| mDNATimePaint.setColor(mMonthBusyBitsBusyTimeColor); |
| mDNATimePaint.setStyle(Style.FILL_AND_STROKE); |
| mDNATimePaint.setStrokeWidth(DNA_WIDTH); |
| mDNATimePaint.setAntiAlias(false); |
| mDNAAllDayPaint.setColor(mMonthBusyBitsConflictTimeColor); |
| mDNAAllDayPaint.setStyle(Style.FILL_AND_STROKE); |
| mDNAAllDayPaint.setStrokeWidth(DNA_ALL_DAY_WIDTH); |
| mDNAAllDayPaint.setAntiAlias(false); |
| |
| mEventSquarePaint = new Paint(); |
| mEventSquarePaint.setStrokeWidth(EVENT_SQUARE_BORDER); |
| mEventSquarePaint.setAntiAlias(false); |
| |
| if (DEBUG_LAYOUT) { |
| Log.d("EXTRA", "mScale=" + mScale); |
| Log.d("EXTRA", "mMonthNumPaint ascent=" + mMonthNumPaint.ascent() |
| + " descent=" + mMonthNumPaint.descent() + " int height=" + mMonthNumHeight); |
| Log.d("EXTRA", "mEventPaint ascent=" + mEventPaint.ascent() |
| + " descent=" + mEventPaint.descent() + " int height=" + mEventHeight |
| + " int ascent=" + mEventAscentHeight); |
| Log.d("EXTRA", "mEventExtrasPaint ascent=" + mEventExtrasPaint.ascent() |
| + " descent=" + mEventExtrasPaint.descent() + " int height=" + mExtrasHeight); |
| Log.d("EXTRA", "mWeekNumPaint ascent=" + mWeekNumPaint.ascent() |
| + " descent=" + mWeekNumPaint.descent()); |
| } |
| } |
| |
| @Override |
| public void setWeekParams(HashMap<String, Integer> params, String tz) { |
| super.setWeekParams(params, tz); |
| |
| if (params.containsKey(VIEW_PARAMS_ORIENTATION)) { |
| mOrientation = params.get(VIEW_PARAMS_ORIENTATION); |
| } |
| |
| updateToday(tz); |
| mNumCells = mNumDays + 1; |
| |
| if (params.containsKey(VIEW_PARAMS_ANIMATE_TODAY) && mHasToday) { |
| synchronized (mAnimatorListener) { |
| if (mTodayAnimator != null) { |
| mTodayAnimator.removeAllListeners(); |
| mTodayAnimator.cancel(); |
| } |
| mTodayAnimator = ObjectAnimator.ofInt(this, "animateTodayAlpha", |
| Math.max(mAnimateTodayAlpha, 80), 255); |
| mTodayAnimator.setDuration(150); |
| mAnimatorListener.setAnimator(mTodayAnimator); |
| mAnimatorListener.setFadingIn(true); |
| mTodayAnimator.addListener(mAnimatorListener); |
| mAnimateToday = true; |
| mTodayAnimator.start(); |
| } |
| } |
| } |
| |
| /** |
| * @param tz |
| */ |
| public boolean updateToday(String tz) { |
| mToday.timezone = tz; |
| mToday.setToNow(); |
| mToday.normalize(true); |
| int julianToday = Time.getJulianDay(mToday.toMillis(false), mToday.gmtoff); |
| if (julianToday >= mFirstJulianDay && julianToday < mFirstJulianDay + mNumDays) { |
| mHasToday = true; |
| mTodayIndex = julianToday - mFirstJulianDay; |
| } else { |
| mHasToday = false; |
| mTodayIndex = -1; |
| } |
| return mHasToday; |
| } |
| |
| public void setAnimateTodayAlpha(int alpha) { |
| mAnimateTodayAlpha = alpha; |
| invalidate(); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| drawBackground(canvas); |
| drawWeekNums(canvas); |
| drawDaySeparators(canvas); |
| if (mHasToday && mAnimateToday) { |
| drawToday(canvas); |
| } |
| if (mShowDetailsInMonth) { |
| drawEvents(canvas); |
| } else { |
| if (mDna == null && mUnsortedEvents != null) { |
| createDna(mUnsortedEvents); |
| } |
| drawDNA(canvas); |
| } |
| drawClick(canvas); |
| } |
| |
| protected void drawToday(Canvas canvas) { |
| r.top = DAY_SEPARATOR_INNER_WIDTH + (TODAY_HIGHLIGHT_WIDTH / 2); |
| r.bottom = mHeight - (int) Math.ceil(TODAY_HIGHLIGHT_WIDTH / 2.0f); |
| p.setStyle(Style.STROKE); |
| p.setStrokeWidth(TODAY_HIGHLIGHT_WIDTH); |
| r.left = computeDayLeftPosition(mTodayIndex) + (TODAY_HIGHLIGHT_WIDTH / 2); |
| r.right = computeDayLeftPosition(mTodayIndex + 1) |
| - (int) Math.ceil(TODAY_HIGHLIGHT_WIDTH / 2.0f); |
| p.setColor(mTodayAnimateColor | (mAnimateTodayAlpha << 24)); |
| canvas.drawRect(r, p); |
| p.setStyle(Style.FILL); |
| } |
| |
| // TODO move into SimpleWeekView |
| // Computes the x position for the left side of the given day |
| private int computeDayLeftPosition(int day) { |
| int effectiveWidth = mWidth; |
| int x = 0; |
| int xOffset = 0; |
| if (mShowWeekNum) { |
| xOffset = SPACING_WEEK_NUMBER + mPadding; |
| effectiveWidth -= xOffset; |
| } |
| x = day * effectiveWidth / mNumDays + xOffset; |
| return x; |
| } |
| |
| @Override |
| protected void drawDaySeparators(Canvas canvas) { |
| float lines[] = new float[8 * 4]; |
| int count = 6 * 4; |
| int wkNumOffset = 0; |
| int i = 0; |
| if (mShowWeekNum) { |
| // This adds the first line separating the week number |
| int xOffset = SPACING_WEEK_NUMBER + mPadding; |
| count += 4; |
| lines[i++] = xOffset; |
| lines[i++] = 0; |
| lines[i++] = xOffset; |
| lines[i++] = mHeight; |
| wkNumOffset++; |
| } |
| count += 4; |
| lines[i++] = 0; |
| lines[i++] = 0; |
| lines[i++] = mWidth; |
| lines[i++] = 0; |
| int y0 = 0; |
| int y1 = mHeight; |
| |
| while (i < count) { |
| int x = computeDayLeftPosition(i / 4 - wkNumOffset); |
| lines[i++] = x; |
| lines[i++] = y0; |
| lines[i++] = x; |
| lines[i++] = y1; |
| } |
| p.setColor(mDaySeparatorInnerColor); |
| p.setStrokeWidth(DAY_SEPARATOR_INNER_WIDTH); |
| canvas.drawLines(lines, 0, count, p); |
| } |
| |
| @Override |
| protected void drawBackground(Canvas canvas) { |
| int i = 0; |
| int offset = 0; |
| r.top = DAY_SEPARATOR_INNER_WIDTH; |
| r.bottom = mHeight; |
| if (mShowWeekNum) { |
| i++; |
| offset++; |
| } |
| if (!mOddMonth[i]) { |
| while (++i < mOddMonth.length && !mOddMonth[i]) |
| ; |
| r.right = computeDayLeftPosition(i - offset); |
| r.left = 0; |
| p.setColor(mMonthBGOtherColor); |
| canvas.drawRect(r, p); |
| // compute left edge for i, set up r, draw |
| } else if (!mOddMonth[(i = mOddMonth.length - 1)]) { |
| while (--i >= offset && !mOddMonth[i]) |
| ; |
| i++; |
| // compute left edge for i, set up r, draw |
| r.right = mWidth; |
| r.left = computeDayLeftPosition(i - offset); |
| p.setColor(mMonthBGOtherColor); |
| canvas.drawRect(r, p); |
| } |
| if (mHasToday) { |
| p.setColor(mMonthBGTodayColor); |
| r.left = computeDayLeftPosition(mTodayIndex); |
| r.right = computeDayLeftPosition(mTodayIndex + 1); |
| canvas.drawRect(r, p); |
| } |
| } |
| |
| // Draw the "clicked" color on the tapped day |
| private void drawClick(Canvas canvas) { |
| if (mClickedDayIndex != -1) { |
| int alpha = p.getAlpha(); |
| p.setColor(mClickedDayColor); |
| p.setAlpha(mClickedAlpha); |
| r.left = computeDayLeftPosition(mClickedDayIndex); |
| r.right = computeDayLeftPosition(mClickedDayIndex + 1); |
| r.top = DAY_SEPARATOR_INNER_WIDTH; |
| r.bottom = mHeight; |
| canvas.drawRect(r, p); |
| p.setAlpha(alpha); |
| } |
| } |
| |
| @Override |
| protected void drawWeekNums(Canvas canvas) { |
| int y; |
| |
| int i = 0; |
| int offset = -1; |
| int todayIndex = mTodayIndex; |
| int x = 0; |
| int numCount = mNumDays; |
| if (mShowWeekNum) { |
| x = SIDE_PADDING_WEEK_NUMBER + mPadding; |
| y = mWeekNumAscentHeight + TOP_PADDING_WEEK_NUMBER; |
| canvas.drawText(mDayNumbers[0], x, y, mWeekNumPaint); |
| numCount++; |
| i++; |
| todayIndex++; |
| offset++; |
| |
| } |
| |
| y = mMonthNumAscentHeight + TOP_PADDING_MONTH_NUMBER; |
| |
| boolean isFocusMonth = mFocusDay[i]; |
| boolean isBold = false; |
| mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor); |
| for (; i < numCount; i++) { |
| if (mHasToday && todayIndex == i) { |
| mMonthNumPaint.setColor(mMonthNumTodayColor); |
| mMonthNumPaint.setFakeBoldText(isBold = true); |
| if (i + 1 < numCount) { |
| // Make sure the color will be set back on the next |
| // iteration |
| isFocusMonth = !mFocusDay[i + 1]; |
| } |
| } else if (mFocusDay[i] != isFocusMonth) { |
| isFocusMonth = mFocusDay[i]; |
| mMonthNumPaint.setColor(isFocusMonth ? mMonthNumColor : mMonthNumOtherColor); |
| } |
| x = computeDayLeftPosition(i - offset) - (SIDE_PADDING_MONTH_NUMBER); |
| canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint); |
| if (isBold) { |
| mMonthNumPaint.setFakeBoldText(isBold = false); |
| } |
| } |
| } |
| |
| protected void drawEvents(Canvas canvas) { |
| if (mEvents == null) { |
| return; |
| } |
| |
| int day = -1; |
| for (ArrayList<Event> eventDay : mEvents) { |
| day++; |
| if (eventDay == null || eventDay.size() == 0) { |
| continue; |
| } |
| int ySquare; |
| int xSquare = computeDayLeftPosition(day) + SIDE_PADDING_MONTH_NUMBER + 1; |
| int rightEdge = computeDayLeftPosition(day + 1); |
| |
| if (mOrientation == Configuration.ORIENTATION_PORTRAIT) { |
| ySquare = EVENT_Y_OFFSET_PORTRAIT + mMonthNumHeight + TOP_PADDING_MONTH_NUMBER; |
| rightEdge -= SIDE_PADDING_MONTH_NUMBER + 1; |
| } else { |
| ySquare = EVENT_Y_OFFSET_LANDSCAPE; |
| rightEdge -= EVENT_X_OFFSET_LANDSCAPE; |
| } |
| |
| // Determine if everything will fit when time ranges are shown. |
| boolean showTimes = true; |
| Iterator<Event> iter = eventDay.iterator(); |
| int yTest = ySquare; |
| while (iter.hasNext()) { |
| Event event = iter.next(); |
| int newY = drawEvent(canvas, event, xSquare, yTest, rightEdge, iter.hasNext(), |
| showTimes, /*doDraw*/ false); |
| if (newY == yTest) { |
| showTimes = false; |
| break; |
| } |
| yTest = newY; |
| } |
| |
| int eventCount = 0; |
| iter = eventDay.iterator(); |
| while (iter.hasNext()) { |
| Event event = iter.next(); |
| int newY = drawEvent(canvas, event, xSquare, ySquare, rightEdge, iter.hasNext(), |
| showTimes, /*doDraw*/ true); |
| if (newY == ySquare) { |
| break; |
| } |
| eventCount++; |
| ySquare = newY; |
| } |
| |
| int remaining = eventDay.size() - eventCount; |
| if (remaining > 0) { |
| drawMoreEvents(canvas, remaining, xSquare); |
| } |
| } |
| } |
| |
| protected int addChipOutline(FloatRef lines, int count, int x, int y) { |
| lines.ensureSize(count + 16); |
| // top of box |
| lines.array[count++] = x; |
| lines.array[count++] = y; |
| lines.array[count++] = x + EVENT_SQUARE_WIDTH; |
| lines.array[count++] = y; |
| // right side of box |
| lines.array[count++] = x + EVENT_SQUARE_WIDTH; |
| lines.array[count++] = y; |
| lines.array[count++] = x + EVENT_SQUARE_WIDTH; |
| lines.array[count++] = y + EVENT_SQUARE_WIDTH; |
| // left side of box |
| lines.array[count++] = x; |
| lines.array[count++] = y; |
| lines.array[count++] = x; |
| lines.array[count++] = y + EVENT_SQUARE_WIDTH + 1; |
| // bottom of box |
| lines.array[count++] = x; |
| lines.array[count++] = y + EVENT_SQUARE_WIDTH; |
| lines.array[count++] = x + EVENT_SQUARE_WIDTH + 1; |
| lines.array[count++] = y + EVENT_SQUARE_WIDTH; |
| |
| return count; |
| } |
| |
| /** |
| * Attempts to draw the given event. Returns the y for the next event or the |
| * original y if the event will not fit. An event is considered to not fit |
| * if the event and its extras won't fit or if there are more events and the |
| * more events line would not fit after drawing this event. |
| * |
| * @param canvas the canvas to draw on |
| * @param event the event to draw |
| * @param x the top left corner for this event's color chip |
| * @param y the top left corner for this event's color chip |
| * @param rightEdge the rightmost point we're allowed to draw on (exclusive) |
| * @param moreEvents indicates whether additional events will follow this one |
| * @param showTimes if set, a second line with a time range will be displayed for non-all-day |
| * events |
| * @param doDraw if set, do the actual drawing; otherwise this just computes the height |
| * and returns |
| * @return the y for the next event or the original y if it won't fit |
| */ |
| protected int drawEvent(Canvas canvas, Event event, int x, int y, int rightEdge, |
| boolean moreEvents, boolean showTimes, boolean doDraw) { |
| /* |
| * Vertical layout: |
| * (top of box) |
| * a. EVENT_Y_OFFSET_LANDSCAPE or portrait equivalent |
| * b. Event title: mEventHeight for a normal event, + 2xBORDER_SPACE for all-day event |
| * c. [optional] Time range (mExtrasHeight) |
| * d. EVENT_LINE_PADDING |
| * |
| * Repeat (b,c,d) as needed and space allows. If we have more events than fit, we need |
| * to leave room for something like "+2" at the bottom: |
| * |
| * e. "+ more" line (mExtrasHeight) |
| * |
| * f. EVENT_BOTTOM_PADDING (overlaps EVENT_LINE_PADDING) |
| * (bottom of box) |
| */ |
| final int BORDER_SPACE = EVENT_SQUARE_BORDER + 1; // want a 1-pixel gap inside border |
| final int STROKE_WIDTH_ADJ = EVENT_SQUARE_BORDER / 2; // adjust bounds for stroke width |
| boolean allDay = event.allDay; |
| int eventRequiredSpace = mEventHeight; |
| if (allDay) { |
| // Add a few pixels for the box we draw around all-day events. |
| eventRequiredSpace += BORDER_SPACE * 2; |
| } else if (showTimes) { |
| // Need room for the "1pm - 2pm" line. |
| eventRequiredSpace += mExtrasHeight; |
| } |
| int reservedSpace = EVENT_BOTTOM_PADDING; // leave a bit of room at the bottom |
| if (moreEvents) { |
| // More events follow. Leave a bit of space between events. |
| eventRequiredSpace += EVENT_LINE_PADDING; |
| |
| // Make sure we have room for the "+ more" line. (The "+ more" line is expected |
| // to be <= the height of an event line, so we won't show "+1" when we could be |
| // showing the event.) |
| reservedSpace += mExtrasHeight; |
| } |
| |
| if (y + eventRequiredSpace + reservedSpace > mHeight) { |
| // Not enough space, return original y |
| return y; |
| } else if (!doDraw) { |
| return y + eventRequiredSpace; |
| } |
| |
| boolean isDeclined = event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED; |
| int color = event.color; |
| if (isDeclined) { |
| color = Utils.getDeclinedColorFromColor(color); |
| } |
| |
| int textX, textY, textRightEdge; |
| |
| if (allDay) { |
| // We shift the render offset "inward", because drawRect with a stroke width greater |
| // than 1 draws outside the specified bounds. (We don't adjust the left edge, since |
| // we want to match the existing appearance of the "event square".) |
| r.left = x; |
| r.right = rightEdge - STROKE_WIDTH_ADJ; |
| r.top = y + STROKE_WIDTH_ADJ; |
| r.bottom = y + mEventHeight + BORDER_SPACE * 2 - STROKE_WIDTH_ADJ; |
| textX = x + BORDER_SPACE; |
| textY = y + mEventAscentHeight + BORDER_SPACE; |
| textRightEdge = rightEdge - BORDER_SPACE; |
| } else { |
| r.left = x; |
| r.right = x + EVENT_SQUARE_WIDTH; |
| r.bottom = y + mEventAscentHeight; |
| r.top = r.bottom - EVENT_SQUARE_WIDTH; |
| textX = x + EVENT_SQUARE_WIDTH + EVENT_RIGHT_PADDING; |
| textY = y + mEventAscentHeight; |
| textRightEdge = rightEdge; |
| } |
| |
| Style boxStyle = Style.STROKE; |
| boolean solidBackground = false; |
| if (event.selfAttendeeStatus != Attendees.ATTENDEE_STATUS_INVITED) { |
| boxStyle = Style.FILL_AND_STROKE; |
| if (allDay) { |
| solidBackground = true; |
| } |
| } |
| mEventSquarePaint.setStyle(boxStyle); |
| mEventSquarePaint.setColor(color); |
| canvas.drawRect(r, mEventSquarePaint); |
| |
| float avail = textRightEdge - textX; |
| CharSequence text = TextUtils.ellipsize( |
| event.title, mEventPaint, avail, TextUtils.TruncateAt.END); |
| Paint textPaint; |
| if (solidBackground) { |
| // Text color needs to contrast with solid background. |
| textPaint = mSolidBackgroundEventPaint; |
| } else if (isDeclined) { |
| // Use "declined event" color. |
| textPaint = mDeclinedEventPaint; |
| } else if (allDay) { |
| // Text inside frame is same color as frame. |
| mFramedEventPaint.setColor(color); |
| textPaint = mFramedEventPaint; |
| } else { |
| // Use generic event text color. |
| textPaint = mEventPaint; |
| } |
| canvas.drawText(text.toString(), textX, textY, textPaint); |
| y += mEventHeight; |
| if (allDay) { |
| y += BORDER_SPACE * 2; |
| } |
| |
| if (showTimes && !allDay) { |
| // show start/end time, e.g. "1pm - 2pm" |
| textY = y + mExtrasAscentHeight; |
| mStringBuilder.setLength(0); |
| text = DateUtils.formatDateRange(getContext(), mFormatter, event.startMillis, |
| event.endMillis, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL, |
| Utils.getTimeZone(getContext(), null)).toString(); |
| text = TextUtils.ellipsize(text, mEventExtrasPaint, avail, TextUtils.TruncateAt.END); |
| canvas.drawText(text.toString(), textX, textY, isDeclined ? mEventDeclinedExtrasPaint |
| : mEventExtrasPaint); |
| y += mExtrasHeight; |
| } |
| |
| y += EVENT_LINE_PADDING; |
| |
| return y; |
| } |
| |
| protected void drawMoreEvents(Canvas canvas, int remainingEvents, int x) { |
| int y = mHeight - (mExtrasDescent + EVENT_BOTTOM_PADDING); |
| String text = getContext().getResources().getQuantityString( |
| R.plurals.month_more_events, remainingEvents); |
| mEventExtrasPaint.setAntiAlias(true); |
| mEventExtrasPaint.setFakeBoldText(true); |
| canvas.drawText(String.format(text, remainingEvents), x, y, mEventExtrasPaint); |
| mEventExtrasPaint.setFakeBoldText(false); |
| } |
| |
| /** |
| * Draws a line showing busy times in each day of week The method draws |
| * non-conflicting times in the event color and times with conflicting |
| * events in the dna conflict color defined in colors. |
| * |
| * @param canvas |
| */ |
| protected void drawDNA(Canvas canvas) { |
| // Draw event and conflict times |
| if (mDna != null) { |
| for (Utils.DNAStrand strand : mDna.values()) { |
| if (strand.color == CONFLICT_COLOR || strand.points == null |
| || strand.points.length == 0) { |
| continue; |
| } |
| mDNATimePaint.setColor(strand.color); |
| canvas.drawLines(strand.points, mDNATimePaint); |
| } |
| // Draw black last to make sure it's on top |
| Utils.DNAStrand strand = mDna.get(CONFLICT_COLOR); |
| if (strand != null && strand.points != null && strand.points.length != 0) { |
| mDNATimePaint.setColor(strand.color); |
| canvas.drawLines(strand.points, mDNATimePaint); |
| } |
| if (mDayXs == null) { |
| return; |
| } |
| int numDays = mDayXs.length; |
| int xOffset = (DNA_ALL_DAY_WIDTH - DNA_WIDTH) / 2; |
| if (strand != null && strand.allDays != null && strand.allDays.length == numDays) { |
| for (int i = 0; i < numDays; i++) { |
| // this adds at most 7 draws. We could sort it by color and |
| // build an array instead but this is easier. |
| if (strand.allDays[i] != 0) { |
| mDNAAllDayPaint.setColor(strand.allDays[i]); |
| canvas.drawLine(mDayXs[i] + xOffset, DNA_MARGIN, mDayXs[i] + xOffset, |
| DNA_MARGIN + DNA_ALL_DAY_HEIGHT, mDNAAllDayPaint); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void updateSelectionPositions() { |
| if (mHasSelectedDay) { |
| int selectedPosition = mSelectedDay - mWeekStart; |
| if (selectedPosition < 0) { |
| selectedPosition += 7; |
| } |
| int effectiveWidth = mWidth - mPadding * 2; |
| effectiveWidth -= SPACING_WEEK_NUMBER; |
| mSelectedLeft = selectedPosition * effectiveWidth / mNumDays + mPadding; |
| mSelectedRight = (selectedPosition + 1) * effectiveWidth / mNumDays + mPadding; |
| mSelectedLeft += SPACING_WEEK_NUMBER; |
| mSelectedRight += SPACING_WEEK_NUMBER; |
| } |
| } |
| |
| public int getDayIndexFromLocation(float x) { |
| int dayStart = mShowWeekNum ? SPACING_WEEK_NUMBER + mPadding : mPadding; |
| if (x < dayStart || x > mWidth - mPadding) { |
| return -1; |
| } |
| // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels |
| return ((int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding))); |
| } |
| |
| @Override |
| public Time getDayFromLocation(float x) { |
| int dayPosition = getDayIndexFromLocation(x); |
| if (dayPosition == -1) { |
| return null; |
| } |
| int day = mFirstJulianDay + dayPosition; |
| |
| Time time = new Time(mTimeZone); |
| if (mWeek == 0) { |
| // This week is weird... |
| if (day < Time.EPOCH_JULIAN_DAY) { |
| day++; |
| } else if (day == Time.EPOCH_JULIAN_DAY) { |
| time.set(1, 0, 1970); |
| time.normalize(true); |
| return time; |
| } |
| } |
| |
| time.setJulianDay(day); |
| return time; |
| } |
| |
| @Override |
| public boolean onHoverEvent(MotionEvent event) { |
| Context context = getContext(); |
| // only send accessibility events if accessibility and exploration are |
| // on. |
| AccessibilityManager am = (AccessibilityManager) context |
| .getSystemService(Service.ACCESSIBILITY_SERVICE); |
| if (!am.isEnabled() || !am.isTouchExplorationEnabled()) { |
| return super.onHoverEvent(event); |
| } |
| if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) { |
| Time hover = getDayFromLocation(event.getX()); |
| if (hover != null |
| && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) != 0)) { |
| Long millis = hover.toMillis(true); |
| String date = Utils.formatDateRange(context, millis, millis, |
| DateUtils.FORMAT_SHOW_DATE); |
| AccessibilityEvent accessEvent = AccessibilityEvent |
| .obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); |
| accessEvent.getText().add(date); |
| if (mShowDetailsInMonth && mEvents != null) { |
| int dayStart = SPACING_WEEK_NUMBER + mPadding; |
| int dayPosition = (int) ((event.getX() - dayStart) * mNumDays / (mWidth |
| - dayStart - mPadding)); |
| ArrayList<Event> events = mEvents.get(dayPosition); |
| List<CharSequence> text = accessEvent.getText(); |
| for (Event e : events) { |
| text.add(e.getTitleAndLocation() + ". "); |
| int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; |
| if (!e.allDay) { |
| flags |= DateUtils.FORMAT_SHOW_TIME; |
| if (DateFormat.is24HourFormat(context)) { |
| flags |= DateUtils.FORMAT_24HOUR; |
| } |
| } else { |
| flags |= DateUtils.FORMAT_UTC; |
| } |
| text.add(Utils.formatDateRange(context, e.startMillis, e.endMillis, |
| flags) + ". "); |
| } |
| } |
| sendAccessibilityEventUnchecked(accessEvent); |
| mLastHoverTime = hover; |
| } |
| } |
| return true; |
| } |
| |
| public void setClickedDay(float xLocation) { |
| mClickedDayIndex = getDayIndexFromLocation(xLocation); |
| invalidate(); |
| } |
| public void clearClickedDay() { |
| mClickedDayIndex = -1; |
| invalidate(); |
| } |
| } |