/******************************************************************************* * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * emil.crumhorn@gmail.com - initial API and implementation *******************************************************************************/ package org.eclipse.nebula.widgets.ganttchart; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import org.eclipse.nebula.widgets.ganttchart.utils.DateRange; import org.eclipse.swt.graphics.Color; /** * This class allows you to color a certain date range in a special background color, as well as set things such as if * events should be allowed to be moved onto this range or not. You can also set a repeating range by setting certain * days (Monday, Tuesday, etc) that will be repeatedly blocked. <p /> For example, to block all events from ending up on * weekends, you would do: * * <pre> * GanttSpecialDateRange weekends = new GanttSpecialDateRange(parentChart); * weekends.addRecurDay(Calendar.SATURDAY); * weekends.addRecurDay(Calendar.SUNDAY); * weekends.setAllowEventsOnDates(false); * </pre> * * To block a Tuesday before 8.30am and after 5.30pm, 10 times, starting Jan 1, 2009, you would do: * * <pre> * Calendar cal = Calendar.getInstance(Locale.getDefault()); * cal.set(Calendar.YEAR, 2009); * cal.set(Calendar.MONTH, Calendar.JANUARY); * cal.set(Calendar.DATE, 1); * * GanttSpecialDateRange blockPre = new GanttSpecialDateRange(parentChart); * blockPre.setStart(cal); * blockPre.addRecurDay(Calendar.SATURDAY); * blockPre.setEndHour(8); * blockPre.setEndMinute(29); * blockPre.setEndAfter(10); * * GanttSpecialDateRange blockPost = new GanttSpecialDateRange(parentChart); * blockPre.setStart(cal); * blockPost.addRecurDay(Calendar.SATURDAY); * blockPost.setStartHour(17); * blockPost.setStartMinute(30); * blockPost.setEndAfter(10); * * blockPre.setAllowEventsOnDates(false); * blockPost.setAllowEventsOnDates(false); * </pre> * * For a D-Day calendar (which does not use actual dates (at least visibily)) a typical creation may look like this: * * <pre> * Calendar start = (Calendar) _ddayRootCalendar.clone(); * Calendar end = (Calendar) start.clone(); * end.add(Calendar.DATE, 50); * GanttSpecialDateRange range = new GanttSpecialDateRange(_ganttChart, start, end); * // these need to be set to indicate that the range should adapt to D-Day logic * range.setFrequency(GanttSpecialDateRange.REPEAT_DDAY); * range.setDDayRepeatInterval(10); * // -- * range.setRecurCount(50); * range.setBackgroundColorTop(ColorCache.getRandomColor()); * range.setBackgroundColorBottom(ColorCache.getRandomColor()); * </pre> * * @author cre */ public class GanttSpecialDateRange { public static final int REPEAT_DAILY = 1; public static final int REPEAT_WEEKLY = 2; public static final int REPEAT_MONTHLY = 3; public static final int REPEAT_YEARLY = 4; public static final int REPEAT_DDAY = 5; public static final int NO_END = -1; private Calendar _start; private Calendar _end; private Color _bgColorTop = ColorCache.getWhite(); private Color _bgColorBottom = ColorCache.getBlack(); private GanttChart _parentChart; private GanttComposite _parentComposite; private boolean _allowEventsOnDates = true; private int _frequency = REPEAT_WEEKLY; private int _recurCount = 1; private List _recurDays; private int _startHour = 0; private int _startMinute = 0; private final int _startSecond = 0; private int _endHour = 23; private int _endMinute = 59; private final int _endSecond = 59; private int _endAfter = NO_END; private Calendar _lastActualEndDate; private List _cachedRanges = null; private int _ddayRepeatInterval = 0; GanttSpecialDateRange() { this(null, null, null); } /** * Creates a new Gantt Special Date Range that indicates a certain set of dates with colors. * * @param parent Parent chart */ public GanttSpecialDateRange(final GanttChart parent) { this(parent, null, null); } /** * Creates a new Gantt Special Date Range that indicates a certain set of dates. * * @param parent Parent chart * @param start Start date * @param end End date */ public GanttSpecialDateRange(final GanttChart parent, final Calendar start, final Calendar end) { _recurDays = new ArrayList(); _parentChart = parent; if (parent != null) { _parentComposite = parent.getGanttComposite(); } _start = (start == null ? null : DateHelper.getNewCalendar(start)); _end = (end == null ? null : DateHelper.getNewCalendar(end)); if (parent != null) { _parentComposite.addSpecialDateRange(this); } updateCalculations(); } /** * Returns the start date. * * @return Start date */ public Calendar getStart() { return _start; } /** * Sets the start date. * * @param start Start date */ public void setStart(final Calendar start) { _start = (start == null ? null : DateHelper.getNewCalendar(start)); updateCalculations(); } /** * Returns the end date. * * @return End date */ public Calendar getEnd() { return _end; } /** * Sets the end date. * * @param end End date */ public void setEnd(final Calendar end) { _end = (end == null ? null : DateHelper.getNewCalendar(end)); updateCalculations(); } /** * Returns the gradient top color. * * @return Top color */ public Color getBackgroundColorTop() { return _bgColorTop; } /** * Sets the gradient top color. * * @param backgroundColorTop Top color or null if none (transparent) */ public void setBackgroundColorTop(final Color backgroundColorTop) { _bgColorTop = backgroundColorTop; } /** * Returns the gradient bottom color. * * @return Bottom color */ public Color getBackgroundColorBottom() { return _bgColorBottom; } /** * Sets the gradient bottom color. * * @param backgroundColorBottom Bottom color or null if none (transparent) */ public void setBackgroundColorBottom(final Color backgroundColorBottom) { _bgColorBottom = backgroundColorBottom; } /** * Returns the chart that this range is associated with. * * @return {@link GanttChart} parent */ public GanttChart getParentChart() { return _parentChart; } /** * Returns the chart composite this range is associated with. * * @return {@link GanttComposite} parent */ public GanttComposite getParentComposite() { return _parentComposite; } /** * Whether events can be resized or dropped on the date range specified in this class. Default is true. * * @return true if allowed */ public boolean isAllowEventsOnDates() { return _allowEventsOnDates; } /** * Sets whether events can be resized or dropped on to the date range specified in this class. Default is true. * * @param allowEventsOnDates true if allowed */ public void setAllowEventsOnDates(final boolean allowEventsOnDates) { _allowEventsOnDates = allowEventsOnDates; } /** * Adds a date that will be always used as a range date. The date is one of the Calendar dates, such as * {@link Calendar#MONDAY}. This is purely for convenience instead of having to create multiple special date ranges * to cover things such as weekends. Do note if you add specific hours, only the specified hour on the set days will * be covered and not the full day itself. <p /> If the frequency is set to {@value #REPEAT_DDAY} this method does * nothing and you should instead be using {@link #setDDayRepeatInterval(int)} as DDay calendars has no notion of * weekdates. * * @param day Calendar weekday to add * @return true if added, false if not */ public boolean addRecurDay(final int day) { if (day < Calendar.SUNDAY || day > Calendar.SATURDAY) { return false; } if (_recurDays.contains(new Integer(day))) { return false; } final boolean ret = _recurDays.add(new Integer(day)); if (ret) { updateCalculations(); } return ret; } /** * Removes a set date. * * @param calDate Date to remove * @return true if removed */ public boolean removeRecurDay(final int calDate) { final boolean ret = _recurDays.remove(new Integer(calDate)); if (ret) { updateCalculations(); } return ret; } /** * Returns the frequency. * * @return frequency */ public int getFrequency() { return _frequency; } /** * Sets the repeat frequency. Options are {@link #REPEAT_DAILY}, {@link #REPEAT_MONTHLY}, {@link #REPEAT_WEEKLY}, * {@link #REPEAT_YEARLY} or {@link #REPEAT_DDAY} for DDay calendars. * * @param frequency Frequency to set */ public void setFrequency(final int frequency) { _frequency = frequency; updateCalculations(); } /** * Returns the currently set DDay repeat interval. This is only used if frequency is set to {@link #REPEAT_DDAY}. * * @return repeat interval */ public int getDDayRepeatInterval() { return _ddayRepeatInterval; } /** * Sets the custom DDay repeat interval. This is only used if frequency is set to {@link #REPEAT_DDAY}. * * @param interval Custom repeat interval of n DDays */ public void setDDayRepeatInterval(final int interval) { _ddayRepeatInterval = interval; } /** * Returns the "recurs every" value. * * @return recurs every value */ public int getRecurCount() { return _recurCount; } /** * How often this event re-occurs. By default it's always 1. To end after a certain number of recurrences, use * {@link #setEndAfter(int)}. * * @param recurMax Recurrence frequency */ public void setRecurCount(final int recurMax) { _recurCount = recurMax; updateCalculations(); } /** * Returns the list of currently set recurring days. * * @return List of recurring days */ public List getRecurDays() { return _recurDays; } /** * Returns the start hour. * * @return Start hour */ public int getStartHour() { return _startHour; } /** * Sets the start hour. Hour should be in a 24h format from 0 to 23. * * @param startHour start hour * @return true if set */ public boolean setStartHour(final int startHour) { if (startHour < 0 || startHour > 23) { return false; } _startHour = startHour; updateCalculations(); return true; } /** * Returns the start minute. * * @return start minute */ public int getStartMinute() { return _startMinute; } /** * Sets the start minute. Minute should be between 0 and 59. * * @param startMinute start minute * @return true if set */ public boolean setStartMinute(final int startMinute) { if (startMinute < 0 || startMinute > 59) { return false; } _startMinute = startMinute; updateCalculations(); return true; } /** * Returns the end hour. * * @return end hour */ public int getEndHour() { return _endHour; } /** * Sets the end hour. Hour should be in a 24h format from 0 to 23. * * @param endHour end hour * @return true if set */ public boolean setEndHour(final int endHour) { if (endHour < 0 || endHour > 23) { return false; } _endHour = endHour; updateCalculations(); return true; } /** * Returns the end minute * * @return end minute */ public int getEndMinute() { return _endMinute; } /** * Sets the end minute. Minute should be between 0 and 59. * * @param endMinute start minute * @return true if set */ public boolean setEndMinute(final int endMinute) { if (endMinute < 0 || endMinute > 59) { return false; } _endMinute = endMinute; updateCalculations(); return true; } /** * Returns the end after value that defines the number of recurring repetitions of the event. * * @return end after value */ public int getEndAfter() { return _endAfter; } /** * Sets how many times an event should re-occur and then end. This is the end value. To set a no-end, use * {@link #NO_END} as value. * * @param endAfter After how many re-occurances to stop. */ public void setEndAfter(final int endAfter) { _endAfter = endAfter; updateCalculations(); } public void setParentChart(final GanttChart parentChart) { _parentChart = parentChart; } public void setParentComposite(final GanttComposite parentComposite) { _parentComposite = parentComposite; } private void updateCalculations() { _lastActualEndDate = null; _cachedRanges = null; } /** * Checks whether a set of dates overlap any of the dates in this range. * * @param start Start date * @param end End date * @return true if any date is overlapping the dates of this range */ public boolean canEventOccupy(final Calendar start, final Calendar end) { if (isAllowEventsOnDates()) { return true; } // we're not in range, check this first as it's faster if (!isVisible(start, end)) { return true; } // get all blocks that we occupy List blocks = getBlocks(start, end); DateRange us = new DateRange(_start, _end); for (int i = 0; i < blocks.size(); i++) { ArrayList block = (ArrayList) blocks.get(i); Calendar blockStart = (Calendar) block.get(0); Calendar blockEnd = (Calendar) block.get(1); DateRange range = new DateRange(blockStart, blockEnd); if (us.Overlaps(range)) { return false; } } return true; } /* * Checks whether this range is visible in the given start/end date range */ boolean isVisible(final Calendar start, final Calendar end) { if (!isUseable()) { return false; } // TODO: DDay calendar is fucked here //System.err.println(start.getTime() + " # " + end.getTime() + " --- " + _start.getTime() + " # " + _end.getTime() + " -- " + getActualEndDate().getTime() + " + " + _recurCount + " + " + _lastActualEndDate.getTime()); // doesn't recur on any days at all // TODO: This should be possible to be empty, then we just use the start / end dates if (_recurDays.isEmpty() && _frequency != REPEAT_DDAY) { return false; } // doesn't recur, what's the point?? if (_recurCount <= 0) { return false; } // ends on specific date which is in the past if (_end != null && _end.before(start)) { return false; } // same deal final Calendar aEnd = getActualEndDate(); //System.err.println(aEnd.getTime() + " "+ end.getTime()); if (aEnd != null && aEnd.before(start)) { return false; } // now it's easy if (_start.before(end) && aEnd.after(start)) { return true; } return false; } Calendar getActualStartDate() { return _start; } Calendar getActualEndDate() { if (_lastActualEndDate != null) { return _lastActualEndDate; } if (_end != null) { _lastActualEndDate = DateHelper.getNewCalendar(_end); return _end; } // move calendar to end recurring date final Calendar cal = DateHelper.getNewCalendar(_start); for (int i = 0; i < _recurCount; i++) { switch (_frequency) { case REPEAT_DAILY: cal.add(Calendar.DATE, 1); break; case REPEAT_WEEKLY: cal.add(Calendar.WEEK_OF_YEAR, 1); break; case REPEAT_MONTHLY: cal.add(Calendar.MONTH, 1); break; case REPEAT_YEARLY: cal.add(Calendar.YEAR, 1); break; case REPEAT_DDAY: cal.add(Calendar.DATE, _ddayRepeatInterval); break; default: break; } } // set the end day to the highest day of that week final int d = getHighestRecurDate(); cal.set(Calendar.DAY_OF_WEEK, d); cal.set(Calendar.HOUR_OF_DAY, _endHour); cal.set(Calendar.MINUTE, _endMinute); cal.set(Calendar.SECOND, _endSecond); cal.set(Calendar.MILLISECOND, 999); _lastActualEndDate = DateHelper.getNewCalendar(cal); return cal; } List getBlocks() { return getBlocks(null, null); } List getBlocks(final Calendar start, final Calendar end) { // if (_cachedRanges != null) { return _cachedRanges; } _cachedRanges = new ArrayList(); final Calendar cal = DateHelper.getNewCalendar(_start); final Calendar ourEnd = getActualEndDate(); for (int i = 0; i < _recurCount; i++) { final Calendar calEnd = DateHelper.getNewCalendar(cal); if (_recurDays.isEmpty() && _frequency == REPEAT_DDAY) { cal.set(Calendar.HOUR_OF_DAY, _startHour); cal.set(Calendar.MINUTE, _startMinute); cal.set(Calendar.SECOND, _startSecond); cal.set(Calendar.MILLISECOND, 0); calEnd.set(Calendar.HOUR_OF_DAY, _endHour); calEnd.set(Calendar.MINUTE, _endMinute); calEnd.set(Calendar.SECOND, _endSecond); calEnd.set(Calendar.MILLISECOND, 999); if (calEnd.after(end)) { continue; } if (ourEnd != null && calEnd.after(ourEnd)) { continue; } final List foo = new ArrayList(); foo.add(DateHelper.getNewCalendar(cal)); foo.add(DateHelper.getNewCalendar(calEnd)); _cachedRanges.add(foo); } else { for (int x = 0; x < _recurDays.size(); x++) { final int day = ((Integer) _recurDays.get(x)).intValue(); cal.set(Calendar.HOUR_OF_DAY, _startHour); cal.set(Calendar.MINUTE, _startMinute); cal.set(Calendar.SECOND, _startSecond); cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.DAY_OF_WEEK, day); calEnd.set(Calendar.HOUR_OF_DAY, _endHour); calEnd.set(Calendar.MINUTE, _endMinute); calEnd.set(Calendar.SECOND, _endSecond); calEnd.set(Calendar.MILLISECOND, 999); calEnd.set(Calendar.DAY_OF_WEEK, day); if (start != null && calEnd.before(start)) { continue; } if (end != null && cal.after(end) || cal.after(ourEnd)) { continue; } final List foo = new ArrayList(); foo.add(DateHelper.getNewCalendar(cal)); foo.add(DateHelper.getNewCalendar(calEnd)); _cachedRanges.add(foo); } } switch (_frequency) { case REPEAT_DAILY: cal.add(Calendar.DATE, 1); break; case REPEAT_WEEKLY: cal.add(Calendar.WEEK_OF_YEAR, 1); break; case REPEAT_MONTHLY: cal.add(Calendar.MONTH, 1); break; case REPEAT_YEARLY: cal.add(Calendar.YEAR, 1); break; case REPEAT_DDAY: cal.add(Calendar.DATE, _ddayRepeatInterval); break; default: break; } } return _cachedRanges; } int getHighestRecurDate() { int max = 0; for (int i = 0; i < _recurDays.size(); i++) { final Integer day = (Integer) _recurDays.get(i); // NOPMD max = Math.max(max, day.intValue()); } return max; } boolean isUseable() { if (_start == null) { return false; } return true; } public String toString() { String freq = ""; switch (_frequency) { case REPEAT_DAILY: freq = "Daily"; break; case REPEAT_WEEKLY: freq = "Weekly"; break; case REPEAT_MONTHLY: freq = "Monthly"; break; case REPEAT_YEARLY: freq = "Yearly"; break; case REPEAT_DDAY: freq = "DDay"; break; default: break; } return "[GanttSpecialDateRange: "+ (_start == null ? null : _start.getTime()) + " - " + (_end == null ? null : _end.getTime()) + ". Freqency: " + freq + ". Recur Count: " + _recurCount + ". Last actual end date: " + (_lastActualEndDate == null ? null : _lastActualEndDate.getTime()) + "]"; } }