/*
* Copyright (C) 2008 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.androsz.electricsleepbeta.widget.calendar;
import java.util.ArrayList;
import java.util.Collection;
import android.graphics.Rect;
public class SessionGeometry {
/* package */static final int MINUTES_PER_HOUR = 60;
/* package */static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * 24;
/**
* Computes a position for each event. Each event is displayed as a
* non-overlapping rectangle. For normal events, these rectangles are
* displayed in separate columns in the week view and day view. For all-day
* events, these rectangles are displayed in separate rows along the top. In
* both cases, each event is assigned two numbers: N, and Max, that specify
* that this event is the Nth event of Max number of events that are
* displayed in a group. The width and position of each rectangle depend on
* the maximum number of rectangles that occur at the same time.
*
* @param eventsList
* the list of events, sorted into increasing time order
*/
public static void computePositions(Collection<SessionGeometry> eventsList) {
if (eventsList == null) {
return;
}
doComputePositions(eventsList);
}
private static void doComputePositions(Collection<SessionGeometry> eventsList) {
final Collection<SessionGeometry> activeList = new ArrayList<SessionGeometry>();
final Collection<SessionGeometry> groupList = new ArrayList<SessionGeometry>();
long colMask = 0;
int maxCols = 0;
for (final SessionGeometry record : eventsList) {
// long start = record.getStartTime();
// Remove the inactive events. An event on the active list
// becomes inactive when its end time is less than or equal to
// the current event's start time.
/*
* Iterator<SleepRecord> iter = activeList.iterator(); while
* (iter.hasNext()) { SleepRecord active = iter.next(); if
* (active.getEndTime() <= start) { colMask &= ~(1L <<
* active.getColumn()); iter.remove(); } }
*/
// If the active list is empty, then reset the max columns, clear
// the column bit mask, and empty the groupList.
if (activeList.isEmpty()) {
for (final SessionGeometry ev : groupList) {
ev.setMaxColumns(maxCols);
}
maxCols = 0;
colMask = 0;
groupList.clear();
}
// Find the first empty column. Empty columns are represented by
// zero bits in the column mask "colMask".
int col = findFirstZeroBit(colMask);
if (col == 64) {
col = 63;
}
colMask |= (1L << col);
record.setColumn(col);
activeList.add(record);
groupList.add(record);
final int len = activeList.size();
if (maxCols < len) {
maxCols = len;
}
}
for (final SessionGeometry ev : groupList) {
ev.setMaxColumns(maxCols);
}
}
public static int findFirstZeroBit(long val) {
for (int ii = 0; ii < 64; ++ii) {
if ((val & (1L << ii)) == 0) {
return ii;
}
}
return 64;
}
// This is the space from the grid line to the event rectangle.
private int mCellMargin = 0;
private float mHourGap;
private float mMinEventHeight;
private float mMinuteHeight;
private int mColumn;
private int mMaxColumns;
// The coordinates of the event rectangle drawn on the screen.
public float left;
public float right;
public float top;
public float bottom;
Long startDay;
Long endDay;
long startTime;
long endTime;
public SessionGeometry(Long[] sessionBounds) {
startTime = sessionBounds[0];
endTime = sessionBounds[1];
startDay = sessionBounds[2];
endDay = sessionBounds[3];
}
// Computes the rectangle coordinates of the given event on the screen.
// Returns true if the rectangle is visible on the screen.
boolean computeEventRect(int date, int left, int top, int cellWidth) {
final float cellMinuteHeight = mMinuteHeight;
if (this.startDay > date || this.endDay < date) {
return false;
}
// If the event started on a previous day, then show it starting
// at the beginning of this day.
if (this.startDay < date) {
this.startTime = 0;
}
// If the event ends on a future day, then show it extending to
// the end of this day.
if (this.endDay > date) {
this.endTime = MINUTES_PER_DAY;
}
final int col = this.getColumn();
final int maxCols = this.getMaxColumns();
final int startHour = (int) (this.startTime / 60);
int endHour = (int) (this.endTime / 60);
// If the end point aligns on a cell boundary then count it as
// ending in the previous cell so that we don't cross the border
// between hours.
if (endHour * 60 == this.endTime) {
endHour -= 1;
}
this.top = top;
this.top += (int) (this.startTime * cellMinuteHeight);
this.top += startHour * mHourGap;
this.bottom = top;
this.bottom += (int) (this.endTime * cellMinuteHeight);
this.bottom += endHour * this.mHourGap;
// Make the rectangle be at least mMinEventHeight pixels high
if (this.bottom < this.top + mMinEventHeight) {
this.bottom = this.top + mMinEventHeight;
}
final float colWidth = (float) (cellWidth - 2 * mCellMargin)
/ (float) maxCols;
this.left = left + mCellMargin + col * colWidth;
this.right = this.left + colWidth;
return true;
}
/**
* Returns true if this event intersects the selection region.
*/
boolean eventIntersectsSelection(SessionGeometry event, Rect selection) {
if (event.left < selection.right && event.right >= selection.left
&& event.top < selection.bottom && event.bottom >= selection.top) {
return true;
}
return false;
}
public int getColumn() {
return mColumn;
}
public int getMaxColumns() {
return mMaxColumns;
}
/**
* Computes the distance from the given point to the given event.
*/
float pointToEvent(float x, float y, SessionGeometry event) {
final float left = event.left;
final float right = event.right;
final float top = event.top;
final float bottom = event.bottom;
if (x >= left) {
if (x <= right) {
if (y >= top) {
if (y <= bottom) {
// x,y is inside the event rectangle
return 0f;
}
// x,y is below the event rectangle
return y - bottom;
}
// x,y is above the event rectangle
return top - y;
}
// x > right
final float dx = x - right;
if (y < top) {
// the upper right corner
final float dy = top - y;
return (float) Math.sqrt(dx * dx + dy * dy);
}
if (y > bottom) {
// the lower right corner
final float dy = y - bottom;
return (float) Math.sqrt(dx * dx + dy * dy);
}
// x,y is to the right of the event rectangle
return dx;
}
// x < left
final float dx = left - x;
if (y < top) {
// the upper left corner
final float dy = top - y;
return (float) Math.sqrt(dx * dx + dy * dy);
}
if (y > bottom) {
// the lower left corner
final float dy = y - bottom;
return (float) Math.sqrt(dx * dx + dy * dy);
}
// x,y is to the left of the event rectangle
return dx;
}
void setCellMargin(int cellMargin) {
mCellMargin = cellMargin;
}
public void setColumn(int column) {
mColumn = column;
}
void setHourGap(float gap) {
mHourGap = gap;
}
void setHourHeight(float height) {
mMinuteHeight = height / 60.0f;
}
public void setMaxColumns(int maxColumns) {
mMaxColumns = maxColumns;
}
void setMinEventHeight(float height) {
mMinEventHeight = height;
}
}