calendarui/views/dayview/src/calendayinfo.cpp
author hgs
Mon, 28 Jun 2010 15:22:02 +0530
changeset 45 b6db4fd4947b
child 70 a5ed90760192
permissions -rw-r--r--
201025

/*
* Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  Storage class for day and week views.
*
*/


//  INCLUDES
#include "calendayinfo.h"
#include "calenagendautils.h"
#include "calenconstants.h"
#include "calendateutils.h"

namespace
{
const int KUntimedDefaultSlot(16);
}

/*!
 \brief CalenDayInfo::CalenDayInfo
*/
CalenDayInfo::CalenDayInfo(TSlotsInHour aSlotsInHour) :
    iFirstUntimedSlot(KUntimedDefaultSlot), iSlotsInHour(aSlotsInHour)
{
    iSelectedSlot = KFSCalStartingHour * iSlotsInHour;
    iSelectedRegion = KErrNotFound;
    iSelectedColumn = 0;
    iSelectedAlldayEvent = KErrNotFound;
    iEarliestEndSlot = KErrNotFound;
    iLastStartSlot = KErrNotFound;
}

/*!
 \brief Destructor
*/
CalenDayInfo::~CalenDayInfo()
{
    for (int i = 0; i < iRegionList.count(); i++) {
        iRegionList[i].Close();
    }
    iRegionList.clear();
    iUntimedEvents.clear();//Close();
    iTodoEvents.clear();//Close();
    iAlldayEvents.clear();//Close();
}

/*!
 \brief CalenDayInfo::Reset
*/
void CalenDayInfo::Reset()
{
    for (int i = 0; i < iRegionList.count(); i++) {
        iRegionList[i].Close();
    }
    iRegionList.clear();//Reset();
    iUntimedEvents.clear();//Reset();
    iTodoEvents.clear();//Reset();
    iAlldayEvents.clear();//Reset();

    iUntimedSlotCount = 0;
    iFirstUntimedSlot = KFSCalStartingHour * iSlotsInHour;

    iSelectedSlot = KFSCalStartingHour * iSlotsInHour;
    iSelectedRegion = KErrNotFound;
    iSelectedColumn = 0;
    iSelectedAlldayEvent = KErrNotFound;
    iEarliestEndSlot = KErrNotFound;
    iLastStartSlot = KErrNotFound;
}

/*!
 \brief CalenDayInfo::InsertTimedEvent
*/
void CalenDayInfo::InsertTimedEvent(const SCalenApptInfo& aItemInfo)
{

    int startIndex = SlotIndexForStartTime(aItemInfo.iStartTime);
    int endIndex = SlotIndexForEndTime(aItemInfo.iEndTime);
    if (endIndex == startIndex) {
        endIndex++;
    }
    if (iRegionList.count() > 0) {
        // the timed events must be added in order
        ASSERT( startIndex >= iLastStartSlot );
    }

    CalenTimedEventInfo info;
    info.iStartSlot = startIndex;
    info.iEndSlot = endIndex;
    info.iId = aItemInfo.iId;
    info.iStatus = aItemInfo.iStatus;
    //    info.iReplicationStatus = aItemInfo.iReplicationStatus;
    iLastStartSlot = startIndex;
    if (iEarliestEndSlot == KErrNotFound || endIndex < iEarliestEndSlot) {
        iEarliestEndSlot = endIndex;
    }

    bool added(false);
    if (iRegionList.count() > 0) {
        // Since events are added in order, the event either overlaps the
        // last region or doesn't overlap any region at all
        CalenTimeRegion& region = iRegionList[iRegionList.count() - 1];
        if (region.Overlaps(info)) {
            region.AddEvent(info);
            added = true;
        }
    }

    if (!added) {
        // If it didn't overlap the last region, add a new region for this event.
        iRegionList.append(CalenTimeRegion());
        CalenTimeRegion& region = iRegionList[iRegionList.count() - 1];
        region.AddEvent(info);

        // Do rounding of the region ends
        if (iRegionList.count() >= 2) {
            // Check if the previous region end time can be rounded down
            CalenTimeRegion& prevRegion = iRegionList[iRegionList.count() - 2];
            int end = RoundHourDown(prevRegion.iEndSlot);
            if (end <= region.iStartSlot) {
                prevRegion.iEndSlot = end;
            }
            // Check if the start time of the new region can be rounded up
            int start = RoundHourUp(region.iStartSlot);
            if (start >= prevRegion.iEndSlot) {
                region.iStartSlot = start;
            }
        }
        else {
            // No previous region, round the start time up
            region.iStartSlot = RoundHourUp(region.iStartSlot);
        }
    }
}

/*!
 \brief CalenDayInfo::InsertAlldayEvent
*/
void CalenDayInfo::InsertAlldayEvent(const SCalenApptInfo& aItemInfo)
{
    CalenTimedEventInfo info;
    info.iStartSlot = 0;
    info.iEndSlot = iSlotsInHour * KCalenHoursInDay + iUntimedSlotCount;
    info.iId = aItemInfo.iId;
    info.iStatus = aItemInfo.iStatus;
    //    info.iReplicationStatus = aItemInfo.iReplicationStatus;

    iAlldayEvents.append(info);
}

/*!
 \brief CalenDayInfo::IsAllDayEvent
*/
bool CalenDayInfo::IsAlldayEvent(QDateTime aStart, QDateTime aEnd)
{
    bool isAllDay(false);

    if (!CalenDateUtils::onSameDay(aStart, aEnd)) {
        QDateTime startMidnight = CalenDateUtils::beginningOfDay(aStart);
        QDateTime endMidnight = CalenDateUtils::beginningOfDay(aEnd);

        if ((aStart == startMidnight) && (aEnd == endMidnight)) {
            isAllDay = true;
        }
    }
    return isAllDay;
}

/*!
 \brief CalenDayInfo::IsAllDayEvent
 Allday event is an event starting and ending at midnight,
 with a duration of n*24h.
*/
/*
bool CalenDayInfo::IsAlldayEvent(const CCalInstance& aInstance)
{
    bool isAllDay(false);

    // If getting the start or end time fails, return false.
    QDateTime start;
    QDateTime end;
    TRAPD( error,
        {
            start = aInstance.StartTimeL().TimeLocalL();
            end = aInstance.EndTimeL().TimeLocalL();
        });

    if (error == KErrNone && !CalenDateUtils::onSameDay(start, end)) {
        QDateTime startMidnight = CalenDateUtils::beginningOfDay(start);
        QDateTime endMidnight = CalenDateUtils::beginningOfDay(end);

        if ((start == startMidnight) && (end == endMidnight)) {
            isAllDay = true;
        }
    }

    if (error != KErrNone) {
        CEikonEnv::Static()->HandleError(error);//codescanner::eikonenvstatic
    }

    return isAllDay;
}
*/

/*!
 \brief CalenDayInfo::AlldayCount
*/
int CalenDayInfo::AlldayCount()
{
    return iAlldayEvents.count();
}

/*!
 \brief CalenDayInfo::TodoCount
*/
int CalenDayInfo::TodoCount()
{
    return iTodoEvents.count();
}

/*!
 \brief CalenDayInfo::GetLocation
*/
void CalenDayInfo::GetLocation(
    const SCalenApptInfo& aItemInfo,
    int& aStartSlot,
    int& aEndSlot,
    int& aColumnIndex,
    int& aColumns)
{
    int startIndex = SlotIndexForStartTime(aItemInfo.iStartTime);
    int endIndex = SlotIndexForEndTime(aItemInfo.iEndTime);
    if (endIndex == startIndex) {
        endIndex++;
    }

    aStartSlot = startIndex;
    aEndSlot = endIndex;

    CalenSlotInterval interval;
    interval.iStartSlot = startIndex;
    interval.iEndSlot = endIndex;

    bool found(false);
    // Scan through all allday events and see if it matches any of them
    for (int i = 0; i < iAlldayEvents.count() && !found; i++) {
        if (aItemInfo.iId == iAlldayEvents[i].iId) {
            aColumns = iAlldayEvents.count();
            aColumnIndex = i;
            found = true;
        }
    }

    // Scan through all regions and see if the event overlaps any region.
    // If it overlaps a region, the event must be in that region.
    for (int i = 0; i < iRegionList.count() && !found; i++) {
        CalenTimeRegion& region = iRegionList[i];
        if (region.Overlaps(interval)) {
            // Scan through all columns and look for the event
            aColumns = region.iColumns.count();
            for (int j = 0; j < aColumns && !found; j++) {
                if (region.iColumns[j].ContainsEvent(aItemInfo.iId)) {
                    aColumnIndex = j;
                    found = true;
                }
            }
            // the event should be in this region if it overlaps the region
            ASSERT( found );
        }
    }
    // the event should have been found somewhere
    ASSERT( found );
}

/*!
 \brief CalenDayInfo::GetSelectedSlot
*/
void CalenDayInfo::GetSelectedSlot(int& aSlot, int& aRegion, int& aColumnIndex, int& aColumns)
{

    aSlot = iSelectedSlot;
    aColumnIndex = iSelectedColumn;
    if (iSelectedRegion >= 0) {
        aColumns = iRegionList[iSelectedRegion].iColumns.count();
        aRegion = iSelectedRegion;
    }
    else {
        aColumns = 0;
        aRegion = KErrNotFound;
    }
}

