summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/guilib
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/pvr/guilib')
-rw-r--r--xbmc/pvr/guilib/CMakeLists.txt35
-rw-r--r--xbmc/pvr/guilib/GUIEPGGridContainer.cpp2549
-rw-r--r--xbmc/pvr/guilib/GUIEPGGridContainer.dox245
-rw-r--r--xbmc/pvr/guilib/GUIEPGGridContainer.h274
-rw-r--r--xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp723
-rw-r--r--xbmc/pvr/guilib/GUIEPGGridContainerModel.h178
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionListener.cpp420
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionListener.h46
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsChannels.cpp424
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsChannels.h182
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsClients.cpp88
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsClients.h38
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp341
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsDatabase.h40
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsEPG.cpp217
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsEPG.h84
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsParentalControl.cpp74
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsParentalControl.h59
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp564
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsPlayback.h150
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp190
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsPowerManagement.h55
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp361
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsRecordings.h114
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsTimers.cpp1009
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsTimers.h234
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsUtils.cpp36
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsUtils.h40
-rw-r--r--xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp101
-rw-r--r--xbmc/pvr/guilib/PVRGUIChannelIconUpdater.h46
-rw-r--r--xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp373
-rw-r--r--xbmc/pvr/guilib/PVRGUIChannelNavigator.h189
-rw-r--r--xbmc/pvr/guilib/PVRGUIProgressHandler.cpp97
-rw-r--r--xbmc/pvr/guilib/PVRGUIProgressHandler.h59
-rw-r--r--xbmc/pvr/guilib/guiinfo/CMakeLists.txt9
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp2017
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h217
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.cpp270
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.h111
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp424
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h87
41 files changed, 12770 insertions, 0 deletions
diff --git a/xbmc/pvr/guilib/CMakeLists.txt b/xbmc/pvr/guilib/CMakeLists.txt
new file mode 100644
index 0000000..3a73a68
--- /dev/null
+++ b/xbmc/pvr/guilib/CMakeLists.txt
@@ -0,0 +1,35 @@
+set(SOURCES GUIEPGGridContainer.cpp
+ GUIEPGGridContainerModel.cpp
+ PVRGUIActionListener.cpp
+ PVRGUIActionsChannels.cpp
+ PVRGUIActionsClients.cpp
+ PVRGUIActionsDatabase.cpp
+ PVRGUIActionsEPG.cpp
+ PVRGUIActionsUtils.cpp
+ PVRGUIActionsParentalControl.cpp
+ PVRGUIActionsPlayback.cpp
+ PVRGUIActionsPowerManagement.cpp
+ PVRGUIActionsRecordings.cpp
+ PVRGUIActionsTimers.cpp
+ PVRGUIChannelIconUpdater.cpp
+ PVRGUIChannelNavigator.cpp
+ PVRGUIProgressHandler.cpp)
+
+set(HEADERS GUIEPGGridContainer.h
+ GUIEPGGridContainerModel.h
+ PVRGUIActionListener.h
+ PVRGUIActionsChannels.h
+ PVRGUIActionsClients.h
+ PVRGUIActionsDatabase.h
+ PVRGUIActionsEPG.h
+ PVRGUIActionsUtils.h
+ PVRGUIActionsParentalControl.h
+ PVRGUIActionsPlayback.h
+ PVRGUIActionsPowerManagement.h
+ PVRGUIActionsRecordings.h
+ PVRGUIActionsTimers.h
+ PVRGUIChannelIconUpdater.h
+ PVRGUIChannelNavigator.h
+ PVRGUIProgressHandler.h)
+
+core_add_library(pvr_guilib)
diff --git a/xbmc/pvr/guilib/GUIEPGGridContainer.cpp b/xbmc/pvr/guilib/GUIEPGGridContainer.cpp
new file mode 100644
index 0000000..d88dc86
--- /dev/null
+++ b/xbmc/pvr/guilib/GUIEPGGridContainer.cpp
@@ -0,0 +1,2549 @@
+/*
+ * 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 "GUIEPGGridContainer.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "guilib/DirtyRegion.h"
+#include "guilib/GUIAction.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pvr/PVRManager.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/guilib/GUIEPGGridContainerModel.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+
+#include <tinyxml.h>
+
+using namespace PVR;
+
+#define BLOCKJUMP 4 // how many blocks are jumped with each analogue scroll action
+static const int BLOCK_SCROLL_OFFSET = 60 / CGUIEPGGridContainerModel::MINSPERBLOCK; // how many blocks are jumped if we are at left/right edge of grid
+
+CGUIEPGGridContainer::CGUIEPGGridContainer(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ ORIENTATION orientation,
+ int scrollTime,
+ int preloadItems,
+ int timeBlocks,
+ int rulerUnit,
+ const CTextureInfo& progressIndicatorTexture)
+ : IGUIContainer(parentID, controlID, posX, posY, width, height),
+ m_orientation(orientation),
+ m_channelLayout(nullptr),
+ m_focusedChannelLayout(nullptr),
+ m_programmeLayout(nullptr),
+ m_focusedProgrammeLayout(nullptr),
+ m_rulerLayout(nullptr),
+ m_rulerDateLayout(nullptr),
+ m_pageControl(0),
+ m_rulerUnit(rulerUnit),
+ m_channelsPerPage(0),
+ m_programmesPerPage(0),
+ m_channelCursor(0),
+ m_channelOffset(0),
+ m_blocksPerPage(timeBlocks),
+ m_blockCursor(0),
+ m_blockOffset(0),
+ m_blockTravelAxis(0),
+ m_cacheChannelItems(preloadItems),
+ m_cacheProgrammeItems(preloadItems),
+ m_cacheRulerItems(preloadItems),
+ m_rulerDateHeight(0),
+ m_rulerDateWidth(0),
+ m_rulerPosX(0),
+ m_rulerPosY(0),
+ m_rulerHeight(0),
+ m_rulerWidth(0),
+ m_channelPosX(0),
+ m_channelPosY(0),
+ m_channelHeight(0),
+ m_channelWidth(0),
+ m_gridPosX(0),
+ m_gridPosY(0),
+ m_gridWidth(0),
+ m_gridHeight(0),
+ m_blockSize(0),
+ m_analogScrollCount(0),
+ m_guiProgressIndicatorTexture(
+ CGUITexture::CreateTexture(posX, posY, width, height, progressIndicatorTexture)),
+ m_scrollTime(scrollTime ? scrollTime : 1),
+ m_programmeScrollLastTime(0),
+ m_programmeScrollSpeed(0),
+ m_programmeScrollOffset(0),
+ m_channelScrollLastTime(0),
+ m_channelScrollSpeed(0),
+ m_channelScrollOffset(0),
+ m_gridModel(new CGUIEPGGridContainerModel)
+{
+ ControlType = GUICONTAINER_EPGGRID;
+}
+
+CGUIEPGGridContainer::CGUIEPGGridContainer(const CGUIEPGGridContainer& other)
+ : IGUIContainer(other),
+ m_renderOffset(other.m_renderOffset),
+ m_orientation(other.m_orientation),
+ m_channelLayouts(other.m_channelLayouts),
+ m_focusedChannelLayouts(other.m_focusedChannelLayouts),
+ m_focusedProgrammeLayouts(other.m_focusedProgrammeLayouts),
+ m_programmeLayouts(other.m_programmeLayouts),
+ m_rulerLayouts(other.m_rulerLayouts),
+ m_rulerDateLayouts(other.m_rulerDateLayouts),
+ m_channelLayout(other.m_channelLayout),
+ m_focusedChannelLayout(other.m_focusedChannelLayout),
+ m_programmeLayout(other.m_programmeLayout),
+ m_focusedProgrammeLayout(other.m_focusedProgrammeLayout),
+ m_rulerLayout(other.m_rulerLayout),
+ m_rulerDateLayout(other.m_rulerDateLayout),
+ m_pageControl(other.m_pageControl),
+ m_rulerUnit(other.m_rulerUnit),
+ m_channelsPerPage(other.m_channelsPerPage),
+ m_programmesPerPage(other.m_programmesPerPage),
+ m_channelCursor(other.m_channelCursor),
+ m_channelOffset(other.m_channelOffset),
+ m_blocksPerPage(other.m_blocksPerPage),
+ m_blockCursor(other.m_blockCursor),
+ m_blockOffset(other.m_blockOffset),
+ m_blockTravelAxis(other.m_blockTravelAxis),
+ m_cacheChannelItems(other.m_cacheChannelItems),
+ m_cacheProgrammeItems(other.m_cacheProgrammeItems),
+ m_cacheRulerItems(other.m_cacheRulerItems),
+ m_rulerDateHeight(other.m_rulerDateHeight),
+ m_rulerDateWidth(other.m_rulerDateWidth),
+ m_rulerPosX(other.m_rulerPosX),
+ m_rulerPosY(other.m_rulerPosY),
+ m_rulerHeight(other.m_rulerHeight),
+ m_rulerWidth(other.m_rulerWidth),
+ m_channelPosX(other.m_channelPosX),
+ m_channelPosY(other.m_channelPosY),
+ m_channelHeight(other.m_channelHeight),
+ m_channelWidth(other.m_channelWidth),
+ m_gridPosX(other.m_gridPosX),
+ m_gridPosY(other.m_gridPosY),
+ m_gridWidth(other.m_gridWidth),
+ m_gridHeight(other.m_gridHeight),
+ m_blockSize(other.m_blockSize),
+ m_analogScrollCount(other.m_analogScrollCount),
+ m_guiProgressIndicatorTexture(other.m_guiProgressIndicatorTexture->Clone()),
+ m_lastItem(other.m_lastItem),
+ m_lastChannel(other.m_lastChannel),
+ m_scrollTime(other.m_scrollTime),
+ m_programmeScrollLastTime(other.m_programmeScrollLastTime),
+ m_programmeScrollSpeed(other.m_programmeScrollSpeed),
+ m_programmeScrollOffset(other.m_programmeScrollOffset),
+ m_channelScrollLastTime(other.m_channelScrollLastTime),
+ m_channelScrollSpeed(other.m_channelScrollSpeed),
+ m_channelScrollOffset(other.m_channelScrollOffset),
+ m_gridModel(new CGUIEPGGridContainerModel(*other.m_gridModel)),
+ m_updatedGridModel(other.m_updatedGridModel
+ ? new CGUIEPGGridContainerModel(*other.m_updatedGridModel)
+ : nullptr),
+ m_itemStartBlock(other.m_itemStartBlock)
+{
+}
+
+bool CGUIEPGGridContainer::HasData() const
+{
+ return m_gridModel && m_gridModel->HasChannelItems();
+}
+
+void CGUIEPGGridContainer::AllocResources()
+{
+ IGUIContainer::AllocResources();
+ m_guiProgressIndicatorTexture->AllocResources();
+}
+
+void CGUIEPGGridContainer::FreeResources(bool immediately)
+{
+ m_guiProgressIndicatorTexture->FreeResources(immediately);
+ IGUIContainer::FreeResources(immediately);
+}
+
+void CGUIEPGGridContainer::SetPageControl(int id)
+{
+ m_pageControl = id;
+}
+
+void CGUIEPGGridContainer::Process(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ ValidateOffset();
+
+ if (m_bInvalidated)
+ {
+ UpdateLayout();
+
+ if (m_pageControl)
+ {
+ int iItemsPerPage;
+ int iTotalItems;
+
+ if (m_orientation == VERTICAL)
+ {
+ iItemsPerPage = m_channelsPerPage;
+ iTotalItems = m_gridModel->ChannelItemsSize();
+ }
+ else
+ {
+ iItemsPerPage = m_blocksPerPage;
+ iTotalItems = m_gridModel->GridItemsSize();
+ }
+
+ CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, iItemsPerPage, iTotalItems);
+ SendWindowMessage(msg);
+ }
+ }
+
+ UpdateScrollOffset(currentTime);
+ ProcessChannels(currentTime, dirtyregions);
+ ProcessRulerDate(currentTime, dirtyregions);
+ ProcessRuler(currentTime, dirtyregions);
+ ProcessProgrammeGrid(currentTime, dirtyregions);
+ ProcessProgressIndicator(currentTime, dirtyregions);
+
+ if (m_pageControl)
+ {
+ int iItem =
+ (m_orientation == VERTICAL)
+ ? MathUtils::round_int(static_cast<double>(m_channelScrollOffset / m_channelHeight))
+ : MathUtils::round_int(
+ static_cast<double>(m_programmeScrollOffset / (m_gridHeight / m_blocksPerPage)));
+
+ CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, iItem);
+ SendWindowMessage(msg);
+ }
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIEPGGridContainer::Render()
+{
+ RenderChannels();
+ RenderRulerDate();
+ RenderRuler();
+ RenderProgrammeGrid();
+ RenderProgressIndicator();
+
+ CGUIControl::Render();
+}
+
+void CGUIEPGGridContainer::ProcessChannels(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ HandleChannels(false, currentTime, dirtyregions);
+}
+
+void CGUIEPGGridContainer::RenderChannels()
+{
+ // params not needed for render.
+ unsigned int dummyTime = 0;
+ CDirtyRegionList dummyRegions;
+ HandleChannels(true, dummyTime, dummyRegions);
+}
+
+void CGUIEPGGridContainer::ProcessRulerDate(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ HandleRulerDate(false, currentTime, dirtyregions);
+}
+
+void CGUIEPGGridContainer::RenderRulerDate()
+{
+ // params not needed for render.
+ unsigned int dummyTime = 0;
+ CDirtyRegionList dummyRegions;
+ HandleRulerDate(true, dummyTime, dummyRegions);
+}
+
+void CGUIEPGGridContainer::ProcessRuler(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ HandleRuler(false, currentTime, dirtyregions);
+}
+
+void CGUIEPGGridContainer::RenderRuler()
+{
+ // params not needed for render.
+ unsigned int dummyTime = 0;
+ CDirtyRegionList dummyRegions;
+ HandleRuler(true, dummyTime, dummyRegions);
+}
+
+void CGUIEPGGridContainer::ProcessProgrammeGrid(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ HandleProgrammeGrid(false, currentTime, dirtyregions);
+}
+
+void CGUIEPGGridContainer::RenderProgrammeGrid()
+{
+ // params not needed for render.
+ unsigned int dummyTime = 0;
+ CDirtyRegionList dummyRegions;
+ HandleProgrammeGrid(true, dummyTime, dummyRegions);
+}
+
+float CGUIEPGGridContainer::GetCurrentTimePositionOnPage() const
+{
+ if (!m_gridModel->GetGridStart().IsValid())
+ return -1.0f;
+
+ const CDateTimeSpan startDelta(CDateTime::GetUTCDateTime() - m_gridModel->GetGridStart());
+ const float fPos = (startDelta.GetSecondsTotal() * m_blockSize) /
+ (CGUIEPGGridContainerModel::MINSPERBLOCK * 60) -
+ GetProgrammeScrollOffsetPos();
+ return std::min(fPos, m_orientation == VERTICAL ? m_gridWidth : m_gridHeight);
+}
+
+float CGUIEPGGridContainer::GetProgressIndicatorWidth() const
+{
+ return (m_orientation == VERTICAL) ? GetCurrentTimePositionOnPage() : m_rulerWidth + m_gridWidth;
+}
+
+float CGUIEPGGridContainer::GetProgressIndicatorHeight() const
+{
+ return (m_orientation == VERTICAL) ? m_rulerHeight + m_gridHeight : GetCurrentTimePositionOnPage();
+}
+
+void CGUIEPGGridContainer::ProcessProgressIndicator(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ float width = GetProgressIndicatorWidth();
+ float height = GetProgressIndicatorHeight();
+
+ if (width > 0 && height > 0)
+ {
+ m_guiProgressIndicatorTexture->SetVisible(true);
+ m_guiProgressIndicatorTexture->SetPosition(m_rulerPosX + m_renderOffset.x,
+ m_rulerPosY + m_renderOffset.y);
+ m_guiProgressIndicatorTexture->SetWidth(width);
+ m_guiProgressIndicatorTexture->SetHeight(height);
+ }
+ else
+ {
+ m_guiProgressIndicatorTexture->SetVisible(false);
+ }
+
+ m_guiProgressIndicatorTexture->Process(currentTime);
+}
+
+void CGUIEPGGridContainer::RenderProgressIndicator()
+{
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_rulerPosX, m_rulerPosY, GetProgressIndicatorWidth(), GetProgressIndicatorHeight()))
+ {
+ m_guiProgressIndicatorTexture->SetDiffuseColor(m_diffuseColor);
+ m_guiProgressIndicatorTexture->Render();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+}
+
+void CGUIEPGGridContainer::ProcessItem(float posX, float posY, const CFileItemPtr& item, CFileItemPtr& lastitem,
+ bool focused, CGUIListItemLayout* normallayout, CGUIListItemLayout* focusedlayout,
+ unsigned int currentTime, CDirtyRegionList& dirtyregions, float resize /* = -1.0f */)
+{
+ if (!normallayout || !focusedlayout)
+ return;
+
+ // set the origin
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
+
+ if (m_bInvalidated)
+ item->SetInvalid();
+
+ if (focused)
+ {
+ if (!item->GetFocusedLayout())
+ {
+ item->SetFocusedLayout(std::make_unique<CGUIListItemLayout>(*focusedlayout, this));
+ }
+
+ if (resize != -1.0f)
+ {
+ if (m_orientation == VERTICAL)
+ item->GetFocusedLayout()->SetWidth(resize);
+ else
+ item->GetFocusedLayout()->SetHeight(resize);
+ }
+
+ if (item != lastitem || !HasFocus())
+ item->GetFocusedLayout()->SetFocusedItem(0);
+
+ if (item != lastitem && HasFocus())
+ {
+ item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
+
+ unsigned int subItem = 1;
+ if (lastitem && lastitem->GetFocusedLayout())
+ subItem = lastitem->GetFocusedLayout()->GetFocusedItem();
+
+ item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
+ }
+
+ item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ lastitem = item;
+ }
+ else
+ {
+ if (!item->GetLayout())
+ {
+ item->SetLayout(std::make_unique<CGUIListItemLayout>(*normallayout, this));
+ }
+
+ if (resize != -1.0f)
+ {
+ if (m_orientation == VERTICAL)
+ item->GetLayout()->SetWidth(resize);
+ else
+ item->GetLayout()->SetHeight(resize);
+ }
+
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->SetFocusedItem(0);
+
+ if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
+ item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ else
+ item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+void CGUIEPGGridContainer::RenderItem(float posX, float posY, CGUIListItem* item, bool focused)
+{
+ // set the origin
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
+
+ if (focused)
+ {
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->Render(item, m_parentID);
+ }
+ else
+ {
+ if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
+ item->GetFocusedLayout()->Render(item, m_parentID);
+ else if (item->GetLayout())
+ item->GetLayout()->Render(item, m_parentID);
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+bool CGUIEPGGridContainer::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_LEFT:
+ case ACTION_MOVE_RIGHT:
+ case ACTION_MOVE_DOWN:
+ case ACTION_MOVE_UP:
+ case ACTION_NAV_BACK:
+ // use base class implementation
+ return CGUIControl::OnAction(action);
+
+ case ACTION_NEXT_ITEM:
+ // skip +12h
+ ScrollToBlockOffset(m_blockOffset + (12 * 60 / CGUIEPGGridContainerModel::MINSPERBLOCK));
+ SetBlock(m_blockCursor);
+ return true;
+
+ case ACTION_PREV_ITEM:
+ // skip -12h
+ ScrollToBlockOffset(m_blockOffset - (12 * 60 / CGUIEPGGridContainerModel::MINSPERBLOCK));
+ SetBlock(m_blockCursor);
+ return true;
+
+ case REMOTE_0:
+ GoToNow();
+ return true;
+
+ case ACTION_PAGE_UP:
+ if (m_orientation == VERTICAL)
+ {
+ if (m_channelOffset == 0)
+ {
+ // already on the first page, so move to the first item
+ SetChannel(0);
+ }
+ else
+ {
+ // scroll up to the previous page
+ ChannelScroll(-m_channelsPerPage);
+ }
+ }
+ else
+ {
+ if (m_blockOffset == 0)
+ {
+ // already on the first page, so move to the first item
+ SetBlock(0);
+ }
+ else
+ {
+ // scroll up to the previous page
+ ProgrammesScroll(-m_blocksPerPage);
+ }
+ }
+ return true;
+
+ case ACTION_PAGE_DOWN:
+ if (m_orientation == VERTICAL)
+ {
+ if (m_channelOffset == m_gridModel->ChannelItemsSize() - m_channelsPerPage ||
+ m_gridModel->ChannelItemsSize() < m_channelsPerPage)
+ {
+ // already at the last page, so move to the last item.
+ SetChannel(m_gridModel->GetLastChannel() - m_channelOffset);
+ }
+ else
+ {
+ // scroll down to the next page
+ ChannelScroll(m_channelsPerPage);
+ }
+ }
+ else
+ {
+ if (m_blockOffset == m_gridModel->GridItemsSize() - m_blocksPerPage ||
+ m_gridModel->GridItemsSize() < m_blocksPerPage)
+ {
+ // already at the last page, so move to the last item.
+ SetBlock(m_gridModel->GetLastBlock() - m_blockOffset);
+ }
+ else
+ {
+ // scroll down to the next page
+ ProgrammesScroll(m_blocksPerPage);
+ }
+ }
+
+ return true;
+
+ // smooth scrolling (for analog controls)
+ case ACTION_TELETEXT_RED:
+ case ACTION_TELETEXT_GREEN:
+ case ACTION_SCROLL_UP: // left horizontal scrolling
+ if (m_orientation == VERTICAL)
+ {
+ int blocksToJump = action.GetID() == ACTION_TELETEXT_RED ? m_blocksPerPage / 2 : m_blocksPerPage / 4;
+
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+
+ if (m_blockOffset > 0 && m_blockCursor <= m_blocksPerPage / 2)
+ ProgrammesScroll(-blocksToJump);
+ else if (m_blockCursor > blocksToJump)
+ SetBlock(m_blockCursor - blocksToJump);
+ }
+ return handled;
+ }
+ else
+ {
+ int channelsToJump = action.GetID() == ACTION_TELETEXT_RED ? m_channelsPerPage / 2 : m_channelsPerPage / 4;
+
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+
+ if (m_channelOffset > 0 && m_channelCursor <= m_channelsPerPage / 2)
+ ChannelScroll(-channelsToJump);
+ else if (m_channelCursor > channelsToJump)
+ SetChannel(m_channelCursor - channelsToJump);
+ }
+ return handled;
+ }
+ break;
+
+ case ACTION_TELETEXT_BLUE:
+ case ACTION_TELETEXT_YELLOW:
+ case ACTION_SCROLL_DOWN: // right horizontal scrolling
+ if (m_orientation == VERTICAL)
+ {
+ int blocksToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_blocksPerPage / 2 : m_blocksPerPage / 4;
+
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+
+ if (m_blockOffset + m_blocksPerPage < m_gridModel->GridItemsSize() &&
+ m_blockCursor >= m_blocksPerPage / 2)
+ ProgrammesScroll(blocksToJump);
+ else if (m_blockCursor < m_blocksPerPage - blocksToJump &&
+ m_blockOffset + m_blockCursor < m_gridModel->GridItemsSize() - blocksToJump)
+ SetBlock(m_blockCursor + blocksToJump);
+ }
+ return handled;
+ }
+ else
+ {
+ int channelsToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_channelsPerPage / 2 : m_channelsPerPage / 4;
+
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+
+ if (m_channelOffset + m_channelsPerPage < m_gridModel->ChannelItemsSize() && m_channelCursor >= m_channelsPerPage / 2)
+ ChannelScroll(channelsToJump);
+ else if (m_channelCursor < m_channelsPerPage - channelsToJump && m_channelOffset + m_channelCursor < m_gridModel->ChannelItemsSize() - channelsToJump)
+ SetChannel(m_channelCursor + channelsToJump);
+ }
+ return handled;
+ }
+ break;
+
+ default:
+ if (action.GetID())
+ return OnClick(action.GetID());
+
+ break;
+ }
+
+ return false;
+}
+
+bool CGUIEPGGridContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID())
+ {
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_PAGE_CHANGE:
+ if (message.GetSenderId() == m_pageControl && IsVisible())
+ {
+ if (m_orientation == VERTICAL)
+ {
+ ScrollToChannelOffset(message.GetParam1());
+ SetChannel(m_channelCursor);
+ }
+ else
+ {
+ ScrollToBlockOffset(message.GetParam1());
+ SetBlock(m_blockCursor);
+ }
+ return true;
+ }
+ break;
+
+ case GUI_MSG_LABEL_BIND:
+ UpdateItems();
+ return true;
+
+ case GUI_MSG_REFRESH_LIST:
+ // update our list contents
+ m_gridModel->SetInvalid();
+ break;
+ }
+ }
+
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIEPGGridContainer::UpdateItems()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (!m_updatedGridModel)
+ return;
+
+ // Save currently selected epg tag and grid coordinates. Selection shall be restored after update.
+ std::shared_ptr<CPVREpgInfoTag> prevSelectedEpgTag;
+ if (HasData())
+ prevSelectedEpgTag =
+ m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset)
+ ->GetEPGInfoTag();
+
+ const int oldChannelIndex = m_channelOffset + m_channelCursor;
+ const int oldBlockIndex = m_blockOffset + m_blockCursor;
+ const CDateTime oldGridStart(m_gridModel->GetGridStart());
+ int eventOffset = oldBlockIndex;
+ int newChannelIndex = oldChannelIndex;
+ int newBlockIndex = oldBlockIndex;
+ int channelUid = -1;
+ unsigned int broadcastUid = 0;
+
+ if (prevSelectedEpgTag)
+ {
+ // get the block offset relative to the first block of the selected event
+ eventOffset =
+ oldBlockIndex - m_gridModel->GetGridItemStartBlock(oldChannelIndex, oldBlockIndex);
+
+ if (!prevSelectedEpgTag->IsGapTag()) // "normal" tag selected
+ {
+ if (oldGridStart >= prevSelectedEpgTag->StartAsUTC())
+ {
+ // start of previously selected event is before grid start
+ newBlockIndex = eventOffset;
+ }
+ else
+ {
+ newBlockIndex = m_gridModel->GetFirstEventBlock(prevSelectedEpgTag) + eventOffset;
+ }
+
+ channelUid = prevSelectedEpgTag->UniqueChannelID();
+ broadcastUid = prevSelectedEpgTag->UniqueBroadcastID();
+ }
+ else // "gap" tag selected
+ {
+ channelUid = prevSelectedEpgTag->UniqueChannelID();
+
+ // As gap tags do not have a unique broadcast id, we will look for the real tag preceding
+ // the gap tag and add the respective offset to restore the gap tag selection, assuming that
+ // the real tag is still the predecessor of the gap tag after the grid model update.
+
+ const std::shared_ptr<CFileItem> prevItem = GetPrevItem().first;
+ if (prevItem)
+ {
+ const std::shared_ptr<CPVREpgInfoTag> tag = prevItem->GetEPGInfoTag();
+ if (tag && !tag->IsGapTag())
+ {
+ if (oldGridStart >= tag->StartAsUTC())
+ {
+ // start of previously selected event is before grid start
+ newBlockIndex = eventOffset;
+ }
+ else
+ {
+ newBlockIndex = m_gridModel->GetFirstEventBlock(tag);
+ eventOffset += m_gridModel->GetFirstEventBlock(prevSelectedEpgTag) - newBlockIndex;
+ }
+
+ broadcastUid = tag->UniqueBroadcastID();
+ }
+ }
+ }
+ }
+
+ m_lastItem = nullptr;
+ m_lastChannel = nullptr;
+
+ // always use asynchronously precalculated grid data.
+ m_gridModel = std::move(m_updatedGridModel);
+
+ if (prevSelectedEpgTag)
+ {
+ if (oldGridStart != m_gridModel->GetGridStart())
+ {
+ // grid start changed. block offset for selected event might have changed.
+ newBlockIndex += m_gridModel->GetBlock(oldGridStart);
+ if (newBlockIndex < 0 || newBlockIndex > m_gridModel->GetLastBlock())
+ {
+ // previous selection is no longer in grid.
+ SetInvalid();
+ m_bEnableChannelScrolling = false;
+ GoToChannel(newChannelIndex);
+ m_bEnableProgrammeScrolling = false;
+ GoToNow();
+ return;
+ }
+ }
+
+ if (newChannelIndex >= m_gridModel->ChannelItemsSize() ||
+ newBlockIndex >= m_gridModel->GridItemsSize() ||
+ m_gridModel->GetGridItem(newChannelIndex, newBlockIndex)->GetEPGInfoTag() !=
+ prevSelectedEpgTag)
+ {
+ int iChannelIndex = CGUIEPGGridContainerModel::INVALID_INDEX;
+ int iBlockIndex = CGUIEPGGridContainerModel::INVALID_INDEX;
+ m_gridModel->FindChannelAndBlockIndex(channelUid, broadcastUid, eventOffset, iChannelIndex, iBlockIndex);
+
+ if (iBlockIndex != CGUIEPGGridContainerModel::INVALID_INDEX)
+ {
+ newBlockIndex = iBlockIndex;
+ }
+ else if (newBlockIndex > m_gridModel->GetLastBlock())
+ {
+ // default to now
+ newBlockIndex = m_gridModel->GetNowBlock();
+
+ if (newBlockIndex > m_gridModel->GetLastBlock())
+ {
+ // last block is in the past. default to last block
+ newBlockIndex = m_gridModel->GetLastBlock();
+ }
+ }
+
+ if (iChannelIndex != CGUIEPGGridContainerModel::INVALID_INDEX)
+ {
+ newChannelIndex = iChannelIndex;
+ }
+ else if (newChannelIndex >= m_gridModel->ChannelItemsSize() ||
+ (m_gridModel->GetGridItem(newChannelIndex, newBlockIndex)->GetEPGInfoTag()->UniqueChannelID() != prevSelectedEpgTag->UniqueChannelID() &&
+ m_gridModel->GetGridItem(newChannelIndex, newBlockIndex)->GetEPGInfoTag()->ClientID() != prevSelectedEpgTag->ClientID()))
+ {
+ // default to first channel
+ newChannelIndex = 0;
+ }
+ }
+
+ // restore previous selection.
+ if (newChannelIndex == oldChannelIndex && newBlockIndex == oldBlockIndex)
+ {
+ // same coordinates, keep current grid view port
+ UpdateItem();
+ }
+ else
+ {
+ // new coordinates, move grid view port accordingly
+ SetInvalid();
+
+ if (newBlockIndex != oldBlockIndex)
+ {
+ m_bEnableProgrammeScrolling = false;
+ GoToBlock(newBlockIndex);
+ }
+
+ if (newChannelIndex != oldChannelIndex)
+ {
+ m_bEnableChannelScrolling = false;
+ GoToChannel(newChannelIndex);
+ }
+ }
+ }
+ else
+ {
+ // no previous selection, goto now
+ SetInvalid();
+ m_bEnableProgrammeScrolling = false;
+ GoToNow();
+ }
+}
+
+float CGUIEPGGridContainer::GetChannelScrollOffsetPos() const
+{
+ if (m_bEnableChannelScrolling)
+ return m_channelScrollOffset;
+ else
+ return m_channelOffset * m_channelLayout->Size(m_orientation);
+}
+
+float CGUIEPGGridContainer::GetProgrammeScrollOffsetPos() const
+{
+ if (m_bEnableProgrammeScrolling)
+ return m_programmeScrollOffset;
+ else
+ return m_blockOffset * m_blockSize;
+}
+
+int CGUIEPGGridContainer::GetChannelScrollOffset(CGUIListItemLayout* layout) const
+{
+ if (m_bEnableChannelScrolling)
+ return MathUtils::round_int(
+ static_cast<double>(m_channelScrollOffset / layout->Size(m_orientation)));
+ else
+ return m_channelOffset;
+}
+
+int CGUIEPGGridContainer::GetProgrammeScrollOffset() const
+{
+ if (m_bEnableProgrammeScrolling)
+ return MathUtils::round_int(static_cast<double>(m_programmeScrollOffset / m_blockSize));
+ else
+ return m_blockOffset;
+}
+
+void CGUIEPGGridContainer::ChannelScroll(int amount)
+{
+ // increase or decrease the vertical offset
+ int offset = m_channelOffset + amount;
+
+ if (offset > m_gridModel->ChannelItemsSize() - m_channelsPerPage)
+ offset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
+
+ if (offset < 0)
+ offset = 0;
+
+ ScrollToChannelOffset(offset);
+ SetChannel(m_channelCursor);
+}
+
+void CGUIEPGGridContainer::ProgrammesScroll(int amount)
+{
+ // increase or decrease the horizontal offset
+ ScrollToBlockOffset(m_blockOffset + amount);
+ SetBlock(m_blockCursor);
+}
+
+void CGUIEPGGridContainer::OnUp()
+{
+ if (!HasData())
+ return CGUIControl::OnUp();
+
+ if (m_orientation == VERTICAL)
+ {
+ CGUIAction action = GetAction(ACTION_MOVE_UP);
+ if (m_channelCursor > 0)
+ {
+ SetChannel(m_channelCursor - 1);
+ }
+ else if (m_channelCursor == 0 && m_channelOffset)
+ {
+ ScrollToChannelOffset(m_channelOffset - 1);
+ SetChannel(0);
+ }
+ else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
+ {
+ int offset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
+
+ if (offset < 0)
+ offset = 0;
+
+ SetChannel(m_gridModel->GetLastChannel() - offset);
+ ScrollToChannelOffset(offset);
+ }
+ else
+ CGUIControl::OnUp();
+ }
+ else
+ {
+ if (m_gridModel->GetGridItemStartBlock(m_channelCursor + m_channelOffset,
+ m_blockCursor + m_blockOffset) > m_blockOffset)
+ {
+ // this is not first item on page
+ SetItem(GetPrevItem());
+ UpdateBlock();
+ return;
+ }
+ else if (m_blockCursor <= 0 && m_blockOffset && m_blockOffset - BLOCK_SCROLL_OFFSET >= 0)
+ {
+ // this is the first item on page
+ ScrollToBlockOffset(m_blockOffset - BLOCK_SCROLL_OFFSET);
+ UpdateBlock();
+ return;
+ }
+
+ CGUIControl::OnUp();
+ }
+}
+
+void CGUIEPGGridContainer::OnDown()
+{
+ if (!HasData())
+ return CGUIControl::OnDown();
+
+ if (m_orientation == VERTICAL)
+ {
+ CGUIAction action = GetAction(ACTION_MOVE_DOWN);
+ if (m_channelOffset + m_channelCursor < m_gridModel->GetLastChannel())
+ {
+ if (m_channelCursor + 1 < m_channelsPerPage)
+ {
+ SetChannel(m_channelCursor + 1);
+ }
+ else
+ {
+ ScrollToChannelOffset(m_channelOffset + 1);
+ SetChannel(m_channelsPerPage - 1);
+ }
+ }
+ else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
+ {
+ ScrollToChannelOffset(0);
+ SetChannel(0);
+ }
+ else
+ CGUIControl::OnDown();
+ }
+ else
+ {
+ if (m_gridModel->GetGridItemEndBlock(m_channelCursor + m_channelOffset,
+ m_blockCursor + m_blockOffset) <
+ (m_blockOffset + m_blocksPerPage - 1))
+ {
+ // this is not last item on page
+ SetItem(GetNextItem());
+ UpdateBlock();
+ return;
+ }
+ else if ((m_blockOffset != m_gridModel->GridItemsSize() - m_blocksPerPage) &&
+ m_gridModel->GridItemsSize() > m_blocksPerPage &&
+ m_blockOffset + BLOCK_SCROLL_OFFSET < m_gridModel->GetLastBlock())
+ {
+ // this is the last item on page
+ ScrollToBlockOffset(m_blockOffset + BLOCK_SCROLL_OFFSET);
+ UpdateBlock();
+ return;
+ }
+
+ CGUIControl::OnDown();
+ }
+}
+
+void CGUIEPGGridContainer::OnLeft()
+{
+ if (!HasData())
+ return CGUIControl::OnLeft();
+
+ if (m_orientation == VERTICAL)
+ {
+ if (m_gridModel->GetGridItemStartBlock(m_channelCursor + m_channelOffset,
+ m_blockCursor + m_blockOffset) > m_blockOffset)
+ {
+ // this is not first item on page
+ SetItem(GetPrevItem());
+ UpdateBlock();
+ return;
+ }
+ else if (m_blockCursor <= 0 && m_blockOffset && m_blockOffset - BLOCK_SCROLL_OFFSET >= 0)
+ {
+ // this is the first item on page
+ ScrollToBlockOffset(m_blockOffset - BLOCK_SCROLL_OFFSET);
+ UpdateBlock();
+ return;
+ }
+
+ CGUIControl::OnLeft();
+ }
+ else
+ {
+ CGUIAction action = GetAction(ACTION_MOVE_LEFT);
+ if (m_channelCursor > 0)
+ {
+ SetChannel(m_channelCursor - 1);
+ }
+ else if (m_channelCursor == 0 && m_channelOffset)
+ {
+ ScrollToChannelOffset(m_channelOffset - 1);
+ SetChannel(0);
+ }
+ else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
+ {
+ int offset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
+
+ if (offset < 0)
+ offset = 0;
+
+ SetChannel(m_gridModel->GetLastChannel() - offset);
+ ScrollToChannelOffset(offset);
+ }
+ else
+ CGUIControl::OnLeft();
+ }
+}
+
+void CGUIEPGGridContainer::OnRight()
+{
+ if (!HasData())
+ return CGUIControl::OnRight();
+
+ if (m_orientation == VERTICAL)
+ {
+ if (m_gridModel->GetGridItemEndBlock(m_channelCursor + m_channelOffset,
+ m_blockCursor + m_blockOffset) <
+ (m_blockOffset + m_blocksPerPage - 1))
+ {
+ // this is not last item on page
+ SetItem(GetNextItem());
+ UpdateBlock();
+ return;
+ }
+ else if ((m_blockOffset != m_gridModel->GridItemsSize() - m_blocksPerPage) &&
+ m_gridModel->GridItemsSize() > m_blocksPerPage &&
+ m_blockOffset + BLOCK_SCROLL_OFFSET < m_gridModel->GetLastBlock())
+ {
+ // this is the last item on page
+ ScrollToBlockOffset(m_blockOffset + BLOCK_SCROLL_OFFSET);
+ UpdateBlock();
+ return;
+ }
+
+ CGUIControl::OnRight();
+ }
+ else
+ {
+ CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
+ if (m_channelOffset + m_channelCursor < m_gridModel->GetLastChannel())
+ {
+ if (m_channelCursor + 1 < m_channelsPerPage)
+ {
+ SetChannel(m_channelCursor + 1);
+ }
+ else
+ {
+ ScrollToChannelOffset(m_channelOffset + 1);
+ SetChannel(m_channelsPerPage - 1);
+ }
+ }
+ else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
+ {
+ SetChannel(0);
+ ScrollToChannelOffset(0);
+ }
+ else
+ CGUIControl::OnRight();
+ }
+}
+
+bool CGUIEPGGridContainer::SetChannel(const std::string& channel)
+{
+ for (int iIndex = 0; iIndex < m_gridModel->ChannelItemsSize(); iIndex++)
+ {
+ std::string strPath = m_gridModel->GetChannelItem(iIndex)->GetProperty("path").asString();
+ if (strPath == channel)
+ {
+ GoToChannel(iIndex);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIEPGGridContainer::SetChannel(const std::shared_ptr<CPVRChannel>& channel)
+{
+ for (int iIndex = 0; iIndex < m_gridModel->ChannelItemsSize(); iIndex++)
+ {
+ int iChannelId = static_cast<int>(m_gridModel->GetChannelItem(iIndex)->GetProperty("channelid").asInteger(-1));
+ if (iChannelId == channel->ChannelID())
+ {
+ GoToChannel(iIndex);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIEPGGridContainer::SetChannel(const CPVRChannelNumber& channelNumber)
+{
+ for (int iIndex = 0; iIndex < m_gridModel->ChannelItemsSize(); iIndex++)
+ {
+ const CPVRChannelNumber& number =
+ m_gridModel->GetChannelItem(iIndex)->GetPVRChannelGroupMemberInfoTag()->ChannelNumber();
+ if (number == channelNumber)
+ {
+ GoToChannel(iIndex);
+ return true;
+ }
+ }
+ return false;
+}
+
+void CGUIEPGGridContainer::SetChannel(int channel)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ int channelIndex = channel + m_channelOffset;
+ int blockIndex = m_blockCursor + m_blockOffset;
+ if (channelIndex < m_gridModel->ChannelItemsSize() && blockIndex < m_gridModel->GridItemsSize())
+ {
+ if (SetItem(m_gridModel->GetGridItem(channelIndex, m_blockTravelAxis), channelIndex,
+ m_blockTravelAxis))
+ {
+ m_channelCursor = channel;
+ MarkDirtyRegion();
+ UpdateBlock(false);
+ }
+ }
+}
+
+void CGUIEPGGridContainer::SetBlock(int block, bool bUpdateBlockTravelAxis /* = true */)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (block < 0)
+ m_blockCursor = 0;
+ else if (block > m_blocksPerPage - 1)
+ m_blockCursor = m_blocksPerPage - 1;
+ else
+ m_blockCursor = block;
+
+ if (bUpdateBlockTravelAxis)
+ m_blockTravelAxis = m_blockOffset + m_blockCursor;
+
+ UpdateItem();
+ MarkDirtyRegion();
+}
+
+void CGUIEPGGridContainer::UpdateBlock(bool bUpdateBlockTravelAxis /* = true */)
+{
+ SetBlock(m_itemStartBlock > 0 ? m_itemStartBlock - m_blockOffset : 0, bUpdateBlockTravelAxis);
+}
+
+CGUIListItemLayout* CGUIEPGGridContainer::GetFocusedLayout() const
+{
+ CGUIListItemPtr item = GetListItem(0);
+
+ if (item)
+ return item->GetFocusedLayout();
+
+ return nullptr;
+}
+
+bool CGUIEPGGridContainer::SelectItemFromPoint(const CPoint& point, bool justGrid /* = false */)
+{
+ /* point has already had origin set to m_posX, m_posY */
+ if (!m_focusedProgrammeLayout || !m_programmeLayout || (justGrid && point.x < 0))
+ return false;
+
+ int channel;
+ int block;
+
+ if (m_orientation == VERTICAL)
+ {
+ channel = point.y / m_channelHeight;
+ block = point.x / m_blockSize;
+ }
+ else
+ {
+ channel = point.x / m_channelWidth;
+ block = point.y / m_blockSize;
+ }
+
+ if (channel > m_channelsPerPage)
+ channel = m_channelsPerPage - 1;
+
+ if (channel >= m_gridModel->ChannelItemsSize())
+ channel = m_gridModel->GetLastChannel();
+
+ if (channel < 0)
+ channel = 0;
+
+ if (block > m_blocksPerPage)
+ block = m_blocksPerPage - 1;
+
+ if (block < 0)
+ block = 0;
+
+ int channelIndex = channel + m_channelOffset;
+ int blockIndex = block + m_blockOffset;
+
+ // bail if out of range
+ if (channelIndex >= m_gridModel->ChannelItemsSize() || blockIndex >= m_gridModel->GridItemsSize())
+ return false;
+
+ // bail if block isn't occupied
+ if (!m_gridModel->GetGridItem(channelIndex, blockIndex))
+ return false;
+
+ SetChannel(channel);
+ SetBlock(block);
+ return true;
+}
+
+EVENT_RESULT CGUIEPGGridContainer::OnMouseEvent(const CPoint& point, const CMouseEvent& event)
+{
+ switch (event.m_id)
+ {
+ case ACTION_MOUSE_LEFT_CLICK:
+ OnMouseClick(0, point);
+ return EVENT_RESULT_HANDLED;
+ case ACTION_MOUSE_RIGHT_CLICK:
+ OnMouseClick(1, point);
+ return EVENT_RESULT_HANDLED;
+ case ACTION_MOUSE_DOUBLE_CLICK:
+ OnMouseDoubleClick(0, point);
+ return EVENT_RESULT_HANDLED;
+ case ACTION_MOUSE_WHEEL_UP:
+ OnMouseWheel(-1, point);
+ return EVENT_RESULT_HANDLED;
+ case ACTION_MOUSE_WHEEL_DOWN:
+ OnMouseWheel(1, point);
+ return EVENT_RESULT_HANDLED;
+ case ACTION_GESTURE_BEGIN:
+ {
+ // we want exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ case ACTION_GESTURE_END:
+ case ACTION_GESTURE_ABORT:
+ {
+ // we're done with exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ ScrollToChannelOffset(MathUtils::round_int(
+ static_cast<double>(m_channelScrollOffset / m_channelLayout->Size(m_orientation))));
+ SetChannel(m_channelCursor);
+ ScrollToBlockOffset(
+ MathUtils::round_int(static_cast<double>(m_programmeScrollOffset / m_blockSize)));
+ SetBlock(m_blockCursor);
+ return EVENT_RESULT_HANDLED;
+ }
+ case ACTION_GESTURE_PAN:
+ {
+ m_programmeScrollOffset -= event.m_offsetX;
+ m_channelScrollOffset -= event.m_offsetY;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_channelOffset = MathUtils::round_int(
+ static_cast<double>(m_channelScrollOffset / m_channelLayout->Size(m_orientation)));
+ m_blockOffset =
+ MathUtils::round_int(static_cast<double>(m_programmeScrollOffset / m_blockSize));
+ ValidateOffset();
+ }
+ return EVENT_RESULT_HANDLED;
+ }
+ default:
+ return EVENT_RESULT_UNHANDLED;
+ }
+}
+
+bool CGUIEPGGridContainer::OnMouseOver(const CPoint& point)
+{
+ // select the item under the pointer
+ SelectItemFromPoint(point - CPoint(m_gridPosX, m_gridPosY), false);
+ return CGUIControl::OnMouseOver(point);
+}
+
+bool CGUIEPGGridContainer::OnMouseClick(int dwButton, const CPoint& point)
+{
+ if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_gridPosY)))
+ {
+ // send click message to window
+ OnClick(ACTION_MOUSE_LEFT_CLICK + dwButton);
+ return true;
+ }
+ return false;
+}
+
+bool CGUIEPGGridContainer::OnMouseDoubleClick(int dwButton, const CPoint& point)
+{
+ if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_gridPosY)))
+ {
+ // send double click message to window
+ OnClick(ACTION_MOUSE_DOUBLE_CLICK + dwButton);
+ return true;
+ }
+ return false;
+}
+
+bool CGUIEPGGridContainer::OnClick(int actionID)
+{
+ int subItem = 0;
+
+ if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
+ {
+ // grab the currently focused subitem (if applicable)
+ CGUIListItemLayout* focusedLayout = GetFocusedLayout();
+
+ if (focusedLayout)
+ subItem = focusedLayout->GetFocusedItem();
+ }
+
+ // Don't know what to do, so send to our parent window.
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
+ return SendWindowMessage(msg);
+}
+
+bool CGUIEPGGridContainer::OnMouseWheel(char wheel, const CPoint& point)
+{
+ // doesn't work while an item is selected?
+ ProgrammesScroll(-wheel);
+ return true;
+}
+
+std::shared_ptr<CPVRChannelGroupMember> CGUIEPGGridContainer::GetSelectedChannelGroupMember() const
+{
+ CFileItemPtr fileItem;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_channelCursor + m_channelOffset < m_gridModel->ChannelItemsSize())
+ fileItem = m_gridModel->GetChannelItem(m_channelCursor + m_channelOffset);
+ }
+
+ if (fileItem)
+ return fileItem->GetPVRChannelGroupMemberInfoTag();
+
+ return {};
+}
+
+CDateTime CGUIEPGGridContainer::GetSelectedDate() const
+{
+ return m_gridModel->GetStartTimeForBlock(m_blockOffset + m_blockCursor);
+}
+
+CFileItemPtr CGUIEPGGridContainer::GetSelectedGridItem(int offset /*= 0*/) const
+{
+ CFileItemPtr item;
+
+ if (m_channelCursor + m_channelOffset + offset < m_gridModel->ChannelItemsSize() &&
+ m_blockCursor + m_blockOffset < m_gridModel->GridItemsSize())
+ item = m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset);
+
+ return item;
+}
+
+
+CGUIListItemPtr CGUIEPGGridContainer::GetListItem(int offset, unsigned int flag) const
+{
+ if (!m_gridModel->HasChannelItems())
+ return CGUIListItemPtr();
+
+ int item = m_channelCursor + m_channelOffset + offset;
+ if (flag & INFOFLAG_LISTITEM_POSITION)
+ item = GetChannelScrollOffset(m_channelLayout);
+
+ if (flag & INFOFLAG_LISTITEM_WRAP)
+ {
+ item %= m_gridModel->ChannelItemsSize();
+ if (item < 0)
+ item += m_gridModel->ChannelItemsSize();
+
+ return m_gridModel->GetChannelItem(item);
+ }
+ else
+ {
+ if (item >= 0 && item < m_gridModel->ChannelItemsSize())
+ return m_gridModel->GetChannelItem(item);
+ }
+ return CGUIListItemPtr();
+}
+
+std::string CGUIEPGGridContainer::GetLabel(int info) const
+{
+ std::string label;
+ switch (info)
+ {
+ case CONTAINER_NUM_PAGES:
+ if (m_channelsPerPage > 0)
+ label = std::to_string((m_gridModel->ChannelItemsSize() + m_channelsPerPage - 1) /
+ m_channelsPerPage);
+ else
+ label = std::to_string(0);
+ break;
+ case CONTAINER_CURRENT_PAGE:
+ if (m_channelsPerPage > 0)
+ label = std::to_string(1 + (m_channelCursor + m_channelOffset) / m_channelsPerPage);
+ else
+ label = std::to_string(1);
+ break;
+ case CONTAINER_POSITION:
+ label = std::to_string(1 + m_channelCursor + m_channelOffset);
+ break;
+ case CONTAINER_NUM_ITEMS:
+ label = std::to_string(m_gridModel->ChannelItemsSize());
+ break;
+ default:
+ break;
+ }
+ return label;
+}
+
+void CGUIEPGGridContainer::SetItem(const std::pair<std::shared_ptr<CFileItem>, int>& itemInfo)
+{
+ SetItem(itemInfo.first, m_channelCursor + m_channelOffset, itemInfo.second);
+}
+
+bool CGUIEPGGridContainer::SetItem(const std::shared_ptr<CFileItem>& item,
+ int channelIndex,
+ int blockIndex)
+{
+ if (item && channelIndex < m_gridModel->ChannelItemsSize() &&
+ blockIndex < m_gridModel->GridItemsSize())
+ {
+ m_itemStartBlock = m_gridModel->GetGridItemStartBlock(channelIndex, blockIndex);
+ return true;
+ }
+ else
+ {
+ m_itemStartBlock = 0;
+ return false;
+ }
+}
+
+std::shared_ptr<CFileItem> CGUIEPGGridContainer::GetItem() const
+{
+ const int channelIndex = m_channelCursor + m_channelOffset;
+ const int blockIndex = m_blockCursor + m_blockOffset;
+
+ if (channelIndex >= m_gridModel->ChannelItemsSize() || blockIndex >= m_gridModel->GridItemsSize())
+ return {};
+
+ return m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset);
+}
+
+std::pair<std::shared_ptr<CFileItem>, int> CGUIEPGGridContainer::GetNextItem() const
+{
+ int block = m_gridModel->GetGridItemEndBlock(m_channelCursor + m_channelOffset,
+ m_blockCursor + m_blockOffset);
+ if (block < m_gridModel->GridItemsSize())
+ {
+ // first block of next event is one block after end block of selected event
+ block += 1;
+ }
+
+ return {m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, block), block};
+}
+
+std::pair<std::shared_ptr<CFileItem>, int> CGUIEPGGridContainer::GetPrevItem() const
+{
+ int block = m_gridModel->GetGridItemStartBlock(m_channelCursor + m_channelOffset,
+ m_blockCursor + m_blockOffset);
+ if (block > 0)
+ {
+ // last block of previous event is one block before start block of selected event
+ block -= 1;
+ }
+
+ return {m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, block), block};
+}
+
+void CGUIEPGGridContainer::UpdateItem()
+{
+ SetItem(GetItem(), m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset);
+}
+
+void CGUIEPGGridContainer::SetFocus(bool focus)
+{
+ if (focus != HasFocus())
+ SetInvalid();
+
+ CGUIControl::SetFocus(focus);
+}
+
+void CGUIEPGGridContainer::ScrollToChannelOffset(int offset)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ float size = m_programmeLayout->Size(m_orientation);
+ int range = m_channelsPerPage / 4;
+
+ if (range <= 0)
+ range = 1;
+
+ if (offset * size < m_channelScrollOffset && m_channelScrollOffset - offset * size > size * range)
+ {
+ // scrolling up, and we're jumping more than 0.5 of a screen
+ m_channelScrollOffset = (offset + range) * size;
+ }
+
+ if (offset * size > m_channelScrollOffset && offset * size - m_channelScrollOffset > size * range)
+ {
+ // scrolling down, and we're jumping more than 0.5 of a screen
+ m_channelScrollOffset = (offset - range) * size;
+ }
+
+ m_channelScrollSpeed = (offset * size - m_channelScrollOffset) / m_scrollTime;
+ m_channelOffset = offset;
+ MarkDirtyRegion();
+}
+
+void CGUIEPGGridContainer::ScrollToBlockOffset(int offset)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // make sure offset is in valid range
+ offset = std::max(0, std::min(offset, m_gridModel->GridItemsSize() - m_blocksPerPage));
+
+ float size = m_blockSize;
+ int range = m_blocksPerPage / 1;
+
+ if (range <= 0)
+ range = 1;
+
+ if (offset * size < m_programmeScrollOffset && m_programmeScrollOffset - offset * size > size * range)
+ {
+ // scrolling left, and we're jumping more than 0.5 of a screen
+ m_programmeScrollOffset = (offset + range) * size;
+ }
+
+ if (offset * size > m_programmeScrollOffset && offset * size - m_programmeScrollOffset > size * range)
+ {
+ // scrolling right, and we're jumping more than 0.5 of a screen
+ m_programmeScrollOffset = (offset - range) * size;
+ }
+
+ m_programmeScrollSpeed = (offset * size - m_programmeScrollOffset) / m_scrollTime;
+ m_blockOffset = offset;
+ MarkDirtyRegion();
+}
+
+void CGUIEPGGridContainer::ValidateOffset()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (!m_programmeLayout)
+ return;
+
+ float pos = (m_orientation == VERTICAL) ? m_channelHeight : m_channelWidth;
+
+ if (m_gridModel->ChannelItemsSize() &&
+ (m_channelOffset > m_gridModel->ChannelItemsSize() - m_channelsPerPage ||
+ m_channelScrollOffset > (m_gridModel->ChannelItemsSize() - m_channelsPerPage) * pos))
+ {
+ m_channelOffset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
+ m_channelScrollOffset = m_channelOffset * pos;
+ }
+
+ if (m_channelOffset < 0 || m_channelScrollOffset < 0)
+ {
+ m_channelOffset = 0;
+ m_channelScrollOffset = 0;
+ }
+
+ if (m_gridModel->GridItemsSize() &&
+ (m_blockOffset > m_gridModel->GridItemsSize() - m_blocksPerPage ||
+ m_programmeScrollOffset > (m_gridModel->GridItemsSize() - m_blocksPerPage) * m_blockSize))
+ {
+ m_blockOffset = m_gridModel->GridItemsSize() - m_blocksPerPage;
+ m_programmeScrollOffset = m_blockOffset * m_blockSize;
+ }
+
+ if (m_blockOffset < 0 || m_programmeScrollOffset < 0)
+ {
+ m_blockOffset = 0;
+ m_programmeScrollOffset = 0;
+ }
+}
+
+void CGUIEPGGridContainer::LoadLayout(TiXmlElement* layout)
+{
+ /* layouts for the channel column */
+ TiXmlElement* itemElement = layout->FirstChildElement("channellayout");
+ while (itemElement)
+ {
+ m_channelLayouts.emplace_back();
+ m_channelLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("channellayout");
+ }
+ itemElement = layout->FirstChildElement("focusedchannellayout");
+ while (itemElement)
+ {
+ m_focusedChannelLayouts.emplace_back();
+ m_focusedChannelLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("focusedchannellayout");
+ }
+
+ /* layouts for the grid items */
+ itemElement = layout->FirstChildElement("focusedlayout");
+ while (itemElement)
+ {
+ m_focusedProgrammeLayouts.emplace_back();
+ m_focusedProgrammeLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("focusedlayout");
+ }
+ itemElement = layout->FirstChildElement("itemlayout");
+ while (itemElement)
+ {
+ m_programmeLayouts.emplace_back();
+ m_programmeLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("itemlayout");
+ }
+
+ /* layout for the date label for the grid */
+ itemElement = layout->FirstChildElement("rulerdatelayout");
+ while (itemElement)
+ {
+ m_rulerDateLayouts.emplace_back();
+ m_rulerDateLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("rulerdatelayout");
+ }
+
+ /* layout for the timeline for the grid */
+ itemElement = layout->FirstChildElement("rulerlayout");
+ while (itemElement)
+ {
+ m_rulerLayouts.emplace_back();
+ m_rulerLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("rulerlayout");
+ }
+
+ UpdateLayout();
+}
+
+std::string CGUIEPGGridContainer::GetDescription() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ const int channelIndex = m_channelCursor + m_channelOffset;
+ const int blockIndex = m_blockCursor + m_blockOffset;
+
+ if (channelIndex < m_gridModel->ChannelItemsSize() && blockIndex < m_gridModel->GridItemsSize())
+ {
+ const std::shared_ptr<CFileItem> item = m_gridModel->GetGridItem(channelIndex, blockIndex);
+ if (item)
+ return item->GetLabel();
+ }
+
+ return {};
+}
+
+void CGUIEPGGridContainer::JumpToNow()
+{
+ m_bEnableProgrammeScrolling = false;
+ GoToNow();
+}
+
+void CGUIEPGGridContainer::JumpToDate(const CDateTime& date)
+{
+ m_bEnableProgrammeScrolling = false;
+ GoToDate(date);
+}
+
+void CGUIEPGGridContainer::GoToBegin()
+{
+ ScrollToBlockOffset(0);
+ SetBlock(0);
+}
+
+void CGUIEPGGridContainer::GoToEnd()
+{
+ ScrollToBlockOffset(m_gridModel->GetLastBlock() - m_blocksPerPage + 1);
+ SetBlock(m_blocksPerPage - 1);
+}
+
+void CGUIEPGGridContainer::GoToNow()
+{
+ GoToDate(CDateTime::GetUTCDateTime());
+}
+
+void CGUIEPGGridContainer::GoToDate(const CDateTime& date)
+{
+ unsigned int offset = m_gridModel->GetPageNowOffset();
+ ScrollToBlockOffset(m_gridModel->GetBlock(date) - offset);
+
+ // ensure we're selecting the active event, not its predecessor.
+ const int iChannel = m_channelOffset + m_channelCursor;
+ const int iBlock = m_blockOffset + offset;
+ if (iChannel >= m_gridModel->ChannelItemsSize() || iBlock >= m_gridModel->GridItemsSize() ||
+ m_gridModel->GetGridItemEndTime(iChannel, iBlock) > date)
+ {
+ SetBlock(offset);
+ }
+ else
+ {
+ SetBlock(offset + 1);
+ }
+}
+
+void CGUIEPGGridContainer::GoToFirstChannel()
+{
+ GoToChannel(0);
+}
+
+void CGUIEPGGridContainer::GoToLastChannel()
+{
+ if (m_gridModel->ChannelItemsSize())
+ GoToChannel(m_gridModel->GetLastChannel());
+ else
+ GoToChannel(0);
+}
+
+void CGUIEPGGridContainer::GoToTop()
+{
+ if (m_orientation == VERTICAL)
+ {
+ GoToChannel(0);
+ }
+ else
+ {
+ GoToBlock(0);
+ }
+}
+
+void CGUIEPGGridContainer::GoToBottom()
+{
+ if (m_orientation == VERTICAL)
+ {
+ if (m_gridModel->HasChannelItems())
+ GoToChannel(m_gridModel->GetLastChannel());
+ else
+ GoToChannel(0);
+ }
+ else
+ {
+ if (m_gridModel->GridItemsSize())
+ GoToBlock(m_gridModel->GetLastBlock());
+ else
+ GoToBlock(0);
+ }
+}
+
+void CGUIEPGGridContainer::GoToMostLeft()
+{
+ if (m_orientation == VERTICAL)
+ {
+ GoToBlock(0);
+ }
+ else
+ {
+ GoToChannel(0);
+ }
+}
+
+void CGUIEPGGridContainer::GoToMostRight()
+{
+ if (m_orientation == VERTICAL)
+ {
+ if (m_gridModel->GridItemsSize())
+ GoToBlock(m_gridModel->GetLastBlock());
+ else
+ GoToBlock(0);
+ }
+ else
+ {
+ if (m_gridModel->HasChannelItems())
+ GoToChannel(m_gridModel->GetLastChannel());
+ else
+ GoToChannel(0);
+ }
+}
+
+void CGUIEPGGridContainer::SetTimelineItems(const std::unique_ptr<CFileItemList>& items,
+ const CDateTime& gridStart,
+ const CDateTime& gridEnd)
+{
+ int iRulerUnit;
+ int iFirstChannel;
+ int iChannelsPerPage;
+ int iBlocksPerPage;
+ int iFirstBlock;
+ float fBlockSize;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ iRulerUnit = m_rulerUnit;
+ iFirstChannel = m_channelOffset;
+ iChannelsPerPage = m_channelsPerPage;
+ iFirstBlock = m_blockOffset;
+ iBlocksPerPage = m_blocksPerPage;
+ fBlockSize = m_blockSize;
+ }
+
+ std::unique_ptr<CGUIEPGGridContainerModel> oldUpdatedGridModel;
+ std::unique_ptr<CGUIEPGGridContainerModel> newUpdatedGridModel(new CGUIEPGGridContainerModel);
+
+ newUpdatedGridModel->Initialize(items, gridStart, gridEnd, iFirstChannel, iChannelsPerPage,
+ iFirstBlock, iBlocksPerPage, iRulerUnit, fBlockSize);
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // grid contains CFileItem instances. CFileItem dtor locks global graphics mutex.
+ // by increasing its refcount make sure, old data are not deleted while we're holding own mutex.
+ oldUpdatedGridModel = std::move(m_updatedGridModel);
+
+ m_updatedGridModel = std::move(newUpdatedGridModel);
+ }
+}
+
+std::unique_ptr<CFileItemList> CGUIEPGGridContainer::GetCurrentTimeLineItems() const
+{
+ return m_gridModel->GetCurrentTimeLineItems(m_channelOffset, m_channelsPerPage);
+}
+
+void CGUIEPGGridContainer::GoToChannel(int channelIndex)
+{
+ if (channelIndex < m_channelsPerPage)
+ {
+ // first page
+ ScrollToChannelOffset(0);
+ SetChannel(channelIndex);
+ }
+ else if (channelIndex > m_gridModel->ChannelItemsSize() - m_channelsPerPage)
+ {
+ // last page
+ ScrollToChannelOffset(m_gridModel->ChannelItemsSize() - m_channelsPerPage);
+ SetChannel(channelIndex - (m_gridModel->ChannelItemsSize() - m_channelsPerPage));
+ }
+ else
+ {
+ ScrollToChannelOffset(channelIndex - m_channelCursor);
+ SetChannel(m_channelCursor);
+ }
+}
+
+void CGUIEPGGridContainer::GoToBlock(int blockIndex)
+{
+ int lastPage = m_gridModel->GridItemsSize() - m_blocksPerPage;
+ if (blockIndex > lastPage)
+ {
+ // last page
+ ScrollToBlockOffset(lastPage);
+ SetBlock(blockIndex - lastPage);
+ }
+ else
+ {
+ ScrollToBlockOffset(blockIndex - m_blockCursor);
+ SetBlock(m_blockCursor);
+ }
+}
+
+void CGUIEPGGridContainer::UpdateLayout()
+{
+ CGUIListItemLayout* oldFocusedChannelLayout = m_focusedChannelLayout;
+ CGUIListItemLayout* oldChannelLayout = m_channelLayout;
+ CGUIListItemLayout* oldFocusedProgrammeLayout = m_focusedProgrammeLayout;
+ CGUIListItemLayout* oldProgrammeLayout = m_programmeLayout;
+ CGUIListItemLayout* oldRulerLayout = m_rulerLayout;
+ CGUIListItemLayout* oldRulerDateLayout = m_rulerDateLayout;
+
+ GetCurrentLayouts();
+
+ // Note: m_rulerDateLayout is optional
+ if (!m_focusedProgrammeLayout || !m_programmeLayout || !m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout)
+ return;
+
+ if (oldChannelLayout == m_channelLayout && oldFocusedChannelLayout == m_focusedChannelLayout &&
+ oldProgrammeLayout == m_programmeLayout && oldFocusedProgrammeLayout == m_focusedProgrammeLayout &&
+ oldRulerLayout == m_rulerLayout && oldRulerDateLayout == m_rulerDateLayout)
+ return; // nothing has changed, so don't update stuff
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_channelHeight = m_channelLayout->Size(VERTICAL);
+ m_channelWidth = m_channelLayout->Size(HORIZONTAL);
+
+ m_rulerDateHeight = m_rulerDateLayout ? m_rulerDateLayout->Size(VERTICAL) : 0;
+ m_rulerDateWidth = m_rulerDateLayout ? m_rulerDateLayout->Size(HORIZONTAL) : 0;
+
+ if (m_orientation == VERTICAL)
+ {
+ m_rulerHeight = m_rulerLayout->Size(VERTICAL);
+ m_gridPosX = m_posX + m_channelWidth;
+ m_gridPosY = m_posY + m_rulerHeight + m_rulerDateHeight;
+ m_gridWidth = m_width - m_channelWidth;
+ m_gridHeight = m_height - m_rulerHeight - m_rulerDateHeight;
+ m_blockSize = m_gridWidth / m_blocksPerPage;
+ m_rulerWidth = m_rulerUnit * m_blockSize;
+ m_channelPosX = m_posX;
+ m_channelPosY = m_posY + m_rulerHeight + m_rulerDateHeight;
+ m_rulerPosX = m_posX + m_channelWidth;
+ m_rulerPosY = m_posY + m_rulerDateHeight;
+ m_channelsPerPage = m_gridHeight / m_channelHeight;
+ m_programmesPerPage = (m_gridWidth / m_blockSize) + 1;
+
+ m_programmeLayout->SetHeight(m_channelHeight);
+ m_focusedProgrammeLayout->SetHeight(m_channelHeight);
+ }
+ else
+ {
+ m_rulerWidth = m_rulerLayout->Size(HORIZONTAL);
+ m_gridPosX = m_posX + m_rulerWidth;
+ m_gridPosY = m_posY + m_channelHeight + m_rulerDateHeight;
+ m_gridWidth = m_width - m_rulerWidth;
+ m_gridHeight = m_height - m_channelHeight - m_rulerDateHeight;
+ m_blockSize = m_gridHeight / m_blocksPerPage;
+ m_rulerHeight = m_rulerUnit * m_blockSize;
+ m_channelPosX = m_posX + m_rulerWidth;
+ m_channelPosY = m_posY + m_rulerDateHeight;
+ m_rulerPosX = m_posX;
+ m_rulerPosY = m_posY + m_channelHeight + m_rulerDateHeight;
+ m_channelsPerPage = m_gridWidth / m_channelWidth;
+ m_programmesPerPage = (m_gridHeight / m_blockSize) + 1;
+
+ m_programmeLayout->SetWidth(m_channelWidth);
+ m_focusedProgrammeLayout->SetWidth(m_channelWidth);
+ }
+
+ // ensure that the scroll offsets are a multiple of our sizes
+ m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
+ m_programmeScrollOffset = m_blockOffset * m_blockSize;
+}
+
+void CGUIEPGGridContainer::UpdateScrollOffset(unsigned int currentTime)
+{
+ if (!m_programmeLayout)
+ return;
+
+ m_channelScrollOffset += m_channelScrollSpeed * (currentTime - m_channelScrollLastTime);
+ if ((m_channelScrollSpeed < 0 && m_channelScrollOffset < m_channelOffset * m_programmeLayout->Size(m_orientation)) ||
+ (m_channelScrollSpeed > 0 && m_channelScrollOffset > m_channelOffset * m_programmeLayout->Size(m_orientation)))
+ {
+ m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
+ m_channelScrollSpeed = 0;
+ m_bEnableChannelScrolling = true;
+ }
+
+ m_channelScrollLastTime = currentTime;
+ m_programmeScrollOffset += m_programmeScrollSpeed * (currentTime - m_programmeScrollLastTime);
+
+ if ((m_programmeScrollSpeed < 0 && m_programmeScrollOffset < m_blockOffset * m_blockSize) ||
+ (m_programmeScrollSpeed > 0 && m_programmeScrollOffset > m_blockOffset * m_blockSize))
+ {
+ m_programmeScrollOffset = m_blockOffset * m_blockSize;
+ m_programmeScrollSpeed = 0;
+ m_bEnableProgrammeScrolling = true;
+ }
+
+ m_programmeScrollLastTime = currentTime;
+
+ if (m_channelScrollSpeed || m_programmeScrollSpeed)
+ MarkDirtyRegion();
+}
+
+void CGUIEPGGridContainer::GetCurrentLayouts()
+{
+ m_channelLayout = nullptr;
+
+ for (unsigned int i = 0; i < m_channelLayouts.size(); i++)
+ {
+ if (m_channelLayouts[i].CheckCondition())
+ {
+ m_channelLayout = &m_channelLayouts[i];
+ break;
+ }
+ }
+
+ if (!m_channelLayout && !m_channelLayouts.empty())
+ m_channelLayout = &m_channelLayouts[0]; // failsafe
+
+ m_focusedChannelLayout = nullptr;
+
+ for (unsigned int i = 0; i < m_focusedChannelLayouts.size(); i++)
+ {
+ if (m_focusedChannelLayouts[i].CheckCondition())
+ {
+ m_focusedChannelLayout = &m_focusedChannelLayouts[i];
+ break;
+ }
+ }
+
+ if (!m_focusedChannelLayout && !m_focusedChannelLayouts.empty())
+ m_focusedChannelLayout = &m_focusedChannelLayouts[0]; // failsafe
+
+ m_programmeLayout = nullptr;
+
+ for (unsigned int i = 0; i < m_programmeLayouts.size(); i++)
+ {
+ if (m_programmeLayouts[i].CheckCondition())
+ {
+ m_programmeLayout = &m_programmeLayouts[i];
+ break;
+ }
+ }
+
+ if (!m_programmeLayout && !m_programmeLayouts.empty())
+ m_programmeLayout = &m_programmeLayouts[0]; // failsafe
+
+ m_focusedProgrammeLayout = nullptr;
+
+ for (unsigned int i = 0; i < m_focusedProgrammeLayouts.size(); i++)
+ {
+ if (m_focusedProgrammeLayouts[i].CheckCondition())
+ {
+ m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[i];
+ break;
+ }
+ }
+
+ if (!m_focusedProgrammeLayout && !m_focusedProgrammeLayouts.empty())
+ m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[0]; // failsafe
+
+ m_rulerLayout = nullptr;
+
+ for (unsigned int i = 0; i < m_rulerLayouts.size(); i++)
+ {
+ if (m_rulerLayouts[i].CheckCondition())
+ {
+ m_rulerLayout = &m_rulerLayouts[i];
+ break;
+ }
+ }
+
+ if (!m_rulerLayout && !m_rulerLayouts.empty())
+ m_rulerLayout = &m_rulerLayouts[0]; // failsafe
+
+ m_rulerDateLayout = nullptr;
+
+ for (unsigned int i = 0; i < m_rulerDateLayouts.size(); i++)
+ {
+ if (m_rulerDateLayouts[i].CheckCondition())
+ {
+ m_rulerDateLayout = &m_rulerDateLayouts[i];
+ break;
+ }
+ }
+
+ // Note: m_rulerDateLayout is optional; so no "failsafe" logic here (see above)
+}
+
+void CGUIEPGGridContainer::SetRenderOffset(const CPoint& offset)
+{
+ m_renderOffset = offset;
+}
+
+void CGUIEPGGridContainer::GetChannelCacheOffsets(int& cacheBefore, int& cacheAfter)
+{
+ if (m_channelScrollSpeed > 0)
+ {
+ cacheBefore = 0;
+ cacheAfter = m_cacheChannelItems;
+ }
+ else if (m_channelScrollSpeed < 0)
+ {
+ cacheBefore = m_cacheChannelItems;
+ cacheAfter = 0;
+ }
+ else
+ {
+ cacheBefore = m_cacheChannelItems / 2;
+ cacheAfter = m_cacheChannelItems / 2;
+ }
+}
+
+void CGUIEPGGridContainer::GetProgrammeCacheOffsets(int& cacheBefore, int& cacheAfter)
+{
+ if (m_programmeScrollSpeed > 0)
+ {
+ cacheBefore = 0;
+ cacheAfter = m_cacheProgrammeItems;
+ }
+ else if (m_programmeScrollSpeed < 0)
+ {
+ cacheBefore = m_cacheProgrammeItems;
+ cacheAfter = 0;
+ }
+ else
+ {
+ cacheBefore = m_cacheProgrammeItems / 2;
+ cacheAfter = m_cacheProgrammeItems / 2;
+ }
+}
+
+void CGUIEPGGridContainer::HandleChannels(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ if (!m_focusedChannelLayout || !m_channelLayout)
+ return;
+
+ const int chanOffset = GetChannelScrollOffset(m_programmeLayout);
+
+ int cacheBeforeChannel, cacheAfterChannel;
+ GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
+
+ if (bRender)
+ {
+ if (m_orientation == VERTICAL)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_channelPosX, m_channelPosY, m_channelWidth, m_gridHeight);
+ else
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_channelPosX, m_channelPosY, m_gridWidth, m_channelHeight);
+ }
+ else
+ {
+ // Free memory not used on screen
+ if (m_gridModel->ChannelItemsSize() > m_channelsPerPage + cacheBeforeChannel + cacheAfterChannel)
+ m_gridModel->FreeChannelMemory(chanOffset - cacheBeforeChannel,
+ chanOffset + m_channelsPerPage - 1 + cacheAfterChannel);
+ }
+
+ CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
+ float pos;
+ float end;
+
+ if (m_orientation == VERTICAL)
+ {
+ pos = originChannel.y;
+ end = m_posY + m_height;
+ }
+ else
+ {
+ pos = originChannel.x;
+ end = m_posX + m_width;
+ }
+
+ // we offset our draw position to take into account scrolling and whether or not our focused
+ // item is offscreen "above" the list.
+ float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) -
+ GetChannelScrollOffsetPos();
+ if (m_channelOffset + m_channelCursor < chanOffset)
+ drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
+
+ pos += drawOffset;
+ end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
+
+ float focusedPos = 0;
+ CGUIListItemPtr focusedItem;
+
+ CFileItemPtr item;
+ int current = chanOffset - cacheBeforeChannel;
+ while (pos < end && m_gridModel->HasChannelItems())
+ {
+ int itemNo = current;
+ if (itemNo >= m_gridModel->ChannelItemsSize())
+ break;
+
+ bool focused = (current == m_channelOffset + m_channelCursor);
+ if (itemNo >= 0)
+ {
+ item = m_gridModel->GetChannelItem(itemNo);
+ if (bRender)
+ {
+ // render our item
+ if (focused)
+ {
+ focusedPos = pos;
+ focusedItem = item;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(originChannel.x, pos, item.get(), false);
+ else
+ RenderItem(pos, originChannel.y, item.get(), false);
+ }
+ }
+ else
+ {
+ // process our item
+ if (m_orientation == VERTICAL)
+ ProcessItem(originChannel.x, pos, item, m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
+ else
+ ProcessItem(pos, originChannel.y, item, m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
+ }
+ }
+ // increment our position
+ pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
+ current++;
+ }
+
+ if (bRender)
+ {
+ // render focused item last so it can overlap other items
+ if (focusedItem)
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(originChannel.x, focusedPos, focusedItem.get(), true);
+ else
+ RenderItem(focusedPos, originChannel.y, focusedItem.get(), true);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+}
+
+void CGUIEPGGridContainer::HandleRulerDate(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ if (!m_rulerDateLayout || m_gridModel->RulerItemsSize() <= 1 || m_gridModel->IsZeroGridDuration())
+ return;
+
+ CFileItemPtr item(m_gridModel->GetRulerItem(0));
+
+ if (bRender)
+ {
+ // Render single ruler item with date of selected programme
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_rulerDateWidth, m_rulerDateHeight);
+ RenderItem(m_posX, m_posY, item.get(), false);
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ else
+ {
+ const int rulerOffset = GetProgrammeScrollOffset();
+ item->SetLabel(m_gridModel->GetRulerItem(rulerOffset / m_rulerUnit + 1)->GetLabel2());
+
+ CFileItemPtr lastitem;
+ ProcessItem(m_posX, m_posY, item, lastitem, false, m_rulerDateLayout, m_rulerDateLayout, currentTime, dirtyregions);
+ }
+}
+
+void CGUIEPGGridContainer::HandleRuler(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ if (!m_rulerLayout || m_gridModel->RulerItemsSize() <= 1 || m_gridModel->IsZeroGridDuration())
+ return;
+
+ int rulerOffset = GetProgrammeScrollOffset();
+
+ CFileItemPtr item(m_gridModel->GetRulerItem(0));
+ CFileItemPtr lastitem;
+ int cacheBeforeRuler, cacheAfterRuler;
+
+ if (bRender)
+ {
+ if (!m_rulerDateLayout)
+ {
+ // Render single ruler item with date of selected programme
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height);
+ RenderItem(m_posX, m_posY, item.get(), false);
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+
+ // render ruler items
+ GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
+
+ if (m_orientation == VERTICAL)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_rulerHeight);
+ else
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_rulerPosX, m_rulerPosY, m_rulerWidth, m_gridHeight);
+ }
+ else
+ {
+ if (!m_rulerDateLayout)
+ {
+ item->SetLabel(m_gridModel->GetRulerItem(rulerOffset / m_rulerUnit + 1)->GetLabel2());
+ ProcessItem(m_posX, m_posY, item, lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_channelWidth);
+ }
+
+ GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
+
+ // Free memory not used on screen
+ if (m_gridModel->RulerItemsSize() > m_blocksPerPage + cacheBeforeRuler + cacheAfterRuler)
+ m_gridModel->FreeRulerMemory(rulerOffset / m_rulerUnit + 1 - cacheBeforeRuler,
+ rulerOffset / m_rulerUnit + 1 + m_blocksPerPage - 1 +
+ cacheAfterRuler);
+ }
+
+ CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
+ float pos;
+ float end;
+
+ if (m_orientation == VERTICAL)
+ {
+ pos = originRuler.x;
+ end = m_posX + m_width;
+ }
+ else
+ {
+ pos = originRuler.y;
+ end = m_posY + m_height;
+ }
+
+ const float drawOffset =
+ (rulerOffset - cacheBeforeRuler) * m_blockSize - GetProgrammeScrollOffsetPos();
+ pos += drawOffset;
+ end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
+
+ if (rulerOffset % m_rulerUnit != 0)
+ {
+ /* first ruler marker starts before current view */
+ int startBlock = rulerOffset - 1;
+
+ while (startBlock % m_rulerUnit != 0)
+ startBlock--;
+
+ int missingSection = rulerOffset - startBlock;
+
+ pos -= missingSection * m_blockSize;
+ }
+
+ while (pos < end && (rulerOffset / m_rulerUnit + 1) < m_gridModel->RulerItemsSize())
+ {
+ item = m_gridModel->GetRulerItem(rulerOffset / m_rulerUnit + 1);
+
+ if (m_orientation == VERTICAL)
+ {
+ if (bRender)
+ RenderItem(pos, originRuler.y, item.get(), false);
+ else
+ ProcessItem(pos, originRuler.y, item, lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
+
+ pos += m_rulerWidth;
+ }
+ else
+ {
+ if (bRender)
+ RenderItem(originRuler.x, pos, item.get(), false);
+ else
+ ProcessItem(originRuler.x, pos, item, lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerHeight);
+
+ pos += m_rulerHeight;
+ }
+
+ rulerOffset += m_rulerUnit;
+ }
+
+ if (bRender)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+}
+
+void CGUIEPGGridContainer::HandleProgrammeGrid(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ if (!m_focusedProgrammeLayout || !m_programmeLayout || m_gridModel->RulerItemsSize() <= 1 || m_gridModel->IsZeroGridDuration())
+ return;
+
+ const int blockOffset = GetProgrammeScrollOffset();
+ const int chanOffset = GetChannelScrollOffset(m_programmeLayout);
+
+ int cacheBeforeProgramme, cacheAfterProgramme;
+ GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
+
+ if (bRender)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_gridPosX, m_gridPosY, m_gridWidth, m_gridHeight);
+ }
+ else
+ {
+ int cacheBeforeChannel, cacheAfterChannel;
+ GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
+
+ // Free memory not used on screen
+ int firstChannel = chanOffset - cacheBeforeChannel;
+ if (firstChannel < 0)
+ firstChannel = 0;
+ int lastChannel = chanOffset + m_channelsPerPage - 1 + cacheAfterChannel;
+ if (lastChannel > m_gridModel->GetLastChannel())
+ lastChannel = m_gridModel->GetLastChannel();
+ int firstBlock = blockOffset - cacheBeforeProgramme;
+ if (firstBlock < 0)
+ firstBlock = 0;
+ int lastBlock = blockOffset + m_programmesPerPage - 1 + cacheAfterProgramme;
+ if (lastBlock > m_gridModel->GetLastBlock())
+ lastBlock = m_gridModel->GetLastBlock();
+
+ if (m_gridModel->FreeProgrammeMemory(firstChannel, lastChannel, firstBlock, lastBlock))
+ {
+ // announce changed viewport
+ const CGUIMessage msg(
+ GUI_MSG_REFRESH_LIST, GetParentID(), GetID(), static_cast<int>(PVREvent::Epg));
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(msg);
+ }
+ }
+
+ CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
+ float posA;
+ float endA;
+ float posB;
+ float endB;
+
+ if (m_orientation == VERTICAL)
+ {
+ posA = originProgramme.x;
+ endA = m_posX + m_width;
+ posB = originProgramme.y;
+ endB = m_gridPosY + m_gridHeight;
+ }
+ else
+ {
+ posA = originProgramme.y;
+ endA = m_posY + m_height;
+ posB = originProgramme.x;
+ endB = m_gridPosX + m_gridWidth;
+ }
+
+ endA += cacheAfterProgramme * m_blockSize;
+
+ const float drawOffsetA = blockOffset * m_blockSize - GetProgrammeScrollOffsetPos();
+ posA += drawOffsetA;
+ const float drawOffsetB =
+ (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) -
+ GetChannelScrollOffsetPos();
+ posB += drawOffsetB;
+
+ int channel = chanOffset - cacheBeforeProgramme;
+
+ float focusedPosX = 0;
+ float focusedPosY = 0;
+ CFileItemPtr focusedItem;
+ CFileItemPtr item;
+
+ const int lastChannel = m_gridModel->GetLastChannel();
+ while (posB < endB && HasData() && channel <= lastChannel)
+ {
+ if (channel >= 0)
+ {
+ int block = blockOffset;
+ float posA2 = posA;
+
+ const int startBlock = blockOffset == 0 ? 0 : blockOffset - 1;
+ if (startBlock == 0 || m_gridModel->IsSameGridItem(channel, block, startBlock))
+ {
+ // First program starts before current view
+ block = m_gridModel->GetGridItemStartBlock(channel, startBlock);
+ const int missingSection = blockOffset - block;
+ posA2 -= missingSection * m_blockSize;
+ }
+
+ const int lastBlock = m_gridModel->GetLastBlock();
+ while (posA2 < endA && HasData() && block <= lastBlock)
+ {
+ item = m_gridModel->GetGridItem(channel, block);
+
+ bool focused = (channel == m_channelOffset + m_channelCursor) &&
+ m_gridModel->IsSameGridItem(m_channelOffset + m_channelCursor,
+ m_blockOffset + m_blockCursor, block);
+
+ if (bRender)
+ {
+ // reset to grid start position if first item is out of grid view
+ if (posA2 < posA)
+ posA2 = posA;
+
+ // render our item
+ if (focused)
+ {
+ focusedPosX = posA2;
+ focusedPosY = posB;
+ focusedItem = item;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(posA2, posB, item.get(), focused);
+ else
+ RenderItem(posB, posA2, item.get(), focused);
+ }
+ }
+ else
+ {
+ // calculate the size to truncate if item is out of grid view
+ float truncateSize = 0;
+ if (posA2 < posA)
+ {
+ truncateSize = posA - posA2;
+ posA2 = posA; // reset to grid start position
+ }
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // truncate item's width
+ m_gridModel->DecreaseGridItemWidth(channel, block, truncateSize);
+ }
+
+ if (m_orientation == VERTICAL)
+ ProcessItem(posA2, posB, item, m_lastChannel, focused, m_programmeLayout,
+ m_focusedProgrammeLayout, currentTime, dirtyregions,
+ m_gridModel->GetGridItemWidth(channel, block));
+ else
+ ProcessItem(posB, posA2, item, m_lastChannel, focused, m_programmeLayout,
+ m_focusedProgrammeLayout, currentTime, dirtyregions,
+ m_gridModel->GetGridItemWidth(channel, block));
+ }
+
+ // increment our X position
+ posA2 += m_gridModel->GetGridItemWidth(
+ channel, block); // assumes focused & unfocused layouts have equal length
+ block += MathUtils::round_int(
+ static_cast<double>(m_gridModel->GetGridItemOriginWidth(channel, block) / m_blockSize));
+ }
+ }
+
+ // increment our Y position
+ channel++;
+ posB += (m_orientation == VERTICAL) ? m_channelHeight : m_channelWidth;
+ }
+
+ if (bRender)
+ {
+ // and render the focused item last (for overlapping purposes)
+ if (focusedItem)
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(focusedPosX, focusedPosY, focusedItem.get(), true);
+ else
+ RenderItem(focusedPosY, focusedPosX, focusedItem.get(), true);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+}
diff --git a/xbmc/pvr/guilib/GUIEPGGridContainer.dox b/xbmc/pvr/guilib/GUIEPGGridContainer.dox
new file mode 100644
index 0000000..1916cde
--- /dev/null
+++ b/xbmc/pvr/guilib/GUIEPGGridContainer.dox
@@ -0,0 +1,245 @@
+/*!
+
+\page EPGGrid_control EPGGrid control
+\brief **Used to display the EPG guide in the PVR section.**
+
+\tableofcontents
+
+The epggrid control is used for creating an epg timeline in Kodi. You can choose
+the position, size, and look of the grid and it's contents.
+
+
+--------------------------------------------------------------------------------
+\section EPGGrid_control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="epggrid" id="10">
+ <description>EPG Grid</description>
+ <posx>80</posx>
+ <posy>81</posy>
+ <width>1120</width>
+ <height>555</height>
+ <pagecontrol>10</pagecontrol>
+ <scrolltime>350</scrolltime>
+ <timeblocks>40</timeblocks>
+ <rulerunit>6</rulerunit>
+ <progresstexture border="5">PVR-EpgProgressIndicator.png</progresstexture>
+ <orienttation>vertical</orientation>
+ <onleft>31</onleft>
+ <onright>31</onright>
+ <onup>10</onup>
+ <ondown>10</ondown>
+ <rulerlayout height="35" width="40">
+ <control type="image" id="1">
+ <width>40</width>
+ <height>29</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <texture border="5">button-nofocus.png</texture>
+ </control>
+ <control type="label" id="2">
+ <posx>10</posx>
+ <posy>0</posy>
+ <width>34</width>
+ <height>29</height>
+ <font>font12</font>
+ <aligny>center</aligny>
+ <selectedcolor>selected</selectedcolor>
+ <align>left</align>
+ <label>$INFO[ListItem.Label]</label>
+ </control>
+ </rulerlayout>
+ <channellayout height="52" width="280">
+ <animation effect="fade" start="110" time="200">UnFocus</animation>
+ <control type="image" id="1">
+ <posx>0</posx>
+ <posy>0</posy>
+ <width>270</width>
+ <height>52</height>
+ <texture border="5">button-nofocus.png</texture>
+ </control>
+ <control type="label">
+ <posx>5</posx>
+ <posy>5</posy>
+ <width>40</width>
+ <height>35</height>
+ <font>font12</font>
+ <align>left</align>
+ <aligny>center</aligny>
+ <textcolor>grey</textcolor>
+ <selectedcolor>grey</selectedcolor>
+ <info>ListItem.ChannelNumber</info>
+ </control>
+ <control type="image">
+ <posx>45</posx>
+ <posy>4</posy>
+ <width>45</width>
+ <height>44</height>
+ <texture>$INFO[ListItem.Icon]</texture>
+ </control>
+ <control type="label" id="1">
+ <posx>94</posx>
+ <posy>0</posy>
+ <width>160</width>
+ <height>52</height>
+ <font>special12</font>
+ <aligny>center</aligny>
+ <selectedcolor>selected</selectedcolor>
+ <align>left</align>
+ <label>$INFO[ListItem.ChannelName]</label>
+ </control>
+ </channellayout>
+ <focusedchannellayout height="52" width="280">
+ <animation effect="fade" start="110" time="200">OnFocus</animation>
+ <control type="image" id="1">
+ <posx>0</posx>
+ <posy>0</posy>
+ <width>270</width>
+ <height>52</height>
+ <texture border="5">button-focus.png</texture>
+ </control>
+ <control type="label">
+ <posx>5</posx>
+ <posy>5</posy>
+ <width>40</width>
+ <height>35</height>
+ <font>font12</font>
+ <align>left</align>
+ <aligny>center</aligny>
+ <textcolor>grey</textcolor>
+ <selectedcolor>grey</selectedcolor>
+ <info>ListItem.ChannelNumber</info>
+ </control>
+ <control type="image">
+ <posx>45</posx>
+ <posy>4</posy>
+ <width>45</width>
+ <height>44</height>
+ <texture>$INFO[ListItem.Icon]</texture>
+ </control>
+ <control type="label" id="1">
+ <posx>94</posx>
+ <posy>0</posy>
+ <width>160</width>
+ <height>52</height>
+ <font>special12</font>
+ <aligny>center</aligny>
+ <selectedcolor>selected</selectedcolor>
+ <align>left</align>
+ <label>$INFO[ListItem.ChannelName]</label>
+ </control>
+ </focusedchannellayout>
+ <itemlayout height="52" width="40">
+ <control type="image" id="2">
+ <width>40</width>
+ <height>52</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <aspectratio>stretch</aspectratio>
+ <texture border="3">epg-genres/$INFO[ListItem.Property(GenreType)].png</texture>
+ </control>
+ <control type="label" id="1">
+ <posx>6</posx>
+ <posy>3</posy>
+ <width>30</width>
+ <height>25</height>
+ <font>font12</font>
+ <aligny>center</aligny>
+ <selectedcolor>selected</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>28</posy>
+ <width>30</width>
+ <height>20</height>
+ <texture>PVR-IsRecording.png</texture>
+ <visible>ListItem.IsRecording</visible>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>28</posy>
+ <width>20</width>
+ <height>20</height>
+ <texture>PVR-HasTimer.png</texture>
+ <visible>ListItem.HasTimer + !ListItem.IsRecording</visible>
+ </control>
+ </itemlayout>
+ <focusedlayout height="52" width="40">
+ <control type="image" id="14">
+ <width>40</width>
+ <height>52</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <texture border="5">folder-focus.png</texture>
+ </control>
+ <control type="image" id="2">
+ <width>40</width>
+ <height>52</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <aspectratio>stretch</aspectratio>
+ <texture border="3">epg-genres/$INFO[ListItem.Property(GenreType)].png</texture>
+ </control>
+ <control type="label" id="1">
+ <posx>6</posx>
+ <posy>3</posy>
+ <width>30</width>
+ <height>25</height>
+ <font>font12</font>
+ <aligny>center</aligny>
+ <selectedcolor>selected</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>28</posy>
+ <width>30</width>
+ <height>20</height>
+ <texture>PVR-IsRecording.png</texture>
+ <visible>ListItem.IsRecording</visible>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>28</posy>
+ <width>20</width>
+ <height>20</height>
+ <texture>PVR-HasTimer.png</texture>
+ <visible>ListItem.HasTimer + !ListItem.IsRecording</visible>
+ </control>
+ </focusedlayout>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section EPGGrid_control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|----------------------:|:--------------------------------------------------------------|
+| timeblocks | The number of timeframes on the ruler.
+| rulerunit | Timeframe of each unit on ruler. 1 unit equals 5 minutes.
+| rulerdatelayout | The layout of a ruler date item (usually used to display the start date of current epg page).
+| rulerlayout | The layout of a ruler item.
+| progresstexture | A texture which indicates the current progress time
+| channellayout | The layout of a channel item.
+| focusedchannellayout | The focused layout of a channel item.
+| itemlayout | The layout of the grid
+| focusedlayout | The focused layout of the grid
+
+
+--------------------------------------------------------------------------------
+\section EPGGrid_control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/pvr/guilib/GUIEPGGridContainer.h b/xbmc/pvr/guilib/GUIEPGGridContainer.h
new file mode 100644
index 0000000..360ade9
--- /dev/null
+++ b/xbmc/pvr/guilib/GUIEPGGridContainer.h
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "guilib/DirtyRegion.h"
+#include "guilib/GUIControl.h"
+#include "guilib/GUIListItemLayout.h"
+#include "guilib/GUITexture.h"
+#include "guilib/IGUIContainer.h"
+#include "threads/CriticalSection.h"
+#include "utils/Geometry.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+class CDateTime;
+class CFileItem;
+class CFileItemList;
+class CGUIListItem;
+class CGUIListItemLayout;
+
+namespace PVR
+{
+ class CPVRChannel;
+ class CPVRChannelGroupMember;
+ class CPVRChannelNumber;
+
+ class CGUIEPGGridContainerModel;
+
+ class CGUIEPGGridContainer : public IGUIContainer
+ {
+ public:
+ CGUIEPGGridContainer(int parentID, int controlID, float posX, float posY, float width, float height,
+ ORIENTATION orientation, int scrollTime, int preloadItems, int minutesPerPage,
+ int rulerUnit, const CTextureInfo& progressIndicatorTexture);
+ CGUIEPGGridContainer(const CGUIEPGGridContainer& other);
+
+ CGUIEPGGridContainer* Clone() const override { return new CGUIEPGGridContainer(*this); }
+
+ /*!
+ * @brief Check whether the control currently holds data.
+ * @return true if the control has data, false otherwise.
+ */
+ bool HasData() const;
+
+ void AllocResources() override;
+ void FreeResources(bool immediately) override;
+
+ bool OnAction(const CAction& action) override;
+ void OnDown() override;
+ void OnUp() override;
+ void OnLeft() override;
+ void OnRight() override;
+ bool OnMouseOver(const CPoint& point) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void SetFocus(bool focus) override;
+ std::string GetDescription() const override;
+ EVENT_RESULT OnMouseEvent(const CPoint& point, const CMouseEvent& event) override;
+
+ void Process(unsigned int currentTime, CDirtyRegionList& dirtyregions) override;
+ void Render() override;
+
+ CGUIListItemPtr GetListItem(int offset, unsigned int flag = 0) const override;
+ std::string GetLabel(int info) const override;
+
+ std::shared_ptr<CFileItem> GetSelectedGridItem(int offset = 0) const;
+ std::shared_ptr<CPVRChannelGroupMember> GetSelectedChannelGroupMember() const;
+ CDateTime GetSelectedDate() const;
+
+ void LoadLayout(TiXmlElement* layout);
+ void SetPageControl(int id);
+
+ /*! \brief Set the offset of the first item in the container from the container's position
+ Useful for lists/panels where the focused item may be larger than the non-focused items and thus
+ normally cut off from the clipping window defined by the container's position + size.
+ \param offset CPoint holding the offset in skin coordinates.
+ */
+ void SetRenderOffset(const CPoint& offset);
+
+ void JumpToNow();
+ void JumpToDate(const CDateTime& date);
+
+ void GoToBegin();
+ void GoToEnd();
+ void GoToNow();
+ void GoToDate(const CDateTime& date);
+
+ void GoToFirstChannel();
+ void GoToLastChannel();
+
+ void GoToTop();
+ void GoToBottom();
+ void GoToMostLeft();
+ void GoToMostRight();
+
+ void SetTimelineItems(const std::unique_ptr<CFileItemList>& items,
+ const CDateTime& gridStart,
+ const CDateTime& gridEnd);
+
+ std::unique_ptr<CFileItemList> GetCurrentTimeLineItems() const;
+
+ /*!
+ * @brief Set the control's selection to the given channel and set the control's view port to show the channel.
+ * @param channel the channel.
+ * @return true if the selection was set to the given channel, false otherwise.
+ */
+ bool SetChannel(const std::shared_ptr<CPVRChannel>& channel);
+
+ /*!
+ * @brief Set the control's selection to the given channel and set the control's view port to show the channel.
+ * @param channel the channel's path.
+ * @return true if the selection was set to the given channel, false otherwise.
+ */
+ bool SetChannel(const std::string& channel);
+
+ /*!
+ * @brief Set the control's selection to the given channel and set the control's view port to show the channel.
+ * @param channelNumber the channel's number.
+ * @return true if the selection was set to the given channel, false otherwise.
+ */
+ bool SetChannel(const CPVRChannelNumber& channelNumber);
+
+ private:
+ bool OnClick(int actionID);
+ bool SelectItemFromPoint(const CPoint& point, bool justGrid = true);
+
+ void SetChannel(int channel);
+
+ void SetBlock(int block, bool bUpdateBlockTravelAxis = true);
+ void UpdateBlock(bool bUpdateBlockTravelAxis = true);
+
+ void ChannelScroll(int amount);
+ void ProgrammesScroll(int amount);
+ void ValidateOffset();
+ void UpdateLayout();
+
+ void SetItem(const std::pair<std::shared_ptr<CFileItem>, int>& itemInfo);
+ bool SetItem(const std::shared_ptr<CFileItem>& item, int channelIndex, int blockIndex);
+ std::shared_ptr<CFileItem> GetItem() const;
+ std::pair<std::shared_ptr<CFileItem>, int> GetNextItem() const;
+ std::pair<std::shared_ptr<CFileItem>, int> GetPrevItem() const;
+ void UpdateItem();
+
+ void MoveToRow(int row);
+
+ CGUIListItemLayout* GetFocusedLayout() const;
+
+ void ScrollToBlockOffset(int offset);
+ void ScrollToChannelOffset(int offset);
+ void GoToBlock(int blockIndex);
+ void GoToChannel(int channelIndex);
+ void UpdateScrollOffset(unsigned int currentTime);
+ void ProcessItem(float posX, float posY, const std::shared_ptr<CFileItem>& item, std::shared_ptr<CFileItem>& lastitem, bool focused, CGUIListItemLayout* normallayout, CGUIListItemLayout* focusedlayout, unsigned int currentTime, CDirtyRegionList& dirtyregions, float resize = -1.0f);
+ void RenderItem(float posX, float posY, CGUIListItem* item, bool focused);
+ void GetCurrentLayouts();
+
+ void ProcessChannels(unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void ProcessRuler(unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void ProcessRulerDate(unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void ProcessProgrammeGrid(unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void ProcessProgressIndicator(unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void RenderChannels();
+ void RenderRulerDate();
+ void RenderRuler();
+ void RenderProgrammeGrid();
+ void RenderProgressIndicator();
+
+ CPoint m_renderOffset; ///< \brief render offset of the first item in the list \sa SetRenderOffset
+
+ ORIENTATION m_orientation;
+
+ std::vector<CGUIListItemLayout> m_channelLayouts;
+ std::vector<CGUIListItemLayout> m_focusedChannelLayouts;
+ std::vector<CGUIListItemLayout> m_focusedProgrammeLayouts;
+ std::vector<CGUIListItemLayout> m_programmeLayouts;
+ std::vector<CGUIListItemLayout> m_rulerLayouts;
+ std::vector<CGUIListItemLayout> m_rulerDateLayouts;
+
+ CGUIListItemLayout* m_channelLayout;
+ CGUIListItemLayout* m_focusedChannelLayout;
+ CGUIListItemLayout* m_programmeLayout;
+ CGUIListItemLayout* m_focusedProgrammeLayout;
+ CGUIListItemLayout* m_rulerLayout;
+ CGUIListItemLayout* m_rulerDateLayout;
+
+ int m_pageControl;
+
+ void GetChannelCacheOffsets(int& cacheBefore, int& cacheAfter);
+ void GetProgrammeCacheOffsets(int& cacheBefore, int& cacheAfter);
+
+ private:
+ bool OnMouseClick(int dwButton, const CPoint& point);
+ bool OnMouseDoubleClick(int dwButton, const CPoint& point);
+ bool OnMouseWheel(char wheel, const CPoint& point);
+
+ void HandleChannels(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void HandleRuler(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void HandleRulerDate(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions);
+ void HandleProgrammeGrid(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions);
+
+ float GetCurrentTimePositionOnPage() const;
+ float GetProgressIndicatorWidth() const;
+ float GetProgressIndicatorHeight() const;
+
+ void UpdateItems();
+
+ float GetChannelScrollOffsetPos() const;
+ float GetProgrammeScrollOffsetPos() const;
+ int GetChannelScrollOffset(CGUIListItemLayout* layout) const;
+ int GetProgrammeScrollOffset() const;
+
+ int m_rulerUnit; //! number of blocks that makes up one element of the ruler
+ int m_channelsPerPage;
+ int m_programmesPerPage;
+ int m_channelCursor;
+ int m_channelOffset;
+ int m_blocksPerPage;
+ int m_blockCursor;
+ int m_blockOffset;
+ int m_blockTravelAxis;
+ int m_cacheChannelItems;
+ int m_cacheProgrammeItems;
+ int m_cacheRulerItems;
+
+ float m_rulerDateHeight; //! height of ruler date item
+ float m_rulerDateWidth; //! width of ruler date item
+ float m_rulerPosX; //! X position of first ruler item
+ float m_rulerPosY; //! Y position of first ruler item
+ float m_rulerHeight; //! height of the scrolling timeline above the ruler items
+ float m_rulerWidth; //! width of each element of the ruler
+ float m_channelPosX; //! X position of first channel row
+ float m_channelPosY; //! Y position of first channel row
+ float m_channelHeight; //! height of the channel item
+ float m_channelWidth; //! width of the channel item
+ float m_gridPosX; //! X position of first grid item
+ float m_gridPosY; //! Y position of first grid item
+ float m_gridWidth; //! width of the epg grid control
+ float m_gridHeight; //! height of the epg grid control
+ float m_blockSize; //! a block's width in pixels
+ float m_analogScrollCount;
+
+ std::unique_ptr<CGUITexture> m_guiProgressIndicatorTexture;
+
+ std::shared_ptr<CFileItem> m_lastItem;
+ std::shared_ptr<CFileItem> m_lastChannel;
+
+ bool m_bEnableProgrammeScrolling = true;
+ bool m_bEnableChannelScrolling = true;
+
+ int m_scrollTime;
+
+ int m_programmeScrollLastTime;
+ float m_programmeScrollSpeed;
+ float m_programmeScrollOffset;
+
+ int m_channelScrollLastTime;
+ float m_channelScrollSpeed;
+ float m_channelScrollOffset;
+
+ mutable CCriticalSection m_critSection;
+ std::unique_ptr<CGUIEPGGridContainerModel> m_gridModel;
+ std::unique_ptr<CGUIEPGGridContainerModel> m_updatedGridModel;
+
+ int m_itemStartBlock = 0;
+ };
+}
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;
+}
diff --git a/xbmc/pvr/guilib/GUIEPGGridContainerModel.h b/xbmc/pvr/guilib/GUIEPGGridContainerModel.h
new file mode 100644
index 0000000..2e0a6d6
--- /dev/null
+++ b/xbmc/pvr/guilib/GUIEPGGridContainerModel.h
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+class CFileItem;
+class CFileItemList;
+
+namespace PVR
+{
+struct GridItem
+{
+ GridItem(const std::shared_ptr<CFileItem>& _item, float _width, int _startBlock, int _endBlock)
+ : item(_item), originWidth(_width), width(_width), startBlock(_startBlock), endBlock(_endBlock)
+ {
+ }
+
+ bool operator==(const GridItem& other) const
+ {
+ return (startBlock == other.startBlock && endBlock == other.endBlock);
+ }
+
+ std::shared_ptr<CFileItem> item;
+ float originWidth = 0.0f;
+ float width = 0.0f;
+ int startBlock = 0;
+ int endBlock = 0;
+};
+
+class CPVREpgInfoTag;
+
+class CGUIEPGGridContainerModel
+{
+public:
+ static constexpr int MINSPERBLOCK = 5; // minutes
+
+ CGUIEPGGridContainerModel() = default;
+ virtual ~CGUIEPGGridContainerModel() = default;
+
+ void 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);
+ void SetInvalid();
+
+ static const int INVALID_INDEX = -1;
+ void FindChannelAndBlockIndex(int channelUid,
+ unsigned int broadcastUid,
+ int eventOffset,
+ int& newChannelIndex,
+ int& newBlockIndex) const;
+
+ void FreeChannelMemory(int keepStart, int keepEnd);
+ bool FreeProgrammeMemory(int firstChannel, int lastChannel, int firstBlock, int lastBlock);
+ void FreeRulerMemory(int keepStart, int keepEnd);
+
+ std::shared_ptr<CFileItem> GetChannelItem(int iIndex) const { return m_channelItems[iIndex]; }
+ bool HasChannelItems() const { return !m_channelItems.empty(); }
+ int ChannelItemsSize() const { return static_cast<int>(m_channelItems.size()); }
+ int GetLastChannel() const
+ {
+ return m_channelItems.empty() ? -1 : static_cast<int>(m_channelItems.size()) - 1;
+ }
+
+ std::shared_ptr<CFileItem> GetRulerItem(int iIndex) const { return m_rulerItems[iIndex]; }
+ int RulerItemsSize() const { return static_cast<int>(m_rulerItems.size()); }
+
+ int GridItemsSize() const { return m_blocks; }
+ bool IsSameGridItem(int iChannel, int iBlock1, int iBlock2) const;
+ std::shared_ptr<CFileItem> GetGridItem(int iChannel, int iBlock) const;
+ int GetGridItemStartBlock(int iChannel, int iBlock) const;
+ int GetGridItemEndBlock(int iChannel, int iBlock) const;
+ CDateTime GetGridItemEndTime(int iChannel, int iBlock) const;
+ float GetGridItemWidth(int iChannel, int iBlock) const;
+ float GetGridItemOriginWidth(int iChannel, int iBlock) const;
+ void DecreaseGridItemWidth(int iChannel, int iBlock, float fSize);
+
+ bool IsZeroGridDuration() const { return (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0); }
+ const CDateTime& GetGridStart() const { return m_gridStart; }
+ const CDateTime& GetGridEnd() const { return m_gridEnd; }
+ unsigned int GetGridStartPadding() const;
+
+ unsigned int GetPageNowOffset() const;
+ int GetNowBlock() const;
+ int GetLastBlock() const { return m_blocks - 1; }
+
+ CDateTime GetStartTimeForBlock(int block) const;
+ int GetBlock(const CDateTime& datetime) const;
+ int GetFirstEventBlock(const std::shared_ptr<CPVREpgInfoTag>& event) const;
+ int GetLastEventBlock(const std::shared_ptr<CPVREpgInfoTag>& event) const;
+ bool IsEventMemberOfBlock(const std::shared_ptr<CPVREpgInfoTag>& event, int iBlock) const;
+
+ std::unique_ptr<CFileItemList> GetCurrentTimeLineItems(int firstChannel, int numChannels) const;
+
+private:
+ GridItem* GetGridItemPtr(int iChannel, int iBlock) const;
+ std::shared_ptr<CFileItem> CreateGapItem(int iChannel) const;
+ std::shared_ptr<CFileItem> GetItem(int iChannel, int iBlock) const;
+
+ std::vector<std::shared_ptr<CPVREpgInfoTag>> GetEPGTimeline(int iChannel,
+ const CDateTime& minEventEnd,
+ const CDateTime& maxEventStart) const;
+
+ struct EpgTags
+ {
+ std::vector<std::shared_ptr<CFileItem>> tags;
+ int firstBlock = -1;
+ int lastBlock = -1;
+ };
+
+ using EpgTagsMap = std::unordered_map<int, EpgTags>;
+
+ std::shared_ptr<CFileItem> CreateEpgTags(int iChannel, int iBlock) const;
+ std::shared_ptr<CFileItem> GetEpgTags(EpgTagsMap::iterator& itEpg,
+ int iChannel,
+ int iBlock) const;
+ std::shared_ptr<CFileItem> GetEpgTagsBefore(EpgTags& epgTags, int iChannel, int iBlock) const;
+ std::shared_ptr<CFileItem> GetEpgTagsAfter(EpgTags& epgTags, int iChannel, int iBlock) const;
+
+ mutable EpgTagsMap m_epgItems;
+
+ CDateTime m_gridStart;
+ CDateTime m_gridEnd;
+
+ std::vector<std::shared_ptr<CFileItem>> m_channelItems;
+ std::vector<std::shared_ptr<CFileItem>> m_rulerItems;
+
+ struct GridCoordinates
+ {
+ GridCoordinates(int _channel, int _block) : channel(_channel), block(_block) {}
+
+ bool operator==(const GridCoordinates& other) const
+ {
+ return (channel == other.channel && block == other.block);
+ }
+
+ int channel = 0;
+ int block = 0;
+ };
+
+ struct GridCoordinatesHash
+ {
+ std::size_t operator()(const GridCoordinates& coordinates) const
+ {
+ return std::hash<int>()(coordinates.channel) ^ std::hash<int>()(coordinates.block);
+ }
+ };
+
+ mutable std::unordered_map<GridCoordinates, GridItem, GridCoordinatesHash> m_gridIndex;
+
+ int m_blocks = 0;
+ float m_fBlockSize = 0.0f;
+
+ int m_firstActiveChannel = 0;
+ int m_lastActiveChannel = 0;
+ int m_firstActiveBlock = 0;
+ int m_lastActiveBlock = 0;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionListener.cpp b/xbmc/pvr/guilib/PVRGUIActionListener.cpp
new file mode 100644
index 0000000..3e68386
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionListener.cpp
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2005-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 "PVRGUIActionListener.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "application/ApplicationActionListeners.h"
+#include "application/ApplicationComponents.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsClients.h"
+#include "pvr/guilib/PVRGUIActionsDatabase.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/guilib/PVRGUIActionsTimers.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+
+#include <memory>
+#include <string>
+
+namespace PVR
+{
+
+CPVRGUIActionListener::CPVRGUIActionListener()
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appListener = components.GetComponent<CApplicationActionListeners>();
+ appListener->RegisterActionListener(this);
+ CServiceBroker::GetSettingsComponent()->GetSettings()->RegisterCallback(
+ this,
+ {CSettings::SETTING_PVRPARENTAL_ENABLED, CSettings::SETTING_PVRMANAGER_RESETDB,
+ CSettings::SETTING_EPG_RESETEPG, CSettings::SETTING_PVRMANAGER_ADDONS,
+ CSettings::SETTING_PVRMANAGER_CLIENTPRIORITIES, CSettings::SETTING_PVRMANAGER_CHANNELMANAGER,
+ CSettings::SETTING_PVRMANAGER_GROUPMANAGER, CSettings::SETTING_PVRMANAGER_CHANNELSCAN,
+ CSettings::SETTING_PVRMENU_SEARCHICONS, CSettings::SETTING_PVRCLIENT_MENUHOOK,
+ CSettings::SETTING_EPG_PAST_DAYSTODISPLAY, CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY});
+}
+
+CPVRGUIActionListener::~CPVRGUIActionListener()
+{
+ CServiceBroker::GetSettingsComponent()->GetSettings()->UnregisterCallback(this);
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appListener = components.GetComponent<CApplicationActionListeners>();
+ appListener->UnregisterActionListener(this);
+}
+
+void CPVRGUIActionListener::Init(CPVRManager& mgr)
+{
+ mgr.Events().Subscribe(this, &CPVRGUIActionListener::OnPVRManagerEvent);
+}
+
+void CPVRGUIActionListener::Deinit(CPVRManager& mgr)
+{
+ mgr.Events().Unsubscribe(this);
+}
+
+void CPVRGUIActionListener::OnPVRManagerEvent(const PVREvent& event)
+{
+ if (event == PVREvent::AnnounceReminder)
+ {
+ if (g_application.IsInitialized())
+ {
+ // if GUI is ready, dispatch to GUI thread and handle the action there
+ CServiceBroker::GetAppMessenger()->PostMsg(
+ TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(new CAction(ACTION_PVR_ANNOUNCE_REMINDERS)));
+ }
+ }
+}
+
+ChannelSwitchMode CPVRGUIActionListener::GetChannelSwitchMode(int iAction)
+{
+ if ((iAction == ACTION_MOVE_UP || iAction == ACTION_MOVE_DOWN) &&
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRPLAYBACK_CONFIRMCHANNELSWITCH))
+ return ChannelSwitchMode::NO_SWITCH;
+
+ return ChannelSwitchMode::INSTANT_OR_DELAYED_SWITCH;
+}
+
+bool CPVRGUIActionListener::OnAction(const CAction& action)
+{
+ bool bIsJumpSMS = false;
+ bool bIsPlayingPVR = CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying() &&
+ g_application.CurrentFileItem().HasPVRChannelInfoTag();
+
+ switch (action.GetID())
+ {
+ case ACTION_PVR_PLAY:
+ case ACTION_PVR_PLAY_TV:
+ case ACTION_PVR_PLAY_RADIO:
+ {
+ // see if we're already playing a PVR stream and if not or the stream type
+ // doesn't match the demanded type, start playback of according type
+ switch (action.GetID())
+ {
+ case ACTION_PVR_PLAY:
+ if (!bIsPlayingPVR)
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ PlaybackTypeAny);
+ break;
+ case ACTION_PVR_PLAY_TV:
+ if (!bIsPlayingPVR || g_application.CurrentFileItem().GetPVRChannelInfoTag()->IsRadio())
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ PlaybackTypeTV);
+ break;
+ case ACTION_PVR_PLAY_RADIO:
+ if (!bIsPlayingPVR || !g_application.CurrentFileItem().GetPVRChannelInfoTag()->IsRadio())
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ PlaybackTypeRadio);
+ break;
+ }
+ return true;
+ }
+
+ case ACTION_JUMP_SMS2:
+ case ACTION_JUMP_SMS3:
+ case ACTION_JUMP_SMS4:
+ case ACTION_JUMP_SMS5:
+ case ACTION_JUMP_SMS6:
+ case ACTION_JUMP_SMS7:
+ case ACTION_JUMP_SMS8:
+ case ACTION_JUMP_SMS9:
+ bIsJumpSMS = true;
+ // fallthru is intended
+ [[fallthrough]];
+ case REMOTE_0:
+ case REMOTE_1:
+ case REMOTE_2:
+ case REMOTE_3:
+ case REMOTE_4:
+ case REMOTE_5:
+ case REMOTE_6:
+ case REMOTE_7:
+ case REMOTE_8:
+ case REMOTE_9:
+ case ACTION_CHANNEL_NUMBER_SEP:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_FULLSCREEN_VIDEO) ||
+ CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_VISUALISATION))
+ {
+ // do not consume action if a python modal is the top most dialog
+ // as a python modal can't return that it consumed the action.
+ if (CServiceBroker::GetGUI()->GetWindowManager().IsPythonWindow(
+ CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog()))
+ return false;
+
+ char cCharacter;
+ if (action.GetID() == ACTION_CHANNEL_NUMBER_SEP)
+ {
+ cCharacter = CPVRChannelNumber::SEPARATOR;
+ }
+ else
+ {
+ int iRemote =
+ bIsJumpSMS ? action.GetID() - (ACTION_JUMP_SMS2 - REMOTE_2) : action.GetID();
+ cCharacter = static_cast<char>(iRemote - REMOTE_0) + '0';
+ }
+ CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNumberInputHandler()
+ .AppendChannelNumberCharacter(cCharacter);
+ return true;
+ }
+ return false;
+ }
+
+ case ACTION_SHOW_INFO:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelNavigator().ToggleInfo();
+ return true;
+ }
+
+ case ACTION_SELECT_ITEM:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ // If the button that caused this action matches action "Select" ...
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRPLAYBACK_CONFIRMCHANNELSWITCH) &&
+ CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNavigator()
+ .IsPreview())
+ {
+ // ... and if "confirm channel switch" setting is active and a channel
+ // preview is currently shown, switch to the currently previewed channel.
+ CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNavigator()
+ .SwitchToCurrentChannel();
+ return true;
+ }
+ else if (CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNumberInputHandler()
+ .CheckInputAndExecuteAction())
+ {
+ // ... or if the action was processed by direct channel number input, we're done.
+ return true;
+ }
+ return false;
+ }
+
+ case ACTION_NEXT_ITEM:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SeekForward();
+ return true;
+ }
+
+ case ACTION_PREV_ITEM:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SeekBackward(
+ CApplication::ACTION_PREV_ITEM_THRESHOLD);
+ return true;
+ }
+
+ case ACTION_MOVE_UP:
+ case ACTION_CHANNEL_UP:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNavigator()
+ .SelectNextChannel(GetChannelSwitchMode(action.GetID()));
+ return true;
+ }
+
+ case ACTION_MOVE_DOWN:
+ case ACTION_CHANNEL_DOWN:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNavigator()
+ .SelectPreviousChannel(GetChannelSwitchMode(action.GetID()));
+ return true;
+ }
+
+ case ACTION_CHANNEL_SWITCH:
+ {
+ if (!bIsPlayingPVR)
+ return false;
+
+ int iChannelNumber = static_cast<int>(action.GetAmount(0));
+ int iSubChannelNumber = static_cast<int>(action.GetAmount(1));
+
+ const std::shared_ptr<CPVRPlaybackState> playbackState =
+ CServiceBroker::GetPVRManager().PlaybackState();
+ const std::shared_ptr<CPVRChannelGroup> activeGroup =
+ playbackState->GetActiveChannelGroup(playbackState->IsPlayingRadio());
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ activeGroup->GetByChannelNumber(CPVRChannelNumber(iChannelNumber, iSubChannelNumber));
+
+ if (!groupMember)
+ return false;
+
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ CFileItem(groupMember), false);
+ return true;
+ }
+
+ case ACTION_RECORD:
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().ToggleRecordingOnPlayingChannel();
+ return true;
+ }
+
+ case ACTION_PVR_ANNOUNCE_REMINDERS:
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AnnounceReminders();
+ return true;
+ }
+ }
+ return false;
+}
+
+void CPVRGUIActionListener::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == nullptr)
+ return;
+
+ const std::string& settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_PVRPARENTAL_ENABLED)
+ {
+ if (std::static_pointer_cast<const CSettingBool>(setting)->GetValue() &&
+ CServiceBroker::GetSettingsComponent()
+ ->GetSettings()
+ ->GetString(CSettings::SETTING_PVRPARENTAL_PIN)
+ .empty())
+ {
+ std::string newPassword = "";
+ // password set... save it
+ if (CGUIDialogNumeric::ShowAndVerifyNewPassword(newPassword))
+ CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(
+ CSettings::SETTING_PVRPARENTAL_PIN, newPassword);
+ // password not set... disable parental
+ else
+ std::static_pointer_cast<CSettingBool>(std::const_pointer_cast<CSetting>(setting))
+ ->SetValue(false);
+ }
+ }
+ else if (settingId == CSettings::SETTING_EPG_PAST_DAYSTODISPLAY)
+ {
+ CServiceBroker::GetPVRManager().Clients()->SetEPGMaxPastDays(
+ std::static_pointer_cast<const CSettingInt>(setting)->GetValue());
+ }
+ else if (settingId == CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY)
+ {
+ CServiceBroker::GetPVRManager().Clients()->SetEPGMaxFutureDays(
+ std::static_pointer_cast<const CSettingInt>(setting)->GetValue());
+ }
+}
+
+void CPVRGUIActionListener::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == nullptr)
+ return;
+
+ const std::string& settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_PVRMANAGER_RESETDB)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Database>().ResetDatabase(false);
+ }
+ else if (settingId == CSettings::SETTING_EPG_RESETEPG)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Database>().ResetDatabase(true);
+ }
+ else if (settingId == CSettings::SETTING_PVRMANAGER_CLIENTPRIORITIES)
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ CGUIDialog* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetDialog(
+ WINDOW_DIALOG_PVR_CLIENT_PRIORITIES);
+ if (dialog)
+ {
+ dialog->Open();
+ CServiceBroker::GetPVRManager().ChannelGroups()->UpdateFromClients({});
+ }
+ }
+ }
+ else if (settingId == CSettings::SETTING_PVRMANAGER_CHANNELMANAGER)
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ CGUIDialog* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_PVR_CHANNEL_MANAGER);
+ if (dialog)
+ dialog->Open();
+ }
+ }
+ else if (settingId == CSettings::SETTING_PVRMANAGER_GROUPMANAGER)
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ CGUIDialog* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_PVR_GROUP_MANAGER);
+ if (dialog)
+ dialog->Open();
+ }
+ }
+ else if (settingId == CSettings::SETTING_PVRMANAGER_CHANNELSCAN)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().StartChannelScan();
+ }
+ else if (settingId == CSettings::SETTING_PVRMENU_SEARCHICONS)
+ {
+ CServiceBroker::GetPVRManager().TriggerSearchMissingChannelIcons();
+ }
+ else if (settingId == CSettings::SETTING_PVRCLIENT_MENUHOOK)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Clients>().ProcessSettingsMenuHooks();
+ }
+ else if (settingId == CSettings::SETTING_PVRMANAGER_ADDONS)
+ {
+ const std::vector<std::string> params{"addons://default_binary_addons_source/kodi.pvrclient",
+ "return"};
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_ADDON_BROWSER, params);
+ }
+}
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionListener.h b/xbmc/pvr/guilib/PVRGUIActionListener.h
new file mode 100644
index 0000000..d24818b
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionListener.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-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.
+ */
+
+#pragma once
+
+#include "interfaces/IActionListener.h"
+#include "settings/lib/ISettingCallback.h"
+
+namespace PVR
+{
+
+class CPVRManager;
+enum class ChannelSwitchMode;
+enum class PVREvent;
+
+class CPVRGUIActionListener : public IActionListener, public ISettingCallback
+{
+public:
+ CPVRGUIActionListener();
+ ~CPVRGUIActionListener() override;
+
+ void Init(CPVRManager& mgr);
+ void Deinit(CPVRManager& mgr);
+
+ // IActionListener implementation
+ bool OnAction(const CAction& action) override;
+
+ // ISettingCallback implementation
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+ void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override;
+
+ void OnPVRManagerEvent(const PVREvent& event);
+
+private:
+ CPVRGUIActionListener(const CPVRGUIActionListener&) = delete;
+ CPVRGUIActionListener& operator=(const CPVRGUIActionListener&) = delete;
+
+ static ChannelSwitchMode GetChannelSwitchMode(int iAction);
+};
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp
new file mode 100644
index 0000000..80fb90e
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsChannels.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/windows/GUIWindowPVRBase.h"
+#include "settings/Settings.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+void CPVRChannelSwitchingInputHandler::AppendChannelNumberCharacter(char cCharacter)
+{
+ // special case. if only a single zero was typed in, switch to previously played channel.
+ if (GetCurrentDigitCount() == 0 && cCharacter == '0')
+ {
+ SwitchToPreviousChannel();
+ return;
+ }
+
+ CPVRChannelNumberInputHandler::AppendChannelNumberCharacter(cCharacter);
+}
+
+void CPVRChannelSwitchingInputHandler::GetChannelNumbers(std::vector<std::string>& channelNumbers)
+{
+ const CPVRManager& pvrMgr = CServiceBroker::GetPVRManager();
+ const std::shared_ptr<CPVRChannel> playingChannel = pvrMgr.PlaybackState()->GetPlayingChannel();
+ if (playingChannel)
+ {
+ const std::shared_ptr<CPVRChannelGroup> group =
+ pvrMgr.ChannelGroups()->GetGroupAll(playingChannel->IsRadio());
+ if (group)
+ group->GetChannelNumbers(channelNumbers);
+ }
+}
+
+void CPVRChannelSwitchingInputHandler::OnInputDone()
+{
+ CPVRChannelNumber channelNumber = GetChannelNumber();
+ if (channelNumber.GetChannelNumber())
+ SwitchToChannel(channelNumber);
+}
+
+void CPVRChannelSwitchingInputHandler::SwitchToChannel(const CPVRChannelNumber& channelNumber)
+{
+ if (channelNumber.IsValid() && CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
+ {
+ const std::shared_ptr<CPVRChannel> playingChannel =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ if (playingChannel)
+ {
+ bool bRadio = playingChannel->IsRadio();
+ const std::shared_ptr<CPVRChannelGroup> group =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(bRadio);
+
+ if (channelNumber != group->GetChannelNumber(playingChannel))
+ {
+ // channel number present in active group?
+ std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ group->GetByChannelNumber(channelNumber);
+
+ if (!groupMember)
+ {
+ // channel number present in any group?
+ const CPVRChannelGroups* groupAccess =
+ CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio);
+ const std::vector<std::shared_ptr<CPVRChannelGroup>> groups =
+ groupAccess->GetMembers(true);
+ for (const auto& currentGroup : groups)
+ {
+ if (currentGroup == group) // we have already checked this group
+ continue;
+
+ groupMember = currentGroup->GetByChannelNumber(channelNumber);
+ if (groupMember)
+ break;
+ }
+ }
+
+ if (groupMember)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(
+ TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(new CAction(
+ ACTION_CHANNEL_SWITCH, static_cast<float>(channelNumber.GetChannelNumber()),
+ static_cast<float>(channelNumber.GetSubChannelNumber()))));
+ }
+ }
+ }
+ }
+}
+
+void CPVRChannelSwitchingInputHandler::SwitchToPreviousChannel()
+{
+ const std::shared_ptr<CPVRPlaybackState> playbackState =
+ CServiceBroker::GetPVRManager().PlaybackState();
+ if (playbackState->IsPlaying())
+ {
+ const std::shared_ptr<CPVRChannel> playingChannel = playbackState->GetPlayingChannel();
+ if (playingChannel)
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ playbackState->GetPreviousToLastPlayedChannelGroupMember(playingChannel->IsRadio());
+ if (groupMember)
+ {
+ const CPVRChannelNumber channelNumber = groupMember->ChannelNumber();
+ CServiceBroker::GetAppMessenger()->SendMsg(
+ TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(new CAction(
+ ACTION_CHANNEL_SWITCH, static_cast<float>(channelNumber.GetChannelNumber()),
+ static_cast<float>(channelNumber.GetSubChannelNumber()))));
+ }
+ }
+ }
+}
+
+CPVRGUIActionsChannels::CPVRGUIActionsChannels()
+ : m_settings({CSettings::SETTING_PVRMANAGER_PRESELECTPLAYINGCHANNEL})
+{
+ RegisterChannelNumberInputHandler(&m_channelNumberInputHandler);
+}
+
+CPVRGUIActionsChannels::~CPVRGUIActionsChannels()
+{
+ DeregisterChannelNumberInputHandler(&m_channelNumberInputHandler);
+}
+
+void CPVRGUIActionsChannels::RegisterChannelNumberInputHandler(
+ CPVRChannelNumberInputHandler* handler)
+{
+ if (handler)
+ handler->Events().Subscribe(this, &CPVRGUIActionsChannels::Notify);
+}
+
+void CPVRGUIActionsChannels::DeregisterChannelNumberInputHandler(
+ CPVRChannelNumberInputHandler* handler)
+{
+ if (handler)
+ handler->Events().Unsubscribe(this);
+}
+
+void CPVRGUIActionsChannels::Notify(const PVRChannelNumberInputChangedEvent& event)
+{
+ m_events.Publish(event);
+}
+
+bool CPVRGUIActionsChannels::HideChannel(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVRChannel> channel = item.GetPVRChannelInfoTag();
+
+ if (!channel)
+ return false;
+
+ if (!CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{19054}, // "Hide channel"
+ CVariant{19039}, // "Are you sure you want to hide this channel?"
+ CVariant{""}, CVariant{channel->ChannelName()}))
+ return false;
+
+ if (!CServiceBroker::GetPVRManager()
+ .ChannelGroups()
+ ->GetGroupAll(channel->IsRadio())
+ ->RemoveFromGroup(channel))
+ return false;
+
+ CGUIWindowPVRBase* pvrWindow =
+ dynamic_cast<CGUIWindowPVRBase*>(CServiceBroker::GetGUI()->GetWindowManager().GetWindow(
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()));
+ if (pvrWindow)
+ pvrWindow->DoRefresh();
+ else
+ CLog::LogF(LOGERROR, "Called on non-pvr window. No refresh possible.");
+
+ return true;
+}
+
+bool CPVRGUIActionsChannels::StartChannelScan()
+{
+ return StartChannelScan(PVR_INVALID_CLIENT_ID);
+}
+
+bool CPVRGUIActionsChannels::StartChannelScan(int clientId)
+{
+ if (!CServiceBroker::GetPVRManager().IsStarted() || IsRunningChannelScan())
+ return false;
+
+ std::shared_ptr<CPVRClient> scanClient;
+ std::vector<std::shared_ptr<CPVRClient>> possibleScanClients =
+ CServiceBroker::GetPVRManager().Clients()->GetClientsSupportingChannelScan();
+ m_bChannelScanRunning = true;
+
+ if (clientId != PVR_INVALID_CLIENT_ID)
+ {
+ const auto it =
+ std::find_if(possibleScanClients.cbegin(), possibleScanClients.cend(),
+ [clientId](const auto& client) { return client->GetID() == clientId; });
+
+ if (it != possibleScanClients.cend())
+ scanClient = (*it);
+
+ if (!scanClient)
+ {
+ CLog::LogF(LOGERROR,
+ "Provided client id '{}' could not be found in list of possible scan clients!",
+ clientId);
+ m_bChannelScanRunning = false;
+ return false;
+ }
+ }
+ /* multiple clients found */
+ else if (possibleScanClients.size() > 1)
+ {
+ CGUIDialogSelect* pDialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT);
+ if (!pDialog)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT!");
+ m_bChannelScanRunning = false;
+ return false;
+ }
+
+ pDialog->Reset();
+ pDialog->SetHeading(CVariant{19119}); // "On which backend do you want to search?"
+
+ for (const auto& client : possibleScanClients)
+ pDialog->Add(client->GetFriendlyName());
+
+ pDialog->Open();
+
+ int selection = pDialog->GetSelectedItem();
+ if (selection >= 0)
+ scanClient = possibleScanClients[selection];
+ }
+ /* one client found */
+ else if (possibleScanClients.size() == 1)
+ {
+ scanClient = possibleScanClients[0];
+ }
+ /* no clients found */
+ else if (!scanClient)
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033}, // "Information"
+ CVariant{19192}); // "None of the connected PVR backends supports scanning for channels."
+ m_bChannelScanRunning = false;
+ return false;
+ }
+
+ /* start the channel scan */
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Starting to scan for channels on client {}",
+ scanClient->GetFriendlyName());
+ auto start = std::chrono::steady_clock::now();
+
+ /* do the scan */
+ if (scanClient->StartChannelScan() != PVR_ERROR_NO_ERROR)
+ HELPERS::ShowOKDialogText(
+ CVariant{257}, // "Error"
+ CVariant{
+ 19193}); // "The channel scan can't be started. Check the log for more information about this message."
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Channel scan finished after {} ms", duration.count());
+
+ m_bChannelScanRunning = false;
+ return true;
+}
+
+std::shared_ptr<CPVRChannelGroupMember> CPVRGUIActionsChannels::GetChannelGroupMember(
+ const std::shared_ptr<CPVRChannel>& channel) const
+{
+ if (!channel)
+ return {};
+
+ std::shared_ptr<CPVRChannelGroupMember> groupMember;
+
+ // first, try whether the channel is contained in the active channel group, except
+ // if a window is active which never uses the active channel group, e.g. Timers window
+ const int activeWindowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
+
+ static std::vector<int> windowIDs = {
+ WINDOW_TV_RECORDINGS, WINDOW_TV_TIMERS, WINDOW_TV_TIMER_RULES, WINDOW_TV_SEARCH,
+ WINDOW_RADIO_RECORDINGS, WINDOW_RADIO_TIMERS, WINDOW_RADIO_TIMER_RULES, WINDOW_RADIO_SEARCH,
+ };
+
+ if (std::find(windowIDs.cbegin(), windowIDs.cend(), activeWindowID) == windowIDs.cend())
+ {
+ const std::shared_ptr<CPVRChannelGroup> group =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(channel->IsRadio());
+ if (group)
+ groupMember = group->GetByUniqueID(channel->StorageId());
+ }
+
+ // as fallback, obtain the member from the 'all channels' group
+ if (!groupMember)
+ {
+ const std::shared_ptr<CPVRChannelGroup> group =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(channel->IsRadio());
+ if (group)
+ groupMember = group->GetByUniqueID(channel->StorageId());
+ }
+
+ return groupMember;
+}
+
+std::shared_ptr<CPVRChannelGroupMember> CPVRGUIActionsChannels::GetChannelGroupMember(
+ const CFileItem& item) const
+{
+ std::shared_ptr<CPVRChannelGroupMember> groupMember = item.GetPVRChannelGroupMemberInfoTag();
+
+ if (!groupMember)
+ groupMember = GetChannelGroupMember(CPVRItem(std::make_shared<CFileItem>(item)).GetChannel());
+
+ return groupMember;
+}
+
+CPVRChannelNumberInputHandler& CPVRGUIActionsChannels::GetChannelNumberInputHandler()
+{
+ // window/dialog specific input handler
+ CPVRChannelNumberInputHandler* windowInputHandler = dynamic_cast<CPVRChannelNumberInputHandler*>(
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow(
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()));
+ if (windowInputHandler)
+ return *windowInputHandler;
+
+ // default
+ return m_channelNumberInputHandler;
+}
+
+CPVRGUIChannelNavigator& CPVRGUIActionsChannels::GetChannelNavigator()
+{
+ return m_channelNavigator;
+}
+
+void CPVRGUIActionsChannels::OnPlaybackStarted(const CFileItem& item)
+{
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember = GetChannelGroupMember(item);
+ if (groupMember)
+ {
+ m_channelNavigator.SetPlayingChannel(groupMember);
+ SetSelectedChannelPath(groupMember->Channel()->IsRadio(), groupMember->Path());
+ }
+}
+
+void CPVRGUIActionsChannels::OnPlaybackStopped(const CFileItem& item)
+{
+ if (item.HasPVRChannelInfoTag() || item.HasEPGInfoTag())
+ {
+ m_channelNavigator.ClearPlayingChannel();
+ }
+}
+
+void CPVRGUIActionsChannels::SetSelectedChannelPath(bool bRadio, const std::string& path)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (bRadio)
+ m_selectedChannelPathRadio = path;
+ else
+ m_selectedChannelPathTV = path;
+}
+
+std::string CPVRGUIActionsChannels::GetSelectedChannelPath(bool bRadio) const
+{
+ if (m_settings.GetBoolValue(CSettings::SETTING_PVRMANAGER_PRESELECTPLAYINGCHANNEL))
+ {
+ CPVRManager& mgr = CServiceBroker::GetPVRManager();
+
+ // if preselect playing channel is activated, return the path of the playing channel, if any.
+ const std::shared_ptr<CPVRChannelGroupMember> playingChannel =
+ mgr.PlaybackState()->GetPlayingChannelGroupMember();
+ if (playingChannel && playingChannel->IsRadio() == bRadio)
+ return playingChannel->Path();
+
+ const std::shared_ptr<CPVREpgInfoTag> playingTag = mgr.PlaybackState()->GetPlayingEpgTag();
+ if (playingTag && playingTag->IsRadio() == bRadio)
+ {
+ const std::shared_ptr<CPVRChannel> channel =
+ mgr.ChannelGroups()->GetChannelForEpgTag(playingTag);
+ if (channel)
+ return GetChannelGroupMember(channel)->Path();
+ }
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return bRadio ? m_selectedChannelPathRadio : m_selectedChannelPathTV;
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.h b/xbmc/pvr/guilib/PVRGUIActionsChannels.h
new file mode 100644
index 0000000..a7656fe
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.h
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+#include "pvr/PVRChannelNumberInputHandler.h"
+#include "pvr/guilib/PVRGUIChannelNavigator.h"
+#include "pvr/settings/PVRSettings.h"
+#include "threads/CriticalSection.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVRChannel;
+class CPVRChannelGroupMember;
+
+class CPVRChannelSwitchingInputHandler : public CPVRChannelNumberInputHandler
+{
+public:
+ // CPVRChannelNumberInputHandler implementation
+ void GetChannelNumbers(std::vector<std::string>& channelNumbers) override;
+ void AppendChannelNumberCharacter(char cCharacter) override;
+ void OnInputDone() override;
+
+private:
+ /*!
+ * @brief Switch to the channel with the given number.
+ * @param channelNumber the channel number
+ */
+ void SwitchToChannel(const CPVRChannelNumber& channelNumber);
+
+ /*!
+ * @brief Switch to the previously played channel.
+ */
+ void SwitchToPreviousChannel();
+};
+
+class CPVRGUIActionsChannels : public IPVRComponent
+{
+public:
+ CPVRGUIActionsChannels();
+ ~CPVRGUIActionsChannels() override;
+
+ /*!
+ * @brief Get the events available for CEventStream.
+ * @return The events.
+ */
+ CEventStream<PVRChannelNumberInputChangedEvent>& Events() { return m_events; }
+
+ /*!
+ * @brief Register a handler for channel number input.
+ * @param handler The handler to register.
+ */
+ void RegisterChannelNumberInputHandler(CPVRChannelNumberInputHandler* handler);
+
+ /*!
+ * @brief Deregister a handler for channel number input.
+ * @param handler The handler to deregister.
+ */
+ void DeregisterChannelNumberInputHandler(CPVRChannelNumberInputHandler* handler);
+
+ /*!
+ * @brief CEventStream callback for channel number input changes.
+ * @param event The event.
+ */
+ void Notify(const PVRChannelNumberInputChangedEvent& event);
+
+ /*!
+ * @brief Hide a channel, always showing a confirmation dialog.
+ * @param item containing a channel or an epg tag.
+ * @return true on success, false otherwise.
+ */
+ bool HideChannel(const CFileItem& item) const;
+
+ /*!
+ * @brief Open a selection dialog and start a channel scan on the selected client.
+ * @return true on success, false otherwise.
+ */
+ bool StartChannelScan();
+
+ /*!
+ * @brief Start a channel scan on the specified client or open a dialog to select a client
+ * @param clientId the id of client to scan or PVR_INVALID_CLIENT_ID if a dialog will be opened
+ * @return true on success, false otherwise.
+ */
+ bool StartChannelScan(int clientId);
+
+ /*!
+ * @return True when a channel scan is currently running, false otherwise.
+ */
+ bool IsRunningChannelScan() const { return m_bChannelScanRunning; }
+
+ /*!
+ * @brief Get a channel group member for the given channel, either from the currently active
+ * group or if not found there, from the 'all channels' group.
+ * @param channel the channel.
+ * @return the group member or nullptr if not found.
+ */
+ std::shared_ptr<CPVRChannelGroupMember> GetChannelGroupMember(
+ const std::shared_ptr<CPVRChannel>& channel) const;
+
+ /*!
+ * @brief Get a channel group member for the given item, either from the currently active group
+ * or if not found there, from the 'all channels' group.
+ * @param item the item containing a channel, channel group, recording, timer or epg tag.
+ * @return the group member or nullptr if not found.
+ */
+ std::shared_ptr<CPVRChannelGroupMember> GetChannelGroupMember(const CFileItem& item) const;
+
+ /*!
+ * @brief Get the currently active channel number input handler.
+ * @return the handler.
+ */
+ CPVRChannelNumberInputHandler& GetChannelNumberInputHandler();
+
+ /*!
+ * @brief Get the channel navigator.
+ * @return the navigator.
+ */
+ CPVRGUIChannelNavigator& GetChannelNavigator();
+
+ /*!
+ * @brief Inform GUI actions that playback of an item just started.
+ * @param item The item that started to play.
+ */
+ void OnPlaybackStarted(const CFileItem& item);
+
+ /*!
+ * @brief Inform GUI actions that playback of an item was stopped due to user interaction.
+ * @param item The item that stopped to play.
+ */
+ void OnPlaybackStopped(const CFileItem& item);
+
+ /*!
+ * @brief Get the currently selected channel item path; used across several windows/dialogs to
+ * share item selection.
+ * @param bRadio True to query the selected path for PVR radio, false for Live TV.
+ * @return the path.
+ */
+ std::string GetSelectedChannelPath(bool bRadio) const;
+
+ /*!
+ * @brief Set the currently selected channel item path; used across several windows/dialogs to
+ * share item selection.
+ * @param bRadio True to set the selected path for PVR radio, false for Live TV.
+ * @param path The new path to set.
+ */
+ void SetSelectedChannelPath(bool bRadio, const std::string& path);
+
+private:
+ CPVRGUIActionsChannels(const CPVRGUIActionsChannels&) = delete;
+ CPVRGUIActionsChannels const& operator=(CPVRGUIActionsChannels const&) = delete;
+
+ CPVRChannelSwitchingInputHandler m_channelNumberInputHandler;
+ bool m_bChannelScanRunning{false};
+ CPVRGUIChannelNavigator m_channelNavigator;
+ CEventSource<PVRChannelNumberInputChangedEvent> m_events;
+
+ mutable CCriticalSection m_critSection;
+ CPVRSettings m_settings;
+ std::string m_selectedChannelPathTV;
+ std::string m_selectedChannelPathRadio;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Channels = CPVRGUIActionsChannels;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsClients.cpp b/xbmc/pvr/guilib/PVRGUIActionsClients.cpp
new file mode 100644
index 0000000..b9c598c
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsClients.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsClients.h"
+
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClientMenuHooks.h"
+#include "pvr/addons/PVRClients.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <utility>
+#include <vector>
+
+using namespace KODI::MESSAGING;
+
+using namespace PVR;
+
+bool CPVRGUIActionsClients::ProcessSettingsMenuHooks()
+{
+ const CPVRClientMap clients = CServiceBroker::GetPVRManager().Clients()->GetCreatedClients();
+
+ std::vector<std::pair<std::shared_ptr<CPVRClient>, CPVRClientMenuHook>> settingsHooks;
+ for (const auto& client : clients)
+ {
+ const auto hooks = client.second->GetMenuHooks()->GetSettingsHooks();
+ std::transform(hooks.cbegin(), hooks.cend(), std::back_inserter(settingsHooks),
+ [&client](const auto& hook) { return std::make_pair(client.second, hook); });
+ }
+
+ if (settingsHooks.empty())
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033}, // "Information"
+ CVariant{19347}); // "None of the active PVR clients does provide client-specific settings."
+ return true; // no settings hooks, no error
+ }
+
+ auto selectedHook = settingsHooks.begin();
+
+ // if there is only one settings hook, execute it directly, otherwise let the user select
+ if (settingsHooks.size() > 1)
+ {
+ CGUIDialogSelect* pDialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT);
+ if (!pDialog)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT!");
+ return false;
+ }
+
+ pDialog->Reset();
+ pDialog->SetHeading(CVariant{19196}); // "PVR client specific actions"
+
+ for (const auto& hook : settingsHooks)
+ {
+ if (clients.size() == 1)
+ pDialog->Add(hook.second.GetLabel());
+ else
+ pDialog->Add(hook.first->GetFriendlyName() + ": " + hook.second.GetLabel());
+ }
+
+ pDialog->Open();
+
+ int selection = pDialog->GetSelectedItem();
+ if (selection < 0)
+ return true; // cancelled
+
+ std::advance(selectedHook, selection);
+ }
+ return selectedHook->first->CallSettingsMenuHook(selectedHook->second) == PVR_ERROR_NO_ERROR;
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsClients.h b/xbmc/pvr/guilib/PVRGUIActionsClients.h
new file mode 100644
index 0000000..7c48274
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsClients.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+
+namespace PVR
+{
+class CPVRGUIActionsClients : public IPVRComponent
+{
+public:
+ CPVRGUIActionsClients() = default;
+ ~CPVRGUIActionsClients() override = default;
+
+ /*!
+ * @brief Select and invoke client-specific settings actions
+ * @return true on success, false otherwise.
+ */
+ bool ProcessSettingsMenuHooks();
+
+private:
+ CPVRGUIActionsClients(const CPVRGUIActionsClients&) = delete;
+ CPVRGUIActionsClients const& operator=(CPVRGUIActionsClients const&) = delete;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Clients = CPVRGUIActionsClients;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp b/xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp
new file mode 100644
index 0000000..53bbf81
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsDatabase.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pvr/PVRDatabase.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgDatabase.h"
+#include "pvr/guilib/PVRGUIActionsParentalControl.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/recordings/PVRRecordingsPath.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "video/VideoDatabase.h"
+
+#include <memory>
+#include <string>
+
+using namespace PVR;
+
+namespace
+{
+class CPVRGUIDatabaseResetComponentsSelector
+{
+public:
+ CPVRGUIDatabaseResetComponentsSelector() = default;
+ virtual ~CPVRGUIDatabaseResetComponentsSelector() = default;
+
+ bool Select()
+ {
+ CGUIDialogSelect* pDlgSelect =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT);
+ if (!pDlgSelect)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT!");
+ return false;
+ }
+
+ CFileItemList options;
+
+ const std::shared_ptr<CFileItem> itemAll =
+ std::make_shared<CFileItem>(StringUtils::Format(g_localizeStrings.Get(593))); // All
+ itemAll->SetPath("all");
+ options.Add(itemAll);
+
+ // if channels are cleared, groups, EPG data and providers must also be cleared
+ const std::shared_ptr<CFileItem> itemChannels =
+ std::make_shared<CFileItem>(StringUtils::Format("{}, {}, {}, {}",
+ g_localizeStrings.Get(19019), // Channels
+ g_localizeStrings.Get(19146), // Groups
+ g_localizeStrings.Get(19069), // Guide
+ g_localizeStrings.Get(19334))); // Providers
+ itemChannels->SetPath("channels");
+ itemChannels->Select(true); // preselect this item in dialog
+ options.Add(itemChannels);
+
+ const std::shared_ptr<CFileItem> itemGroups =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(19146)); // Groups
+ itemGroups->SetPath("groups");
+ options.Add(itemGroups);
+
+ const std::shared_ptr<CFileItem> itemGuide =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(19069)); // Guide
+ itemGuide->SetPath("guide");
+ options.Add(itemGuide);
+
+ const std::shared_ptr<CFileItem> itemProviders =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(19334)); // Providers
+ itemProviders->SetPath("providers");
+ options.Add(itemProviders);
+
+ const std::shared_ptr<CFileItem> itemReminders =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(19215)); // Reminders
+ itemReminders->SetPath("reminders");
+ options.Add(itemReminders);
+
+ const std::shared_ptr<CFileItem> itemRecordings =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(19017)); // Recordings
+ itemRecordings->SetPath("recordings");
+ options.Add(itemRecordings);
+
+ const std::shared_ptr<CFileItem> itemClients =
+ std::make_shared<CFileItem>(g_localizeStrings.Get(24019)); // PVR clients
+ itemClients->SetPath("clients");
+ options.Add(itemClients);
+
+ pDlgSelect->Reset();
+ pDlgSelect->SetHeading(CVariant{g_localizeStrings.Get(19185)}); // "Clear data"
+ pDlgSelect->SetItems(options);
+ pDlgSelect->SetMultiSelection(true);
+ pDlgSelect->Open();
+
+ if (!pDlgSelect->IsConfirmed())
+ return false;
+
+ for (int i : pDlgSelect->GetSelectedItems())
+ {
+ const std::string path = options.Get(i)->GetPath();
+
+ m_bResetChannels |= (path == "channels" || path == "all");
+ m_bResetGroups |= (path == "groups" || path == "all");
+ m_bResetGuide |= (path == "guide" || path == "all");
+ m_bResetProviders |= (path == "providers" || path == "all");
+ m_bResetReminders |= (path == "reminders" || path == "all");
+ m_bResetRecordings |= (path == "recordings" || path == "all");
+ m_bResetClients |= (path == "clients" || path == "all");
+ }
+
+ m_bResetGroups |= m_bResetChannels;
+ m_bResetGuide |= m_bResetChannels;
+ m_bResetProviders |= m_bResetChannels;
+
+ return (m_bResetChannels || m_bResetGroups || m_bResetGuide || m_bResetProviders ||
+ m_bResetReminders || m_bResetRecordings || m_bResetClients);
+ }
+
+ bool IsResetChannelsSelected() const { return m_bResetChannels; }
+ bool IsResetGroupsSelected() const { return m_bResetGroups; }
+ bool IsResetGuideSelected() const { return m_bResetGuide; }
+ bool IsResetProvidersSelected() const { return m_bResetProviders; }
+ bool IsResetRemindersSelected() const { return m_bResetReminders; }
+ bool IsResetRecordingsSelected() const { return m_bResetRecordings; }
+ bool IsResetClientsSelected() const { return m_bResetClients; }
+
+private:
+ bool m_bResetChannels = false;
+ bool m_bResetGroups = false;
+ bool m_bResetGuide = false;
+ bool m_bResetProviders = false;
+ bool m_bResetReminders = false;
+ bool m_bResetRecordings = false;
+ bool m_bResetClients = false;
+};
+
+} // unnamed namespace
+
+bool CPVRGUIActionsDatabase::ResetDatabase(bool bResetEPGOnly)
+{
+ CGUIDialogProgress* pDlgProgress =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
+ WINDOW_DIALOG_PROGRESS);
+ if (!pDlgProgress)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PROGRESS!");
+ return false;
+ }
+
+ bool bResetChannels = false;
+ bool bResetGroups = false;
+ bool bResetGuide = false;
+ bool bResetProviders = false;
+ bool bResetReminders = false;
+ bool bResetRecordings = false;
+ bool bResetClients = false;
+
+ if (bResetEPGOnly)
+ {
+ if (!CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{19098}, // "Warning!"
+ CVariant{19188})) // "All guide data will be cleared. Are you sure?"
+ return false;
+
+ bResetGuide = true;
+ }
+ else
+ {
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalPIN() !=
+ ParentalCheckResult::SUCCESS)
+ return false;
+
+ CPVRGUIDatabaseResetComponentsSelector selector;
+ if (!selector.Select())
+ return false;
+
+ if (!CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{19098}, // "Warning!"
+ CVariant{19186})) // "All selected data will be cleared. ... Are you sure?"
+ return false;
+
+ bResetChannels = selector.IsResetChannelsSelected();
+ bResetGroups = selector.IsResetGroupsSelected();
+ bResetGuide = selector.IsResetGuideSelected();
+ bResetProviders = selector.IsResetProvidersSelected();
+ bResetReminders = selector.IsResetRemindersSelected();
+ bResetRecordings = selector.IsResetRecordingsSelected();
+ bResetClients = selector.IsResetClientsSelected();
+ }
+
+ CDateTime::ResetTimezoneBias();
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "PVR clearing {} database", bResetEPGOnly ? "EPG" : "PVR and EPG");
+
+ pDlgProgress->SetHeading(CVariant{313}); // "Cleaning database"
+ pDlgProgress->SetLine(0, CVariant{g_localizeStrings.Get(19187)}); // "Clearing all related data."
+ pDlgProgress->SetLine(1, CVariant{""});
+ pDlgProgress->SetLine(2, CVariant{""});
+
+ pDlgProgress->Open();
+ pDlgProgress->Progress();
+
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
+ {
+ CLog::Log(LOGINFO, "PVR is stopping playback for {} database reset",
+ bResetEPGOnly ? "EPG" : "PVR and EPG");
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP);
+ }
+
+ const std::shared_ptr<CPVRDatabase> pvrDatabase(CServiceBroker::GetPVRManager().GetTVDatabase());
+ const std::shared_ptr<CPVREpgDatabase> epgDatabase(
+ CServiceBroker::GetPVRManager().EpgContainer().GetEpgDatabase());
+
+ // increase db open refcounts, so they don't get closed during following pvr manager shutdown
+ pvrDatabase->Open();
+ epgDatabase->Open();
+
+ // stop pvr manager; close both pvr and epg databases
+ CServiceBroker::GetPVRManager().Stop();
+
+ const int iProgressStepPercentage =
+ 100 / ((2 * bResetChannels) + bResetGroups + bResetGuide + bResetProviders + bResetReminders +
+ bResetRecordings + bResetClients + 1);
+ int iProgressStepsDone = 0;
+
+ if (bResetProviders)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all providers
+ pvrDatabase->DeleteProviders();
+ }
+
+ if (bResetGuide)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // reset channel's EPG pointers
+ pvrDatabase->ResetEPG();
+
+ // delete all entries from the EPG database
+ epgDatabase->DeleteEpg();
+ }
+
+ if (bResetGroups)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all channel groups (including data only available locally, like user defined groups)
+ pvrDatabase->DeleteChannelGroups();
+ }
+
+ if (bResetChannels)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all channels (including data only available locally, like user set icons)
+ pvrDatabase->DeleteChannels();
+ }
+
+ if (bResetReminders)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all timers data (e.g. all reminders, which are only stored locally)
+ pvrDatabase->DeleteTimers();
+ }
+
+ if (bResetClients)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all clients data (e.g priorities, which are only stored locally)
+ pvrDatabase->DeleteClients();
+ }
+
+ if (bResetChannels || bResetRecordings)
+ {
+ CVideoDatabase videoDatabase;
+
+ if (videoDatabase.Open())
+ {
+ if (bResetChannels)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all channel's entries (e.g. settings, bookmarks, stream details)
+ videoDatabase.EraseAllForPath("pvr://channels/");
+ }
+
+ if (bResetRecordings)
+ {
+ pDlgProgress->SetPercentage(iProgressStepPercentage * ++iProgressStepsDone);
+ pDlgProgress->Progress();
+
+ // delete all recording's entries (e.g. settings, bookmarks, stream details)
+ videoDatabase.EraseAllForPath(CPVRRecordingsPath::PATH_RECORDINGS);
+ }
+
+ videoDatabase.Close();
+ }
+ }
+
+ // decrease db open refcounts; this actually closes dbs because refcounts drops to zero
+ pvrDatabase->Close();
+ epgDatabase->Close();
+
+ CLog::LogFC(LOGDEBUG, LOGPVR, "{} database cleared", bResetEPGOnly ? "EPG" : "PVR and EPG");
+
+ CLog::Log(LOGINFO, "Restarting the PVR Manager after {} database reset",
+ bResetEPGOnly ? "EPG" : "PVR and EPG");
+ CServiceBroker::GetPVRManager().Start();
+
+ pDlgProgress->SetPercentage(100);
+ pDlgProgress->Close();
+ return true;
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsDatabase.h b/xbmc/pvr/guilib/PVRGUIActionsDatabase.h
new file mode 100644
index 0000000..62eb484
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsDatabase.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+
+namespace PVR
+{
+class CPVRGUIActionsDatabase : public IPVRComponent
+{
+public:
+ CPVRGUIActionsDatabase() = default;
+ ~CPVRGUIActionsDatabase() override = default;
+
+ /*!
+ * @brief Reset the TV database to it's initial state and delete all the data.
+ * @param bResetEPGOnly True to only reset the EPG database, false to reset both PVR and EPG
+ * database.
+ * @return true on success, false otherwise.
+ */
+ bool ResetDatabase(bool bResetEPGOnly);
+
+private:
+ CPVRGUIActionsDatabase(const CPVRGUIActionsDatabase&) = delete;
+ CPVRGUIActionsDatabase const& operator=(CPVRGUIActionsDatabase const&) = delete;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Database = CPVRGUIActionsDatabase;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
new file mode 100644
index 0000000..8b5cc9b
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsEPG.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/dialogs/GUIDialogPVRChannelGuide.h"
+#include "pvr/dialogs/GUIDialogPVRGuideInfo.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgSearchFilter.h"
+#include "pvr/guilib/PVRGUIActionsParentalControl.h"
+#include "pvr/windows/GUIWindowPVRSearch.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <memory>
+#include <string>
+
+using namespace PVR;
+
+namespace
+{
+PVR::CGUIWindowPVRSearchBase* GetSearchWindow(bool bRadio)
+{
+ const int windowSearchId = bRadio ? WINDOW_RADIO_SEARCH : WINDOW_TV_SEARCH;
+
+ PVR::CGUIWindowPVRSearchBase* windowSearch;
+
+ CGUIWindowManager& windowMgr = CServiceBroker::GetGUI()->GetWindowManager();
+ if (bRadio)
+ windowSearch = windowMgr.GetWindow<PVR::CGUIWindowPVRRadioSearch>(windowSearchId);
+ else
+ windowSearch = windowMgr.GetWindow<PVR::CGUIWindowPVRTVSearch>(windowSearchId);
+
+ if (!windowSearch)
+ CLog::LogF(LOGERROR, "Unable to get {}!", bRadio ? "WINDOW_RADIO_SEARCH" : "WINDOW_TV_SEARCH");
+
+ return windowSearch;
+}
+} // unnamed namespace
+
+bool CPVRGUIActionsEPG::ShowEPGInfo(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
+ if (channel && CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(
+ channel) != ParentalCheckResult::SUCCESS)
+ return false;
+
+ const std::shared_ptr<CPVREpgInfoTag> epgTag(CPVRItem(item).GetEpgInfoTag());
+ if (!epgTag)
+ {
+ CLog::LogF(LOGERROR, "No epg tag!");
+ return false;
+ }
+
+ CGUIDialogPVRGuideInfo* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRGuideInfo>(
+ WINDOW_DIALOG_PVR_GUIDE_INFO);
+ if (!pDlgInfo)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_GUIDE_INFO!");
+ return false;
+ }
+
+ pDlgInfo->SetProgInfo(std::make_shared<CFileItem>(epgTag));
+ pDlgInfo->Open();
+ return true;
+}
+
+bool CPVRGUIActionsEPG::ShowChannelEPG(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
+ if (channel && CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(
+ channel) != ParentalCheckResult::SUCCESS)
+ return false;
+
+ CGUIDialogPVRChannelGuide* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRChannelGuide>(
+ WINDOW_DIALOG_PVR_CHANNEL_GUIDE);
+ if (!pDlgInfo)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_CHANNEL_GUIDE!");
+ return false;
+ }
+
+ pDlgInfo->Open(channel);
+ return true;
+}
+
+bool CPVRGUIActionsEPG::FindSimilar(const CFileItem& item) const
+{
+ CGUIWindowPVRSearchBase* windowSearch = GetSearchWindow(CPVRItem(item).IsRadio());
+ if (!windowSearch)
+ return false;
+
+ //! @todo If we want dialogs to spawn program search in a clean way - without having to force-close any
+ // other dialogs - we must introduce a search dialog with functionality similar to the search window.
+
+ for (int iId = CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog(
+ true /* ignoreClosing */);
+ iId != WINDOW_INVALID;
+ iId = CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog(
+ true /* ignoreClosing */))
+ {
+ CLog::LogF(LOGWARNING,
+ "Have to close modal dialog with id {} before search window can be opened.", iId);
+
+ CGUIWindow* window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(iId);
+ if (window)
+ {
+ window->Close();
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Unable to get window instance {}! Cannot open search window.", iId);
+ return false; // return, otherwise we run into an endless loop
+ }
+ }
+
+ windowSearch->SetItemToSearch(item);
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(windowSearch->GetID());
+ return true;
+};
+
+bool CPVRGUIActionsEPG::ExecuteSavedSearch(const CFileItem& item)
+{
+ const auto searchFilter = item.GetEPGSearchFilter();
+
+ if (!searchFilter)
+ {
+ CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present.");
+ return false;
+ }
+
+ CGUIWindowPVRSearchBase* windowSearch = GetSearchWindow(searchFilter->IsRadio());
+ if (!windowSearch)
+ return false;
+
+ windowSearch->SetItemToSearch(item);
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(windowSearch->GetID());
+ return true;
+}
+
+bool CPVRGUIActionsEPG::EditSavedSearch(const CFileItem& item)
+{
+ const auto searchFilter = item.GetEPGSearchFilter();
+
+ if (!searchFilter)
+ {
+ CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present.");
+ return false;
+ }
+
+ CGUIWindowPVRSearchBase* windowSearch = GetSearchWindow(searchFilter->IsRadio());
+ if (!windowSearch)
+ return false;
+
+ if (windowSearch->OpenDialogSearch(item) == CGUIDialogPVRGuideSearch::Result::SEARCH)
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(windowSearch->GetID());
+
+ return true;
+}
+
+bool CPVRGUIActionsEPG::RenameSavedSearch(const CFileItem& item)
+{
+ const auto searchFilter = item.GetEPGSearchFilter();
+
+ if (!searchFilter)
+ {
+ CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present.");
+ return false;
+ }
+
+ std::string title = searchFilter->GetTitle();
+ if (CGUIKeyboardFactory::ShowAndGetInput(title,
+ CVariant{g_localizeStrings.Get(528)}, // "Enter title"
+ false))
+ {
+ searchFilter->SetTitle(title);
+ CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*searchFilter);
+ return true;
+ }
+ return false;
+}
+
+bool CPVRGUIActionsEPG::DeleteSavedSearch(const CFileItem& item)
+{
+ const auto searchFilter = item.GetEPGSearchFilter();
+
+ if (!searchFilter)
+ {
+ CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present.");
+ return false;
+ }
+
+ if (CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, // "Confirm delete"
+ CVariant{19338}, // "Delete this saved search?"
+ CVariant{""}, CVariant{item.GetLabel()}))
+ {
+ return CServiceBroker::GetPVRManager().EpgContainer().DeleteSavedSearch(*searchFilter);
+ }
+ return false;
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.h b/xbmc/pvr/guilib/PVRGUIActionsEPG.h
new file mode 100644
index 0000000..e0a3edc
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVRGUIActionsEPG : public IPVRComponent
+{
+public:
+ CPVRGUIActionsEPG() = default;
+ ~CPVRGUIActionsEPG() override = default;
+
+ /*!
+ * @brief Open a dialog with epg information for a given item.
+ * @param item containing epg data to show. item must be an epg tag, a channel or a timer.
+ * @return true on success, false otherwise.
+ */
+ bool ShowEPGInfo(const CFileItem& item) const;
+
+ /*!
+ * @brief Open a dialog with the epg list for a given item.
+ * @param item containing channel info. item must be an epg tag, a channel or a timer.
+ * @return true on success, false otherwise.
+ */
+ bool ShowChannelEPG(const CFileItem& item) const;
+
+ /*!
+ * @brief Open a window containing a list of epg tags 'similar' to a given item.
+ * @param item containing epg data for matching. item must be an epg tag, a channel or a
+ * recording.
+ * @return true on success, false otherwise.
+ */
+ bool FindSimilar(const CFileItem& item) const;
+
+ /*!
+ * @brief Execute a saved search. Displays result in search window if it is open.
+ * @param item The item containing a search filter.
+ * @return True on success, false otherwise.
+ */
+ bool ExecuteSavedSearch(const CFileItem& item);
+
+ /*!
+ * @brief Edit a saved search. Opens the search dialog.
+ * @param item The item containing a search filter.
+ * @return True on success, false otherwise.
+ */
+ bool EditSavedSearch(const CFileItem& item);
+
+ /*!
+ * @brief Rename a saved search. Opens a title input dialog.
+ * @param item The item containing a search filter.
+ * @return True on success, false otherwise.
+ */
+ bool RenameSavedSearch(const CFileItem& item);
+
+ /*!
+ * @brief Delete a saved search. Opens confirmation dialog before deleting.
+ * @param item The item containing a search filter.
+ * @return True on success, false otherwise.
+ */
+ bool DeleteSavedSearch(const CFileItem& item);
+
+private:
+ CPVRGUIActionsEPG(const CPVRGUIActionsEPG&) = delete;
+ CPVRGUIActionsEPG const& operator=(CPVRGUIActionsEPG const&) = delete;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using EPG = CPVRGUIActionsEPG;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsParentalControl.cpp b/xbmc/pvr/guilib/PVRGUIActionsParentalControl.cpp
new file mode 100644
index 0000000..96947c4
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsParentalControl.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsParentalControl.h"
+
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "guilib/LocalizeStrings.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRManager.h"
+#include "pvr/channels/PVRChannel.h"
+#include "settings/Settings.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <memory>
+#include <string>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+CPVRGUIActionsParentalControl::CPVRGUIActionsParentalControl()
+ : m_settings({CSettings::SETTING_PVRPARENTAL_PIN, CSettings::SETTING_PVRPARENTAL_ENABLED})
+{
+}
+
+ParentalCheckResult CPVRGUIActionsParentalControl::CheckParentalLock(
+ const std::shared_ptr<CPVRChannel>& channel) const
+{
+ if (!CServiceBroker::GetPVRManager().IsParentalLocked(channel))
+ return ParentalCheckResult::SUCCESS;
+
+ ParentalCheckResult ret = CheckParentalPIN();
+
+ if (ret == ParentalCheckResult::FAILED)
+ CLog::LogF(LOGERROR, "Parental lock verification failed for channel '{}': wrong PIN entered.",
+ channel->ChannelName());
+
+ return ret;
+}
+
+ParentalCheckResult CPVRGUIActionsParentalControl::CheckParentalPIN() const
+{
+ if (!m_settings.GetBoolValue(CSettings::SETTING_PVRPARENTAL_ENABLED))
+ return ParentalCheckResult::SUCCESS;
+
+ std::string pinCode = m_settings.GetStringValue(CSettings::SETTING_PVRPARENTAL_PIN);
+ if (pinCode.empty())
+ return ParentalCheckResult::SUCCESS;
+
+ InputVerificationResult ret = CGUIDialogNumeric::ShowAndVerifyInput(
+ pinCode, g_localizeStrings.Get(19262), true); // "Parental control. Enter PIN:"
+
+ if (ret == InputVerificationResult::SUCCESS)
+ {
+ CServiceBroker::GetPVRManager().RestartParentalTimer();
+ return ParentalCheckResult::SUCCESS;
+ }
+ else if (ret == InputVerificationResult::FAILED)
+ {
+ HELPERS::ShowOKDialogText(CVariant{19264},
+ CVariant{19265}); // "Incorrect PIN", "The entered PIN was incorrect."
+ return ParentalCheckResult::FAILED;
+ }
+ else
+ {
+ return ParentalCheckResult::CANCELED;
+ }
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsParentalControl.h b/xbmc/pvr/guilib/PVRGUIActionsParentalControl.h
new file mode 100644
index 0000000..921f597
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsParentalControl.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+#include "pvr/settings/PVRSettings.h"
+
+#include <memory>
+
+namespace PVR
+{
+enum class ParentalCheckResult
+{
+ CANCELED,
+ FAILED,
+ SUCCESS
+};
+
+class CPVRChannel;
+
+class CPVRGUIActionsParentalControl : public IPVRComponent
+{
+public:
+ CPVRGUIActionsParentalControl();
+ ~CPVRGUIActionsParentalControl() override = default;
+
+ /*!
+ * @brief Check if channel is parental locked. Ask for PIN if necessary.
+ * @param channel The channel to do the check for.
+ * @return the result of the check (success, failed, or canceled by user).
+ */
+ ParentalCheckResult CheckParentalLock(const std::shared_ptr<CPVRChannel>& channel) const;
+
+ /*!
+ * @brief Open Numeric dialog to check for parental PIN.
+ * @return the result of the check (success, failed, or canceled by user).
+ */
+ ParentalCheckResult CheckParentalPIN() const;
+
+private:
+ CPVRGUIActionsParentalControl(const CPVRGUIActionsParentalControl&) = delete;
+ CPVRGUIActionsParentalControl const& operator=(CPVRGUIActionsParentalControl const&) = delete;
+
+ CPVRSettings m_settings;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Parental = CPVRGUIActionsParentalControl;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp
new file mode 100644
index 0000000..66fdb80
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsPlayback.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationEnums.h"
+#include "cores/DataCacheCore.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/PVRStreamProperties.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsParentalControl.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "settings/MediaSettings.h"
+#include "settings/Settings.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+CPVRGUIActionsPlayback::CPVRGUIActionsPlayback()
+ : m_settings({CSettings::SETTING_LOOKANDFEEL_STARTUPACTION,
+ CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES})
+{
+}
+
+std::string CPVRGUIActionsPlayback::GetResumeLabel(const CFileItem& item) const
+{
+ std::string resumeString;
+
+ const std::shared_ptr<CPVRRecording> recording(
+ CPVRItem(CFileItemPtr(new CFileItem(item))).GetRecording());
+ if (recording && !recording->IsDeleted())
+ {
+ int positionInSeconds = lrint(recording->GetResumePoint().timeInSeconds);
+ if (positionInSeconds > 0)
+ resumeString = StringUtils::Format(
+ g_localizeStrings.Get(12022),
+ StringUtils::SecondsToTimeString(positionInSeconds, TIME_FORMAT_HH_MM_SS));
+ }
+ return resumeString;
+}
+
+bool CPVRGUIActionsPlayback::CheckResumeRecording(const CFileItem& item) const
+{
+ bool bPlayIt(true);
+ std::string resumeString(GetResumeLabel(item));
+ if (!resumeString.empty())
+ {
+ CContextButtons choices;
+ choices.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString);
+ choices.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); // Play from beginning
+ int choice = CGUIDialogContextMenu::ShowAndGetChoice(choices);
+ if (choice > 0)
+ const_cast<CFileItem*>(&item)->SetStartOffset(
+ choice == CONTEXT_BUTTON_RESUME_ITEM ? STARTOFFSET_RESUME : 0);
+ else
+ bPlayIt = false; // context menu cancelled
+ }
+ return bPlayIt;
+}
+
+bool CPVRGUIActionsPlayback::ResumePlayRecording(const CFileItem& item, bool bFallbackToPlay) const
+{
+ bool bCanResume = !GetResumeLabel(item).empty();
+ if (bCanResume)
+ {
+ const_cast<CFileItem*>(&item)->SetStartOffset(STARTOFFSET_RESUME);
+ }
+ else
+ {
+ if (bFallbackToPlay)
+ const_cast<CFileItem*>(&item)->SetStartOffset(0);
+ else
+ return false;
+ }
+
+ return PlayRecording(item, false);
+}
+
+void CPVRGUIActionsPlayback::CheckAndSwitchToFullscreen(bool bFullscreen) const
+{
+ CMediaSettings::GetInstance().SetMediaStartWindowed(!bFullscreen);
+
+ if (bFullscreen)
+ {
+ CGUIMessage msg(GUI_MSG_FULLSCREEN, 0,
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+}
+
+void CPVRGUIActionsPlayback::StartPlayback(CFileItem* item,
+ bool bFullscreen,
+ const CPVRStreamProperties* epgProps) const
+{
+ // Obtain dynamic playback url and properties from the respective pvr client
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
+ if (client)
+ {
+ CPVRStreamProperties props;
+
+ if (item->IsPVRChannel())
+ {
+ // If this was an EPG Tag to be played as live then PlayEpgTag() will create a channel
+ // fileitem instead and pass the epg tags props so we use those and skip the client call
+ if (epgProps)
+ props = *epgProps;
+ else
+ client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), props);
+ }
+ else if (item->IsPVRRecording())
+ {
+ client->GetRecordingStreamProperties(item->GetPVRRecordingInfoTag(), props);
+ }
+ else if (item->IsEPG())
+ {
+ if (epgProps) // we already have props from PlayEpgTag()
+ props = *epgProps;
+ else
+ client->GetEpgTagStreamProperties(item->GetEPGInfoTag(), props);
+ }
+
+ if (props.size())
+ {
+ const std::string url = props.GetStreamURL();
+ if (!url.empty())
+ item->SetDynPath(url);
+
+ const std::string mime = props.GetStreamMimeType();
+ if (!mime.empty())
+ {
+ item->SetMimeType(mime);
+ item->SetContentLookup(false);
+ }
+
+ for (const auto& prop : props)
+ item->SetProperty(prop.first, prop.second);
+ }
+ }
+
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item));
+ CheckAndSwitchToFullscreen(bFullscreen);
+}
+
+bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem& item, bool bCheckResume) const
+{
+ const std::shared_ptr<CPVRRecording> recording(CPVRItem(item).GetRecording());
+ if (!recording)
+ return false;
+
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording))
+ {
+ CGUIMessage msg(GUI_MSG_FULLSCREEN, 0,
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ return true;
+ }
+
+ if (!bCheckResume || CheckResumeRecording(item))
+ {
+ CFileItem* itemToPlay = new CFileItem(recording);
+ itemToPlay->SetStartOffset(item.GetStartOffset());
+ StartPlayback(itemToPlay, true);
+ }
+ return true;
+}
+
+bool CPVRGUIActionsPlayback::PlayEpgTag(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVREpgInfoTag> epgTag(CPVRItem(item).GetEpgInfoTag());
+ if (!epgTag)
+ return false;
+
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingEpgTag(epgTag))
+ {
+ CGUIMessage msg(GUI_MSG_FULLSCREEN, 0,
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ return true;
+ }
+
+ // Obtain dynamic playback url and properties from the respective pvr client
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(epgTag->ClientID());
+ if (!client)
+ return false;
+
+ CPVRStreamProperties props;
+ client->GetEpgTagStreamProperties(epgTag, props);
+
+ CFileItem* itemToPlay = nullptr;
+ if (props.EPGPlaybackAsLive())
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(item);
+ if (!groupMember)
+ return false;
+
+ itemToPlay = new CFileItem(groupMember);
+ }
+ else
+ {
+ itemToPlay = new CFileItem(epgTag);
+ }
+
+ StartPlayback(itemToPlay, true, &props);
+ return true;
+}
+
+bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem& item, bool bCheckResume) const
+{
+ if (item.m_bIsFolder)
+ return false;
+
+ std::shared_ptr<CPVRRecording> recording;
+ const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
+ if (channel)
+ {
+ bool bSwitchToFullscreen =
+ CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(channel);
+
+ if (!bSwitchToFullscreen)
+ {
+ recording =
+ CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel->GetEPGNow());
+ bSwitchToFullscreen =
+ recording &&
+ CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording);
+ }
+
+ if (bSwitchToFullscreen)
+ {
+ CGUIMessage msg(GUI_MSG_FULLSCREEN, 0,
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ return true;
+ }
+ }
+
+ ParentalCheckResult result =
+ channel ? CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel)
+ : ParentalCheckResult::FAILED;
+ if (result == ParentalCheckResult::SUCCESS)
+ {
+ // switch to channel or if recording present, ask whether to switch or play recording...
+ if (!recording)
+ recording =
+ CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel->GetEPGNow());
+
+ if (recording)
+ {
+ bool bCancel(false);
+ bool bPlayRecording = CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{19687}, // "Play recording"
+ CVariant{""}, CVariant{12021}, // "Play from beginning"
+ CVariant{recording->m_strTitle}, bCancel, CVariant{19000}, // "Switch to channel"
+ CVariant{19687}, // "Play recording"
+ 0); // no autoclose
+ if (bCancel)
+ return false;
+
+ if (bPlayRecording)
+ return PlayRecording(CFileItem(recording), bCheckResume);
+ }
+
+ bool bFullscreen;
+ switch (m_settings.GetIntValue(CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES))
+ {
+ case 0: // never
+ bFullscreen = false;
+ break;
+ case 1: // TV channels
+ bFullscreen = !channel->IsRadio();
+ break;
+ case 2: // Radio channels
+ bFullscreen = channel->IsRadio();
+ break;
+ case 3: // TV and radio channels
+ default:
+ bFullscreen = true;
+ break;
+ }
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(item);
+ if (!groupMember)
+ return false;
+
+ StartPlayback(new CFileItem(groupMember), bFullscreen);
+ return true;
+ }
+ else if (result == ParentalCheckResult::FAILED)
+ {
+ const std::string channelName =
+ channel ? channel->ChannelName() : g_localizeStrings.Get(19029); // Channel
+ const std::string msg = StringUtils::Format(
+ g_localizeStrings.Get(19035),
+ channelName); // CHANNELNAME could not be played. Check the log for details.
+
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(19166),
+ msg); // PVR information
+ }
+
+ return false;
+}
+
+bool CPVRGUIActionsPlayback::SwitchToChannel(PlaybackType type) const
+{
+ std::shared_ptr<CPVRChannelGroupMember> groupMember;
+ bool bIsRadio(false);
+
+ // check if the desired PlaybackType is already playing,
+ // and if not, try to grab the last played channel of this type
+ switch (type)
+ {
+ case PlaybackTypeRadio:
+ {
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio())
+ return true;
+
+ const std::shared_ptr<CPVRChannelGroup> allGroup =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllRadio();
+ if (allGroup)
+ groupMember = allGroup->GetLastPlayedChannelGroupMember();
+
+ bIsRadio = true;
+ break;
+ }
+ case PlaybackTypeTV:
+ {
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV())
+ return true;
+
+ const std::shared_ptr<CPVRChannelGroup> allGroup =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllTV();
+ if (allGroup)
+ groupMember = allGroup->GetLastPlayedChannelGroupMember();
+
+ break;
+ }
+ default:
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
+ return true;
+
+ groupMember =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetLastPlayedChannelGroupMember();
+ break;
+ }
+
+ // if we have a last played channel, start playback
+ if (groupMember)
+ {
+ return SwitchToChannel(CFileItem(groupMember), true);
+ }
+ else
+ {
+ // if we don't, find the active channel group of the demanded type and play it's first channel
+ const std::shared_ptr<CPVRChannelGroup> channelGroup =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(bIsRadio);
+ if (channelGroup)
+ {
+ // try to start playback of first channel in this group
+ const std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers =
+ channelGroup->GetMembers();
+ if (!groupMembers.empty())
+ {
+ return SwitchToChannel(CFileItem(*groupMembers.begin()), true);
+ }
+ }
+ }
+
+ CLog::LogF(LOGERROR,
+ "Could not determine {} channel to playback. No last played channel found, and "
+ "first channel of active group could also not be determined.",
+ bIsRadio ? "Radio" : "TV");
+
+ CGUIDialogKaiToast::QueueNotification(
+ CGUIDialogKaiToast::Error,
+ g_localizeStrings.Get(19166), // PVR information
+ StringUtils::Format(
+ g_localizeStrings.Get(19035),
+ g_localizeStrings.Get(
+ bIsRadio ? 19021
+ : 19020))); // Radio/TV could not be played. Check the log for details.
+ return false;
+}
+
+bool CPVRGUIActionsPlayback::PlayChannelOnStartup() const
+{
+ int iAction = m_settings.GetIntValue(CSettings::SETTING_LOOKANDFEEL_STARTUPACTION);
+ if (iAction != STARTUP_ACTION_PLAY_TV && iAction != STARTUP_ACTION_PLAY_RADIO)
+ return false;
+
+ bool playRadio = (iAction == STARTUP_ACTION_PLAY_RADIO);
+
+ // get the last played channel or fallback to first channel of all channels group
+ std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetLastPlayedChannelGroupMember(playRadio);
+
+ if (!groupMember)
+ {
+ const std::shared_ptr<CPVRChannelGroup> group =
+ CServiceBroker::GetPVRManager().ChannelGroups()->Get(playRadio)->GetGroupAll();
+ auto channels = group->GetMembers();
+ if (channels.empty())
+ return false;
+
+ groupMember = channels.front();
+ if (!groupMember)
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "PVR is starting playback of channel '{}'",
+ groupMember->Channel()->ChannelName());
+ return SwitchToChannel(CFileItem(groupMember), true);
+}
+
+bool CPVRGUIActionsPlayback::PlayMedia(const CFileItem& item) const
+{
+ std::unique_ptr<CFileItem> pvrItem = std::make_unique<CFileItem>(item);
+ if (URIUtils::IsPVRChannel(item.GetPath()) && !item.HasPVRChannelInfoTag())
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelGroupMemberByPath(
+ item.GetPath());
+ if (groupMember)
+ pvrItem = std::make_unique<CFileItem>(groupMember);
+ }
+ else if (URIUtils::IsPVRRecording(item.GetPath()) && !item.HasPVRRecordingInfoTag())
+ {
+ const std::shared_ptr<CPVRRecording> recording =
+ CServiceBroker::GetPVRManager().Recordings()->GetByPath(item.GetPath());
+ if (recording)
+ {
+ pvrItem = std::make_unique<CFileItem>(recording);
+ pvrItem->SetStartOffset(item.GetStartOffset());
+ }
+ }
+ bool bCheckResume = true;
+ if (item.HasProperty("check_resume"))
+ bCheckResume = item.GetProperty("check_resume").asBoolean();
+
+ if (pvrItem && pvrItem->HasPVRChannelInfoTag())
+ {
+ return SwitchToChannel(*pvrItem, bCheckResume);
+ }
+ else if (pvrItem && pvrItem->HasPVRRecordingInfoTag())
+ {
+ return PlayRecording(*pvrItem, bCheckResume);
+ }
+
+ return false;
+}
+
+void CPVRGUIActionsPlayback::SeekForward()
+{
+ time_t playbackStartTime = CServiceBroker::GetDataCacheCore().GetStartTime();
+ if (playbackStartTime > 0)
+ {
+ const std::shared_ptr<CPVRChannel> playingChannel =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ if (playingChannel)
+ {
+ time_t nextTime = 0;
+ std::shared_ptr<CPVREpgInfoTag> next = playingChannel->GetEPGNext();
+ if (next)
+ {
+ next->StartAsUTC().GetAsTime(nextTime);
+ }
+ else
+ {
+ // if there is no next event, jump to end of currently playing event
+ next = playingChannel->GetEPGNow();
+ if (next)
+ next->EndAsUTC().GetAsTime(nextTime);
+ }
+
+ int64_t seekTime = 0;
+ if (nextTime != 0)
+ {
+ seekTime = (nextTime - playbackStartTime) * 1000;
+ }
+ else
+ {
+ // no epg; jump to end of buffer
+ seekTime = CServiceBroker::GetDataCacheCore().GetMaxTime();
+ }
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_SEEK_TIME, seekTime);
+ }
+ }
+}
+
+void CPVRGUIActionsPlayback::SeekBackward(unsigned int iThreshold)
+{
+ time_t playbackStartTime = CServiceBroker::GetDataCacheCore().GetStartTime();
+ if (playbackStartTime > 0)
+ {
+ const std::shared_ptr<CPVRChannel> playingChannel =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ if (playingChannel)
+ {
+ time_t prevTime = 0;
+ std::shared_ptr<CPVREpgInfoTag> prev = playingChannel->GetEPGNow();
+ if (prev)
+ {
+ prev->StartAsUTC().GetAsTime(prevTime);
+
+ // if playback time of current event is above threshold jump to start of current event
+ int64_t playTime = CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000;
+ if ((playbackStartTime + playTime - prevTime) <= iThreshold)
+ {
+ // jump to start of previous event
+ prevTime = 0;
+ prev = playingChannel->GetEPGPrevious();
+ if (prev)
+ prev->StartAsUTC().GetAsTime(prevTime);
+ }
+ }
+
+ int64_t seekTime = 0;
+ if (prevTime != 0)
+ {
+ seekTime = (prevTime - playbackStartTime) * 1000;
+ }
+ else
+ {
+ // no epg; jump to begin of buffer
+ seekTime = CServiceBroker::GetDataCacheCore().GetMinTime();
+ }
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_SEEK_TIME, seekTime);
+ }
+ }
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.h b/xbmc/pvr/guilib/PVRGUIActionsPlayback.h
new file mode 100644
index 0000000..8b0ab55
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+#include "pvr/settings/PVRSettings.h"
+
+#include <string>
+
+class CFileItem;
+
+namespace PVR
+{
+enum PlaybackType
+{
+ PlaybackTypeAny = 0,
+ PlaybackTypeTV,
+ PlaybackTypeRadio
+};
+
+class CPVRStreamProperties;
+
+class CPVRGUIActionsPlayback : public IPVRComponent
+{
+public:
+ CPVRGUIActionsPlayback();
+ ~CPVRGUIActionsPlayback() override = default;
+
+ /*!
+ * @brief Get a localized resume play label, if the given item can be resumed.
+ * @param item containing a recording or an epg tag.
+ * @return the localized resume play label that can be used for instance as context menu item
+ * label or an empty string if resume is not possible.
+ */
+ std::string GetResumeLabel(const CFileItem& item) const;
+
+ /*!
+ * @brief Resume a previously not completely played recording.
+ * @param item containing a recording or an epg tag.
+ * @param bFallbackToPlay controls whether playback of the recording should be started at the
+ * beginning ig no resume data are available.
+ * @return true on success, false otherwise.
+ */
+ bool ResumePlayRecording(const CFileItem& item, bool bFallbackToPlay) const;
+
+ /*!
+ * @brief Play recording.
+ * @param item containing a recording or an epg tag.
+ * @param bCheckResume controls resume check.
+ * @return true on success, false otherwise.
+ */
+ bool PlayRecording(const CFileItem& item, bool bCheckResume) const;
+
+ /*!
+ * @brief Play EPG tag.
+ * @param item containing an epg tag.
+ * @return true on success, false otherwise.
+ */
+ bool PlayEpgTag(const CFileItem& item) const;
+
+ /*!
+ * @brief Switch channel.
+ * @param item containing a channel or an epg tag.
+ * @param bCheckResume controls resume check in case a recording for the current epg event is
+ * present.
+ * @return true on success, false otherwise.
+ */
+ bool SwitchToChannel(const CFileItem& item, bool bCheckResume) const;
+
+ /*!
+ * @brief Playback the given file item.
+ * @param item containing a channel or a recording.
+ * @return True if the item could be played, false otherwise.
+ */
+ bool PlayMedia(const CFileItem& item) const;
+
+ /*!
+ * @brief Start playback of the last played channel, and if there is none, play first channel in
+ * the current channelgroup.
+ * @param type The type of playback to be started (any, radio, tv). See PlaybackType enum
+ * @return True if playback was started, false otherwise.
+ */
+ bool SwitchToChannel(PlaybackType type) const;
+
+ /*!
+ * @brief Plays the last played channel or the first channel of TV or Radio on startup.
+ * @return True if playback was started, false otherwise.
+ */
+ bool PlayChannelOnStartup() const;
+
+ /*!
+ * @brief Seek to the start of the next epg event in timeshift buffer, relative to the currently
+ * playing event. If there is no next event, seek to the end of the currently playing event (to
+ * the 'live' position).
+ */
+ void SeekForward();
+
+ /*!
+ * @brief Seek to the start of the previous epg event in timeshift buffer, relative to the
+ * currently playing event or if there is no previous event or if playback time is greater than
+ * given threshold, seek to the start of the playing event.
+ * @param iThreshold the value in seconds to trigger seek to start of current event instead of
+ * start of previous event.
+ */
+ void SeekBackward(unsigned int iThreshold);
+
+private:
+ CPVRGUIActionsPlayback(const CPVRGUIActionsPlayback&) = delete;
+ CPVRGUIActionsPlayback const& operator=(CPVRGUIActionsPlayback const&) = delete;
+
+ /*!
+ * @brief Check whether resume play is possible for a given item, display "resume from ..."/"play
+ * from start" context menu in case.
+ * @param item containing a recording or an epg tag.
+ * @return true, to play/resume the item, false otherwise.
+ */
+ bool CheckResumeRecording(const CFileItem& item) const;
+
+ /*!
+ * @brief Check "play minimized" settings value and switch to fullscreen if not set.
+ * @param bFullscreen switch to fullscreen or set windowed playback.
+ */
+ void CheckAndSwitchToFullscreen(bool bFullscreen) const;
+
+ /*!
+ * @brief Start playback of the given item.
+ * @param bFullscreen start playback fullscreen or not.
+ * @param epgProps properties to be used instead of calling to the client if supplied.
+ * @param item containing a channel or a recording.
+ */
+ void StartPlayback(CFileItem* item,
+ bool bFullscreen,
+ const CPVRStreamProperties* epgProps = nullptr) const;
+
+ CPVRSettings m_settings;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Playback = CPVRGUIActionsPlayback;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp b/xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp
new file mode 100644
index 0000000..1a0a99a
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsPowerManagement.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsPowerManagement.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "network/Network.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/Settings.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+CPVRGUIActionsPowerManagement::CPVRGUIActionsPowerManagement()
+ : m_settings({CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME,
+ CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME})
+{
+}
+
+bool CPVRGUIActionsPowerManagement::CanSystemPowerdown(bool bAskUser /*= true*/) const
+{
+ bool bReturn(true);
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ std::shared_ptr<CPVRTimerInfoTag> cause;
+ if (!AllLocalBackendsIdle(cause))
+ {
+ if (bAskUser)
+ {
+ std::string text;
+
+ if (cause)
+ {
+ if (cause->IsRecording())
+ {
+ text = StringUtils::Format(
+ g_localizeStrings.Get(19691), // "PVR is currently recording...."
+ cause->Title(), cause->ChannelName());
+ }
+ else
+ {
+ // Next event is due to a local recording or reminder.
+ const CDateTime now(CDateTime::GetUTCDateTime());
+ const CDateTime start(cause->StartAsUTC());
+ const CDateTimeSpan prestart(0, 0, cause->MarginStart(), 0);
+
+ CDateTimeSpan diff(start - now);
+ diff -= prestart;
+ int mins = diff.GetSecondsTotal() / 60;
+
+ std::string dueStr;
+ if (mins > 1)
+ {
+ // "%d minutes"
+ dueStr = StringUtils::Format(g_localizeStrings.Get(19694), mins);
+ }
+ else
+ {
+ // "about a minute"
+ dueStr = g_localizeStrings.Get(19695);
+ }
+
+ text = StringUtils::Format(
+ cause->IsReminder()
+ ? g_localizeStrings.Get(19690) // "PVR has scheduled a reminder...."
+ : g_localizeStrings.Get(19692), // "PVR will start recording...."
+ cause->Title(), cause->ChannelName(), dueStr);
+ }
+ }
+ else
+ {
+ // Next event is due to automatic daily wakeup of PVR.
+ const CDateTime now(CDateTime::GetUTCDateTime());
+
+ CDateTime dailywakeuptime;
+ dailywakeuptime.SetFromDBTime(
+ m_settings.GetStringValue(CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME));
+ dailywakeuptime = dailywakeuptime.GetAsUTCDateTime();
+
+ const CDateTimeSpan diff(dailywakeuptime - now);
+ int mins = diff.GetSecondsTotal() / 60;
+
+ std::string dueStr;
+ if (mins > 1)
+ {
+ // "%d minutes"
+ dueStr = StringUtils::Format(g_localizeStrings.Get(19694), mins);
+ }
+ else
+ {
+ // "about a minute"
+ dueStr = g_localizeStrings.Get(19695);
+ }
+
+ text = StringUtils::Format(g_localizeStrings.Get(19693), // "Daily wakeup is due in...."
+ dueStr);
+ }
+
+ // Inform user about PVR being busy. Ask if user wants to powerdown anyway.
+ bReturn = HELPERS::ShowYesNoDialogText(CVariant{19685}, // "Confirm shutdown"
+ CVariant{text}, CVariant{222}, // "Shutdown anyway",
+ CVariant{19696}, // "Cancel"
+ 10000) // timeout value before closing
+ == HELPERS::DialogResponse::CHOICE_YES;
+ }
+ else
+ bReturn = false; // do not powerdown (busy, but no user interaction requested).
+ }
+ }
+ return bReturn;
+}
+
+bool CPVRGUIActionsPowerManagement::AllLocalBackendsIdle(
+ std::shared_ptr<CPVRTimerInfoTag>& causingEvent) const
+{
+ // active recording on local backend?
+ const std::vector<std::shared_ptr<CPVRTimerInfoTag>> activeRecordings =
+ CServiceBroker::GetPVRManager().Timers()->GetActiveRecordings();
+ for (const auto& timer : activeRecordings)
+ {
+ if (EventOccursOnLocalBackend(timer))
+ {
+ causingEvent = timer;
+ return false;
+ }
+ }
+
+ // soon recording on local backend?
+ if (IsNextEventWithinBackendIdleTime())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer =
+ CServiceBroker::GetPVRManager().Timers()->GetNextActiveTimer(false);
+ if (!timer)
+ {
+ // Next event is due to automatic daily wakeup of PVR!
+ causingEvent.reset();
+ return false;
+ }
+
+ if (EventOccursOnLocalBackend(timer))
+ {
+ causingEvent = timer;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CPVRGUIActionsPowerManagement::EventOccursOnLocalBackend(
+ const std::shared_ptr<CPVRTimerInfoTag>& event) const
+{
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(CFileItem(event));
+ if (client)
+ {
+ const std::string hostname = client->GetBackendHostname();
+ if (!hostname.empty() && CServiceBroker::GetNetwork().IsLocalHost(hostname))
+ return true;
+ }
+ return false;
+}
+
+bool CPVRGUIActionsPowerManagement::IsNextEventWithinBackendIdleTime() const
+{
+ // timers going off soon?
+ const CDateTime now(CDateTime::GetUTCDateTime());
+ const CDateTimeSpan idle(
+ 0, 0, m_settings.GetIntValue(CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME), 0);
+ const CDateTime next(CServiceBroker::GetPVRManager().Timers()->GetNextEventTime());
+ const CDateTimeSpan delta(next - now);
+
+ return (delta <= idle);
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsPowerManagement.h b/xbmc/pvr/guilib/PVRGUIActionsPowerManagement.h
new file mode 100644
index 0000000..bf99819
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsPowerManagement.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+#include "pvr/settings/PVRSettings.h"
+
+#include <memory>
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVRTimerInfoTag;
+
+class CPVRGUIActionsPowerManagement : public IPVRComponent
+{
+public:
+ CPVRGUIActionsPowerManagement();
+ ~CPVRGUIActionsPowerManagement() override = default;
+
+ /*!
+ * @brief Check whether the system Kodi is running on can be powered down
+ * (shutdown/reboot/suspend/hibernate) without stopping any active recordings and/or without
+ * preventing the start of recordings scheduled for now + pvrpowermanagement.backendidletime.
+ * @param bAskUser True to informs user in case of potential data loss. User can decide to allow
+ * powerdown anyway. False to not to ask user and to not confirm power down.
+ * @return True if system can be safely powered down, false otherwise.
+ */
+ bool CanSystemPowerdown(bool bAskUser = true) const;
+
+private:
+ CPVRGUIActionsPowerManagement(const CPVRGUIActionsPowerManagement&) = delete;
+ CPVRGUIActionsPowerManagement const& operator=(CPVRGUIActionsPowerManagement const&) = delete;
+
+ bool AllLocalBackendsIdle(std::shared_ptr<CPVRTimerInfoTag>& causingEvent) const;
+ bool EventOccursOnLocalBackend(const std::shared_ptr<CPVRTimerInfoTag>& event) const;
+ bool IsNextEventWithinBackendIdleTime() const;
+
+ CPVRSettings m_settings;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using PowerManagement = CPVRGUIActionsPowerManagement;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp
new file mode 100644
index 0000000..df23f38
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsRecordings.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "filesystem/IDirectory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/dialogs/GUIDialogPVRRecordingInfo.h"
+#include "pvr/dialogs/GUIDialogPVRRecordingSettings.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "settings/Settings.h"
+#include "threads/IRunnable.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <memory>
+#include <numeric>
+#include <string>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+namespace
+{
+class AsyncRecordingAction : private IRunnable
+{
+public:
+ bool Execute(const CFileItem& item);
+
+protected:
+ AsyncRecordingAction() = default;
+
+private:
+ // IRunnable implementation
+ void Run() override;
+
+ // the worker function
+ virtual bool DoRun(const std::shared_ptr<CFileItem>& item) = 0;
+
+ std::shared_ptr<CFileItem> m_item;
+ bool m_bSuccess = false;
+};
+
+bool AsyncRecordingAction::Execute(const CFileItem& item)
+{
+ m_item = std::make_shared<CFileItem>(item);
+ CGUIDialogBusy::Wait(this, 100, false);
+ return m_bSuccess;
+}
+
+void AsyncRecordingAction::Run()
+{
+ m_bSuccess = DoRun(m_item);
+
+ if (m_bSuccess)
+ CServiceBroker::GetPVRManager().TriggerRecordingsUpdate();
+}
+
+class AsyncRenameRecording : public AsyncRecordingAction
+{
+public:
+ explicit AsyncRenameRecording(const std::string& strNewName) : m_strNewName(strNewName) {}
+
+private:
+ bool DoRun(const std::shared_ptr<CFileItem>& item) override
+ {
+ if (item->IsUsablePVRRecording())
+ {
+ return item->GetPVRRecordingInfoTag()->Rename(m_strNewName);
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Cannot rename item '{}': no valid recording tag", item->GetPath());
+ return false;
+ }
+ }
+ std::string m_strNewName;
+};
+
+class AsyncDeleteRecording : public AsyncRecordingAction
+{
+public:
+ explicit AsyncDeleteRecording(bool bWatchedOnly = false) : m_bWatchedOnly(bWatchedOnly) {}
+
+private:
+ bool DoRun(const std::shared_ptr<CFileItem>& item) override
+ {
+ CFileItemList items;
+ if (item->m_bIsFolder)
+ {
+ CUtil::GetRecursiveListing(item->GetPath(), items, "", XFILE::DIR_FLAG_NO_FILE_INFO);
+ }
+ else
+ {
+ items.Add(item);
+ }
+
+ return std::accumulate(
+ items.cbegin(), items.cend(), true, [this](bool success, const auto& itemToDelete) {
+ return (itemToDelete->IsPVRRecording() &&
+ (!m_bWatchedOnly || itemToDelete->GetPVRRecordingInfoTag()->GetPlayCount() > 0) &&
+ !itemToDelete->GetPVRRecordingInfoTag()->Delete())
+ ? false
+ : success;
+ });
+ }
+ bool m_bWatchedOnly = false;
+};
+
+class AsyncEmptyRecordingsTrash : public AsyncRecordingAction
+{
+private:
+ bool DoRun(const std::shared_ptr<CFileItem>& item) override
+ {
+ return CServiceBroker::GetPVRManager().Clients()->DeleteAllRecordingsFromTrash() ==
+ PVR_ERROR_NO_ERROR;
+ }
+};
+
+class AsyncUndeleteRecording : public AsyncRecordingAction
+{
+private:
+ bool DoRun(const std::shared_ptr<CFileItem>& item) override
+ {
+ if (item->IsDeletedPVRRecording())
+ {
+ return item->GetPVRRecordingInfoTag()->Undelete();
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Cannot undelete item '{}': no valid recording tag", item->GetPath());
+ return false;
+ }
+ }
+};
+
+class AsyncSetRecordingPlayCount : public AsyncRecordingAction
+{
+private:
+ bool DoRun(const std::shared_ptr<CFileItem>& item) override
+ {
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
+ if (client)
+ {
+ const std::shared_ptr<CPVRRecording> recording = item->GetPVRRecordingInfoTag();
+ return client->SetRecordingPlayCount(*recording, recording->GetLocalPlayCount()) ==
+ PVR_ERROR_NO_ERROR;
+ }
+ return false;
+ }
+};
+
+class AsyncSetRecordingLifetime : public AsyncRecordingAction
+{
+private:
+ bool DoRun(const std::shared_ptr<CFileItem>& item) override
+ {
+ const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
+ if (client)
+ return client->SetRecordingLifetime(*item->GetPVRRecordingInfoTag()) == PVR_ERROR_NO_ERROR;
+ return false;
+ }
+};
+
+} // unnamed namespace
+
+bool CPVRGUIActionsRecordings::ShowRecordingInfo(const CFileItem& item) const
+{
+ if (!item.IsPVRRecording())
+ {
+ CLog::LogF(LOGERROR, "No recording!");
+ return false;
+ }
+
+ CGUIDialogPVRRecordingInfo* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRRecordingInfo>(
+ WINDOW_DIALOG_PVR_RECORDING_INFO);
+ if (!pDlgInfo)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_RECORDING_INFO!");
+ return false;
+ }
+
+ pDlgInfo->SetRecording(item);
+ pDlgInfo->Open();
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::EditRecording(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVRRecording> recording = CPVRItem(item).GetRecording();
+ if (!recording)
+ {
+ CLog::LogF(LOGERROR, "No recording!");
+ return false;
+ }
+
+ std::shared_ptr<CPVRRecording> origRecording(new CPVRRecording);
+ origRecording->Update(*recording,
+ *CServiceBroker::GetPVRManager().GetClient(recording->ClientID()));
+
+ if (!ShowRecordingSettings(recording))
+ return false;
+
+ if (origRecording->m_strTitle != recording->m_strTitle)
+ {
+ if (!AsyncRenameRecording(recording->m_strTitle).Execute(item))
+ CLog::LogF(LOGERROR, "Renaming recording failed!");
+ }
+
+ if (origRecording->GetLocalPlayCount() != recording->GetLocalPlayCount())
+ {
+ if (!AsyncSetRecordingPlayCount().Execute(item))
+ CLog::LogF(LOGERROR, "Setting recording playcount failed!");
+ }
+
+ if (origRecording->LifeTime() != recording->LifeTime())
+ {
+ if (!AsyncSetRecordingLifetime().Execute(item))
+ CLog::LogF(LOGERROR, "Setting recording lifetime failed!");
+ }
+
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::CanEditRecording(const CFileItem& item) const
+{
+ return CGUIDialogPVRRecordingSettings::CanEditRecording(item);
+}
+
+bool CPVRGUIActionsRecordings::DeleteRecording(const CFileItem& item) const
+{
+ if ((!item.IsPVRRecording() && !item.m_bIsFolder) || item.IsParentFolder())
+ return false;
+
+ if (!ConfirmDeleteRecording(item))
+ return false;
+
+ if (!AsyncDeleteRecording().Execute(item))
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19111}); // "Error", "PVR backend error. Check the log for more information about this message."
+ return false;
+ }
+
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::ConfirmDeleteRecording(const CFileItem& item) const
+{
+ return CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{122}, // "Confirm delete"
+ item.m_bIsFolder
+ ? CVariant{19113} // "Delete all recordings in this folder?"
+ : item.GetPVRRecordingInfoTag()->IsDeleted()
+ ? CVariant{19294}
+ // "Remove this deleted recording from trash? This operation cannot be reverted."
+ : CVariant{19112}, // "Delete this recording?"
+ CVariant{""}, CVariant{item.GetLabel()});
+}
+
+bool CPVRGUIActionsRecordings::DeleteWatchedRecordings(const CFileItem& item) const
+{
+ if (!item.m_bIsFolder || item.IsParentFolder())
+ return false;
+
+ if (!ConfirmDeleteWatchedRecordings(item))
+ return false;
+
+ if (!AsyncDeleteRecording(true).Execute(item))
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19111}); // "Error", "PVR backend error. Check the log for more information about this message."
+ return false;
+ }
+
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::ConfirmDeleteWatchedRecordings(const CFileItem& item) const
+{
+ return CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{122}, // "Confirm delete"
+ CVariant{19328}, // "Delete all watched recordings in this folder?"
+ CVariant{""}, CVariant{item.GetLabel()});
+}
+
+bool CPVRGUIActionsRecordings::DeleteAllRecordingsFromTrash() const
+{
+ if (!ConfirmDeleteAllRecordingsFromTrash())
+ return false;
+
+ if (!AsyncEmptyRecordingsTrash().Execute({}))
+ return false;
+
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::ConfirmDeleteAllRecordingsFromTrash() const
+{
+ return CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{19292}, // "Delete all permanently"
+ CVariant{
+ 19293}); // "Remove all deleted recordings from trash? This operation cannot be reverted."
+}
+
+bool CPVRGUIActionsRecordings::UndeleteRecording(const CFileItem& item) const
+{
+ if (!item.IsDeletedPVRRecording())
+ return false;
+
+ if (!AsyncUndeleteRecording().Execute(item))
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19111}); // "Error", "PVR backend error. Check the log for more information about this message."
+ return false;
+ }
+
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::ShowRecordingSettings(
+ const std::shared_ptr<CPVRRecording>& recording) const
+{
+ CGUIDialogPVRRecordingSettings* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRRecordingSettings>(
+ WINDOW_DIALOG_PVR_RECORDING_SETTING);
+ if (!pDlgInfo)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_RECORDING_SETTING!");
+ return false;
+ }
+
+ pDlgInfo->SetRecording(recording);
+ pDlgInfo->Open();
+
+ return pDlgInfo->IsConfirmed();
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h
new file mode 100644
index 0000000..795a2c7
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+
+#include <memory>
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVRRecording;
+
+class CPVRGUIActionsRecordings : public IPVRComponent
+{
+public:
+ CPVRGUIActionsRecordings() = default;
+ ~CPVRGUIActionsRecordings() override = default;
+
+ /*!
+ * @brief Open a dialog with information for a given recording.
+ * @param item containing a recording.
+ * @return true on success, false otherwise.
+ */
+ bool ShowRecordingInfo(const CFileItem& item) const;
+
+ /*!
+ * @brief Open the recording settings dialog to edit a recording.
+ * @param item containing the recording to edit.
+ * @return true on success, false otherwise.
+ */
+ bool EditRecording(const CFileItem& item) const;
+
+ /*!
+ * @brief Check if any recording settings can be edited.
+ * @param item containing the recording to edit.
+ * @return true on success, false otherwise.
+ */
+ bool CanEditRecording(const CFileItem& item) const;
+
+ /*!
+ * @brief Delete a recording, always showing a confirmation dialog.
+ * @param item containing a recording to delete.
+ * @return true, if the recording was deleted successfully, false otherwise.
+ */
+ bool DeleteRecording(const CFileItem& item) const;
+
+ /*!
+ * @brief Delete all watched recordings contained in the given folder, always showing a
+ * confirmation dialog.
+ * @param item containing a recording folder containing the items to delete.
+ * @return true, if the recordings were deleted successfully, false otherwise.
+ */
+ bool DeleteWatchedRecordings(const CFileItem& item) const;
+
+ /*!
+ * @brief Delete all recordings from trash, always showing a confirmation dialog.
+ * @return true, if the recordings were permanently deleted successfully, false otherwise.
+ */
+ bool DeleteAllRecordingsFromTrash() const;
+
+ /*!
+ * @brief Undelete a recording.
+ * @param item containing a recording to undelete.
+ * @return true, if the recording was undeleted successfully, false otherwise.
+ */
+ bool UndeleteRecording(const CFileItem& item) const;
+
+private:
+ CPVRGUIActionsRecordings(const CPVRGUIActionsRecordings&) = delete;
+ CPVRGUIActionsRecordings const& operator=(CPVRGUIActionsRecordings const&) = delete;
+
+ /*!
+ * @brief Open a dialog to confirm to delete a recording.
+ * @param item the recording to delete.
+ * @return true, to proceed with delete, false otherwise.
+ */
+ bool ConfirmDeleteRecording(const CFileItem& item) const;
+
+ /*!
+ * @brief Open a dialog to confirm delete all watched recordings contained in the given folder.
+ * @param item containing a recording folder containing the items to delete.
+ * @return true, to proceed with delete, false otherwise.
+ */
+ bool ConfirmDeleteWatchedRecordings(const CFileItem& item) const;
+
+ /*!
+ * @brief Open a dialog to confirm to permanently remove all deleted recordings.
+ * @return true, to proceed with delete, false otherwise.
+ */
+ bool ConfirmDeleteAllRecordingsFromTrash() const;
+
+ /*!
+ * @brief Open the recording settings dialog.
+ * @param recording containing the recording the settings shall be displayed for.
+ * @return true, if the dialog was ended successfully, false otherwise.
+ */
+ bool ShowRecordingSettings(const std::shared_ptr<CPVRRecording>& recording) const;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Recordings = CPVRGUIActionsRecordings;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp
new file mode 100644
index 0000000..be8e313
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsTimers.cpp
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2016-2022 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 "PVRGUIActionsTimers.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVREventLogJob.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/dialogs/GUIDialogPVRTimerSettings.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsParentalControl.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/Settings.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+CPVRGUIActionsTimers::CPVRGUIActionsTimers()
+ : m_settings({CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME,
+ CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION,
+ CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY,
+ CSettings::SETTING_PVRREMINDERS_AUTORECORD,
+ CSettings::SETTING_PVRREMINDERS_AUTOSWITCH})
+{
+}
+
+bool CPVRGUIActionsTimers::ShowTimerSettings(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ CGUIDialogPVRTimerSettings* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRTimerSettings>(
+ WINDOW_DIALOG_PVR_TIMER_SETTING);
+ if (!pDlgInfo)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_PVR_TIMER_SETTING!");
+ return false;
+ }
+
+ pDlgInfo->SetTimer(timer);
+ pDlgInfo->Open();
+
+ return pDlgInfo->IsConfirmed();
+}
+
+bool CPVRGUIActionsTimers::AddReminder(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (!epgTag)
+ {
+ CLog::LogF(LOGERROR, "No epg tag!");
+ return false;
+ }
+
+ if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag))
+ {
+ HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
+ CVariant{19034}); // "There is already a timer set for this event"
+ return false;
+ }
+
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer =
+ CPVRTimerInfoTag::CreateReminderFromEpg(epgTag);
+ if (!newTimer)
+ {
+ HELPERS::ShowOKDialogText(CVariant{19033}, // "Information"
+ CVariant{19094}); // Timer creation failed. Unsupported timer type.
+ return false;
+ }
+
+ return AddTimer(newTimer);
+}
+
+bool CPVRGUIActionsTimers::AddTimer(bool bRadio) const
+{
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag(bRadio));
+ if (ShowTimerSettings(newTimer))
+ {
+ return AddTimer(newTimer);
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item, bool bShowTimerSettings) const
+{
+ return AddTimer(item, false, bShowTimerSettings, false);
+}
+
+bool CPVRGUIActionsTimers::AddTimerRule(const CFileItem& item,
+ bool bShowTimerSettings,
+ bool bFallbackToOneShotTimer) const
+{
+ return AddTimer(item, true, bShowTimerSettings, bFallbackToOneShotTimer);
+}
+
+bool CPVRGUIActionsTimers::AddTimer(const CFileItem& item,
+ bool bCreateRule,
+ bool bShowTimerSettings,
+ bool bFallbackToOneShotTimer) const
+{
+ const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel());
+ if (!channel)
+ {
+ CLog::LogF(LOGERROR, "No channel!");
+ return false;
+ }
+
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) !=
+ ParentalCheckResult::SUCCESS)
+ return false;
+
+ std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (epgTag)
+ {
+ if (epgTag->IsGapTag())
+ epgTag.reset(); // for gap tags, we can only create instant timers
+ }
+ else if (bCreateRule)
+ {
+ CLog::LogF(LOGERROR, "No epg tag!");
+ return false;
+ }
+
+ std::shared_ptr<CPVRTimerInfoTag> timer(
+ bCreateRule || !epgTag ? nullptr
+ : CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag));
+ std::shared_ptr<CPVRTimerInfoTag> rule(
+ bCreateRule ? CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer) : nullptr);
+ if (timer || rule)
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033},
+ CVariant{19034}); // "Information", "There is already a timer set for this event"
+ return false;
+ }
+
+ std::shared_ptr<CPVRTimerInfoTag> newTimer(
+ epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, bCreateRule)
+ : CPVRTimerInfoTag::CreateInstantTimerTag(channel));
+ if (!newTimer)
+ {
+ if (bCreateRule && bFallbackToOneShotTimer)
+ newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
+
+ if (!newTimer)
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033}, // "Information"
+ bCreateRule ? CVariant{19095} // Timer rule creation failed. Unsupported timer type.
+ : CVariant{19094}); // Timer creation failed. Unsupported timer type.
+ return false;
+ }
+ }
+
+ if (bShowTimerSettings)
+ {
+ if (!ShowTimerSettings(newTimer))
+ return false;
+ }
+
+ return AddTimer(newTimer);
+}
+
+bool CPVRGUIActionsTimers::AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& item) const
+{
+ if (!item->Channel() && !item->GetTimerType()->IsEpgBasedTimerRule())
+ {
+ CLog::LogF(LOGERROR, "No channel given");
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
+ return false;
+ }
+
+ if (!item->IsTimerRule() && item->GetEpgInfoTag() && !item->GetEpgInfoTag()->IsRecordable())
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{19033},
+ CVariant{19189}); // "Information", "The PVR backend does not allow to record this event."
+ return false;
+ }
+
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(
+ item->Channel()) != ParentalCheckResult::SUCCESS)
+ return false;
+
+ if (!CServiceBroker::GetPVRManager().Timers()->AddTimer(item))
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19109}); // "Error", "Could not save the timer. Check the log for more information about this message."
+ return false;
+ }
+
+ return true;
+}
+
+namespace
+{
+enum PVRRECORD_INSTANTRECORDACTION
+{
+ NONE = -1,
+ RECORD_CURRENT_SHOW = 0,
+ RECORD_INSTANTRECORDTIME = 1,
+ ASK = 2,
+ RECORD_30_MINUTES = 3,
+ RECORD_60_MINUTES = 4,
+ RECORD_120_MINUTES = 5,
+ RECORD_NEXT_SHOW = 6
+};
+
+class InstantRecordingActionSelector
+{
+public:
+ explicit InstantRecordingActionSelector(int iInstantRecordTime);
+ virtual ~InstantRecordingActionSelector() = default;
+
+ void AddAction(PVRRECORD_INSTANTRECORDACTION eAction, const std::string& title);
+ void PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction);
+ PVRRECORD_INSTANTRECORDACTION Select();
+
+private:
+ int m_iInstantRecordTime;
+ CGUIDialogSelect* m_pDlgSelect; // not owner!
+ std::map<PVRRECORD_INSTANTRECORDACTION, int> m_actions;
+};
+
+InstantRecordingActionSelector::InstantRecordingActionSelector(int iInstantRecordTime)
+ : m_iInstantRecordTime(iInstantRecordTime),
+ m_pDlgSelect(CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
+ WINDOW_DIALOG_SELECT))
+{
+ if (m_pDlgSelect)
+ {
+ m_pDlgSelect->Reset();
+ m_pDlgSelect->SetMultiSelection(false);
+ m_pDlgSelect->SetHeading(CVariant{19086}); // Instant recording action
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Unable to obtain WINDOW_DIALOG_SELECT instance");
+ }
+}
+
+void InstantRecordingActionSelector::AddAction(PVRRECORD_INSTANTRECORDACTION eAction,
+ const std::string& title)
+{
+ if (m_actions.find(eAction) == m_actions.end())
+ {
+ switch (eAction)
+ {
+ case RECORD_INSTANTRECORDTIME:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090),
+ m_iInstantRecordTime)); // Record next <default duration> minutes
+ break;
+ case RECORD_30_MINUTES:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090), 30)); // Record next 30 minutes
+ break;
+ case RECORD_60_MINUTES:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090), 60)); // Record next 60 minutes
+ break;
+ case RECORD_120_MINUTES:
+ m_pDlgSelect->Add(
+ StringUtils::Format(g_localizeStrings.Get(19090), 120)); // Record next 120 minutes
+ break;
+ case RECORD_CURRENT_SHOW:
+ m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19091),
+ title)); // Record current show (<title>)
+ break;
+ case RECORD_NEXT_SHOW:
+ m_pDlgSelect->Add(StringUtils::Format(g_localizeStrings.Get(19092),
+ title)); // Record next show (<title>)
+ break;
+ case NONE:
+ case ASK:
+ default:
+ return;
+ }
+
+ m_actions.insert(std::make_pair(eAction, static_cast<int>(m_actions.size())));
+ }
+}
+
+void InstantRecordingActionSelector::PreSelectAction(PVRRECORD_INSTANTRECORDACTION eAction)
+{
+ const auto& it = m_actions.find(eAction);
+ if (it != m_actions.end())
+ m_pDlgSelect->SetSelected(it->second);
+}
+
+PVRRECORD_INSTANTRECORDACTION InstantRecordingActionSelector::Select()
+{
+ PVRRECORD_INSTANTRECORDACTION eAction = NONE;
+
+ m_pDlgSelect->Open();
+
+ if (m_pDlgSelect->IsConfirmed())
+ {
+ int iSelection = m_pDlgSelect->GetSelectedItem();
+ const auto it =
+ std::find_if(m_actions.cbegin(), m_actions.cend(),
+ [iSelection](const auto& action) { return action.second == iSelection; });
+
+ if (it != m_actions.cend())
+ eAction = (*it).first;
+ }
+
+ return eAction;
+}
+
+} // unnamed namespace
+
+bool CPVRGUIActionsTimers::ToggleRecordingOnPlayingChannel()
+{
+ const std::shared_ptr<CPVRChannel> channel =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ if (channel && channel->CanRecord())
+ return SetRecordingOnChannel(
+ channel, !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel));
+
+ return false;
+}
+
+bool CPVRGUIActionsTimers::SetRecordingOnChannel(const std::shared_ptr<CPVRChannel>& channel,
+ bool bOnOff)
+{
+ bool bReturn = false;
+
+ if (!channel)
+ return bReturn;
+
+ if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) !=
+ ParentalCheckResult::SUCCESS)
+ return bReturn;
+
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(channel->ClientID());
+ if (client && client->GetClientCapabilities().SupportsTimers())
+ {
+ /* timers are supported on this channel */
+ if (bOnOff && !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
+ {
+ std::shared_ptr<CPVREpgInfoTag> epgTag;
+ int iDuration = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
+
+ int iAction = m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION);
+ switch (iAction)
+ {
+ case RECORD_CURRENT_SHOW:
+ epgTag = channel->GetEPGNow();
+ break;
+
+ case RECORD_INSTANTRECORDTIME:
+ epgTag.reset();
+ break;
+
+ case ASK:
+ {
+ PVRRECORD_INSTANTRECORDACTION ePreselect = RECORD_INSTANTRECORDTIME;
+ const int iDurationDefault =
+ m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME);
+ InstantRecordingActionSelector selector(iDurationDefault);
+ std::shared_ptr<CPVREpgInfoTag> epgTagNext;
+
+ // fixed length recordings
+ selector.AddAction(RECORD_30_MINUTES, "");
+ selector.AddAction(RECORD_60_MINUTES, "");
+ selector.AddAction(RECORD_120_MINUTES, "");
+
+ if (iDurationDefault != 30 && iDurationDefault != 60 && iDurationDefault != 120)
+ selector.AddAction(RECORD_INSTANTRECORDTIME, "");
+
+ // epg-based recordings
+ epgTag = channel->GetEPGNow();
+ if (epgTag)
+ {
+ bool bLocked = CServiceBroker::GetPVRManager().IsParentalLocked(epgTag);
+
+ // "now"
+ const std::string currentTitle =
+ bLocked ? g_localizeStrings.Get(19266) /* Parental locked */ : epgTag->Title();
+ selector.AddAction(RECORD_CURRENT_SHOW, currentTitle);
+ ePreselect = RECORD_CURRENT_SHOW;
+
+ // "next"
+ epgTagNext = channel->GetEPGNext();
+ if (epgTagNext)
+ {
+ const std::string nextTitle = bLocked
+ ? g_localizeStrings.Get(19266) /* Parental locked */
+ : epgTagNext->Title();
+ selector.AddAction(RECORD_NEXT_SHOW, nextTitle);
+
+ // be smart. if current show is almost over, preselect next show.
+ if (epgTag->ProgressPercentage() > 90.0f)
+ ePreselect = RECORD_NEXT_SHOW;
+ }
+ }
+
+ if (ePreselect == RECORD_INSTANTRECORDTIME)
+ {
+ if (iDurationDefault == 30)
+ ePreselect = RECORD_30_MINUTES;
+ else if (iDurationDefault == 60)
+ ePreselect = RECORD_60_MINUTES;
+ else if (iDurationDefault == 120)
+ ePreselect = RECORD_120_MINUTES;
+ }
+
+ selector.PreSelectAction(ePreselect);
+
+ PVRRECORD_INSTANTRECORDACTION eSelected = selector.Select();
+ switch (eSelected)
+ {
+ case NONE:
+ return false; // dialog canceled
+
+ case RECORD_30_MINUTES:
+ iDuration = 30;
+ epgTag.reset();
+ break;
+
+ case RECORD_60_MINUTES:
+ iDuration = 60;
+ epgTag.reset();
+ break;
+
+ case RECORD_120_MINUTES:
+ iDuration = 120;
+ epgTag.reset();
+ break;
+
+ case RECORD_INSTANTRECORDTIME:
+ iDuration = iDurationDefault;
+ epgTag.reset();
+ break;
+
+ case RECORD_CURRENT_SHOW:
+ break;
+
+ case RECORD_NEXT_SHOW:
+ epgTag = epgTagNext;
+ break;
+
+ default:
+ CLog::LogF(LOGERROR,
+ "Unknown instant record action selection ({}), defaulting to fixed "
+ "length recording.",
+ static_cast<int>(eSelected));
+ epgTag.reset();
+ break;
+ }
+ break;
+ }
+
+ default:
+ CLog::LogF(LOGERROR,
+ "Unknown instant record action setting value ({}), defaulting to fixed "
+ "length recording.",
+ iAction);
+ break;
+ }
+
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer(
+ epgTag ? CPVRTimerInfoTag::CreateFromEpg(epgTag, false)
+ : CPVRTimerInfoTag::CreateInstantTimerTag(channel, iDuration));
+
+ if (newTimer)
+ bReturn = CServiceBroker::GetPVRManager().Timers()->AddTimer(newTimer);
+
+ if (!bReturn)
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19164}); // "Error", "Could not start recording. Check the log for more information about this message."
+ }
+ else if (!bOnOff && CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel))
+ {
+ /* delete active timers */
+ bReturn =
+ CServiceBroker::GetPVRManager().Timers()->DeleteTimersOnChannel(channel, true, true);
+
+ if (!bReturn)
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
+ }
+ }
+
+ return bReturn;
+}
+
+bool CPVRGUIActionsTimers::ToggleTimer(const CFileItem& item) const
+{
+ if (!item.HasEPGInfoTag())
+ return false;
+
+ const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
+ if (timer)
+ {
+ if (timer->IsRecording())
+ return StopRecording(item);
+ else
+ return DeleteTimer(item);
+ }
+ else
+ return AddTimer(item, false);
+}
+
+bool CPVRGUIActionsTimers::ToggleTimerState(const CFileItem& item) const
+{
+ if (!item.HasPVRTimerInfoTag())
+ return false;
+
+ const std::shared_ptr<CPVRTimerInfoTag> timer = item.GetPVRTimerInfoTag();
+ if (timer->IsDisabled())
+ timer->SetState(PVR_TIMER_STATE_SCHEDULED);
+ else
+ timer->SetState(PVR_TIMER_STATE_DISABLED);
+
+ if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(timer))
+ return true;
+
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
+ return false;
+}
+
+bool CPVRGUIActionsTimers::EditTimer(const CFileItem& item) const
+{
+ const std::shared_ptr<CPVRTimerInfoTag> timer(CPVRItem(item).GetTimerInfoTag());
+ if (!timer)
+ {
+ CLog::LogF(LOGERROR, "No timer!");
+ return false;
+ }
+
+ // clone the timer.
+ const std::shared_ptr<CPVRTimerInfoTag> newTimer(new CPVRTimerInfoTag);
+ newTimer->UpdateEntry(timer);
+
+ if (ShowTimerSettings(newTimer) &&
+ (!timer->GetTimerType()->IsReadOnly() || timer->GetTimerType()->SupportsEnableDisable()))
+ {
+ if (newTimer->GetTimerType() == timer->GetTimerType())
+ {
+ if (CServiceBroker::GetPVRManager().Timers()->UpdateTimer(newTimer))
+ return true;
+
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19263}); // "Error", "Could not update the timer. Check the log for more information about this message."
+ return false;
+ }
+ else
+ {
+ // timer type changed. delete the original timer, then create the new timer. this order is
+ // important. for instance, the new timer might be a rule which schedules the original timer.
+ // deleting the original timer after creating the rule would do literally this and we would
+ // end up with one timer missing wrt to the rule defined by the new timer.
+ if (DeleteTimer(timer, timer->IsRecording(), false))
+ {
+ if (AddTimer(newTimer))
+ return true;
+
+ // rollback.
+ return AddTimer(timer);
+ }
+ }
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::EditTimerRule(const CFileItem& item) const
+{
+ const std::shared_ptr<CFileItem> parentTimer = GetTimerRule(item);
+ if (parentTimer)
+ return EditTimer(*parentTimer);
+
+ return false;
+}
+
+std::shared_ptr<CFileItem> CPVRGUIActionsTimers::GetTimerRule(const CFileItem& item) const
+{
+ std::shared_ptr<CPVRTimerInfoTag> timer;
+ if (item.HasEPGInfoTag())
+ timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item.GetEPGInfoTag());
+ else if (item.HasPVRTimerInfoTag())
+ timer = item.GetPVRTimerInfoTag();
+
+ if (timer)
+ {
+ timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
+ if (timer)
+ return std::make_shared<CFileItem>(timer);
+ }
+ return {};
+}
+
+bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item) const
+{
+ return DeleteTimer(item, false, false);
+}
+
+bool CPVRGUIActionsTimers::DeleteTimerRule(const CFileItem& item) const
+{
+ return DeleteTimer(item, false, true);
+}
+
+bool CPVRGUIActionsTimers::DeleteTimer(const CFileItem& item,
+ bool bIsRecording,
+ bool bDeleteRule) const
+{
+ std::shared_ptr<CPVRTimerInfoTag> timer;
+ const std::shared_ptr<CPVRRecording> recording(CPVRItem(item).GetRecording());
+ if (recording)
+ timer = recording->GetRecordingTimer();
+
+ if (!timer)
+ timer = CPVRItem(item).GetTimerInfoTag();
+
+ if (!timer)
+ {
+ CLog::LogF(LOGERROR, "No timer!");
+ return false;
+ }
+
+ if (bDeleteRule && !timer->IsTimerRule())
+ timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer);
+
+ if (!timer)
+ {
+ CLog::LogF(LOGERROR, "No timer rule!");
+ return false;
+ }
+
+ if (bIsRecording)
+ {
+ if (ConfirmStopRecording(timer))
+ {
+ if (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, true, false) ==
+ TimerOperationResult::OK)
+ return true;
+
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19170}); // "Error", "Could not stop recording. Check the log for more information about this message."
+ return false;
+ }
+ }
+ else if (!timer->GetTimerType()->AllowsDelete())
+ {
+ return false;
+ }
+ else
+ {
+ bool bAlsoDeleteRule(false);
+ if (ConfirmDeleteTimer(timer, bAlsoDeleteRule))
+ return DeleteTimer(timer, false, bAlsoDeleteRule);
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ bool bIsRecording,
+ bool bDeleteRule) const
+{
+ TimerOperationResult result =
+ CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, bIsRecording, bDeleteRule);
+ switch (result)
+ {
+ case TimerOperationResult::RECORDING:
+ {
+ // recording running. ask the user if it should be deleted anyway
+ if (HELPERS::ShowYesNoDialogText(
+ CVariant{122}, // "Confirm delete"
+ CVariant{
+ 19122}) // "This timer is still recording. Are you sure you want to delete this timer?"
+ != HELPERS::DialogResponse::CHOICE_YES)
+ return false;
+
+ return DeleteTimer(timer, true, bDeleteRule);
+ }
+ case TimerOperationResult::OK:
+ {
+ return true;
+ }
+ case TimerOperationResult::FAILED:
+ {
+ HELPERS::ShowOKDialogText(
+ CVariant{257},
+ CVariant{
+ 19110}); // "Error", "Could not delete the timer. Check the log for more information about this message."
+ return false;
+ }
+ default:
+ {
+ CLog::LogF(LOGERROR, "Unhandled TimerOperationResult ({})!", static_cast<int>(result));
+ break;
+ }
+ }
+ return false;
+}
+
+bool CPVRGUIActionsTimers::ConfirmDeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ bool& bDeleteRule) const
+{
+ bool bConfirmed(false);
+ const std::shared_ptr<CPVRTimerInfoTag> parentTimer(
+ CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer));
+
+ if (parentTimer && parentTimer->GetTimerType()->AllowsDelete())
+ {
+ // timer was scheduled by a deletable timer rule. prompt user for confirmation for deleting the timer rule, including scheduled timers.
+ bool bCancel(false);
+ bDeleteRule = CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{122}, // "Confirm delete"
+ CVariant{
+ 840}, // "Do you want to delete only this timer or also the timer rule that has scheduled it?"
+ CVariant{""}, CVariant{timer->Title()}, bCancel, CVariant{841}, // "Only this"
+ CVariant{593}, // "All"
+ 0); // no autoclose
+ bConfirmed = !bCancel;
+ }
+ else
+ {
+ bDeleteRule = false;
+
+ // prompt user for confirmation for deleting the timer
+ bConfirmed = CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{122}, // "Confirm delete"
+ timer->IsTimerRule()
+ ? CVariant{845}
+ // "Are you sure you want to delete this timer rule and all timers it has scheduled?"
+ : CVariant{846}, // "Are you sure you want to delete this timer?"
+ CVariant{""}, CVariant{timer->Title()});
+ }
+
+ return bConfirmed;
+}
+
+bool CPVRGUIActionsTimers::StopRecording(const CFileItem& item) const
+{
+ if (!DeleteTimer(item, true, false))
+ return false;
+
+ CServiceBroker::GetPVRManager().TriggerRecordingsUpdate();
+ return true;
+}
+
+bool CPVRGUIActionsTimers::ConfirmStopRecording(
+ const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ return CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{847}, // "Confirm stop recording"
+ CVariant{848}, // "Are you sure you want to stop this recording?"
+ CVariant{""}, CVariant{timer->Title()});
+}
+
+namespace
+{
+std::string GetAnnouncerText(const std::shared_ptr<CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
+{
+ std::string text;
+ if (timer->IsEpgBased())
+ {
+ text = StringUtils::Format(g_localizeStrings.Get(idEpg),
+ timer->Title(), // tv show title
+ timer->ChannelName(),
+ timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
+ }
+ else
+ {
+ text = StringUtils::Format(g_localizeStrings.Get(idNoEpg), timer->ChannelName(),
+ timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false));
+ }
+ return text;
+}
+
+void AddEventLogEntry(const std::shared_ptr<CPVRTimerInfoTag>& timer, int idEpg, int idNoEpg)
+{
+ std::string name;
+ std::string icon;
+
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(timer->GetTimerType()->GetClientId());
+ if (client)
+ {
+ name = client->GetFriendlyName();
+ icon = client->Icon();
+ }
+ else
+ {
+ name = g_sysinfo.GetAppName();
+ icon = "special://xbmc/media/icon256x256.png";
+ }
+
+ CPVREventLogJob* job = new CPVREventLogJob;
+ job->AddEvent(false, // do not display a toast, only log event
+ EventLevel::Information, // info, no error
+ name, GetAnnouncerText(timer, idEpg, idNoEpg), icon);
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+}
+} // unnamed namespace
+
+void CPVRGUIActionsTimers::AnnounceReminder(const std::shared_ptr<CPVRTimerInfoTag>& timer) const
+{
+ if (!timer->IsReminder())
+ {
+ CLog::LogF(LOGERROR, "No reminder timer!");
+ return;
+ }
+
+ if (timer->EndAsUTC() < CDateTime::GetUTCDateTime())
+ {
+ // expired. timer end is in the past. write event log entry.
+ AddEventLogEntry(timer, 19305, 19306); // Deleted missed PVR reminder ...
+ return;
+ }
+
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(timer->Channel()))
+ {
+ // no need for an announcement. channel in question is already playing.
+ return;
+ }
+
+ // show the reminder dialog
+ CGUIDialogProgress* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
+ WINDOW_DIALOG_PROGRESS);
+ if (!dialog)
+ return;
+
+ dialog->Reset();
+
+ dialog->SetHeading(CVariant{19312}); // "PVR reminder"
+ dialog->ShowChoice(0, CVariant{19165}); // "Switch"
+
+ std::string text = GetAnnouncerText(timer, 19307, 19308); // Reminder for ...
+
+ bool bCanRecord = false;
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().GetClient(timer->ClientID());
+ if (client && client->GetClientCapabilities().SupportsTimers())
+ {
+ bCanRecord = true;
+ dialog->ShowChoice(1, CVariant{264}); // "Record"
+ dialog->ShowChoice(2, CVariant{222}); // "Cancel"
+
+ if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD))
+ text += "\n\n" + g_localizeStrings.Get(
+ 19309); // (Auto-close of this reminder will schedule a recording...)
+ else if (m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH))
+ text += "\n\n" + g_localizeStrings.Get(
+ 19331); // (Auto-close of this reminder will switch to channel...)
+ }
+ else
+ {
+ dialog->ShowChoice(1, CVariant{222}); // "Cancel"
+ }
+
+ dialog->SetText(text);
+ dialog->SetPercentage(100);
+
+ dialog->Open();
+
+ int result = CGUIDialogProgress::CHOICE_NONE;
+
+ static constexpr int PROGRESS_TIMESLICE_MILLISECS = 50;
+
+ const int iWait = m_settings.GetIntValue(CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY) * 1000;
+ int iRemaining = iWait;
+ while (iRemaining > 0)
+ {
+ result = dialog->GetChoice();
+ if (result != CGUIDialogProgress::CHOICE_NONE)
+ break;
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(PROGRESS_TIMESLICE_MILLISECS));
+
+ iRemaining -= PROGRESS_TIMESLICE_MILLISECS;
+ dialog->SetPercentage(iRemaining * 100 / iWait);
+ dialog->Progress();
+ }
+
+ dialog->Close();
+
+ bool bAutoClosed = (iRemaining <= 0);
+ bool bSwitch = (result == 0);
+ bool bRecord = (result == 1);
+
+ if (bAutoClosed)
+ {
+ bRecord = (bCanRecord && m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTORECORD));
+ bSwitch = m_settings.GetBoolValue(CSettings::SETTING_PVRREMINDERS_AUTOSWITCH);
+ }
+
+ if (bRecord)
+ {
+ std::shared_ptr<CPVRTimerInfoTag> newTimer;
+
+ std::shared_ptr<CPVREpgInfoTag> epgTag = timer->GetEpgInfoTag();
+ if (epgTag)
+ {
+ newTimer = CPVRTimerInfoTag::CreateFromEpg(epgTag, false);
+ if (newTimer)
+ {
+ // an epgtag can only have max one timer - we need to clear the reminder to be able to
+ // attach the recording timer
+ DeleteTimer(timer, false, false);
+ }
+ }
+ else
+ {
+ int iDuration = (timer->EndAsUTC() - timer->StartAsUTC()).GetSecondsTotal() / 60;
+ newTimer = CPVRTimerInfoTag::CreateTimerTag(timer->Channel(), timer->StartAsUTC(), iDuration);
+ }
+
+ if (newTimer)
+ {
+ // schedule recording
+ AddTimer(CFileItem(newTimer), false);
+ }
+
+ if (bAutoClosed)
+ {
+ AddEventLogEntry(timer, 19310,
+ 19311); // Scheduled recording for auto-closed PVR reminder ...
+ }
+ }
+
+ if (bSwitch)
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(
+ timer->Channel());
+ if (groupMember)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ CFileItem(groupMember), false);
+
+ if (bAutoClosed)
+ {
+ AddEventLogEntry(timer, 19332,
+ 19333); // Switched channel for auto-closed PVR reminder ...
+ }
+ }
+ }
+}
+
+void CPVRGUIActionsTimers::AnnounceReminders() const
+{
+ // Prevent multiple yesno dialogs, all on same call stack, due to gui message processing while dialog is open.
+ if (m_bReminderAnnouncementRunning)
+ return;
+
+ m_bReminderAnnouncementRunning = true;
+ std::shared_ptr<CPVRTimerInfoTag> timer =
+ CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
+ while (timer)
+ {
+ AnnounceReminder(timer);
+ timer = CServiceBroker::GetPVRManager().Timers()->GetNextReminderToAnnnounce();
+ }
+ m_bReminderAnnouncementRunning = false;
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsTimers.h b/xbmc/pvr/guilib/PVRGUIActionsTimers.h
new file mode 100644
index 0000000..f6ae8fe
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsTimers.h
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2016-2022 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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+#include "pvr/settings/PVRSettings.h"
+
+#include <memory>
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVRChannel;
+class CPVRTimerInfoTag;
+
+class CPVRGUIActionsTimers : public IPVRComponent
+{
+public:
+ CPVRGUIActionsTimers();
+ ~CPVRGUIActionsTimers() override = default;
+
+ /*!
+ * @brief Open the timer settings dialog to create a new tv or radio timer.
+ * @param bRadio indicates whether a radio or tv timer shall be created.
+ * @return true on success, false otherwise.
+ */
+ bool AddTimer(bool bRadio) const;
+
+ /*!
+ * @brief Create a new timer, either interactive or non-interactive.
+ * @param item containing epg data to create a timer for. item must be an epg tag or a channel.
+ * @param bShowTimerSettings is used to control whether a settings dialog will be opened prior
+ * creating the timer.
+ * @return true, if the timer was created successfully, false otherwise.
+ */
+ bool AddTimer(const CFileItem& item, bool bShowTimerSettings) const;
+
+ /*!
+ * @brief Add a timer to the client. Doesn't add the timer to the container. The backend will
+ * do this.
+ * @return True if it was sent correctly, false if not.
+ */
+ bool AddTimer(const std::shared_ptr<CPVRTimerInfoTag>& item) const;
+
+ /*!
+ * @brief Create a new timer rule, either interactive or non-interactive.
+ * @param item containing epg data to create a timer rule for. item must be an epg tag or a
+ * channel.
+ * @param bShowTimerSettings is used to control whether a settings dialog will be opened prior
+ * creating the timer rule.
+ * @param bFallbackToOneShotTimer if no timer rule can be created, try to create a one-shot
+ * timer instead.
+ * @return true, if the timer rule was created successfully, false otherwise.
+ */
+ bool AddTimerRule(const CFileItem& item,
+ bool bShowTimerSettings,
+ bool bFallbackToOneShotTimer) const;
+
+ /*!
+ * @brief Creates or deletes a timer for the given epg tag.
+ * @param item containing an epg tag.
+ * @return true on success, false otherwise.
+ */
+ bool ToggleTimer(const CFileItem& item) const;
+
+ /*!
+ * @brief Toggles a given timer's enabled/disabled state.
+ * @param item containing a timer.
+ * @return true on success, false otherwise.
+ */
+ bool ToggleTimerState(const CFileItem& item) const;
+
+ /*!
+ * @brief Open the timer settings dialog to edit an existing timer.
+ * @param item containing an epg tag or a timer.
+ * @return true on success, false otherwise.
+ */
+ bool EditTimer(const CFileItem& item) const;
+
+ /*!
+ * @brief Open the timer settings dialog to edit an existing timer rule.
+ * @param item containing an epg tag or a timer.
+ * @return true on success, false otherwise.
+ */
+ bool EditTimerRule(const CFileItem& item) const;
+
+ /*!
+ * @brief Get the timer rule for a given timer
+ * @param item containing an item to query the timer rule for. item must be a timer or an epg tag.
+ * @return The timer rule item, or nullptr if none was found.
+ */
+ std::shared_ptr<CFileItem> GetTimerRule(const CFileItem& item) const;
+
+ /*!
+ * @brief Delete a timer, always showing a confirmation dialog.
+ * @param item containing a timer to delete. item must be a timer, an epg tag or a channel.
+ * @return true, if the timer was deleted successfully, false otherwise.
+ */
+ bool DeleteTimer(const CFileItem& item) const;
+
+ /*!
+ * @brief Delete a timer rule, always showing a confirmation dialog.
+ * @param item containing a timer rule to delete. item must be a timer, an epg tag or a channel.
+ * @return true, if the timer rule was deleted successfully, false otherwise.
+ */
+ bool DeleteTimerRule(const CFileItem& item) const;
+
+ /*!
+ * @brief Toggle recording on the currently playing channel, if any.
+ * @return True if the recording was started or stopped successfully, false otherwise.
+ */
+ bool ToggleRecordingOnPlayingChannel();
+
+ /*!
+ * @brief Start or stop recording on a given channel.
+ * @param channel the channel to start/stop recording.
+ * @param bOnOff True to start recording, false to stop.
+ * @return True if the recording was started or stopped successfully, false otherwise.
+ */
+ bool SetRecordingOnChannel(const std::shared_ptr<CPVRChannel>& channel, bool bOnOff);
+
+ /*!
+ * @brief Stop a currently active recording, always showing a confirmation dialog.
+ * @param item containing a recording to stop. item must be a timer, an epg tag or a channel.
+ * @return true, if the recording was stopped successfully, false otherwise.
+ */
+ bool StopRecording(const CFileItem& item) const;
+
+ /*!
+ * @brief Create a new reminder timer, non-interactive.
+ * @param item containing epg data to create a reminder timer for. item must be an epg tag.
+ * @return true, if the timer was created successfully, false otherwise.
+ */
+ bool AddReminder(const CFileItem& item) const;
+
+ /*!
+ * @brief Announce due reminders, if any.
+ */
+ void AnnounceReminders() const;
+
+private:
+ CPVRGUIActionsTimers(const CPVRGUIActionsTimers&) = delete;
+ CPVRGUIActionsTimers const& operator=(CPVRGUIActionsTimers const&) = delete;
+
+ /*!
+ * @brief Open the timer settings dialog.
+ * @param timer containing the timer the settings shall be displayed for.
+ * @return true, if the dialog was ended successfully, false otherwise.
+ */
+ bool ShowTimerSettings(const std::shared_ptr<CPVRTimerInfoTag>& timer) const;
+
+ /*!
+ * @brief Add a timer or timer rule, either interactive or non-interactive.
+ * @param item containing epg data to create a timer or timer rule for. item must be an epg tag
+ * or a channel.
+ * @param bCreateteRule denotes whether to create a one-shot timer or a timer rule.
+ * @param bShowTimerSettings is used to control whether a settings dialog will be opened prior
+ * creating the timer or timer rule.
+ * @param bFallbackToOneShotTimer if bCreateteRule is true and no timer rule can be created, try
+ * to create a one-shot timer instead.
+ * @return true, if the timer or timer rule was created successfully, false otherwise.
+ */
+ bool AddTimer(const CFileItem& item,
+ bool bCreateRule,
+ bool bShowTimerSettings,
+ bool bFallbackToOneShotTimer) const;
+
+ /*!
+ * @brief Delete a timer or timer rule, always showing a confirmation dialog.
+ * @param item containing a timer or timer rule to delete. item must be a timer, an epg tag or
+ * a channel.
+ * @param bIsRecording denotes whether the timer is currently recording (controls correct
+ * confirmation dialog).
+ * @param bDeleteRule denotes to delete a timer rule. For convenience, one can pass a timer
+ * created by a rule.
+ * @return true, if the timer or timer rule was deleted successfully, false otherwise.
+ */
+ bool DeleteTimer(const CFileItem& item, bool bIsRecording, bool bDeleteRule) const;
+
+ /*!
+ * @brief Delete a timer or timer rule, showing a confirmation dialog in case a timer currently
+ * recording shall be deleted.
+ * @param timer containing a timer or timer rule to delete.
+ * @param bIsRecording denotes whether the timer is currently recording (controls correct
+ * confirmation dialog).
+ * @param bDeleteRule denotes to delete a timer rule. For convenience, one can pass a timer
+ * created by a rule.
+ * @return true, if the timer or timer rule was deleted successfully, false otherwise.
+ */
+ bool DeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer,
+ bool bIsRecording,
+ bool bDeleteRule) const;
+
+ /*!
+ * @brief Open a dialog to confirm timer delete.
+ * @param timer the timer to delete.
+ * @param bDeleteRule in: ignored. out, for one shot timer scheduled by a timer rule: true to
+ * also delete the timer rule that has scheduled this timer, false to only delete the one shot
+ * timer. out, for one shot timer not scheduled by a timer rule: ignored
+ * @return true, to proceed with delete, false otherwise.
+ */
+ bool ConfirmDeleteTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer, bool& bDeleteRule) const;
+
+ /*!
+ * @brief Open a dialog to confirm stop recording.
+ * @param timer the recording to stop (actually the timer to delete).
+ * @return true, to proceed with delete, false otherwise.
+ */
+ bool ConfirmStopRecording(const std::shared_ptr<CPVRTimerInfoTag>& timer) const;
+
+ /*!
+ * @brief Announce and process a reminder timer.
+ * @param timer The reminder timer.
+ */
+ void AnnounceReminder(const std::shared_ptr<CPVRTimerInfoTag>& timer) const;
+
+ CPVRSettings m_settings;
+ mutable bool m_bReminderAnnouncementRunning{false};
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Timers = CPVRGUIActionsTimers;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp
new file mode 100644
index 0000000..28e5582
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016-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 "PVRGUIActionsUtils.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "pvr/PVRManager.h"
+#include "pvr/guilib/PVRGUIActionsEPG.h"
+#include "pvr/guilib/PVRGUIActionsRecordings.h"
+
+namespace PVR
+{
+bool CPVRGUIActionsUtils::OnInfo(const CFileItem& item)
+{
+ if (item.HasPVRRecordingInfoTag())
+ {
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().ShowRecordingInfo(item);
+ }
+ else if (item.HasPVRChannelInfoTag() || item.HasPVRTimerInfoTag())
+ {
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ShowEPGInfo(item);
+ }
+ else if (item.HasEPGSearchFilter())
+ {
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().EditSavedSearch(item);
+ }
+ return false;
+}
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.h b/xbmc/pvr/guilib/PVRGUIActionsUtils.h
new file mode 100644
index 0000000..a880548
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016-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.
+ */
+
+#pragma once
+
+#include "pvr/IPVRComponent.h"
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVRGUIActionsUtils : public IPVRComponent
+{
+public:
+ CPVRGUIActionsUtils() = default;
+ ~CPVRGUIActionsUtils() override = default;
+
+ /*!
+ * @brief Process info action for the given item.
+ * @param item The item.
+ */
+ bool OnInfo(const CFileItem& item);
+
+private:
+ CPVRGUIActionsUtils(const CPVRGUIActionsUtils&) = delete;
+ CPVRGUIActionsUtils const& operator=(CPVRGUIActionsUtils const&) = delete;
+};
+
+namespace GUI
+{
+// pretty scope and name
+using Utils = CPVRGUIActionsUtils;
+} // namespace GUI
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp
new file mode 100644
index 0000000..a239fe2
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012-2019 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 "PVRGUIChannelIconUpdater.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "filesystem/Directory.h"
+#include "guilib/LocalizeStrings.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/guilib/PVRGUIProgressHandler.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+
+void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const
+{
+ const std::string iconPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_PVRMENU_ICONPATH);
+ if (iconPath.empty())
+ return;
+
+ // fetch files in icon path for fast lookup
+ CFileItemList fileItemList;
+ XFILE::CDirectory::GetDirectory(iconPath, fileItemList, ".jpg|.png|.tbn", XFILE::DIR_FLAG_DEFAULTS);
+
+ if (fileItemList.IsEmpty())
+ return;
+
+ CLog::Log(LOGINFO, "Starting PVR channel icon search");
+
+ // create a map for fast lookup of normalized file base name
+ std::map<std::string, std::string> fileItemMap;
+ for (const auto& item : fileItemList)
+ {
+ std::string baseName = URIUtils::GetFileName(item->GetPath());
+ URIUtils::RemoveExtension(baseName);
+ StringUtils::ToLower(baseName);
+ fileItemMap.insert({baseName, item->GetPath()});
+ }
+
+ std::unique_ptr<CPVRGUIProgressHandler> progressHandler;
+ if (!m_groups.empty())
+ progressHandler.reset(
+ new CPVRGUIProgressHandler(g_localizeStrings.Get(19286))); // Searching for channel icons
+
+ for (const auto& group : m_groups)
+ {
+ const std::vector<std::shared_ptr<CPVRChannelGroupMember>> members = group->GetMembers();
+ int channelIndex = 0;
+ for (const auto& member : members)
+ {
+ const std::shared_ptr<CPVRChannel> channel = member->Channel();
+
+ progressHandler->UpdateProgress(channel->ChannelName(), channelIndex++, members.size());
+
+ // skip if an icon is already set and exists
+ if (CFileUtils::Exists(channel->IconPath()))
+ continue;
+
+ // reset icon before searching for a new one
+ channel->SetIconPath("");
+
+ const std::string strChannelUid = StringUtils::Format("{:08}", channel->UniqueID());
+ std::string strLegalClientChannelName =
+ CUtil::MakeLegalFileName(channel->ClientChannelName());
+ StringUtils::ToLower(strLegalClientChannelName);
+ std::string strLegalChannelName = CUtil::MakeLegalFileName(channel->ChannelName());
+ StringUtils::ToLower(strLegalChannelName);
+
+ std::map<std::string, std::string>::iterator itItem;
+ if ((itItem = fileItemMap.find(strLegalClientChannelName)) != fileItemMap.end() ||
+ (itItem = fileItemMap.find(strLegalChannelName)) != fileItemMap.end() ||
+ (itItem = fileItemMap.find(strChannelUid)) != fileItemMap.end())
+ {
+ channel->SetIconPath(itItem->second, CServiceBroker::GetSettingsComponent()
+ ->GetAdvancedSettings()
+ ->m_bPVRAutoScanIconsUserSet);
+ }
+
+ if (m_bUpdateDb)
+ channel->Persist();
+ }
+ }
+}
diff --git a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.h b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.h
new file mode 100644
index 0000000..d64a41f
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012-2019 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+namespace PVR
+{
+
+class CPVRChannelGroup;
+
+class CPVRGUIChannelIconUpdater
+{
+public:
+ /*!
+ * @brief ctor.
+ * @param groups The channel groups for which the channel icons shall be updated.
+ * @param bUpdateDb If true, persist the changed values in the PVR database.
+ */
+ CPVRGUIChannelIconUpdater(const std::vector<std::shared_ptr<CPVRChannelGroup>>& groups, bool bUpdateDb)
+ : m_groups(groups), m_bUpdateDb(bUpdateDb) {}
+
+ CPVRGUIChannelIconUpdater() = delete;
+ CPVRGUIChannelIconUpdater(const CPVRGUIChannelIconUpdater&) = delete;
+ CPVRGUIChannelIconUpdater& operator=(const CPVRGUIChannelIconUpdater&) = delete;
+
+ virtual ~CPVRGUIChannelIconUpdater() = default;
+
+ /*!
+ * @brief Search and update missing channel icons.
+ */
+ void SearchAndUpdateMissingChannelIcons() const;
+
+private:
+ const std::vector<std::shared_ptr<CPVRChannelGroup>> m_groups;
+ const bool m_bUpdateDb = false;
+};
+
+}
diff --git a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp
new file mode 100644
index 0000000..0ccfdf8
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2017-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 "PVRGUIChannelNavigator.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SystemClock.h"
+#include "utils/Job.h"
+#include "utils/JobManager.h"
+#include "utils/XTimeUtils.h"
+
+#include <mutex>
+
+using namespace KODI::GUILIB::GUIINFO;
+using namespace PVR;
+using namespace std::chrono_literals;
+
+namespace
+{
+class CPVRChannelTimeoutJobBase : public CJob, public IJobCallback
+{
+public:
+ CPVRChannelTimeoutJobBase() = delete;
+ CPVRChannelTimeoutJobBase(PVR::CPVRGUIChannelNavigator& channelNavigator,
+ std::chrono::milliseconds timeout)
+ : m_channelNavigator(channelNavigator)
+ {
+ m_delayTimer.Set(timeout);
+ }
+
+ ~CPVRChannelTimeoutJobBase() override = default;
+
+ virtual void OnTimeout() = 0;
+
+ void OnJobComplete(unsigned int iJobID, bool bSuccess, CJob* job) override {}
+
+ bool DoWork() override
+ {
+ while (!ShouldCancel(0, 0))
+ {
+ if (m_delayTimer.IsTimePast())
+ {
+ OnTimeout();
+ return true;
+ }
+ KODI::TIME::Sleep(10ms);
+ }
+ return false;
+ }
+
+protected:
+ PVR::CPVRGUIChannelNavigator& m_channelNavigator;
+
+private:
+ XbmcThreads::EndTime<> m_delayTimer;
+};
+
+class CPVRChannelEntryTimeoutJob : public CPVRChannelTimeoutJobBase
+{
+public:
+ CPVRChannelEntryTimeoutJob(PVR::CPVRGUIChannelNavigator& channelNavigator,
+ std::chrono::milliseconds timeout)
+ : CPVRChannelTimeoutJobBase(channelNavigator, timeout)
+ {
+ }
+ ~CPVRChannelEntryTimeoutJob() override = default;
+ const char* GetType() const override { return "pvr-channel-entry-timeout-job"; }
+ void OnTimeout() override { m_channelNavigator.SwitchToCurrentChannel(); }
+};
+
+class CPVRChannelInfoTimeoutJob : public CPVRChannelTimeoutJobBase
+{
+public:
+ CPVRChannelInfoTimeoutJob(PVR::CPVRGUIChannelNavigator& channelNavigator,
+ std::chrono::milliseconds timeout)
+ : CPVRChannelTimeoutJobBase(channelNavigator, timeout)
+ {
+ }
+ ~CPVRChannelInfoTimeoutJob() override = default;
+ const char* GetType() const override { return "pvr-channel-info-timeout-job"; }
+ void OnTimeout() override { m_channelNavigator.HideInfo(); }
+};
+} // unnamed namespace
+
+CPVRGUIChannelNavigator::CPVRGUIChannelNavigator()
+{
+ // Note: we cannot subscribe to PlayerInfoProvider here, as we're getting constructed
+ // before the info providers. We will subscribe once our first subscriber appears.
+}
+
+CPVRGUIChannelNavigator::~CPVRGUIChannelNavigator()
+{
+ const auto gui = CServiceBroker::GetGUI();
+ if (!gui)
+ return;
+
+ gui->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().Events().Unsubscribe(this);
+}
+
+void CPVRGUIChannelNavigator::SubscribeToShowInfoEventStream()
+{
+ CServiceBroker::GetGUI()
+ ->GetInfoManager()
+ .GetInfoProviders()
+ .GetPlayerInfoProvider()
+ .Events()
+ .Subscribe(this, &CPVRGUIChannelNavigator::Notify);
+}
+
+void CPVRGUIChannelNavigator::CheckAndPublishPreviewAndPlayerShowInfoChangedEvent()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ const bool currentValue = IsPreview() && m_playerShowInfo;
+ if (m_previewAndPlayerShowInfo != currentValue)
+ {
+ m_previewAndPlayerShowInfo = currentValue;
+
+ // inform subscribers
+ m_events.Publish(PVRPreviewAndPlayerShowInfoChangedEvent(currentValue));
+ }
+}
+
+void CPVRGUIChannelNavigator::Notify(const PlayerShowInfoChangedEvent& event)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_playerShowInfo = event.m_showInfo;
+ CheckAndPublishPreviewAndPlayerShowInfoChangedEvent();
+}
+
+void CPVRGUIChannelNavigator::SelectNextChannel(ChannelSwitchMode eSwitchMode)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (!m_playerShowInfo && eSwitchMode == ChannelSwitchMode::NO_SWITCH)
+ {
+ // show info for current channel on first next channel selection.
+ ShowInfo(false);
+ return;
+ }
+
+ const std::shared_ptr<CPVRChannelGroupMember> nextMember = GetNextOrPrevChannel(true);
+ if (nextMember)
+ SelectChannel(nextMember, eSwitchMode);
+}
+
+void CPVRGUIChannelNavigator::SelectPreviousChannel(ChannelSwitchMode eSwitchMode)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (!m_playerShowInfo && eSwitchMode == ChannelSwitchMode::NO_SWITCH)
+ {
+ // show info for current channel on first previous channel selection.
+ ShowInfo(false);
+ return;
+ }
+
+ const std::shared_ptr<CPVRChannelGroupMember> prevMember = GetNextOrPrevChannel(false);
+ if (prevMember)
+ SelectChannel(prevMember, eSwitchMode);
+}
+
+std::shared_ptr<CPVRChannelGroupMember> CPVRGUIChannelNavigator::GetNextOrPrevChannel(bool bNext)
+{
+ const bool bPlayingRadio = CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio();
+ const bool bPlayingTV = CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV();
+
+ if (bPlayingTV || bPlayingRadio)
+ {
+ const std::shared_ptr<CPVRChannelGroup> group =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(bPlayingRadio);
+ if (group)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return bNext ? group->GetNextChannelGroupMember(m_currentChannel)
+ : group->GetPreviousChannelGroupMember(m_currentChannel);
+ }
+ }
+ return {};
+}
+
+void CPVRGUIChannelNavigator::SelectChannel(
+ const std::shared_ptr<CPVRChannelGroupMember>& groupMember, ChannelSwitchMode eSwitchMode)
+{
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(CFileItem(groupMember));
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_currentChannel = groupMember;
+ ShowInfo(false);
+
+ CheckAndPublishPreviewAndPlayerShowInfoChangedEvent();
+
+ if (IsPreview() && eSwitchMode == ChannelSwitchMode::INSTANT_OR_DELAYED_SWITCH)
+ {
+ auto timeout =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_PVRPLAYBACK_CHANNELENTRYTIMEOUT));
+ if (timeout > 0ms)
+ {
+ // delayed switch
+ if (m_iChannelEntryJobId >= 0)
+ CServiceBroker::GetJobManager()->CancelJob(m_iChannelEntryJobId);
+
+ CPVRChannelEntryTimeoutJob* job = new CPVRChannelEntryTimeoutJob(*this, timeout);
+ m_iChannelEntryJobId =
+ CServiceBroker::GetJobManager()->AddJob(job, dynamic_cast<IJobCallback*>(job));
+ }
+ else
+ {
+ // instant switch
+ SwitchToCurrentChannel();
+ }
+ }
+}
+
+void CPVRGUIChannelNavigator::SwitchToCurrentChannel()
+{
+ std::unique_ptr<CFileItem> item;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_iChannelEntryJobId >= 0)
+ {
+ CServiceBroker::GetJobManager()->CancelJob(m_iChannelEntryJobId);
+ m_iChannelEntryJobId = -1;
+ }
+
+ item = std::make_unique<CFileItem>(m_currentChannel);
+ }
+
+ if (item)
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(*item, false);
+}
+
+bool CPVRGUIChannelNavigator::IsPreview() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_currentChannel != m_playingChannel;
+}
+
+bool CPVRGUIChannelNavigator::IsPreviewAndShowInfo() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_previewAndPlayerShowInfo;
+}
+
+void CPVRGUIChannelNavigator::ShowInfo()
+{
+ ShowInfo(true);
+}
+
+void CPVRGUIChannelNavigator::ShowInfo(bool bForce)
+{
+ auto timeout = std::chrono::seconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_PVRMENU_DISPLAYCHANNELINFO));
+
+ if (bForce || timeout > 0s)
+ {
+ CServiceBroker::GetGUI()
+ ->GetInfoManager()
+ .GetInfoProviders()
+ .GetPlayerInfoProvider()
+ .SetShowInfo(true);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_iChannelInfoJobId >= 0)
+ {
+ CServiceBroker::GetJobManager()->CancelJob(m_iChannelInfoJobId);
+ m_iChannelInfoJobId = -1;
+ }
+
+ if (!bForce && timeout > 0s)
+ {
+ CPVRChannelInfoTimeoutJob* job = new CPVRChannelInfoTimeoutJob(*this, timeout);
+ m_iChannelInfoJobId =
+ CServiceBroker::GetJobManager()->AddJob(job, dynamic_cast<IJobCallback*>(job));
+ }
+ }
+}
+
+void CPVRGUIChannelNavigator::HideInfo()
+{
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().SetShowInfo(
+ false);
+
+ CFileItemPtr item;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_iChannelInfoJobId >= 0)
+ {
+ CServiceBroker::GetJobManager()->CancelJob(m_iChannelInfoJobId);
+ m_iChannelInfoJobId = -1;
+ }
+
+ if (m_currentChannel != m_playingChannel)
+ {
+ m_currentChannel = m_playingChannel;
+ if (m_playingChannel)
+ item.reset(new CFileItem(m_playingChannel));
+ }
+
+ CheckAndPublishPreviewAndPlayerShowInfoChangedEvent();
+ }
+
+ if (item)
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*item);
+}
+
+void CPVRGUIChannelNavigator::ToggleInfo()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_playerShowInfo)
+ HideInfo();
+ else
+ ShowInfo();
+}
+
+void CPVRGUIChannelNavigator::SetPlayingChannel(
+ const std::shared_ptr<CPVRChannelGroupMember>& groupMember)
+{
+ CFileItemPtr item;
+
+ if (groupMember)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_playingChannel = groupMember;
+ if (m_currentChannel != m_playingChannel)
+ {
+ m_currentChannel = m_playingChannel;
+ if (m_playingChannel)
+ item.reset(new CFileItem(m_playingChannel));
+ }
+
+ CheckAndPublishPreviewAndPlayerShowInfoChangedEvent();
+ }
+
+ if (item)
+ CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*item);
+
+ ShowInfo(false);
+}
+
+void CPVRGUIChannelNavigator::ClearPlayingChannel()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_playingChannel.reset();
+ HideInfo();
+
+ CheckAndPublishPreviewAndPlayerShowInfoChangedEvent();
+}
diff --git a/xbmc/pvr/guilib/PVRGUIChannelNavigator.h b/xbmc/pvr/guilib/PVRGUIChannelNavigator.h
new file mode 100644
index 0000000..5c27074
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIChannelNavigator.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017-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.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "utils/EventStream.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+struct PlayerShowInfoChangedEvent;
+}
+} // namespace GUILIB
+} // namespace KODI
+
+namespace PVR
+{
+enum class ChannelSwitchMode
+{
+ NO_SWITCH, // no channel switch
+ INSTANT_OR_DELAYED_SWITCH // switch according to SETTING_PVRPLAYBACK_CHANNELENTRYTIMEOUT
+};
+
+struct PVRPreviewAndPlayerShowInfoChangedEvent
+{
+ explicit PVRPreviewAndPlayerShowInfoChangedEvent(bool previewAndPlayerShowInfo)
+ : m_previewAndPlayerShowInfo(previewAndPlayerShowInfo)
+ {
+ }
+ virtual ~PVRPreviewAndPlayerShowInfoChangedEvent() = default;
+
+ bool m_previewAndPlayerShowInfo{false};
+};
+
+class CPVRChannelGroupMember;
+
+class CPVRGUIChannelNavigator
+{
+public:
+ CPVRGUIChannelNavigator();
+ virtual ~CPVRGUIChannelNavigator();
+
+ /*!
+ * @brief Subscribe to the event stream for changes of channel preview and player show info.
+ * @param owner The subscriber.
+ * @param fn The callback function of the subscriber for the events.
+ */
+ template<typename A>
+ void Subscribe(A* owner, void (A::*fn)(const PVRPreviewAndPlayerShowInfoChangedEvent&))
+ {
+ SubscribeToShowInfoEventStream();
+ m_events.Subscribe(owner, fn);
+ }
+
+ /*!
+ * @brief Unsubscribe from the event stream for changes of channel preview and player show info.
+ * @param obj The subscriber.
+ */
+ template<typename A>
+ void Unsubscribe(A* obj)
+ {
+ m_events.Unsubscribe(obj);
+ }
+
+ /*!
+ * @brief CEventStream callback for player show info flag changes.
+ * @param event The event.
+ */
+ void Notify(const KODI::GUILIB::GUIINFO::PlayerShowInfoChangedEvent& event);
+
+ /*!
+ * @brief Select the next channel in currently playing channel group, relative to the currently
+ * selected channel.
+ * @param eSwitchMode controls whether only the channel info OSD is triggered or whether
+ * additionally a (delayed) channel switch will be done.
+ */
+ void SelectNextChannel(ChannelSwitchMode eSwitchMode);
+
+ /*!
+ * @brief Select the previous channel in currently playing channel group, relative to the
+ * currently selected channel.
+ * @param eSwitchMode controls whether only the channel info OSD is triggered or whether
+ * additionally a (delayed) channel switch will be done.
+ */
+ void SelectPreviousChannel(ChannelSwitchMode eSwitchMode);
+
+ /*!
+ * @brief Switch to the currently selected channel.
+ */
+ void SwitchToCurrentChannel();
+
+ /*!
+ * @brief Query the state of channel preview.
+ * @return True, if the currently selected channel is different from the currently playing
+ * channel, False otherwise.
+ */
+ bool IsPreview() const;
+
+ /*!
+ * @brief Query the state of channel preview and channel info OSD.
+ * @return True, if the currently selected channel is different from the currently playing channel
+ * and channel info OSD is active, False otherwise.
+ */
+ bool IsPreviewAndShowInfo() const;
+
+ /*!
+ * @brief Show the channel info OSD.
+ */
+ void ShowInfo();
+
+ /*!
+ * @brief Hide the channel info OSD.
+ */
+ void HideInfo();
+
+ /*!
+ * @brief Toggle the channel info OSD visibility.
+ */
+ void ToggleInfo();
+
+ /*!
+ * @brief Set a new playing channel group member and show the channel info OSD for the new
+ * channel.
+ * @param groupMember The new playing channel group member
+ */
+ void SetPlayingChannel(const std::shared_ptr<CPVRChannelGroupMember>& groupMember);
+
+ /*!
+ * @brief Clear the currently playing channel and hide the channel info OSD.
+ */
+ void ClearPlayingChannel();
+
+private:
+ /*!
+ * @brief Get next or previous channel group member of the playing channel group, relative to the
+ * currently selected channel group member.
+ * @param bNext True to get the next channel group member, false to get the previous channel group
+ * member.
+ * @param return The channel or nullptr if not found.
+ */
+ std::shared_ptr<CPVRChannelGroupMember> GetNextOrPrevChannel(bool bNext);
+
+ /*!
+ * @brief Select a given channel group member, display channel info OSD, switch according to given
+ * switch mode.
+ * @param groupMember The channel group member to select.
+ * @param eSwitchMode The channel switch mode.
+ */
+ void SelectChannel(const std::shared_ptr<CPVRChannelGroupMember>& groupMember,
+ ChannelSwitchMode eSwitchMode);
+
+ /*!
+ * @brief Show the channel info OSD.
+ * @param bForce True ignores value of SETTING_PVRMENU_DISPLAYCHANNELINFO and always activates the
+ * info, False acts aaccording settings value.
+ */
+ void ShowInfo(bool bForce);
+
+ /*!
+ * @brief Subscribe to the event stream for changes of player show info.
+ */
+ void SubscribeToShowInfoEventStream();
+
+ /*!
+ * @brief Check if property preview and show info value changed, inform subscribers in case.
+ */
+ void CheckAndPublishPreviewAndPlayerShowInfoChangedEvent();
+
+ mutable CCriticalSection m_critSection;
+ std::shared_ptr<CPVRChannelGroupMember> m_playingChannel;
+ std::shared_ptr<CPVRChannelGroupMember> m_currentChannel;
+ int m_iChannelEntryJobId = -1;
+ int m_iChannelInfoJobId = -1;
+ CEventSource<PVRPreviewAndPlayerShowInfoChangedEvent> m_events;
+ bool m_playerShowInfo{false};
+ bool m_previewAndPlayerShowInfo{false};
+};
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp b/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp
new file mode 100644
index 0000000..f876522
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017-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 "PVRGUIProgressHandler.h"
+
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogExtendedProgressBar.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/WindowIDs.h"
+
+#include <algorithm>
+#include <cmath>
+#include <mutex>
+#include <string>
+
+using namespace std::chrono_literals;
+
+namespace PVR
+{
+
+CPVRGUIProgressHandler::CPVRGUIProgressHandler(const std::string& strTitle)
+ : CThread("PVRGUIProgressHandler"), m_strTitle(strTitle)
+{
+}
+
+void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, float fProgress)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_bChanged = true;
+ m_strText = strText;
+ m_fProgress = fProgress;
+
+ if (!m_bCreated)
+ {
+ m_bCreated = true;
+ Create();
+ }
+}
+
+void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, int iCurrent, int iMax)
+{
+ float fPercentage = (iCurrent * 100.0f) / iMax;
+ if (!std::isnan(fPercentage))
+ fPercentage = std::min(100.0f, fPercentage);
+
+ UpdateProgress(strText, fPercentage);
+}
+
+void CPVRGUIProgressHandler::Process()
+{
+ CGUIDialogExtendedProgressBar* progressBar =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(
+ WINDOW_DIALOG_EXT_PROGRESS);
+ if (m_bStop || !progressBar)
+ return;
+
+ CGUIDialogProgressBarHandle* progressHandle = progressBar->GetHandle(m_strTitle);
+ if (!progressHandle)
+ return;
+
+ while (!m_bStop)
+ {
+ float fProgress = 0.0;
+ std::string strText;
+ bool bUpdate = false;
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_bChanged)
+ {
+ m_bChanged = false;
+ fProgress = m_fProgress;
+ strText = m_strText;
+ bUpdate = true;
+ }
+ }
+
+ if (bUpdate)
+ {
+ progressHandle->SetPercentage(fProgress);
+ progressHandle->SetText(strText);
+ }
+
+ // Intentionally ignore some changes that come in too fast. Humans cannot read as fast as Mr. Data ;-)
+ CThread::Sleep(100ms);
+ }
+
+ progressHandle->MarkFinished();
+}
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/PVRGUIProgressHandler.h b/xbmc/pvr/guilib/PVRGUIProgressHandler.h
new file mode 100644
index 0000000..6e7b72f
--- /dev/null
+++ b/xbmc/pvr/guilib/PVRGUIProgressHandler.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017-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.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <string>
+
+namespace PVR
+{
+ class CPVRGUIProgressHandler : private CThread
+ {
+ public:
+ CPVRGUIProgressHandler() = delete;
+
+ /*!
+ * @brief Creates and asynchronously shows a progress dialog with the given title.
+ * @param strTitle The title for the progress dialog.
+ */
+ explicit CPVRGUIProgressHandler(const std::string& strTitle);
+
+ ~CPVRGUIProgressHandler() override = default;
+
+ /*!
+ * @brief Update the progress dialogs's content.
+ * @param strText The new progress text.
+ * @param fProgress The new progress value, in a range from 0.0 to 100.0.
+ */
+ void UpdateProgress(const std::string& strText, float fProgress);
+
+ /*!
+ * @brief Update the progress dialogs's content.
+ * @param strText The new progress text.
+ * @param iCurrent The new current progress value, must be less or equal iMax.
+ * @param iMax The new maximum progress value, must be greater or equal iCurrent.
+ */
+ void UpdateProgress(const std::string& strText, int iCurrent, int iMax);
+
+ protected:
+ // CThread implementation
+ void Process() override;
+
+ private:
+ CCriticalSection m_critSection;
+ const std::string m_strTitle;
+ std::string m_strText;
+ float m_fProgress{0.0f};
+ bool m_bChanged{false};
+ bool m_bCreated{false};
+ };
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/guiinfo/CMakeLists.txt b/xbmc/pvr/guilib/guiinfo/CMakeLists.txt
new file mode 100644
index 0000000..18491ee
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES PVRGUIInfo.cpp
+ PVRGUITimerInfo.cpp
+ PVRGUITimesInfo.cpp)
+
+set(HEADERS PVRGUIInfo.h
+ PVRGUITimerInfo.h
+ PVRGUITimesInfo.h)
+
+core_add_library(pvr_guilib_guiinfo)
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp
new file mode 100644
index 0000000..9cffb9a
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp
@@ -0,0 +1,2017 @@
+/*
+ * 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 "PVRGUIInfo.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/channels/PVRRadioRDSInfoTag.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/epg/EpgSearchFilter.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/providers/PVRProvider.h"
+#include "pvr/providers/PVRProviders.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SingleLock.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+
+#include <cmath>
+#include <ctime>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+using namespace KODI::GUILIB::GUIINFO;
+using namespace std::chrono_literals;
+
+CPVRGUIInfo::CPVRGUIInfo() : CThread("PVRGUIInfo")
+{
+ ResetProperties();
+}
+
+void CPVRGUIInfo::ResetProperties()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_anyTimersInfo.ResetProperties();
+ m_tvTimersInfo.ResetProperties();
+ m_radioTimersInfo.ResetProperties();
+ m_timesInfo.Reset();
+ m_bHasTVRecordings = false;
+ m_bHasRadioRecordings = false;
+ m_iCurrentActiveClient = 0;
+ m_strPlayingClientName.clear();
+ m_strBackendName.clear();
+ m_strBackendVersion.clear();
+ m_strBackendHost.clear();
+ m_strBackendTimers.clear();
+ m_strBackendRecordings.clear();
+ m_strBackendDeletedRecordings.clear();
+ m_strBackendProviders.clear();
+ m_strBackendChannelGroups.clear();
+ m_strBackendChannels.clear();
+ m_iBackendDiskTotal = 0;
+ m_iBackendDiskUsed = 0;
+ m_bIsPlayingTV = false;
+ m_bIsPlayingRadio = false;
+ m_bIsPlayingRecording = false;
+ m_bIsPlayingEpgTag = false;
+ m_bIsPlayingEncryptedStream = false;
+ m_bIsRecordingPlayingChannel = false;
+ m_bCanRecordPlayingChannel = false;
+ m_bIsPlayingActiveRecording = false;
+ m_bHasTVChannels = false;
+ m_bHasRadioChannels = false;
+
+ ClearQualityInfo(m_qualityInfo);
+ ClearDescrambleInfo(m_descrambleInfo);
+
+ m_updateBackendCacheRequested = false;
+ m_bRegistered = false;
+}
+
+void CPVRGUIInfo::ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo)
+{
+ memset(&qualityInfo, 0, sizeof(qualityInfo));
+ strncpy(qualityInfo.strAdapterName, g_localizeStrings.Get(13106).c_str(),
+ PVR_ADDON_NAME_STRING_LENGTH - 1);
+ strncpy(qualityInfo.strAdapterStatus, g_localizeStrings.Get(13106).c_str(),
+ PVR_ADDON_NAME_STRING_LENGTH - 1);
+}
+
+void CPVRGUIInfo::ClearDescrambleInfo(PVR_DESCRAMBLE_INFO& descrambleInfo)
+{
+ descrambleInfo = {};
+}
+
+void CPVRGUIInfo::Start()
+{
+ ResetProperties();
+ Create();
+ SetPriority(ThreadPriority::BELOW_NORMAL);
+}
+
+void CPVRGUIInfo::Stop()
+{
+ StopThread();
+
+ auto& mgr = CServiceBroker::GetPVRManager();
+ auto& channels = mgr.Get<PVR::GUI::Channels>();
+ channels.GetChannelNavigator().Unsubscribe(this);
+ channels.Events().Unsubscribe(this);
+ mgr.Events().Unsubscribe(this);
+
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ {
+ gui->GetInfoManager().UnregisterInfoProvider(this);
+ m_bRegistered = false;
+ }
+}
+
+void CPVRGUIInfo::Notify(const PVREvent& event)
+{
+ if (event == PVREvent::Timers || event == PVREvent::TimersInvalidated)
+ UpdateTimersCache();
+}
+
+void CPVRGUIInfo::Notify(const PVRChannelNumberInputChangedEvent& event)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_channelNumberInput = event.m_input;
+}
+
+void CPVRGUIInfo::Notify(const PVRPreviewAndPlayerShowInfoChangedEvent& event)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_previewAndPlayerShowInfo = event.m_previewAndPlayerShowInfo;
+}
+
+void CPVRGUIInfo::Process()
+{
+ auto toggleIntervalMs = std::chrono::milliseconds(
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRInfoToggleInterval);
+ XbmcThreads::EndTime<> cacheTimer(toggleIntervalMs);
+
+ auto& mgr = CServiceBroker::GetPVRManager();
+ mgr.Events().Subscribe(this, &CPVRGUIInfo::Notify);
+
+ auto& channels = mgr.Get<PVR::GUI::Channels>();
+ channels.Events().Subscribe(this, &CPVRGUIInfo::Notify);
+ channels.GetChannelNavigator().Subscribe(this, &CPVRGUIInfo::Notify);
+
+ /* updated on request */
+ UpdateTimersCache();
+
+ /* update the backend cache once initially */
+ m_updateBackendCacheRequested = true;
+
+ while (!g_application.m_bStop && !m_bStop)
+ {
+ if (!m_bRegistered)
+ {
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ {
+ gui->GetInfoManager().RegisterInfoProvider(this);
+ m_bRegistered = true;
+ }
+ }
+
+ if (!m_bStop)
+ UpdateQualityData();
+ std::this_thread::yield();
+
+ if (!m_bStop)
+ UpdateDescrambleData();
+ std::this_thread::yield();
+
+ if (!m_bStop)
+ UpdateMisc();
+ std::this_thread::yield();
+
+ if (!m_bStop)
+ UpdateTimeshiftData();
+ std::this_thread::yield();
+
+ if (!m_bStop)
+ UpdateTimersToggle();
+ std::this_thread::yield();
+
+ if (!m_bStop)
+ UpdateNextTimer();
+ std::this_thread::yield();
+
+ // Update the backend cache every toggleInterval seconds
+ if (!m_bStop && cacheTimer.IsTimePast())
+ {
+ UpdateBackendCache();
+ cacheTimer.Set(toggleIntervalMs);
+ }
+
+ if (!m_bStop)
+ CThread::Sleep(500ms);
+ }
+}
+
+void CPVRGUIInfo::UpdateQualityData()
+{
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRPLAYBACK_SIGNALQUALITY))
+ return;
+
+ const std::shared_ptr<CPVRPlaybackState> playbackState =
+ CServiceBroker::GetPVRManager().PlaybackState();
+ if (!playbackState)
+ return;
+
+ PVR_SIGNAL_STATUS qualityInfo;
+ ClearQualityInfo(qualityInfo);
+
+ const int channelUid = playbackState->GetPlayingChannelUniqueID();
+ if (channelUid > 0)
+ {
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().Clients()->GetCreatedClient(
+ playbackState->GetPlayingClientID());
+ if (client)
+ client->SignalQuality(channelUid, qualityInfo);
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_qualityInfo = qualityInfo;
+}
+
+void CPVRGUIInfo::UpdateDescrambleData()
+{
+ const std::shared_ptr<CPVRPlaybackState> playbackState =
+ CServiceBroker::GetPVRManager().PlaybackState();
+ if (!playbackState)
+ return;
+
+ PVR_DESCRAMBLE_INFO descrambleInfo;
+ ClearDescrambleInfo(descrambleInfo);
+
+ const int channelUid = playbackState->GetPlayingChannelUniqueID();
+ if (channelUid > 0)
+ {
+ const std::shared_ptr<CPVRClient> client =
+ CServiceBroker::GetPVRManager().Clients()->GetCreatedClient(
+ playbackState->GetPlayingClientID());
+ if (client)
+ client->GetDescrambleInfo(channelUid, descrambleInfo);
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_descrambleInfo = descrambleInfo;
+}
+
+void CPVRGUIInfo::UpdateMisc()
+{
+ const CPVRManager& mgr = CServiceBroker::GetPVRManager();
+ bool bStarted = mgr.IsStarted();
+ const std::shared_ptr<CPVRPlaybackState> state = mgr.PlaybackState();
+
+ /* safe to fetch these unlocked, since they're updated from the same thread as this one */
+ const std::string strPlayingClientName = bStarted ? state->GetPlayingClientName() : "";
+ const bool bHasTVRecordings = bStarted && mgr.Recordings()->GetNumTVRecordings() > 0;
+ const bool bHasRadioRecordings = bStarted && mgr.Recordings()->GetNumRadioRecordings() > 0;
+ const bool bIsPlayingTV = bStarted && state->IsPlayingTV();
+ const bool bIsPlayingRadio = bStarted && state->IsPlayingRadio();
+ const bool bIsPlayingRecording = bStarted && state->IsPlayingRecording();
+ const bool bIsPlayingEpgTag = bStarted && state->IsPlayingEpgTag();
+ const bool bIsPlayingEncryptedStream = bStarted && state->IsPlayingEncryptedChannel();
+ const bool bHasTVChannels = bStarted && mgr.ChannelGroups()->GetGroupAllTV()->HasChannels();
+ const bool bHasRadioChannels = bStarted && mgr.ChannelGroups()->GetGroupAllRadio()->HasChannels();
+ const bool bCanRecordPlayingChannel = bStarted && state->CanRecordOnPlayingChannel();
+ const bool bIsRecordingPlayingChannel = bStarted && state->IsRecordingOnPlayingChannel();
+ const bool bIsPlayingActiveRecording = bStarted && state->IsPlayingActiveRecording();
+ const std::string strPlayingTVGroup =
+ (bStarted && bIsPlayingTV) ? state->GetActiveChannelGroup(false)->GroupName() : "";
+ const std::string strPlayingRadioGroup =
+ (bStarted && bIsPlayingRadio) ? state->GetActiveChannelGroup(true)->GroupName() : "";
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_strPlayingClientName = strPlayingClientName;
+ m_bHasTVRecordings = bHasTVRecordings;
+ m_bHasRadioRecordings = bHasRadioRecordings;
+ m_bIsPlayingTV = bIsPlayingTV;
+ m_bIsPlayingRadio = bIsPlayingRadio;
+ m_bIsPlayingRecording = bIsPlayingRecording;
+ m_bIsPlayingEpgTag = bIsPlayingEpgTag;
+ m_bIsPlayingEncryptedStream = bIsPlayingEncryptedStream;
+ m_bHasTVChannels = bHasTVChannels;
+ m_bHasRadioChannels = bHasRadioChannels;
+ m_strPlayingTVGroup = strPlayingTVGroup;
+ m_strPlayingRadioGroup = strPlayingRadioGroup;
+ m_bCanRecordPlayingChannel = bCanRecordPlayingChannel;
+ m_bIsRecordingPlayingChannel = bIsRecordingPlayingChannel;
+ m_bIsPlayingActiveRecording = bIsPlayingActiveRecording;
+}
+
+void CPVRGUIInfo::UpdateTimeshiftData()
+{
+ m_timesInfo.Update();
+}
+
+bool CPVRGUIInfo::InitCurrentItem(CFileItem* item)
+{
+ CServiceBroker::GetPVRManager().PublishEvent(PVREvent::CurrentItem);
+ return false;
+}
+
+bool CPVRGUIInfo::GetLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback) const
+{
+ return GetListItemAndPlayerLabel(item, info, value) || GetPVRLabel(item, info, value) ||
+ GetRadioRDSLabel(item, info, value);
+}
+
+namespace
+{
+std::string GetAsLocalizedDateString(const CDateTime& datetime, bool bLongDate)
+{
+ return datetime.IsValid() ? datetime.GetAsLocalizedDate(bLongDate) : "";
+}
+
+std::string GetAsLocalizedTimeString(const CDateTime& datetime)
+{
+ return datetime.IsValid() ? datetime.GetAsLocalizedTime("", false) : "";
+}
+
+std::string GetAsLocalizedDateTimeString(const CDateTime& datetime)
+{
+ return datetime.IsValid() ? datetime.GetAsLocalizedDateTime(false, false) : "";
+}
+
+std::string GetEpgTagTitle(const std::shared_ptr<CPVREpgInfoTag>& epgTag)
+{
+ if (epgTag)
+ {
+ if (CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
+ return g_localizeStrings.Get(19266); // Parental locked
+ else if (!epgTag->Title().empty())
+ return epgTag->Title();
+ }
+
+ if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_EPG_HIDENOINFOAVAILABLE))
+ return g_localizeStrings.Get(19055); // no information available
+
+ return {};
+}
+
+} // unnamed namespace
+
+bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
+ const CGUIInfo& info,
+ std::string& strValue) const
+{
+ const std::shared_ptr<CPVRTimerInfoTag> timer = item->GetPVRTimerInfoTag();
+ if (timer)
+ {
+ switch (info.m_info)
+ {
+ case LISTITEM_DATE:
+ strValue = timer->Summary();
+ return true;
+ case LISTITEM_STARTDATE:
+ strValue = GetAsLocalizedDateString(timer->StartAsLocalTime(), true);
+ return true;
+ case LISTITEM_STARTTIME:
+ strValue = GetAsLocalizedTimeString(timer->StartAsLocalTime());
+ return true;
+ case LISTITEM_ENDDATE:
+ strValue = GetAsLocalizedDateString(timer->EndAsLocalTime(), true);
+ return true;
+ case LISTITEM_ENDTIME:
+ strValue = GetAsLocalizedTimeString(timer->EndAsLocalTime());
+ return true;
+ case LISTITEM_DURATION:
+ if (timer->GetDuration() > 0)
+ {
+ strValue = StringUtils::SecondsToTimeString(timer->GetDuration(),
+ static_cast<TIME_FORMAT>(info.GetData4()));
+ return true;
+ }
+ return false;
+ case LISTITEM_TITLE:
+ strValue = timer->Title();
+ return true;
+ case LISTITEM_COMMENT:
+ strValue =
+ timer->GetStatus(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() ==
+ WINDOW_RADIO_TIMER_RULES);
+ return true;
+ case LISTITEM_TIMERTYPE:
+ strValue = timer->GetTypeAsString();
+ return true;
+ case LISTITEM_CHANNEL_NAME:
+ strValue = timer->ChannelName();
+ return true;
+ case LISTITEM_EPG_EVENT_TITLE:
+ case LISTITEM_EPG_EVENT_ICON:
+ case LISTITEM_GENRE:
+ case LISTITEM_PLOT:
+ case LISTITEM_PLOT_OUTLINE:
+ case LISTITEM_ORIGINALTITLE:
+ case LISTITEM_YEAR:
+ case LISTITEM_SEASON:
+ case LISTITEM_EPISODE:
+ case LISTITEM_EPISODENAME:
+ case LISTITEM_DIRECTOR:
+ case LISTITEM_CHANNEL_NUMBER:
+ case LISTITEM_PREMIERED:
+ break; // obtain value from channel/epg
+ default:
+ return false;
+ }
+ }
+
+ const std::shared_ptr<CPVRRecording> recording(item->GetPVRRecordingInfoTag());
+ if (recording)
+ {
+ // Note: CPVRRecoding is derived from CVideoInfoTag. All base class properties will be handled
+ // by CVideoGUIInfoProvider. Only properties introduced by CPVRRecording need to be handled here.
+ switch (info.m_info)
+ {
+ case LISTITEM_DATE:
+ strValue = GetAsLocalizedDateTimeString(recording->RecordingTimeAsLocalTime());
+ return true;
+ case LISTITEM_STARTDATE:
+ strValue = GetAsLocalizedDateString(recording->RecordingTimeAsLocalTime(), true);
+ return true;
+ case VIDEOPLAYER_STARTTIME:
+ case LISTITEM_STARTTIME:
+ strValue = GetAsLocalizedTimeString(recording->RecordingTimeAsLocalTime());
+ return true;
+ case LISTITEM_ENDDATE:
+ strValue = GetAsLocalizedDateString(recording->EndTimeAsLocalTime(), true);
+ return true;
+ case VIDEOPLAYER_ENDTIME:
+ case LISTITEM_ENDTIME:
+ strValue = GetAsLocalizedTimeString(recording->EndTimeAsLocalTime());
+ return true;
+ case LISTITEM_EXPIRATION_DATE:
+ if (recording->HasExpirationTime())
+ {
+ strValue = GetAsLocalizedDateString(recording->ExpirationTimeAsLocalTime(), false);
+ return true;
+ }
+ break;
+ case LISTITEM_EXPIRATION_TIME:
+ if (recording->HasExpirationTime())
+ {
+ strValue = GetAsLocalizedTimeString(recording->ExpirationTimeAsLocalTime());
+ ;
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_EPISODENAME:
+ case LISTITEM_EPISODENAME:
+ strValue = recording->EpisodeName();
+ // fixup multiline episode name strings (which do not fit in any way in our GUI)
+ StringUtils::Replace(strValue, "\n", ", ");
+ return true;
+ case VIDEOPLAYER_CHANNEL_NAME:
+ case LISTITEM_CHANNEL_NAME:
+ strValue = recording->ChannelName();
+ if (strValue.empty())
+ {
+ if (recording->ProviderName().empty())
+ {
+ const auto& provider = recording->GetProvider();
+ strValue = provider->GetName();
+ }
+ else
+ {
+ strValue = recording->ProviderName();
+ }
+ }
+ return true;
+ case VIDEOPLAYER_CHANNEL_NUMBER:
+ case LISTITEM_CHANNEL_NUMBER:
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(*item);
+ if (groupMember)
+ {
+ strValue = groupMember->ChannelNumber().FormattedChannelNumber();
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_ICON:
+ if (recording->ClientIconPath().empty() && recording->ClientThumbnailPath().empty() &&
+ // Only use a fallback if there is more than a single provider available
+ // Note: an add-on itself is a provider, hence we don't use > 0
+ CServiceBroker::GetPVRManager().Providers()->GetNumProviders() > 1 &&
+ !recording->Channel())
+ {
+ auto provider = recording->GetProvider();
+ if (!provider->GetIconPath().empty())
+ {
+ strValue = provider->GetIconPath();
+ return true;
+ }
+ }
+ return false;
+ case VIDEOPLAYER_CHANNEL_GROUP:
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ strValue = recording->IsRadio() ? m_strPlayingRadioGroup : m_strPlayingTVGroup;
+ return true;
+ }
+ case VIDEOPLAYER_PREMIERED:
+ case LISTITEM_PREMIERED:
+ if (recording->FirstAired().IsValid())
+ {
+ strValue = recording->FirstAired().GetAsLocalizedDate();
+ return true;
+ }
+ else if (recording->HasYear())
+ {
+ strValue = std::to_string(recording->GetYear());
+ return true;
+ }
+ return false;
+ case LISTITEM_SIZE:
+ if (recording->GetSizeInBytes() > 0)
+ {
+ strValue = StringUtils::SizeToString(recording->GetSizeInBytes());
+ return true;
+ }
+ return false;
+ }
+ return false;
+ }
+
+ const std::shared_ptr<CPVREpgSearchFilter> filter = item->GetEPGSearchFilter();
+ if (filter)
+ {
+ switch (info.m_info)
+ {
+ case LISTITEM_DATE:
+ {
+ CDateTime lastExecLocal;
+ lastExecLocal.SetFromUTCDateTime(filter->GetLastExecutedDateTime());
+ strValue = GetAsLocalizedDateTimeString(lastExecLocal);
+ if (strValue.empty())
+ strValue = g_localizeStrings.Get(10006); // "N/A"
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::shared_ptr<CPVREpgInfoTag> epgTag;
+ std::shared_ptr<CPVRChannel> channel;
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ CPVRItem pvrItem(item);
+ channel = pvrItem.GetChannel();
+
+ switch (info.m_info)
+ {
+ case VIDEOPLAYER_NEXT_TITLE:
+ case VIDEOPLAYER_NEXT_GENRE:
+ case VIDEOPLAYER_NEXT_PLOT:
+ case VIDEOPLAYER_NEXT_PLOT_OUTLINE:
+ case VIDEOPLAYER_NEXT_STARTTIME:
+ case VIDEOPLAYER_NEXT_ENDTIME:
+ case VIDEOPLAYER_NEXT_DURATION:
+ case LISTITEM_NEXT_TITLE:
+ case LISTITEM_NEXT_GENRE:
+ case LISTITEM_NEXT_PLOT:
+ case LISTITEM_NEXT_PLOT_OUTLINE:
+ case LISTITEM_NEXT_STARTDATE:
+ case LISTITEM_NEXT_STARTTIME:
+ case LISTITEM_NEXT_ENDDATE:
+ case LISTITEM_NEXT_ENDTIME:
+ case LISTITEM_NEXT_DURATION:
+ // next playing event
+ epgTag = pvrItem.GetNextEpgInfoTag();
+ break;
+ default:
+ // now playing event
+ epgTag = pvrItem.GetEpgInfoTag();
+ break;
+ }
+
+ switch (info.m_info)
+ {
+ // special handling for channels without epg or with radio rds data
+ case PLAYER_TITLE:
+ case LISTITEM_TITLE:
+ case VIDEOPLAYER_NEXT_TITLE:
+ case LISTITEM_NEXT_TITLE:
+ case LISTITEM_EPG_EVENT_TITLE:
+ // Note: in difference to LISTITEM_TITLE, LISTITEM_EPG_EVENT_TITLE returns the title
+ // associated with the epg event of a timer, if any, and not the title of the timer.
+ strValue = GetEpgTagTitle(epgTag);
+ return true;
+ }
+ }
+
+ if (epgTag)
+ {
+ switch (info.m_info)
+ {
+ case VIDEOPLAYER_GENRE:
+ case LISTITEM_GENRE:
+ case VIDEOPLAYER_NEXT_GENRE:
+ case LISTITEM_NEXT_GENRE:
+ strValue = epgTag->GetGenresLabel();
+ return true;
+ case VIDEOPLAYER_PLOT:
+ case LISTITEM_PLOT:
+ case VIDEOPLAYER_NEXT_PLOT:
+ case LISTITEM_NEXT_PLOT:
+ if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
+ strValue = epgTag->Plot();
+ return true;
+ case VIDEOPLAYER_PLOT_OUTLINE:
+ case LISTITEM_PLOT_OUTLINE:
+ case VIDEOPLAYER_NEXT_PLOT_OUTLINE:
+ case LISTITEM_NEXT_PLOT_OUTLINE:
+ if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
+ strValue = epgTag->PlotOutline();
+ return true;
+ case LISTITEM_DATE:
+ strValue = GetAsLocalizedDateTimeString(epgTag->StartAsLocalTime());
+ return true;
+ case LISTITEM_STARTDATE:
+ case LISTITEM_NEXT_STARTDATE:
+ strValue = GetAsLocalizedDateString(epgTag->StartAsLocalTime(), true);
+ return true;
+ case VIDEOPLAYER_STARTTIME:
+ case VIDEOPLAYER_NEXT_STARTTIME:
+ case LISTITEM_STARTTIME:
+ case LISTITEM_NEXT_STARTTIME:
+ strValue = GetAsLocalizedTimeString(epgTag->StartAsLocalTime());
+ return true;
+ case LISTITEM_ENDDATE:
+ case LISTITEM_NEXT_ENDDATE:
+ strValue = GetAsLocalizedDateString(epgTag->EndAsLocalTime(), true);
+ return true;
+ case VIDEOPLAYER_ENDTIME:
+ case VIDEOPLAYER_NEXT_ENDTIME:
+ case LISTITEM_ENDTIME:
+ case LISTITEM_NEXT_ENDTIME:
+ strValue = GetAsLocalizedTimeString(epgTag->EndAsLocalTime());
+ return true;
+ // note: for some reason, there is no VIDEOPLAYER_DURATION
+ case LISTITEM_DURATION:
+ case VIDEOPLAYER_NEXT_DURATION:
+ case LISTITEM_NEXT_DURATION:
+ if (epgTag->GetDuration() > 0)
+ {
+ strValue = StringUtils::SecondsToTimeString(epgTag->GetDuration(),
+ static_cast<TIME_FORMAT>(info.GetData4()));
+ return true;
+ }
+ return false;
+ case VIDEOPLAYER_IMDBNUMBER:
+ case LISTITEM_IMDBNUMBER:
+ strValue = epgTag->IMDBNumber();
+ return true;
+ case VIDEOPLAYER_ORIGINALTITLE:
+ case LISTITEM_ORIGINALTITLE:
+ if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
+ strValue = epgTag->OriginalTitle();
+ return true;
+ case VIDEOPLAYER_YEAR:
+ case LISTITEM_YEAR:
+ if (epgTag->Year() > 0)
+ {
+ strValue = std::to_string(epgTag->Year());
+ return true;
+ }
+ return false;
+ case VIDEOPLAYER_SEASON:
+ case LISTITEM_SEASON:
+ if (epgTag->SeriesNumber() >= 0)
+ {
+ strValue = std::to_string(epgTag->SeriesNumber());
+ return true;
+ }
+ return false;
+ case VIDEOPLAYER_EPISODE:
+ case LISTITEM_EPISODE:
+ if (epgTag->EpisodeNumber() >= 0)
+ {
+ strValue = std::to_string(epgTag->EpisodeNumber());
+ return true;
+ }
+ return false;
+ case VIDEOPLAYER_EPISODENAME:
+ case LISTITEM_EPISODENAME:
+ if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
+ {
+ strValue = epgTag->EpisodeName();
+ // fixup multiline episode name strings (which do not fit in any way in our GUI)
+ StringUtils::Replace(strValue, "\n", ", ");
+ }
+ return true;
+ case VIDEOPLAYER_CAST:
+ case LISTITEM_CAST:
+ strValue = epgTag->GetCastLabel();
+ return true;
+ case VIDEOPLAYER_DIRECTOR:
+ case LISTITEM_DIRECTOR:
+ strValue = epgTag->GetDirectorsLabel();
+ return true;
+ case VIDEOPLAYER_WRITER:
+ case LISTITEM_WRITER:
+ strValue = epgTag->GetWritersLabel();
+ return true;
+ case LISTITEM_EPG_EVENT_ICON:
+ strValue = epgTag->IconPath();
+ return true;
+ case VIDEOPLAYER_PARENTAL_RATING:
+ case LISTITEM_PARENTAL_RATING:
+ if (epgTag->ParentalRating() > 0)
+ {
+ strValue = std::to_string(epgTag->ParentalRating());
+ return true;
+ }
+ return false;
+ case VIDEOPLAYER_PREMIERED:
+ case LISTITEM_PREMIERED:
+ if (epgTag->FirstAired().IsValid())
+ {
+ strValue = epgTag->FirstAired().GetAsLocalizedDate();
+ return true;
+ }
+ else if (epgTag->Year() > 0)
+ {
+ strValue = std::to_string(epgTag->Year());
+ return true;
+ }
+ return false;
+ case VIDEOPLAYER_RATING:
+ case LISTITEM_RATING:
+ {
+ int iStarRating = epgTag->StarRating();
+ if (iStarRating > 0)
+ {
+ strValue = StringUtils::FormatNumber(iStarRating);
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+
+ if (channel)
+ {
+ switch (info.m_info)
+ {
+ case MUSICPLAYER_CHANNEL_NAME:
+ {
+ const std::shared_ptr<CPVRRadioRDSInfoTag> rdsTag = channel->GetRadioRDSInfoTag();
+ if (rdsTag)
+ {
+ strValue = rdsTag->GetProgStation();
+ if (!strValue.empty())
+ return true;
+ }
+ // fall-thru is intended
+ [[fallthrough]];
+ }
+ case VIDEOPLAYER_CHANNEL_NAME:
+ case LISTITEM_CHANNEL_NAME:
+ strValue = channel->ChannelName();
+ return true;
+ case MUSICPLAYER_CHANNEL_NUMBER:
+ case VIDEOPLAYER_CHANNEL_NUMBER:
+ case LISTITEM_CHANNEL_NUMBER:
+ {
+ auto groupMember = item->GetPVRChannelGroupMemberInfoTag();
+ if (!groupMember)
+ groupMember =
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(
+ *item);
+ if (groupMember)
+ {
+ strValue = groupMember->ChannelNumber().FormattedChannelNumber();
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_CHANNEL_GROUP:
+ case VIDEOPLAYER_CHANNEL_GROUP:
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ strValue = channel->IsRadio() ? m_strPlayingRadioGroup : m_strPlayingTVGroup;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CPVRGUIInfo::GetPVRLabel(const CFileItem* item,
+ const CGUIInfo& info,
+ std::string& strValue) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ switch (info.m_info)
+ {
+ case PVR_EPG_EVENT_ICON:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ if (epgTag)
+ {
+ strValue = epgTag->IconPath();
+ }
+ return true;
+ }
+ case PVR_EPG_EVENT_DURATION:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ strValue = m_timesInfo.GetEpgEventDuration(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PVR_EPG_EVENT_ELAPSED_TIME:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ strValue =
+ m_timesInfo.GetEpgEventElapsedTime(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PVR_EPG_EVENT_REMAINING_TIME:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ strValue =
+ m_timesInfo.GetEpgEventRemainingTime(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PVR_EPG_EVENT_FINISH_TIME:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ strValue =
+ m_timesInfo.GetEpgEventFinishTime(epgTag, static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PVR_TIMESHIFT_START_TIME:
+ strValue = m_timesInfo.GetTimeshiftStartTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_TIMESHIFT_END_TIME:
+ strValue = m_timesInfo.GetTimeshiftEndTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_TIMESHIFT_PLAY_TIME:
+ strValue = m_timesInfo.GetTimeshiftPlayTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_TIMESHIFT_OFFSET:
+ strValue = m_timesInfo.GetTimeshiftOffset(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_DURATION:
+ strValue =
+ m_timesInfo.GetTimeshiftProgressDuration(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_START_TIME:
+ strValue =
+ m_timesInfo.GetTimeshiftProgressStartTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_END_TIME:
+ strValue = m_timesInfo.GetTimeshiftProgressEndTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PVR_EPG_EVENT_SEEK_TIME:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ strValue = m_timesInfo.GetEpgEventSeekTime(appPlayer->GetSeekHandler().GetSeekSize(),
+ static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PVR_NOW_RECORDING_TITLE:
+ strValue = m_anyTimersInfo.GetActiveTimerTitle();
+ return true;
+ case PVR_NOW_RECORDING_CHANNEL:
+ strValue = m_anyTimersInfo.GetActiveTimerChannelName();
+ return true;
+ case PVR_NOW_RECORDING_CHAN_ICO:
+ strValue = m_anyTimersInfo.GetActiveTimerChannelIcon();
+ return true;
+ case PVR_NOW_RECORDING_DATETIME:
+ strValue = m_anyTimersInfo.GetActiveTimerDateTime();
+ return true;
+ case PVR_NEXT_RECORDING_TITLE:
+ strValue = m_anyTimersInfo.GetNextTimerTitle();
+ return true;
+ case PVR_NEXT_RECORDING_CHANNEL:
+ strValue = m_anyTimersInfo.GetNextTimerChannelName();
+ return true;
+ case PVR_NEXT_RECORDING_CHAN_ICO:
+ strValue = m_anyTimersInfo.GetNextTimerChannelIcon();
+ return true;
+ case PVR_NEXT_RECORDING_DATETIME:
+ strValue = m_anyTimersInfo.GetNextTimerDateTime();
+ return true;
+ case PVR_TV_NOW_RECORDING_TITLE:
+ strValue = m_tvTimersInfo.GetActiveTimerTitle();
+ return true;
+ case PVR_TV_NOW_RECORDING_CHANNEL:
+ strValue = m_tvTimersInfo.GetActiveTimerChannelName();
+ return true;
+ case PVR_TV_NOW_RECORDING_CHAN_ICO:
+ strValue = m_tvTimersInfo.GetActiveTimerChannelIcon();
+ return true;
+ case PVR_TV_NOW_RECORDING_DATETIME:
+ strValue = m_tvTimersInfo.GetActiveTimerDateTime();
+ return true;
+ case PVR_TV_NEXT_RECORDING_TITLE:
+ strValue = m_tvTimersInfo.GetNextTimerTitle();
+ return true;
+ case PVR_TV_NEXT_RECORDING_CHANNEL:
+ strValue = m_tvTimersInfo.GetNextTimerChannelName();
+ return true;
+ case PVR_TV_NEXT_RECORDING_CHAN_ICO:
+ strValue = m_tvTimersInfo.GetNextTimerChannelIcon();
+ return true;
+ case PVR_TV_NEXT_RECORDING_DATETIME:
+ strValue = m_tvTimersInfo.GetNextTimerDateTime();
+ return true;
+ case PVR_RADIO_NOW_RECORDING_TITLE:
+ strValue = m_radioTimersInfo.GetActiveTimerTitle();
+ return true;
+ case PVR_RADIO_NOW_RECORDING_CHANNEL:
+ strValue = m_radioTimersInfo.GetActiveTimerChannelName();
+ return true;
+ case PVR_RADIO_NOW_RECORDING_CHAN_ICO:
+ strValue = m_radioTimersInfo.GetActiveTimerChannelIcon();
+ return true;
+ case PVR_RADIO_NOW_RECORDING_DATETIME:
+ strValue = m_radioTimersInfo.GetActiveTimerDateTime();
+ return true;
+ case PVR_RADIO_NEXT_RECORDING_TITLE:
+ strValue = m_radioTimersInfo.GetNextTimerTitle();
+ return true;
+ case PVR_RADIO_NEXT_RECORDING_CHANNEL:
+ strValue = m_radioTimersInfo.GetNextTimerChannelName();
+ return true;
+ case PVR_RADIO_NEXT_RECORDING_CHAN_ICO:
+ strValue = m_radioTimersInfo.GetNextTimerChannelIcon();
+ return true;
+ case PVR_RADIO_NEXT_RECORDING_DATETIME:
+ strValue = m_radioTimersInfo.GetNextTimerDateTime();
+ return true;
+ case PVR_NEXT_TIMER:
+ strValue = m_anyTimersInfo.GetNextTimer();
+ return true;
+ case PVR_ACTUAL_STREAM_SIG:
+ CharInfoSignal(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_SNR:
+ CharInfoSNR(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_BER:
+ CharInfoBER(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_UNC:
+ CharInfoUNC(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_CLIENT:
+ CharInfoPlayingClientName(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_DEVICE:
+ CharInfoFrontendName(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_STATUS:
+ CharInfoFrontendStatus(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_CRYPTION:
+ CharInfoEncryption(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_SERVICE:
+ CharInfoService(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_MUX:
+ CharInfoMux(strValue);
+ return true;
+ case PVR_ACTUAL_STREAM_PROVIDER:
+ CharInfoProvider(strValue);
+ return true;
+ case PVR_BACKEND_NAME:
+ CharInfoBackendName(strValue);
+ return true;
+ case PVR_BACKEND_VERSION:
+ CharInfoBackendVersion(strValue);
+ return true;
+ case PVR_BACKEND_HOST:
+ CharInfoBackendHost(strValue);
+ return true;
+ case PVR_BACKEND_DISKSPACE:
+ CharInfoBackendDiskspace(strValue);
+ return true;
+ case PVR_BACKEND_PROVIDERS:
+ CharInfoBackendProviders(strValue);
+ return true;
+ case PVR_BACKEND_CHANNEL_GROUPS:
+ CharInfoBackendChannelGroups(strValue);
+ return true;
+ case PVR_BACKEND_CHANNELS:
+ CharInfoBackendChannels(strValue);
+ return true;
+ case PVR_BACKEND_TIMERS:
+ CharInfoBackendTimers(strValue);
+ return true;
+ case PVR_BACKEND_RECORDINGS:
+ CharInfoBackendRecordings(strValue);
+ return true;
+ case PVR_BACKEND_DELETED_RECORDINGS:
+ CharInfoBackendDeletedRecordings(strValue);
+ return true;
+ case PVR_BACKEND_NUMBER:
+ CharInfoBackendNumber(strValue);
+ return true;
+ case PVR_TOTAL_DISKSPACE:
+ CharInfoTotalDiskSpace(strValue);
+ return true;
+ case PVR_CHANNEL_NUMBER_INPUT:
+ strValue = m_channelNumberInput;
+ return true;
+ }
+
+ return false;
+}
+
+bool CPVRGUIInfo::GetRadioRDSLabel(const CFileItem* item,
+ const CGUIInfo& info,
+ std::string& strValue) const
+{
+ if (!item->HasPVRChannelInfoTag())
+ return false;
+
+ const std::shared_ptr<CPVRRadioRDSInfoTag> tag =
+ item->GetPVRChannelInfoTag()->GetRadioRDSInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ case RDS_CHANNEL_COUNTRY:
+ strValue = tag->GetCountry();
+ return true;
+ case RDS_TITLE:
+ strValue = tag->GetTitle();
+ return true;
+ case RDS_ARTIST:
+ strValue = tag->GetArtist();
+ return true;
+ case RDS_BAND:
+ strValue = tag->GetBand();
+ return true;
+ case RDS_COMPOSER:
+ strValue = tag->GetComposer();
+ return true;
+ case RDS_CONDUCTOR:
+ strValue = tag->GetConductor();
+ return true;
+ case RDS_ALBUM:
+ strValue = tag->GetAlbum();
+ return true;
+ case RDS_ALBUM_TRACKNUMBER:
+ if (tag->GetAlbumTrackNumber() > 0)
+ {
+ strValue = std::to_string(tag->GetAlbumTrackNumber());
+ return true;
+ }
+ break;
+ case RDS_GET_RADIO_STYLE:
+ strValue = tag->GetRadioStyle();
+ return true;
+ case RDS_COMMENT:
+ strValue = tag->GetComment();
+ return true;
+ case RDS_INFO_NEWS:
+ strValue = tag->GetInfoNews();
+ return true;
+ case RDS_INFO_NEWS_LOCAL:
+ strValue = tag->GetInfoNewsLocal();
+ return true;
+ case RDS_INFO_STOCK:
+ strValue = tag->GetInfoStock();
+ return true;
+ case RDS_INFO_STOCK_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoStock().size()));
+ return true;
+ case RDS_INFO_SPORT:
+ strValue = tag->GetInfoSport();
+ return true;
+ case RDS_INFO_SPORT_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoSport().size()));
+ return true;
+ case RDS_INFO_LOTTERY:
+ strValue = tag->GetInfoLottery();
+ return true;
+ case RDS_INFO_LOTTERY_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoLottery().size()));
+ return true;
+ case RDS_INFO_WEATHER:
+ strValue = tag->GetInfoWeather();
+ return true;
+ case RDS_INFO_WEATHER_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoWeather().size()));
+ return true;
+ case RDS_INFO_HOROSCOPE:
+ strValue = tag->GetInfoHoroscope();
+ return true;
+ case RDS_INFO_HOROSCOPE_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoHoroscope().size()));
+ return true;
+ case RDS_INFO_CINEMA:
+ strValue = tag->GetInfoCinema();
+ return true;
+ case RDS_INFO_CINEMA_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoCinema().size()));
+ return true;
+ case RDS_INFO_OTHER:
+ strValue = tag->GetInfoOther();
+ return true;
+ case RDS_INFO_OTHER_SIZE:
+ strValue = std::to_string(static_cast<int>(tag->GetInfoOther().size()));
+ return true;
+ case RDS_PROG_HOST:
+ strValue = tag->GetProgHost();
+ return true;
+ case RDS_PROG_EDIT_STAFF:
+ strValue = tag->GetEditorialStaff();
+ return true;
+ case RDS_PROG_HOMEPAGE:
+ strValue = tag->GetProgWebsite();
+ return true;
+ case RDS_PROG_STYLE:
+ strValue = tag->GetProgStyle();
+ return true;
+ case RDS_PHONE_HOTLINE:
+ strValue = tag->GetPhoneHotline();
+ return true;
+ case RDS_PHONE_STUDIO:
+ strValue = tag->GetPhoneStudio();
+ return true;
+ case RDS_SMS_STUDIO:
+ strValue = tag->GetSMSStudio();
+ return true;
+ case RDS_EMAIL_HOTLINE:
+ strValue = tag->GetEMailHotline();
+ return true;
+ case RDS_EMAIL_STUDIO:
+ strValue = tag->GetEMailStudio();
+ return true;
+ case RDS_PROG_STATION:
+ strValue = tag->GetProgStation();
+ return true;
+ case RDS_PROG_NOW:
+ strValue = tag->GetProgNow();
+ return true;
+ case RDS_PROG_NEXT:
+ strValue = tag->GetProgNext();
+ return true;
+ case RDS_AUDIO_LANG:
+ strValue = tag->GetLanguage();
+ return true;
+ case RDS_GET_RADIOTEXT_LINE:
+ strValue = tag->GetRadioText(info.GetData1());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CPVRGUIInfo::GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback)
+{
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*, MUSICPLAYER_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_TITLE:
+ case MUSICPLAYER_TITLE:
+ value = GetEpgTagTitle(CPVRItem(item).GetEpgInfoTag());
+ return !value.empty();
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+bool CPVRGUIInfo::GetInt(int& value,
+ const CGUIListItem* item,
+ int contextWindow,
+ const CGUIInfo& info) const
+{
+ if (!item->IsFileItem())
+ return false;
+
+ const CFileItem* fitem = static_cast<const CFileItem*>(item);
+ return GetListItemAndPlayerInt(fitem, info, value) || GetPVRInt(fitem, info, value);
+}
+
+bool CPVRGUIInfo::GetListItemAndPlayerInt(const CFileItem* item,
+ const CGUIInfo& info,
+ int& iValue) const
+{
+ switch (info.m_info)
+ {
+ case LISTITEM_PROGRESS:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (epgTag)
+ iValue = static_cast<int>(epgTag->ProgressPercentage());
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CPVRGUIInfo::GetPVRInt(const CFileItem* item, const CGUIInfo& info, int& iValue) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ switch (info.m_info)
+ {
+ case PVR_EPG_EVENT_DURATION:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ iValue = m_timesInfo.GetEpgEventDuration(epgTag);
+ return true;
+ }
+ case PVR_EPG_EVENT_PROGRESS:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ (item->IsPVRChannel() || item->IsEPG()) ? CPVRItem(item).GetEpgInfoTag() : nullptr;
+ iValue = m_timesInfo.GetEpgEventProgress(epgTag);
+ return true;
+ }
+ case PVR_TIMESHIFT_PROGRESS:
+ iValue = m_timesInfo.GetTimeshiftProgress();
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_DURATION:
+ iValue = m_timesInfo.GetTimeshiftProgressDuration();
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_PLAY_POS:
+ iValue = m_timesInfo.GetTimeshiftProgressPlayPosition();
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_EPG_START:
+ iValue = m_timesInfo.GetTimeshiftProgressEpgStart();
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_EPG_END:
+ iValue = m_timesInfo.GetTimeshiftProgressEpgEnd();
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_BUFFER_START:
+ iValue = m_timesInfo.GetTimeshiftProgressBufferStart();
+ return true;
+ case PVR_TIMESHIFT_PROGRESS_BUFFER_END:
+ iValue = m_timesInfo.GetTimeshiftProgressBufferEnd();
+ return true;
+ case PVR_TIMESHIFT_SEEKBAR:
+ iValue = GetTimeShiftSeekPercent();
+ return true;
+ case PVR_ACTUAL_STREAM_SIG_PROGR:
+ iValue = std::lrintf(static_cast<float>(m_qualityInfo.iSignal) / 0xFFFF * 100);
+ return true;
+ case PVR_ACTUAL_STREAM_SNR_PROGR:
+ iValue = std::lrintf(static_cast<float>(m_qualityInfo.iSNR) / 0xFFFF * 100);
+ return true;
+ case PVR_BACKEND_DISKSPACE_PROGR:
+ if (m_iBackendDiskTotal > 0)
+ iValue = std::lrintf(static_cast<float>(m_iBackendDiskUsed) / m_iBackendDiskTotal * 100);
+ else
+ iValue = 0xFF;
+ return true;
+ }
+ return false;
+}
+
+bool CPVRGUIInfo::GetBool(bool& value,
+ const CGUIListItem* item,
+ int contextWindow,
+ const CGUIInfo& info) const
+{
+ if (!item->IsFileItem())
+ return false;
+
+ const CFileItem* fitem = static_cast<const CFileItem*>(item);
+ return GetListItemAndPlayerBool(fitem, info, value) || GetPVRBool(fitem, info, value) ||
+ GetRadioRDSBool(fitem, info, value);
+}
+
+bool CPVRGUIInfo::GetListItemAndPlayerBool(const CFileItem* item,
+ const CGUIInfo& info,
+ bool& bValue) const
+{
+ switch (info.m_info)
+ {
+ case LISTITEM_HASARCHIVE:
+ if (item->IsPVRChannel())
+ {
+ bValue = item->GetPVRChannelInfoTag()->HasArchive();
+ return true;
+ }
+ break;
+ case LISTITEM_ISPLAYABLE:
+ if (item->IsEPG())
+ {
+ bValue = item->GetEPGInfoTag()->IsPlayable();
+ return true;
+ }
+ break;
+ case LISTITEM_ISRECORDING:
+ if (item->IsPVRChannel())
+ {
+ bValue = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(
+ *item->GetPVRChannelInfoTag());
+ return true;
+ }
+ else if (item->IsEPG() || item->IsPVRTimer())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = timer->IsRecording();
+ return true;
+ }
+ else if (item->IsPVRRecording())
+ {
+ bValue = item->GetPVRRecordingInfoTag()->IsInProgress();
+ return true;
+ }
+ break;
+ case LISTITEM_INPROGRESS:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (epgTag)
+ bValue = epgTag->IsActive();
+ return true;
+ }
+ break;
+ case LISTITEM_HASTIMER:
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = true;
+ return true;
+ }
+ break;
+ case LISTITEM_HASTIMERSCHEDULE:
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = timer->HasParent();
+ return true;
+ }
+ break;
+ case LISTITEM_HASREMINDER:
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = timer->IsReminder();
+ return true;
+ }
+ break;
+ case LISTITEM_HASREMINDERRULE:
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = timer->IsReminder() && timer->HasParent();
+ return true;
+ }
+ break;
+ case LISTITEM_TIMERISACTIVE:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = timer->IsActive();
+ break;
+ }
+ break;
+ case LISTITEM_TIMERHASCONFLICT:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = timer->HasConflict();
+ return true;
+ }
+ break;
+ case LISTITEM_TIMERHASERROR:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> timer = CPVRItem(item).GetTimerInfoTag();
+ if (timer)
+ bValue = (timer->IsBroken() && !timer->HasConflict());
+ return true;
+ }
+ break;
+ case LISTITEM_HASRECORDING:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ if (epgTag)
+ bValue = !!CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(epgTag);
+ return true;
+ }
+ break;
+ case LISTITEM_HAS_EPG:
+ if (item->IsPVRChannel() || item->IsEPG() || item->IsPVRTimer())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = CPVRItem(item).GetEpgInfoTag();
+ bValue = (epgTag != nullptr);
+ return true;
+ }
+ break;
+ case LISTITEM_ISENCRYPTED:
+ if (item->IsPVRChannel() || item->IsEPG())
+ {
+ const std::shared_ptr<CPVRChannel> channel = CPVRItem(item).GetChannel();
+ if (channel)
+ bValue = channel->IsEncrypted();
+ return true;
+ }
+ break;
+ case LISTITEM_IS_NEW:
+ if (item->IsEPG())
+ {
+ if (item->GetEPGInfoTag())
+ {
+ bValue = item->GetEPGInfoTag()->IsNew();
+ return true;
+ }
+ }
+ else if (item->IsPVRRecording())
+ {
+ bValue = item->GetPVRRecordingInfoTag()->IsNew();
+ return true;
+ }
+ else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
+ {
+ bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsNew();
+ return true;
+ }
+ else if (item->IsPVRChannel())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
+ bValue = epgNow ? epgNow->IsNew() : false;
+ return true;
+ }
+ break;
+ case LISTITEM_IS_PREMIERE:
+ if (item->IsEPG())
+ {
+ bValue = item->GetEPGInfoTag()->IsPremiere();
+ return true;
+ }
+ else if (item->IsPVRRecording())
+ {
+ bValue = item->GetPVRRecordingInfoTag()->IsPremiere();
+ return true;
+ }
+ else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
+ {
+ bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsPremiere();
+ return true;
+ }
+ else if (item->IsPVRChannel())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
+ bValue = epgNow ? epgNow->IsPremiere() : false;
+ return true;
+ }
+ break;
+ case LISTITEM_IS_FINALE:
+ if (item->IsEPG())
+ {
+ bValue = item->GetEPGInfoTag()->IsFinale();
+ return true;
+ }
+ else if (item->IsPVRRecording())
+ {
+ bValue = item->GetPVRRecordingInfoTag()->IsFinale();
+ return true;
+ }
+ else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
+ {
+ bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsFinale();
+ return true;
+ }
+ else if (item->IsPVRChannel())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
+ bValue = epgNow ? epgNow->IsFinale() : false;
+ return true;
+ }
+ break;
+ case LISTITEM_IS_LIVE:
+ if (item->IsEPG())
+ {
+ bValue = item->GetEPGInfoTag()->IsLive();
+ return true;
+ }
+ else if (item->IsPVRRecording())
+ {
+ bValue = item->GetPVRRecordingInfoTag()->IsLive();
+ return true;
+ }
+ else if (item->IsPVRTimer() && item->GetPVRTimerInfoTag()->GetEpgInfoTag())
+ {
+ bValue = item->GetPVRTimerInfoTag()->GetEpgInfoTag()->IsLive();
+ return true;
+ }
+ else if (item->IsPVRChannel())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgNow = item->GetPVRChannelInfoTag()->GetEPGNow();
+ bValue = epgNow ? epgNow->IsLive() : false;
+ return true;
+ }
+ break;
+ case MUSICPLAYER_CONTENT:
+ case VIDEOPLAYER_CONTENT:
+ if (item->IsPVRChannel())
+ {
+ bValue = StringUtils::EqualsNoCase(info.GetData3(), "livetv");
+ return bValue; // if no match for this provider, other providers shall be asked.
+ }
+ break;
+ case VIDEOPLAYER_HAS_INFO:
+ if (item->IsPVRChannel())
+ {
+ bValue = !item->GetPVRChannelInfoTag()->ChannelName().empty();
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_HAS_EPG:
+ if (item->IsPVRChannel())
+ {
+ bValue = (item->GetPVRChannelInfoTag()->GetEPGNow() != nullptr);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_CAN_RESUME_LIVE_TV:
+ if (item->IsPVRRecording())
+ {
+ const std::shared_ptr<CPVRRecording> recording = item->GetPVRRecordingInfoTag();
+ const std::shared_ptr<CPVREpg> epg =
+ recording->Channel() ? recording->Channel()->GetEPG() : nullptr;
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ CServiceBroker::GetPVRManager().EpgContainer().GetTagById(epg,
+ recording->BroadcastUid());
+ bValue = (epgTag && epgTag->IsActive());
+ return true;
+ }
+ break;
+ case PLAYER_IS_CHANNEL_PREVIEW_ACTIVE:
+ if (item->IsPVRChannel())
+ {
+ if (m_previewAndPlayerShowInfo)
+ {
+ bValue = true;
+ }
+ else
+ {
+ bValue = !m_videoInfo.valid;
+ if (bValue && item->GetPVRChannelInfoTag()->IsRadio())
+ bValue = !m_audioInfo.valid;
+ }
+ return true;
+ }
+ break;
+ }
+ return false;
+}
+
+bool CPVRGUIInfo::GetPVRBool(const CFileItem* item, const CGUIInfo& info, bool& bValue) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ switch (info.m_info)
+ {
+ case PVR_IS_RECORDING:
+ bValue = m_anyTimersInfo.HasRecordingTimers();
+ return true;
+ case PVR_IS_RECORDING_TV:
+ bValue = m_tvTimersInfo.HasRecordingTimers();
+ return true;
+ case PVR_IS_RECORDING_RADIO:
+ bValue = m_radioTimersInfo.HasRecordingTimers();
+ return true;
+ case PVR_HAS_TIMER:
+ bValue = m_anyTimersInfo.HasTimers();
+ return true;
+ case PVR_HAS_TV_TIMER:
+ bValue = m_tvTimersInfo.HasTimers();
+ return true;
+ case PVR_HAS_RADIO_TIMER:
+ bValue = m_radioTimersInfo.HasTimers();
+ return true;
+ case PVR_HAS_TV_CHANNELS:
+ bValue = m_bHasTVChannels;
+ return true;
+ case PVR_HAS_RADIO_CHANNELS:
+ bValue = m_bHasRadioChannels;
+ return true;
+ case PVR_HAS_NONRECORDING_TIMER:
+ bValue = m_anyTimersInfo.HasNonRecordingTimers();
+ return true;
+ case PVR_HAS_NONRECORDING_TV_TIMER:
+ bValue = m_tvTimersInfo.HasNonRecordingTimers();
+ return true;
+ case PVR_HAS_NONRECORDING_RADIO_TIMER:
+ bValue = m_radioTimersInfo.HasNonRecordingTimers();
+ return true;
+ case PVR_IS_PLAYING_TV:
+ bValue = m_bIsPlayingTV;
+ return true;
+ case PVR_IS_PLAYING_RADIO:
+ bValue = m_bIsPlayingRadio;
+ return true;
+ case PVR_IS_PLAYING_RECORDING:
+ bValue = m_bIsPlayingRecording;
+ return true;
+ case PVR_IS_PLAYING_EPGTAG:
+ bValue = m_bIsPlayingEpgTag;
+ return true;
+ case PVR_ACTUAL_STREAM_ENCRYPTED:
+ bValue = m_bIsPlayingEncryptedStream;
+ return true;
+ case PVR_IS_TIMESHIFTING:
+ bValue = m_timesInfo.IsTimeshifting();
+ return true;
+ case PVR_CAN_RECORD_PLAYING_CHANNEL:
+ bValue = m_bCanRecordPlayingChannel;
+ return true;
+ case PVR_IS_RECORDING_PLAYING_CHANNEL:
+ bValue = m_bIsRecordingPlayingChannel;
+ return true;
+ case PVR_IS_PLAYING_ACTIVE_RECORDING:
+ bValue = m_bIsPlayingActiveRecording;
+ return true;
+ }
+ return false;
+}
+
+bool CPVRGUIInfo::GetRadioRDSBool(const CFileItem* item, const CGUIInfo& info, bool& bValue) const
+{
+ if (!item->HasPVRChannelInfoTag())
+ return false;
+
+ const std::shared_ptr<CPVRRadioRDSInfoTag> tag =
+ item->GetPVRChannelInfoTag()->GetRadioRDSInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ case RDS_HAS_RADIOTEXT:
+ bValue = tag->IsPlayingRadioText();
+ return true;
+ case RDS_HAS_RADIOTEXT_PLUS:
+ bValue = tag->IsPlayingRadioTextPlus();
+ return true;
+ case RDS_HAS_HOTLINE_DATA:
+ bValue = (!tag->GetEMailHotline().empty() || !tag->GetPhoneHotline().empty());
+ return true;
+ case RDS_HAS_STUDIO_DATA:
+ bValue = (!tag->GetEMailStudio().empty() || !tag->GetSMSStudio().empty() ||
+ !tag->GetPhoneStudio().empty());
+ return true;
+ }
+ }
+
+ switch (info.m_info)
+ {
+ case RDS_HAS_RDS:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ bValue = appPlayer->IsPlayingRDS();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CPVRGUIInfo::CharInfoBackendNumber(std::string& strValue) const
+{
+ size_t numBackends = m_backendProperties.size();
+
+ if (numBackends > 0)
+ strValue = StringUtils::Format("{0} {1} {2}", m_iCurrentActiveClient + 1,
+ g_localizeStrings.Get(20163), numBackends);
+ else
+ strValue = g_localizeStrings.Get(14023);
+}
+
+void CPVRGUIInfo::CharInfoTotalDiskSpace(std::string& strValue) const
+{
+ strValue = StringUtils::SizeToString(m_iBackendDiskTotal).c_str();
+}
+
+void CPVRGUIInfo::CharInfoSignal(std::string& strValue) const
+{
+ strValue = StringUtils::Format("{} %", m_qualityInfo.iSignal / 655);
+}
+
+void CPVRGUIInfo::CharInfoSNR(std::string& strValue) const
+{
+ strValue = StringUtils::Format("{} %", m_qualityInfo.iSNR / 655);
+}
+
+void CPVRGUIInfo::CharInfoBER(std::string& strValue) const
+{
+ strValue = StringUtils::Format("{:08X}", m_qualityInfo.iBER);
+}
+
+void CPVRGUIInfo::CharInfoUNC(std::string& strValue) const
+{
+ strValue = StringUtils::Format("{:08X}", m_qualityInfo.iUNC);
+}
+
+void CPVRGUIInfo::CharInfoFrontendName(std::string& strValue) const
+{
+ if (!strlen(m_qualityInfo.strAdapterName))
+ strValue = g_localizeStrings.Get(13205);
+ else
+ strValue = m_qualityInfo.strAdapterName;
+}
+
+void CPVRGUIInfo::CharInfoFrontendStatus(std::string& strValue) const
+{
+ if (!strlen(m_qualityInfo.strAdapterStatus))
+ strValue = g_localizeStrings.Get(13205);
+ else
+ strValue = m_qualityInfo.strAdapterStatus;
+}
+
+void CPVRGUIInfo::CharInfoBackendName(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendName;
+}
+
+void CPVRGUIInfo::CharInfoBackendVersion(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendVersion;
+}
+
+void CPVRGUIInfo::CharInfoBackendHost(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendHost;
+}
+
+void CPVRGUIInfo::CharInfoBackendDiskspace(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+
+ auto diskTotal = m_iBackendDiskTotal;
+ auto diskUsed = m_iBackendDiskUsed;
+
+ if (diskTotal > 0)
+ {
+ strValue = StringUtils::Format(g_localizeStrings.Get(802),
+ StringUtils::SizeToString(diskTotal - diskUsed),
+ StringUtils::SizeToString(diskTotal));
+ }
+ else
+ strValue = g_localizeStrings.Get(13205);
+}
+
+void CPVRGUIInfo::CharInfoBackendProviders(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendProviders;
+}
+
+void CPVRGUIInfo::CharInfoBackendChannelGroups(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendChannelGroups;
+}
+
+void CPVRGUIInfo::CharInfoBackendChannels(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendChannels;
+}
+
+void CPVRGUIInfo::CharInfoBackendTimers(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendTimers;
+}
+
+void CPVRGUIInfo::CharInfoBackendRecordings(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendRecordings;
+}
+
+void CPVRGUIInfo::CharInfoBackendDeletedRecordings(std::string& strValue) const
+{
+ m_updateBackendCacheRequested = true;
+ strValue = m_strBackendDeletedRecordings;
+}
+
+void CPVRGUIInfo::CharInfoPlayingClientName(std::string& strValue) const
+{
+ if (m_strPlayingClientName.empty())
+ strValue = g_localizeStrings.Get(13205);
+ else
+ strValue = m_strPlayingClientName;
+}
+
+void CPVRGUIInfo::CharInfoEncryption(std::string& strValue) const
+{
+ if (m_descrambleInfo.iCaid != PVR_DESCRAMBLE_INFO_NOT_AVAILABLE)
+ {
+ // prefer dynamically updated info, if available
+ strValue = CPVRChannel::GetEncryptionName(m_descrambleInfo.iCaid);
+ return;
+ }
+ else
+ {
+ const std::shared_ptr<CPVRChannel> channel =
+ CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ if (channel)
+ {
+ strValue = channel->EncryptionName();
+ return;
+ }
+ }
+
+ strValue.clear();
+}
+
+void CPVRGUIInfo::CharInfoService(std::string& strValue) const
+{
+ if (!strlen(m_qualityInfo.strServiceName))
+ strValue = g_localizeStrings.Get(13205);
+ else
+ strValue = m_qualityInfo.strServiceName;
+}
+
+void CPVRGUIInfo::CharInfoMux(std::string& strValue) const
+{
+ if (!strlen(m_qualityInfo.strMuxName))
+ strValue = g_localizeStrings.Get(13205);
+ else
+ strValue = m_qualityInfo.strMuxName;
+}
+
+void CPVRGUIInfo::CharInfoProvider(std::string& strValue) const
+{
+ if (!strlen(m_qualityInfo.strProviderName))
+ strValue = g_localizeStrings.Get(13205);
+ else
+ strValue = m_qualityInfo.strProviderName;
+}
+
+void CPVRGUIInfo::UpdateBackendCache()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // Update the backend information for all backends if
+ // an update has been requested
+ if (m_iCurrentActiveClient == 0 && m_updateBackendCacheRequested)
+ {
+ std::vector<SBackend> backendProperties;
+ {
+ CSingleExit exit(m_critSection);
+ backendProperties = CServiceBroker::GetPVRManager().Clients()->GetBackendProperties();
+ }
+
+ m_backendProperties = backendProperties;
+ m_updateBackendCacheRequested = false;
+ }
+
+ // Store some defaults
+ m_strBackendName = g_localizeStrings.Get(13205);
+ m_strBackendVersion = g_localizeStrings.Get(13205);
+ m_strBackendHost = g_localizeStrings.Get(13205);
+ m_strBackendProviders = g_localizeStrings.Get(13205);
+ m_strBackendChannelGroups = g_localizeStrings.Get(13205);
+ m_strBackendChannels = g_localizeStrings.Get(13205);
+ m_strBackendTimers = g_localizeStrings.Get(13205);
+ m_strBackendRecordings = g_localizeStrings.Get(13205);
+ m_strBackendDeletedRecordings = g_localizeStrings.Get(13205);
+ m_iBackendDiskTotal = 0;
+ m_iBackendDiskUsed = 0;
+
+ // Update with values from the current client when we have at least one
+ if (!m_backendProperties.empty())
+ {
+ const auto& backend = m_backendProperties[m_iCurrentActiveClient];
+
+ m_strBackendName = backend.name;
+ m_strBackendVersion = backend.version;
+ m_strBackendHost = backend.host;
+
+ // We always display one extra as the add-on itself counts as a provider
+ if (backend.numProviders >= 0)
+ m_strBackendProviders = std::to_string(backend.numProviders + 1);
+
+ if (backend.numChannelGroups >= 0)
+ m_strBackendChannelGroups = std::to_string(backend.numChannelGroups);
+
+ if (backend.numChannels >= 0)
+ m_strBackendChannels = std::to_string(backend.numChannels);
+
+ if (backend.numTimers >= 0)
+ m_strBackendTimers = std::to_string(backend.numTimers);
+
+ if (backend.numRecordings >= 0)
+ m_strBackendRecordings = std::to_string(backend.numRecordings);
+
+ if (backend.numDeletedRecordings >= 0)
+ m_strBackendDeletedRecordings = std::to_string(backend.numDeletedRecordings);
+
+ m_iBackendDiskTotal = backend.diskTotal;
+ m_iBackendDiskUsed = backend.diskUsed;
+ }
+
+ // Update the current active client, eventually wrapping around
+ if (++m_iCurrentActiveClient >= m_backendProperties.size())
+ m_iCurrentActiveClient = 0;
+}
+
+void CPVRGUIInfo::UpdateTimersCache()
+{
+ m_anyTimersInfo.UpdateTimersCache();
+ m_tvTimersInfo.UpdateTimersCache();
+ m_radioTimersInfo.UpdateTimersCache();
+}
+
+void CPVRGUIInfo::UpdateTimersToggle()
+{
+ m_anyTimersInfo.UpdateTimersToggle();
+ m_tvTimersInfo.UpdateTimersToggle();
+ m_radioTimersInfo.UpdateTimersToggle();
+}
+
+void CPVRGUIInfo::UpdateNextTimer()
+{
+ m_anyTimersInfo.UpdateNextTimer();
+ m_tvTimersInfo.UpdateNextTimer();
+ m_radioTimersInfo.UpdateNextTimer();
+}
+
+int CPVRGUIInfo::GetTimeShiftSeekPercent() const
+{
+ int progress = m_timesInfo.GetTimeshiftProgressPlayPosition();
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ int seekSize = appPlayer->GetSeekHandler().GetSeekSize();
+ if (seekSize != 0)
+ {
+ int total = m_timesInfo.GetTimeshiftProgressDuration();
+
+ float totalTime = static_cast<float>(total);
+ if (totalTime == 0.0f)
+ return 0;
+
+ float percentPerSecond = 100.0f / totalTime;
+ float percent = progress + percentPerSecond * seekSize;
+ percent = std::max(0.0f, std::min(percent, 100.0f));
+ return std::lrintf(percent);
+ }
+ return progress;
+}
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h
new file mode 100644
index 0000000..5591353
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.h
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h"
+#include "guilib/guiinfo/GUIInfoProvider.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/guilib/guiinfo/PVRGUITimerInfo.h"
+#include "pvr/guilib/guiinfo/PVRGUITimesInfo.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+#include <string>
+#include <vector>
+
+class CFileItem;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+class CGUIInfo;
+}
+} // namespace GUILIB
+} // namespace KODI
+
+namespace PVR
+{
+enum class PVREvent;
+struct PVRChannelNumberInputChangedEvent;
+struct PVRPreviewAndPlayerShowInfoChangedEvent;
+
+class CPVRGUIInfo : public KODI::GUILIB::GUIINFO::CGUIInfoProvider, private CThread
+{
+public:
+ CPVRGUIInfo();
+ ~CPVRGUIInfo() override = default;
+
+ void Start();
+ void Stop();
+
+ /*!
+ * @brief CEventStream callback for PVR events.
+ * @param event The event.
+ */
+ void Notify(const PVREvent& event);
+
+ /*!
+ * @brief CEventStream callback for channel number input changes.
+ * @param event The event.
+ */
+ void Notify(const PVRChannelNumberInputChangedEvent& event);
+
+ /*!
+ * @brief CEventStream callback for channel preview and player show info changes.
+ * @param event The event.
+ */
+ void Notify(const PVRPreviewAndPlayerShowInfoChangedEvent& event);
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem* item) override;
+ bool GetLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ std::string* fallback) const override;
+ bool GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ std::string* fallback) override;
+ bool GetInt(int& value,
+ const CGUIListItem* item,
+ int contextWindow,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info) const override;
+ bool GetBool(bool& value,
+ const CGUIListItem* item,
+ int contextWindow,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info) const override;
+
+private:
+ void ResetProperties();
+ void ClearQualityInfo(PVR_SIGNAL_STATUS& qualityInfo);
+ void ClearDescrambleInfo(PVR_DESCRAMBLE_INFO& descrambleInfo);
+
+ void Process() override;
+
+ void UpdateTimersCache();
+ void UpdateBackendCache();
+ void UpdateQualityData();
+ void UpdateDescrambleData();
+ void UpdateMisc();
+ void UpdateNextTimer();
+ void UpdateTimeshiftData();
+ void UpdateTimeshiftProgressData();
+
+ void UpdateTimersToggle();
+
+ bool GetListItemAndPlayerLabel(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ std::string& strValue) const;
+ bool GetPVRLabel(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ std::string& strValue) const;
+ bool GetRadioRDSLabel(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ std::string& strValue) const;
+
+ bool GetListItemAndPlayerInt(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ int& iValue) const;
+ bool GetPVRInt(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ int& iValue) const;
+ int GetTimeShiftSeekPercent() const;
+
+ bool GetListItemAndPlayerBool(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ bool& bValue) const;
+ bool GetPVRBool(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ bool& bValue) const;
+ bool GetRadioRDSBool(const CFileItem* item,
+ const KODI::GUILIB::GUIINFO::CGUIInfo& info,
+ bool& bValue) const;
+
+ void CharInfoBackendNumber(std::string& strValue) const;
+ void CharInfoTotalDiskSpace(std::string& strValue) const;
+ void CharInfoSignal(std::string& strValue) const;
+ void CharInfoSNR(std::string& strValue) const;
+ void CharInfoBER(std::string& strValue) const;
+ void CharInfoUNC(std::string& strValue) const;
+ void CharInfoFrontendName(std::string& strValue) const;
+ void CharInfoFrontendStatus(std::string& strValue) const;
+ void CharInfoBackendName(std::string& strValue) const;
+ void CharInfoBackendVersion(std::string& strValue) const;
+ void CharInfoBackendHost(std::string& strValue) const;
+ void CharInfoBackendDiskspace(std::string& strValue) const;
+ void CharInfoBackendProviders(std::string& strValue) const;
+ void CharInfoBackendChannelGroups(std::string& strValue) const;
+ void CharInfoBackendChannels(std::string& strValue) const;
+ void CharInfoBackendTimers(std::string& strValue) const;
+ void CharInfoBackendRecordings(std::string& strValue) const;
+ void CharInfoBackendDeletedRecordings(std::string& strValue) const;
+ void CharInfoPlayingClientName(std::string& strValue) const;
+ void CharInfoEncryption(std::string& strValue) const;
+ void CharInfoService(std::string& strValue) const;
+ void CharInfoMux(std::string& strValue) const;
+ void CharInfoProvider(std::string& strValue) const;
+
+ /** @name PVRGUIInfo data */
+ //@{
+ CPVRGUIAnyTimerInfo m_anyTimersInfo; // tv + radio
+ CPVRGUITVTimerInfo m_tvTimersInfo;
+ CPVRGUIRadioTimerInfo m_radioTimersInfo;
+
+ CPVRGUITimesInfo m_timesInfo;
+
+ bool m_bHasTVRecordings;
+ bool m_bHasRadioRecordings;
+ unsigned int m_iCurrentActiveClient;
+ std::string m_strPlayingClientName;
+ std::string m_strBackendName;
+ std::string m_strBackendVersion;
+ std::string m_strBackendHost;
+ std::string m_strBackendTimers;
+ std::string m_strBackendRecordings;
+ std::string m_strBackendDeletedRecordings;
+ std::string m_strBackendProviders;
+ std::string m_strBackendChannelGroups;
+ std::string m_strBackendChannels;
+ long long m_iBackendDiskTotal;
+ long long m_iBackendDiskUsed;
+ bool m_bIsPlayingTV;
+ bool m_bIsPlayingRadio;
+ bool m_bIsPlayingRecording;
+ bool m_bIsPlayingEpgTag;
+ bool m_bIsPlayingEncryptedStream;
+ bool m_bHasTVChannels;
+ bool m_bHasRadioChannels;
+ bool m_bCanRecordPlayingChannel;
+ bool m_bIsRecordingPlayingChannel;
+ bool m_bIsPlayingActiveRecording;
+ std::string m_strPlayingTVGroup;
+ std::string m_strPlayingRadioGroup;
+
+ //@}
+
+ PVR_SIGNAL_STATUS m_qualityInfo; /*!< stream quality information */
+ PVR_DESCRAMBLE_INFO m_descrambleInfo; /*!< stream descramble information */
+ std::vector<SBackend> m_backendProperties;
+
+ std::string m_channelNumberInput;
+ bool m_previewAndPlayerShowInfo{false};
+
+ mutable CCriticalSection m_critSection;
+
+ /**
+ * The various backend-related fields will only be updated when this
+ * flag is set. This is done to limit the amount of unnecessary
+ * backend querying when we're not displaying any of the queried
+ * information.
+ */
+ mutable std::atomic<bool> m_updateBackendCacheRequested;
+
+ bool m_bRegistered;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.cpp
new file mode 100644
index 0000000..24a468a
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.cpp
@@ -0,0 +1,270 @@
+/*
+ * 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 "PVRGUITimerInfo.h"
+
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "pvr/PVRManager.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+
+CPVRGUITimerInfo::CPVRGUITimerInfo()
+{
+ ResetProperties();
+}
+
+void CPVRGUITimerInfo::ResetProperties()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_strActiveTimerTitle.clear();
+ m_strActiveTimerChannelName.clear();
+ m_strActiveTimerChannelIcon.clear();
+ m_strActiveTimerTime.clear();
+ m_strNextTimerInfo.clear();
+ m_strNextRecordingTitle.clear();
+ m_strNextRecordingChannelName.clear();
+ m_strNextRecordingChannelIcon.clear();
+ m_strNextRecordingTime.clear();
+ m_iTimerAmount = 0;
+ m_iRecordingTimerAmount = 0;
+ m_iTimerInfoToggleStart = {};
+ m_iTimerInfoToggleCurrent = 0;
+}
+
+bool CPVRGUITimerInfo::TimerInfoToggle()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_iTimerInfoToggleStart.time_since_epoch().count() == 0)
+ {
+ m_iTimerInfoToggleStart = std::chrono::steady_clock::now();
+ m_iTimerInfoToggleCurrent = 0;
+ return true;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+ auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - m_iTimerInfoToggleStart);
+
+ if (duration.count() >
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRInfoToggleInterval)
+ {
+ unsigned int iPrevious = m_iTimerInfoToggleCurrent;
+ unsigned int iBoundary = m_iRecordingTimerAmount > 0 ? m_iRecordingTimerAmount : m_iTimerAmount;
+ if (++m_iTimerInfoToggleCurrent > iBoundary - 1)
+ m_iTimerInfoToggleCurrent = 0;
+
+ if (m_iTimerInfoToggleCurrent != iPrevious)
+ {
+ m_iTimerInfoToggleStart = std::chrono::steady_clock::now();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CPVRGUITimerInfo::UpdateTimersToggle()
+{
+ if (!TimerInfoToggle())
+ return;
+
+ std::string strActiveTimerTitle;
+ std::string strActiveTimerChannelName;
+ std::string strActiveTimerChannelIcon;
+ std::string strActiveTimerTime;
+
+ /* safe to fetch these unlocked, since they're updated from the same thread as this one */
+ if (m_iRecordingTimerAmount > 0)
+ {
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> activeTags = GetActiveRecordings();
+ if (m_iTimerInfoToggleCurrent < activeTags.size())
+ {
+ const std::shared_ptr<CPVRTimerInfoTag> tag = activeTags.at(m_iTimerInfoToggleCurrent);
+ strActiveTimerTitle = tag->Title();
+ strActiveTimerChannelName = tag->ChannelName();
+ strActiveTimerChannelIcon = tag->ChannelIcon();
+ strActiveTimerTime = tag->StartAsLocalTime().GetAsLocalizedDateTime(false, false);
+ }
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_strActiveTimerTitle = strActiveTimerTitle;
+ m_strActiveTimerChannelName = strActiveTimerChannelName;
+ m_strActiveTimerChannelIcon = strActiveTimerChannelIcon;
+ m_strActiveTimerTime = strActiveTimerTime;
+}
+
+void CPVRGUITimerInfo::UpdateTimersCache()
+{
+ int iTimerAmount = AmountActiveTimers();
+ int iRecordingTimerAmount = AmountActiveRecordings();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iTimerAmount = iTimerAmount;
+ m_iRecordingTimerAmount = iRecordingTimerAmount;
+ m_iTimerInfoToggleStart = {};
+ }
+
+ UpdateTimersToggle();
+}
+
+void CPVRGUITimerInfo::UpdateNextTimer()
+{
+ std::string strNextRecordingTitle;
+ std::string strNextRecordingChannelName;
+ std::string strNextRecordingChannelIcon;
+ std::string strNextRecordingTime;
+ std::string strNextTimerInfo;
+
+ const std::shared_ptr<CPVRTimerInfoTag> timer = GetNextActiveTimer();
+ if (timer)
+ {
+ strNextRecordingTitle = timer->Title();
+ strNextRecordingChannelName = timer->ChannelName();
+ strNextRecordingChannelIcon = timer->ChannelIcon();
+ strNextRecordingTime = timer->StartAsLocalTime().GetAsLocalizedDateTime(false, false);
+
+ strNextTimerInfo = StringUtils::Format("{} {} {} {}", g_localizeStrings.Get(19106),
+ timer->StartAsLocalTime().GetAsLocalizedDate(true),
+ g_localizeStrings.Get(19107),
+ timer->StartAsLocalTime().GetAsLocalizedTime("", false));
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_strNextRecordingTitle = strNextRecordingTitle;
+ m_strNextRecordingChannelName = strNextRecordingChannelName;
+ m_strNextRecordingChannelIcon = strNextRecordingChannelIcon;
+ m_strNextRecordingTime = strNextRecordingTime;
+ m_strNextTimerInfo = strNextTimerInfo;
+}
+
+const std::string& CPVRGUITimerInfo::GetActiveTimerTitle() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strActiveTimerTitle;
+}
+
+const std::string& CPVRGUITimerInfo::GetActiveTimerChannelName() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strActiveTimerChannelName;
+}
+
+const std::string& CPVRGUITimerInfo::GetActiveTimerChannelIcon() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strActiveTimerChannelIcon;
+}
+
+const std::string& CPVRGUITimerInfo::GetActiveTimerDateTime() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strActiveTimerTime;
+}
+
+const std::string& CPVRGUITimerInfo::GetNextTimerTitle() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strNextRecordingTitle;
+}
+
+const std::string& CPVRGUITimerInfo::GetNextTimerChannelName() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strNextRecordingChannelName;
+}
+
+const std::string& CPVRGUITimerInfo::GetNextTimerChannelIcon() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strNextRecordingChannelIcon;
+}
+
+const std::string& CPVRGUITimerInfo::GetNextTimerDateTime() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strNextRecordingTime;
+}
+
+const std::string& CPVRGUITimerInfo::GetNextTimer() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_strNextTimerInfo;
+}
+
+int CPVRGUIAnyTimerInfo::AmountActiveTimers()
+{
+ return CServiceBroker::GetPVRManager().Timers()->AmountActiveTimers();
+}
+
+int CPVRGUIAnyTimerInfo::AmountActiveRecordings()
+{
+ return CServiceBroker::GetPVRManager().Timers()->AmountActiveRecordings();
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRGUIAnyTimerInfo::GetActiveRecordings()
+{
+ return CServiceBroker::GetPVRManager().Timers()->GetActiveRecordings();
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRGUIAnyTimerInfo::GetNextActiveTimer()
+{
+ return CServiceBroker::GetPVRManager().Timers()->GetNextActiveTimer();
+}
+
+int CPVRGUITVTimerInfo::AmountActiveTimers()
+{
+ return CServiceBroker::GetPVRManager().Timers()->AmountActiveTVTimers();
+}
+
+int CPVRGUITVTimerInfo::AmountActiveRecordings()
+{
+ return CServiceBroker::GetPVRManager().Timers()->AmountActiveTVRecordings();
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRGUITVTimerInfo::GetActiveRecordings()
+{
+ return CServiceBroker::GetPVRManager().Timers()->GetActiveTVRecordings();
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRGUITVTimerInfo::GetNextActiveTimer()
+{
+ return CServiceBroker::GetPVRManager().Timers()->GetNextActiveTVTimer();
+}
+
+int CPVRGUIRadioTimerInfo::AmountActiveTimers()
+{
+ return CServiceBroker::GetPVRManager().Timers()->AmountActiveRadioTimers();
+}
+
+int CPVRGUIRadioTimerInfo::AmountActiveRecordings()
+{
+ return CServiceBroker::GetPVRManager().Timers()->AmountActiveRadioRecordings();
+}
+
+std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRGUIRadioTimerInfo::GetActiveRecordings()
+{
+ return CServiceBroker::GetPVRManager().Timers()->GetActiveRadioRecordings();
+}
+
+std::shared_ptr<CPVRTimerInfoTag> CPVRGUIRadioTimerInfo::GetNextActiveTimer()
+{
+ return CServiceBroker::GetPVRManager().Timers()->GetNextActiveRadioTimer();
+}
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.h
new file mode 100644
index 0000000..260db6c
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimerInfo.h
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+
+#include <chrono>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace PVR
+{
+ class CPVRTimerInfoTag;
+
+ class CPVRGUITimerInfo
+ {
+ public:
+ CPVRGUITimerInfo();
+ virtual ~CPVRGUITimerInfo() = default;
+
+ void ResetProperties();
+
+ void UpdateTimersCache();
+ void UpdateTimersToggle();
+ void UpdateNextTimer();
+
+ const std::string& GetActiveTimerTitle() const;
+ const std::string& GetActiveTimerChannelName() const;
+ const std::string& GetActiveTimerChannelIcon() const;
+ const std::string& GetActiveTimerDateTime() const;
+ const std::string& GetNextTimerTitle() const;
+ const std::string& GetNextTimerChannelName() const;
+ const std::string& GetNextTimerChannelIcon() const;
+ const std::string& GetNextTimerDateTime() const;
+ const std::string& GetNextTimer() const;
+
+ bool HasTimers() const { return m_iTimerAmount > 0; }
+ bool HasRecordingTimers() const { return m_iRecordingTimerAmount > 0; }
+ bool HasNonRecordingTimers() const { return m_iTimerAmount - m_iRecordingTimerAmount > 0; }
+
+ private:
+ bool TimerInfoToggle();
+
+ virtual int AmountActiveTimers() = 0;
+ virtual int AmountActiveRecordings() = 0;
+ virtual std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRecordings() = 0;
+ virtual std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTimer() = 0;
+
+ unsigned int m_iTimerAmount;
+ unsigned int m_iRecordingTimerAmount;
+
+ std::string m_strActiveTimerTitle;
+ std::string m_strActiveTimerChannelName;
+ std::string m_strActiveTimerChannelIcon;
+ std::string m_strActiveTimerTime;
+ std::string m_strNextRecordingTitle;
+ std::string m_strNextRecordingChannelName;
+ std::string m_strNextRecordingChannelIcon;
+ std::string m_strNextRecordingTime;
+ std::string m_strNextTimerInfo;
+
+ std::chrono::time_point<std::chrono::steady_clock> m_iTimerInfoToggleStart;
+ unsigned int m_iTimerInfoToggleCurrent;
+
+ mutable CCriticalSection m_critSection;
+ };
+
+ class CPVRGUIAnyTimerInfo : public CPVRGUITimerInfo
+ {
+ public:
+ CPVRGUIAnyTimerInfo() = default;
+
+ private:
+ int AmountActiveTimers() override;
+ int AmountActiveRecordings() override;
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRecordings() override;
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTimer() override;
+ };
+
+ class CPVRGUITVTimerInfo : public CPVRGUITimerInfo
+ {
+ public:
+ CPVRGUITVTimerInfo() = default;
+
+ private:
+ int AmountActiveTimers() override;
+ int AmountActiveRecordings() override;
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRecordings() override;
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTimer() override;
+ };
+
+ class CPVRGUIRadioTimerInfo : public CPVRGUITimerInfo
+ {
+ public:
+ CPVRGUIRadioTimerInfo() = default;
+
+ private:
+ int AmountActiveTimers() override;
+ int AmountActiveRecordings() override;
+ std::vector<std::shared_ptr<CPVRTimerInfoTag>> GetActiveRecordings() override;
+ std::shared_ptr<CPVRTimerInfoTag> GetNextActiveTimer() override;
+ };
+
+} // namespace PVR
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp
new file mode 100644
index 0000000..e5dfdb9
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp
@@ -0,0 +1,424 @@
+/*
+ * 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 "PVRGUITimesInfo.h"
+
+#include "ServiceBroker.h"
+#include "cores/DataCacheCore.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+
+#include <cmath>
+#include <ctime>
+#include <memory>
+#include <mutex>
+
+using namespace PVR;
+
+CPVRGUITimesInfo::CPVRGUITimesInfo()
+{
+ Reset();
+}
+
+void CPVRGUITimesInfo::Reset()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_iStartTime = 0;
+ m_iDuration = 0;
+ m_iTimeshiftStartTime = 0;
+ m_iTimeshiftEndTime = 0;
+ m_iTimeshiftPlayTime = 0;
+ m_iTimeshiftOffset = 0;
+
+ m_iTimeshiftProgressStartTime = 0;
+ m_iTimeshiftProgressEndTime = 0;
+ m_iTimeshiftProgressDuration = 0;
+
+ m_playingEpgTag.reset();
+ m_playingChannel.reset();
+}
+
+void CPVRGUITimesInfo::UpdatePlayingTag()
+{
+ const std::shared_ptr<CPVRChannel> currentChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+ std::shared_ptr<CPVREpgInfoTag> currentTag = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingEpgTag();
+
+ if (currentChannel || currentTag)
+ {
+ if (currentChannel && !currentTag)
+ currentTag = currentChannel->GetEPGNow();
+
+ const std::shared_ptr<CPVRChannelGroupsContainer> groups = CServiceBroker::GetPVRManager().ChannelGroups();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ const std::shared_ptr<CPVRChannel> playingChannel =
+ m_playingEpgTag ? groups->GetChannelForEpgTag(m_playingEpgTag) : nullptr;
+
+ if (!m_playingEpgTag || !currentTag || !playingChannel || !currentChannel ||
+ m_playingEpgTag->StartAsUTC() != currentTag->StartAsUTC() ||
+ m_playingEpgTag->EndAsUTC() != currentTag->EndAsUTC() || *playingChannel != *currentChannel)
+ {
+ if (currentTag)
+ {
+ m_playingEpgTag = currentTag;
+ m_iDuration = m_playingEpgTag->GetDuration();
+ }
+ else if (m_iTimeshiftEndTime > m_iTimeshiftStartTime)
+ {
+ m_playingEpgTag.reset();
+ m_iDuration = m_iTimeshiftEndTime - m_iTimeshiftStartTime;
+ }
+ else
+ {
+ m_playingEpgTag.reset();
+ m_iDuration = 0;
+ }
+ }
+ }
+ else
+ {
+ const std::shared_ptr<CPVRRecording> recording = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingRecording();
+ if (recording)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_playingEpgTag.reset();
+ m_iDuration = recording->GetDuration();
+ }
+ }
+}
+
+void CPVRGUITimesInfo::UpdateTimeshiftData()
+{
+ if (!CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV() && !CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio())
+ {
+ // If nothing is playing (anymore), there is no need to update data.
+ Reset();
+ return;
+ }
+
+ time_t now = std::time(nullptr);
+ time_t iStartTime;
+ int64_t iPlayTime, iMinTime, iMaxTime;
+ CServiceBroker::GetDataCacheCore().GetPlayTimes(iStartTime, iPlayTime, iMinTime, iMaxTime);
+ bool bPlaying = CServiceBroker::GetDataCacheCore().GetSpeed() == 1.0f;
+ const std::shared_ptr<CPVRChannel> playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (playingChannel != m_playingChannel)
+ {
+ // playing channel changed. we need to reset offset and playtime.
+ m_iTimeshiftOffset = 0;
+ m_iTimeshiftPlayTime = 0;
+ m_playingChannel = playingChannel;
+ }
+
+ if (!iStartTime)
+ {
+ if (m_iStartTime == 0)
+ iStartTime = now;
+ else
+ iStartTime = m_iStartTime;
+
+ iMinTime = iPlayTime;
+ iMaxTime = iPlayTime;
+ }
+
+ m_iStartTime = iStartTime;
+ m_iTimeshiftStartTime = iStartTime + iMinTime / 1000;
+ m_iTimeshiftEndTime = iStartTime + iMaxTime / 1000;
+
+ if (m_iTimeshiftEndTime > m_iTimeshiftStartTime)
+ {
+ // timeshifting supported
+ m_iTimeshiftPlayTime = iStartTime + iPlayTime / 1000;
+ if (iMaxTime > iPlayTime)
+ m_iTimeshiftOffset = (iMaxTime - iPlayTime) / 1000;
+ else
+ m_iTimeshiftOffset = 0;
+ }
+ else
+ {
+ // timeshifting not supported
+ if (bPlaying)
+ m_iTimeshiftPlayTime = now - m_iTimeshiftOffset;
+
+ m_iTimeshiftOffset = now - m_iTimeshiftPlayTime;
+ }
+
+ UpdateTimeshiftProgressData();
+}
+
+void CPVRGUITimesInfo::UpdateTimeshiftProgressData()
+{
+ // Note: General idea of the ts progress is always to be able to visualise both the complete
+ // ts buffer and the complete playing epg event (if any) side by side with the same time
+ // scale. TS progress start and end times will be calculated accordingly.
+ // + Start is usually ts buffer start, except if start time of playing epg event is
+ // before ts buffer start, then progress start is epg event start.
+ // + End is usually ts buffer end, except if end time of playing epg event is
+ // after ts buffer end, then progress end is epg event end.
+ // In simple timeshift mode (settings value), progress start is always the start time of
+ // playing epg event and progress end is always the end time of playing epg event.
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // start time
+ //////////////////////////////////////////////////////////////////////////////////////
+ bool bUpdatedStartTime = false;
+ if (m_playingEpgTag)
+ {
+ time_t start = 0;
+ m_playingEpgTag->StartAsUTC().GetAsTime(start);
+ if (start < m_iTimeshiftStartTime ||
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bPVRTimeshiftSimpleOSD)
+ {
+ // playing event started before start of ts buffer or simple ts osd to be used
+ m_iTimeshiftProgressStartTime = start;
+ bUpdatedStartTime = true;
+ }
+ }
+
+ if (!bUpdatedStartTime)
+ {
+ // default to ts buffer start
+ m_iTimeshiftProgressStartTime = m_iTimeshiftStartTime;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // end time
+ //////////////////////////////////////////////////////////////////////////////////////
+ bool bUpdatedEndTime = false;
+ if (m_playingEpgTag)
+ {
+ time_t end = 0;
+ m_playingEpgTag->EndAsUTC().GetAsTime(end);
+ if (end > m_iTimeshiftEndTime ||
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bPVRTimeshiftSimpleOSD)
+ {
+ // playing event will end after end of ts buffer or simple ts osd to be used
+ m_iTimeshiftProgressEndTime = end;
+ bUpdatedEndTime = true;
+ }
+ }
+
+ if (!bUpdatedEndTime)
+ {
+ // default to ts buffer end
+ m_iTimeshiftProgressEndTime = m_iTimeshiftEndTime;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // duration
+ //////////////////////////////////////////////////////////////////////////////////////
+ m_iTimeshiftProgressDuration = m_iTimeshiftProgressEndTime - m_iTimeshiftProgressStartTime;
+}
+
+void CPVRGUITimesInfo::Update()
+{
+ UpdatePlayingTag();
+ UpdateTimeshiftData();
+}
+
+std::string CPVRGUITimesInfo::TimeToTimeString(time_t datetime, TIME_FORMAT format, bool withSeconds)
+{
+ CDateTime time;
+ time.SetFromUTCDateTime(datetime);
+ return time.GetAsLocalizedTime(format, withSeconds);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftStartTime(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return TimeToTimeString(m_iTimeshiftStartTime, format, false);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftEndTime(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return TimeToTimeString(m_iTimeshiftEndTime, format, false);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftPlayTime(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return TimeToTimeString(m_iTimeshiftPlayTime, format, true);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftOffset(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return StringUtils::SecondsToTimeString(m_iTimeshiftOffset, format);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftProgressDuration(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return StringUtils::SecondsToTimeString(m_iTimeshiftProgressDuration, format);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftProgressStartTime(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return TimeToTimeString(m_iTimeshiftProgressStartTime, format, false);
+}
+
+std::string CPVRGUITimesInfo::GetTimeshiftProgressEndTime(TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return TimeToTimeString(m_iTimeshiftProgressEndTime, format, false);
+}
+
+std::string CPVRGUITimesInfo::GetEpgEventDuration(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return StringUtils::SecondsToTimeString(GetEpgEventDuration(epgTag), format);
+}
+
+std::string CPVRGUITimesInfo::GetEpgEventElapsedTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const
+{
+ int iElapsed = 0;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
+ iElapsed = epgTag->Progress();
+ else
+ iElapsed = GetElapsedTime();
+
+ return StringUtils::SecondsToTimeString(iElapsed, format);
+}
+
+std::string CPVRGUITimesInfo::GetEpgEventRemainingTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return StringUtils::SecondsToTimeString(GetRemainingTime(epgTag), format);
+}
+
+std::string CPVRGUITimesInfo::GetEpgEventFinishTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const
+{
+ CDateTime finish = CDateTime::GetCurrentDateTime();
+ finish += CDateTimeSpan(0, 0, 0, GetRemainingTime(epgTag));
+ return finish.GetAsLocalizedTime(format);
+}
+
+std::string CPVRGUITimesInfo::GetEpgEventSeekTime(int iSeekSize, TIME_FORMAT format) const
+{
+ return StringUtils::SecondsToTimeString(GetElapsedTime() + iSeekSize, format);
+}
+
+int CPVRGUITimesInfo::GetElapsedTime() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_playingEpgTag || m_iTimeshiftStartTime)
+ {
+ CDateTime current(m_iTimeshiftPlayTime);
+ CDateTime start = m_playingEpgTag ? m_playingEpgTag->StartAsUTC() : CDateTime(m_iTimeshiftStartTime);
+ CDateTimeSpan time = current > start ? current - start : CDateTimeSpan(0, 0, 0, 0);
+ return time.GetSecondsTotal();
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+int CPVRGUITimesInfo::GetRemainingTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
+ return epgTag->GetDuration() - epgTag->Progress();
+ else
+ return m_iDuration - GetElapsedTime();
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgress() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::lrintf(static_cast<float>(m_iTimeshiftPlayTime - m_iTimeshiftStartTime) / (m_iTimeshiftEndTime - m_iTimeshiftStartTime) * 100);
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgressDuration() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_iTimeshiftProgressDuration;
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgressPlayPosition() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::lrintf(static_cast<float>(m_iTimeshiftPlayTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgressEpgStart() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_playingEpgTag)
+ {
+ time_t epgStart = 0;
+ m_playingEpgTag->StartAsUTC().GetAsTime(epgStart);
+ return std::lrintf(static_cast<float>(epgStart - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ }
+ return 0;
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgressEpgEnd() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_playingEpgTag)
+ {
+ time_t epgEnd = 0;
+ m_playingEpgTag->EndAsUTC().GetAsTime(epgEnd);
+ return std::lrintf(static_cast<float>(epgEnd - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ }
+ return 0;
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgressBufferStart() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::lrintf(static_cast<float>(m_iTimeshiftStartTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+}
+
+int CPVRGUITimesInfo::GetTimeshiftProgressBufferEnd() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::lrintf(static_cast<float>(m_iTimeshiftEndTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+}
+
+int CPVRGUITimesInfo::GetEpgEventDuration(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
+ return epgTag->GetDuration();
+ else
+ return m_iDuration;
+}
+
+int CPVRGUITimesInfo::GetEpgEventProgress(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
+ return std::lrintf(epgTag->ProgressPercentage());
+ else
+ return std::lrintf(static_cast<float>(GetElapsedTime()) / m_iDuration * 100);
+}
+
+bool CPVRGUITimesInfo::IsTimeshifting() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return (m_iTimeshiftOffset > static_cast<unsigned int>(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeshiftThreshold));
+}
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h
new file mode 100644
index 0000000..47dd9f5
--- /dev/null
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+#include "utils/TimeFormat.h"
+
+#include <memory>
+
+namespace PVR
+{
+ class CPVRChannel;
+ class CPVREpgInfoTag;
+
+ class CPVRGUITimesInfo
+ {
+ public:
+ CPVRGUITimesInfo();
+ virtual ~CPVRGUITimesInfo() = default;
+
+ void Reset();
+ void Update();
+
+ // GUI info labels
+ std::string GetTimeshiftStartTime(TIME_FORMAT format) const;
+ std::string GetTimeshiftEndTime(TIME_FORMAT format) const;
+ std::string GetTimeshiftPlayTime(TIME_FORMAT format) const;
+ std::string GetTimeshiftOffset(TIME_FORMAT format) const;
+ std::string GetTimeshiftProgressDuration(TIME_FORMAT format) const;
+ std::string GetTimeshiftProgressStartTime(TIME_FORMAT format) const;
+ std::string GetTimeshiftProgressEndTime(TIME_FORMAT format) const;
+
+ std::string GetEpgEventDuration(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const;
+ std::string GetEpgEventElapsedTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const;
+ std::string GetEpgEventRemainingTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const;
+ std::string GetEpgEventFinishTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag, TIME_FORMAT format) const;
+ std::string GetEpgEventSeekTime(int iSeekSize, TIME_FORMAT format) const;
+
+ // GUI info ints
+ int GetTimeshiftProgress() const;
+ int GetTimeshiftProgressDuration() const;
+ int GetTimeshiftProgressPlayPosition() const;
+ int GetTimeshiftProgressEpgStart() const;
+ int GetTimeshiftProgressEpgEnd() const;
+ int GetTimeshiftProgressBufferStart() const;
+ int GetTimeshiftProgressBufferEnd() const;
+
+ int GetEpgEventDuration(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+ int GetEpgEventProgress(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+
+ // GUI info bools
+ bool IsTimeshifting() const;
+
+ private:
+ void UpdatePlayingTag();
+ void UpdateTimeshiftData();
+ void UpdateTimeshiftProgressData();
+
+ static std::string TimeToTimeString(time_t datetime, TIME_FORMAT format, bool withSeconds);
+
+ int GetElapsedTime() const;
+ int GetRemainingTime(const std::shared_ptr<CPVREpgInfoTag>& epgTag) const;
+
+ mutable CCriticalSection m_critSection;
+
+ std::shared_ptr<CPVREpgInfoTag> m_playingEpgTag;
+ std::shared_ptr<CPVRChannel> m_playingChannel;
+
+ time_t m_iStartTime;
+ unsigned int m_iDuration;
+ time_t m_iTimeshiftStartTime;
+ time_t m_iTimeshiftEndTime;
+ time_t m_iTimeshiftPlayTime;
+ unsigned int m_iTimeshiftOffset;
+
+ time_t m_iTimeshiftProgressStartTime;
+ time_t m_iTimeshiftProgressEndTime;
+ unsigned int m_iTimeshiftProgressDuration;
+ };
+
+} // namespace PVR