com.vaadin.addon.calendar.gwt.client.ui.schedule.WeekGrid.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.addon.calendar.gwt.client.ui.schedule.WeekGrid.java

Source

/*
@VaadinAddonLicenseForJavaFiles@
 */
package com.vaadin.addon.calendar.gwt.client.ui.schedule;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.addon.calendar.gwt.client.ui.VCalendar;
import com.vaadin.addon.calendar.gwt.client.ui.schedule.WeekGrid.DateCell.DayEvent;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.DateTimeService;
import com.vaadin.terminal.gwt.client.Util;
import com.vaadin.terminal.gwt.client.VTooltip;

public class WeekGrid extends SimplePanel {

    private int width = 0;
    private int height = 0;
    private final HorizontalPanel content;
    private VCalendar calendar;
    private boolean disabled;
    private final Timebar timebar;
    private Panel wrapper;
    private boolean verticalScrollEnabled;
    private boolean horizontalScrollEnabled;
    private int[] cellHeights;
    private final int slotInMinutes = 30;
    private int dateCellBorder;
    private DateCell dateCellOfToday;
    private int[] cellWidths;
    private int firstHour;
    private int lastHour;

    public WeekGrid(VCalendar parent, boolean format24h) {
        setCalendar(parent);
        content = new HorizontalPanel();
        timebar = new Timebar(format24h);
        content.add(timebar);

        wrapper = new SimplePanel();
        wrapper.setStylePrimaryName("v-calendar-week-wrapper");
        wrapper.add(content);

        setWidget(wrapper);
    }

    private void setVerticalScroll(boolean isVerticalScrollEnabled) {
        if (isVerticalScrollEnabled && !(isVerticalScrollable())) {
            verticalScrollEnabled = true;
            horizontalScrollEnabled = false;
            wrapper.remove(content);

            final ScrollPanel scrollPanel = new ScrollPanel();
            scrollPanel.setStylePrimaryName("v-calendar-week-wrapper");
            scrollPanel.setWidget(content);

            scrollPanel.addScrollHandler(new ScrollHandler() {
                public void onScroll(ScrollEvent event) {
                    if (calendar.getScrollListener() != null) {
                        calendar.getScrollListener().scroll(scrollPanel.getScrollPosition());
                    }
                }
            });

            setWidget(scrollPanel);
            wrapper = scrollPanel;

        } else if (!isVerticalScrollEnabled && (isVerticalScrollable())) {
            verticalScrollEnabled = false;
            horizontalScrollEnabled = false;
            wrapper.remove(content);

            SimplePanel simplePanel = new SimplePanel();
            simplePanel.setStylePrimaryName("v-calendar-week-wrapper");
            simplePanel.setWidget(content);

            setWidget(simplePanel);
            wrapper = simplePanel;
        }
    }

    public void setVerticalScrollPosition(int verticalScrollPosition) {
        if (isVerticalScrollable()) {
            ((ScrollPanel) wrapper).setScrollPosition(verticalScrollPosition);
        }
    }

    public int getInternalWidth() {
        return width;
    }

    public void addDate(Date d) {
        final DateCell dc = new DateCell(this, d);
        dc.setDisabled(isDisabled());
        dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
        dc.setVerticalSized(isVerticalScrollable());
        content.add(dc);
    }

    /**
     * @param dateCell
     * @return get the index of the given date cell in this week, starting from
     *         0
     */
    public int getDateCellIndex(DateCell dateCell) {
        return content.getWidgetIndex(dateCell) - 1;
    }

    /**
     * @return get the slot border in pixels
     */
    public int getDateSlotBorder() {
        return ((DateCell) content.getWidget(1)).getSlotBorder();
    }

    private boolean isVerticalScrollable() {
        return verticalScrollEnabled;
    }

    private boolean isHorizontalScrollable() {
        return horizontalScrollEnabled;
    }

    public void setWidthPX(int width) {
        if (isHorizontalScrollable()) {
            updateCellWidths();

            // Otherwise the scroll wrapper is somehow too narrow = horizontal
            // scroll
            wrapper.setWidth(content.getOffsetWidth() + Util.getNativeScrollbarSize() + "px");

            this.width = content.getOffsetWidth() - timebar.getOffsetWidth();

        } else {
            this.width = (width == -1) ? width : width - timebar.getOffsetWidth();

            if (isVerticalScrollable() && width != -1) {
                this.width = this.width - Util.getNativeScrollbarSize();
            }
            updateCellWidths();
        }
    }

    public void setHeightPX(int intHeight) {
        height = intHeight;

        setVerticalScroll(height <= -1);

        // if not scrollable, use any height given
        if (!isVerticalScrollable() && height > 0) {

            if (BrowserInfo.get().isIE7() || BrowserInfo.get().isIE6()) {
                --height;
            }

            content.setHeight(height + "px");
            setHeight(height + "px");
            wrapper.setHeight(height + "px");
            wrapper.removeStyleDependentName("Vsized");
            updateCellHeights();
            timebar.setCellHeights(cellHeights);
            timebar.setHeightPX(height);

        } else if (isVerticalScrollable()) {
            updateCellHeights();
            wrapper.addStyleDependentName("Vsized");
            timebar.setHeightPX(height);
        }
    }

    public void clearDates() {
        while (content.getWidgetCount() > 1) {
            content.remove(1);
        }

        dateCellOfToday = null;
    }

    /**
     * @return true if this weekgrid contains a date that is today
     */
    public boolean hasToday() {
        return dateCellOfToday != null;
    }

    public void updateCellWidths() {
        if (!isHorizontalScrollable() && width != -1) {
            int count = content.getWidgetCount();
            int datesWidth = width;
            if (datesWidth > 0 && count > 1) {
                cellWidths = VCalendar.distributeSize(datesWidth, count - 1, -1);

                for (int i = 1; i < count; i++) {
                    DateCell dc = (DateCell) content.getWidget(i);
                    dc.setWidthPX(cellWidths[i - 1]);
                    if (dc.isToday()) {
                        dc.setTimeBarWidth(getOffsetWidth());
                    }
                }
            }

        } else {
            int count = content.getWidgetCount();
            if (count > 1) {
                for (int i = 1; i < count; i++) {
                    DateCell dc = (DateCell) content.getWidget(i);
                    dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
                }
            }
        }
    }

    /**
     * @return an int-array containing the widths of the cells (days)
     */
    public int[] getDateCellWidths() {
        return cellWidths;
    }

    public void updateCellHeights() {
        if (!isVerticalScrollable()) {
            int count = content.getWidgetCount();
            if (count > 1) {
                DateCell first = (DateCell) content.getWidget(1);
                dateCellBorder = first.getSlotBorder();
                cellHeights = VCalendar.distributeSize(height, first.getNumberOfSlots(), -dateCellBorder);
                for (int i = 1; i < count; i++) {
                    DateCell dc = (DateCell) content.getWidget(i);
                    dc.setHeightPX(height, cellHeights);
                }
            }

        } else {
            int count = content.getWidgetCount();
            if (count > 1) {
                DateCell first = (DateCell) content.getWidget(1);
                dateCellBorder = first.getSlotBorder();
                int dateHeight = (first.getOffsetHeight() / first.getNumberOfSlots()) - dateCellBorder;
                cellHeights = new int[48];
                Arrays.fill(cellHeights, dateHeight);

                for (int i = 1; i < count; i++) {
                    DateCell dc = (DateCell) content.getWidget(i);
                    dc.setVerticalSized(isVerticalScrollable());
                }
            }
        }
    }