/*!
 \brief CalenDayInfo::FindRegion
*/
int CalenDayInfo::FindRegion(const CalenSlotInterval& aInterval, int aDirection)
{
    // Setup the start and end region indices for iterating
    int start, end;
    if (aDirection > 0) {
        start = 0;
        end = iRegionList.count();
    }
    else {
        start = iRegionList.count() - 1;
        end = -1;
    }

    int region = KErrNotFound;

    // Iterate over the regions, in the order given, looking for a region
    // overlapping this interval
    for (int i = start; i != end && region < 0; i += aDirection) {
        if (iRegionList[i].Overlaps(aInterval)) {
            region = i;
        }
    }
    return region;
}

/*!
 \brief Find an event within the given interval of the current column,
 searching in the given direction.
*/
int CalenDayInfo::FindEvent(const CalenSlotInterval& aInterval, int aDirection)
{
    ASSERT( iSelectedRegion >= 0 && iSelectedRegion < iRegionList.count() );

    CalenTimeRegion& region = iRegionList[iSelectedRegion];
    int index = KErrNotFound;

    if (iSelectedColumn < region.iColumns.count()) {
        CalenTimeColumn& column = region.iColumns[iSelectedColumn];
        int start, end;
        if (aDirection > 0) {
            start = 0;
            end = column.iEventArray.count();
        }
        else {
            start = column.iEventArray.count() - 1;
            end = -1;
        }

        // Iterate over the events in the chosen column in the order given,
        // looking for an event overlapping this interval
        for (int i = start; i != end && index < 0; i += aDirection) {
            if (column.iEventArray[i].Overlaps(aInterval)) {
                index = i;
            }
        }
    }
    return index;
}

/*!
 \brief CalenDayInfo::IsEventSelected
*/
bool CalenDayInfo::IsEventSelected() const
{
    bool selected(false);
    if (iSelectedAlldayEvent >= 0) {
        selected = true;
    }
    else
        if (iSelectedRegion >= 0 && iSelectedColumn < iRegionList[iSelectedRegion].iColumns.count()
            && iSelectedColumnEventIndex >= 0) {
            selected = true;
        }
        else
            if (IsExtraSlot(iSelectedSlot) && iSelectedSlot - iFirstUntimedSlot
                >= iEmptyUntimedSlots) {
                selected = true;
            }
            else {
                // if no event is selected, the selection slot must be a hour starting slot or an extra slot
                ASSERT( IsHourStartSlot( iSelectedSlot ) || IsExtraSlot( iSelectedSlot ) );
                selected = false;
            }
    return selected;
}

/*!
 \brief CalenDayInfo::IsMultipleEventsSelected
*/
bool CalenDayInfo::IsMultipleEventsSelected() const
{
    bool isMultiple(false);
    if (iSelectedAlldayEvent >= 0) {
        // If an allday event is selected, disregard the value of
        // iSelectedSlot
        isMultiple = false;
    }
    else
        if (IsExtraSlot(iSelectedSlot)) {
            int index = iSelectedSlot - iFirstUntimedSlot - iEmptyUntimedSlots;
            isMultiple = (index == 0) && iTodoEvents.count() > 1;
        }
    return isMultiple;
}

/*!
 \brief CalenDayInfo::IsAlldayEventSelected
*/
bool CalenDayInfo::IsAlldayEventSelected() const
{
    bool selected(false);
    if (iSelectedAlldayEvent >= 0) {
        ASSERT( iSelectedAlldayEvent < iAlldayEvents.count() );
        selected = true;
    }
    return selected;
}

/*!
 \brief CalenDayInfo::UntimedEvent
*/
TCalenInstanceId CalenDayInfo::UntimedEvent(int aIndex)
{
    TCalenInstanceId id;
    if (iTodoEvents.count() > 0 && aIndex == 0) {
        id = iTodoEvents[0];
    }
    else {
        // If we have one or more todo items, the first slot is used for them
        // (but not more than one slot)
        if (iTodoEvents.count() > 0) {
            aIndex--;
        }

        if (aIndex >= 0 && aIndex < iUntimedEvents.count()) {
            id = iUntimedEvents[aIndex];
        }
        else {
            // should not happen
            User::Invariant();
        }
    }

    return id;
}

/*!
 \brief CalenDayInfo::AlldayEvent
*/
const CalenTimedEventInfo& CalenDayInfo::AlldayEvent(int aIndex)
{
    ASSERT( aIndex >= 0 && aIndex < iAlldayEvents.count() );
    return iAlldayEvents[aIndex];
}

/*!
 \brief CalenDayInfo::SelectedEvent
*/
TCalenInstanceId CalenDayInfo::SelectedEvent()
{
    TCalenInstanceId id;
    if (iSelectedAlldayEvent >= 0) {
        ASSERT( iSelectedAlldayEvent < iAlldayEvents.count() );
        id = iAlldayEvents[iSelectedAlldayEvent].iId;
    }
    else
        if (iSelectedRegion >= 0) {
            ASSERT( iSelectedRegion < iRegionList.count() );

            CalenTimeRegion& region = iRegionList[iSelectedRegion];
            if (iSelectedColumn < region.iColumns.count()) {
                CalenTimeColumn& column = region.iColumns[iSelectedColumn];
                if (iSelectedColumnEventIndex >= 0 && iSelectedColumnEventIndex
                    < column.iEventArray.count()) {
                    CalenTimedEventInfo& info = column.iEventArray[iSelectedColumnEventIndex];
                    id = info.iId;
                }
                else {
                    // should not happen, no event selected in the column
                    User::Invariant();
                }
            }
            else {
                // should not happen, the empty column was selected
                User::Invariant();
            }
        }
        else
            if (IsExtraSlot(iSelectedSlot)) {
                int index = iSelectedSlot - iFirstUntimedSlot - iEmptyUntimedSlots;
                id = UntimedEvent(index);
            }
            else {
                // should not happen
                User::Invariant();
            }

    return id;
}

/*!
 \brief CalenDayInfo::SelectEvent
 Return KErrNotFound if not found, otherwise KErrNone
*/
int CalenDayInfo::SelectEvent(const TCalenInstanceId& aId)
{
    // Id can be of allday event, untimed event or other events

    // Look for this event id in all event data structures and set
    // the state accordingly if it was found

    for (int i(0); i < iAlldayEvents.count(); i++) {
        TCalenInstanceId id = iAlldayEvents[i].iId;
        if (id == aId) {
            iSelectedAlldayEvent = i;
            iSelectedRegion = KErrNotFound;
            return KErrNone;
        }
    }

    for (int i(0); i < iTodoEvents.count(); i++) {
        TCalenInstanceId id = iTodoEvents[i];
        if (id == aId) {
            iSelectedAlldayEvent = KErrNotFound;
            iSelectedRegion = KErrNotFound;
            iSelectedSlot = iFirstUntimedSlot + iEmptyUntimedSlots;
            return KErrNone;
        }
    }

    for (int i(0); i < iUntimedEvents.count(); i++) {
        TCalenInstanceId id = iUntimedEvents[i];
        if (id == aId) {
            iSelectedAlldayEvent = KErrNotFound;
            iSelectedRegion = KErrNotFound;
            iSelectedSlot = iFirstUntimedSlot + iEmptyUntimedSlots + i;
            if (iTodoEvents.count() > 0) {
                iSelectedSlot++;
            }
            return KErrNone;
        }
    }

    for (int i(0); i < iRegionList.count(); i++) {
        CalenTimeRegion& region = iRegionList[i];
        for (int col(0); col < region.iColumns.count(); col++) {
            for (int ind(0); ind < region.iColumns[col].iEventArray.count(); ind++) {
                if (region.iColumns[col].iEventArray[ind].iId == aId) {
                    iSelectedAlldayEvent = KErrNotFound;
                    iSelectedRegion = i;
                    iSelectedColumn = col;
                    iSelectedColumnEventIndex = ind;
                    SetSelectionInEvent(EMoveDirectionDown);
                    return KErrNone;
                }
            }
        }
    }

    return KErrNotFound;
}

/*!
 \brief CalenDayInfo::EnterRegionFromBelow
*/
void CalenDayInfo::EnterRegionFromBelow()
{
    ASSERT( iSelectedRegion >= 0 && iSelectedRegion < iRegionList.count() );
    iSelectedColumn = KErrNotFound;
    CalenTimeRegion& region = iRegionList[iSelectedRegion];
    int latestEnd = region.iStartSlot;
    // Look for the column with the latest end
    for (int i = 0; i < region.iColumns.count(); i++) {
        CalenTimeColumn& column = region.iColumns[i];
        ASSERT( column.iEventArray.count()> 0 );
        if (column.iEndSlot > latestEnd) {
            // found a column with an event touching the end of the region
            latestEnd = column.iEndSlot;
            iSelectedColumn = i;
            iSelectedColumnEventIndex = column.iEventArray.count() - 1;
            SetSelectionInEvent(EMoveDirectionUp);
        }
    }
    ASSERT( iSelectedColumn >= 0 );
}

