/*
GanttProject is an opensource project management tool.
Copyright (C) 2004-2012 GanttProject Team
GanttProject is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
GanttProject is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GanttProject. If not, see <http://www.gnu.org/licenses/>.
*/
package biz.ganttproject.core.chart.scene;
import java.awt.Color;
import java.util.Date;
import java.util.List;
import biz.ganttproject.core.calendar.CalendarEvent;
import biz.ganttproject.core.calendar.GPCalendar;
import biz.ganttproject.core.calendar.GPCalendar.DayMask;
import biz.ganttproject.core.chart.canvas.Canvas;
import biz.ganttproject.core.chart.canvas.Canvas.Line;
import biz.ganttproject.core.chart.canvas.Canvas.Rectangle;
import biz.ganttproject.core.chart.grid.Offset;
import biz.ganttproject.core.chart.grid.OffsetList;
import biz.ganttproject.core.chart.grid.OffsetLookup;
import biz.ganttproject.core.option.BooleanOption;
/**
* Builds a scene consisting of vertical columns on the charts, such as weekend days, today line and
* project boundaries.
*
* @author dbarashev (Dmitry Barashev)
*/
public class DayGridSceneBuilder extends AbstractSceneBuilder {
private final BooleanOption myRedlineOption;
private final BooleanOption myProjectDatesOption;
private final Canvas myTimelineCanvas;
private final InputApi myInputApi;
public static interface InputApi {
BooleanOption getRedlineOption();
BooleanOption getProjectDatesOption();
int getTopLineHeight();
Color getWeekendColor();
Color getHolidayColor(Date holiday);
Date getProjectStart();
Date getProjectEnd();
OffsetList getAtomUnitOffsets();
CalendarEvent getEvent(Date offsetStart);
}
public DayGridSceneBuilder(InputApi inputApi, Canvas timelineCanvas) {
myInputApi = inputApi;
myRedlineOption = inputApi.getRedlineOption();
myProjectDatesOption = inputApi.getProjectDatesOption();
myTimelineCanvas = timelineCanvas;
}
@Override
public void build() {
if (myRedlineOption.isChecked()) {
renderLine(new Date(), "timeline.today", 2, OffsetLookup.BY_END_DATE);
}
if (isProjectBoundariesOptionOn()) {
renderLine(myInputApi.getProjectStart(), "timeline.project_start", -2, OffsetLookup.BY_START_DATE);
renderLine(myInputApi.getProjectEnd(), "timeline.project_end", 2, OffsetLookup.BY_START_DATE);
}
renderNonWorkingDayColumns();
}
private void renderLine(Date date, String style, int marginPx, OffsetLookup.ComparatorBy<Date> dateComparator) {
final int topUnitHeight = myInputApi.getTopLineHeight();
OffsetLookup lookup = new OffsetLookup();
int todayOffsetIdx = lookup.lookupOffsetBy(date, myInputApi.getAtomUnitOffsets(), dateComparator);
if (todayOffsetIdx < 0) {
todayOffsetIdx = -todayOffsetIdx - 1;
}
Offset yesterdayOffset = todayOffsetIdx == 0 ? null : myInputApi.getAtomUnitOffsets().get(
todayOffsetIdx - 1);
if (yesterdayOffset == null) {
return;
}
int yesterdayEndPixel = yesterdayOffset.getOffsetPixels();
Line line = getCanvas().createLine(yesterdayEndPixel + marginPx, topUnitHeight * 2,
yesterdayEndPixel + marginPx, getHeight() + topUnitHeight * 2);
line.setStyle(style);
}
// This method creates colored vertical stripes on the chart which correspond
// to weekend days and holidays. It is not necessary that colored stripe is
// a non-working day, though: e.g. we may show weekends but count them as working days.
private void renderNonWorkingDayColumns() {
List<Offset> defaultOffsets = myInputApi.getAtomUnitOffsets();
int curX = defaultOffsets.get(0).getOffsetPixels();
if (curX > 0) {
curX = 0;
}
for (final Offset offset : defaultOffsets) {
int dayMask = offset.getDayMask();
CalendarEvent event = myInputApi.getEvent(offset.getOffsetStart());
final int _curX = curX;
Runnable r = new Runnable() {
@Override
public void run() {
// Create a holiday/weekend day bar in the main area
renderNonWorkingDay(_curX, offset);
// And expand it to the timeline area.
Rectangle r = myTimelineCanvas.createRectangle(_curX, getLineTopPosition(),
offset.getOffsetPixels() - _curX, getLineBottomPosition() - getLineTopPosition());
applyRectangleStyle(r, offset);
}
};
if ((dayMask & (DayMask.WEEKEND)) != 0) {
// We render weekends always. If there is a colored event its color will be applied
// in applyRectangleStyle because getholidaycolor returns non-null
r.run();
} else if (event != null) {
// It is not a weekends but it is an event
// Holidays should always be painted, but neutral and working days should not unless
// they have a custom color
if (event.getType() == CalendarEvent.Type.HOLIDAY || event.getColor() != null) {
r.run();
}
}
curX = offset.getOffsetPixels();
}
}
private void renderNonWorkingDay(int curX, Offset curOffset) {
Canvas.Rectangle r = getCanvas().createRectangle(curX, getLineBottomPosition(),
curOffset.getOffsetPixels() - curX, getHeight());
applyRectangleStyle(r, curOffset);
}
private void applyRectangleStyle(Rectangle r, Offset offset) {
Color customColor = myInputApi.getHolidayColor(offset.getOffsetStart());
if (customColor != null) {
r.setBackgroundColor(customColor);
r.setOpacity(1.0f);
}
if ((offset.getDayMask() & DayMask.HOLIDAY) == DayMask.HOLIDAY) {
r.setStyle("calendar.holiday");
return;
}
if ((offset.getDayMask() & DayMask.WEEKEND) == DayMask.WEEKEND) {
r.setStyle("calendar.weekend");
return;
}
}
private boolean isProjectBoundariesOptionOn() {
return myProjectDatesOption.isChecked();
}
private int getLineTopPosition() {
return myInputApi.getTopLineHeight();
}
private int getLineBottomPosition() {
return getLineTopPosition() + getLineHeight();
}
private int getLineHeight() {
return getLineTopPosition();
}
}