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