/*!
 \brief CalenDayInfo::EnterRegionFromAbove
*/
void CalenDayInfo::EnterRegionFromAbove()
{
    ASSERT( iSelectedRegion >= 0 && iSelectedRegion < iRegionList.count() );
    iSelectedColumn = KErrNotFound;
    CalenTimeRegion& region = iRegionList[iSelectedRegion];
    int earliestStart = region.iEndSlot;
    // Look for the column with the earliest start
    for (int i = 0; i < region.iColumns.count(); i++) {
        CalenTimeColumn& column = region.iColumns[i];
        ASSERT( column.iEventArray.count()> 0 );
        if (column.iStartSlot < earliestStart) {
            // found a column with an event
            earliestStart = column.iStartSlot;
            iSelectedColumn = i;
            iSelectedColumnEventIndex = 0;
            SetSelectionInEvent(EMoveDirectionDown);
        }
    }
    ASSERT( iSelectedColumn >= 0 );
}

/*!
 \brief CalenDayInfo::NextFocusArea
 Determines how large area to scan for new events/regions when moving in
 the given direction.  If no events are found in this area, focus an
 empty slot instead
*/
CalenSlotInterval CalenDayInfo::NextFocusArea(const CalenSlotInterval& aInterval, int aDirection)
{
    // NOTE: the comments are written with the visual layout in mind.
    // upwards == up in the display, towards earlier times
    // downwards == down in the display, towards later times
    CalenSlotInterval target;
    target.iEndSlot = 0;
    target.iStartSlot = 0;

    if (IsExtraSlot(aInterval.iStartSlot)) {
        // Special case logic for moving within the extra slots.

        // The index within the extra slot area
        int extraIndex = aInterval.iStartSlot - iFirstUntimedSlot;
        // Generally, move just one slot at a time in this area
        int newIndex = extraIndex + aDirection;

        if (newIndex < iEmptyUntimedSlots) {
            // Moved upwards past the first slot, return
            // the whole last hour before the untimed slot area
            target.iEndSlot = iFirstUntimedSlot;
            target.iStartSlot = target.iEndSlot - iSlotsInHour;
        }
        else
            if (newIndex >= iUntimedSlotCount) {
                // Moved downwards past the last untimed slot, return
                // the whole first hour after the untimed slot area
                target.iStartSlot = iFirstUntimedSlot + iUntimedSlotCount;
                target.iEndSlot = target.iStartSlot + iSlotsInHour;
            }
            else {
                // Moved normally within the untimed slot area
                target.iStartSlot = aInterval.iStartSlot + aDirection;
                target.iEndSlot = target.iStartSlot + 1;
            }
        return target;
    }

    // Helper interval for the whole untimed area
    CalenSlotInterval untimedInterval;
    untimedInterval.iStartSlot = iFirstUntimedSlot;
    untimedInterval.iEndSlot = untimedInterval.iStartSlot + iUntimedSlotCount;

    if (aDirection < 0) {
        // Moving upwards
        // Create a target interval of one hour upwards
        target.iEndSlot = aInterval.iStartSlot;
        target.iStartSlot = aInterval.iStartSlot - iSlotsInHour;

        if (iUntimedSlotCount > 0 && untimedInterval.Overlaps(target)) {
            // The target interval overlaps the untimed area
            if (iUntimedSlotCount > iEmptyUntimedSlots) {
                // Make the interval start at the last untimed slot
                target.iStartSlot = untimedInterval.iEndSlot - 1;
            }
            else {
                // Untimed area containing no actual events.
                // Include one whole hour before the untimed area.
                target.iStartSlot = iFirstUntimedSlot - iSlotsInHour;
            }
            return target;
        }

        // Round the start slot upwards to an even hour
        target.iStartSlot = RoundHourUp(target.iStartSlot);
    }
    else {
        // Moving downwards
        // Create a target interval of one hour downwards
        target.iStartSlot = aInterval.iEndSlot;
        target.iEndSlot = aInterval.iEndSlot + iSlotsInHour;

        if (iUntimedSlotCount > 0 && untimedInterval.Overlaps(target)) {
            // The target interval overlaps the untimed area

            // Assumption: There can't be any regions before the untimed area
            if (iUntimedSlotCount > iEmptyUntimedSlots) {
                // Make the interval end at the first untimed slot containing
                // an event
                target.iStartSlot = untimedInterval.iStartSlot + iEmptyUntimedSlots;
                target.iEndSlot = target.iStartSlot + 1;
            }
            else {
                // Untimed area containing no actual events.
                // Include one whole hour after the untimed area.
                target.iStartSlot = untimedInterval.iStartSlot + iUntimedSlotCount;
                target.iEndSlot = target.iStartSlot + iSlotsInHour;
            }
            return target;
        }

        target.iEndSlot = RoundHourDown(target.iEndSlot);
    }

    return target;
}

/*!
 \brief CalenDayInfo::NextEmptyFocusSlot
 If no events are found in an empty area scanned, focus this slot instead
*/
int CalenDayInfo::NextEmptyFocusSlot(const CalenSlotInterval& aInterval, int aDirection)
{
    // NOTE: the comments are written with the visual layout in mind.
    // upwards == up in the display, towards earlier times
    // downwards == down in the display, towards later times

    int target(0);

    // If the next focus area logic says we should go to an untimed slot,
    // skip the rest of this method
    CalenSlotInterval testInterval = NextFocusArea(aInterval, aDirection);
    if (IsExtraSlot(testInterval.iStartSlot)) {
        target = testInterval.iStartSlot;
    }
    else {
        if (aDirection < 0) {
            // Moving upwards, target slot is the start of the area returned by
            // NextFocusArea. This always is an even hour.
            target = testInterval.iStartSlot;
            ASSERT( IsHourStartSlot( target ) );
        }
        else {
            target = testInterval.iStartSlot;
            if (!IsHourStartSlot(target)) {
                // If the interval doesn't start on an even hour, round downwards.
                target = RoundHourDown(target);
            }
        }
    }

    return target;
}

/*!
 \brief CalenDayInfo::EmptySelectionInterval
 Create an interval representation of the current focus area
*/
CalenSlotInterval CalenDayInfo::EmptySelectionInterval()
{
    CalenSlotInterval target;
    target.iEndSlot = 0;
    target.iStartSlot = 0;

    if (IsExtraSlot(iSelectedSlot)) {
        // Return an interval consisting of one single slot
        target.iStartSlot = iSelectedSlot;
        target.iEndSlot = target.iStartSlot + 1;
    }
    else {
        // Round the start slot to an even hour if it isn't.
        // NOTE: This actually updates the selection state!
        // Only call this method if no event actually is selected.
        if (!IsHourStartSlot(iSelectedSlot)) {
            iSelectedSlot = RoundHourUp(iSelectedSlot);
        }
        target.iStartSlot = iSelectedSlot;
        target.iEndSlot = iSelectedSlot + iSlotsInHour;
    }

    return target;
}

/*!
 \brief CalenDayInfo::SelectedInterval
*/
CalenSlotInterval CalenDayInfo::SelectedInterval()
{
    CalenSlotInterval selection;
    selection.iStartSlot = selection.iEndSlot = 0;

    if (iSelectedAlldayEvent >= 0) {
        selection.iStartSlot = 0;
        selection.iEndSlot = iSlotsInHour * KCalenHoursInDay + iUntimedSlotCount;
    }
    else
        if (iSelectedRegion >= 0) {

            CalenTimeRegion& region = iRegionList[iSelectedRegion];
            if (iSelectedColumn < region.iColumns.count()) {
                CalenTimeColumn& column = region.iColumns[iSelectedColumn];
                if (iSelectedColumnEventIndex >= 0 && iSelectedColumnEventIndex
                    < column.iEventArray.count()) {
                    CalenTimedEventInfo& info = column.iEventArray[iSelectedColumnEventIndex];
                    // gets only the CalenSlotInterval part from the CalenTimedEventInfo struct
                    selection = info;
                }
            }
        }

    if (selection.IsEmpty()) {
        // No selection was set in the cases above, no event is selected, thus
        // use the empty area selection interval instead.
        selection = EmptySelectionInterval();
    }
    return selection;
}

/*!
 \brief CalenDayInfo::SetSelectionInRegion
 Sets selection within a region
*/
bool CalenDayInfo::SetSelectionInRegion(int aRegion, int aColumn, int aSlot)
{
    bool selected(false);
    StoreOrigSelection();
    //validate given values
    if (aRegion >= 0 && aRegion < iRegionList.count()) {//ASSERT( iSelectedRegion >= 0 && iSelectedRegion < iRegionList.count() );
        CalenSlotInterval interval;
        //TODO: round aSlot hour to full hour down????
        interval.iStartSlot = aSlot;
        interval.iEndSlot = interval.iStartSlot + 1;

        CalenTimeRegion& region = iRegionList[aRegion];
        if (region.Overlaps(interval)) {
            if (aColumn >= 0 && aColumn < region.iColumns.count()) {
                bool eventFound(false);
                CalenTimeColumn& column = region.iColumns[aColumn];
                if (column.Overlaps(interval)) {
                    ASSERT( column.iEventArray.count()> 0 );
                    for (int i = 0; i < column.iEventArray.count(); i++) {
                        CalenTimedEventInfo& event = column.iEventArray[i];
                        if (event.Overlaps(interval)) {
                            iSelectedColumnEventIndex = i;

                            iSelectedAlldayEvent = KErrNotFound;
                            iSelectedRegion = aRegion;
                            iSelectedColumn = aColumn;
                            iSelectedSlot = aSlot;
                            selected = true;
                            eventFound = true;
                            break;
                        }

                    }
                }

                if (!eventFound) {
                    iSelectedAlldayEvent = KErrNotFound;
                    iSelectedRegion = aRegion;
                    iSelectedColumn = aColumn;
                    iSelectedSlot = aSlot;
                    iSelectedColumnEventIndex = KErrNotFound;
                    selected = true;
                }
            }
            else
                if (aColumn == iRegionList[aRegion].iColumns.count()) {//empty selection on right selected
                    iSelectedAlldayEvent = KErrNotFound;
                    iSelectedRegion = aRegion;
                    iSelectedColumn = aColumn;
                    iSelectedSlot = aSlot;
                    iSelectedColumnEventIndex = KErrNotFound;
                    selected = true;
                }
        }
    }

    return selected;
}

