net.sourceforge.ganttproject.chart.ChartModelBase.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.ganttproject.chart.ChartModelBase.java

Source

/*
GanttProject is an opensource project management tool.
Copyright (C) 2004-2011 Dmitry Barashev, GanttProject Team
    
This program 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.
    
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.sourceforge.ganttproject.chart;

import biz.ganttproject.core.calendar.CalendarEvent;
import biz.ganttproject.core.chart.canvas.Canvas;
import biz.ganttproject.core.chart.canvas.Painter;
import biz.ganttproject.core.chart.grid.*;
import biz.ganttproject.core.chart.grid.OffsetManager.OffsetBuilderFactory;
import biz.ganttproject.core.chart.render.TextLengthCalculatorImpl;
import biz.ganttproject.core.chart.scene.DayGridSceneBuilder;
import biz.ganttproject.core.chart.scene.SceneBuilder;
import biz.ganttproject.core.chart.scene.TimelineSceneBuilder;
import biz.ganttproject.core.chart.text.TimeFormatter;
import biz.ganttproject.core.chart.text.TimeFormatters;
import biz.ganttproject.core.chart.text.TimeUnitText.Position;
import biz.ganttproject.core.option.*;
import biz.ganttproject.core.time.TimeDuration;
import biz.ganttproject.core.time.TimeUnit;
import biz.ganttproject.core.time.TimeUnitFunctionOfDate;
import biz.ganttproject.core.time.TimeUnitStack;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.sourceforge.ganttproject.chart.item.CalendarChartItem;
import net.sourceforge.ganttproject.chart.item.ChartItem;
import net.sourceforge.ganttproject.chart.item.TimelineLabelChartItem;
import net.sourceforge.ganttproject.gui.UIConfiguration;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.language.GanttLanguage.Event;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.text.DateFormat;
import java.util.*;
import java.util.List;

/**
 * Controls painting of the common part of Gantt and resource charts (in
 * particular, timeline). Calculates data required by the specific charts (e.g.
 * calculates the offsets of the timeline grid cells)
 */
