/*
* This code is provided under the terms of GPL version 3.
* Please see LICENSE file for details
* (C) Dmitry Barashev, GanttProject team, 2004-2010
*/
package biz.ganttproject.core.chart.grid;
import java.util.Date;
import java.util.List;
import com.google.common.base.Function;
import biz.ganttproject.core.calendar.GPCalendar;
import biz.ganttproject.core.calendar.GPCalendar.DayMask;
import biz.ganttproject.core.calendar.GPCalendar.DayType;
import biz.ganttproject.core.calendar.GPCalendarCalc;
import biz.ganttproject.core.calendar.walker.WorkingUnitCounter;
import biz.ganttproject.core.time.TimeUnit;
import biz.ganttproject.core.time.TimeUnitFunctionOfDate;
/**
* Builds grid offsets for timelines where top cells are always constructed from
* the integer number of bottom cells (e.g. week from days)
*/
public class OffsetBuilderImpl implements OffsetBuilder {
protected static class OffsetStep {
public float parrots;
public int dayMask;
}
// We want weekend units to be less wide than working ones. This constant
// is a decrease factor
public static final int WEEKEND_UNIT_WIDTH_DECREASE_FACTOR = 10;
private final TimeUnit myTopUnit;
private final TimeUnit myBottomUnit;
private final Date myStartDate;
private final int myDefaultUnitWidth;
private final int myChartWidth;
private final GPCalendar myCalendar;
private final float myWeekendDecreaseFactor;
private final Date myEndDate;
private final TimeUnit baseUnit;
private final int myRightMarginBottomUnitCount;
private final Date myViewportStartDate;
private final Function<TimeUnit, Float> myOffsetStepFn;
// protected RegularFrameOffsetBuilder(GPCalendar calendar, TimeUnit topUnit, TimeUnit bottomUnit, Date startDate,
// Date viewportStartDate, int defaultUnitWidth, int chartWidth, float weekendDecreaseFactor, Date endDate,
// int rightMarginTimeUnits) {
protected OffsetBuilderImpl(OffsetBuilder.Factory factory) {
myCalendar = factory.myCalendar;
myStartDate = factory.myStartDate;
myViewportStartDate = factory.myViewportStartDate;
myTopUnit = factory.myTopUnit;
myBottomUnit = factory.myBottomUnit;
myDefaultUnitWidth = factory.myAtomicUnitWidth;
myChartWidth = factory.myEndOffset;
myWeekendDecreaseFactor = factory.myWeekendDecreaseFactor;
myEndDate = factory.myEndDate;
baseUnit = factory.myBaseUnit;
myRightMarginBottomUnitCount = factory.myRightMarginTimeUnits;
myOffsetStepFn = factory.myOffsetStepFn;
}
private TimeUnit getBottomUnit() {
return myBottomUnit;
}
private TimeUnit getTopUnit() {
return myTopUnit;
}
public static TimeUnit getConcreteUnit(TimeUnit timeUnit, Date date) {
return (timeUnit instanceof TimeUnitFunctionOfDate) ? ((TimeUnitFunctionOfDate) timeUnit).createTimeUnit(date)
: timeUnit;
}
private int getDefaultUnitWidth() {
return myDefaultUnitWidth;
}
protected float getOffsetStep(TimeUnit timeUnit) {
return myOffsetStepFn.apply(timeUnit);
}
private int getChartWidth() {
return myChartWidth;
}
private GPCalendar getCalendar() {
return myCalendar;
}
// public void setRightMarginBottomUnitCount(int value) {
// myRightMarginBottomUnitCount = value;
// }
@Override
public void constructOffsets(List<Offset> topUnitOffsets, OffsetList bottomUnitOffsets) {
constructOffsets(topUnitOffsets, bottomUnitOffsets, 0);
}
void constructOffsets(List<Offset> topUnitOffsets, List<Offset> bottomUnitOffsets, int initialEnd) {
// bottomUnitOffsets.add(new Offset(getBottomUnit(), myStartDate,
// myStartDate, myStartDate, 0, GPCalendar.DayType.WORKING));
constructBottomOffsets(bottomUnitOffsets, initialEnd);
if (topUnitOffsets != null) {
constructTopOffsets(getTopUnit(), topUnitOffsets, bottomUnitOffsets, initialEnd, getDefaultUnitWidth());
}
}
void constructBottomOffsets(List<Offset> offsets, int initialEnd) {
int marginUnitCount = myRightMarginBottomUnitCount;
Date currentDate = myStartDate;
int shift = 0;
OffsetStep step = new OffsetStep();
int prevEnd = initialEnd;
do {
TimeUnit concreteTimeUnit = getConcreteUnit(getBottomUnit(), currentDate);
calculateNextStep(step, concreteTimeUnit, currentDate);
Date endDate = concreteTimeUnit.adjustRight(currentDate);
if (endDate.compareTo(myViewportStartDate) <= 0) {
shift = (int) (step.parrots * getDefaultUnitWidth());
}
int offsetEnd = (int) (step.parrots * getDefaultUnitWidth()) - shift;
Offset offset = Offset.createFullyClosed(concreteTimeUnit, myStartDate, currentDate, endDate,
prevEnd, initialEnd + offsetEnd, step.dayMask);
prevEnd = initialEnd + offsetEnd;
offsets.add(offset);
currentDate = endDate;
boolean hasNext = true;
if (offsetEnd > getChartWidth()) {
hasNext &= marginUnitCount-- > 0;
}
if (hasNext && myEndDate != null) {
hasNext &= currentDate.before(myEndDate);
}
if (!hasNext) {
return;
}
} while (true);
}
private void constructTopOffsets(TimeUnit timeUnit, List<Offset> topOffsets, List<Offset> bottomOffsets,
int initialEnd, int baseUnitWidth) {
int lastBottomOffset = bottomOffsets.get(bottomOffsets.size() - 1).getOffsetPixels();
OffsetLookup offsetLookup = new OffsetLookup();
Date currentDate = myStartDate;
int prevEnd = initialEnd;
int offsetEnd;
do {
TimeUnit concreteTimeUnit = getConcreteUnit(timeUnit, currentDate);
Date endDate = concreteTimeUnit.adjustRight(currentDate);
int bottomOffsetLowerBound = offsetLookup.lookupOffsetByEndDate(endDate, bottomOffsets);
if (bottomOffsetLowerBound >= 0) {
offsetEnd = bottomOffsets.get(bottomOffsetLowerBound).getOffsetPixels();
} else {
if (-bottomOffsetLowerBound > bottomOffsets.size()) {
offsetEnd = lastBottomOffset + 1;
} else {
Offset ubOffset = bottomOffsetLowerBound <= -2 ? bottomOffsets.get(-bottomOffsetLowerBound - 2) : null;
Date ubEndDate = ubOffset == null ? myStartDate : ubOffset.getOffsetEnd();
int ubEndPixel = ubOffset == null ? 0 : ubOffset.getOffsetPixels();
WorkingUnitCounter counter = new WorkingUnitCounter(GPCalendarCalc.PLAIN, baseUnit);
offsetEnd = ubEndPixel + counter.run(ubEndDate, endDate).getLength() * baseUnitWidth;
}
}
topOffsets.add(Offset.createFullyClosed(concreteTimeUnit, myStartDate, currentDate, endDate, prevEnd, initialEnd
+ offsetEnd, DayMask.WORKING));
prevEnd = initialEnd + offsetEnd;
currentDate = endDate;
} while (offsetEnd <= lastBottomOffset && (myEndDate == null || currentDate.before(myEndDate)));
}
protected void calculateNextStep(OffsetStep step, TimeUnit timeUnit, Date startDate) {
float offsetStep = getOffsetStep(timeUnit);
step.dayMask = getCalendar().getDayMask(startDate);
if ((step.dayMask & DayMask.WORKING) == 0) {
offsetStep = offsetStep / myWeekendDecreaseFactor;
}
step.parrots += offsetStep;
}
public static class FactoryImpl extends OffsetBuilder.Factory {
@Override
public OffsetBuilder build() {
preBuild();
return new OffsetBuilderImpl(this);
}
}
}