/*!
 \brief CalenDayInfo::MoveOutFromRegion
*/
void CalenDayInfo::MoveOutFromRegion(int aDirection)
{
    CalenTimeRegion& region = iRegionList[iSelectedRegion];
    CalenSlotInterval targetInterval = NextFocusArea(region, aDirection);
    int next = iSelectedRegion + aDirection;
    // Check what the next region would be and if we should move into that
    // or into an empty area
    if (next >= 0 && next < iRegionList.count()) {
        if (iRegionList[next].Overlaps(targetInterval)) {
            iSelectedRegion = next;
            if (aDirection < 0) {
                EnterRegionFromBelow();
            }
            else {
                EnterRegionFromAbove();
            }
        }
        else {
            iSelectedRegion = KErrNotFound;
            iSelectedSlot = NextEmptyFocusSlot(region, aDirection);
        }
    }
    else {
        iSelectedRegion = KErrNotFound;
        iSelectedSlot = NextEmptyFocusSlot(region, aDirection);
    }
}

/*!
 \brief CalenDayInfo::MoveInColumn
*/
void CalenDayInfo::MoveInColumn(int aDirection)
{
    CalenTimeRegion& region = iRegionList[iSelectedRegion];
    if (iSelectedColumn < region.iColumns.count()) {
        CalenTimeColumn& column = region.iColumns[iSelectedColumn];
        if (iSelectedColumnEventIndex >= 0) {
            // We currently have an event in a column selected
            CalenSlotInterval curInterval = column.iEventArray[iSelectedColumnEventIndex];
            CalenSlotInterval targetInterval = NextFocusArea(curInterval, aDirection);

            int next = iSelectedColumnEventIndex + aDirection;
            // Check if the target interval overlaps the next event in this column
            if (next >= 0 && next < column.iEventArray.count()) {
                if (column.iEventArray[next].Overlaps(targetInterval)) {
                    iSelectedColumnEventIndex = next;
                    SetSelectionInEvent(aDirection);
                }
                else {
                    iSelectedColumnEventIndex = KErrNotFound;
                    iSelectedSlot = NextEmptyFocusSlot(curInterval, aDirection);
                }
            }
            else {
                // There's no next event in this column, focus an empty area instead.
                iSelectedColumnEventIndex = KErrNotFound;
                iSelectedSlot = NextEmptyFocusSlot(curInterval, aDirection);
            }
        }
        else {
            // No event selected in the current column, find the target interval
            // and check if there's any event there
            CalenSlotInterval curInterval = EmptySelectionInterval();
            CalenSlotInterval targetInterval = NextFocusArea(curInterval, aDirection);
            iSelectedColumnEventIndex = FindEvent(targetInterval, aDirection);
            if (iSelectedColumnEventIndex >= 0) {
                SetSelectionInEvent(aDirection);
            }
            else {
                iSelectedSlot = NextEmptyFocusSlot(curInterval, aDirection);
            }
        }

        // If the new selection lies outside of the region, move out from the region.
        CalenSlotInterval selection = SelectedInterval();
        if (selection.iStartSlot < region.iStartSlot || selection.iEndSlot > region.iEndSlot) {
            MoveOutFromRegion(aDirection);
        }

    }
    else {
        // The empty column was selected, just move up/down in that column
        iSelectedSlot = NextEmptyFocusSlot(EmptySelectionInterval(), aDirection);

        // When moving in the empty column, move out from the region only if
        // the selection moved completely out from the region.
        CalenSlotInterval selection = SelectedInterval();
        if (!selection.Overlaps(region)) {
            // The target selection is completely outside of the current region
            int next = iSelectedRegion + aDirection;
            if (next >= 0 && next < iRegionList.count() && iRegionList[next].Overlaps(selection)) {
                // The target selection overlaps the next region, move into that,
                // stay within the rightmost empty column
                iSelectedRegion = next;
                iSelectedColumn = iRegionList[next].iColumns.count();
                iSelectedColumnEventIndex = KErrNotFound;
            }
            else {
                // Move out from the current region
                iSelectedRegion = KErrNotFound;
                iSelectedColumn = KErrNotFound;
                iSelectedColumnEventIndex = KErrNotFound;
                // iSelectedSlot was update above, keep the same value
            }
        }

    }

}

/*!
 \brief CalenDayInfo::MoveInAlldayEvent
*/
bool CalenDayInfo::MoveInAlldayEvent(TScrollDirection aDirection)
{
    ASSERT( iSelectedAlldayEvent >= 0 );
    bool moved(true);
    switch (aDirection) {
        case EScrollUp: {
            // don't do anything
        }
            break;
        case EScrollDown: {
            // don't do anything
        }
            break;
        case EScrollLeft: {
            if (iSelectedAlldayEvent > 0) {
                // there are more allday events to the left, select next
                iSelectedAlldayEvent--;
            }
            else {
                // move to previous day
                moved = false;
            }
        }
            break;
        case EScrollRight: {
            if (iSelectedAlldayEvent < iAlldayEvents.count() - 1) {
                // there are more allday events to the right, select next
                iSelectedAlldayEvent++;
            }
            else {
                iSelectedAlldayEvent = KErrNotFound;
                // find a region to select
                if (iSelectedRegion >= 0) {
                    // keep using the old selection state
                }
                else {
                    // find a new suitable selection

                    // if iSelectedSlot was moved to the empty untimed slot area,
                    // move it down to a real slot

                    if (iSelectedSlot >= iFirstUntimedSlot && iSelectedSlot < iFirstUntimedSlot
                        + iEmptyUntimedSlots) {
                        iSelectedSlot = iFirstUntimedSlot + iEmptyUntimedSlots;
                    }

                    CalenSlotInterval interval = EmptySelectionInterval();

                    iSelectedRegion = FindRegion(interval, EMoveDirectionDown);
                    int firstRegion = iSelectedRegion;
                    bool found(false);
                    // Iterate over all regions overlapping this interval, see
                    // if any of them has events in this column
                    while (iSelectedRegion >= 0 && iSelectedRegion < iRegionList.count()
                        && iRegionList[iSelectedRegion].Overlaps(interval) && !found) {

                        ASSERT( iSelectedRegion < iRegionList.count() );
                        CalenTimeRegion& region = iRegionList[iSelectedRegion];

                        ASSERT( region.iColumns.count()> 0 );
                        iSelectedColumn = 0;
                        CalenTimeColumn& column = region.iColumns[iSelectedColumn];

                        iSelectedColumnEventIndex = FindEvent(interval, EMoveDirectionDown);
                        if (iSelectedColumnEventIndex >= 0) {
                            ASSERT( iSelectedColumnEventIndex < column.iEventArray.count() );
                            // Keep using the old selected slot also here, just
                            // update it to make sure it's within the actual event
                            UpdateSelectionInEvent();
                            found = true;
                        }
                        else {
                            // Check the next region
                            iSelectedRegion++;
                        }
                    }

                    if (!found) {
                        iSelectedRegion = firstRegion;
                        iSelectedColumn = 0;
                        iSelectedSlot = interval.iStartSlot;
                    }

                }
            }
        }
            break;
        default:
            // Do nothing
            break;
    }

    return moved;
}

