/* Copyright 2010-2012 GanttProject Team This file is part of GanttProject, an opensource project management tool. 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.gantt; import biz.ganttproject.core.chart.canvas.Canvas; import biz.ganttproject.core.chart.canvas.Canvas.Polygon; 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.chart.render.AlphaRenderingOption; import biz.ganttproject.core.chart.render.ShapeConstants; import biz.ganttproject.core.chart.render.ShapePaint; import biz.ganttproject.core.chart.scene.BarChartActivity; import com.google.common.collect.Lists; import java.awt.*; import java.util.Date; import java.util.List; /** * Renders task activity rectangles on the Gantt chart. * * @author dbarashev (Dmitry Barashev) */ public class TaskActivitySceneBuilder<T, A extends BarChartActivity<T>> { private final Canvas myCanvas; private final TaskLabelSceneBuilder<T> myLabelsRenderer; private final Style myStyle; private final TaskApi<T, A> myTaskApi; private final ChartApi myChartApi; private final StyleApplier<T, A> myStyleApplier; public static class Style { int marginTop; public Style(int marginTop) { this.marginTop = marginTop; } } public static interface TaskApi<T, A> { boolean isFirst(A activity); boolean isLast(A activity); boolean isVoid(A activity); boolean isCriticalTask(T task); boolean isProjectTask(T task); boolean isMilestone(T task); boolean hasNestedTasks(T task); boolean hasNotes(T task); Color getColor(T task); ShapePaint getShapePaint(T task); } public static interface ChartApi { Date getChartStartDate(); Date getEndDate(); OffsetList getBottomUnitOffsets(); int getRowHeight(); int getBarHeight(); int getViewportWidth(); AlphaRenderingOption getWeekendOpacityOption(); } public TaskActivitySceneBuilder(TaskApi<T, A> taskApi, ChartApi chartApi, Canvas canvas, TaskLabelSceneBuilder<T> labelsRenderer, Style style) { myTaskApi = taskApi; myChartApi = chartApi; myStyle = style; myCanvas = canvas; myLabelsRenderer = labelsRenderer; myStyleApplier = new StyleApplier<T, A>(taskApi); } static class StyleApplier<T, A> { private T myTask; private final TaskApi<T, A> myTaskApi; StyleApplier(TaskApi<T, A> taskApi) { myTaskApi = taskApi; } void setTask(T task) { myTask = task; } /** * Depending on the context of the associated task, paint the color and * striping of a shape. * * @param shape the graphic component to paint */ void applyStyle(Canvas.Shape shape) { if (shape instanceof Canvas.Rectangle) { ((Canvas.Rectangle) shape).setBackgroundPaint(new ShapePaint( myTaskApi.getShapePaint(myTask), Color.BLACK, myTaskApi.getColor(myTask))); } if (myTaskApi.isCriticalTask(myTask)) { if (myTaskApi.hasNestedTasks(myTask)) { shape.setBackgroundColor(Color.RED); } else if (shape instanceof Canvas.Rectangle) { ((Canvas.Rectangle) shape).setBackgroundPaint(new ShapePaint( ShapeConstants.THICK_BACKSLASH, Color.BLACK, myTaskApi .getColor(myTask))); } } else if (!"task.holiday".equals(shape.getStyle())) { shape.setBackgroundColor(myTaskApi.getColor(myTask)); } } } public List<Polygon> renderActivities(int rowNum, List<A> activities, OffsetList offsets) { List<Polygon> rectangles = Lists.newArrayList(); for (A activity : activities) { if (myTaskApi.isFirst(activity) || myTaskApi.isLast(activity)) { if (myTaskApi.isVoid(activity)) { continue; } } myStyleApplier.setTask(activity.getOwner()); if (activity.getEnd().compareTo(myChartApi.getChartStartDate()) <= 0) { processActivityEarlierThanViewport(rowNum, activity, rectangles); } else if (activity.getStart().compareTo(myChartApi.getEndDate()) >= 0) { processActivityLaterThanViewport(rowNum, activity, rectangles); } else { processRegularActivity(rowNum, activity, offsets, rectangles); } } return rectangles; } private void processActivityLaterThanViewport(int rowNum, BarChartActivity<T> nextActivity, List<Polygon> polygons) { Canvas container = myCanvas; int startx = myChartApi.getBottomUnitOffsets().getEndPx() + 1; int topy = rowNum * getRowHeight() + 4; Rectangle rectangle = container.createRectangle(startx, topy, 1, getRowHeight()); container.bind(rectangle, nextActivity); rectangle.setVisible(false); polygons.add(rectangle); } private void processActivityEarlierThanViewport(int rowNum, BarChartActivity<T> nextActivity, List<Polygon> polygons) { Canvas container = myCanvas; int startx = myChartApi.getBottomUnitOffsets().getStartPx() - 1; int topy = rowNum * getRowHeight() + 4; Rectangle rectangle = container.createRectangle(startx, topy, 1, getRowHeight()); container.bind(rectangle, nextActivity); rectangle.setVisible(false); polygons.add(rectangle); } private void processRegularActivity(int rowNum, A activity, OffsetList offsets, List<Polygon> polygons) { T nextTask = activity.getOwner(); if (myTaskApi.isMilestone(nextTask) && !myTaskApi.isFirst(activity)) { return; } java.awt.Rectangle nextBounds = getBoundingRectangle(rowNum, activity, offsets); myLabelsRenderer.stripVerticalLabelSpace(nextBounds); final int nextLength = nextBounds.width; final int topy = nextBounds.y + myStyle.marginTop; boolean nextHasNested = myTaskApi.hasNestedTasks(nextTask); Canvas container = myCanvas; Canvas.Polygon resultShape; if (myTaskApi.isMilestone(nextTask)) { //nextRectangle.setVisible(false); //System.err.println("milestone rect=" + nextRectangle); //container.bind(nextRectangle, activity); Canvas.Rectangle rect = container.createDetachedRectangle(nextBounds.x, topy, nextLength, getRectangleHeight()); int rectHeight = rect.getHeight(); int rectHalf = rectHeight / 2; int middleX = rect.getLeftX() + 3; // This is important to draw dependencies to/from milestones properly Canvas.Polygon p = container.createPolygon( middleX - rectHalf, rect.getMiddleY(), middleX, rect.getMiddleY() - rectHalf, middleX + rectHalf, rect.getMiddleY(), middleX, rect.getMiddleY() + rectHalf); p.setStyle("task.milestone"); //container.bind(p, activity); resultShape = p; } else { Canvas.Rectangle nextRectangle = container.createRectangle(nextBounds.x, topy, nextLength, getRectangleHeight()); resultShape = nextRectangle; if (nextHasNested || myTaskApi.isProjectTask(nextTask)) { String prefix = myTaskApi.isProjectTask(nextTask) ? "task.projectTask" : "task.supertask"; nextRectangle.setStyle(prefix); if (myTaskApi.isFirst(activity)) { Canvas.Polygon ending = container.createPolygon(nextRectangle.getLeftX(), nextRectangle.getTopY(), nextRectangle.getLeftX() + nextRectangle.getHeight(), nextRectangle.getTopY(), nextRectangle.getLeftX(), nextRectangle.getBottomY()); ending.setStyle(prefix + ".start"); ending.addStyle("task.ending"); myStyleApplier.applyStyle(ending); polygons.add(ending); } if (myTaskApi.isLast(activity)) { Canvas.Polygon ending = container.createPolygon(nextRectangle.getRightX(), nextRectangle.getTopY(), nextRectangle.getRightX() - nextRectangle.getHeight(), nextRectangle.getTopY(), nextRectangle.getRightX(), nextRectangle.getBottomY()); ending.setStyle(prefix + ".end"); ending.addStyle("task.ending"); myStyleApplier.applyStyle(ending); polygons.add(ending); } } else { if (myTaskApi.isFirst(activity) && myTaskApi.isLast(activity)) { nextRectangle.setStyle("task.startend"); } else if (false == myTaskApi.isFirst(activity) ^ myTaskApi.isLast(activity)) { nextRectangle.setStyle("task"); } else if (myTaskApi.isFirst(activity)) { nextRectangle.setStyle("task.start"); } else if (myTaskApi.isLast(activity)) { nextRectangle.setStyle("task.end"); } if (myTaskApi.isVoid(activity)) { nextRectangle.setOpacity(myChartApi.getWeekendOpacityOption().getValueAsFloat()); } } } myStyleApplier.applyStyle(resultShape); container.bind(resultShape, activity); polygons.add(resultShape); } private java.awt.Rectangle getBoundingRectangle(int rowNum, BarChartActivity<T> activity, List<Offset> offsets) { OffsetLookup offsetLookup = new OffsetLookup(); int[] bounds = offsetLookup.getBounds(activity.getStart(), activity.getEnd(), offsets); int leftX = bounds[0]; int rightX = bounds[1]; int topY = rowNum * getRowHeight(); return new java.awt.Rectangle(leftX, topY, rightX - leftX, getRowHeight()); } private int getRectangleHeight() { return myChartApi.getBarHeight(); } private int getRowHeight() { return myChartApi.getRowHeight(); } }