public abstract class ChartModelBase
        implements /* TimeUnitStack.Listener, */ChartModel, TimelineLabelRendererImpl.ChartModelApi {
    public static interface ScrollingSession {
        void scrollTo(int xpos, int ypos);

        void finish();
    }

    private class ScrollingSessionImpl implements ScrollingSession {
        private int myPrevXpos;

        private OffsetList myTopOffsets;
        private OffsetList myBottomOffsets;
        private OffsetList myDefaultOffsets;

        private ScrollingSessionImpl(int startXpos) {
            // System.err.println("start xpos=" + startXpos);
            myPrevXpos = startXpos;
            ChartModelBase.this.myScrollingSession = this;
            ChartModelBase.this.myOffsetManager.reset();
            myTopOffsets = getTopUnitOffsets();
            myBottomOffsets = getBottomUnitOffsets();
            myDefaultOffsets = getDefaultUnitOffsets();
            // shiftOffsets(-myBottomOffsets.get(0).getOffsetPixels());
            // System.err.println(myBottomOffsets.subList(0, 3));
        }

        @Override
        public void scrollTo(int xpos, int ypos) {
            int shift = xpos - myPrevXpos;
            // System.err.println("xpos="+xpos+" shift=" + shift);
            shiftOffsets(shift);
            if (myBottomOffsets.get(0).getOffsetPixels() > 0) {
                int currentExceed = myBottomOffsets.get(0).getOffsetPixels();
                ChartModelBase.this.setStartDate(getBottomUnit().jumpLeft(getStartDate()));
                ChartModelBase.this.myOffsetManager.constructOffsets();
                shiftOffsets(-myBottomOffsets.get(1).getOffsetPixels() + currentExceed);
                // System.err.println("one time unit to the left. start date=" +
                // ChartModelBase.this.getStartDate());
                // System.err.println(myBottomOffsets.subList(0, 3));
            } else if (myBottomOffsets.get(1).getOffsetPixels() <= 0) {
                ChartModelBase.this.setStartDate(myBottomOffsets.get(2).getOffsetStart());
                ChartModelBase.this.myOffsetManager.constructOffsets();
                shiftOffsets(-myBottomOffsets.get(0).getOffsetPixels());
                // System.err.println("one time unit to the right. start date=" +
                // ChartModelBase.this.getStartDate());
                // System.err.println(myBottomOffsets.subList(0, 3));
            }
            myPrevXpos = xpos;
        }

        @Override
        public void finish() {
            Offset offset0 = myBottomOffsets.get(0);
            Offset offset1 = myBottomOffsets.get(1);
            int middle = (offset1.getOffsetPixels() + offset0.getOffsetPixels()) / 2;
            if (middle < 0) {
                ChartModelBase.this.setStartDate(myBottomOffsets.get(2).getOffsetStart());
            }

            ChartModelBase.this.myScrollingSession = null;
        }

        private void shiftOffsets(int shiftPixels) {
            myBottomOffsets.shift(shiftPixels);
            myTopOffsets.shift(shiftPixels);
            if (myDefaultOffsets != myBottomOffsets) {
                if (myDefaultOffsets.isEmpty()) {
                    myDefaultOffsets = ChartModelBase.this.getDefaultUnitOffsets();
                }
                myDefaultOffsets.shift(shiftPixels);
            }
        }
    }

    public static final Object STATIC_MUTEX = new Object();

    private static final Predicate<? super Task> MILESTONE_PREDICATE = new Predicate<Task>() {
        @Override
        public boolean apply(Task input) {
            return input.isMilestone();
        }
    };

    private final OptionEventDispatcher myOptionEventDispatcher = new OptionEventDispatcher();

    private Dimension myBounds;

    private Date myStartDate;

    protected int myAtomUnitPixels;

    protected final TimeUnitStack myTimeUnitStack;

    private TimeUnit myTopUnit;

    protected TimeUnit myBottomUnit;

    private final TimelineSceneBuilder myChartHeader;
    private final BackgroundRendererImpl myBackgroundRenderer;

    private final StyledPainterImpl myPainter;

    private final List<GPOptionChangeListener> myOptionListeners = new ArrayList<GPOptionChangeListener>();

    private final UIConfiguration myProjectConfig;
    private ChartUIConfiguration myChartUIConfiguration;

    private final List<SceneBuilder> myRenderers = Lists.newArrayList();

    private final DayGridSceneBuilder myChartGrid;

    private final TimelineLabelRendererImpl myTimelineLabelRenderer;

    protected final TaskManager myTaskManager;

    private int myVerticalOffset;

    private int myHorizontalOffset;

    private ScrollingSessionImpl myScrollingSession;

    private Set<Task> myTimelineTasks = Collections.emptySet();

    private final ChartOptionGroup myChartGridOptions;
    private final GPOptionGroup myTimelineLabelOptions;

    private final BooleanOption myTimelineMilestonesOption = new DefaultBooleanOption("timeline.showMilestones",
            true);
    private final FontOption myChartFontOption;

    public ChartModelBase(TaskManager taskManager, TimeUnitStack timeUnitStack,
            final UIConfiguration projectConfig) {
        myTaskManager = taskManager;
        myProjectConfig = projectConfig;
        myChartUIConfiguration = new ChartUIConfiguration(projectConfig);
        myChartFontOption = projectConfig.getChartFontOption();
        myPainter = new StyledPainterImpl(myChartUIConfiguration);
        myTimeUnitStack = timeUnitStack;
        final TimeFormatters.LocaleApi localeApi = new TimeFormatters.LocaleApi() {
            @Override
            public Locale getLocale() {
                return GanttLanguage.getInstance().getDateFormatLocale();
            }

            @Override
            public DateFormat createDateFormat(String pattern) {
                return GanttLanguage.getInstance().createDateFormat(pattern);
            }

            @Override
            public DateFormat getShortDateFormat() {
                return GanttLanguage.getInstance().getShortDateFormat();
            }

            @Override
            public String i18n(String key) {
                return GanttLanguage.getInstance().getText(key);
            }
        };
        final TimeFormatters timeFormatters = new TimeFormatters(localeApi);
        GanttLanguage.getInstance().addListener(new GanttLanguage.Listener() {
            @Override
            public void languageChanged(Event event) {
                timeFormatters.setLocaleApi(localeApi);
            }
        });
        myChartHeader = new TimelineSceneBuilder(new TimelineSceneBuilder.InputApi() {
            @Override
            public Date getViewportStartDate() {
                return getStartDate();
            }

            @Override
            public OffsetList getTopUnitOffsets() {
                return ChartModelBase.this.getTopUnitOffsets();
            }

            @Override
            public int getTopLineHeight() {
                return getChartUIConfiguration().getSpanningHeaderHeight();
            }

            @Override
            public int getTimelineHeight() {
                return getChartUIConfiguration().getHeaderHeight();
            }

            @Override
            public Color getTimelineBorderColor() {
                return getChartUIConfiguration().getHeaderBorderColor();
            }

            @Override
            public Color getTimelineBackgroundColor() {
                return getChartUIConfiguration().getSpanningHeaderBackgroundColor();
            }

            @Override
            public int getViewportWidth() {
                return getBounds().width;
            }

            @Override
            public OffsetList getBottomUnitOffsets() {
                return ChartModelBase.this.getBottomUnitOffsets();
            }

            @Override
            public TimeFormatter getFormatter(TimeUnit timeUnit, Position position) {
                return timeFormatters.getFormatter(timeUnit, position);
            }
        });
        myChartGridOptions = new ChartOptionGroup("ganttChartGridDetails",
                new GPOption[] { projectConfig.getRedlineOption(), projectConfig.getProjectBoundariesOption(),
                        projectConfig.getWeekendAlphaRenderingOption(),
                        myChartUIConfiguration.getChartStylesOption() },
                getOptionEventDispatcher());
        myChartGrid = new DayGridSceneBuilder(new DayGridSceneBuilder.InputApi() {
            @Override
            public Color getWeekendColor() {
                return myChartUIConfiguration.getHolidayTimeBackgroundColor();
            }

            @Override
            public Color getHolidayColor(Date holiday) {
                CalendarEvent event = getTaskManager().getCalendar().getEvent(holiday);
                if (event == null || event.getColor() == null) {
                    return null;
                }
                return event.getColor();
            }

            public CalendarEvent getEvent(Date date) {
                return getTaskManager().getCalendar().getEvent(date);
            }

            @Override
            public int getTopLineHeight() {
                return myChartUIConfiguration.getSpanningHeaderHeight();
            }

            @Override
            public BooleanOption getRedlineOption() {
                return projectConfig.getRedlineOption();
            }

            @Override
            public Date getProjectStart() {
                return getTaskManager().getProjectStart();
            }

            @Override
            public Date getProjectEnd() {
                return getTaskManager().getProjectEnd();
            }

            @Override
            public BooleanOption getProjectDatesOption() {
                return projectConfig.getProjectBoundariesOption();
            }

            @Override
            public OffsetList getAtomUnitOffsets() {
                return getDefaultUnitOffsets();
            }
        }, myChartHeader.getTimelineContainer());
        myBackgroundRenderer = new BackgroundRendererImpl(this);
        myTimelineLabelOptions = new ChartOptionGroup("timelineLabels",
                new GPOption[] { myTimelineMilestonesOption }, getOptionEventDispatcher());
        myTimelineLabelRenderer = new TimelineLabelRendererImpl(this);
        addRenderer(myBackgroundRenderer);
        addRenderer(myChartHeader);
        addRenderer(myChartGrid);
        addRenderer(myTimelineLabelRenderer);

        ChangeValueListener fontChangeValueListener = new ChangeValueListener() {
            @Override
            public void changeValue(ChangeValueEvent event) {
                setBaseFont(myChartFontOption.getValue());
            }
        };
        myChartFontOption.addChangeValueListener(fontChangeValueListener);
        getProjectConfig().getDpiOption().addChangeValueListener(fontChangeValueListener);
        setBaseFont(myChartFontOption.getValue());
    }

    private void setBaseFont(FontSpec fontSpec) {
        float scaleFactor = fontSpec.getSize().getFactor();
        if (getProjectConfig().getDpiOption() != null) {
            scaleFactor *= getProjectConfig().getDpiOption().getValue().floatValue() / UIFacade.DEFAULT_DPI;
        }
        Font font = new Font(fontSpec.getFamily(), Font.PLAIN, (int) (10 * scaleFactor));
        BufferedImage dummyImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR);
        Graphics2D g = (Graphics2D) dummyImage.getGraphics();
        TextLengthCalculatorImpl calculator = new TextLengthCalculatorImpl(g);
        int fontSize = calculator.getTextHeight(font, "Agpqf");
        getChartUIConfiguration().setBaseFont(font, fontSize);
    }

    private OffsetManager myOffsetManager = new OffsetManager(new OffsetBuilderFactory() {
        @Override
        public OffsetBuilder createTopAndBottomUnitBuilder() {
            return createOffsetBuilderFactory().build();
        }

        @Override
        public OffsetBuilder createAtomUnitBuilder() {
            int defaultUnitCountPerLastBottomUnit = OffsetBuilderImpl.getConcreteUnit(getBottomUnit(), getEndDate())
                    .getAtomCount(getDefaultUnit());
            return createOffsetBuilderFactory()
                    .withRightMargin(myScrollingSession == null ? 0 : defaultUnitCountPerLastBottomUnit * 2)
                    .withTopUnit(getBottomUnit()).withBottomUnit(getTimeUnitStack().getDefaultTimeUnit())
                    .withOffsetStepFunction(new Function<TimeUnit, Float>() {
                        @Override
                        public Float apply(TimeUnit timeUnit) {
                            int offsetUnitCount = timeUnit.getAtomCount(getTimeUnitStack().getDefaultTimeUnit());
                            return 1f / offsetUnitCount;
                        }
                    }).build();
        }
    });

    @Override
    public void resetOffsets() {
        myOffsetManager.reset();
    }

    @Override
    public OffsetList getTopUnitOffsets() {
        return myOffsetManager.getTopUnitOffsets();
    }

    @Override
    public OffsetList getBottomUnitOffsets() {
        return myOffsetManager.getBottomUnitOffsets();
    }

    @Override
    public OffsetList getDefaultUnitOffsets() {
        if (getBottomUnit().equals(getTimeUnitStack().getDefaultTimeUnit())) {
            return getBottomUnitOffsets();
        }
        return myOffsetManager.getAtomUnitOffsets();
    }

    Date getOffsetAnchorDate() {
        return /*
               * myScrollingSession == null ? myStartDate :
               */getBottomUnit().jumpLeft(myStartDate);
    }

    public OffsetBuilder.Factory createOffsetBuilderFactory() {
        OffsetBuilder.Factory factory = new OffsetBuilderImpl.FactoryImpl()
                .withAtomicUnitWidth(getBottomUnitWidth()).withBottomUnit(getBottomUnit())
                .withCalendar(myTaskManager.getCalendar()).withRightMargin(myScrollingSession == null ? 0 : 1)
                .withStartDate(getOffsetAnchorDate()).withViewportStartDate(getStartDate()).withTopUnit(myTopUnit)
                .withWeekendDecreaseFactor(getTopUnit().isConstructedFrom(getBottomUnit())
                        ? OffsetBuilderImpl.WEEKEND_UNIT_WIDTH_DECREASE_FACTOR
                        : 1f);
        if (getBounds() != null) {
            factory.withEndOffset((int) getBounds().getWidth());
        }
        return factory;
    }

    @Override
    public void paint(Graphics g) {
        int height = (int) getBounds().getHeight();
        for (SceneBuilder renderer : getRenderers()) {
            renderer.reset(height);
        }
        for (SceneBuilder renderer : getRenderers()) {
            renderer.build();
        }
        myPainter.setGraphics(g);
        for (SceneBuilder renderer : getRenderers()) {
            renderer.getCanvas().paint(myPainter);
        }
        for (int layer = 0;; layer++) {
            boolean layerPainted = false;
            for (SceneBuilder renderer : getRenderers()) {
                List<Canvas> layers = renderer.getCanvas().getLayers();
                if (layer < layers.size()) {
                    layers.get(layer).paint(myPainter);
                    layerPainted = true;
                }
            }
            if (!layerPainted) {
                break;
            }
        }
    }

    protected List<SceneBuilder> getRenderers() {
        return myRenderers;
    }

    @Override
    public void addRenderer(SceneBuilder renderer) {
        myRenderers.add(renderer);
    }

    protected Painter getPainter() {
        return myPainter;
    }

    public void resetRenderers() {
        myRenderers.clear();
    }

    @Override
    public void setBounds(Dimension bounds) {
        if (bounds != null && bounds.equals(myBounds)) {
            return;
        }
        myBounds = bounds;
        myOffsetManager.reset();
    }

    @Override
    public void setStartDate(Date startDate) {
        myHorizontalOffset = 0;
        if (!startDate.equals(myStartDate)) {
            myStartDate = startDate;
            myOffsetManager.reset();
        }
    }

    @Override
    public Date getStartDate() {
        return myStartDate;
    }

    @Override
    public Date getEndDate() {
        List<Offset> offsets = getBottomUnitOffsets();
        return offsets.isEmpty() ? null : offsets.get(offsets.size() - 1).getOffsetEnd();
    }

    @Override
    public void setBottomUnitWidth(int pixelsWidth) {
        if (pixelsWidth == myAtomUnitPixels) {
            return;
        }
        myAtomUnitPixels = pixelsWidth;
        myOffsetManager.reset();
    }

    @Override
    public void setRowHeight(int rowHeight) {
        getChartUIConfiguration().setRowHeight(rowHeight);
    }

    @Override
    public void setTopTimeUnit(TimeUnit topTimeUnit) {
        setTopUnit(topTimeUnit);
    }

    @Override
    public void setBottomTimeUnit(TimeUnit bottomTimeUnit) {
        if (bottomTimeUnit.equals(myBottomUnit)) {
            return;
        }
        myBottomUnit = bottomTimeUnit;
        myOffsetManager.reset();
    }

    protected UIConfiguration getProjectConfig() {
        return myProjectConfig;
    }

    @Override
    public Dimension getBounds() {
        return myBounds;
    }

    // @Override
    // public Dimension getMaxBounds() {
    // OffsetBuilderImpl offsetBuilder = new OffsetBuilderImpl(
    // this, Integer.MAX_VALUE, getTaskManager().getProjectEnd());
    // List<Offset> topUnitOffsets = new ArrayList<Offset>();
    // OffsetList bottomUnitOffsets = new OffsetList();
    // offsetBuilder.constructOffsets(topUnitOffsets, bottomUnitOffsets);
    // int width = topUnitOffsets.get(topUnitOffsets.size()-1).getOffsetPixels();
    // int height = calculateRowHeight()*getRowCount();
    // return new Dimension(width, height);
    // }

    public abstract int calculateRowHeight();

    // protected abstract int getRowCount();

    @Override
    public int getBottomUnitWidth() {
        return myAtomUnitPixels;
    }

    @Override
    public TimeUnitStack getTimeUnitStack() {
        return myTimeUnitStack;
    }

    @Override
    public ChartUIConfiguration getChartUIConfiguration() {
        return myChartUIConfiguration;
    }

    @Override
    public int getTimelineTopLineHeight() {
        return getChartUIConfiguration().getSpanningHeaderHeight();
    }

    private void setChartUIConfiguration(ChartUIConfiguration chartConfig) {
        myChartUIConfiguration = chartConfig;
    }

    @Override
    public TaskManager getTaskManager() {
        return myTaskManager;
    }

    @Override
    public Offset getOffsetAt(int x) {
        for (Offset offset : getDefaultUnitOffsets()) {
            if (offset.getOffsetPixels() >= x) {
                // System.err.println("result=" + offset);
                return offset;
            }
        }
        List<Offset> offsets = getBottomUnitOffsets();
        return offsets.get(offsets.size() - 1);
    }

    /**
     * @return A length of the visible part of this chart area measured in the
     *         bottom line time units
     */
    public TimeDuration getVisibleLength() {
        double pixelsLength = getBounds().getWidth();
        float unitsLength = (float) (pixelsLength / getBottomUnitWidth());
        TimeDuration result = getTaskManager().createLength(getBottomUnit(), unitsLength);
        return result;
    }

    public void setHeaderHeight(int i) {
        getChartUIConfiguration().setHeaderHeight(i);
    }

    @Override
    public void setVerticalOffset(int offset) {
        myVerticalOffset = offset;
    }

    protected int getVerticalOffset() {
        return myVerticalOffset;
    }

    public void setHorizontalOffset(int pixels) {
        myHorizontalOffset = pixels;
    }

    protected int getHorizontalOffset() {
        return myHorizontalOffset;
    }

    @Override
    public TimeUnit getBottomUnit() {
        return myBottomUnit;
    }

    private TimeUnit getDefaultUnit() {
        return getTimeUnitStack().getDefaultTimeUnit();
    }

    private void setTopUnit(TimeUnit topUnit) {
        if (topUnit.equals(myTopUnit)) {
            return;
        }
        this.myTopUnit = topUnit;
        myOffsetManager.reset();
    }

    public TimeUnit getTopUnit() {
        return getTopUnit(myStartDate);
    }

    private TimeUnit getTopUnit(Date startDate) {
        TimeUnit result = myTopUnit;
        if (myTopUnit instanceof TimeUnitFunctionOfDate) {
            if (startDate == null) {
                throw new RuntimeException("No date is set");
            }
            result = ((TimeUnitFunctionOfDate) myTopUnit).createTimeUnit(startDate);
        }
        return result;
    }

    public GPOptionGroup[] getChartOptionGroups() {
        return new GPOptionGroup[] { myChartGridOptions, myTimelineLabelOptions };
    }

    public void addOptionChangeListener(GPOptionChangeListener listener) {
        myOptionListeners.add(listener);
    }

    protected void fireOptionsChanged() {
        for (GPOptionChangeListener next : myOptionListeners) {
            next.optionsChanged();
        }
    }

    public abstract ChartModelBase createCopy();

    protected void setupCopy(ChartModelBase copy) {
        copy.setTopTimeUnit(getTopUnit());
        copy.setBottomTimeUnit(getBottomUnit());
        copy.setBottomUnitWidth(getBottomUnitWidth());
        copy.setStartDate(getStartDate());
        copy.setChartUIConfiguration(myChartUIConfiguration.createCopy());
        copy.setBounds(getBounds());
        copy.setTimelineTasks(myTimelineTasks);
        copy.myTimelineMilestonesOption.setValue(myTimelineMilestonesOption.getValue());
        GPOptionGroup[] copyOptions = copy.getChartOptionGroups();
        GPOptionGroup[] thisOptions = getChartOptionGroups();
        assert copyOptions.length == thisOptions.length;
        for (int i = 0; i < copyOptions.length; i++) {
            copyOptions[i].copyFrom(thisOptions[i]);
        }
        copy.myChartFontOption.setValue(myChartFontOption.getValue());
        copy.calculateRowHeight();
    }

    @Override
    public OptionEventDispatcher getOptionEventDispatcher() {
        return myOptionEventDispatcher;
    }

    public class OptionEventDispatcher {
        void optionsChanged() {
            fireOptionsChanged();
        }
    }

    public ScrollingSession createScrollingSession(int startXpos) {
        assert myScrollingSession == null;
        return new ScrollingSessionImpl(startXpos);
    }

    public ChartItem getChartItemWithCoordinates(int x, int y) {
        Canvas.Shape text = myTimelineLabelRenderer.getLabelLayer().getPrimitive(x, y);
        if (text instanceof Canvas.Text) {
            return new TimelineLabelChartItem((Task) text.getModelObject());
        }
        Offset offset = getOffsetAt(x);
        if (offset != null) {
            return new CalendarChartItem(offset.getOffsetStart());
        }
        return null;
    }

    @Override
    public Collection<Task> getTimelineTasks() {
        return Sets.union(myTimelineTasks, getMilestones());
    }

    private Set<Task> getMilestones() {
        return myTimelineMilestonesOption.getValue()
                ? Sets.filter(Sets.newHashSet(getTaskManager().getTasks()), MILESTONE_PREDICATE)
                : Collections.<Task>emptySet();
    }

    public void setTimelineTasks(Set<Task> timelineTasks) {
        myTimelineTasks = timelineTasks;
    }
}