/*!
 \brief CalenDayInfo::MoveBetweenColumns
*/
void CalenDayInfo::MoveBetweenColumns(TScrollDirection aDirection)
{
    // Moving to another column
    CalenSlotInterval selection = SelectedInterval();
    if (IsEventSelected()) {
        // If moving from an event, just use the selected
        // slot pos within the event, not the whole event
        // interval.
        selection.iStartSlot = iSelectedSlot;
        selection.iEndSlot = selection.iStartSlot + iSlotsInHour;
        // Crop the selection to be within the event
        selection.Intersect(SelectedInterval());
    }

    switch (aDirection) {
        case EScrollLeft: {
            iSelectedColumn--;
            break;
        }
        case EScrollRight: {
            iSelectedColumn++;
            break;
        }
        default:
            // Do nothing
            break;
    }

    // First try finding events exactly in the selection area
    iSelectedColumnEventIndex = FindEvent(selection, EMoveDirectionDown);
    if (iSelectedColumnEventIndex >= 0) {
        // An event was found in the given interval
        // Keep using the old selection position, just update it to make
        // sure it's within the event
        UpdateSelectionInEvent();
    }
    else {
        // No event found in the interval

        // Expand the interval to even hour endings
        selection.iStartSlot = RoundHourUp(selection.iStartSlot);
        selection.iEndSlot = RoundHourDown(selection.iEndSlot);
        CalenTimeRegion& region = iRegionList[iSelectedRegion];
        // Crop the selection to the current region
        selection.Intersect(region);

        // Try again with the possibly larger interval
        iSelectedColumnEventIndex = FindEvent(selection, EMoveDirectionDown);
        if (iSelectedColumnEventIndex >= 0) {
            // An event was found in the given interval
            // Keep using the old selection position, just update it to make
            // sure it's within the event
            UpdateSelectionInEvent();
        }
        else {
            // Still no events found. Select an empty interval if possible.

            if (!IsHourStartSlot(selection.iStartSlot)) {
                // The starting slot isn't a hour start slot. Rounding directly
                // would move the interval outside of the region. See if we can
                // round the starting slot down instead and still be within
                // the region.
                int start = RoundHourDown(selection.iStartSlot);
                if (start + iSlotsInHour <= region.iEndSlot) {
                    // Ok, an empty selection fits within the region.
                    iSelectedSlot = start;
                }
            }
            else
                if (!IsHourStartSlot(selection.iEndSlot)) {
                    // The ending slot isn't a hour start slot. Rounding directly
                    // could move the interval outside of the region. See if we can
                    // round the upwards instead and still be within
                    // the region.
                    int start = RoundHourUp(selection.iEndSlot - iSlotsInHour);
                    if (start >= region.iStartSlot) {
                        // Ok, an empty selection fits within the region.
                        iSelectedSlot = start;
                    }
                }

            // Make sure the selected interval really is a valid
            // empty interval selection (this forces rounding if needed)
            selection = EmptySelectionInterval();

            // Check the final target interval once more.
            iSelectedColumnEventIndex = FindEvent(selection, EMoveDirectionDown);
            if (iSelectedColumnEventIndex >= 0) {
                // An event was found in the given interval
                UpdateSelectionInEvent();
            }
            else {
                // No even found, use the empty selection

                // If not within the region, move out
                if (selection.iStartSlot < region.iStartSlot) {
                    MoveOutFromRegion(EMoveDirectionUp);
                }
                else
                    if (selection.iEndSlot > region.iEndSlot) {
                        MoveOutFromRegion(EMoveDirectionDown);
                    }
            }
        }

    }

}

/*!
 \brief CalenDayInfo::MoveInRegion
*/
bool CalenDayInfo::MoveInRegion(TScrollDirection aDirection)
{
    ASSERT( iSelectedAlldayEvent < 0 && iSelectedRegion >= 0 );
    bool moved(true);
    switch (aDirection) {
        case EScrollUp: {
            MoveInColumn(EMoveDirectionUp);
        }
            break;
        case EScrollDown: {
            MoveInColumn(EMoveDirectionDown);
        }
            break;

        case EScrollLeft: {
            ASSERT( iSelectedRegion < iRegionList.count() );
            CalenTimeRegion& region = iRegionList[iSelectedRegion];
            ASSERT( iSelectedColumn >= 0 && iSelectedColumn <= region.iColumns.count() );
            if (iSelectedColumn == 0) {
                if (iAlldayEvents.count() > 0) {
                    // remember the last selection state
                    iSelectedAlldayEvent = iAlldayEvents.count() - 1;
                }
                else {
                    // move to previous day
                    moved = false;
                }
            }
            else {
                MoveBetweenColumns(aDirection);
            }
        }
            break;

        case EScrollRight: {
            ASSERT( iSelectedRegion < iRegionList.count() );
            CalenTimeRegion& region = iRegionList[iSelectedRegion];
            if (iSelectedColumn < region.iColumns.count() - 1) {
                MoveBetweenColumns(aDirection);
            }
            else
                if (iSelectedColumn == region.iColumns.count() - 1) {
                    // In the last column, moving to the empty column
                    iSelectedColumn++;
                    iSelectedColumnEventIndex = KErrNotFound;

                    // Round the selection to an empty interval
                    EmptySelectionInterval();
                }
                else {
                    // in the last, empty column
                    // move to next day
                    moved = false;
                }
        }
            break;

        default:
            // Do nothing
            break;
    }

    return moved;
}

/*!
 \brief CalenDayInfo::MoveInEmptyArea
*/
bool CalenDayInfo::MoveInEmptyArea(TScrollDirection aDirection)
{
    ASSERT( iSelectedAlldayEvent < 0 && iSelectedRegion < 0 );
    bool moved(true);
    switch (aDirection) {
        case EScrollUp: {
            CalenSlotInterval curInterval = EmptySelectionInterval();
            CalenSlotInterval targetInterval = NextFocusArea(curInterval, EMoveDirectionUp);
            iSelectedRegion = FindRegion(targetInterval, EMoveDirectionUp);
            if (iSelectedRegion >= 0) {
                EnterRegionFromBelow();
            }
            else {
                iSelectedSlot = NextEmptyFocusSlot(curInterval, EMoveDirectionUp);
            }

        }
            break;
        case EScrollDown: {
            CalenSlotInterval curInterval = EmptySelectionInterval();
            CalenSlotInterval targetInterval = NextFocusArea(curInterval, EMoveDirectionDown);
            iSelectedRegion = FindRegion(targetInterval, EMoveDirectionDown);
            if (iSelectedRegion >= 0) {
                EnterRegionFromAbove();
            }
            else {
                iSelectedSlot = NextEmptyFocusSlot(curInterval, EMoveDirectionDown);
            }
        }
            break;
        case EScrollLeft: {
            if (iAlldayEvents.count() > 0) {
                iSelectedAlldayEvent = iAlldayEvents.count() - 1;
            }
            else {
                // move to previous day
                moved = false;
            }
        }
            break;
        case EScrollRight: {
            // move to next day
            moved = false;
        }
            break;
        default:
            // Do nothing
            break;
    }

    return moved;
}

/*!
 \brief CalenDayInfo::MoveSelection
*/
bool CalenDayInfo::MoveSelection(TScrollDirection aDirection)
{
    bool moved(true);
    StoreOrigSelection();
    if (iSelectedAlldayEvent >= 0) {
        // focused on an allday event
        moved = MoveInAlldayEvent(aDirection);
    }
    else
        if (iSelectedRegion >= 0) {
            // focused on some event region
            moved = MoveInRegion(aDirection);
        }
        else {
            // focused on an empty, plain area
            moved = MoveInEmptyArea(aDirection);
        }
    if (!ValidateSelection()) {
        moved = false;
    }
    return moved;
}

/*!
 \brief CalenDayInfo::MoveSelectionInEvent
*/
void CalenDayInfo::MoveSelectionInEvent(TScrollDirection aDirection)
{
    ASSERT( IsEventSelected() );

    switch (aDirection) {
        case EScrollUp: {
            iSelectedSlot -= KFSCalSlotsInHour;
        }
            break;
        case EScrollDown: {
            iSelectedSlot += KFSCalSlotsInHour;
        }
            break;

        default:
            // Do nothing
            break;
    }

    UpdateSelectionInEvent();

    if (iSelectedAlldayEvent >= 0) {
        // if an allday event is selected, invalidate
        // the old region/event selection
        iSelectedRegion = KErrNotFound;
    }
}

/*!
 \brief CalenDayInfo::UpdateSelectionInEvent
*/
void CalenDayInfo::UpdateSelectionInEvent()
{
    ASSERT( IsEventSelected() );
    CalenSlotInterval event = SelectedInterval();

    if (iSelectedSlot < event.iStartSlot) {
        iSelectedSlot = event.iStartSlot;
    }
    if (iSelectedSlot >= event.iEndSlot) {
        iSelectedSlot = event.iEndSlot - 1;
    }

}

/*!
 \brief CalenDayInfo::SetSelectionInEvent
*/
void CalenDayInfo::SetSelectionInEvent(int aDirection)
{
    CalenSlotInterval event = SelectedInterval();
    if (aDirection > 0) {
        iSelectedSlot = event.iStartSlot;
    }
    else {
        iSelectedSlot = event.iEndSlot - 1;
    }
}

/*!
 \brief CalenDayInfo::StoreOrigSelection
*/
void CalenDayInfo::StoreOrigSelection()
{
    iOrigSelectedAlldayEvent = iSelectedAlldayEvent;
    iOrigSelectedRegion = iSelectedRegion;
    iOrigSelectedColumn = iSelectedColumn;
    iOrigSelectedSlot = iSelectedSlot;
    iOrigSelectedColumnEventIndex = iSelectedColumnEventIndex;
}

/*!
 \brief CalenDayInfo::ValidateSelection
*/
bool CalenDayInfo::ValidateSelection()
{
    bool validate(true);
    if (iSelectedSlot < 0 || iSelectedSlot >= iSlotsInHour * KCalenHoursInDay + iUntimedSlotCount) {
        // tried to move outside of the day
        // revert to the original coords
        iSelectedAlldayEvent = iOrigSelectedAlldayEvent;
        iSelectedRegion = iOrigSelectedRegion;
        iSelectedColumn = iOrigSelectedColumn;
        iSelectedSlot = iOrigSelectedSlot;
        iSelectedColumnEventIndex = iOrigSelectedColumnEventIndex;
        validate = false;
    }
    return validate;
}

