| /* |
| * Copyright (C) 2006 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; |
| |
| import static android.provider.Calendar.EVENT_BEGIN_TIME; |
| import static android.provider.Calendar.EVENT_END_TIME; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.PorterDuff; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.text.format.DateFormat; |
| import android.text.format.DateUtils; |
| import android.text.format.Time; |
| import android.util.DayOfMonthCursor; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.view.ContextMenu; |
| import android.view.GestureDetector; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.view.ContextMenu.ContextMenuInfo; |
| import android.widget.PopupWindow; |
| import android.widget.TextView; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| |
| public class MonthView extends View implements View.OnCreateContextMenuListener { |
| |
| private static final boolean PROFILE_LOAD_TIME = false; |
| |
| private static float mScale = 0; // Used for supporting different screen densities |
| private static int WEEK_GAP = 0; |
| private static int MONTH_DAY_GAP = 1; |
| private static float HOUR_GAP = 0.5f; |
| private static int MONTH_DAY_TEXT_SIZE = 20; |
| private static int WEEK_BANNER_HEIGHT = 17; |
| private static int WEEK_TEXT_SIZE = 15; |
| private static int WEEK_TEXT_PADDING = 3; |
| private static int EVENT_DOT_TOP_MARGIN = 5; |
| private static int EVENT_DOT_LEFT_MARGIN = 7; |
| private static int EVENT_DOT_W_H = 10; |
| private static int EVENT_NUM_DAYS = 31; |
| private static int TEXT_BOTTOM_MARGIN = 7; |
| |
| private static int HORIZONTAL_FLING_THRESHOLD = 50; |
| |
| private int mCellHeight; |
| private int mBorder; |
| private boolean mLaunchDayView; |
| |
| private GestureDetector mGestureDetector; |
| |
| private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW; |
| |
| private Time mToday; |
| private Time mViewCalendar; |
| private Time mSavedTime = new Time(); // the time when we entered this view |
| |
| // This Time object is used to set the time for the other Month view. |
| private Time mOtherViewCalendar = new Time(); |
| |
| // This Time object is used for temporary calculations and is allocated |
| // once to avoid extra garbage collection |
| private Time mTempTime = new Time(); |
| |
| private DayOfMonthCursor mCursor; |
| |
| private Drawable mBoxSelected; |
| private Drawable mBoxPressed; |
| private Drawable mBoxLongPressed; |
| private Drawable mEventDot; |
| private int mCellWidth; |
| |
| private Resources mResources; |
| private MonthActivity mParentActivity; |
| private Navigator mNavigator; |
| private final EventGeometry mEventGeometry; |
| |
| // Pre-allocate and reuse |
| private Rect mRect = new Rect(); |
| |
| //An array of which days have events for quick reference |
| private boolean[] eventDay = new boolean[31]; |
| |
| private PopupWindow mPopup; |
| private View mPopupView; |
| private static final int POPUP_HEIGHT = 100; |
| private int mPreviousPopupHeight; |
| private static final int POPUP_DISMISS_DELAY = 3000; |
| private DismissPopup mDismissPopup = new DismissPopup(); |
| |
| // For drawing to an off-screen Canvas |
| private Bitmap mBitmap; |
| private Canvas mCanvas; |
| private boolean mRedrawScreen = true; |
| private Rect mBitmapRect = new Rect(); |
| private boolean mAnimating; |
| |
| // These booleans disable features that were taken out of the spec. |
| private boolean mShowWeekNumbers = false; |
| private boolean mShowToast = false; |
| |
| // Bitmap caches. |
| // These improve performance by minimizing calls to NinePatchDrawable.draw() for common |
| // drawables for day backgrounds. |
| // mDayBitmapCache is indexed by a unique integer constructed from the width/height. |
| private SparseArray<Bitmap> mDayBitmapCache = new SparseArray<Bitmap>(4); |
| |
| private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler(); |
| |
| /** |
| * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS. |
| */ |
| private static final int SELECTION_HIDDEN = 0; |
| private static final int SELECTION_PRESSED = 1; |
| private static final int SELECTION_SELECTED = 2; |
| private static final int SELECTION_LONGPRESS = 3; |
| |
| // Modulo used to pack (width,height) into a unique integer |
| private static final int MODULO_SHIFT = 16; |
| |
| private int mSelectionMode = SELECTION_HIDDEN; |
| |
| /** |
| * The first Julian day of the current month. |
| */ |
| private int mFirstJulianDay; |
| |
| private int mStartDay; |
| |
| private final EventLoader mEventLoader; |
| |
| private ArrayList<Event> mEvents = new ArrayList<Event>(); |
| |
| private Drawable mTodayBackground; |
| private Drawable mDayBackground; |
| |
| // Cached colors |
| private int mMonthOtherMonthColor; |
| private int mMonthWeekBannerColor; |
| private int mMonthOtherMonthBannerColor; |
| private int mMonthOtherMonthDayNumberColor; |
| private int mMonthDayNumberColor; |
| private int mMonthTodayNumberColor; |
| private int mMonthSaturdayColor; |
| private int mMonthSundayColor; |
| |
| public MonthView(MonthActivity activity, Navigator navigator) { |
| super(activity); |
| if (mScale == 0) { |
| mScale = getContext().getResources().getDisplayMetrics().density; |
| if (mScale != 1) { |
| WEEK_GAP *= mScale; |
| MONTH_DAY_GAP *= mScale; |
| HOUR_GAP *= mScale; |
| |
| MONTH_DAY_TEXT_SIZE *= mScale; |
| WEEK_BANNER_HEIGHT *= mScale; |
| WEEK_TEXT_SIZE *= mScale; |
| WEEK_TEXT_PADDING *= mScale; |
| EVENT_DOT_TOP_MARGIN *= mScale; |
| EVENT_DOT_LEFT_MARGIN *= mScale; |
| EVENT_DOT_W_H *= mScale; |
| TEXT_BOTTOM_MARGIN *= mScale; |
| HORIZONTAL_FLING_THRESHOLD *= mScale; |
| } |
| } |
| |
| mEventLoader = activity.mEventLoader; |
| mNavigator = navigator; |
| mEventGeometry = new EventGeometry(); |
| mEventGeometry.setMinEventHeight(1.0f); |
| mEventGeometry.setHourGap(HOUR_GAP); |
| init(activity); |
| } |
| |
| private void init(MonthActivity activity) { |
| setFocusable(true); |
| setClickable(true); |
| setOnCreateContextMenuListener(this); |
| mParentActivity = activity; |
| mViewCalendar = new Time(); |
| long now = System.currentTimeMillis(); |
| mViewCalendar.set(now); |
| mViewCalendar.monthDay = 1; |
| long millis = mViewCalendar.normalize(true /* ignore DST */); |
| mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff); |
| mStartDay = Utils.getFirstDayOfWeek(); |
| mViewCalendar.set(now); |
| |
| mCursor = new DayOfMonthCursor(mViewCalendar.year, mViewCalendar.month, |
| mViewCalendar.monthDay, mParentActivity.getStartDay()); |
| mToday = new Time(); |
| mToday.set(System.currentTimeMillis()); |
| |
| mResources = activity.getResources(); |
| mBoxSelected = mResources.getDrawable(R.drawable.month_view_selected); |
| mBoxPressed = mResources.getDrawable(R.drawable.month_view_pressed); |
| mBoxLongPressed = mResources.getDrawable(R.drawable.month_view_longpress); |
| |
| mEventDot = mResources.getDrawable(R.drawable.event_dot); |
| mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background); |
| mDayBackground = mResources.getDrawable(R.drawable.month_view_background); |
| |
| // Cache color lookups |
| Resources res = getResources(); |
| mMonthOtherMonthColor = res.getColor(R.color.month_other_month); |
| mMonthWeekBannerColor = res.getColor(R.color.month_week_banner); |
| mMonthOtherMonthBannerColor = res.getColor(R.color.month_other_month_banner); |
| mMonthOtherMonthDayNumberColor = res.getColor(R.color.month_other_month_day_number); |
| mMonthDayNumberColor = res.getColor(R.color.month_day_number); |
| mMonthTodayNumberColor = res.getColor(R.color.month_today_number); |
| mMonthSaturdayColor = res.getColor(R.color.month_saturday); |
| mMonthSundayColor = res.getColor(R.color.month_sunday); |
| |
| if (mShowToast) { |
| LayoutInflater inflater; |
| inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| mPopupView = inflater.inflate(R.layout.month_bubble, null); |
| mPopup = new PopupWindow(activity); |
| mPopup.setContentView(mPopupView); |
| Resources.Theme dialogTheme = getResources().newTheme(); |
| dialogTheme.applyStyle(android.R.style.Theme_Dialog, true); |
| TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] { |
| android.R.attr.windowBackground }); |
| mPopup.setBackgroundDrawable(ta.getDrawable(0)); |
| ta.recycle(); |
| } |
| |
| mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, |
| float velocityY) { |
| // The user might do a slow "fling" after touching the screen |
| // and we don't want the long-press to pop up a context menu. |
| // Setting mLaunchDayView to false prevents the long-press. |
| mLaunchDayView = false; |
| mSelectionMode = SELECTION_HIDDEN; |
| |
| int distanceX = Math.abs((int) e2.getX() - (int) e1.getX()); |
| int distanceY = Math.abs((int) e2.getY() - (int) e1.getY()); |
| if (distanceY < HORIZONTAL_FLING_THRESHOLD || distanceY < distanceX) { |
| return false; |
| } |
| |
| // Switch to a different month |
| Time time = mOtherViewCalendar; |
| time.set(mViewCalendar); |
| if (velocityY < 0) { |
| time.month += 1; |
| } else { |
| time.month -= 1; |
| } |
| time.normalize(true); |
| mParentActivity.goTo(time, true); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onDown(MotionEvent e) { |
| mLaunchDayView = false; |
| return true; |
| } |
| |
| @Override |
| public void onShowPress(MotionEvent e) { |
| int x = (int) e.getX(); |
| int y = (int) e.getY(); |
| int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight); |
| int col = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth); |
| if (row > 5) { |
| row = 5; |
| } |
| if (col > 6) { |
| col = 6; |
| } |
| |
| // Launch the Day/Agenda view when the finger lifts up, |
| // unless the finger moves before lifting up. |
| mLaunchDayView = true; |
| |
| // Highlight the selected day. |
| mCursor.setSelectedRowColumn(row, col); |
| mSelectionMode = SELECTION_PRESSED; |
| mRedrawScreen = true; |
| invalidate(); |
| } |
| |
| @Override |
| public void onLongPress(MotionEvent e) { |
| // If mLaunchDayView is true, then we haven't done any scrolling |
| // after touching the screen, so allow long-press to proceed |
| // with popping up the context menu. |
| if (mLaunchDayView) { |
| mLaunchDayView = false; |
| mSelectionMode = SELECTION_LONGPRESS; |
| mRedrawScreen = true; |
| invalidate(); |
| performLongClick(); |
| } |
| } |
| |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, |
| float distanceX, float distanceY) { |
| // If the user moves his finger after touching, then do not |
| // launch the Day view when he lifts his finger. Also, turn |
| // off the selection. |
| mLaunchDayView = false; |
| |
| if (mSelectionMode != SELECTION_HIDDEN) { |
| mSelectionMode = SELECTION_HIDDEN; |
| mRedrawScreen = true; |
| invalidate(); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onSingleTapUp(MotionEvent e) { |
| if (mLaunchDayView) { |
| mSelectionMode = SELECTION_SELECTED; |
| mRedrawScreen = true; |
| invalidate(); |
| mLaunchDayView = false; |
| int x = (int) e.getX(); |
| int y = (int) e.getY(); |
| long millis = getSelectedMillisFor(x, y); |
| Utils.startActivity(getContext(), mDetailedView, millis); |
| } |
| |
| return true; |
| } |
| }); |
| } |
| |
| public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { |
| MenuItem item; |
| |
| final long startMillis = getSelectedTimeInMillis(); |
| final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE |
| | DateUtils.FORMAT_ABBREV_MONTH; |
| |
| final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags); |
| menu.setHeaderTitle(title); |
| |
| item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view); |
| item.setOnMenuItemClickListener(mContextMenuHandler); |
| item.setIcon(android.R.drawable.ic_menu_day); |
| item.setAlphabeticShortcut('d'); |
| |
| item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view); |
| item.setOnMenuItemClickListener(mContextMenuHandler); |
| item.setIcon(android.R.drawable.ic_menu_agenda); |
| item.setAlphabeticShortcut('a'); |
| |
| item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create); |
| item.setOnMenuItemClickListener(mContextMenuHandler); |
| item.setIcon(android.R.drawable.ic_menu_add); |
| item.setAlphabeticShortcut('n'); |
| } |
| |
| private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener { |
| public boolean onMenuItemClick(MenuItem item) { |
| switch (item.getItemId()) { |
| case MenuHelper.MENU_DAY: { |
| long startMillis = getSelectedTimeInMillis(); |
| Utils.startActivity(mParentActivity, DayActivity.class.getName(), startMillis); |
| break; |
| } |
| case MenuHelper.MENU_AGENDA: { |
| long startMillis = getSelectedTimeInMillis(); |
| Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis); |
| break; |
| } |
| case MenuHelper.MENU_EVENT_CREATE: { |
| long startMillis = getSelectedTimeInMillis(); |
| long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS; |
| Intent intent = new Intent(Intent.ACTION_VIEW); |
| intent.setClassName(mContext, EditEvent.class.getName()); |
| intent.putExtra(EVENT_BEGIN_TIME, startMillis); |
| intent.putExtra(EVENT_END_TIME, endMillis); |
| mParentActivity.startActivity(intent); |
| break; |
| } |
| default: { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| void reloadEvents() { |
| // Get the date for the beginning of the month |
| Time monthStart = mTempTime; |
| monthStart.set(mViewCalendar); |
| monthStart.monthDay = 1; |
| monthStart.hour = 0; |
| monthStart.minute = 0; |
| monthStart.second = 0; |
| long millis = monthStart.normalize(true /* ignore isDst */); |
| int startDay = Time.getJulianDay(millis, monthStart.gmtoff); |
| |
| // Load the days with events in the background |
| mParentActivity.startProgressSpinner(); |
| final long startMillis; |
| if (PROFILE_LOAD_TIME) { |
| startMillis = SystemClock.uptimeMillis(); |
| } else { |
| // To avoid a compiler error that this variable might not be initialized. |
| startMillis = 0; |
| } |
| |
| mEventLoader.loadEventDaysInBackground(startDay, EVENT_NUM_DAYS, eventDay, |
| new Runnable() { |
| public void run() { |
| if (PROFILE_LOAD_TIME) { |
| long endMillis = SystemClock.uptimeMillis(); |
| long elapsed = endMillis - startMillis; |
| Log.i("Cal", (mViewCalendar.month+1) + "/" + mViewCalendar.year + |
| " Month view load eventDays: " + elapsed); |
| } |
| //Refresh the screen when we're done and stop the spinner |
| mRedrawScreen = true; |
| mParentActivity.stopProgressSpinner(); |
| invalidate(); |
| } |
| }); |
| } |
| |
| void animationStarted() { |
| mAnimating = true; |
| } |
| |
| void animationFinished() { |
| mAnimating = false; |
| mRedrawScreen = true; |
| invalidate(); |
| } |
| |
| @Override |
| protected void onSizeChanged(int width, int height, int oldw, int oldh) { |
| drawingCalc(width, height); |
| // If the size changed, then we should rebuild the bitmaps... |
| clearBitmapCache(); |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| // No need to hang onto the bitmaps... |
| clearBitmapCache(); |
| if (mBitmap != null) { |
| mBitmap.recycle(); |
| } |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| if (mRedrawScreen) { |
| if (mCanvas == null) { |
| drawingCalc(getWidth(), getHeight()); |
| } |
| |
| // If we are zero-sized, the canvas will remain null so check again |
| if (mCanvas != null) { |
| // Clear the background |
| final Canvas bitmapCanvas = mCanvas; |
| bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR); |
| doDraw(bitmapCanvas); |
| mRedrawScreen = false; |
| } |
| } |
| |
| // If we are zero-sized, the bitmap will be null so guard against this |
| if (mBitmap != null) { |
| canvas.drawBitmap(mBitmap, mBitmapRect, mBitmapRect, null); |
| } |
| } |
| |
| private void doDraw(Canvas canvas) { |
| boolean isLandscape = getResources().getConfiguration().orientation |
| == Configuration.ORIENTATION_LANDSCAPE; |
| |
| Paint p = new Paint(); |
| Rect r = mRect; |
| int columnDay1 = mCursor.getColumnOf(1); |
| |
| // Get the Julian day for the date at row 0, column 0. |
| int day = mFirstJulianDay - columnDay1; |
| |
| int weekNum = 0; |
| Calendar calendar = null; |
| if (mShowWeekNumbers) { |
| calendar = Calendar.getInstance(); |
| boolean noPrevMonth = (columnDay1 == 0); |
| |
| // Compute the week number for the first row. |
| weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar); |
| } |
| |
| for (int row = 0; row < 6; row++) { |
| for (int column = 0; column < 7; column++) { |
| drawBox(day, weekNum, row, column, canvas, p, r, isLandscape); |
| day += 1; |
| } |
| |
| if (mShowWeekNumbers) { |
| weekNum += 1; |
| if (weekNum >= 53) { |
| boolean inCurrentMonth = (day - mFirstJulianDay < 31); |
| weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar); |
| } |
| } |
| } |
| |
| drawGrid(canvas, p); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| if (mGestureDetector.onTouchEvent(event)) { |
| return true; |
| } |
| |
| return super.onTouchEvent(event); |
| } |
| |
| private long getSelectedMillisFor(int x, int y) { |
| int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight); |
| int column = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth); |
| if (column > 6) { |
| column = 6; |
| } |
| |
| DayOfMonthCursor c = mCursor; |
| Time time = mTempTime; |
| time.set(mViewCalendar); |
| |
| // Compute the day number from the row and column. If the row and |
| // column are in a different month from the current one, then the |
| // monthDay might be negative or it might be greater than the number |
| // of days in this month, but that is okay because the normalize() |
| // method will adjust the month (and year) if necessary. |
| time.monthDay = 7 * row + column - c.getOffset() + 1; |
| return time.normalize(true); |
| } |
| |
| /** |
| * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect. |
| * |
| * @param drawable the drawable we wish to render |
| * @param width the width of the resulting bitmap |
| * @param height the height of the resulting bitmap |
| * @return a new bitmap |
| */ |
| private Bitmap createBitmap(Drawable drawable, int width, int height) { |
| // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888) |
| Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig()); |
| |
| // Draw the drawable into the bitmap at the origin. |
| Canvas canvas = new Canvas(bitmap); |
| drawable.setBounds(0, 0, width, height); |
| drawable.draw(canvas); |
| return bitmap; |
| } |
| |
| /** |
| * Clears the bitmap cache. Generally only needed when the screen size changed. |
| */ |
| private void clearBitmapCache() { |
| recycleAndClearBitmapCache(mDayBitmapCache); |
| } |
| |
| private void recycleAndClearBitmapCache(SparseArray<Bitmap> bitmapCache) { |
| int size = bitmapCache.size(); |
| for(int i = 0; i < size; i++) { |
| bitmapCache.valueAt(i).recycle(); |
| } |
| bitmapCache.clear(); |
| |
| } |
| |
| /** |
| * Draw the grid lines for the calendar |
| * @param canvas The canvas to draw on. |
| * @param p The paint used for drawing. |
| */ |
| private void drawGrid(Canvas canvas, Paint p) { |
| p.setColor(mMonthOtherMonthColor); |
| p.setAntiAlias(false); |
| |
| final int width = getMeasuredWidth(); |
| final int height = getMeasuredHeight(); |
| |
| for (int row = 0; row < 6; row++) { |
| int y = WEEK_GAP + row * (WEEK_GAP + mCellHeight) - 1; |
| canvas.drawLine(0, y, width, y, p); |
| } |
| for (int column = 1; column < 7; column++) { |
| int x = mBorder + column * (MONTH_DAY_GAP + mCellWidth) - 1; |
| canvas.drawLine(x, WEEK_GAP, x, height, p); |
| } |
| } |
| |
| /** |
| * Draw a single box onto the canvas. |
| * @param day The Julian day. |
| * @param weekNum The week number. |
| * @param row The row of the box (0-5). |
| * @param column The column of the box (0-6). |
| * @param canvas The canvas to draw on. |
| * @param p The paint used for drawing. |
| * @param r The rectangle used for each box. |
| * @param isLandscape Is the current orientation landscape. |
| */ |
| private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p, |
| Rect r, boolean isLandscape) { |
| |
| // Only draw the selection if we are in the press state or if we have |
| // moved the cursor with key input. |
| boolean drawSelection = false; |
| if (mSelectionMode != SELECTION_HIDDEN) { |
| drawSelection = mCursor.isSelected(row, column); |
| } |
| |
| boolean withinCurrentMonth = mCursor.isWithinCurrentMonth(row, column); |
| boolean isToday = false; |
| int dayOfBox = mCursor.getDayAt(row, column); |
| if (dayOfBox == mToday.monthDay && mCursor.getYear() == mToday.year |
| && mCursor.getMonth() == mToday.month) { |
| isToday = true; |
| } |
| |
| int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight); |
| int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth); |
| |
| r.left = x; |
| r.top = y; |
| r.right = x + mCellWidth; |
| r.bottom = y + mCellHeight; |
| |
| |
| // Adjust the left column, right column, and bottom row to leave |
| // no border. |
| if (column == 0) { |
| r.left = -1; |
| } else if (column == 6) { |
| r.right += mBorder + 2; |
| } |
| |
| if (row == 5) { |
| r.bottom = getMeasuredHeight(); |
| } |
| |
| // Draw the cell contents (excluding monthDay number) |
| if (!withinCurrentMonth) { |
| boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column); |
| |
| // Adjust cell boundaries to compensate for the different border |
| // style. |
| r.top--; |
| if (column != 0) { |
| r.left--; |
| } |
| } else if (drawSelection) { |
| if (mSelectionMode == SELECTION_SELECTED) { |
| mBoxSelected.setBounds(r); |
| mBoxSelected.draw(canvas); |
| } else if (mSelectionMode == SELECTION_PRESSED) { |
| mBoxPressed.setBounds(r); |
| mBoxPressed.draw(canvas); |
| } else { |
| mBoxLongPressed.setBounds(r); |
| mBoxLongPressed.draw(canvas); |
| } |
| |
| //Places a green dot in the upper left corner of the day if there is |
| //an event on that day |
| if (withinCurrentMonth && eventDay[day-mFirstJulianDay]) { |
| drawEvents(day, canvas, r, p); |
| } |
| if (!mAnimating) { |
| updateEventDetails(day); |
| } |
| } else { |
| // Today gets a different background |
| if (isToday) { |
| // We could cache this for a little bit more performance, but it's not on the |
| // performance radar... |
| Drawable background = mTodayBackground; |
| background.setBounds(r); |
| background.draw(canvas); |
| } else { |
| // Use the bitmap cache to draw the day background |
| int width = r.right - r.left; |
| int height = r.bottom - r.top; |
| // Compute a unique id that depends on width and height. |
| int id = (height << MODULO_SHIFT) | width; |
| Bitmap bitmap = mDayBitmapCache.get(id); |
| if (bitmap == null) { |
| bitmap = createBitmap(mDayBackground, width, height); |
| mDayBitmapCache.put(id, bitmap); |
| } |
| canvas.drawBitmap(bitmap, r.left, r.top, p); |
| } |
| //Places a green dot near the top of the day if there is |
| //an event on that day |
| if (withinCurrentMonth && eventDay[day-mFirstJulianDay]) { |
| drawEvents(day, canvas, r, p); |
| } |
| } |
| |
| // Draw week number |
| if (mShowWeekNumbers && column == 0) { |
| // Draw the banner |
| p.setStyle(Paint.Style.FILL); |
| p.setColor(mMonthWeekBannerColor); |
| if (isLandscape) { |
| int bottom = r.bottom; |
| r.bottom = r.top + WEEK_BANNER_HEIGHT; |
| r.left++; |
| canvas.drawRect(r, p); |
| r.bottom = bottom; |
| r.left--; |
| } else { |
| int top = r.top; |
| r.top = r.bottom - WEEK_BANNER_HEIGHT; |
| r.left++; |
| canvas.drawRect(r, p); |
| r.top = top; |
| r.left--; |
| } |
| |
| // Draw the number |
| p.setColor(mMonthOtherMonthBannerColor); |
| p.setAntiAlias(true); |
| p.setTypeface(null); |
| p.setTextSize(WEEK_TEXT_SIZE); |
| p.setTextAlign(Paint.Align.LEFT); |
| |
| int textX = r.left + WEEK_TEXT_PADDING; |
| int textY; |
| if (isLandscape) { |
| textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING; |
| } else { |
| textY = r.bottom - WEEK_TEXT_PADDING; |
| } |
| |
| canvas.drawText(String.valueOf(weekNum), textX, textY, p); |
| } |
| |
| // Draw the monthDay number |
| p.setStyle(Paint.Style.FILL); |
| p.setAntiAlias(true); |
| p.setTypeface(null); |
| p.setTextSize(MONTH_DAY_TEXT_SIZE); |
| |
| if (!withinCurrentMonth) { |
| p.setColor(mMonthOtherMonthDayNumberColor); |
| } else { |
| if (isToday && !drawSelection) { |
| p.setColor(mMonthTodayNumberColor); |
| } else if (Utils.isSunday(column, mStartDay)) { |
| p.setColor(mMonthSundayColor); |
| } else if (Utils.isSaturday(column, mStartDay)) { |
| p.setColor(mMonthSaturdayColor); |
| } else { |
| p.setColor(mMonthDayNumberColor); |
| } |
| //bolds the day if there's an event that day |
| p.setFakeBoldText(eventDay[day-mFirstJulianDay]); |
| } |
| /*Drawing of day number is done here |
| *easy to find tags draw number draw day*/ |
| p.setTextAlign(Paint.Align.CENTER); |
| int right = r.right; |
| int textX = r.left + (right - r.left) / 2; // center of text |
| int textY = r.bottom - TEXT_BOTTOM_MARGIN - 2; // bottom of text |
| canvas.drawText(String.valueOf(mCursor.getDayAt(row, column)), textX, textY, p); |
| } |
| |
| ///Create and draw an event dot for this day |
| private void drawEvents(int date, Canvas canvas, Rect rect, Paint p) |
| { |
| //Coords of the upper left corner where we'll draw the event dot |
| int top = rect.top + EVENT_DOT_TOP_MARGIN; |
| int left = (rect.left + rect.right-EVENT_DOT_W_H)/2; |
| Bitmap bitmap = createBitmap(mEventDot, EVENT_DOT_W_H, EVENT_DOT_W_H); |
| |
| canvas.drawBitmap(bitmap, left, top, p); |
| } |
| |
| private boolean isFirstDayOfNextMonth(int row, int column) { |
| if (column == 0) { |
| column = 6; |
| row--; |
| } else { |
| column--; |
| } |
| return mCursor.isWithinCurrentMonth(row, column); |
| } |
| |
| private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth, |
| Calendar calendar) { |
| calendar.set(Calendar.DAY_OF_MONTH, mCursor.getDayAt(row, column)); |
| if (isWithinCurrentMonth) { |
| calendar.set(Calendar.MONTH, mCursor.getMonth()); |
| calendar.set(Calendar.YEAR, mCursor.getYear()); |
| } else { |
| int month = mCursor.getMonth(); |
| int year = mCursor.getYear(); |
| if (row < 2) { |
| // Previous month |
| if (month == 0) { |
| year--; |
| month = 11; |
| } else { |
| month--; |
| } |
| } else { |
| // Next month |
| if (month == 11) { |
| year++; |
| month = 0; |
| } else { |
| month++; |
| } |
| } |
| calendar.set(Calendar.MONTH, month); |
| calendar.set(Calendar.YEAR, year); |
| } |
| |
| return calendar.get(Calendar.WEEK_OF_YEAR); |
| } |
| |
| void setDetailedView(String detailedView) { |
| mDetailedView = detailedView; |
| } |
| |
| void setSelectedTime(Time time) { |
| // Save the selected time so that we can restore it later when we switch views. |
| mSavedTime.set(time); |
| |
| mViewCalendar.set(time); |
| mViewCalendar.monthDay = 1; |
| long millis = mViewCalendar.normalize(true /* ignore DST */); |
| mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff); |
| mViewCalendar.set(time); |
| |
| mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay, |
| mCursor.getWeekStartDay()); |
| |
| mRedrawScreen = true; |
| invalidate(); |
| } |
| |
| public long getSelectedTimeInMillis() { |
| Time time = mTempTime; |
| time.set(mViewCalendar); |
| |
| time.month += mCursor.getSelectedMonthOffset(); |
| time.monthDay = mCursor.getSelectedDayOfMonth(); |
| |
| // Restore the saved hour:minute:second offset from when we entered |
| // this view. |
| time.second = mSavedTime.second; |
| time.minute = mSavedTime.minute; |
| time.hour = mSavedTime.hour; |
| return time.normalize(true); |
| } |
| |
| Time getTime() { |
| return mViewCalendar; |
| } |
| |
| public int getSelectionMode() { |
| return mSelectionMode; |
| } |
| |
| public void setSelectionMode(int selectionMode) { |
| mSelectionMode = selectionMode; |
| } |
| |
| private void drawingCalc(int width, int height) { |
| mCellHeight = (height - (6 * WEEK_GAP)) / 6; |
| mEventGeometry.setHourHeight((mCellHeight - 25.0f * HOUR_GAP) / 24.0f); |
| mCellWidth = (width - (6 * MONTH_DAY_GAP)) / 7; |
| mBorder = (width - 6 * (mCellWidth + MONTH_DAY_GAP) - mCellWidth) / 2; |
| |
| if (mShowToast) { |
| mPopup.dismiss(); |
| mPopup.setWidth(width - 20); |
| mPopup.setHeight(POPUP_HEIGHT); |
| } |
| |
| if (((mBitmap == null) |
| || mBitmap.isRecycled() |
| || (mBitmap.getHeight() != height) |
| || (mBitmap.getWidth() != width)) |
| && (width > 0) && (height > 0)) { |
| if (mBitmap != null) { |
| mBitmap.recycle(); |
| } |
| mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
| mCanvas = new Canvas(mBitmap); |
| } |
| |
| mBitmapRect.top = 0; |
| mBitmapRect.bottom = height; |
| mBitmapRect.left = 0; |
| mBitmapRect.right = width; |
| } |
| |
| private void updateEventDetails(int date) { |
| if (!mShowToast) { |
| return; |
| } |
| |
| getHandler().removeCallbacks(mDismissPopup); |
| ArrayList<Event> events = mEvents; |
| int numEvents = events.size(); |
| if (numEvents == 0) { |
| mPopup.dismiss(); |
| return; |
| } |
| |
| int eventIndex = 0; |
| for (int i = 0; i < numEvents; i++) { |
| Event event = events.get(i); |
| |
| if (event.startDay > date || event.endDay < date) { |
| continue; |
| } |
| |
| // If we have all the event that we can display, then just count |
| // the extra ones. |
| if (eventIndex >= 4) { |
| eventIndex += 1; |
| continue; |
| } |
| |
| int flags; |
| boolean showEndTime = false; |
| if (event.allDay) { |
| int numDays = event.endDay - event.startDay; |
| if (numDays == 0) { |
| flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE |
| | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL; |
| } else { |
| showEndTime = true; |
| flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE |
| | DateUtils.FORMAT_ABBREV_ALL; |
| } |
| } else { |
| flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT; |
| if (DateFormat.is24HourFormat(mContext)) { |
| flags |= DateUtils.FORMAT_24HOUR; |
| } |
| } |
| |
| String timeRange; |
| if (showEndTime) { |
| timeRange = DateUtils.formatDateRange(mParentActivity, |
| event.startMillis, event.endMillis, flags); |
| } else { |
| timeRange = DateUtils.formatDateRange(mParentActivity, |
| event.startMillis, event.startMillis, flags); |
| } |
| |
| TextView timeView = null; |
| TextView titleView = null; |
| switch (eventIndex) { |
| case 0: |
| timeView = (TextView) mPopupView.findViewById(R.id.time0); |
| titleView = (TextView) mPopupView.findViewById(R.id.event_title0); |
| break; |
| case 1: |
| timeView = (TextView) mPopupView.findViewById(R.id.time1); |
| titleView = (TextView) mPopupView.findViewById(R.id.event_title1); |
| break; |
| case 2: |
| timeView = (TextView) mPopupView.findViewById(R.id.time2); |
| titleView = (TextView) mPopupView.findViewById(R.id.event_title2); |
| break; |
| case 3: |
| timeView = (TextView) mPopupView.findViewById(R.id.time3); |
| titleView = (TextView) mPopupView.findViewById(R.id.event_title3); |
| break; |
| } |
| |
| timeView.setText(timeRange); |
| titleView.setText(event.title); |
| eventIndex += 1; |
| } |
| if (eventIndex == 0) { |
| // We didn't find any events for this day |
| mPopup.dismiss(); |
| return; |
| } |
| |
| // Hide the items that have no event information |
| View view; |
| switch (eventIndex) { |
| case 1: |
| view = mPopupView.findViewById(R.id.item_layout1); |
| view.setVisibility(View.GONE); |
| view = mPopupView.findViewById(R.id.item_layout2); |
| view.setVisibility(View.GONE); |
| view = mPopupView.findViewById(R.id.item_layout3); |
| view.setVisibility(View.GONE); |
| view = mPopupView.findViewById(R.id.plus_more); |
| view.setVisibility(View.GONE); |
| break; |
| case 2: |
| view = mPopupView.findViewById(R.id.item_layout1); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout2); |
| view.setVisibility(View.GONE); |
| view = mPopupView.findViewById(R.id.item_layout3); |
| view.setVisibility(View.GONE); |
| view = mPopupView.findViewById(R.id.plus_more); |
| view.setVisibility(View.GONE); |
| break; |
| case 3: |
| view = mPopupView.findViewById(R.id.item_layout1); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout2); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout3); |
| view.setVisibility(View.GONE); |
| view = mPopupView.findViewById(R.id.plus_more); |
| view.setVisibility(View.GONE); |
| break; |
| case 4: |
| view = mPopupView.findViewById(R.id.item_layout1); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout2); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout3); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.plus_more); |
| view.setVisibility(View.GONE); |
| break; |
| default: |
| view = mPopupView.findViewById(R.id.item_layout1); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout2); |
| view.setVisibility(View.VISIBLE); |
| view = mPopupView.findViewById(R.id.item_layout3); |
| view.setVisibility(View.VISIBLE); |
| TextView tv = (TextView) mPopupView.findViewById(R.id.plus_more); |
| tv.setVisibility(View.VISIBLE); |
| String format = mResources.getString(R.string.plus_N_more); |
| String plusMore = String.format(format, eventIndex - 4); |
| tv.setText(plusMore); |
| break; |
| } |
| |
| if (eventIndex > 5) { |
| eventIndex = 5; |
| } |
| int popupHeight = 20 * eventIndex + 15; |
| mPopup.setHeight(popupHeight); |
| |
| if (mPreviousPopupHeight != popupHeight) { |
| mPreviousPopupHeight = popupHeight; |
| mPopup.dismiss(); |
| } |
| mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0); |
| postDelayed(mDismissPopup, POPUP_DISMISS_DELAY); |
| } |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| long duration = event.getEventTime() - event.getDownTime(); |
| |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DPAD_CENTER: |
| if (mSelectionMode == SELECTION_HIDDEN) { |
| // Don't do anything unless the selection is visible. |
| break; |
| } |
| |
| if (mSelectionMode == SELECTION_PRESSED) { |
| // This was the first press when there was nothing selected. |
| // Change the selection from the "pressed" state to the |
| // the "selected" state. We treat short-press and |
| // long-press the same here because nothing was selected. |
| mSelectionMode = SELECTION_SELECTED; |
| mRedrawScreen = true; |
| invalidate(); |
| break; |
| } |
| |
| // Check the duration to determine if this was a short press |
| if (duration < ViewConfiguration.getLongPressTimeout()) { |
| long millis = getSelectedTimeInMillis(); |
| Utils.startActivity(getContext(), mDetailedView, millis); |
| } else { |
| mSelectionMode = SELECTION_LONGPRESS; |
| mRedrawScreen = true; |
| invalidate(); |
| performLongClick(); |
| } |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (mSelectionMode == SELECTION_HIDDEN) { |
| if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT |
| || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP |
| || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { |
| // Display the selection box but don't move or select it |
| // on this key press. |
| mSelectionMode = SELECTION_SELECTED; |
| mRedrawScreen = true; |
| invalidate(); |
| return true; |
| } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { |
| // Display the selection box but don't select it |
| // on this key press. |
| mSelectionMode = SELECTION_PRESSED; |
| mRedrawScreen = true; |
| invalidate(); |
| return true; |
| } |
| } |
| |
| mSelectionMode = SELECTION_SELECTED; |
| boolean redraw = false; |
| Time other = null; |
| |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_ENTER: |
| long millis = getSelectedTimeInMillis(); |
| Utils.startActivity(getContext(), mDetailedView, millis); |
| return true; |
| case KeyEvent.KEYCODE_DPAD_UP: |
| if (mCursor.up()) { |
| other = mOtherViewCalendar; |
| other.set(mViewCalendar); |
| other.month -= 1; |
| other.monthDay = mCursor.getSelectedDayOfMonth(); |
| |
| // restore the calendar cursor for the animation |
| mCursor.down(); |
| } |
| redraw = true; |
| break; |
| |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| if (mCursor.down()) { |
| other = mOtherViewCalendar; |
| other.set(mViewCalendar); |
| other.month += 1; |
| other.monthDay = mCursor.getSelectedDayOfMonth(); |
| |
| // restore the calendar cursor for the animation |
| mCursor.up(); |
| } |
| redraw = true; |
| break; |
| |
| case KeyEvent.KEYCODE_DPAD_LEFT: |
| if (mCursor.left()) { |
| other = mOtherViewCalendar; |
| other.set(mViewCalendar); |
| other.month -= 1; |
| other.monthDay = mCursor.getSelectedDayOfMonth(); |
| |
| // restore the calendar cursor for the animation |
| mCursor.right(); |
| } |
| redraw = true; |
| break; |
| |
| case KeyEvent.KEYCODE_DPAD_RIGHT: |
| if (mCursor.right()) { |
| other = mOtherViewCalendar; |
| other.set(mViewCalendar); |
| other.month += 1; |
| other.monthDay = mCursor.getSelectedDayOfMonth(); |
| |
| // restore the calendar cursor for the animation |
| mCursor.left(); |
| } |
| redraw = true; |
| break; |
| } |
| |
| if (other != null) { |
| other.normalize(true /* ignore DST */); |
| mNavigator.goTo(other, true); |
| } else if (redraw) { |
| mRedrawScreen = true; |
| invalidate(); |
| } |
| |
| return redraw; |
| } |
| |
| class DismissPopup implements Runnable { |
| public void run() { |
| mPopup.dismiss(); |
| } |
| } |
| |
| // This is called when the activity is paused so that the popup can |
| // be dismissed. |
| void dismissPopup() { |
| if (!mShowToast) { |
| return; |
| } |
| |
| // Protect against null-pointer exceptions |
| if (mPopup != null) { |
| mPopup.dismiss(); |
| } |
| |
| Handler handler = getHandler(); |
| if (handler != null) { |
| handler.removeCallbacks(mDismissPopup); |
| } |
| } |
| } |