diff options
author | Mahi Kolla <mahikolla@google.com> | 2021-07-12 18:57:04 +0000 |
---|---|---|
committer | Mahi Kolla <mahikolla@google.com> | 2021-07-12 18:57:04 +0000 |
commit | 7adab399715fcad7ea185eb9e349c9a073d3a0f3 (patch) | |
tree | c92d5afb9679f75bdaca14aa009f20c1a8a06b6b | |
parent | 68ba903864a2480a99c80c4280da26dfef7cad23 (diff) | |
download | Calendar-7adab399715fcad7ea185eb9e349c9a073d3a0f3.tar.gz |
AOSP/Calendar - Add Kotlin copy of MonthWeekEventsView.java
Test: none, no functional change in this commit when merged with corresponding Kotlin conversion.
Change-Id: I4d956b5f0ae5f983ac30d09e01b55ff3a75bd606
-rw-r--r-- | src/com/android/calendar/month/MonthWeekEventsView.kt | 1110 |
1 files changed, 1110 insertions, 0 deletions
diff --git a/src/com/android/calendar/month/MonthWeekEventsView.kt b/src/com/android/calendar/month/MonthWeekEventsView.kt new file mode 100644 index 00000000..071daee3 --- /dev/null +++ b/src/com/android/calendar/month/MonthWeekEventsView.kt @@ -0,0 +1,1110 @@ +/* + * 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_LENGTH_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_LENGTH_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(); + } +} |