/*!
 \brief CalenDayInfo::SelectSlot
*/
void CalenDayInfo::SelectSlot(int aSlot)
{
    // Check for special case: empty untimed slot is selected
    if (aSlot >= iFirstUntimedSlot && aSlot < iFirstUntimedSlot + iEmptyUntimedSlots) {
        aSlot = iFirstUntimedSlot + iEmptyUntimedSlots;
    }

    iSelectedAlldayEvent = KErrNotFound;
    iSelectedSlot = aSlot;
    CalenSlotInterval interval = EmptySelectionInterval();
    iSelectedRegion = FindRegion(interval, EMoveDirectionDown);
    if (iSelectedRegion >= 0) {
        CalenTimeRegion& region = iRegionList[iSelectedRegion];
        int regCount(region.iColumns.count());
        for (int i = 0; i < regCount; i++) {
            CalenTimeColumn& column = region.iColumns[i];
            int colCount(column.iEventArray.count());
            for (int j = 0; j < colCount; j++) {
                if (column.iEventArray[j].Overlaps(interval)) {
                    iSelectedColumn = i;
                    iSelectedColumnEventIndex = j;
                    iSelectedSlot = aSlot;
                    UpdateSelectionInEvent();
                    return;
                }
            }
        }
        // Regions can have empty areas where there aren't any events,
        // due to rounding of the ends of the region to whole hours.
        // A region cannot, however, have a empty complete whole hour.
        // Therefore, this cannot occur.
        User::Invariant();
    }
}

/*!
 \brief CalenDayInfo::RegionList
*/
const QList<CalenTimeRegion>& CalenDayInfo::RegionList() const
{
    return iRegionList;
}

/*!
 \brief CalenDayInfo::GetEventIntervals
*/
void CalenDayInfo::GetEventIntervals(QList<CalenEventInterval>& aArray) const
{
    int regCount(iRegionList.count());
    for (int i = 0; i < regCount; i++) {
        const CalenTimeRegion& region = iRegionList[i];
        for (int j = 0; j < region.iIntervals.count(); j++) {
            aArray.append(region.iIntervals[j]);
        }
    }
}

/*!
 \brief CalenDayInfo::InsertUntimedEventL
*/
/*
void CalenDayInfo::InsertUntimedEventL(const CCalInstance& aInstance)
{
    if (CalenAgendaUtils::IsTimedEntryL(aInstance.Entry().EntryTypeL())) {
        // timed entry added as untimed event
        User::Invariant();
    }

    TCalenInstanceId id = TCalenInstanceId::CreateL(aInstance);
    InsertUntimedEvent(aInstance.Entry().EntryTypeL(), id);
}
*/

/*!
 \brief CalenDayInfo::InsertUntimedEvent
*/
void CalenDayInfo::InsertUntimedEvent(AgendaEntry::Type aType, const TCalenInstanceId& aId)
{
    if (aType == AgendaEntry::TypeTodo) {
        iTodoEvents.append(aId);
    }
    else {
        iUntimedEvents.append(aId);
    }
}

/*!
 \brief CalenDayInfo::SuggestedUntimedSlotPos
*/
int CalenDayInfo::SuggestedUntimedSlotPos()
{
    int slot = KFSCalStartingHour * iSlotsInHour;
    if (iRegionList.count() > 0) {
        CalenTimeRegion& region = iRegionList[0];
        if (region.iStartSlot < slot) {
            slot = RoundHourUp(region.iStartSlot);
        }
    }
    return slot;
}

/*!
 \brief CalenDayInfo::NeededUntimedSlotCount
*/
int CalenDayInfo::NeededUntimedSlotCount()
{
    int count = iUntimedEvents.count();
    if (iTodoEvents.count() > 0) {
        count++;
    }
    return count;
}

/*!
 \brief CalenDayInfo::NeededUntimedSlotCount
*/
int CalenDayInfo::UpdateUntimedPos(int aSlot, int aUntimedCount)
{
    int newValue = NeededUntimedSlotCount();

    // If this method is called many times, first undo the previous modifications
    int regCount(iRegionList.count());
    for (int i = 0; i < regCount; i++) {
        iRegionList[i].AddOffset(-iUntimedSlotCount, iFirstUntimedSlot);
    }
    if (iSelectedSlot >= iFirstUntimedSlot) {
        iSelectedSlot -= iUntimedSlotCount;
    }

    // Reset the untimed slot count
    iUntimedSlotCount = 0;

    // Get the default values
    iFirstUntimedSlot = SuggestedUntimedSlotPos();
    iUntimedSlotCount = newValue;

    // If parameters were given, use them instead of the defaults
    if (aSlot >= 0) {
        ASSERT( aSlot <= iFirstUntimedSlot );
        ASSERT( iUntimedSlotCount <= aUntimedCount );

        iFirstUntimedSlot = aSlot;
        iUntimedSlotCount = aUntimedCount;
    }

    iEmptyUntimedSlots = iUntimedSlotCount - NeededUntimedSlotCount();

    // Add the new offset to all regions and update the selected slot
    regCount = iRegionList.count();
    for (int i = 0; i < regCount; i++) {
        iRegionList[i].AddOffset(iUntimedSlotCount, iFirstUntimedSlot);
    }
    if (iSelectedSlot >= iFirstUntimedSlot) {
        iSelectedSlot += iUntimedSlotCount;
    }
    int eventCount(iAlldayEvents.count());
    for (int i = 0; i < eventCount; i++) {
        iAlldayEvents[i].iEndSlot = iSlotsInHour * KCalenHoursInDay + iUntimedSlotCount;
    }

    // Do rounding of the end of the last region. This should be done when no
    // more events will be added, but there's no real harm if new events are
    // added after this.
    if (iRegionList.count() > 0) {
        // Round the end of the last region down
        CalenTimeRegion& lastRegion = iRegionList[iRegionList.count() - 1];
        lastRegion.iEndSlot = RoundHourDown(lastRegion.iEndSlot);
    }

    return iFirstUntimedSlot;
}

/*!
 \brief Returns the index of the first occupied slot
*/
int CalenDayInfo::FirstOccupiedSlot()
{

    int firstNonemptySlot(KErrNotFound);

    if (iUntimedSlotCount > 0) {
        firstNonemptySlot = iFirstUntimedSlot;
    }
    else {
        if (iRegionList.count() > 0) {
            CalenTimeRegion& region = iRegionList[0];
            firstNonemptySlot = region.iStartSlot;
        }
    }

    return firstNonemptySlot;
}

/*!
 \brief Returns the index of the last occupied slot
*/
int CalenDayInfo::LastOccupiedSlot()
{
    int lastNonemptySlot(KErrNotFound);
    if (iRegionList.count() > 0) {
        int lastIndex = iRegionList.count() - 1;
        CalenTimeRegion& region = iRegionList[lastIndex];
        lastNonemptySlot = region.iEndSlot;
    }
    else {
        if (iUntimedSlotCount > 0) {
            lastNonemptySlot = iFirstUntimedSlot + iUntimedSlotCount - 1;
        }
    }
    return lastNonemptySlot;
}

/*!
 \brief Returns the index of the earliest end slot
*/
int CalenDayInfo::EarliestEndSlot()
{
    int earliestEndSlot(KErrNotFound);
    int untimedEventCount = iUntimedSlotCount - iEmptyUntimedSlots;
    if (untimedEventCount > 0) {
        earliestEndSlot = iFirstUntimedSlot + iEmptyUntimedSlots;
        // add 1, since end slot is actually the one that is right after..
        earliestEndSlot++;
    }
    else {
        if (iRegionList.count() > 0) {
            earliestEndSlot = iEarliestEndSlot + iEmptyUntimedSlots;
        }
    }
    return earliestEndSlot;
}

/*!
 \brief Returns the index of the last start slot
*/
int CalenDayInfo::LastStartSlot()
{
    int lastStartSlot(KErrNotFound);

    if (iRegionList.count() > 0) {
        lastStartSlot = iLastStartSlot + iUntimedSlotCount;
    }
    else {
        if (iUntimedSlotCount - iEmptyUntimedSlots > 0) {
            lastStartSlot = iFirstUntimedSlot + iUntimedSlotCount - 1;
        }
    }
    return lastStartSlot;
}

