diff -r fd30d51f876b -r b6db4fd4947b calendarui/views/dayview/src/calendayinfo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/calendarui/views/dayview/src/calendayinfo.cpp Mon Jun 28 15:22:02 2010 +0530 @@ -0,0 +1,2362 @@ +/* +* 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& CalenDayInfo::RegionList() const +{ + return iRegionList; +} + +/*! + \brief CalenDayInfo::GetEventIntervals +*/ +void CalenDayInfo::GetEventIntervals(QList& 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