net.tourbook.ui.views.calendar.CalendarGraph.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.ui.views.calendar.CalendarGraph.java

Source

/*******************************************************************************
 * Copyright (C) 2011-2015 Matthias Helmling and Contributors
 *
 * 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 version 2 of the License.
 *
 * 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 St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/

package net.tourbook.ui.views.calendar;

import java.util.ArrayList;
import java.util.List;

import net.tourbook.common.CommonActivator;
import net.tourbook.common.color.ColorCacheSWT;
import net.tourbook.common.preferences.ICommonPreferences;
import net.tourbook.data.TourData;
import net.tourbook.data.TourType;
import net.tourbook.database.TourDatabase;
import net.tourbook.tour.TourDoubleClickState;
import net.tourbook.tour.TourManager;
import net.tourbook.ui.ITourProviderAll;
import net.tourbook.ui.UI;
import net.tourbook.ui.views.calendar.CalendarView.TourInfoFormatter;
import net.tourbook.ui.views.calendar.CalendarView.WeekSummaryFormatter;

import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.Days;

public class CalendarGraph extends Canvas implements ITourProviderAll {

    private Composite _parent;

    private final static IPreferenceStore _prefStoreCommon = CommonActivator.getPrefStore();

    private Display _display = Display.getCurrent();

    private final TourDoubleClickState _tourDoubleClickState = new TourDoubleClickState();

    private ColorCacheSWT _colorCache = new ColorCacheSWT();

    private Color _black = _display.getSystemColor(SWT.COLOR_BLACK);
    private Color _gray = _display.getSystemColor(SWT.COLOR_GRAY);
    private Color _white = _display.getSystemColor(SWT.COLOR_WHITE);
    private Color _red = _display.getSystemColor(SWT.COLOR_RED);
    //   private Color                        _green            = _display.getSystemColor(SWT.COLOR_GREEN);
    private Color _blue = _display.getSystemColor(SWT.COLOR_BLUE);
    //   private Color                        _magenta            = _display.getSystemColor(SWT.COLOR_MAGENTA);
    //   private Color                        _cyan               = _display.getSystemColor(SWT.COLOR_CYAN);
    //   private Color                        _darkGray            = _display.getSystemColor(SWT.COLOR_DARK_GRAY);
    private Color _darkGray = _colorCache.getColor(0x404040);

    //   private NavigationStyle                  _navigationStyle   = NavigationStyle.PHYSICAL;

    private ArrayList<RGB> _rgbBright;
    private ArrayList<RGB> _rgbDark;
    private ArrayList<RGB> _rgbLine;
    private ArrayList<RGB> _rgbText;

    private DateTime _dt = new DateTime();
    private DateTime _dt_normal = _dt;
    private DateTime _dt_tiny = _dt;
    private int _numWeeksNormalView = 5;
    private int _numWeeksTinyView = 10;
    private int _lastDayOfWeekToGoTo = -1;

    private List<ObjectLocation> _tourFocus;
    private List<ObjectLocation> _dayFocus;

    private Image _image = null;
    private Image _highlight = null;
    private boolean _highlightChanged = false;
    private boolean _graphClean = false;

    private Selection _noItem = new Selection(new Long(-1), SelectionType.NONE);
    private Selection _selectedItem = _noItem;
    private Selection _highlightedItem = _noItem;
    private Selection _lastSelection = _noItem;

    final private Rectangle _nullRec = null;

    private CalendarTourDataProvider _dataProvider;
    private CalendarYearMonthContributionItem _calendarYearMonthContributor;

    private ListenerList _selectionProvider = new ListenerList();

    private int _scrollBarShift;
    private int _scrollBarLastSelection;
    private boolean _scrollDebug = false;

    private int _numberOfToursPerDay = 3;
    private boolean _dynamicTourFieldSize = true;

    final static private long _WEEK_MILLIS = (1000 * 60 * 60 * 24 * 7);
    final static private int _MIN_SCROLLABLE_WEEKS = 12;

    private TourInfoFormatter[] _tourInfoFormatter = new TourInfoFormatter[CalendarView.numberOfInfoLines];
    private WeekSummaryFormatter[] _weekSummaryFormatter = new WeekSummaryFormatter[CalendarView.numberOfSummaryLines];

    private boolean _useTextColorForTourInfoText;
    private boolean _useBlackForHighlightTourInfoText;

    private Rectangle _calendarAllDaysRectangle;
    //   private Rectangle _calendarFirstWeekRectangle;
    //   private Rectangle _calendarLastWeekRectangle;

    private String _refText = "Tour12"; //$NON-NLS-1$
    private boolean _tinyLayout = false;

    private boolean _showDayNumberInTinyView = false;
    Point _refTextExtent;

    /**
     * Cache font height;
     */
    private int _fontHeight;

    private class Day {

        private long dayId;

        Day(final DateTime date) {
            final Days d = Days.daysBetween(new DateTime(0), date);
            // this.dayId = (long) date.getYear() * 1000 + date.getDayOfYear();
            this.dayId = d.getDays();
        }

        Day(final long dayId) {
            this.dayId = dayId;
        }

    };

    private class ObjectLocation {

        private Rectangle r;
        private Object o;
        private long id;

        ObjectLocation(final Rectangle r, final long id, final Object o) {
            this.o = o;
            this.id = id;
            this.r = r;
        }

    }

    class Selection {

        Long id;
        SelectionType type;

        Selection(final Long id, final SelectionType type) {
            this.id = id;
            this.type = type;
        }

        @Override
        public boolean equals(final Object o) {
            if (o instanceof Selection) {
                return (((Selection) o).id == this.id) && (((Selection) o).type == this.type);
            }
            return super.equals(o);
        }

        boolean isTour() {
            return (id > 0 && type == SelectionType.TOUR);
        }

    }

    enum SelectionType {
        YEAR, MONHT, WEEK, DAY, TOUR, NONE
    }

    CalendarGraph(final Composite parent, final int style) {

        super(parent, style);

        _parent = parent;

        _dataProvider = CalendarTourDataProvider.getInstance();

        _rgbBright = new ArrayList<RGB>();
        _rgbDark = new ArrayList<RGB>();
        _rgbLine = new ArrayList<RGB>();
        _rgbText = new ArrayList<RGB>();

        updateTourTypeColors();

        //      final DateTime dt = new DateTime();
        //
        //      gotoYear(dt.getYear());
        //      gotoMonth(dt.getMonthOfYear());

        addListener();

    }

    private void addListener() {

        addPaintListener(new PaintListener() {
            @Override
            public void paintControl(final PaintEvent event) {
                drawCalendar(event.gc);
            }
        });

        addMouseListener(new MouseListener() {
            @Override
            public void mouseDoubleClick(final MouseEvent e) {
                if (_selectedItem.isTour()) {
                    _tourDoubleClickState.canEditTour = true;
                    _tourDoubleClickState.canOpenTour = true;
                    _tourDoubleClickState.canQuickEditTour = true;
                    _tourDoubleClickState.canEditMarker = true;
                    _tourDoubleClickState.canAdjustAltitude = true;
                    TourManager.getInstance().tourDoubleClickAction(CalendarGraph.this, _tourDoubleClickState);
                }

            }

            @Override
            public void mouseDown(final MouseEvent e) {
                onMouseMove(e);
            }

            @Override
            public void mouseUp(final MouseEvent e) {
                // onMouseMove(e);
            }

        });

        addMouseMoveListener(new MouseMoveListener() {
            @Override
            public void mouseMove(final MouseEvent e) {
                onMouseMove(e);
            }
        });

        addFocusListener(new FocusListener() {

            @Override
            public void focusGained(final FocusEvent e) {
                // System.out.println("Focus gained");
                // redraw();
            }

            @Override
            public void focusLost(final FocusEvent e) {
                // System.out.println("Focus lost");
                // redraw();
            }
        });

        addTraverseListener(new TraverseListener() {
            @Override
            public void keyTraversed(final TraverseEvent e) {
                switch (e.detail) {
                case SWT.TRAVERSE_RETURN:
                    if (_selectedItem.isTour()) {
                        _tourDoubleClickState.canEditTour = true;
                        _tourDoubleClickState.canOpenTour = true;
                        _tourDoubleClickState.canQuickEditTour = true;
                        _tourDoubleClickState.canEditMarker = true;
                        _tourDoubleClickState.canAdjustAltitude = true;
                        TourManager.getInstance().tourDoubleClickAction(CalendarGraph.this, _tourDoubleClickState);
                    } else {
                        gotoNextTour();
                    }
                    break;
                case SWT.TRAVERSE_ESCAPE:
                    _selectedItem = _noItem;
                    redraw();
                    break;
                case SWT.TRAVERSE_PAGE_NEXT:
                case SWT.TRAVERSE_PAGE_PREVIOUS:
                case SWT.TRAVERSE_TAB_NEXT:
                case SWT.TRAVERSE_TAB_PREVIOUS:
                    e.doit = true;
                    break;
                }
            }
        });

        addListener(SWT.KeyDown, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                switch (event.keyCode) {
                case SWT.ARROW_LEFT:
                case 'h':
                    gotoPrevTour();
                    //               switch (_navigationStyle) {
                    //               case PHYSICAL:
                    //                  gotoTourOtherWeekday(-1);
                    //                  break;
                    //               case LOGICAL:
                    //                  gotoPrevTour();
                    //                  break;
                    //               }
                    break;
                case SWT.ARROW_RIGHT:
                case 'l':
                    gotoNextTour();
                    //               switch (_navigationStyle) {
                    //               case PHYSICAL:
                    //                  gotoTourOtherWeekday(+1);
                    //                  break;
                    //               case LOGICAL:
                    //                  gotoNextTour();
                    //                  break;
                    //               }
                    break;
                case SWT.ARROW_UP:
                case 'k':
                    if (_selectedItem.isTour()) {
                        gotoTourSameWeekday(-1);
                    } else {
                        gotoPrevWeek();
                    }
                    //               switch (_navigationStyle) {
                    //               case PHYSICAL:
                    //                  gotoTourSameWeekday(-1);
                    //                  break;
                    //               case LOGICAL:
                    //                  gotoPrevWeek();
                    //                  break;
                    //               }
                    break;
                case SWT.ARROW_DOWN:
                case 'j':
                    if (_selectedItem.isTour()) {
                        gotoTourSameWeekday(+1);
                    } else {
                        gotoNextWeek();
                    }
                    //               switch (_navigationStyle) {
                    //               case PHYSICAL:
                    //                  gotoTourSameWeekday(+1);
                    //                  break;
                    //               case LOGICAL:
                    //                  gotoNextWeek();
                    //                  break;
                    //               }
                    break;
                case SWT.PAGE_DOWN:
                case 'n':
                    gotoNextScreen();
                    break;
                case SWT.PAGE_UP:
                case 'p':
                    gotoPrevScreen();
                    break;
                case SWT.HOME:
                case '.':
                    gotoToday();
                    break;
                case SWT.END:
                case ',':
                    gotoFirstTour();
                    break;
                case 'i':
                    zoomIn();
                    break;
                case 'o':
                    zoomOut();
                    break;
                case ' ':
                    if (_selectedItem.isTour()) {
                        _selectedItem = _noItem;
                        redraw();
                    } else {
                        gotoPrevTour();
                    }
                    break;
                }
            }
        });

        //      addMouseWheelListener(new MouseWheelListener() {
        //         @Override
        //         public void mouseScrolled(final MouseEvent event) {
        //            Point p = new Point(event.x, event.y);
        //            if (_calendarDaysRectangle.contains(p)) {
        //               gotoNextTour();
        //            } else {
        //               gotoNextWeek();
        //            }
        //         }
        //      });

        addListener(SWT.MouseWheel, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                final Point p = new Point(event.x, event.y);
                if (_calendarAllDaysRectangle.contains(p)) {
                    if (event.count > 0) {
                        //                  if (_calendarFirstWeekRectangle.contains(p) || _calendarLastWeekRectangle.contains(p)) {
                        //                     gotoTourSameWeekday(-1);
                        //                  } else {
                        //                     gotoPrevTour();
                        //                  }
                        if (_selectedItem.isTour()) {
                            gotoPrevTour();
                        } else {
                            gotoPrevWeek();
                        }
                    } else {
                        //                  if (_calendarFirstWeekRectangle.contains(p) || _calendarLastWeekRectangle.contains(p)) {
                        //                     gotoTourSameWeekday(1);
                        //                  } else {
                        //                     gotoNextTour();
                        //                  }
                        if (_selectedItem.isTour()) {
                            gotoNextTour();
                        } else {
                            gotoNextWeek();
                        }
                    }
                } else { // left or right column, scroll by pages
                    if (event.count > 0) {
                        gotoPrevScreen();
                    } else {
                        gotoNextScreen();
                    }
                }
                event.doit = false;
            }
        });

        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(final DisposeEvent e) {
                _colorCache.dispose();
            }
        });

        addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(final ControlEvent event) {
                _graphClean = false;
                redraw();
            }
        });

        addFocusListener(new FocusListener() {

            @Override
            public void focusGained(final FocusEvent e) {
                // redraw();
            }

            @Override
            public void focusLost(final FocusEvent e) {
                // redraw();
            }
        });

        addMouseTrackListener(new MouseTrackListener() {
            @Override
            public void mouseEnter(final MouseEvent e) {
                // forceFocus();
            }

            @Override
            public void mouseExit(final MouseEvent e) {
                redraw();
            }

            @Override
            public void mouseHover(final MouseEvent e) {
            }
        });

        final ScrollBar sb = _parent.getVerticalBar();
        sb.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent event) {
                onScroll(event);
            }
        });

    }

    void addSelectionProvider(final ICalendarSelectionProvider provider) {
        _selectionProvider.add(provider);
    }

    void draw() {
        _graphClean = false;
        redraw();
    }

    private void drawCalendar(GC gc) {

        final int dayLabelXOffset = 1;

        final int XX = getSize().x;
        final int YY = getSize().y;

        //      System.out.println(_graphClean ? "clean!" : "NOT clean!");
        //      System.out.println(_highlightChanged ? "HL changed!" : "HL NOT changed");
        //      System.out.println("-----------");

        _fontHeight = gc.getFontMetrics().getHeight();

        if (_graphClean && _image != null) {

            final GC oldGc = gc;
            _highlight = new Image(getDisplay(), XX, YY);
            gc = new GC(_highlight);
            gc.drawImage(_image, 0, 0);

            drawSelection(gc);

            if (_highlightChanged) {
                drawHighLight(gc);
                _highlightChanged = false;
            }

            gc.dispose();
            oldGc.drawImage(_highlight, 0, 0);
            _highlight.dispose();
            return;
        }

        if (_scrollDebug) {
            System.out.println("Drawing year: " + _dt.getYear() + " week: " + _dt.getWeekOfWeekyear()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        if (_image != null && !_image.isDisposed()) {
            _image.dispose();
        }

        DateTime date = new DateTime(_dt);
        _image = new Image(getDisplay(), XX, YY);

        // update month/year dropdown box
        // look at the 1st day of the week after the first day displayed because if we go to
        // a specific month we ensure that the first day of the month is displayed in
        // the first line, meaning the first day in calendar normally contains a day
        // of the *previous* month
        if (_calendarYearMonthContributor.getSelectedYear() != date.plusDays(7).getYear()) {
            _calendarYearMonthContributor.selectYear(date.getYear());
        }
        if (_calendarYearMonthContributor.getSelectedMonth() != date.plusDays(7).getMonthOfYear()) {
            _calendarYearMonthContributor.selectMonth(date.getMonthOfYear());
        }

        final GC oldGc = gc;
        gc = new GC(_image);

        _refTextExtent = gc.stringExtent(_refText);
        final boolean oldLayout = _tinyLayout;
        _tinyLayout = (_refTextExtent.x > XX / 9); // getNumOfWeeks needs the _tinuLayout set

        if (oldLayout != _tinyLayout) { // the layout style changed, try to restore weeks and make selection visible
            if (_tinyLayout) {
                _dt_normal = _dt;
                _dt = _dt_tiny;
            } else {
                _dt_tiny = _dt;
                _dt = _dt_normal;
            }
            scrollBarUpdate();
            if (_selectedItem.id > 0) {
                switch (_selectedItem.type) {
                case DAY:
                    gotoDate(new DateTime(0).plusDays(_selectedItem.id.intValue()));
                    return;
                case TOUR:
                    gotoTourId(_selectedItem.id);
                    return;
                }
            }
        }

        final int numCols = 9; // one col left and right of the week + 7 week days
        final int numRows = getNumOfWeeks(); // number of weeks per month displayed (make sure _tinyLayout is already defined!)

        final Color alternate = _colorCache.getColor(0xf0f0f0);

        _tourFocus = new ArrayList<ObjectLocation>();
        _dayFocus = new ArrayList<ObjectLocation>();

        CalendarTourData[] data;

        final Font normalFont = gc.getFont();
        final FontData fd[] = normalFont.getFontData();
        fd[0].setStyle(SWT.BOLD);
        final Font boldFont = new Font(_display, fd[0]);

        final Rectangle area = getClientArea();
        gc.setBackground(_white);
        gc.setForeground(_black);
        gc.fillRectangle(area);

        final float dY = (float) YY / (float) numRows;
        float dX = (float) XX / (float) numCols;

        // keep the summary column at a minimal width and hide it completely if height goes blow usable value
        final int minSummaryWidth = _refTextExtent.x;
        final int minSummaryHeigth = (_refTextExtent.y * 2) / 3;
        int summaryWidth = 0;
        if (dY > minSummaryHeigth) {
            if (dX < minSummaryWidth) {
                summaryWidth = minSummaryWidth;
            } else {
                summaryWidth = (int) dX;
            }
        }

        final int minInfoWidth = _refTextExtent.x / 2;
        final int minInfoHeigth = (_refTextExtent.y / 3);
        int infoWidth = 0;
        if (dY > minInfoHeigth) {
            if (dX < minInfoWidth) {
                infoWidth = minInfoWidth;
            } else {
                infoWidth = (int) dX;
            }
        }

        dX = (float) (XX - summaryWidth - infoWidth) / (numCols - 2);

        _calendarAllDaysRectangle = new Rectangle(infoWidth, 0, (int) (7 * dX), YY);
        //      _calendarFirstWeekRectangle = new Rectangle(infoWidth, 0, (int) (7 * dX), (int) dY);
        //      _calendarLastWeekRectangle = new Rectangle( infoWidth, (int) ((getNumOfWeeks() - 1) * dY), (int) (7 * dX), (int) dY);

        // first draw the horizontal lines
        gc.setBackground(_white);
        gc.setForeground(_gray);
        for (int i = 0; i <= numRows; i++) {
            gc.drawLine(0, (int) (i * dY), XX, (int) (i * dY));
        }

        //      final Rectangle selectedRec = null;
        //      final CalendarTourData selectedTour = null;
        //      final boolean doSelection = false;

        final long todayDayId = (new Day(new DateTime())).dayId;

        gc.setFont(boldFont);
        // a rough guess about the max size of the label
        final Point[] headerSizes = { gc.stringExtent("22. May 99"), //$NON-NLS-1$
                gc.stringExtent("22. May"), //$NON-NLS-1$
                gc.stringExtent("22") }; //$NON-NLS-1$
        gc.setFont(normalFont);

        final String[] headerFormats = { "dd. MMM yy", "dd. MMM", "dd" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        String headerFormat = UI.EMPTY_STRING;

        // Find a format for the day header which fits into the rectangle available;
        int g = 0;
        while (g < headerSizes.length && headerSizes[g].x > (dX - dayLabelXOffset)) {
            g++;
        }
        g = Math.min(g, headerSizes.length - 1); // if the cell is smaller than the shortest format (no index 'g' was found) we use the shortest format and relay on clipping
        // if (headerSizes[g].y < dY) {
        //    headerFormat = headerFormats[g];
        // }
        headerFormat = headerFormats[g];
        final int dayLabelWidht = headerSizes[g].x;
        int dayLabelHeight = headerSizes[g].y;

        // _tinyLayout = (refTextExtent.x > dX || refTextExtent.y > dY - dayLabelHeight) ? true : false;

        DateTime weekDate;
        long dayId = (new Day(date)).dayId; // we use simple ids

        // Weeks
        for (int i = 0; i < numRows; i++) {

            final int Y1 = (int) (i * dY);
            final int Y2 = (int) ((i + 1) * dY);

            // Days per week
            Rectangle dayRec = null;
            weekDate = date; // save the first day of this week as a pointer to this week

            if (infoWidth > 0) {
                final Rectangle infoRec = new Rectangle(0, Y1, infoWidth, (Y2 - Y1));
                drawWeekInfo(gc, date, infoRec);
            }

            for (int j = 0; j < 7; j++) {
                final int X1 = infoWidth + (int) (j * dX);
                final int X2 = infoWidth + (int) ((j + 1) * dX);
                //            final Rectangle dayRec = new Rectangle(X1, Y1, (X2 - X1), (Y2 - Y1));
                dayRec = new Rectangle(X1, Y1, (X2 - X1), (Y2 - Y1));
                final Day day = new Day(dayId);
                _dayFocus.add(new ObjectLocation(dayRec, dayId, day));
                dayId = day.dayId + 1;
                final int weekDay = date.getDayOfWeek();

                gc.setBackground(_white);

                // Day background rectangle
                if ((date.getMonthOfYear() % 2) == 1) {
                    gc.setBackground(alternate);
                    gc.fillRectangle(dayRec.x, dayRec.y + 1, dayRec.width, dayRec.height - 1);
                }

                data = _dataProvider.getCalendarDayData(date.getYear(), date.getMonthOfYear(),
                        date.getDayOfMonth());

                // Day header box
                if (!_tinyLayout) {
                    gc.setForeground(_gray);
                    gc.fillGradientRectangle(X1, Y1, dayRec.width + 1, dayLabelHeight, true); // no clue why I've to add 1 to the width, looks like a bug on Linux and does not hurt as we overwrite with the vertial line at the end anyway

                    // Day header label
                    gc.setFont(boldFont);
                    if (day.dayId == todayDayId) {
                        gc.setForeground(_blue);
                    } else if (weekDay == DateTimeConstants.SATURDAY || weekDay == DateTimeConstants.SUNDAY) {
                        gc.setForeground(_red);
                    } else {
                        gc.setForeground(_darkGray);
                    }
                    gc.setClipping(X1, Y1, dayRec.width, dayLabelHeight); // this clipping should only kick in if shortest label format is still longer than the cell width
                    gc.drawText(date.toString(headerFormat), X2 - dayLabelWidht - dayLabelXOffset, Y1, true);
                    gc.setFont(normalFont);
                    gc.setClipping(_nullRec);

                } else {
                    dayLabelHeight = 0;
                }

                drawDayTours(gc, data, new Rectangle(dayRec.x, dayRec.y + dayLabelHeight, dayRec.width,
                        dayRec.height - dayLabelHeight));

                if (_tinyLayout && _showDayNumberInTinyView) {
                    if (day.dayId == todayDayId) {
                        gc.setForeground(_blue);
                    } else if (weekDay == DateTimeConstants.SATURDAY || weekDay == DateTimeConstants.SUNDAY) {
                        gc.setForeground(_red);
                    } else {
                        gc.setForeground(_darkGray);
                    }
                    gc.setAlpha(0x50);
                    gc.setFont(boldFont);
                    gc.setClipping(dayRec);
                    gc.drawText(date.toString(headerFormat), X2 - dayLabelWidht - dayLabelXOffset, Y1, true);
                    gc.setFont(normalFont);
                    gc.setClipping(_nullRec);
                    gc.setAlpha(0xFF);
                }

                date = date.plusDays(1);
            }

            if (summaryWidth > 0) {
                final int X1 = infoWidth + (int) (7 * dX);
                final int X2 = X1 + summaryWidth;
                final Rectangle weekRec = new Rectangle(X1, Y1, (X2 - X1), (Y2 - Y1));
                final CalendarTourData weekSummary = _dataProvider.getCalendarWeekSummaryData(weekDate.getYear(),
                        weekDate.getWeekOfWeekyear());
                if (weekSummary.numTours > 0) {
                    drawWeekSummary(gc, weekSummary, weekRec);
                }
            }

        }
        gc.setFont(normalFont);

        // and finally the vertical lines
        gc.setForeground(_display.getSystemColor(SWT.COLOR_GRAY));
        for (int i = 0; i <= 7; i++) {
            gc.drawLine(infoWidth + (int) (i * dX), 0, infoWidth + (int) (i * dX), YY);
        }

        // draw the selection on top of our calendar graph image so we can reuse that image
        _highlight = new Image(getDisplay(), XX, YY);
        gc = new GC(_highlight);
        gc.drawImage(_image, 0, 0);
        drawSelection(gc);
        oldGc.drawImage(_highlight, 0, 0);
        _highlight.dispose();

        boldFont.dispose();
        oldGc.dispose();
        gc.dispose();

        _graphClean = true;

    }

    private void drawDayTours(final GC gc, final CalendarTourData[] data, final Rectangle rec) {

        int max;
        if ((0 == _numberOfToursPerDay) || _tinyLayout) {
            max = data.length;
        } else {
            max = _dynamicTourFieldSize ? data.length : Math.min(_numberOfToursPerDay, data.length);
        }
        for (int i = 0; i < max; i++) {
            int ddy;
            if ((0 == _numberOfToursPerDay) || _tinyLayout) {
                ddy = rec.height / data.length;
            } else {
                final int dy = rec.height / _numberOfToursPerDay;
                // narrow the tour fields to fit if more than _numberOfTourPerDay tours
                ddy = _dynamicTourFieldSize
                        ? (data.length <= _numberOfToursPerDay ? dy : (_numberOfToursPerDay * dy) / data.length)
                        : dy;
            }
            // final Rectangle tour = new Rectangle(rec.x + 1, rec.y + rec.height - (i + 1) * ddy, (rec.width - 2), ddy - 1);
            final int k = max - i; // morning top, evening button
            final Rectangle tour = new Rectangle(rec.x + 1, rec.y + rec.height - k * ddy, (rec.width - 2), ddy - 1);
            final Rectangle focus = new Rectangle(tour.x - 1, tour.y - 1, tour.width + 2, tour.height + 2);

            _tourFocus.add(new ObjectLocation(focus, data[i].tourId, data[i]));

            drawTourInfo(gc, tour, data[i], false);

        }

    }

    private void drawHighLight(final GC gc) {

        List<ObjectLocation> objects;
        if (_highlightedItem.type == SelectionType.TOUR) {
            objects = _tourFocus;
        } else if (_highlightedItem.type == SelectionType.DAY) {
            objects = _dayFocus;
        } else {
            return;
        }

        for (final ObjectLocation ol : objects) {
            if (ol.id == _highlightedItem.id) {
                if (ol.o instanceof CalendarTourData) {
                    drawHighlightedTour(gc, (CalendarTourData) (ol.o), ol.r);
                } else if (ol.o instanceof Day) {
                    // gc.setAlpha(0xd0); // like statistics
                    gc.setAlpha(0xa0);
                    gc.setBackground(_white);
                    gc.setForeground(_gray);
                    gc.fillGradientRectangle(ol.r.x - 4, ol.r.y - 4, ol.r.width + 9, ol.r.height + 9, false);
                    gc.drawRoundRectangle(ol.r.x - 5, ol.r.y - 5, ol.r.width + 10, ol.r.height + 10, 6, 6);
                    gc.setAlpha(0xFF);
                }
                return;
            }
        }
    }

    private void drawHighlightedTour(final GC gc, final CalendarTourData data, final Rectangle rec) {

        gc.setAlpha(0xd0);
        gc.setBackground(_colorCache.getColor(_rgbBright.get(data.typeColorIndex).hashCode()));
        gc.setForeground(_colorCache.getColor(_rgbDark.get(data.typeColorIndex).hashCode()));
        gc.fillGradientRectangle(rec.x - 4, rec.y - 4, rec.width + 9, rec.height + 9, false);
        gc.setForeground(_colorCache.getColor(_rgbLine.get(data.typeColorIndex).hashCode()));
        gc.drawRoundRectangle(rec.x - 5, rec.y - 5, rec.width + 10, rec.height + 10, 6, 6);
        gc.setAlpha(0xFF);

        // focus is 1 pixel larger than tour rectangle
        final Rectangle r = new Rectangle(rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2);
        // only fill in text if the tour rectangle has a reasonable size
        if (!_tinyLayout) {
            Color color;
            if (_useBlackForHighlightTourInfoText) {
                color = _black;
            } else {
                color = _colorCache.getColor(_rgbText.get(data.typeColorIndex).hashCode());
            }
            drawTourInfoText(gc, r, data, color);
        }

    }

    private void drawSelectedDay(final GC gc, final Rectangle r) {

        // gc.setAlpha(0xd0); // like statistics
        //   gc.setAlpha(0xa0);
        gc.setBackground(_blue);
        gc.setForeground(_blue);
        // gc.fillGradientRectangle(r.x - 4, r.y - 4, r.width + 9, r.height + 9, false);
        // gc.drawRoundRectangle(r.x - 5, r.y - 5, r.width + 10, r.height + 10, 6, 6);
        final int oldLw = gc.getLineWidth();
        gc.setLineWidth(4);
        gc.drawRoundRectangle(r.x - 2, r.y - 2, r.width + 5, r.height + 5, 6, 6);
        gc.setLineWidth(oldLw);

    }

    private void drawSelectedTour(final GC gc, final CalendarTourData data, final Rectangle rec) {

        final Color lineColor = _colorCache.getColor(_rgbLine.get(data.typeColorIndex).hashCode());

        // - red box -
        gc.setBackground(_red);
        // gc.setBackground(lineColor);
        gc.fillRectangle(rec.x - 4, rec.y - 4, rec.width + 9, rec.height + 9);
        // gc.setForeground(_red);
        gc.setForeground(lineColor);
        gc.drawRoundRectangle(rec.x - 5, rec.y - 5, rec.width + 10, rec.height + 10, 6, 6);

        // focus is 1 pixel larger than tour rectangle
        final Rectangle r = new Rectangle(rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2);
        drawTourInfo(gc, r, data, true);
        return;

    }

    private void drawSelection(final GC gc) {

        if (!_lastSelection.equals(_selectedItem)) {
            fireSelectionEvent(_selectedItem);
            _lastSelection = _selectedItem;
        }

        List<ObjectLocation> objects;
        if (_selectedItem.type == SelectionType.TOUR) {
            objects = _tourFocus;
        } else if (_selectedItem.type == SelectionType.DAY) {
            objects = _dayFocus;
        } else {
            return;
        }

        for (final ObjectLocation ol : objects) {
            if (ol.id == _selectedItem.id) {
                if (ol.o instanceof CalendarTourData) {
                    drawSelectedTour(gc, (CalendarTourData) (ol.o), ol.r);
                } else if (ol.o instanceof Day) {
                    drawSelectedDay(gc, ol.r);
                }
                return;
            }
        }
    }

    private void drawTourInfo(final GC gc, final Rectangle r, final CalendarTourData data,
            final boolean highlight) {

        final Color line = _colorCache.getColor(_rgbLine.get(data.typeColorIndex).hashCode());

        gc.setForeground(line);
        gc.drawRectangle(r);

        gc.setBackground(_colorCache.getColor(_rgbBright.get(data.typeColorIndex).hashCode()));
        gc.setForeground(_colorCache.getColor(_rgbDark.get(data.typeColorIndex).hashCode()));
        gc.fillGradientRectangle(r.x + 1, r.y + 1, r.width - 1, r.height - 1, false);

        // only fill in text if the tour rectangle has a reasonable size
        if (!_tinyLayout) {
            Color fg;
            if (highlight && _useBlackForHighlightTourInfoText) {
                fg = _black;
            } else if (_useTextColorForTourInfoText) {
                fg = _colorCache.getColor(_rgbText.get(data.typeColorIndex).hashCode());
            } else {
                fg = _darkGray;
            }
            drawTourInfoText(gc, r, data, fg);
        }

    }

    private void drawTourInfoText(final GC gc, final Rectangle r, final CalendarTourData data, final Color fg) {

        gc.setForeground(fg);
        gc.setClipping(r.x + 1, r.y, r.width - 2, r.height);

        int y = r.y + 1;
        final int minToShow = (2 * _fontHeight / 3);

        String prevInfo = null;

        for (int formatterIndex = 0; formatterIndex < _tourInfoFormatter.length
                && y < r.y + r.height - minToShow; formatterIndex++) {

            final String info = _tourInfoFormatter[formatterIndex].format(data);

            // Prevent that the same text is displayed multiple times.
            if (info.length() > 0 && info.equals(prevInfo) == false) {

                // this is another text

                gc.drawText(info, r.x + 2, y, true);

                y += _fontHeight;
            }

            prevInfo = info;
        }

        gc.setClipping(_nullRec);
    }

    private void drawWeekInfo(final GC gc, final DateTime dt, final Rectangle rec) {

        final Font normalFont = gc.getFont();
        final FontData fd[] = normalFont.getFontData();
        fd[0].setStyle(SWT.BOLD);
        // fd[0].setHeight(((rec.height) * 72 / _display.getDPI().y) / 4);
        final Font boldFont = new Font(_display, fd[0]);

        gc.setForeground(_darkGray);
        gc.setBackground(_white);

        gc.setFont(boldFont);
        String text;
        if (_tinyLayout) {
            if (dt.minusDays(1).getMonthOfYear() != dt.plusDays(6).getMonthOfYear()) { // a new month started on this week
                gc.setClipping(new Rectangle(rec.x, rec.y, rec.width, 4 * rec.height)); // clipp to the room left of this month
                text = dt.plusDays(6).toString("MMM"); //$NON-NLS-1$
                if (rec.width < (2 * _refTextExtent.x / 3)) {
                    text = text.substring(0, 1);
                }
                gc.drawText(text, rec.x + 2, rec.y + 2);
                gc.setClipping(_nullRec);
            }
        } else {
            gc.drawText("" + dt.getWeekOfWeekyear(), rec.x + 4, rec.y + 2);//$NON-NLS-1$
        }
        gc.setFont(normalFont);

        boldFont.dispose();

    }

    private void drawWeekSummary(final GC gc, final CalendarTourData data, final Rectangle rec) {

        gc.setClipping(rec);
        gc.setBackground(_white);
        final int xr = rec.x + rec.width - 1;
        final int xl = rec.x + 2;
        int xx;
        int y = rec.y + 1;
        String text;
        final boolean doClip = true;

        //      final int minToShow = (2 * fontHeight / 3);

        Point extent;
        final int maxLength = rec.width - 2;

        final Font normalFont = gc.getFont();
        final FontData fd[] = normalFont.getFontData();
        fd[0].setStyle(SWT.BOLD);
        final Font boldFont = new Font(_display, fd[0]);

        gc.setFont(boldFont);

        for (final WeekSummaryFormatter formatter : _weekSummaryFormatter) {
            gc.setForeground(_colorCache.getColor(formatter.getColor().hashCode()));
            text = formatter.format(data);
            // if (text.length() > 0 && y < rec.y + rec.height - minToShow) {
            if (text.length() > 0 && y < (rec.y + rec.height)) {
                extent = gc.stringExtent(text);
                xx = xr - extent.x;
                if (extent.x > maxLength) {
                    if (doClip && text.contains(UI.SPACE)) {
                        text = text.substring(0, text.lastIndexOf(UI.SPACE));
                        xx = xr - gc.stringExtent(text).x;
                    } else {
                        xx = xl;
                    }
                }
                gc.drawText(text, xx, y);
            }
            y += _fontHeight;
        }

        gc.setFont(normalFont);
        boldFont.dispose();

        gc.setClipping(_nullRec);
    }

    void fireSelectionEvent(final Selection selection) {

        final Object[] listeners = _selectionProvider.getListeners();
        for (final Object listener2 : listeners) {
            final ICalendarSelectionProvider listener = (ICalendarSelectionProvider) listener2;
            SafeRunnable.run(new SafeRunnable() {
                @Override
                public void run() {
                    listener.selectionChanged(selection);
                }
            });
        }
    }

    void fireSelectionEvent(final SelectionType type, final long id) {

        final Object[] listeners = _selectionProvider.getListeners();
        for (final Object listener2 : listeners) {
            final ICalendarSelectionProvider listener = (ICalendarSelectionProvider) listener2;
            SafeRunnable.run(new SafeRunnable() {
                @Override
                public void run() {
                    listener.selectionChanged(_selectedItem);
                }
            });
        }
    }

    @Override
    public ArrayList<TourData> getAllSelectedTours() {
        return getSelectedTours();
    }

    public DateTime getFirstDay() {
        return _dt;
    }

    private int getFirstDayOfWeek() {

        final int firstDayOfWeek = _prefStoreCommon.getInt(ICommonPreferences.CALENDAR_WEEK_FIRST_DAY_OF_WEEK);

        //      int firstDayOfWeek = _prefStore.getInt(ITourbookPreferences.CALENDAR_WEEK_FIRST_DAY_OF_WEEK);
        //      firstDayOfWeek--; // the prefStore is using Calendar Constants (SO=1 ... SA=7) we are using Joda (MO=1 .. SO=7)
        //      if (firstDayOfWeek < 1) {
        //         firstDayOfWeek = 7;
        //      }

        return firstDayOfWeek;

    }

    public int getNumberOfToursPerDay() {
        return _numberOfToursPerDay;
    }

    int getNumOfWeeks() {
        if (_tinyLayout) {
            return _numWeeksTinyView;
        } else {
            return _numWeeksNormalView;
        }
    }

    public int getNumWeeksNormalLayout() {
        return _numWeeksNormalView;
    }

    public int getNumWeeksTinyLayout() {
        return _numWeeksTinyView;
    }

    public Long getSelectedTourId() {
        if (_selectedItem.isTour()) {
            return _selectedItem.id;
        } else {
            return _noItem.id;
        }
    }

    @Override
    public ArrayList<TourData> getSelectedTours() {

        final ArrayList<TourData> selectedTourData = new ArrayList<TourData>();
        if (_selectedItem.isTour()) {
            selectedTourData.add(TourManager.getInstance().getTourData(_selectedItem.id));
        }
        return selectedTourData;
    }

    public boolean getShowDayNumberInTinyView() {
        return _showDayNumberInTinyView;
    }

    public int getTourInfoFormatterIndex(final int line) {
        return _tourInfoFormatter[line].index;
    }

    public boolean getTourInfoUseHighlightTextBlack() {
        return _useBlackForHighlightTourInfoText;
    }

    public boolean getTourInfoUseTextColor() {
        return _useTextColorForTourInfoText;
    }

    public int getWeekSummaryFormatter(final int line) {
        return _weekSummaryFormatter[line].index;
    }

    public void gotoDate(final DateTime dt) {

        _dt = dt;
        _dt = _dt.minusWeeks(getNumOfWeeks() / 2); // center date on screen
        _dt = _dt.withDayOfWeek(getFirstDayOfWeek()); // set first day to start of week

        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    public void gotoFirstTour() {
        _dt = _dataProvider.getFirstDateTime();
        gotoDate(_dt);
    }

    public void gotoMonth(final int month) {

        _dt = _dt.withMonthOfYear(month); // scroll to first day of the week containing the first day of this month
        _dt = _dt.withDayOfMonth(1);
        _dt = _dt.withDayOfWeek(getFirstDayOfWeek()); // set first day to start of week

        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    public void gotoNextScreen() {
        _dt = _dt.plusWeeks(getNumOfWeeks());
        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    public void gotoNextTour() {
        gotoTourOffset(+1);
    }

    public void gotoNextWeek() {
        _dt = _dt.plusWeeks(1);
        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    public void gotoPrevScreen() {
        _dt = _dt.minusWeeks(getNumOfWeeks());
        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    public void gotoPrevTour() {
        gotoTourOffset(-1);
    }

    public void gotoPrevWeek() {
        _dt = _dt.minusWeeks(1);
        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    public void gotoToday() {

        _dt = new DateTime();

        final Days d = Days.daysBetween(new DateTime(0), _dt);
        _selectedItem = new Selection((long) d.getDays(), SelectionType.DAY);
        gotoDate(_dt);

    }

    public void gotoTourId(final Long tourId) {

        final DateTime dt = _dataProvider.getCalendarTourDateTime(tourId);

        _selectedItem = new Selection(tourId, SelectionType.TOUR);

        if (dt.isBefore(_dt) || dt.isAfter(_dt.plusWeeks(getNumOfWeeks()))) {
            _graphClean = false;
            gotoDate(dt);
        } else {
            redraw();
        }
    }

    private void gotoTourOffset(int offset) {

        if (_tourFocus.size() < 1) {
            if (offset < 0) {
                gotoPrevWeek();
            } else {
                gotoNextWeek();
            }
            return;
        }

        if (!_selectedItem.isTour()) { // if no tour is selected, count from first/last tour and select this tour
            if (offset > 0) {
                _selectedItem = new Selection(_tourFocus.get(0).id, SelectionType.TOUR);
                offset--;
            } else {
                _selectedItem = new Selection(new Long(_tourFocus.get(_tourFocus.size() - 1).id),
                        SelectionType.TOUR);
                offset++;
            }
        }

        boolean visible = false;
        int index = 0;
        for (final ObjectLocation ol : _tourFocus) {
            if (_selectedItem.id == ol.id) {
                visible = true; // the selection is visible
                break;
            }
            index++;
        }
        if (!visible) { // if we are scrolling tours forward and the selection got invisible start at the first tour again
            if (offset > 0) {
                index = -1;
            }
        }
        final int newIndex = index + offset;
        if (newIndex < 0) {
            gotoPrevWeek();
            return;
        } else if (newIndex >= _tourFocus.size()) {
            gotoNextWeek();
            return;
        } else {
            _selectedItem = new Selection(_tourFocus.get(newIndex).id, SelectionType.TOUR);
            _graphClean = false;
            redraw();
        }

    }

    /*
     * private void gotoTourOtherWeekday(final int direction) { if (_tourFocus.size() < 1) { if
     * (direction < 0) { gotoPrevWeek(); } else { gotoNextWeek(); } return; } if
     * (!_selectedItem.isTour()) { // if no tour is selected, count from first/last tour and select
     * this tour if (direction > 0) { _selectedItem = new Selection(_tourFocus.get(0).id,
     * SelectionType.TOUR); } else { _selectedItem = new Selection(new
     * Long(_tourFocus.get(_tourFocus.size() - 1).id), SelectionType.TOUR); } redraw(); return; }
     * int index = 0; CalendarTourData ctd = null; for (final ObjectLocation ol : _tourFocus) { if
     * (_selectedItem.id == ol.id) { ctd = (CalendarTourData) (ol.o); break; } index++; } if (null
     * == ctd) { // selected tour scrolled out of the window, select first/last tour if
     * (_tourFocus.size() > 0) { ObjectLocation ol; if (direction > 0) { ol = _tourFocus.get(0); }
     * else { ol = _tourFocus.get(_tourFocus.size() - 1); } _selectedItem = new Selection(ol.id,
     * SelectionType.TOUR); redraw(); return; } } if (null != ctd) { index += direction; for (int i
     * = index; i >= 0 && i < _tourFocus.size(); i += direction) { final ObjectLocation ol =
     * _tourFocus.get(i); // if (ctd.dayOfWeek != ((CalendarTourData) (ol.o)).dayOfWeek) { final
     * CalendarTourData ctdNext = (CalendarTourData) (ol.o); if (ctd.day != ctdNext.day || ctd.week
     * != ctdNext.week || ctd.year != ctdNext.year) { _selectedItem = new Selection(ol.id,
     * SelectionType.TOUR); redraw(); return; } } } else { // selected Item is not on the screen any
     * more // _selectedItem = _noItem; keep selection } if (direction < 0) { gotoPrevWeek(); } else
     * { gotoNextWeek(); } }
     */

    private void gotoTourSameWeekday(final int direction) {

        if (_tourFocus.size() < 1) {
            if (direction < 0) {
                gotoPrevWeek();
            } else {
                gotoNextWeek();
            }
            return;
        }

        if (!_selectedItem.isTour()) { // if no tour is selected, count from first/last tour and select this tour
            if (direction > 0) {
                _selectedItem = new Selection(_tourFocus.get(0).id, SelectionType.TOUR);
            } else {
                _selectedItem = new Selection(new Long(_tourFocus.get(_tourFocus.size() - 1).id),
                        SelectionType.TOUR);
            }
            redraw();
            return;
        }

        int index = 0;
        CalendarTourData ctd = null;
        for (final ObjectLocation ol : _tourFocus) {
            if (_selectedItem.id == ol.id) {
                ctd = (CalendarTourData) (ol.o);
                break;
            }
            index++;
        }

        int dayOfWeekToGoTo = -1;
        if (null != ctd) {
            dayOfWeekToGoTo = ctd.dayOfWeek;
        } else if (_lastDayOfWeekToGoTo >= 0) { // selection scrolled out of view
            dayOfWeekToGoTo = _lastDayOfWeekToGoTo;
            index = direction > 0 ? 0 : _tourFocus.size();
        }

        if (dayOfWeekToGoTo >= 0) {
            index += direction;
            for (int i = index; i >= 0 && i < _tourFocus.size(); i += direction) {
                final ObjectLocation ol = _tourFocus.get(i);
                if (dayOfWeekToGoTo == ((CalendarTourData) (ol.o)).dayOfWeek) {
                    _selectedItem = new Selection(ol.id, SelectionType.TOUR);
                    _lastDayOfWeekToGoTo = dayOfWeekToGoTo;
                    redraw();
                    return;
                }
            }
        } else {
            // selected Item is not on the screen any more
            _selectedItem = _noItem;
        }

        if (direction < 0) {
            gotoPrevWeek();
        } else {
            gotoNextWeek();
        }

    }

    public void gotoYear(final int year) {

        _dt = _dt.withYear(year);
        // scroll to first day of the week
        _dt = _dt.withDayOfWeek(getFirstDayOfWeek());

        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    boolean isTinyLayout() {
        return _tinyLayout;
    }

    /**
     * Mouse move event handler
     * 
     * @param event
     */
    private void onMouseMove(final MouseEvent event) {

        if (null == _image) {
            return;
        }
        if (_image.isDisposed()) {
            return;
        }

        if (_tourFocus == null) {
            return;
        }

        final Selection oldHighlight = _highlightedItem;
        final Selection oldSelection = _selectedItem;

        if (1 == event.button || 3 == event.button) {
            _selectedItem = _noItem;
        }

        boolean tourFound = false;
        boolean dayFound = false;
        long id;
        for (final ObjectLocation ol : _tourFocus) {
            if (ol.r.contains(event.x, event.y)) {
                id = ol.id;
                if (1 == event.button) {
                    //               if (oldSelection.id == ol.id) {
                    //                   _selectedItem = _noItem; // deselect if already selected
                    //               } else {
                    //                  _selectedItem = new Selection(id, SelectionType.TOUR);
                    //               }
                    _selectedItem = new Selection(id, SelectionType.TOUR);
                } else if (3 == event.button) {
                    _selectedItem = new Selection(id, SelectionType.TOUR);
                } else if (oldHighlight.id != id) { // a new object is highlighted
                    _highlightedItem = new Selection(id, SelectionType.TOUR);
                }
                tourFound = true;
                break;
            }
        }

        if (!tourFound) {
            for (final ObjectLocation ol : _dayFocus) {
                if (ol.r.contains(event.x, event.y)) {
                    id = ol.id;
                    if (1 == event.button) {
                        //                  if (oldSelection.id == ol.id) {
                        //                      _selectedItem = _noItem; // deselect if already selected
                        //                  } else {
                        //                     _selectedItem = new Selection(id, SelectionType.DAY);
                        //                  }
                        _selectedItem = new Selection(id, SelectionType.DAY);
                    } else if (3 == event.button) {
                        _selectedItem = new Selection(id, SelectionType.DAY);
                    } else if (oldHighlight.id != id) { // a new object is highlighted
                        _highlightedItem = new Selection(id, SelectionType.DAY);
                    }
                    dayFound = true;
                    break;
                }
            }
        }

        if (!oldSelection.equals(_selectedItem)) { // highlight selection -> redraw calendar
            redraw();
            return;
        }

        if (!dayFound && !tourFound) { // only draw base calendar, skip highlighting
            redraw();
            return;
        }

        _highlightChanged = (!oldHighlight.equals(_highlightedItem));
        if (_highlightChanged) { // only draw the highlighting on top of the calendar image
            redraw();
        }
        return;
    }

    private void onScroll(final SelectionEvent event) {

        final ScrollBar sb = _parent.getVerticalBar();

        final int selectableMax = sb.getMaximum() - sb.getThumb() - 1;
        final int selectableMin = 0 + 1;
        final int selection = sb.getSelection();
        final int change = selection - _scrollBarLastSelection;

        if (_scrollDebug) {
            System.out.println("Last Selection: " + _scrollBarLastSelection + " - New Selection: " + selection); //$NON-NLS-1$ //$NON-NLS-2$
        }

        if (_scrollBarShift != 0) {
            if (_scrollBarLastSelection == selectableMax) {
                sb.setSelection(selectableMax);
            } else if (_scrollBarLastSelection == selectableMin) {
                sb.setSelection(selectableMin);
            }
            if (change > 0 && _scrollBarShift < 0) { // ensure we are not shifting over "0"
                _scrollBarShift = Math.min(0, _scrollBarShift + change);
            } else if (change < 0 && _scrollBarShift > 0) {
                _scrollBarShift = Math.max(0, _scrollBarShift + change);
            } else {
                _scrollBarShift += change;
            }
        } else { // do we need start shifting the scroll bar ?
            if (selection < selectableMin) { // we are at the upper border
                sb.setSelection(selectableMin);
                _scrollBarShift += change;
            }
            if (selection > selectableMax) { // we are at the lower border
                sb.setSelection(selectableMax);
                _scrollBarShift += change;

            }
        }

        // selection = sb.getSelection();
        // final DateTime dt1 = scrollBarStart();
        // final DateTime dt2 = scrollBarEnd();
        // int weeks = (int) ((dt2.getMillis() - dt1.getMillis()) / (1000 * 60 * 60 * 24 * 7));
        // final int thumbSize = Math.max(getNumWeeks(), weeks / 20); // ensure the thumb isn't getting to small
        // sb.setThumb(thumbSize);
        // weeks += thumbSize;
        // sb.setMinimum(0);
        // sb.setMaximum(weeks);
        // sb.setPageIncrement(getNumWeeks());

        if (_scrollDebug) {
            System.out.println("SbarStart: " + scrollBarStart().getWeekOfWeekyear()); //$NON-NLS-1$
            System.out.println("SbarShift: " + _scrollBarShift + " - Selected Week: " + selection); //$NON-NLS-1$ //$NON-NLS-2$
        }

        _scrollBarLastSelection = sb.getSelection();

        // goto the selected week
        _dt = scrollBarStart().plusDays(selection * 7);

        _graphClean = false;
        redraw();
    }

    public void refreshCalendar() {
        _dataProvider.invalidate();
        _graphClean = false;
        redraw();
    }

    public void removeSelection() {
        if (!_selectedItem.equals(_noItem)) {
            _selectedItem = _noItem;
            _graphClean = false;
            redraw();
        }
    }

    void removeSelectionListener(final ICalendarSelectionProvider listener) {
        _selectionProvider.remove(listener);
    }

    private DateTime scrollBarEnd() {
        final DateTime dt = new DateTime().plusWeeks(_scrollBarShift);

        // ensure the date return is a "FirstDayOfTheWeek" !!!
        return dt.plusWeeks(1).withDayOfWeek(getFirstDayOfWeek());
    }

    private DateTime scrollBarStart() {
        // DateTime dt = _dataProvider.getFirstDateTime().plusWeeks(_scrollBarShift);
        DateTime dt = _dataProvider.getFirstDateTime();
        final DateTime now = new DateTime();
        final int weeks = (int) ((now.getMillis() - dt.getMillis()) / _WEEK_MILLIS);
        if (weeks < _MIN_SCROLLABLE_WEEKS) { // ensure the scrollable area has a reasonable size
            dt = now.minusWeeks(_MIN_SCROLLABLE_WEEKS);
        }
        dt = dt.plusWeeks(_scrollBarShift);
        // ensure the date return is a "FirstDayOfTheWeek" !!!
        return dt.minusWeeks(1).withDayOfWeek(getFirstDayOfWeek());
    }

    private void scrollBarUpdate() {
        _scrollBarShift = 0;
        final ScrollBar sb = _parent.getVerticalBar();
        final long dt1 = scrollBarStart().getMillis();
        final long dt2 = _dt.getMillis();
        final long dt3 = scrollBarEnd().getMillis();
        int maxWeeks = (int) ((dt3 - dt1) / _WEEK_MILLIS);
        final int thumbSize = Math.max(getNumOfWeeks(), maxWeeks / 20); // ensure the thumb isn't getting to small
        sb.setThumb(thumbSize);
        int thisWeek;
        if (dt2 < dt1) {
            // shift negative
            _scrollBarShift = (int) ((dt2 - dt1) / _WEEK_MILLIS);
            thisWeek = 1;
        } else if (dt2 > dt3) {
            // shift positive
            _scrollBarShift = (int) ((dt2 - dt3) / _WEEK_MILLIS);
            thisWeek = maxWeeks - 1;
        } else {
            thisWeek = (int) ((dt2 - dt1) / _WEEK_MILLIS);
        }
        maxWeeks += thumbSize;
        sb.setMinimum(0);
        sb.setMaximum(maxWeeks);
        sb.setPageIncrement(getNumOfWeeks());
        sb.setSelection(thisWeek);
        _scrollBarLastSelection = thisWeek;
    }

    public void setFirstDay(final DateTime dt) {

        _dt = dt;
        _dt_normal = dt;
        _dt_tiny = dt;
        //      _graphClean = false;
        //      redraw();
        //      scrollBarUpdate();
    }

    void setLinked(final boolean linked) {
        if (false == linked) {
            _selectedItem = _noItem;
            _graphClean = false;
            redraw();
        }
    }

    //   void setNavigationStyle(final NavigationStyle navigationStyle) {
    //      _navigationStyle = navigationStyle;
    //   }

    void setNumberOfToursPerDay(final int numberOfToursPerDay) {
        _numberOfToursPerDay = numberOfToursPerDay;
        _graphClean = false;
        redraw();
    }

    void setNumOfWeeks(final int numberOfWeeks) {
        if (_tinyLayout) {
            _numWeeksTinyView = numberOfWeeks;
        } else {
            _numWeeksNormalView = numberOfWeeks;
        }
    }

    void setNumWeeksNormalLayout(final int numberOfWeeksDisplayed) {
        _numWeeksNormalView = numberOfWeeksDisplayed;
    }

    void setNumWeeksTinyLayout(final int numberOfWeeksDisplayed) {
        _numWeeksTinyView = numberOfWeeksDisplayed;
    }

    public void setSelectionTourId(final Long selectedTourId) {
        this._selectedItem = new Selection(selectedTourId, SelectionType.TOUR);
    }

    public void setShowDayNumberInTinyView(final boolean checked) {
        _showDayNumberInTinyView = checked;
        _graphClean = false;
        redraw();
    }

    void setTourFieldSizeDynamic(final boolean dynamicTourFieldSize) {
        _graphClean = false;
        _dynamicTourFieldSize = dynamicTourFieldSize;
        redraw();
    }

    public void setTourInfoFormatter(final int line, final TourInfoFormatter formatter) {
        _tourInfoFormatter[line] = formatter;
        _graphClean = false;
        redraw();
    }

    public void setTourInfoUseHighlightTextBlack(final boolean checked) {
        _useBlackForHighlightTourInfoText = checked;
        // as this is only affecting the highlightning no redraw should be necessary
        // _graphClean = false;
        // redraw();
    }

    public void setTourInfoUseLineColor(final boolean checked) {
        _useTextColorForTourInfoText = checked;
        _graphClean = false;
        redraw();
    };

    public void setWeekSummaryFormatter(final int line, final WeekSummaryFormatter formatter) {
        _weekSummaryFormatter[line] = formatter;
        _graphClean = false;
        redraw();
    }

    public void setYearMonthContributor(final CalendarYearMonthContributionItem calendarYearMonthContribuor) {
        _calendarYearMonthContributor = calendarYearMonthContribuor;

    }

    void updateTourTypeColors() {

        _rgbBright.clear();
        _rgbDark.clear();
        _rgbLine.clear();
        _rgbText.clear();

        // default colors for no tour type
        _rgbBright.add(_white.getRGB());
        _rgbDark.add(_gray.getRGB());
        _rgbLine.add(_darkGray.getRGB());
        _rgbText.add(_darkGray.getRGB());

        /*
         * color index 1...n+1: tour type colors
         */
        final ArrayList<TourType> tourTypes = TourDatabase.getAllTourTypes();
        for (final TourType tourType : tourTypes) {
            _rgbBright.add(tourType.getRGBBright());
            _rgbDark.add(tourType.getRGBDark());
            _rgbLine.add(tourType.getRGBLine());
            _rgbText.add(tourType.getRGBText());
        }
    }

    void zoomIn() {
        int numWeeksDisplayed = getNumOfWeeks();
        setNumOfWeeks(numWeeksDisplayed > 1 ? --numWeeksDisplayed : numWeeksDisplayed);
        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

    void zoomOut() {
        setNumOfWeeks(getNumOfWeeks() + 1);
        _graphClean = false;
        redraw();
        scrollBarUpdate();
    }

}