/*!
 \brief Returns the index of a slot where this event should start drawing,
 based on the start time
*/
int CalenDayInfo::SlotIndexForStartTime(QDateTime aStartTime)
{
    // For example, 1/2 hour accuracy (iSlotsInHour == 2):
    // Start drawing with the previus half hour: 9:15 -> 9:00, 9:59-> 9:30.


    //    TDateTime dt = aStartTime.DateTime();
    //    int minutes = dt.Minute();
    //    int hours = dt.Hour();
    int minutes = aStartTime.time().minute();//dt.Minute();
    int hours = aStartTime.time().hour();//dt.Hour();

    // calculate index based on the hour.
    int slotIndex = hours * iSlotsInHour;

    switch (iSlotsInHour) {
        case EOne: // do nothing
            break;
        case ETwo: {
            if (minutes >= 30) {
                slotIndex++;
            }
        }
            break;
        case EThree: {
            if (minutes >= 20) {
                slotIndex++;
            }
            if (minutes >= 40) {
                slotIndex++;
            }
        }
            break;
        case EFour: // followthrough
        default: {
            if (minutes >= 15) {
                slotIndex++;
            }
            if (minutes >= 30) {
                slotIndex++;
            }
            if (minutes >= 45) {
                slotIndex++;
            }

        }
    }

    if (slotIndex >= iFirstUntimedSlot) {
        slotIndex += iUntimedSlotCount;
    }
    return slotIndex;
}

/*!
 \brief Returns the index of a slot where this event should end drawing,
 based on the end time
*/
int CalenDayInfo::SlotIndexForEndTime(QDateTime aEndTime)
{
    // For example, 1/2 hour accuracy (iSlotsInHour == 2):
    // End drawing with the next half hour: 9:10 -> 9:30, 9:59-> 10:00.

    //    TDateTime dt = aEndTime.DateTime();
    //    int minutes = dt.Minute();
    //    int hours = dt.Hour();
    int minutes = aEndTime.time().minute();
    int hours = aEndTime.time().hour();

    // calculate index based on the hour.
    int slotIndex = hours * iSlotsInHour;

    switch (iSlotsInHour) {
        case EOne: {
            if (minutes > 0) {
                slotIndex++;
            }
        }

            break;
        case ETwo: {
            if (minutes > 0) {
                slotIndex++;
            }
            if (minutes > 30) {
                slotIndex++;
            }
        }
            break;
        case EThree: {
            if (minutes > 0) {
                slotIndex++;
            }
            if (minutes > 20) {
                slotIndex++;
            }
            if (minutes > 40) {
                slotIndex++;
            }
        }
            break;
        case EFour: // followthrough
        default: {
            if (minutes > 0) {
                slotIndex++;
            }

            if (minutes > 15) {
                slotIndex++;
            }
            if (minutes > 30) {
                slotIndex++;
            }
            if (minutes > 45) {
                slotIndex++;
            }

        }
    }

    if (slotIndex >= iFirstUntimedSlot) {
        slotIndex += iUntimedSlotCount;
    }

    return slotIndex;

}

/*!
 \brief CalenDayInfo::IsHourStartSlot
*/
bool CalenDayInfo::IsHourStartSlot(const int& aSlotIndex) const
{
    bool isHourStartSlot(false);
    if (IsExtraSlot(aSlotIndex)) {
        isHourStartSlot = false;
    }
    else {
        int hourIndex(aSlotIndex);
        if (aSlotIndex >= iFirstUntimedSlot + iUntimedSlotCount) {
            hourIndex -= iUntimedSlotCount;
        }
        int hour = hourIndex / iSlotsInHour;
        int startIndOfHour = hour * iSlotsInHour;
        if (hourIndex - startIndOfHour > 0) {
            isHourStartSlot = false;
        }
        else {
            isHourStartSlot = true;
        }
    }
    return isHourStartSlot;

}

/*!
 \brief CalenDayInfo::IsExtraSlot
*/
bool CalenDayInfo::IsExtraSlot(const int& aSlotIndex) const
{
    return (aSlotIndex >= iFirstUntimedSlot) && (aSlotIndex < (iFirstUntimedSlot
        + iUntimedSlotCount));
}

/*!
 \brief CalenDayInfo::HourFromSlotIndex
 Returns the corresponding hour, or KErrNone if index is not hour slot index
*/
int CalenDayInfo::HourFromSlotIndex(const int& aSlotInd) const
{

    int hour(KErrNotFound);

    if (!IsExtraSlot(aSlotInd)) {

        if (aSlotInd < 0) {
            // round downwards, to the previous starting hour
            // e.g. iSlotsInHour = 2, aSlotInd = -1, hour = -1,
            // which by SlotIndexFromHour corresponds to slot -2
            hour = (aSlotInd - iSlotsInHour + 1) / iSlotsInHour;
        }
        else
            if (aSlotInd < iFirstUntimedSlot) {
                hour = aSlotInd / iSlotsInHour;
            }
            else
                if (aSlotInd >= iFirstUntimedSlot + iUntimedSlotCount) {
                    hour = (aSlotInd - iUntimedSlotCount) / iSlotsInHour;
                }
    }
    return hour;
}

/*!
 \brief CalenDayInfo::SlotIndexFromHour
 Returns the corresponding hour, or KErrNone if index is not hour slot index
*/
int CalenDayInfo::SlotIndexFromHour(int aHour)
{

    int slotIndex(KErrNotFound);

    if (aHour >= iFirstUntimedSlot / iSlotsInHour) {
        slotIndex = aHour * iSlotsInHour + iUntimedSlotCount;
    }
    else {
        slotIndex = aHour * iSlotsInHour;
    }

    return slotIndex;
}

/*!
 \brief CalenDayInfo::RoundHourUp
 Rounds the slot number up (towards earlier hours) to an even hour
*/
int CalenDayInfo::RoundHourUp(int aSlot)
{
    if (!IsExtraSlot(aSlot)) {
        aSlot = SlotIndexFromHour(HourFromSlotIndex(aSlot));
    }
    return aSlot;
}

/*!
 \brief CalenDayInfo::RoundHourDown
 Rounds the slot number down (towards later hours) to an even hour
*/
int CalenDayInfo::RoundHourDown(int aSlot)
{
    if (!IsExtraSlot(aSlot) && !IsHourStartSlot(aSlot)) {
        aSlot = SlotIndexFromHour(HourFromSlotIndex(aSlot + iSlotsInHour));
    }
    return aSlot;
}

/*!
 \brief CalenSlotInterval::Overlaps
*/
bool CalenSlotInterval::Overlaps(const CalenSlotInterval& aInterval) const
{
    return aInterval.iStartSlot < iEndSlot && aInterval.iEndSlot > iStartSlot;
}

/*!
 \brief CalenSlotInterval::AddOffset
*/
void CalenSlotInterval::AddOffset(int aOffset, int aPos)
{
    if (iStartSlot >= aPos) {
        iStartSlot += aOffset;
    }
    if (iEndSlot >= aPos) {
        iEndSlot += aOffset;
    }
}

/*!
 \brief CalenSlotInterval::Union
*/
void CalenSlotInterval::Union(const CalenSlotInterval& aInterval)
{
    if (!aInterval.IsEmpty()) {
        iStartSlot = Min(iStartSlot, aInterval.iStartSlot);
        iEndSlot = Max(iEndSlot, aInterval.iEndSlot);
    }
}

/*!
 \brief CalenSlotInterval::Adjacent
*/
bool CalenSlotInterval::Adjacent(const CalenSlotInterval& aInterval) const
{
    bool adjacent(false);
    if (Overlaps(aInterval)) {
        adjacent = true;
    }
    else {
        adjacent = iStartSlot == aInterval.iEndSlot || iEndSlot == aInterval.iStartSlot;
    }
    return adjacent;
}

/*!
 \brief CalenSlotInterval::IsEmpty
*/
bool CalenSlotInterval::IsEmpty() const
{
    return iStartSlot >= iEndSlot;
}

/*!
 \brief CalenSlotInterval::Intersect
*/
void CalenSlotInterval::Intersect(const CalenSlotInterval& aInterval)
{
    if (aInterval.IsEmpty()) {
        iEndSlot = iStartSlot;
    }
    else {
        iStartSlot = Max(iStartSlot, aInterval.iStartSlot);
        iEndSlot = Min(iEndSlot, aInterval.iEndSlot);
    }
}

/*!
 \brief CalenSlotInterval::Subtract
*/
void CalenSlotInterval::Subtract(const CalenSlotInterval& aInterval, CalenSlotInterval& aSecondPart)
{
    aSecondPart.iStartSlot = aSecondPart.iEndSlot = 0;
    if (!aInterval.IsEmpty() && Overlaps(aInterval)) {
        if (aInterval.iStartSlot <= iStartSlot) {
            iStartSlot = aInterval.iEndSlot;
        }
        else
            if (aInterval.iEndSlot >= iEndSlot) {
                iEndSlot = aInterval.iStartSlot;
            }
            else {
                aSecondPart.iStartSlot = aInterval.iEndSlot;
                aSecondPart.iEndSlot = iEndSlot;
                iEndSlot = aInterval.iStartSlot;
            }
    }
}

/*!
 \brief CalenSlotInterval::operator>
*/
bool CalenSlotInterval::operator>(const CalenSlotInterval& aInterval) const
{
    return iStartSlot > aInterval.iStartSlot;
}

/*!
 \brief CalenTimeColumn::Close
*/
void CalenTimeColumn::Close()
{
    iEventArray.clear();
}

/*!
 \brief CalenTimeColumn::AddEvent
*/
void CalenTimeColumn::AddEvent(const CalenTimedEventInfo& aEvent)
{
    ASSERT( aEvent.iStartSlot < aEvent.iEndSlot );
    ASSERT( CanFitEvent( aEvent ) );

    if (iEventArray.count() == 0) {
        iStartSlot = aEvent.iStartSlot;
    }
    iEndSlot = aEvent.iEndSlot;
    iEventArray.append(aEvent);
}