    public void addEvent(CalendarEvent e) {
        int dateCount = content.getWidgetCount();
        Date from = e.getStart();
        Date toTime = e.getEndTime();
        for (int i = 1; i < dateCount; i++) {
            DateCell dc = (DateCell) content.getWidget(i);
            Date dcDate = dc.getDate();
            int comp = dcDate.compareTo(from);
            int comp2 = dcDate.compareTo(toTime);
            if (comp >= 0 && comp2 < 0 || (comp == 0 && comp2 == 0 && VCalendar.isZeroLengthMidnightEvent(e))) {
                // Same event may be over two DateCells if event's date
                // range floats over one day. It can't float over two days,
                // because event which range is over 24 hours, will be handled
                // as a "fullDay" event.
                dc.addEvent(dcDate, e);
            }
        }
    }

    public int getPixelLengthFor(int startFromMinutes, int durationInMinutes) {
        int pixelLength = 0;
        int currentSlot = 0;

        int firstHourInMinutes = firstHour * 60;

        if (firstHourInMinutes > startFromMinutes) {
            startFromMinutes = 0;
        } else {
            startFromMinutes -= firstHourInMinutes;
        }

        // calculate full slots to event
        int slotsTillEvent = startFromMinutes / slotInMinutes;
        int startOverFlowTime = slotInMinutes - (startFromMinutes % slotInMinutes);
        if (startOverFlowTime == slotInMinutes) {
            startOverFlowTime = 0;
            currentSlot = slotsTillEvent;
        } else {
            currentSlot = slotsTillEvent + 1;
        }

        int durationInSlots = 0;
        int endOverFlowTime = 0;

        if (startOverFlowTime > 0) {
            durationInSlots = (durationInMinutes - startOverFlowTime) / slotInMinutes;
            endOverFlowTime = (durationInMinutes - startOverFlowTime) % slotInMinutes;

        } else {
            durationInSlots = durationInMinutes / slotInMinutes;
            endOverFlowTime = durationInMinutes % slotInMinutes;
        }

        // calculate slot overflow at start
        if (startOverFlowTime > 0 && currentSlot < cellHeights.length) {
            int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
            pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * startOverFlowTime);
        }

        // calculate length in full slots
        int lastFullSlot = currentSlot + durationInSlots;
        for (; currentSlot < lastFullSlot && currentSlot < cellHeights.length; currentSlot++) {
            pixelLength += cellHeights[currentSlot] + dateCellBorder;
        }

