summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp723
1 files changed, 723 insertions, 0 deletions
diff --git a/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp b/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp
new file mode 100644
index 0000000..e8fac01
--- /dev/null
+++ b/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIEPGGridContainerModel.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "pvr/PVRManager.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/epg/Epg.h"
+#include "pvr/epg/EpgChannelData.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cmath>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+using namespace PVR;
+
+static const unsigned int GRID_START_PADDING = 30; // minutes
+
+void CGUIEPGGridContainerModel::SetInvalid()
+{
+ for (const auto& gridItem : m_gridIndex)
+ gridItem.second.item->SetInvalid();
+ for (const auto& channel : m_channelItems)
+ channel->SetInvalid();
+ for (const auto& ruler : m_rulerItems)
+ ruler->SetInvalid();
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::CreateGapItem(int iChannel) const
+{
+ const std::shared_ptr<CPVRChannel> channel = m_channelItems[iChannel]->GetPVRChannelInfoTag();
+ const std::shared_ptr<CPVREpgInfoTag> gapTag = channel->CreateEPGGapTag(m_gridStart, m_gridEnd);
+ return std::make_shared<CFileItem>(gapTag);
+}
+
+std::vector<std::shared_ptr<CPVREpgInfoTag>> CGUIEPGGridContainerModel::GetEPGTimeline(
+ int iChannel, const CDateTime& minEventEnd, const CDateTime& maxEventStart) const
+{
+ CDateTime min = minEventEnd - CDateTimeSpan(0, 0, MINSPERBLOCK, 0) + CDateTimeSpan(0, 0, 0, 1);
+ CDateTime max = maxEventStart + CDateTimeSpan(0, 0, MINSPERBLOCK, 0);
+
+ if (min < m_gridStart)
+ min = m_gridStart;
+
+ if (max > m_gridEnd)
+ max = m_gridEnd;
+
+ return m_channelItems[iChannel]->GetPVRChannelInfoTag()->GetEPGTimeline(m_gridStart, m_gridEnd,
+ min, max);
+}
+
+void CGUIEPGGridContainerModel::Initialize(const std::unique_ptr<CFileItemList>& items,
+ const CDateTime& gridStart,
+ const CDateTime& gridEnd,
+ int iFirstChannel,
+ int iChannelsPerPage,
+ int iFirstBlock,
+ int iBlocksPerPage,
+ int iRulerUnit,
+ float fBlockSize)
+{
+ if (!m_channelItems.empty())
+ {
+ CLog::LogF(LOGERROR, "Already initialized!");
+ return;
+ }
+
+ m_fBlockSize = fBlockSize;
+
+ ////////////////////////////////////////////////////////////////////////
+ // Create channel items
+ std::copy(items->cbegin(), items->cend(), std::back_inserter(m_channelItems));
+
+ /* check for invalid start and end time */
+ if (gridStart >= gridEnd)
+ {
+ // default to start "now minus GRID_START_PADDING minutes" and end "start plus one page".
+ m_gridStart = CDateTime::GetUTCDateTime() - CDateTimeSpan(0, 0, GetGridStartPadding(), 0);
+ m_gridEnd = m_gridStart + CDateTimeSpan(0, 0, iBlocksPerPage * MINSPERBLOCK, 0);
+ }
+ else if (gridStart >
+ (CDateTime::GetUTCDateTime() - CDateTimeSpan(0, 0, GetGridStartPadding(), 0)))
+ {
+ // adjust to start "now minus GRID_START_PADDING minutes".
+ m_gridStart = CDateTime::GetUTCDateTime() - CDateTimeSpan(0, 0, GetGridStartPadding(), 0);
+ m_gridEnd = gridEnd;
+ }
+ else
+ {
+ m_gridStart = gridStart;
+ m_gridEnd = gridEnd;
+ }
+
+ // roundup
+ m_gridStart = CDateTime(m_gridStart.GetYear(), m_gridStart.GetMonth(), m_gridStart.GetDay(),
+ m_gridStart.GetHour(), m_gridStart.GetMinute() >= 30 ? 30 : 0, 0);
+ m_gridEnd = CDateTime(m_gridEnd.GetYear(), m_gridEnd.GetMonth(), m_gridEnd.GetDay(),
+ m_gridEnd.GetHour(), m_gridEnd.GetMinute() >= 30 ? 30 : 0, 0);
+
+ m_blocks = GetBlock(m_gridEnd) + 1;
+
+ const int iBlocksLastPage = m_blocks % iBlocksPerPage;
+ if (iBlocksLastPage > 0)
+ {
+ m_gridEnd += CDateTimeSpan(0, 0, (iBlocksPerPage - iBlocksLastPage) * MINSPERBLOCK, 0);
+ m_blocks += (iBlocksPerPage - iBlocksLastPage);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // Create ruler items
+ CDateTime ruler;
+ ruler.SetFromUTCDateTime(m_gridStart);
+ CDateTime rulerEnd;
+ rulerEnd.SetFromUTCDateTime(m_gridEnd);
+ CFileItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedDate(true)));
+ rulerItem->SetProperty("DateLabel", true);
+ m_rulerItems.emplace_back(rulerItem);
+
+ const CDateTimeSpan unit(0, 0, iRulerUnit * MINSPERBLOCK, 0);
+ for (; ruler < rulerEnd; ruler += unit)
+ {
+ rulerItem.reset(new CFileItem(ruler.GetAsLocalizedTime("", false)));
+ rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true));
+ m_rulerItems.emplace_back(rulerItem);
+ }
+
+ m_firstActiveChannel = iFirstChannel;
+ m_lastActiveChannel = iFirstChannel + iChannelsPerPage - 1;
+ m_firstActiveBlock = iFirstBlock;
+ m_lastActiveBlock = iFirstBlock + iBlocksPerPage - 1;
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::CreateEpgTags(int iChannel, int iBlock) const
+{
+ std::shared_ptr<CFileItem> result;
+
+ const int firstBlock = iBlock < m_firstActiveBlock ? iBlock : m_firstActiveBlock;
+ const int lastBlock = iBlock > m_lastActiveBlock ? iBlock : m_lastActiveBlock;
+
+ const auto tags =
+ GetEPGTimeline(iChannel, GetStartTimeForBlock(firstBlock), GetStartTimeForBlock(lastBlock));
+
+ const int firstResultBlock = GetFirstEventBlock(tags.front());
+ const int lastResultBlock = GetLastEventBlock(tags.back());
+ if (firstResultBlock > lastResultBlock)
+ return result;
+
+ auto it = m_epgItems.insert({iChannel, EpgTags()}).first;
+ EpgTags& epgTags = (*it).second;
+
+ epgTags.firstBlock = firstResultBlock;
+ epgTags.lastBlock = lastResultBlock;
+
+ for (const auto& tag : tags)
+ {
+ if (GetFirstEventBlock(tag) > GetLastEventBlock(tag))
+ continue;
+
+ const std::shared_ptr<CFileItem> item = std::make_shared<CFileItem>(tag);
+ if (!result && IsEventMemberOfBlock(tag, iBlock))
+ result = item;
+
+ epgTags.tags.emplace_back(item);
+ }
+
+ return result;
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::GetEpgTags(EpgTagsMap::iterator& itEpg,
+ int iChannel,
+ int iBlock) const
+{
+ std::shared_ptr<CFileItem> result;
+
+ EpgTags& epgTags = (*itEpg).second;
+
+ if (iBlock < epgTags.firstBlock)
+ {
+ result = GetEpgTagsBefore(epgTags, iChannel, iBlock);
+ }
+ else if (iBlock > epgTags.lastBlock)
+ {
+ result = GetEpgTagsAfter(epgTags, iChannel, iBlock);
+ }
+ else
+ {
+ const auto it =
+ std::find_if(epgTags.tags.cbegin(), epgTags.tags.cend(), [this, iBlock](const auto& item) {
+ return IsEventMemberOfBlock(item->GetEPGInfoTag(), iBlock);
+ });
+ if (it != epgTags.tags.cend())
+ result = (*it);
+ }
+
+ return result;
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::GetEpgTagsBefore(EpgTags& epgTags,
+ int iChannel,
+ int iBlock) const
+{
+ std::shared_ptr<CFileItem> result;
+
+ int lastBlock = epgTags.firstBlock - 1;
+ if (lastBlock < 0)
+ lastBlock = 0;
+
+ const auto tags =
+ GetEPGTimeline(iChannel, GetStartTimeForBlock(iBlock), GetStartTimeForBlock(lastBlock));
+
+ if (epgTags.lastBlock == -1)
+ epgTags.lastBlock = lastBlock;
+
+ if (tags.empty())
+ {
+ epgTags.firstBlock = iBlock;
+ }
+ else
+ {
+ const int firstResultBlock = GetFirstEventBlock(tags.front());
+ const int lastResultBlock = GetLastEventBlock(tags.back());
+ if (firstResultBlock > lastResultBlock)
+ return result;
+
+ // insert before the existing tags
+ epgTags.firstBlock = firstResultBlock;
+
+ auto it = tags.crbegin();
+ if (!epgTags.tags.empty())
+ {
+ // ptr comp does not work for gap tags!
+ // if ((*it) == epgTags.tags.front()->GetEPGInfoTag())
+
+ const std::shared_ptr<CPVREpgInfoTag> t = epgTags.tags.front()->GetEPGInfoTag();
+ if ((*it)->StartAsUTC() == t->StartAsUTC() && (*it)->EndAsUTC() == t->EndAsUTC())
+ {
+ if (!result && IsEventMemberOfBlock(*it, iBlock))
+ result = epgTags.tags.front();
+
+ ++it; // skip, because we already have that epg tag
+ }
+ }
+
+ for (; it != tags.crend(); ++it)
+ {
+ if (GetFirstEventBlock(*it) > GetLastEventBlock(*it))
+ continue;
+
+ const std::shared_ptr<CFileItem> item = std::make_shared<CFileItem>(*it);
+ if (!result && IsEventMemberOfBlock(*it, iBlock))
+ result = item;
+
+ epgTags.tags.insert(epgTags.tags.begin(), item);
+ }
+ }
+
+ return result;
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::GetEpgTagsAfter(EpgTags& epgTags,
+ int iChannel,
+ int iBlock) const
+{
+ std::shared_ptr<CFileItem> result;
+
+ int firstBlock = epgTags.lastBlock + 1;
+ if (firstBlock >= GetLastBlock())
+ firstBlock = GetLastBlock();
+
+ const auto tags =
+ GetEPGTimeline(iChannel, GetStartTimeForBlock(firstBlock), GetStartTimeForBlock(iBlock));
+
+ if (epgTags.firstBlock == -1)
+ epgTags.firstBlock = firstBlock;
+
+ if (tags.empty())
+ {
+ epgTags.lastBlock = iBlock;
+ }
+ else
+ {
+ const int firstResultBlock = GetFirstEventBlock(tags.front());
+ const int lastResultBlock = GetLastEventBlock(tags.back());
+ if (firstResultBlock > lastResultBlock)
+ return result;
+
+ // append to the existing tags
+ epgTags.lastBlock = lastResultBlock;
+
+ auto it = tags.cbegin();
+ if (!epgTags.tags.empty())
+ {
+ // ptr comp does not work for gap tags!
+ // if ((*it) == epgTags.tags.back()->GetEPGInfoTag())
+
+ const std::shared_ptr<CPVREpgInfoTag> t = epgTags.tags.back()->GetEPGInfoTag();
+ if ((*it)->StartAsUTC() == t->StartAsUTC() && (*it)->EndAsUTC() == t->EndAsUTC())
+ {
+ if (!result && IsEventMemberOfBlock(*it, iBlock))
+ result = epgTags.tags.back();
+
+ ++it; // skip, because we already have that epg tag
+ }
+ }
+
+ for (; it != tags.cend(); ++it)
+ {
+ if (GetFirstEventBlock(*it) > GetLastEventBlock(*it))
+ continue;
+
+ const std::shared_ptr<CFileItem> item = std::make_shared<CFileItem>(*it);
+ if (!result && IsEventMemberOfBlock(*it, iBlock))
+ result = item;
+
+ epgTags.tags.emplace_back(item);
+ }
+ }
+
+ return result;
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::GetItem(int iChannel, int iBlock) const
+{
+ std::shared_ptr<CFileItem> result;
+
+ auto itEpg = m_epgItems.find(iChannel);
+ if (itEpg == m_epgItems.end())
+ {
+ result = CreateEpgTags(iChannel, iBlock);
+ }
+ else
+ {
+ result = GetEpgTags(itEpg, iChannel, iBlock);
+ }
+
+ if (!result)
+ {
+ // Must never happen. if it does, fix the root cause, don't tolerate nullptr!
+ CLog::LogF(LOGERROR, "EPG tag ({}, {}) not found!", iChannel, iBlock);
+ }
+
+ return result;
+}
+
+void CGUIEPGGridContainerModel::FindChannelAndBlockIndex(int channelUid,
+ unsigned int broadcastUid,
+ int eventOffset,
+ int& newChannelIndex,
+ int& newBlockIndex) const
+{
+ newChannelIndex = INVALID_INDEX;
+ newBlockIndex = INVALID_INDEX;
+
+ // find the new channel index
+ int iCurrentChannel = 0;
+ for (const auto& channel : m_channelItems)
+ {
+ if (channel->GetPVRChannelInfoTag()->UniqueID() == channelUid)
+ {
+ newChannelIndex = iCurrentChannel;
+
+ // find the new block index
+ const std::shared_ptr<CPVREpg> epg = channel->GetPVRChannelInfoTag()->GetEPG();
+ if (epg)
+ {
+ const std::shared_ptr<CPVREpgInfoTag> tag = epg->GetTagByBroadcastId(broadcastUid);
+ if (tag)
+ newBlockIndex = GetFirstEventBlock(tag) + eventOffset;
+ }
+ break; // done
+ }
+ iCurrentChannel++;
+ }
+}
+
+GridItem* CGUIEPGGridContainerModel::GetGridItemPtr(int iChannel, int iBlock) const
+{
+ auto it = m_gridIndex.find({iChannel, iBlock});
+ if (it == m_gridIndex.end())
+ {
+ const CDateTime startTime = GetStartTimeForBlock(iBlock);
+ if (startTime < m_gridStart || m_gridEnd < startTime)
+ {
+ CLog::LogF(LOGERROR, "Requested EPG tag ({}, {}) outside grid boundaries!", iChannel, iBlock);
+ return nullptr;
+ }
+
+ const std::shared_ptr<CFileItem> item = GetItem(iChannel, iBlock);
+ if (!item)
+ {
+ CLog::LogF(LOGERROR, "Got no EPG tag ({}, {})!", iChannel, iBlock);
+ return nullptr;
+ }
+
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = item->GetEPGInfoTag();
+
+ const int startBlock = GetFirstEventBlock(epgTag);
+ const int endBlock = GetLastEventBlock(epgTag);
+
+ //! @todo it seems that this should be done somewhere else. CFileItem ctor maybe.
+ item->SetProperty("GenreType", epgTag->GenreType());
+
+ const float fItemWidth = (endBlock - startBlock + 1) * m_fBlockSize;
+ it = m_gridIndex.insert({{iChannel, iBlock}, {item, fItemWidth, startBlock, endBlock}}).first;
+ }
+
+ return &(*it).second;
+}
+
+bool CGUIEPGGridContainerModel::IsSameGridItem(int iChannel, int iBlock1, int iBlock2) const
+{
+ if (iBlock1 == iBlock2)
+ return true;
+
+ const GridItem* item1 = GetGridItemPtr(iChannel, iBlock1);
+ const GridItem* item2 = GetGridItemPtr(iChannel, iBlock2);
+
+ // compare the instances, not instance pointers, pointers are not unique.
+ return *item1 == *item2;
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainerModel::GetGridItem(int iChannel, int iBlock) const
+{
+ return GetGridItemPtr(iChannel, iBlock)->item;
+}
+
+int CGUIEPGGridContainerModel::GetGridItemStartBlock(int iChannel, int iBlock) const
+{
+ return GetGridItemPtr(iChannel, iBlock)->startBlock;
+}
+
+int CGUIEPGGridContainerModel::GetGridItemEndBlock(int iChannel, int iBlock) const
+{
+ return GetGridItemPtr(iChannel, iBlock)->endBlock;
+}
+
+CDateTime CGUIEPGGridContainerModel::GetGridItemEndTime(int iChannel, int iBlock) const
+{
+ return GetGridItemPtr(iChannel, iBlock)->item->GetEPGInfoTag()->EndAsUTC();
+}
+
+float CGUIEPGGridContainerModel::GetGridItemWidth(int iChannel, int iBlock) const
+{
+ return GetGridItemPtr(iChannel, iBlock)->width;
+}
+
+float CGUIEPGGridContainerModel::GetGridItemOriginWidth(int iChannel, int iBlock) const
+{
+ return GetGridItemPtr(iChannel, iBlock)->originWidth;
+}
+
+void CGUIEPGGridContainerModel::DecreaseGridItemWidth(int iChannel, int iBlock, float fSize)
+{
+ auto it = m_gridIndex.find({iChannel, iBlock});
+ if (it != m_gridIndex.end() && (*it).second.width != ((*it).second.originWidth - fSize))
+ (*it).second.width = (*it).second.originWidth - fSize;
+}
+
+unsigned int CGUIEPGGridContainerModel::GetGridStartPadding() const
+{
+ unsigned int iPastMinutes =
+ CServiceBroker::GetPVRManager().EpgContainer().GetPastDaysToDisplay() * 24 * 60;
+
+ if (iPastMinutes < GRID_START_PADDING)
+ return iPastMinutes;
+
+ return GRID_START_PADDING; // minutes
+}
+
+void CGUIEPGGridContainerModel::FreeChannelMemory(int keepStart, int keepEnd)
+{
+ if (keepStart < keepEnd)
+ {
+ // remove before keepStart and after keepEnd
+ for (int i = 0; i < keepStart && i < ChannelItemsSize(); ++i)
+ m_channelItems[i]->FreeMemory();
+ for (int i = keepEnd + 1; i < ChannelItemsSize(); ++i)
+ m_channelItems[i]->FreeMemory();
+ }
+ else
+ {
+ // wrapping
+ for (int i = keepEnd + 1; i < keepStart && i < ChannelItemsSize(); ++i)
+ m_channelItems[i]->FreeMemory();
+ }
+}
+
+bool CGUIEPGGridContainerModel::FreeProgrammeMemory(int firstChannel,
+ int lastChannel,
+ int firstBlock,
+ int lastBlock)
+{
+ const bool channelsChanged =
+ (firstChannel != m_firstActiveChannel || lastChannel != m_lastActiveChannel);
+ const bool blocksChanged = (firstBlock != m_firstActiveBlock || lastBlock != m_lastActiveBlock);
+ if (!channelsChanged && !blocksChanged)
+ return false;
+
+ // clear the grid. it will be recreated on-demand.
+ m_gridIndex.clear();
+
+ bool newChannels = false;
+
+ if (channelsChanged)
+ {
+ // purge epg tags for inactive channels
+ for (auto it = m_epgItems.begin(); it != m_epgItems.end();)
+ {
+ if ((*it).first < firstChannel || (*it).first > lastChannel)
+ {
+ it = m_epgItems.erase(it);
+ continue; // next channel
+ }
+ ++it;
+ }
+
+ newChannels = (firstChannel < m_firstActiveChannel) || (lastChannel > m_lastActiveChannel);
+ }
+
+ if (blocksChanged || newChannels)
+ {
+ // clear and refetch epg tags for active channels
+ const CDateTime maxEnd = GetStartTimeForBlock(firstBlock);
+ const CDateTime minStart = GetStartTimeForBlock(lastBlock);
+ std::vector<std::shared_ptr<CPVREpgInfoTag>> tags;
+ for (int i = firstChannel; i <= lastChannel; ++i)
+ {
+ auto it = m_epgItems.find(i);
+ if (it == m_epgItems.end())
+ it = m_epgItems.insert({i, EpgTags()}).first;
+
+ if (blocksChanged || i < m_firstActiveChannel || i > m_lastActiveChannel)
+ {
+ EpgTags& epgTags = (*it).second;
+
+ (*it).second.tags.clear();
+
+ tags = GetEPGTimeline(i, maxEnd, minStart);
+ const int firstResultBlock = GetFirstEventBlock(tags.front());
+ const int lastResultBlock = GetLastEventBlock(tags.back());
+ if (firstResultBlock > lastResultBlock)
+ continue;
+
+ epgTags.firstBlock = firstResultBlock;
+ epgTags.lastBlock = lastResultBlock;
+
+ for (const auto& tag : tags)
+ {
+ if (GetFirstEventBlock(tag) > GetLastEventBlock(tag))
+ continue;
+
+ epgTags.tags.emplace_back(std::make_shared<CFileItem>(tag));
+ }
+ }
+ }
+ }
+
+ m_firstActiveChannel = firstChannel;
+ m_lastActiveChannel = lastChannel;
+ m_firstActiveBlock = firstBlock;
+ m_lastActiveBlock = lastBlock;
+
+ return true;
+}
+
+void CGUIEPGGridContainerModel::FreeRulerMemory(int keepStart, int keepEnd)
+{
+ if (keepStart < keepEnd)
+ {
+ // remove before keepStart and after keepEnd
+ for (int i = 1; i < keepStart && i < RulerItemsSize(); ++i)
+ m_rulerItems[i]->FreeMemory();
+ for (int i = keepEnd + 1; i < RulerItemsSize(); ++i)
+ m_rulerItems[i]->FreeMemory();
+ }
+ else
+ {
+ // wrapping
+ for (int i = keepEnd + 1; i < keepStart && i < RulerItemsSize(); ++i)
+ {
+ if (i == 0)
+ continue;
+
+ m_rulerItems[i]->FreeMemory();
+ }
+ }
+}
+
+unsigned int CGUIEPGGridContainerModel::GetPageNowOffset() const
+{
+ return GetGridStartPadding() / MINSPERBLOCK; // this is the 'now' block relative to page start
+}
+
+CDateTime CGUIEPGGridContainerModel::GetStartTimeForBlock(int block) const
+{
+ if (block < 0)
+ block = 0;
+ else if (block >= GridItemsSize())
+ block = GetLastBlock();
+
+ return m_gridStart + CDateTimeSpan(0, 0, block * MINSPERBLOCK, 0);
+}
+
+int CGUIEPGGridContainerModel::GetBlock(const CDateTime& datetime) const
+{
+ int diff;
+
+ if (m_gridStart == datetime)
+ return 0; // block is at grid start
+ else if (m_gridStart > datetime)
+ diff = -1 * (m_gridStart - datetime).GetSecondsTotal(); // block is before grid start
+ else
+ diff = (datetime - m_gridStart).GetSecondsTotal(); // block is after grid start
+
+ // Note: Subtract 1 second from diff to ensure that events ending exactly at block boundary
+ // are unambiguous. Example: An event ending at 5:00:00 shall be mapped to block 9 and
+ // an event starting at 5:00:00 shall be mapped to block 10, not both at block 10.
+ // Only exception is grid end, because there is no successor.
+ if (datetime >= m_gridEnd)
+ return diff / 60 / MINSPERBLOCK; // block is equal or after grid end
+ else
+ return (diff - 1) / 60 / MINSPERBLOCK;
+}
+
+int CGUIEPGGridContainerModel::GetNowBlock() const
+{
+ return GetBlock(CDateTime::GetUTCDateTime()) - GetPageNowOffset();
+}
+
+int CGUIEPGGridContainerModel::GetFirstEventBlock(
+ const std::shared_ptr<CPVREpgInfoTag>& event) const
+{
+ const CDateTime eventStart = event->StartAsUTC();
+ int diff;
+
+ if (m_gridStart == eventStart)
+ return 0; // block is at grid start
+ else if (m_gridStart > eventStart)
+ diff = -1 * (m_gridStart - eventStart).GetSecondsTotal();
+ else
+ diff = (eventStart - m_gridStart).GetSecondsTotal();
+
+ // First block of a tag is always the block calculated using event's start time, rounded up.
+ float fBlockIndex = diff / 60.0f / MINSPERBLOCK;
+ return static_cast<int>(std::ceil(fBlockIndex));
+}
+
+int CGUIEPGGridContainerModel::GetLastEventBlock(const std::shared_ptr<CPVREpgInfoTag>& event) const
+{
+ // Last block of a tag is always the block calculated using event's end time, not rounded up.
+ return GetBlock(event->EndAsUTC());
+}
+
+bool CGUIEPGGridContainerModel::IsEventMemberOfBlock(const std::shared_ptr<CPVREpgInfoTag>& event,
+ int iBlock) const
+{
+ const int iFirstBlock = GetFirstEventBlock(event);
+ const int iLastBlock = GetLastEventBlock(event);
+
+ if (iFirstBlock > iLastBlock)
+ {
+ return false;
+ }
+ else if (iFirstBlock == iBlock)
+ {
+ return true;
+ }
+ else if (iFirstBlock < iBlock)
+ {
+ return (iBlock <= iLastBlock);
+ }
+ return false;
+}
+
+std::unique_ptr<CFileItemList> CGUIEPGGridContainerModel::GetCurrentTimeLineItems(
+ int firstChannel, int numChannels) const
+{
+ // Note: No need to keep this in a member. Gets generally not called multiple times for the
+ // same timeline, but content must be synced with m_epgItems, which changes quite often.
+
+ std::unique_ptr<CFileItemList> items(new CFileItemList);
+
+ if (numChannels > ChannelItemsSize())
+ numChannels = ChannelItemsSize();
+
+ int i = 0;
+ for (int channel = firstChannel; channel < (firstChannel + numChannels); ++channel)
+ {
+ // m_epgItems is not sorted, fileitemlist must be sorted, so we have to 'find' the channel
+ const auto itEpg = m_epgItems.find(channel);
+ if (itEpg != m_epgItems.end())
+ {
+ // tags are sorted, so we can iterate and append
+ for (const auto& tag : (*itEpg).second.tags)
+ {
+ tag->SetProperty("TimelineIndex", i);
+ items->Add(tag);
+ ++i;
+ }
+ }
+ else
+ {
+ // fake empty EPG
+ const std::shared_ptr<CFileItem> tag = CreateGapItem(channel);
+ tag->SetProperty("TimelineIndex", i);
+ items->Add(tag);
+ ++i;
+ }
+ }
+ return items;
+}