/*!
 \brief CalenTimeColumn::CanFitEvent
*/
bool CalenTimeColumn::CanFitEvent(const CalenTimedEventInfo& aEvent)
{
    return (aEvent.iStartSlot >= iEndSlot) || (iEventArray.count() == 0);
}

/*!
 \brief CalenTimeColumn::ContainsEvent
*/
bool CalenTimeColumn::ContainsEvent(const TCalenInstanceId& aId)
{
    bool contains(false);
    int eventCount(iEventArray.count());
    for (int i = 0; i < eventCount && !contains; i++) {
        if (iEventArray[i].iId == aId) {
            contains = true;
        }
    }
    return contains;
}

/*!
 \brief CalenTimeColumn::AddOffset
*/
void CalenTimeColumn::AddOffset(int aOffset, int aPos)
{
    if (aOffset != 0) {
        CalenSlotInterval::AddOffset(aOffset, aPos);
        int eventCount(iEventArray.count());
        for (int i = 0; i < eventCount; i++) {
            iEventArray[i].AddOffset(aOffset, aPos);
        }
    }
}

/*!
 \brief CalenTimeRegion::Close
*/
void CalenTimeRegion::Close()
{
    int colCount(iColumns.count());
    for (int i = 0; i < colCount; i++) {
        iColumns[i].Close();
    }
    iColumns.clear();
    iIntervals.clear();
}

/*!
 \brief CalenTimeRegion::Overlaps
*/
bool CalenTimeRegion::Overlaps(const CalenSlotInterval& aInterval) const
{
    // the base class implementation would be ok, but we might want the assertion here
    ASSERT( iColumns.count()> 0 );
    return CalenSlotInterval::Overlaps(aInterval);
}

/*!
 \brief CalenTimeRegion::AddOffset
*/
void CalenTimeRegion::AddOffset(int aOffset, int aPos)
{
    if (aOffset != 0) {
        CalenSlotInterval::AddOffset(aOffset, aPos);
        int colCount(iColumns.count());
        for (int i = 0; i < colCount; i++) {
            iColumns[i].AddOffset(aOffset, aPos);
        }
        int intervalCount(iIntervals.count());
        for (int i = 0; i < intervalCount; i++) {
            iIntervals[i].AddOffset(aOffset, aPos);
        }
    }

}

/*!
 \brief CalenTimeRegion::AddEvent
*/
void CalenTimeRegion::AddEvent(const CalenTimedEventInfo& aEvent)
{
    if (iColumns.count() == 0) {
        // This is the first event added to this region
        iStartSlot = aEvent.iStartSlot;
        iEndSlot = aEvent.iEndSlot;
    }
    else {
        // Check that the events actually are added in the correct order
        ASSERT( aEvent.iStartSlot >= iStartSlot );
        ASSERT( aEvent.iStartSlot < iEndSlot );

        if (aEvent.iEndSlot > iEndSlot) {
            iEndSlot = aEvent.iEndSlot;
        }
    }

    AddInterval(aEvent);

    // If the event fits in one of the existing columns, add it to that one
    bool added(false);
    int colCount(iColumns.count());
    for (int i = 0; i < colCount && !added; i++) {
        if (iColumns[i].CanFitEvent(aEvent)) {
            iColumns[i].AddEvent(aEvent);
            added = true;
        }
    }

    if (!added) {
        // otherwise create a new column for it
        iColumns.append(CalenTimeColumn());
        iColumns[iColumns.count() - 1].AddEvent(aEvent);
    }
}

/*!
 \brief CalenTimeRegion::AddInterval
*/
void CalenTimeRegion::AddInterval(const CalenTimedEventInfo& aEvent)
{
    /*
     * Here are a few examples of different possible cases for this method.
     * The first picture of each example shows the initial situation in the
     * array, and the new event to be added. The transformations performed
     * are shown with a few intermediate steps.
     *
     * nO = newOverlap, nE = newEvent
     *
     * Example one:
     *
     *               ------------------
     *               | newEvent       |
     * --------------------------------
     * ... | lastOverlap | tail |
     * --------------------------
     *
     *                   --------------
     *                   | newEvent   |
     * --------------------------------
     * ... | lastOverlap | tail |
     * --------------------------
     *
     *                   --------------
     *                   | nO   | nE  |
     * --------------------------------
     * ... | lastOverlap | tail |
     * --------------------------
     *
     * Result:
     * --------------------------------
     * ... | lastOverlap        | nE  |
     * --------------------------------
     *
     *
     * Example two:
     *                          ------------------
     *                          | newEvent       |
     * -------------------------------------------
     * ... | lastOverlap | tail      |
     * -------------------------------
     *
     *                          ------------------
     *                          | nO | newEvent  |
     * -------------------------------------------
     * ... | lastOverlap | tail      |
     * -------------------------------
     *
     * Result:
     * -------------------------------------------
     * ... | lastOverlap | tail | nO | newEvent  |
     * -------------------------------------------
     *
     *
     * Example three:
     *                          --------------
     *                          | newEvent   |
     * -----------------------------------------------------
     * ... | lastOverlap | tail                            |
     * -----------------------------------------------------
     *
     *                          --------------
     *                          | newOverlap |
     * -----------------------------------------------------
     * ... | lastOverlap | tail                            |
     * -----------------------------------------------------
     *
     *                          --------------
     *                          | newOverlap |
     * -----------------------------------------------------
     * ... | lastOverlap | tail |            | tailPart2   |
     * -----------------------------------------------------
     *
     * Result:
     * -----------------------------------------------------
     * ... | lastOverlap | tail | newOverlap | tailPart2   |
     * -----------------------------------------------------
     */

    // Fill out the new event
    CalenEventInterval newEvent;
    newEvent.iStartSlot = aEvent.iStartSlot;
    newEvent.iEndSlot = aEvent.iEndSlot;
    newEvent.iStatus = aEvent.iStatus;
    //    newEvent.iReplicationStatus = aEvent.iReplicationStatus;
    newEvent.iOverlap = false;

    // a pointer to the last interval which is an overlap interval
    CalenEventInterval* lastOverlap = NULL;
    // If nonempty, this is the last interval after the last overlap
    CalenEventInterval tail;
    tail.iStartSlot = tail.iEndSlot = 0;

    // Find lastOverlap and tail.
    if (iIntervals.count() >= 1) {
        // lastInterval is a pointer to the last interval in the array
        CalenEventInterval* lastInterval = &iIntervals[iIntervals.count() - 1];

        // If this is an overlap interval, we haven't got any tail.
        if (lastInterval->iOverlap) {
            lastOverlap = lastInterval;
        }
        else {
            tail = *lastInterval;
        }

        // If there's at least two intervals, and the last one wasn't an overlap,
        // the second last one must be an overlap.
        if (iIntervals.count() >= 2 && !lastOverlap) {
            lastOverlap = &iIntervals[iIntervals.count() - 2];
            ASSERT( lastOverlap->iOverlap );
        }
    }

    // If we got a tail, remove it from the array (it will be readded
    // at the end if needed)
    if (!tail.IsEmpty()) {
        iIntervals.removeAt(iIntervals.count() - 1);
    }

    CalenEventInterval empty;
    if (lastOverlap) {
        // Remove the part which already is marked as an overlap
        // from the new event. The new event can't start before the
        // last overlap starts since events are added in order, therefore
        // the second subtraction result interval will remain empty.
        newEvent.Subtract(*lastOverlap, empty);
        ASSERT( empty.IsEmpty() );
    }

    // Create a new interval, representing the overlap between the old tail
    // and the new event
    CalenEventInterval newOverlap = newEvent;
    newOverlap.iOverlap = true;
    newOverlap.Intersect(tail);

    CalenEventInterval tailPart2 = tail; // initialize iOverlap and iStatus from tail
    // Remove the new overlap from the old tail, possibly creating two separate intervals.
    tail.Subtract(newOverlap, tailPart2);

    // If the subtraction only yielded one single interval, but it's
    // after newOverlap, make tailPart2 contain that and make tail empty.
    if (tail > newOverlap) {
        tailPart2 = tail;
        tail.iEndSlot = tail.iStartSlot;
    }

    // Remove the new overlap from the new event. Since we already removed the old
    // overlap, this subtraction can't produce two intervals either.
    newEvent.Subtract(newOverlap, empty);
    ASSERT( empty.IsEmpty() );

    // If the new overlap is adjacent to the old one, expand the old one
    // and set the new overlap to be empty.
    if (lastOverlap && newOverlap.Adjacent(*lastOverlap)) {
        lastOverlap->Union(newOverlap);
        newOverlap.iEndSlot = newOverlap.iStartSlot;
    }

    // Add all the new intervals, if they're non-empty.
    if (!tail.IsEmpty()) {
        iIntervals.append(tail);
    }
    if (!newOverlap.IsEmpty()) {
        iIntervals.append(newOverlap);
    }
    if (!tailPart2.IsEmpty()) {
        iIntervals.append(tailPart2);
    }
    if (!newEvent.IsEmpty()) {
        iIntervals.append(newEvent);
    }

}

// End of File