        // calculate overflow at end
        if (endOverFlowTime > 0 && currentSlot < cellHeights.length) {
            int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
            pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * endOverFlowTime);
        }

        // reduce possible underflow at end
        if (endOverFlowTime < 0) {
            int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
            pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * endOverFlowTime);
        }

        return pixelLength;
    }

    public int getPixelTopFor(int startFromMinutes) {
        int pixelsToTop = 0;
        int slotIndex = 0;

        int firstHourInMinutes = firstHour * 60;

        if (firstHourInMinutes > startFromMinutes) {
            startFromMinutes = 0;
        } else {
            startFromMinutes -= firstHourInMinutes;
        }

        // calculate full slots to event
        int slotsTillEvent = startFromMinutes / slotInMinutes;
        int overFlowTime = startFromMinutes % slotInMinutes;
        if (slotsTillEvent > 0) {
            for (slotIndex = 0; slotIndex < slotsTillEvent; slotIndex++) {
                pixelsToTop += cellHeights[slotIndex] + dateCellBorder;
            }
        }

        // calculate lengths less than one slot
        if (overFlowTime > 0) {
            int lastSlotHeight = cellHeights[slotIndex] + dateCellBorder;
            pixelsToTop += ((double) lastSlotHeight / (double) slotInMinutes) * overFlowTime;
        }

        return pixelsToTop;
    }

    public void eventMoved(DayEvent dayEvent) {
        Style s = dayEvent.getElement().getStyle();
        int left = Integer.parseInt(s.getLeft().substring(0, s.getLeft().length() - 2));
        DateCell previousParent = (DateCell) dayEvent.getParent();
        DateCell newParent = (DateCell) content.getWidget((left / getDateCellWidth()) + 1);
        CalendarEvent se = dayEvent.getCalendarEvent();
        previousParent.removeEvent(dayEvent);
        newParent.addEvent(dayEvent);
        if (!previousParent.equals(newParent)) {
            previousParent.recalculateEventWidths();
        }
        newParent.recalculateEventWidths();
        if (calendar.getEventMovedListener() != null) {
            calendar.getEventMovedListener().eventMoved(se);
        }
    }

    public void setToday(Date todayDate, Date todayTimestamp) {
        int count = content.getWidgetCount();
        if (count > 1) {
            for (int i = 1; i < count; i++) {
                DateCell dc = (DateCell) content.getWidget(i);
                if (dc.getDate().getTime() == todayDate.getTime()) {
                    if (isVerticalScrollable()) {
                        dc.setToday(todayTimestamp, -1);
                    } else {
                        dc.setToday(todayTimestamp, getOffsetWidth());
                    }
                }
                dateCellOfToday = dc;
            }
        }
    }

    public DateCell getDateCellOfToday() {
        return dateCellOfToday;
    }

    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }

    public boolean isDisabled() {
        return disabled;
    }

    public Timebar getTimeBar() {
        return timebar;
    }

    public void setDateColor(Date when, Date to, String styleName) {
        int dateCount = content.getWidgetCount();
        for (int i = 1; i < dateCount; i++) {
            DateCell dc = (DateCell) content.getWidget(i);
            Date dcDate = dc.getDate();
            int comp = dcDate.compareTo(when);
            int comp2 = dcDate.compareTo(to);
            if (comp >= 0 && comp2 <= 0) {
                dc.setDateColor(styleName);
            }
        }
    }

    /**
     * @param calendar
     *            the calendar to set
     */
    public void setCalendar(VCalendar calendar) {
        this.calendar = calendar;
    }

    /**
     * @return the calendar
     */
    public VCalendar getCalendar() {
        return calendar;
    }

    /**
     * Get width of the single date cell
     * 
     * @return Date cell width
     */
    public int getDateCellWidth() {
        int count = content.getWidgetCount() - 1;
        int cellWidth = -1;
        if (count <= 0) {
            return cellWidth;
        }

        if (width == -1) {
            Widget firstWidget = content.getWidget(1);
            cellWidth = firstWidget.getElement().getOffsetWidth();
        } else {
            cellWidth = getInternalWidth() / count;
        }
        return cellWidth;
    }

    /**
     * @return the number of day cells in this week
     */
    public int getDateCellCount() {
        return content.getWidgetCount() - 1;
    }

    public void setFirstHour(int firstHour) {
        this.firstHour = firstHour;
        timebar.setFirstHour(firstHour);
    }

    public void setLastHour(int lastHour) {
        this.lastHour = lastHour;
        timebar.setLastHour(lastHour);
    }

    public int getFirstHour() {
        return firstHour;
    }

    public int getLastHour() {
        return lastHour;
    }

    public static class Timebar extends HTML {

        private static final int[] timesFor12h = { 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

        private int height;

        private final int verticalPadding = 7; // FIXME measure this from DOM

        private int[] slotCellHeights;

        private int firstHour;

        private int lastHour;

        public Timebar(boolean format24h) {
            createTimeBar(format24h);
        }

        public void setLastHour(int lastHour) {
            this.lastHour = lastHour;
        }

        public void setFirstHour(int firstHour) {
            this.firstHour = firstHour;

        }

        public void setCellHeights(int[] cellHeights) {
            slotCellHeights = cellHeights;
        }

        private void createTimeBar(boolean format24h) {
            setStylePrimaryName("v-calendar-times");

            // Fist "time" is empty
            Element e = DOM.createDiv();
            setStyleName(e, "v-calendar-time");
            e.setInnerText("");
            getElement().appendChild(e);

            DateTimeService dts = new DateTimeService();

            if (format24h) {
                for (int i = firstHour + 1; i <= lastHour; i++) {
                    e = DOM.createDiv();
                    setStyleName(e, "v-calendar-time");
                    String delimiter = dts.getClockDelimeter();
                    e.setInnerHTML("<span>" + i + "</span>" + delimiter + "00");
                    getElement().appendChild(e);
                }
            } else {
                // FIXME Use dts.getAmPmStrings(); and make sure that
                // DateTimeService has a some Locale set.
                String[] ampm = new String[] { "AM", "PM" };

                int amStop = (lastHour < 11) ? lastHour : 11;
                int pmStart = (firstHour > 11) ? firstHour % 11 : 0;

                if (firstHour < 12) {
                    for (int i = firstHour + 1; i <= amStop; i++) {
                        e = DOM.createDiv();
                        setStyleName(e, "v-calendar-time");
                        e.setInnerHTML("<span>" + timesFor12h[i] + "</span>" + " " + ampm[0]);
                        getElement().appendChild(e);
                    }
                }

                if (lastHour > 11) {
                    for (int i = pmStart; i < lastHour - 11; i++) {
                        e = DOM.createDiv();
                        setStyleName(e, "v-calendar-time");
                        e.setInnerHTML("<span>" + timesFor12h[i] + "</span>" + " " + ampm[1]);
                        getElement().appendChild(e);
                    }
                }
            }
        }

        public void updateTimeBar(boolean format24h) {
            clear();
            createTimeBar(format24h);
        }

        private void clear() {
            while (getElement().getChildCount() > 0) {
                getElement().removeChild(getElement().getChild(0));
            }
        }

        public void setHeightPX(int pixelHeight) {
            height = pixelHeight;

            if (pixelHeight > -1) {
                // as the negative margins on children pulls the whole element
                // upwards, we must compensate. otherwise the element would be
                // too short
                super.setHeight((height + verticalPadding) + "px");
                removeStyleDependentName("Vsized");
                updateChildHeights();

            } else {
                addStyleDependentName("Vsized");
                updateChildHeights();
            }
        }

        private void updateChildHeights() {
            int childCount = getElement().getChildCount();

            if (height != -1) {

                // 23 hours + first is empty
                // we try to adjust the height of time labels to the distributed
                // heights of the time slots
                int hoursPerDay = lastHour - firstHour + 1;

                int slotsPerHour = slotCellHeights.length / hoursPerDay;
                int[] cellHeights = new int[slotCellHeights.length / slotsPerHour];

                int slotHeightPosition = 0;
                for (int i = 0; i < cellHeights.length; i++) {
                    for (int j = slotHeightPosition; j < slotHeightPosition + slotsPerHour; j++) {
                        cellHeights[i] += slotCellHeights[j] + 1;
                        // 1px more for borders
                        // FIXME measure from DOM
                    }
                    slotHeightPosition += slotsPerHour;
                }

                for (int i = 0; i < childCount; i++) {
                    Element e = (Element) getElement().getChild(i);
                    e.getStyle().setHeight(cellHeights[i], Unit.PX);
                }

            } else {
                for (int i = 0; i < childCount; i++) {
                    Element e = (Element) getElement().getChild(i);
                    e.getStyle().setProperty("height", "");
                }
            }
        }
    }

    public static class DateCell extends FocusableComplexPanel
            implements MouseDownHandler, MouseMoveHandler, MouseUpHandler, KeyDownHandler, ContextMenuHandler {
        private static final String DRAGEMPHASISSTYLE = " dragemphasis";
        private Date date;
        private int width;
        private int eventRangeStart = -1;
        private int eventRangeStop = -1;
        private final WeekGrid weekgrid;
        private boolean disabled = false;
        private int height;
        private final Element[] slotElements;
        private final List<DateCellSlot> slots = new ArrayList<WeekGrid.DateCell.DateCellSlot>();
        private int[] slotElementHeights;
        private int startingSlotHeight;
        private Date today;
        private Element todaybar;
        private final List<HandlerRegistration> handlers;
        private final int numberOfSlots;
        private final int firstHour;
        private final int lastHour;

        public class DateCellSlot extends Widget {

            private final DateCell cell;

            private final Date from;

            private final Date to;

            public DateCellSlot(DateCell cell, Date from, Date to) {
                setElement(DOM.createDiv());
                getElement().setInnerHTML("&nbsp;");
                this.cell = cell;
                this.from = from;
                this.to = to;
            }

            public Date getFrom() {
                return from;
            }

            public Date getTo() {
                return to;
            }

            public DateCell getParentCell() {
                return cell;
            }
        }

        public DateCell(WeekGrid parent, Date date) {
            weekgrid = parent;
            Element mainElement = DOM.createDiv();
            setElement(mainElement);
            makeFocusable();
            setDate(date);

            addStyleName("v-calendar-day-times");

            handlers = new LinkedList<HandlerRegistration>();

            // 2 slots / hour
            firstHour = weekgrid.getFirstHour();
            lastHour = weekgrid.getLastHour();
            numberOfSlots = (lastHour - firstHour + 1) * 2;
            long slotTime = Math.round(((lastHour - firstHour + 1) * 3600000.0) / numberOfSlots);

            slotElements = new Element[numberOfSlots];
            slotElementHeights = new int[numberOfSlots];

            slots.clear();
            long start = getDate().getTime() + firstHour * 3600000;
            long end = start + slotTime;
            for (int i = 0; i < numberOfSlots; i++) {
                DateCellSlot slot = new DateCellSlot(DateCell.this, new Date(start), new Date(end));
                if (i % 2 == 0) {
                    slot.setStyleName("v-slot-even");
                } else {
                    slot.setStyleName("v-slot");
                }
                Event.sinkEvents(slot.getElement(), Event.MOUSEEVENTS);
                mainElement.appendChild(slot.getElement());
                slotElements[i] = slot.getElement();
                slots.add(slot);
                start = end;
                end = start + slotTime;
            }

            // Sink events for tooltip handling
            Event.sinkEvents(mainElement, Event.MOUSEEVENTS);
        }

        public int getFirstHour() {
            return firstHour;
        }

        public int getLastHour() {
            return lastHour;
        }

        @Override
        protected void onAttach() {
            super.onAttach();

            handlers.add(addHandler(this, MouseDownEvent.getType()));
            handlers.add(addHandler(this, MouseUpEvent.getType()));
            handlers.add(addHandler(this, MouseMoveEvent.getType()));
            handlers.add(addDomHandler(this, ContextMenuEvent.getType()));
            handlers.add(addKeyDownHandler(this));
        }

        @Override
        protected void onDetach() {
            for (HandlerRegistration handler : handlers) {
                handler.removeHandler();
            }
            handlers.clear();

            super.onDetach();
        }

        public int getSlotIndex(Element slotElement) {
            for (int i = 0; i < slotElements.length; i++) {
                if (slotElement == slotElements[i]) {
                    return i;
                }
            }

            throw new IllegalArgumentException("Element not found in this DateCell");
        }

        public DateCellSlot getSlot(int index) {
            return slots.get(index);
        }

        public int getNumberOfSlots() {
            return numberOfSlots;
        }

        public void setTimeBarWidth(int timebarWidth) {
            todaybar.getStyle().setWidth(timebarWidth, Unit.PX);
        }

        /**
         * @param isHorizontalSized
         *            if true, this DateCell is sized with CSS and not via
         *            {@link #setWidthPX(int)}
         */
        public void setHorizontalSized(boolean isHorizontalSized) {
            if (isHorizontalSized) {
                addStyleDependentName("Hsized");

                width = getOffsetWidth() - Util.measureHorizontalBorder(getElement());
                recalculateEventWidths();
            } else {
                removeStyleDependentName("Hsized");
            }
        }

        /**
         * @param isVerticalSized
         *            if true, this DateCell is sized with CSS and not via
         *            {@link #setHeightPX(int)}
         */
        public void setVerticalSized(boolean isVerticalSized) {
            if (isVerticalSized) {
                addStyleDependentName("Vsized");

                // recalc heights&size for events. all other height sizes come
                // from css
                startingSlotHeight = slotElements[0].getOffsetHeight();
                recalculateEventPositions();

                if (isToday()) {
                    recalculateTimeBarPosition();
                }

            } else {
                removeStyleDependentName("Vsized");
            }
        }

        public void setDate(Date date) {
            this.date = date;
        }

        public void setWidthPX(int cellWidth) {
            width = cellWidth;
            setWidth(cellWidth + "px");
            recalculateEventWidths();
        }

        public void setHeightPX(int height, int[] cellHeights) {
            this.height = height;
            slotElementHeights = cellHeights;
            setHeight(height + "px");
            recalculateCellHeights();
            recalculateEventPositions();
            if (today != null) {
                recalculateTimeBarPosition();
            }
        }

        // date methods are not deprecated in GWT
        @SuppressWarnings("deprecation")
        private void recalculateTimeBarPosition() {
            int h = today.getHours();
            int m = today.getMinutes();
            if (h >= firstHour && h <= lastHour) {
                int pixelTop = weekgrid.getPixelTopFor(m + 60 * h);
                todaybar.getStyle().clearDisplay();
                todaybar.getStyle().setTop(pixelTop, Unit.PX);
            } else {
                todaybar.getStyle().setDisplay(Display.NONE);
            }
        }

        private void recalculateEventPositions() {
            for (int i = 0; i < getWidgetCount(); i++) {
                DayEvent dayEvent = (DayEvent) getWidget(i);
                updatePositionFor(dayEvent, getDate(), dayEvent.getCalendarEvent());
            }
        }

        public void recalculateEventWidths() {
            List<Group> groups = new ArrayList<Group>();

            int count = getWidgetCount();

            List<Integer> handled = new ArrayList<Integer>();

            // Iterate through all events and group them. Events that overlaps
            // with each other, are added to the same group.
            for (int i = 0; i < count; i++) {
                if (handled.contains(i)) {
                    continue;
                }

                Group curGroup = getOverlappingEvents(i);
                handled.addAll(curGroup.getItems());

                int top = curGroup.top;
                int bottom = curGroup.bottom;

                boolean newGroup = true;
                // No need to check other groups, if size equals the count
                if (curGroup.getItems().size() != count) {
                    // Check other groups. When the whole group overlaps with
                    // other group, the group is merged to the other.
                    for (Group g : groups) {
                        int nextTop = g.top;
                        int nextBottom = g.bottom;

                        if (doOverlap(top, bottom, nextTop, nextBottom)) {
                            newGroup = false;
                            updateGroup(g, curGroup, top, bottom);
                        }
                    }
                } else {
                    if (newGroup) {
                        groups.add(curGroup);
                    }
                    break;
                }

                if (newGroup) {
                    groups.add(curGroup);
                }
            }

            drawDayEvents(groups);
        }

        private void recalculateCellHeights() {
            startingSlotHeight = height / numberOfSlots;

            boolean isIE6 = BrowserInfo.get().isIE6();

            for (int i = 0; i < slotElements.length; i++) {
                slotElements[i].getStyle().setHeight(slotElementHeights[i], Unit.PX);

                if (isIE6) {
                    slotElements[i].getStyle().setProperty("lineHeight", slotElementHeights[i] + "px");
                }
            }
        }

        public int getSlotHeight() {
            return startingSlotHeight;
        }

        public int getSlotBorder() {
            return Util.measureVerticalBorder((com.google.gwt.user.client.Element) slotElements[0]);
        }

        private void drawDayEvents(List<Group> groups) {
            for (Group g : groups) {
                int col = 0;
                int colCount = 0;
                List<Integer> order = new ArrayList<Integer>();
                Map<Integer, Integer> columns = new HashMap<Integer, Integer>();
                for (Integer eventIndex : g.getItems()) {
                    DayEvent d = (DayEvent) getWidget(eventIndex);
                    d.setMoveWidth(width);

                    int freeSpaceCol = findFreeColumnSpaceOnLeft(d.getTop(), d.getTop() + d.getOffsetHeight() - 1,
                            order, columns);
                    if (freeSpaceCol >= 0) {
                        col = freeSpaceCol;
                        columns.put(eventIndex, col);
                        int newOrderindex = 0;
                        for (Integer i : order) {
                            if (columns.get(i) >= col) {
                                newOrderindex = order.indexOf(i);
                                break;
                            }
                        }
                        order.add(newOrderindex, eventIndex);
                    } else {
                        // New column
                        col = colCount++;
                        columns.put(eventIndex, col);
                        order.add(eventIndex);
                    }
                }

                // Update widths and left position
                int eventWidth = (width / colCount);
                for (Integer index : g.getItems()) {
                    DayEvent d = (DayEvent) getWidget(index);
                    d.getElement().getStyle().setMarginLeft((eventWidth * columns.get(index)), Unit.PX);
                    d.setWidth(eventWidth + "px");
                    d.setSlotHeightInPX(getSlotHeight());
                }
            }
        }

        private int findFreeColumnSpaceOnLeft(int top, int bottom, List<Integer> order,
                Map<Integer, Integer> columns) {
            int freeSpot = -1;
            int skipIndex = -1;
            for (Integer eventIndex : order) {
                int col = columns.get(eventIndex);
                if (col == skipIndex) {
                    continue;
                }

                if (freeSpot != -1 && freeSpot != col) {
                    // Free spot found
                    return freeSpot;
                }

                DayEvent d = (DayEvent) getWidget(eventIndex);

                if (doOverlap(top, bottom, d.getTop(), d.getTop() + d.getOffsetHeight() - 1)) {
                    skipIndex = col;
                    freeSpot = -1;
                } else {
                    freeSpot = col;
                }
            }

            return freeSpot;
        }

        private boolean doOverlap(int top, int bottom, int nextTop, int nextBottom) {
            boolean isInsideFromBottomSide = top >= nextTop && top <= nextBottom;
            boolean isFullyInside = top >= nextTop && bottom <= nextBottom;
            boolean isInsideFromTopSide = bottom <= nextBottom && bottom >= nextTop;
            boolean isFullyOverlapping = top <= nextTop && bottom >= nextBottom;
            return isInsideFromBottomSide || isFullyInside || isInsideFromTopSide || isFullyOverlapping;
        }

        /* Update top and bottom values. Add new index to the group. */
        private void updateGroup(Group targetGroup, Group byGroup, int top, int bottom) {
            if (top < targetGroup.top) {
                targetGroup.top = top;
            }
            if (bottom > targetGroup.bottom) {
                targetGroup.bottom = bottom;
            }

            for (Integer index : byGroup.getItems()) {
                if (!targetGroup.getItems().contains(index)) {
                    targetGroup.add(index);
                }
            }
        }

        /**
         * Returns all overlapping DayEvent indexes in the Group. Including the
         * target.
         * 
         * @param targetIndex
         *            Index of DayEvent in the current DateCell widget.
         * @return Group that contains all Overlapping DayEvent indexes
         */
        public Group getOverlappingEvents(int targetIndex) {
            Group g = new Group(targetIndex);

            int count = getWidgetCount();
            DayEvent target = (DayEvent) getWidget(targetIndex);
            int top = target.getTop();
            int bottom = top + target.getOffsetHeight() - 1;

            for (int i = 0; i < count; i++) {
                if (targetIndex == i) {
                    continue;
                }

                DayEvent d = (DayEvent) getWidget(i);
                int nextTop = d.getTop();
                int nextBottom = nextTop + d.getOffsetHeight() - 1;
                if (doOverlap(top, bottom, nextTop, nextBottom)) {
                    g.add(i);

                    // Update top & bottom values to the greatest
                    if (nextTop < top) {
                        top = nextTop;
                    }
                    if (nextBottom > bottom) {
                        bottom = nextBottom;
                    }
                }
            }

            g.top = top;
            g.bottom = bottom;
            return g;
        }

        public Date getDate() {
            return date;
        }

        public void addEvent(Date targetDay, CalendarEvent calendarEvent) {
            Element main = getElement();
            DayEvent dayEvent = new DayEvent(weekgrid, calendarEvent);
            dayEvent.setSlotHeightInPX(getSlotHeight());
            dayEvent.setDisabled(isDisabled());

            if (startingSlotHeight > 0) {
                updatePositionFor(dayEvent, targetDay, calendarEvent);
            }

            add(dayEvent, (com.google.gwt.user.client.Element) main);
        }

        // date methods are not deprecated in GWT
        @SuppressWarnings("deprecation")
        private void updatePositionFor(DayEvent dayEvent, Date targetDay, CalendarEvent calendarEvent) {
            if (canDisplay(calendarEvent)) {

                dayEvent.getElement().getStyle().clearDisplay();

                Date fromDt = calendarEvent.getStartTime();
                int h = fromDt.getHours();
                int m = fromDt.getMinutes();
                long range = calendarEvent.getRangeInMinutesForDay(targetDay);

                boolean onDifferentDays = calendarEvent.isTimeOnDifferentDays();
                if (onDifferentDays) {
                    if (calendarEvent.getStart().compareTo(targetDay) != 0) {
                        // Current day slot is for the end date. Lets fix also
                        // the
                        // start & end times.
                        h = 0;
                        m = 0;
                    }
                }

                int startFromMinutes = (h * 60) + m;
                dayEvent.updatePosition(startFromMinutes, range);

            } else {
                dayEvent.getElement().getStyle().setDisplay(Display.NONE);
            }
        }

        public void addEvent(DayEvent dayEvent) {
            Element main = getElement();
            int index = 0;
            List<CalendarEvent> events = new ArrayList<CalendarEvent>();

            // events are the only widgets in this panel
            // slots are just elements
            for (; index < getWidgetCount(); index++) {
                DayEvent dc = (DayEvent) getWidget(index);
                dc.setDisabled(isDisabled());
                events.add(dc.getCalendarEvent());
            }
            events.add(dayEvent.getCalendarEvent());

            index = 0;
            for (CalendarEvent e : weekgrid.getCalendar().sortEventsByDuration(events)) {
                if (e.equals(dayEvent.getCalendarEvent())) {
                    break;
                }
                index++;
            }
            this.insert(dayEvent, (com.google.gwt.user.client.Element) main, index, true);
        }

        public void removeEvent(DayEvent dayEvent) {
            remove(dayEvent);
        }

        /**
         * 
         * @param event
         * @return
         */
        // Date methods not deprecated in GWT
        @SuppressWarnings("deprecation")
        private boolean canDisplay(CalendarEvent event) {
            Date eventStart = event.getStartTime();
            Date eventEnd = event.getEndTime();

            int eventStartHours = eventStart.getHours();
            int eventEndHours = eventEnd.getHours();

            return (eventStartHours <= lastHour) && (eventEndHours >= firstHour);
        }

        public void onKeyDown(KeyDownEvent event) {
            int keycode = event.getNativeEvent().getKeyCode();
            if (keycode == KeyCodes.KEY_ESCAPE && eventRangeStart > -1) {
                cancelRangeSelect();
            }
        }

        public void onMouseDown(MouseDownEvent event) {
            if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
                Element e = Element.as(event.getNativeEvent().getEventTarget());
                if (e.getClassName().contains("reserved") || isDisabled()
                        || !weekgrid.getParentCalendar().isRangeSelectAllowed()) {
                    eventRangeStart = -1;
                } else {
                    eventRangeStart = event.getY();
                    eventRangeStop = eventRangeStart;
                    Event.setCapture(getElement());
                    setFocus(true);
                }
            }
        }

        @SuppressWarnings("deprecation")
        public void onMouseUp(MouseUpEvent event) {
            if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
                return;
            }
            Event.releaseCapture(getElement());
            setFocus(false);
            int dragDistance = Math.abs(eventRangeStart - event.getY());
            if (dragDistance > 0 && eventRangeStart >= 0) {
                Element main = getElement();
                if (eventRangeStart > eventRangeStop) {
                    if (eventRangeStop <= -1) {
                        eventRangeStop = 0;
                    }
                    int temp = eventRangeStart;
                    eventRangeStart = eventRangeStop;
                    eventRangeStop = temp;
                }

                NodeList<Node> nodes = main.getChildNodes();

                int slotStart = -1;
                int slotEnd = -1;

                // iterate over all child nodes, until we find first the start,
                // and then the end
                for (int i = 0; i < nodes.getLength(); i++) {
                    Element element = (Element) nodes.getItem(i);
                    boolean isRangeElement = element.getClassName().contains("v-daterange");

                    if (isRangeElement && slotStart == -1) {
                        slotStart = i;
                        slotEnd = i; // to catch one-slot selections

                    } else if (isRangeElement) {
                        slotEnd = i;

                    } else if (slotStart != -1 && slotEnd != -1) {
                        break;
                    }
                }

                GWT.log("Slot start " + slotStart + " slot end " + slotEnd);

                clearSelectionRange();

                int startMinutes = firstHour * 60 + slotStart * 30;
                int endMinutes = (firstHour * 60) + (slotEnd + 1) * 30;
                Date currentDate = getDate();
                String yr = (currentDate.getYear() + 1900) + "-" + (currentDate.getMonth() + 1) + "-"
                        + currentDate.getDate();
                if (weekgrid.getCalendar().getRangeSelectListener() != null) {
                    weekgrid.getCalendar().getRangeSelectListener()
                            .rangeSelected(yr + ":" + startMinutes + ":" + endMinutes);
                }
                eventRangeStart = -1;
            } else {
                // Click event
                eventRangeStart = -1;
                cancelRangeSelect();

            }
        }

        public void onMouseMove(MouseMoveEvent event) {
            if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
                return;
            }

            if (eventRangeStart >= 0) {
                int newY = event.getY();
                int fromY = 0;
                int toY = 0;
                if (newY < eventRangeStart) {
                    fromY = newY;
                    toY = eventRangeStart;
                } else {
                    fromY = eventRangeStart;
                    toY = newY;
                }
                Element main = getElement();
                eventRangeStop = newY;
                NodeList<Node> nodes = main.getChildNodes();
                for (int i = 0; i < nodes.getLength(); i++) {
                    Element c = (Element) nodes.getItem(i);

                    if (todaybar != c) {

                        int elemStart = c.getOffsetTop();
                        int elemStop = elemStart + getSlotHeight();
                        if (elemStart >= fromY && elemStart <= toY) {
                            c.addClassName("v-daterange");
                        } else if (elemStop >= fromY && elemStop <= toY) {
                            c.addClassName("v-daterange");
                        } else if (elemStop >= fromY && elemStart <= toY) {
                            c.addClassName("v-daterange");
                        } else {
                            c.removeClassName("v-daterange");
                        }
                    }
                }
            }

            event.preventDefault();
        }

        public void cancelRangeSelect() {
            Event.releaseCapture(getElement());
            setFocus(false);

            clearSelectionRange();
        }

        private void clearSelectionRange() {
            if (eventRangeStart > -1) {
                // clear all "selected" class names
                Element main = getElement();
                NodeList<Node> nodes = main.getChildNodes();

                for (int i = 0; i <= 47; i++) {
                    Element c = (Element) nodes.getItem(i);
                    if (c == null) {
                        continue;
                    }
                    c.removeClassName("v-daterange");
                }

                eventRangeStart = -1;
            }
        }

        public void setToday(Date today, int width) {
            this.today = today;
            addStyleDependentName("today");
            Element lastChild = (Element) getElement().getLastChild();
            if (lastChild.getClassName().equals("v-calendar-current-time")) {
                todaybar = lastChild;
            } else {
                todaybar = DOM.createDiv();
                todaybar.setClassName("v-calendar-current-time");
                getElement().appendChild(todaybar);
            }

            if (width != -1) {
                todaybar.getStyle().setWidth(width, Unit.PX);
            }

            // position is calculated later, when we know the cell heights
        }

        public Element getTodaybarElement() {
            return todaybar;
        }

        public void setDisabled(boolean disabled) {
            this.disabled = disabled;
        }

        public boolean isDisabled() {
            return disabled;
        }

        public void setDateColor(String styleName) {
            this.setStyleName("v-calendar-datecell " + styleName);
        }

        public boolean isToday() {
            return today != null;
        }

        public void addEmphasisStyle(com.google.gwt.user.client.Element elementOver) {
            String originalStylename = getStyleName(elementOver);
            setStyleName(elementOver, originalStylename + DRAGEMPHASISSTYLE);
        }

        public void removeEmphasisStyle(com.google.gwt.user.client.Element elementOver) {
            String originalStylename = getStyleName(elementOver);
            setStyleName(elementOver,
                    originalStylename.substring(0, originalStylename.length() - DRAGEMPHASISSTYLE.length()));
        }

        private static class Group {
            public int top;
            public int bottom;
            private final List<Integer> items;

            public Group(Integer index) {
                items = new ArrayList<Integer>();
                items.add(index);
            }

            public List<Integer> getItems() {
                return items;
            }

            public void add(Integer index) {
                items.add(index);
            }
        }

        public class DayEvent extends FocusableHTML
                implements MouseDownHandler, MouseUpHandler, MouseMoveHandler, KeyDownHandler, ContextMenuHandler {

            private Element caption = null;
            private final Element eventContent;
            private CalendarEvent calendarEvent = null;
            private HandlerRegistration moveRegistration;
            private int startY = -1;
            private int startX = -1;
            private String moveWidth;
            public static final int halfHourInMilliSeconds = 1800 * 1000;
            private Date startDatetimeFrom;
            private Date startDatetimeTo;
            private boolean mouseMoveStarted;
            private int top;
            private int startYrelative;
            private int startXrelative;
            private boolean disabled;
            private final WeekGrid weekGrid;
            private com.google.gwt.user.client.Element topResizeBar;
            private com.google.gwt.user.client.Element bottomResizeBar;
            private Element clickTarget;
            private final Integer eventIndex;
            private int slotHeight;
            private final List<HandlerRegistration> handlers;
            private boolean mouseMoveCanceled;

            public DayEvent(WeekGrid parent, CalendarEvent event) {
                super();

                handlers = new LinkedList<HandlerRegistration>();

                setStylePrimaryName("v-calendar-event");
                setCalendarEvent(event);

                weekGrid = parent;

                Style s = getElement().getStyle();
                if (event.getStyleName().length() > 0) {
                    addStyleDependentName(event.getStyleName());
                }
                s.setPosition(Position.ABSOLUTE);

                caption = DOM.createDiv();
                caption.addClassName("v-calendar-event-caption");
                getElement().appendChild(caption);

                eventContent = DOM.createDiv();
                eventContent.addClassName("v-calendar-event-content");
                getElement().appendChild(eventContent);

                VCalendar calendar = weekGrid.getCalendar();
                if (weekGrid.getCalendar().isEventResizeAllowed()) {
                    topResizeBar = DOM.createDiv();
                    bottomResizeBar = DOM.createDiv();

                    topResizeBar.addClassName("v-calendar-event-resizetop");
                    bottomResizeBar.addClassName("v-calendar-event-resizebottom");

                    getElement().appendChild(topResizeBar);
                    getElement().appendChild(bottomResizeBar);
                }

                sinkEvents(VTooltip.TOOLTIP_EVENTS);
                eventIndex = event.getIndex();
            }

            @Override
            protected void onAttach() {
                super.onAttach();
                handlers.add(addMouseDownHandler(this));
                handlers.add(addMouseUpHandler(this));
                handlers.add(addKeyDownHandler(this));
                handlers.add(addDomHandler(this, ContextMenuEvent.getType()));
            }

            @Override
            protected void onDetach() {
                for (HandlerRegistration handler : handlers) {
                    handler.removeHandler();
                }
                handlers.clear();
                super.onDetach();
            }

            public void setSlotHeightInPX(int slotHeight) {
                this.slotHeight = slotHeight;
            }

            @Override
            public void onBrowserEvent(Event event) {
                super.onBrowserEvent(event);
                VCalendar calendar = weekGrid.getCalendar();
                calendar.handleTooltipEvent(event, eventIndex);
            }

            public void updatePosition(long startFromMinutes, long durationInMinutes) {
                if (startFromMinutes < 0) {
                    startFromMinutes = 0;
                }
                top = weekGrid.getPixelTopFor((int) startFromMinutes);

                getElement().getStyle().setTop(top, Unit.PX);
                if (durationInMinutes > 0) {
                    int heightMinutes = weekGrid.getPixelLengthFor((int) startFromMinutes, (int) durationInMinutes);
                    setHeight(heightMinutes);
                } else {
                    setHeight(-1);
                }

                boolean multiRowCaption = (durationInMinutes > 30);
                updateCaptions(multiRowCaption);
            }

            public int getTop() {
                return top;
            }

            public void setMoveWidth(int width) {
                moveWidth = width + "px";
            }

            public void setHeight(int h) {
                if (h == -1) {
                    getElement().getStyle().setProperty("height", "");
                    eventContent.getStyle().setProperty("height", "");
                } else {
                    getElement().getStyle().setHeight(h, Unit.PX);
                    // FIXME measure the border height (2px) from the DOM
                    eventContent.getStyle().setHeight(h - 2, Unit.PX);
                }
            }

            /**
             * @param bigMode
             *            If false, event is so small that caption must be in
             *            time-row
             */
            private void updateCaptions(boolean bigMode) {
                String separator = bigMode ? "<br />" : ": ";
                caption.setInnerHTML("<span>" + calendarEvent.getTimeAsText() + "</span>" + separator
                        + Util.escapeHTML(calendarEvent.getCaption()));
                eventContent.setInnerHTML("");
            }

            public void onKeyDown(KeyDownEvent event) {
                int keycode = event.getNativeEvent().getKeyCode();
                if (keycode == KeyCodes.KEY_ESCAPE && mouseMoveStarted) {
                    cancelMouseMove();
                }
            }

            public void onMouseDown(MouseDownEvent event) {
                if (isDisabled() || event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
                    return;
                }

                clickTarget = Element.as(event.getNativeEvent().getEventTarget());
                mouseMoveCanceled = false;

                if (weekGrid.getCalendar().isEventMoveAllowed() || clickTargetsResize()) {
                    moveRegistration = addMouseMoveHandler(this);
                    setFocus(true);
                    startX = event.getClientX();
                    startY = event.getClientY();
                    try {
                        startYrelative = (int) ((double) event.getRelativeY(caption) % slotHeight);
                        startXrelative = (event.getRelativeX(weekGrid.getElement())
                                - weekGrid.timebar.getOffsetWidth()) % getDateCellWidth();
                    } catch (Exception e) {
                        GWT.log("Exception calculating relative start position", e);
                    }
                    mouseMoveStarted = false;
                    Style s = getElement().getStyle();
                    s.setZIndex(1000);
                    startDatetimeFrom = (Date) calendarEvent.getStartTime().clone();
                    startDatetimeTo = (Date) calendarEvent.getEndTime().clone();
                    Event.setCapture(getElement());
                }

                // make sure the right cursor is always displayed
                if (clickTargetsResize()) {
                    addGlobalResizeStyle();
                }

                /*
                 * We need to stop the event propagation or else the WeekGrid
                 * range select will kick in
                 */
                event.stopPropagation();
                event.preventDefault();
            }

            public void onMouseUp(MouseUpEvent event) {
                if (mouseMoveCanceled) {
                    return;
                }

                Event.releaseCapture(getElement());
                setFocus(false);
                if (moveRegistration != null) {
                    moveRegistration.removeHandler();
                    moveRegistration = null;
                }
                int endX = event.getClientX();
                int endY = event.getClientY();
                int xDiff = startX - endX;
                int yDiff = startY - endY;
                startX = -1;
                startY = -1;
                mouseMoveStarted = false;
                Style s = getElement().getStyle();
                s.setZIndex(1);
                if (!clickTargetsResize()) {
                    // check if mouse has moved over threshold of 3 pixels
                    boolean mouseMoved = (xDiff < -3 || xDiff > 3 || yDiff < -3 || yDiff > 3);

                    if (!weekGrid.getCalendar().isDisabledOrReadOnly() && mouseMoved) {
                        // Event Move:
                        // - calendar must be enabled
                        // - calendar must not be in read-only mode
                        weekGrid.eventMoved(this);
                    } else if (!weekGrid.getCalendar().isDisabled()) {
                        // Event Click:
                        // - calendar must be enabled (read-only is allowed)
                        EventTarget et = event.getNativeEvent().getEventTarget();
                        Element e = Element.as(et);
                        if (e == caption || e == eventContent || e.getParentElement() == caption) {
                            if (weekGrid.getCalendar().getEventClickListener() != null) {
                                weekGrid.getCalendar().getEventClickListener().eventClick(calendarEvent);
                            }
                        }
                    }

                } else { // click targeted resize bar
                    removeGlobalResizeStyle();
                    if (weekGrid.getCalendar().getEventResizeListener() != null) {
                        weekGrid.getCalendar().getEventResizeListener().eventResized(calendarEvent);
                    }
                }
            }

            @SuppressWarnings("deprecation")
            public void onMouseMove(MouseMoveEvent event) {
                if (startY < 0 && startX < 0) {
                    return;
                }
                if (isDisabled()) {
                    Event.releaseCapture(getElement());
                    mouseMoveStarted = false;
                    startY = -1;
                    startX = -1;
                    removeGlobalResizeStyle();
                    return;
                }
                int currentY = event.getClientY();
                int currentX = event.getClientX();
                int moveY = (currentY - startY);
                int moveX = (currentX - startX);
                if ((moveY < 5 && moveY > -6) && (moveX < 5 && moveX > -6)) {
                    return;
                }
                if (!mouseMoveStarted) {
                    setWidth(moveWidth);
                    getElement().getStyle().setMarginLeft(0, Unit.PX);
                    mouseMoveStarted = true;
                }

                HorizontalPanel parent = (HorizontalPanel) getParent().getParent();
                int relativeX = event.getRelativeX(parent.getElement()) - weekGrid.timebar.getOffsetWidth();
                int halfHourDiff = 0;
                if (moveY > 0) {
                    halfHourDiff = (startYrelative + moveY) / slotHeight;
                } else {
                    halfHourDiff = (moveY - startYrelative) / slotHeight;
                }

                int dateCellWidth = getDateCellWidth();
                long dayDiff = 0;
                if (moveX >= 0) {
                    dayDiff = (startXrelative + moveX) / dateCellWidth;
                } else {
                    dayDiff = (moveX - (dateCellWidth - startXrelative)) / dateCellWidth;
                }

                int dayOffset = relativeX / dateCellWidth;

                // sanity check for right side overflow
                int dateCellCount = weekGrid.getDateCellCount();
                if (dayOffset >= dateCellCount) {
                    dayOffset--;
                    dayDiff--;
                }

                int dayOffsetPx = calculateDateCellOffsetPx(dayOffset) + weekGrid.timebar.getOffsetWidth();

                GWT.log("DateCellWidth: " + dateCellWidth + " dayDiff: " + dayDiff + " dayOffset: " + dayOffset
                        + " dayOffsetPx: " + dayOffsetPx + " startXrelative: " + startXrelative + " moveX: "
                        + moveX);

                if (relativeX < 0 || relativeX >= getDatesWidth()) {
                    return;
                }

                Style s = getElement().getStyle();

                Date from = calendarEvent.getStartTime();
                Date to = calendarEvent.getEndTime();
                long duration = to.getTime() - from.getTime();

                if (!clickTargetsResize() && weekGrid.getCalendar().isEventMoveAllowed()) {
                    long daysMs = dayDiff * VCalendar.DAYINMILLIS;
                    from.setTime(startDatetimeFrom.getTime() + daysMs);
                    from.setTime(from.getTime() + ((long) halfHourInMilliSeconds * halfHourDiff));
                    to.setTime((from.getTime() + duration));

                    calendarEvent.setStartTime(from);
                    calendarEvent.setEndTime(to);
                    calendarEvent.setStart(new Date(from.getTime()));
                    calendarEvent.setEnd(new Date(to.getTime()));

                    // Set new position for the event
                    long startFromMinutes = (from.getHours() * 60) + from.getMinutes();
                    long range = calendarEvent.getRangeInMinutes();
                    startFromMinutes = calculateStartFromMinute(startFromMinutes, from, to, dayOffsetPx);
                    if (startFromMinutes < 0) {
                        range += startFromMinutes;
                    }
                    updatePosition(startFromMinutes, range);

                    s.setLeft(dayOffsetPx, Unit.PX);

                    if (weekGrid.getDateCellWidths() != null) {
                        s.setWidth(weekGrid.getDateCellWidths()[dayOffset], Unit.PX);
                    } else {
                        setWidth(moveWidth);
                    }

                } else if (clickTarget == topResizeBar) {
                    long oldStartTime = startDatetimeFrom.getTime();
                    long newStartTime = oldStartTime + ((long) halfHourInMilliSeconds * halfHourDiff);

                    if (!isTimeRangeTooSmall(newStartTime, startDatetimeTo.getTime())) {
                        newStartTime = startDatetimeTo.getTime() - getMinTimeRange();
                    }

                    from.setTime(newStartTime);

                    calendarEvent.setStartTime(from);
                    calendarEvent.setStart(new Date(from.getTime()));

                    // Set new position for the event
                    long startFromMinutes = (from.getHours() * 60) + from.getMinutes();
                    long range = calendarEvent.getRangeInMinutes();

                    updatePosition(startFromMinutes, range);

                } else if (clickTarget == bottomResizeBar) {
                    long oldEndTime = startDatetimeTo.getTime();
                    long newEndTime = oldEndTime + ((long) halfHourInMilliSeconds * halfHourDiff);

                    if (!isTimeRangeTooSmall(startDatetimeFrom.getTime(), newEndTime)) {
                        newEndTime = startDatetimeFrom.getTime() + getMinTimeRange();
                    }

                    to.setTime(newEndTime);

                    calendarEvent.setEndTime(to);
                    calendarEvent.setEnd(new Date(to.getTime()));

                    // Set new position for the event
                    long startFromMinutes = (startDatetimeFrom.getHours() * 60) + startDatetimeFrom.getMinutes();
                    long range = calendarEvent.getRangeInMinutes();
                    startFromMinutes = calculateStartFromMinute(startFromMinutes, from, to, dayOffsetPx);
                    if (startFromMinutes < 0) {
                        range += startFromMinutes;
                    }
                    updatePosition(startFromMinutes, range);
                }
            }

            private void cancelMouseMove() {
                mouseMoveCanceled = true;

                // reset and remove everything related to the event handling
                Event.releaseCapture(getElement());
                setFocus(false);

                if (moveRegistration != null) {
                    moveRegistration.removeHandler();
                    moveRegistration = null;
                }

                mouseMoveStarted = false;
                removeGlobalResizeStyle();

                Style s = getElement().getStyle();
                s.setZIndex(1);

                // reset the position of the event
                int dateCellWidth = getDateCellWidth();
                int dayOffset = startXrelative / dateCellWidth;
                s.clearLeft();

                calendarEvent.setStartTime(startDatetimeFrom);
                calendarEvent.setEndTime(startDatetimeTo);

                long startFromMinutes = (startDatetimeFrom.getHours() * 60) + startDatetimeFrom.getMinutes();
                long range = calendarEvent.getRangeInMinutes();

                startFromMinutes = calculateStartFromMinute(startFromMinutes, startDatetimeFrom, startDatetimeTo,
                        dayOffset);
                if (startFromMinutes < 0) {
                    range += startFromMinutes;
                }

                updatePosition(startFromMinutes, range);

                startY = -1;
                startX = -1;

                // to reset the event width
                ((DateCell) getParent()).recalculateEventWidths();
            }

            // date methods are not deprecated in GWT
            @SuppressWarnings("deprecation")
            private long calculateStartFromMinute(long startFromMinutes, Date from, Date to, int dayOffset) {
                boolean eventStartAtDifferentDay = from.getDate() != to.getDate();
                if (eventStartAtDifferentDay) {
                    long minutesOnPrevDay = (getTargetDateByCurrentPosition(dayOffset).getTime() - from.getTime())
                            / VCalendar.MINUTEINMILLIS;
                    startFromMinutes = -1 * minutesOnPrevDay;
                }

                return startFromMinutes;
            }

            /**
             * @param dateOffset
             * @return the amount of pixels the given date is from the left side
             */
            private int calculateDateCellOffsetPx(int dateOffset) {
                int dateCellOffset = 0;
                int[] dateWidths = weekGrid.getDateCellWidths();

                if (dateWidths != null) {
                    for (int i = 0; i < dateOffset; i++) {
                        dateCellOffset += dateWidths[i] + 1;
                    }
                } else {
                    dateCellOffset = dateOffset * weekGrid.getDateCellWidth();
                }

                return dateCellOffset;
            }

            /**
             * Check if the given time range is too small for events
             * 
             * @param start
             * @param end
             * @return
             */
            private boolean isTimeRangeTooSmall(long start, long end) {
                return (end - start) >= getMinTimeRange();
            }

            /**
             * @return the minimum amount of ms that an event must last when
             *         resized
             */
            private long getMinTimeRange() {
                return VCalendar.MINUTEINMILLIS * 30;
            }

            /**
             * Build the string for sending resize events to server
             * 
             * @param event
             * @return
             */
            private String buildResizeString(CalendarEvent event) {
                StringBuilder buffer = new StringBuilder();
                buffer.append(event.getIndex());
                buffer.append(",");
                buffer.append(DateUtil.formatClientSideDate(event.getStart()));
                buffer.append("-");
                buffer.append(DateUtil.formatClientSideTime(event.getStartTime()));
                buffer.append(",");
                buffer.append(DateUtil.formatClientSideDate(event.getEnd()));
                buffer.append("-");
                buffer.append(DateUtil.formatClientSideTime(event.getEndTime()));

                return buffer.toString();
            }

            private Date getTargetDateByCurrentPosition(int left) {
                DateCell newParent = (DateCell) weekGrid.content.getWidget((left / getDateCellWidth()) + 1);
                Date targetDate = newParent.getDate();
                return targetDate;
            }

            private int getDateCellWidth() {
                return weekGrid.getDateCellWidth();
            }

            /* Returns total width of all date cells. */
            private int getDatesWidth() {
                if (weekGrid.width == -1) {
                    // Undefined width. Needs to be calculated by the known cell
                    // widths.
                    int count = weekGrid.content.getWidgetCount() - 1;
                    return count * getDateCellWidth();
                }

                return weekGrid.getInternalWidth();
            }

            /**
             * @return true if the current mouse movement is resizing
             */
            private boolean clickTargetsResize() {
                return weekGrid.getCalendar().isEventResizeAllowed()
                        && (clickTarget == topResizeBar || clickTarget == bottomResizeBar);
            }

            private void addGlobalResizeStyle() {
                if (clickTarget == topResizeBar) {
                    weekGrid.getCalendar().addStyleDependentName("nresize");
                } else if (clickTarget == bottomResizeBar) {
                    weekGrid.getCalendar().addStyleDependentName("sresize");
                }
            }

            private void removeGlobalResizeStyle() {
                weekGrid.getCalendar().removeStyleDependentName("nresize");
                weekGrid.getCalendar().removeStyleDependentName("sresize");
            }

            public void setCalendarEvent(CalendarEvent calendarEvent) {
                this.calendarEvent = calendarEvent;
            }

            public CalendarEvent getCalendarEvent() {
                return calendarEvent;
            }

            public void setDisabled(boolean disabled) {
                this.disabled = disabled;
            }

            public boolean isDisabled() {
                return disabled;
            }

            public void onContextMenu(ContextMenuEvent event) {
                if (weekgrid.getCalendar().getMouseEventListener() != null) {
                    event.preventDefault();
                    event.stopPropagation();
                    weekgrid.getCalendar().getMouseEventListener().contextMenu(event, DayEvent.this);
                }
            }
        }

        public void onContextMenu(ContextMenuEvent event) {
            if (weekgrid.getCalendar().getMouseEventListener() != null) {
                event.preventDefault();
                event.stopPropagation();
                weekgrid.getCalendar().getMouseEventListener().contextMenu(event, DateCell.this);
            }
        }
    }

    public VCalendar getParentCalendar() {
        return calendar;
    }
}