summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/windows
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/windows
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/pvr/windows')
-rw-r--r--xbmc/pvr/windows/CMakeLists.txt21
-rw-r--r--xbmc/pvr/windows/GUIViewStatePVR.cpp182
-rw-r--r--xbmc/pvr/windows/GUIViewStatePVR.h78
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRBase.cpp563
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRBase.h138
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRChannels.cpp414
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRChannels.h65
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRGuide.cpp973
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRGuide.h129
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRRecordings.cpp440
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRRecordings.h71
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRSearch.cpp544
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRSearch.h90
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRTimerRules.cpp47
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRTimerRules.h38
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRTimers.cpp47
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRTimers.h36
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRTimersBase.cpp204
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRTimersBase.h36
19 files changed, 4116 insertions, 0 deletions
diff --git a/xbmc/pvr/windows/CMakeLists.txt b/xbmc/pvr/windows/CMakeLists.txt
new file mode 100644
index 0000000..9f5097e
--- /dev/null
+++ b/xbmc/pvr/windows/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(SOURCES GUIViewStatePVR.cpp
+ GUIWindowPVRBase.cpp
+ GUIWindowPVRChannels.cpp
+ GUIWindowPVRGuide.cpp
+ GUIWindowPVRRecordings.cpp
+ GUIWindowPVRSearch.cpp
+ GUIWindowPVRTimers.cpp
+ GUIWindowPVRTimersBase.cpp
+ GUIWindowPVRTimerRules.cpp)
+
+set(HEADERS GUIViewStatePVR.h
+ GUIWindowPVRBase.h
+ GUIWindowPVRChannels.h
+ GUIWindowPVRGuide.h
+ GUIWindowPVRRecordings.h
+ GUIWindowPVRSearch.h
+ GUIWindowPVRTimerRules.h
+ GUIWindowPVRTimers.h
+ GUIWindowPVRTimersBase.h)
+
+core_add_library(pvr_windows)
diff --git a/xbmc/pvr/windows/GUIViewStatePVR.cpp b/xbmc/pvr/windows/GUIViewStatePVR.cpp
new file mode 100644
index 0000000..d86f6fc
--- /dev/null
+++ b/xbmc/pvr/windows/GUIViewStatePVR.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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 "GUIViewStatePVR.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/epg/EpgSearchPath.h"
+#include "pvr/recordings/PVRRecordingsPath.h"
+#include "pvr/timers/PVRTimersPath.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "view/ViewStateSettings.h"
+
+using namespace PVR;
+
+CGUIViewStateWindowPVRChannels::CGUIViewStateWindowPVRChannels(const int windowId,
+ const CFileItemList& items)
+ : CGUIViewStatePVR(windowId, items)
+{
+ AddSortMethod(SortByChannelNumber, 549, // "Number"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(SortByChannel, 551, // "Name"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(
+ SortByLastPlayed, 568, // "Last played"
+ LABEL_MASKS("%L", "%p", "%L", "%p")); // Filename, LastPlayed | Foldername, LastPlayed
+ AddSortMethod(SortByClientChannelOrder, 19315, // "Backend number"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(SortByProvider, 19348, // "Provider"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+
+ // Default sorting
+ SetSortMethod(SortByChannelNumber);
+
+ LoadViewState("pvr://channels/", m_windowId);
+}
+
+void CGUIViewStateWindowPVRChannels::SaveViewState()
+{
+ SaveViewToDb("pvr://channels/", m_windowId, CViewStateSettings::GetInstance().Get("pvrchannels"));
+}
+
+CGUIViewStateWindowPVRRecordings::CGUIViewStateWindowPVRRecordings(const int windowId,
+ const CFileItemList& items)
+ : CGUIViewStatePVR(windowId, items)
+{
+ AddSortMethod(SortByLabel, 551, // "Name"
+ LABEL_MASKS("%L", "%d", "%L", ""), // Filename, DateTime | Foldername, empty
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
+ ? SortAttributeIgnoreArticle
+ : SortAttributeNone);
+ AddSortMethod(SortByDate, 552, // "Date"
+ LABEL_MASKS("%L", "%d", "%L", "%d")); // Filename, DateTime | Foldername, DateTime
+ AddSortMethod(SortByTime, 180, // "Duration"
+ LABEL_MASKS("%L", "%D", "%L", "")); // Filename, Duration | Foldername, empty
+ AddSortMethod(SortByFile, 561, // "File"
+ LABEL_MASKS("%L", "%d", "%L", "")); // Filename, DateTime | Foldername, empty
+
+ if (CServiceBroker::GetPVRManager().Clients()->AnyClientSupportingRecordingsSize())
+ {
+ // "Size" : Filename, Size | Foldername, Size
+ AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I"));
+ }
+
+ AddSortMethod(SortByEpisodeNumber, 20359, // "Episode"
+ LABEL_MASKS("%L", "%d", "%L", "")); // Filename, DateTime | Foldername, empty
+ AddSortMethod(SortByProvider, 19348, // "Provider"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+
+ SetSortMethod(
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_PVRDefaultSortOrder);
+
+ LoadViewState(items.GetPath(), m_windowId);
+}
+
+void CGUIViewStateWindowPVRRecordings::SaveViewState()
+{
+ SaveViewToDb(m_items.GetPath(), m_windowId,
+ CViewStateSettings::GetInstance().Get("pvrrecordings"));
+}
+
+bool CGUIViewStateWindowPVRRecordings::HideParentDirItems()
+{
+ return (CGUIViewState::HideParentDirItems() ||
+ CPVRRecordingsPath(m_items.GetPath()).IsRecordingsRoot());
+}
+
+CGUIViewStateWindowPVRGuide::CGUIViewStateWindowPVRGuide(const int windowId,
+ const CFileItemList& items)
+ : CGUIViewStatePVR(windowId, items)
+{
+ AddSortMethod(SortByChannelNumber, 549, // "Number"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(SortByChannel, 551, // "Name"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(
+ SortByLastPlayed, SortAttributeIgnoreLabel, 568, // "Last played"
+ LABEL_MASKS("%L", "%p", "%L", "%p")); // Filename, LastPlayed | Foldername, LastPlayed
+ AddSortMethod(SortByClientChannelOrder, 19315, // "Backend number"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(SortByProvider, 19348, // "Provider"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+
+ // Default sorting
+ SetSortMethod(SortByChannelNumber);
+
+ LoadViewState("pvr://guide/", m_windowId);
+}
+
+void CGUIViewStateWindowPVRGuide::SaveViewState()
+{
+ SaveViewToDb("pvr://guide/", m_windowId, CViewStateSettings::GetInstance().Get("pvrguide"));
+}
+
+CGUIViewStateWindowPVRTimers::CGUIViewStateWindowPVRTimers(const int windowId,
+ const CFileItemList& items)
+ : CGUIViewStatePVR(windowId, items)
+{
+ int sortAttributes(CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
+ ? SortAttributeIgnoreArticle
+ : SortAttributeNone);
+ sortAttributes |= SortAttributeIgnoreFolders;
+ AddSortMethod(SortByLabel, static_cast<SortAttribute>(sortAttributes), 551, // "Name"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(SortByDate, static_cast<SortAttribute>(sortAttributes), 552, // "Date"
+ LABEL_MASKS("%L", "%d", "%L", "%d")); // Filename, DateTime | Foldername, DateTime
+
+ // Default sorting
+ SetSortMethod(SortByDate);
+
+ LoadViewState("pvr://timers/", m_windowId);
+}
+
+void CGUIViewStateWindowPVRTimers::SaveViewState()
+{
+ SaveViewToDb("pvr://timers/", m_windowId, CViewStateSettings::GetInstance().Get("pvrtimers"));
+}
+
+bool CGUIViewStateWindowPVRTimers::HideParentDirItems()
+{
+ return (CGUIViewState::HideParentDirItems() || CPVRTimersPath(m_items.GetPath()).IsTimersRoot());
+}
+
+CGUIViewStateWindowPVRSearch::CGUIViewStateWindowPVRSearch(const int windowId,
+ const CFileItemList& items)
+ : CGUIViewStatePVR(windowId, items)
+{
+ AddSortMethod(SortByLabel, 551, // "Name"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+ AddSortMethod(SortByDate, 552, // "Date"
+ LABEL_MASKS("%L", "%d", "%L", "%d")); // Filename, DateTime | Foldername, DateTime
+
+ // Default sorting
+ if (CPVREpgSearchPath(m_items.GetPath()).IsSavedSearchesRoot())
+ SetSortMethod(SortByDate, SortOrderDescending);
+ else
+ SetSortMethod(SortByDate, SortOrderAscending);
+
+ LoadViewState(m_items.GetPath(), m_windowId);
+}
+
+void CGUIViewStateWindowPVRSearch::SaveViewState()
+{
+ SaveViewToDb(m_items.GetPath(), m_windowId, CViewStateSettings::GetInstance().Get("pvrsearch"));
+}
+
+bool CGUIViewStateWindowPVRSearch::HideParentDirItems()
+{
+ return (CGUIViewState::HideParentDirItems() ||
+ CPVREpgSearchPath(m_items.GetPath()).IsSearchRoot());
+}
diff --git a/xbmc/pvr/windows/GUIViewStatePVR.h b/xbmc/pvr/windows/GUIViewStatePVR.h
new file mode 100644
index 0000000..ce39dd7
--- /dev/null
+++ b/xbmc/pvr/windows/GUIViewStatePVR.h
@@ -0,0 +1,78 @@
+/*
+ * 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 "view/GUIViewState.h"
+
+class CFileItemList;
+
+namespace PVR
+{
+class CGUIViewStatePVR : public CGUIViewState
+{
+public:
+ CGUIViewStatePVR(const int windowId, const CFileItemList& items) : CGUIViewState(items)
+ {
+ m_windowId = windowId;
+ }
+
+protected:
+ bool HideParentDirItems() override { return true; }
+
+ int m_windowId;
+};
+
+class CGUIViewStateWindowPVRChannels : public CGUIViewStatePVR
+{
+public:
+ CGUIViewStateWindowPVRChannels(const int windowId, const CFileItemList& items);
+
+protected:
+ void SaveViewState() override;
+};
+
+class CGUIViewStateWindowPVRRecordings : public CGUIViewStatePVR
+{
+public:
+ CGUIViewStateWindowPVRRecordings(const int windowId, const CFileItemList& items);
+
+protected:
+ void SaveViewState() override;
+ bool HideParentDirItems() override;
+};
+
+class CGUIViewStateWindowPVRGuide : public CGUIViewStatePVR
+{
+public:
+ CGUIViewStateWindowPVRGuide(const int windowId, const CFileItemList& items);
+
+protected:
+ void SaveViewState() override;
+};
+
+class CGUIViewStateWindowPVRTimers : public CGUIViewStatePVR
+{
+public:
+ CGUIViewStateWindowPVRTimers(const int windowId, const CFileItemList& items);
+
+protected:
+ void SaveViewState() override;
+ bool HideParentDirItems() override;
+};
+
+class CGUIViewStateWindowPVRSearch : public CGUIViewStatePVR
+{
+public:
+ CGUIViewStateWindowPVRSearch(const int windowId, const CFileItemList& items);
+
+protected:
+ void SaveViewState() override;
+ bool HideParentDirItems() override;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.cpp b/xbmc/pvr/windows/GUIWindowPVRBase.cpp
new file mode 100644
index 0000000..96a14b5
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRBase.cpp
@@ -0,0 +1,563 @@
+/*
+ * 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 "GUIWindowPVRBase.h"
+
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "dialogs/GUIDialogExtendedProgressBar.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/filesystem/PVRGUIDirectory.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace std::chrono_literals;
+
+#define MAX_INVALIDATION_FREQUENCY 2000ms // limit to one invalidation per X milliseconds
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+namespace PVR
+{
+
+class CGUIPVRChannelGroupsSelector
+{
+public:
+ virtual ~CGUIPVRChannelGroupsSelector() = default;
+
+ bool Initialize(CGUIWindow* parent, bool bRadio);
+
+ bool HasFocus() const;
+ std::shared_ptr<CPVRChannelGroup> GetSelectedChannelGroup() const;
+ bool SelectChannelGroup(const std::shared_ptr<CPVRChannelGroup>& newGroup);
+
+private:
+ CGUIControl* m_control = nullptr;
+ std::vector<std::shared_ptr<CPVRChannelGroup>> m_channelGroups;
+};
+
+} // namespace PVR
+
+bool CGUIPVRChannelGroupsSelector::Initialize(CGUIWindow* parent, bool bRadio)
+{
+ CGUIControl* control = parent->GetControl(CONTROL_LSTCHANNELGROUPS);
+ if (control && control->IsContainer())
+ {
+ m_control = control;
+ m_channelGroups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio)->GetMembers(true);
+
+ CFileItemList channelGroupItems;
+ CPVRGUIDirectory::GetChannelGroupsDirectory(bRadio, true, channelGroupItems);
+
+ CGUIMessage msg(GUI_MSG_LABEL_BIND, m_control->GetID(), CONTROL_LSTCHANNELGROUPS, 0, 0, &channelGroupItems);
+ m_control->OnMessage(msg);
+ return true;
+ }
+ return false;
+}
+
+bool CGUIPVRChannelGroupsSelector::HasFocus() const
+{
+ return m_control && m_control->HasFocus();
+}
+
+std::shared_ptr<CPVRChannelGroup> CGUIPVRChannelGroupsSelector::GetSelectedChannelGroup() const
+{
+ if (m_control)
+ {
+ CGUIMessage msg(GUI_MSG_ITEM_SELECTED, m_control->GetID(), CONTROL_LSTCHANNELGROUPS);
+ m_control->OnMessage(msg);
+
+ const auto it = std::next(m_channelGroups.begin(), msg.GetParam1());
+ if (it != m_channelGroups.end())
+ {
+ return *it;
+ }
+ }
+ return std::shared_ptr<CPVRChannelGroup>();
+}
+
+bool CGUIPVRChannelGroupsSelector::SelectChannelGroup(const std::shared_ptr<CPVRChannelGroup>& newGroup)
+{
+ if (m_control && newGroup)
+ {
+ int iIndex = 0;
+ for (const auto& group : m_channelGroups)
+ {
+ if (*newGroup == *group)
+ {
+ CGUIMessage msg(GUI_MSG_ITEM_SELECT, m_control->GetID(), CONTROL_LSTCHANNELGROUPS, iIndex);
+ m_control->OnMessage(msg);
+ return true;
+ }
+ ++iIndex;
+ }
+ }
+ return false;
+}
+
+CGUIWindowPVRBase::CGUIWindowPVRBase(bool bRadio, int id, const std::string& xmlFile) :
+ CGUIMediaWindow(id, xmlFile.c_str()),
+ m_bRadio(bRadio),
+ m_channelGroupsSelector(new CGUIPVRChannelGroupsSelector),
+ m_progressHandle(nullptr)
+{
+ // prevent removable drives to appear in directory listing (base class default behavior).
+ m_rootDir.AllowNonLocalSources(false);
+
+ CServiceBroker::GetPVRManager().Events().Subscribe(this, &CGUIWindowPVRBase::Notify);
+}
+
+CGUIWindowPVRBase::~CGUIWindowPVRBase()
+{
+ if (m_channelGroup)
+ m_channelGroup->Events().Unsubscribe(this);
+
+ CServiceBroker::GetPVRManager().Events().Unsubscribe(this);
+}
+
+void CGUIWindowPVRBase::UpdateSelectedItemPath()
+{
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().SetSelectedChannelPath(
+ m_bRadio, m_viewControl.GetSelectedItemPath());
+}
+
+void CGUIWindowPVRBase::Notify(const PVREvent& event)
+{
+ // call virtual event handler function
+ NotifyEvent(event);
+}
+
+void CGUIWindowPVRBase::NotifyEvent(const PVREvent& event)
+{
+ if (event == PVREvent::ManagerStopped)
+ {
+ ClearData();
+ }
+ else if (m_active)
+ {
+ if (event == PVREvent::SystemSleep)
+ {
+ CGUIMessage m(GUI_MSG_SYSTEM_SLEEP, GetID(), 0, static_cast<int>(event));
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
+ }
+ else if (event == PVREvent::SystemWake)
+ {
+ CGUIMessage m(GUI_MSG_SYSTEM_WAKE, GetID(), 0, static_cast<int>(event));
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
+ }
+ else
+ {
+ CGUIMessage m(GUI_MSG_REFRESH_LIST, GetID(), 0, static_cast<int>(event));
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
+ }
+ }
+}
+
+bool CGUIWindowPVRBase::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PREVIOUS_CHANNELGROUP:
+ ActivatePreviousChannelGroup();
+ return true;
+
+ case ACTION_NEXT_CHANNELGROUP:
+ ActivateNextChannelGroup();
+ return true;
+
+ case ACTION_MOVE_RIGHT:
+ case ACTION_MOVE_LEFT:
+ {
+ if (m_channelGroupsSelector->HasFocus() && CGUIMediaWindow::OnAction(action))
+ {
+ SetChannelGroup(m_channelGroupsSelector->GetSelectedChannelGroup());
+ return true;
+ }
+ }
+ }
+
+ return CGUIMediaWindow::OnAction(action);
+}
+
+bool CGUIWindowPVRBase::ActivatePreviousChannelGroup()
+{
+ const std::shared_ptr<CPVRChannelGroup> channelGroup = GetChannelGroup();
+ if (channelGroup)
+ {
+ const CPVRChannelGroups* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio());
+ if (groups)
+ {
+ SetChannelGroup(groups->GetPreviousGroup(*channelGroup));
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIWindowPVRBase::ActivateNextChannelGroup()
+{
+ const std::shared_ptr<CPVRChannelGroup> channelGroup = GetChannelGroup();
+ if (channelGroup)
+ {
+ const CPVRChannelGroups* groups = CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup->IsRadio());
+ if (groups)
+ {
+ SetChannelGroup(groups->GetNextGroup(*channelGroup));
+ return true;
+ }
+ }
+ return false;
+}
+
+void CGUIWindowPVRBase::ClearData()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_channelGroup.reset();
+ m_channelGroupsSelector.reset(new CGUIPVRChannelGroupsSelector);
+}
+
+void CGUIWindowPVRBase::OnInitWindow()
+{
+ SetProperty("IsRadio", m_bRadio ? "true" : "");
+
+ if (InitChannelGroup())
+ {
+ m_channelGroupsSelector->Initialize(this, m_bRadio);
+
+ CGUIMediaWindow::OnInitWindow();
+
+ // mark item as selected by channel path
+ m_viewControl.SetSelectedItem(
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetSelectedChannelPath(m_bRadio));
+
+ // This has to be done after base class OnInitWindow to restore correct selection
+ m_channelGroupsSelector->SelectChannelGroup(GetChannelGroup());
+ }
+ else
+ {
+ CGUIWindow::OnInitWindow(); // do not call CGUIMediaWindow as it will do a Refresh which in no case works in this state (no channelgroup!)
+ ShowProgressDialog(g_localizeStrings.Get(19235), 0); // PVR manager is starting up
+ }
+}
+
+void CGUIWindowPVRBase::OnDeinitWindow(int nextWindowID)
+{
+ HideProgressDialog();
+ UpdateSelectedItemPath();
+ CGUIMediaWindow::OnDeinitWindow(nextWindowID);
+}
+
+bool CGUIWindowPVRBase::OnMessage(CGUIMessage& message)
+{
+ bool bReturn = false;
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_CLICKED:
+ {
+ switch (message.GetSenderId())
+ {
+ case CONTROL_BTNCHANNELGROUPS:
+ return OpenChannelGroupSelectionDialog();
+
+ case CONTROL_LSTCHANNELGROUPS:
+ {
+ switch (message.GetParam1())
+ {
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ SetChannelGroup(m_channelGroupsSelector->GetSelectedChannelGroup());
+ bReturn = true;
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case GUI_MSG_REFRESH_LIST:
+ {
+ switch (static_cast<PVREvent>(message.GetParam1()))
+ {
+ case PVREvent::ManagerStarted:
+ case PVREvent::ClientsInvalidated:
+ case PVREvent::ChannelGroupsInvalidated:
+ {
+ if (InitChannelGroup())
+ {
+ m_channelGroupsSelector->Initialize(this, m_bRadio);
+ m_channelGroupsSelector->SelectChannelGroup(GetChannelGroup());
+ HideProgressDialog();
+ Refresh(true);
+ m_viewControl.SetFocused();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (IsActive())
+ {
+ // Only the active window must set the selected item path which is shared
+ // between all PVR windows, not the last notified window (observer).
+ UpdateSelectedItemPath();
+ }
+ bReturn = true;
+ break;
+ }
+
+ case GUI_MSG_NOTIFY_ALL:
+ {
+ switch (message.GetParam1())
+ {
+ case GUI_MSG_UPDATE_SOURCES:
+ {
+ // removable drive connected/disconnected. base class triggers a window
+ // content refresh, which makes no sense for pvr windows.
+ bReturn = true;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return bReturn || CGUIMediaWindow::OnMessage(message);
+}
+
+void CGUIWindowPVRBase::SetInvalid()
+{
+ if (m_refreshTimeout.IsTimePast())
+ {
+ for (const auto& item : *m_vecItems)
+ item->SetInvalid();
+
+ CGUIMediaWindow::SetInvalid();
+ m_refreshTimeout.Set(MAX_INVALIDATION_FREQUENCY);
+ }
+}
+
+bool CGUIWindowPVRBase::CanBeActivated() const
+{
+ // check if there is at least one enabled PVR add-on
+ if (!CServiceBroker::GetAddonMgr().HasAddons(ADDON::AddonType::PVRDLL))
+ {
+ HELPERS::ShowOKDialogText(CVariant{19296}, CVariant{19272}); // No PVR add-on enabled, You need a tuner, backend software...
+ return false;
+ }
+
+ return true;
+}
+
+bool CGUIWindowPVRBase::OpenChannelGroupSelectionDialog()
+{
+ CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
+ if (!dialog)
+ return false;
+
+ CFileItemList options;
+ CPVRGUIDirectory::GetChannelGroupsDirectory(m_bRadio, true, options);
+
+ dialog->Reset();
+ dialog->SetHeading(CVariant{g_localizeStrings.Get(19146)});
+ dialog->SetItems(options);
+ dialog->SetMultiSelection(false);
+ if (const std::shared_ptr<CPVRChannelGroup> channelGroup = GetChannelGroup())
+ {
+ dialog->SetSelected(channelGroup->GroupName());
+ }
+ dialog->Open();
+
+ if (!dialog->IsConfirmed())
+ return false;
+
+ const CFileItemPtr item = dialog->GetSelectedFileItem();
+ if (!item)
+ return false;
+
+ SetChannelGroup(CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bRadio)->GetByName(item->m_strTitle));
+
+ return true;
+}
+
+bool CGUIWindowPVRBase::InitChannelGroup()
+{
+ if (!CServiceBroker::GetPVRManager().IsStarted())
+ return false;
+
+ std::shared_ptr<CPVRChannelGroup> group;
+ if (m_channelGroupPath.empty())
+ {
+ group = CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(m_bRadio);
+ }
+ else
+ {
+ group = CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bRadio)->GetGroupByPath(m_channelGroupPath);
+ if (group)
+ CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(group);
+ else
+ CLog::LogF(LOGERROR, "Found no {} channel group with path '{}'!", m_bRadio ? "radio" : "TV",
+ m_channelGroupPath);
+
+ m_channelGroupPath.clear();
+ }
+
+ if (group)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_channelGroup != group)
+ {
+ m_viewControl.SetSelectedItem(0);
+ SetChannelGroup(std::move(group), false);
+ }
+ // Path might have changed since last init. Set it always, not just on group change.
+ m_vecItems->SetPath(GetDirectoryPath());
+ return true;
+ }
+ return false;
+}
+
+std::shared_ptr<CPVRChannelGroup> CGUIWindowPVRBase::GetChannelGroup()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_channelGroup;
+}
+
+void CGUIWindowPVRBase::SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&group, bool bUpdate /* = true */)
+{
+ if (!group)
+ return;
+
+ std::shared_ptr<CPVRChannelGroup> updateChannelGroup;
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_channelGroup != group)
+ {
+ if (m_channelGroup)
+ m_channelGroup->Events().Unsubscribe(this);
+ m_channelGroup = std::move(group);
+ // we need to register the window to receive changes from the new group
+ m_channelGroup->Events().Subscribe(this, &CGUIWindowPVRBase::Notify);
+ if (bUpdate)
+ updateChannelGroup = m_channelGroup;
+ }
+ }
+
+ if (updateChannelGroup)
+ {
+ CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(updateChannelGroup);
+ Update(GetDirectoryPath());
+ }
+}
+
+bool CGUIWindowPVRBase::Update(const std::string& strDirectory, bool updateFilterPath /*= true*/)
+{
+ if (m_bUpdating)
+ {
+ // no concurrent updates
+ return false;
+ }
+
+ CUpdateGuard guard(m_bUpdating);
+
+ if (!GetChannelGroup())
+ {
+ // no updates before fully initialized
+ return false;
+ }
+
+ int iOldCount = m_vecItems->Size();
+ int iSelectedItem = m_viewControl.GetSelectedItem();
+ const std::string oldPath = m_vecItems->GetPath();
+
+ bool bReturn = CGUIMediaWindow::Update(strDirectory, updateFilterPath);
+
+ if (bReturn &&
+ iSelectedItem != -1) // something must have been selected
+ {
+ int iNewCount = m_vecItems->Size();
+ if (iOldCount > iNewCount && // at least one item removed by Update()
+ oldPath == m_vecItems->GetPath()) // update not due changing into another folder
+ {
+ // restore selected item if we just deleted one or more items.
+ if (iSelectedItem >= iNewCount)
+ iSelectedItem = iNewCount - 1;
+
+ m_viewControl.SetSelectedItem(iSelectedItem);
+ }
+ }
+
+ return bReturn;
+}
+
+void CGUIWindowPVRBase::UpdateButtons()
+{
+ CGUIMediaWindow::UpdateButtons();
+
+ const std::shared_ptr<CPVRChannelGroup> channelGroup = GetChannelGroup();
+ if (channelGroup)
+ {
+ SET_CONTROL_LABEL(CONTROL_BTNCHANNELGROUPS, g_localizeStrings.Get(19141) + ": " + channelGroup->GroupName());
+ }
+
+ m_channelGroupsSelector->SelectChannelGroup(channelGroup);
+}
+
+void CGUIWindowPVRBase::ShowProgressDialog(const std::string& strText, int iProgress)
+{
+ if (!m_progressHandle)
+ {
+ CGUIDialogExtendedProgressBar* loadingProgressDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS);
+ if (!loadingProgressDialog)
+ {
+ CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_EXT_PROGRESS!");
+ return;
+ }
+ m_progressHandle = loadingProgressDialog->GetHandle(g_localizeStrings.Get(19235)); // PVR manager is starting up
+ }
+
+ m_progressHandle->SetPercentage(static_cast<float>(iProgress));
+ m_progressHandle->SetText(strText);
+}
+
+void CGUIWindowPVRBase::HideProgressDialog()
+{
+ if (m_progressHandle)
+ {
+ m_progressHandle->MarkFinished();
+ m_progressHandle = nullptr;
+ }
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.h b/xbmc/pvr/windows/GUIWindowPVRBase.h
new file mode 100644
index 0000000..1fe27bb
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRBase.h
@@ -0,0 +1,138 @@
+/*
+ * 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 "threads/SystemClock.h"
+#include "windows/GUIMediaWindow.h"
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+#define CONTROL_BTNVIEWASICONS 2
+#define CONTROL_BTNSORTBY 3
+#define CONTROL_BTNSORTASC 4
+#define CONTROL_BTNGROUPITEMS 5
+#define CONTROL_BTNSHOWHIDDEN 6
+#define CONTROL_BTNSHOWDELETED 7
+#define CONTROL_BTNHIDEDISABLEDTIMERS 8
+#define CONTROL_BTNSHOWMODE 10
+#define CONTROL_LSTCHANNELGROUPS 11
+
+#define CONTROL_BTNCHANNELGROUPS 28
+#define CONTROL_BTNFILTERCHANNELS 31
+
+#define CONTROL_LABEL_HEADER1 29
+#define CONTROL_LABEL_HEADER2 30
+
+class CGUIDialogProgressBarHandle;
+
+namespace PVR
+{
+ enum class PVREvent;
+
+ enum EPGSelectAction
+ {
+ EPG_SELECT_ACTION_CONTEXT_MENU = 0,
+ EPG_SELECT_ACTION_SWITCH = 1,
+ EPG_SELECT_ACTION_INFO = 2,
+ EPG_SELECT_ACTION_RECORD = 3,
+ EPG_SELECT_ACTION_PLAY_RECORDING = 4,
+ EPG_SELECT_ACTION_SMART_SELECT = 5
+ };
+
+ class CPVRChannelGroup;
+ class CGUIPVRChannelGroupsSelector;
+
+ class CGUIWindowPVRBase : public CGUIMediaWindow
+ {
+ public:
+ ~CGUIWindowPVRBase() override;
+
+ void OnInitWindow() override;
+ void OnDeinitWindow(int nextWindowID) override;
+ bool OnMessage(CGUIMessage& message) override;
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+ void UpdateButtons() override;
+ bool OnAction(const CAction& action) override;
+ void SetInvalid() override;
+ bool CanBeActivated() const override;
+
+ bool UseFileDirectories() override { return false; }
+
+ /*!
+ * @brief CEventStream callback for PVR events.
+ * @param event The event.
+ */
+ void Notify(const PVREvent& event);
+ virtual void NotifyEvent(const PVREvent& event);
+
+ /*!
+ * @brief Refresh window content.
+ * @return true, if refresh succeeded, false otherwise.
+ */
+ bool DoRefresh() { return Refresh(true); }
+
+ bool ActivatePreviousChannelGroup();
+ bool ActivateNextChannelGroup();
+ bool OpenChannelGroupSelectionDialog();
+
+ protected:
+ CGUIWindowPVRBase(bool bRadio, int id, const std::string& xmlFile);
+
+ virtual std::string GetDirectoryPath() = 0;
+
+ virtual void ClearData();
+
+ /*!
+ * @brief Init this window's channel group with the currently active (the "playing") channel group.
+ * @return true if group could be set, false otherwise.
+ */
+ bool InitChannelGroup();
+
+ /*!
+ * @brief Get the channel group for this window.
+ * @return the group or null, if no group set.
+ */
+ std::shared_ptr<CPVRChannelGroup> GetChannelGroup();
+
+ /*!
+ * @brief Set a new channel group, start listening to this group, optionally update window content.
+ * @param group The new group.
+ * @param bUpdate if true, window content will be updated.
+ */
+ void SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&group, bool bUpdate = true);
+
+ virtual void UpdateSelectedItemPath();
+
+ CCriticalSection m_critSection;
+ std::string m_channelGroupPath;
+ bool m_bRadio;
+ std::atomic_bool m_bUpdating = {false};
+
+ private:
+ /*!
+ * @brief Show or update the progress dialog.
+ * @param strText The current status.
+ * @param iProgress The current progress in %.
+ */
+ void ShowProgressDialog(const std::string& strText, int iProgress);
+
+ /*!
+ * @brief Hide the progress dialog if it's visible.
+ */
+ void HideProgressDialog();
+
+ std::unique_ptr<CGUIPVRChannelGroupsSelector> m_channelGroupsSelector;
+ std::shared_ptr<CPVRChannelGroup> m_channelGroup;
+ XbmcThreads::EndTime<> m_refreshTimeout;
+ CGUIDialogProgressBarHandle* m_progressHandle; /*!< progress dialog that is displayed while the pvr manager is loading */
+ };
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp
new file mode 100644
index 0000000..2b476f7
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp
@@ -0,0 +1,414 @@
+/*
+ * 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 "GUIWindowPVRChannels.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIKeyboardFactory.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIRadioButtonControl.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/Key.h"
+#include "input/actions/Action.h"
+#include "pvr/PVRManager.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/channels/PVRChannelsPath.h"
+#include "pvr/dialogs/GUIDialogPVRChannelManager.h"
+#include "pvr/dialogs/GUIDialogPVRGroupManager.h"
+#include "pvr/epg/Epg.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsEPG.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+
+#include <mutex>
+#include <string>
+
+using namespace PVR;
+
+CGUIWindowPVRChannelsBase::CGUIWindowPVRChannelsBase(bool bRadio,
+ int id,
+ const std::string& xmlFile)
+ : CGUIWindowPVRBase(bRadio, id, xmlFile), m_bShowHiddenChannels(false)
+{
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().RegisterChannelNumberInputHandler(this);
+}
+
+CGUIWindowPVRChannelsBase::~CGUIWindowPVRChannelsBase()
+{
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().DeregisterChannelNumberInputHandler(
+ this);
+}
+
+std::string CGUIWindowPVRChannelsBase::GetRootPath() const
+{
+ //! @todo Would it make sense to change GetRootPath() declaration in CGUIMediaWindow
+ //! to be non-const to get rid of the const_cast's here?
+
+ CGUIWindowPVRChannelsBase* pThis = const_cast<CGUIWindowPVRChannelsBase*>(this);
+ if (pThis->InitChannelGroup())
+ return pThis->GetDirectoryPath();
+
+ return CGUIWindowPVRBase::GetRootPath();
+}
+
+void CGUIWindowPVRChannelsBase::GetContextButtons(int itemNumber, CContextButtons& buttons)
+{
+ // Add parent buttons before the Manage button
+ CGUIWindowPVRBase::GetContextButtons(itemNumber, buttons);
+
+ buttons.Add(CONTEXT_BUTTON_EDIT, 16106); /* Manage... */
+}
+
+bool CGUIWindowPVRChannelsBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
+{
+ if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
+ return false;
+
+ return OnContextButtonManage(m_vecItems->Get(itemNumber), button) ||
+ CGUIMediaWindow::OnContextButton(itemNumber, button);
+}
+
+bool CGUIWindowPVRChannelsBase::Update(const std::string& strDirectory,
+ bool updateFilterPath /* = true */)
+{
+ bool bReturn = CGUIWindowPVRBase::Update(strDirectory);
+
+ if (bReturn)
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ /* empty list for hidden channels */
+ if (m_vecItems->GetObjectCount() == 0 && m_bShowHiddenChannels)
+ {
+ /* show the visible channels instead */
+ m_bShowHiddenChannels = false;
+ lock.unlock();
+ Update(GetDirectoryPath());
+ }
+ }
+ return bReturn;
+}
+
+void CGUIWindowPVRChannelsBase::UpdateButtons()
+{
+ CGUIRadioButtonControl* btnShowHidden =
+ static_cast<CGUIRadioButtonControl*>(GetControl(CONTROL_BTNSHOWHIDDEN));
+ if (btnShowHidden)
+ {
+ btnShowHidden->SetVisible(CServiceBroker::GetPVRManager()
+ .ChannelGroups()
+ ->GetGroupAll(m_bRadio)
+ ->GetNumHiddenChannels() > 0);
+ btnShowHidden->SetSelected(m_bShowHiddenChannels);
+ }
+
+ CGUIWindowPVRBase::UpdateButtons();
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, m_bShowHiddenChannels ? g_localizeStrings.Get(19022)
+ : GetChannelGroup()->GroupName());
+}
+
+bool CGUIWindowPVRChannelsBase::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ 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:
+ AppendChannelNumberCharacter(static_cast<char>(action.GetID() - REMOTE_0) + '0');
+ return true;
+
+ case ACTION_CHANNEL_NUMBER_SEP:
+ AppendChannelNumberCharacter(CPVRChannelNumber::SEPARATOR);
+ return true;
+ }
+
+ return CGUIWindowPVRBase::OnAction(action);
+}
+
+bool CGUIWindowPVRChannelsBase::OnMessage(CGUIMessage& message)
+{
+ bool bReturn = false;
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_WINDOW_INIT:
+ {
+ const CPVRChannelsPath path(message.GetStringParam(0));
+ if (path.IsValid() && path.IsChannelGroup())
+ {
+ // if a path to a channel group is given we must init
+ // that group instead of last played/selected group
+ m_channelGroupPath = message.GetStringParam(0);
+ }
+ break;
+ }
+
+ case GUI_MSG_CLICKED:
+ if (message.GetSenderId() == m_viewControl.GetCurrentControl())
+ {
+ if (message.GetParam1() == ACTION_SELECT_ITEM ||
+ message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)
+ {
+ // If direct channel number input is active, select the entered channel.
+ if (CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNumberInputHandler()
+ .CheckInputAndExecuteAction())
+ {
+ bReturn = true;
+ break;
+ }
+ }
+
+ int iItem = m_viewControl.GetSelectedItem();
+ if (iItem >= 0 && iItem < m_vecItems->Size())
+ {
+ bReturn = true;
+ switch (message.GetParam1())
+ {
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ case ACTION_PLAYER_PLAY:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ *(m_vecItems->Get(iItem)), true);
+ break;
+ case ACTION_SHOW_INFO:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ShowEPGInfo(
+ *(m_vecItems->Get(iItem)));
+ break;
+ case ACTION_DELETE_ITEM:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().HideChannel(
+ *(m_vecItems->Get(iItem)));
+ break;
+ case ACTION_CONTEXT_MENU:
+ case ACTION_MOUSE_RIGHT_CLICK:
+ OnPopupMenu(iItem);
+ break;
+ default:
+ bReturn = false;
+ break;
+ }
+ }
+ }
+ else if (message.GetSenderId() == CONTROL_BTNSHOWHIDDEN)
+ {
+ CGUIRadioButtonControl* radioButton =
+ static_cast<CGUIRadioButtonControl*>(GetControl(CONTROL_BTNSHOWHIDDEN));
+ if (radioButton)
+ {
+ m_bShowHiddenChannels = radioButton->IsSelected();
+ Update(GetDirectoryPath());
+ }
+
+ bReturn = true;
+ }
+ else if (message.GetSenderId() == CONTROL_BTNFILTERCHANNELS)
+ {
+ std::string filter = GetProperty("filter").asString();
+ CGUIKeyboardFactory::ShowAndGetFilter(filter, false);
+ OnFilterItems(filter);
+ UpdateButtons();
+
+ bReturn = true;
+ }
+ break;
+
+ case GUI_MSG_REFRESH_LIST:
+ {
+ switch (static_cast<PVREvent>(message.GetParam1()))
+ {
+ case PVREvent::ChannelGroup:
+ case PVREvent::CurrentItem:
+ case PVREvent::Epg:
+ case PVREvent::EpgActiveItem:
+ case PVREvent::EpgContainer:
+ case PVREvent::RecordingsInvalidated:
+ case PVREvent::Timers:
+ SetInvalid();
+ break;
+
+ case PVREvent::ChannelGroupInvalidated:
+ Refresh(true);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+
+ return bReturn || CGUIWindowPVRBase::OnMessage(message);
+}
+
+bool CGUIWindowPVRChannelsBase::OnContextButtonManage(const CFileItemPtr& item,
+ CONTEXT_BUTTON button)
+{
+ bool bReturn = false;
+
+ if (button == CONTEXT_BUTTON_EDIT)
+ {
+ // Create context sub menu
+ CContextButtons buttons;
+ buttons.Add(CONTEXT_BUTTON_GROUP_MANAGER, 19048);
+ buttons.Add(CONTEXT_BUTTON_CHANNEL_MANAGER, 19199);
+ buttons.Add(CONTEXT_BUTTON_UPDATE_EPG, 19251);
+
+ // Get the response
+ int choice = CGUIDialogContextMenu::ShowAndGetChoice(buttons);
+ if (choice >= 0)
+ {
+ switch (static_cast<CONTEXT_BUTTON>(choice))
+ {
+ case CONTEXT_BUTTON_GROUP_MANAGER:
+ ShowGroupManager();
+ break;
+ case CONTEXT_BUTTON_CHANNEL_MANAGER:
+ ShowChannelManager();
+ break;
+ case CONTEXT_BUTTON_UPDATE_EPG:
+ UpdateEpg(item);
+ break;
+ default:
+ break;
+ }
+
+ // Refresh the list in case anything was changed
+ Refresh(true);
+ }
+
+ bReturn = true;
+ }
+
+ return bReturn;
+}
+
+void CGUIWindowPVRChannelsBase::UpdateEpg(const CFileItemPtr& item)
+{
+ const std::shared_ptr<CPVRChannel> channel(item->GetPVRChannelInfoTag());
+
+ if (!CGUIDialogYesNo::ShowAndGetInput(
+ CVariant{19251}, // "Update guide information"
+ CVariant{19252}, // "Schedule guide update for this channel?"
+ CVariant{""}, CVariant{channel->ChannelName()}))
+ return;
+
+ const std::shared_ptr<CPVREpg> epg = channel->GetEPG();
+ if (epg)
+ {
+ epg->ForceUpdate();
+
+ const std::string strMessage =
+ StringUtils::Format("{}: '{}'",
+ g_localizeStrings.Get(19253), // "Guide update scheduled for channel"
+ channel->ChannelName());
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info,
+ g_localizeStrings.Get(19166), // "PVR information"
+ strMessage);
+ }
+ else
+ {
+ const std::string strMessage =
+ StringUtils::Format("{}: '{}'",
+ g_localizeStrings.Get(19254), // "Guide update failed for channel"
+ channel->ChannelName());
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
+ g_localizeStrings.Get(19166), // "PVR information"
+ strMessage);
+ }
+}
+
+void CGUIWindowPVRChannelsBase::ShowChannelManager()
+{
+ CGUIDialogPVRChannelManager* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRChannelManager>(
+ WINDOW_DIALOG_PVR_CHANNEL_MANAGER);
+ if (!dialog)
+ return;
+
+ dialog->SetRadio(m_bRadio);
+
+ const int iItem = m_viewControl.GetSelectedItem();
+ dialog->Open(iItem >= 0 && iItem < m_vecItems->Size() ? m_vecItems->Get(iItem) : nullptr);
+}
+
+void CGUIWindowPVRChannelsBase::ShowGroupManager()
+{
+ /* Load group manager dialog */
+ CGUIDialogPVRGroupManager* pDlgInfo =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRGroupManager>(
+ WINDOW_DIALOG_PVR_GROUP_MANAGER);
+ if (!pDlgInfo)
+ return;
+
+ pDlgInfo->SetRadio(m_bRadio);
+ pDlgInfo->Open();
+}
+
+void CGUIWindowPVRChannelsBase::OnInputDone()
+{
+ const CPVRChannelNumber channelNumber = GetChannelNumber();
+ if (channelNumber.IsValid())
+ {
+ int itemIndex = 0;
+ for (const CFileItemPtr& channel : *m_vecItems)
+ {
+ if (channel->GetPVRChannelGroupMemberInfoTag()->ChannelNumber() == channelNumber)
+ {
+ m_viewControl.SetSelectedItem(itemIndex);
+ return;
+ }
+ ++itemIndex;
+ }
+ }
+}
+
+void CGUIWindowPVRChannelsBase::GetChannelNumbers(std::vector<std::string>& channelNumbers)
+{
+ const std::shared_ptr<CPVRChannelGroup> group = GetChannelGroup();
+ if (group)
+ group->GetChannelNumbers(channelNumbers);
+}
+
+CGUIWindowPVRTVChannels::CGUIWindowPVRTVChannels()
+ : CGUIWindowPVRChannelsBase(false, WINDOW_TV_CHANNELS, "MyPVRChannels.xml")
+{
+}
+
+std::string CGUIWindowPVRTVChannels::GetDirectoryPath()
+{
+ return CPVRChannelsPath(false, m_bShowHiddenChannels, GetChannelGroup()->GroupName());
+}
+
+CGUIWindowPVRRadioChannels::CGUIWindowPVRRadioChannels()
+ : CGUIWindowPVRChannelsBase(true, WINDOW_RADIO_CHANNELS, "MyPVRChannels.xml")
+{
+}
+
+std::string CGUIWindowPVRRadioChannels::GetDirectoryPath()
+{
+ return CPVRChannelsPath(true, m_bShowHiddenChannels, GetChannelGroup()->GroupName());
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.h b/xbmc/pvr/windows/GUIWindowPVRChannels.h
new file mode 100644
index 0000000..ef8e848
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRChannels.h
@@ -0,0 +1,65 @@
+/*
+ * 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 "dialogs/GUIDialogContextMenu.h"
+#include "pvr/PVRChannelNumberInputHandler.h"
+#include "pvr/windows/GUIWindowPVRBase.h"
+
+#include <string>
+
+namespace PVR
+{
+class CGUIWindowPVRChannelsBase : public CGUIWindowPVRBase, public CPVRChannelNumberInputHandler
+{
+public:
+ CGUIWindowPVRChannelsBase(bool bRadio, int id, const std::string& xmlFile);
+ ~CGUIWindowPVRChannelsBase() override;
+
+ std::string GetRootPath() const override;
+ bool OnMessage(CGUIMessage& message) override;
+ void GetContextButtons(int itemNumber, CContextButtons& buttons) override;
+ bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override;
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+ void UpdateButtons() override;
+ bool OnAction(const CAction& action) override;
+
+ // CPVRChannelNumberInputHandler implementation
+ void GetChannelNumbers(std::vector<std::string>& channelNumbers) override;
+ void OnInputDone() override;
+
+private:
+ bool OnContextButtonManage(const CFileItemPtr& item, CONTEXT_BUTTON button);
+
+ void ShowChannelManager();
+ void ShowGroupManager();
+ void UpdateEpg(const CFileItemPtr& item);
+
+protected:
+ bool m_bShowHiddenChannels;
+};
+
+class CGUIWindowPVRTVChannels : public CGUIWindowPVRChannelsBase
+{
+public:
+ CGUIWindowPVRTVChannels();
+
+protected:
+ std::string GetDirectoryPath() override;
+};
+
+class CGUIWindowPVRRadioChannels : public CGUIWindowPVRChannelsBase
+{
+public:
+ CGUIWindowPVRRadioChannels();
+
+protected:
+ std::string GetDirectoryPath() override;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp
new file mode 100644
index 0000000..f4c247f
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp
@@ -0,0 +1,973 @@
+/*
+ * 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 "GUIWindowPVRGuide.h"
+
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "addons/Skin.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/PVRPlaybackState.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/channels/PVRChannelsPath.h"
+#include "pvr/epg/EpgChannelData.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/guilib/GUIEPGGridContainer.h"
+#include "pvr/guilib/PVRGUIActionsChannels.h"
+#include "pvr/guilib/PVRGUIActionsEPG.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/guilib/PVRGUIActionsTimers.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/timers/PVRTimers.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "view/GUIViewState.h"
+
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+using namespace KODI::MESSAGING;
+using namespace PVR;
+using namespace std::chrono_literals;
+
+CGUIWindowPVRGuideBase::CGUIWindowPVRGuideBase(bool bRadio, int id, const std::string& xmlFile)
+ : CGUIWindowPVRBase(bRadio, id, xmlFile)
+{
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().RegisterChannelNumberInputHandler(this);
+}
+
+CGUIWindowPVRGuideBase::~CGUIWindowPVRGuideBase()
+{
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().DeregisterChannelNumberInputHandler(
+ this);
+
+ m_bRefreshTimelineItems = false;
+ m_bSyncRefreshTimelineItems = false;
+ StopRefreshTimelineItemsThread();
+}
+
+CGUIEPGGridContainer* CGUIWindowPVRGuideBase::GetGridControl()
+{
+ return dynamic_cast<CGUIEPGGridContainer*>(GetControl(m_viewControl.GetCurrentControl()));
+}
+
+void CGUIWindowPVRGuideBase::InitEpgGridControl()
+{
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ {
+ CPVRManager& mgr = CServiceBroker::GetPVRManager();
+
+ const std::shared_ptr<CPVRChannel> channel = mgr.ChannelGroups()->GetByPath(
+ mgr.Get<PVR::GUI::Channels>().GetSelectedChannelPath(m_bRadio));
+
+ if (channel)
+ {
+ m_bChannelSelectionRestored = epgGridContainer->SetChannel(channel);
+ epgGridContainer->JumpToDate(
+ mgr.PlaybackState()->GetPlaybackTime(channel->ClientID(), channel->UniqueID()));
+ }
+ else
+ {
+ m_bChannelSelectionRestored = false;
+ epgGridContainer->JumpToNow();
+ }
+
+ if (!epgGridContainer->HasData())
+ m_bSyncRefreshTimelineItems = true; // force data update on first window open
+ }
+
+ StartRefreshTimelineItemsThread();
+}
+
+void CGUIWindowPVRGuideBase::ClearData()
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_cachedChannelGroup.reset();
+ }
+
+ CGUIWindowPVRBase::ClearData();
+}
+
+void CGUIWindowPVRGuideBase::OnInitWindow()
+{
+ if (m_guiState)
+ m_viewControl.SetCurrentView(m_guiState->GetViewAsControl(), false);
+
+ if (InitChannelGroup()) // no channels -> lazy init
+ InitEpgGridControl();
+
+ CGUIWindowPVRBase::OnInitWindow();
+}
+
+void CGUIWindowPVRGuideBase::OnDeinitWindow(int nextWindowID)
+{
+ StopRefreshTimelineItemsThread();
+
+ m_bChannelSelectionRestored = false;
+
+ CGUIDialog* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_PVR_GUIDE_CONTROLS);
+ if (dialog && dialog->IsDialogRunning())
+ {
+ dialog->Close();
+ }
+
+ CGUIWindowPVRBase::OnDeinitWindow(nextWindowID);
+}
+
+void CGUIWindowPVRGuideBase::StartRefreshTimelineItemsThread()
+{
+ StopRefreshTimelineItemsThread();
+ m_refreshTimelineItemsThread.reset(new CPVRRefreshTimelineItemsThread(this));
+ m_refreshTimelineItemsThread->Create();
+}
+
+void CGUIWindowPVRGuideBase::StopRefreshTimelineItemsThread()
+{
+ if (m_refreshTimelineItemsThread)
+ m_refreshTimelineItemsThread->Stop();
+}
+
+void CGUIWindowPVRGuideBase::NotifyEvent(const PVREvent& event)
+{
+ if (event == PVREvent::Epg || event == PVREvent::EpgContainer ||
+ event == PVREvent::ChannelGroupInvalidated || event == PVREvent::ChannelGroup)
+ {
+ m_bRefreshTimelineItems = true;
+ // no base class call => do async refresh
+ return;
+ }
+ else if (event == PVREvent::ChannelPlaybackStopped)
+ {
+ if (m_guiState && m_guiState->GetSortMethod().sortBy == SortByLastPlayed)
+ {
+ // set dirty to force sync refresh
+ m_bSyncRefreshTimelineItems = true;
+ }
+ }
+
+ // do sync refresh if dirty
+ CGUIWindowPVRBase::NotifyEvent(event);
+}
+
+void CGUIWindowPVRGuideBase::SetInvalid()
+{
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ epgGridContainer->SetInvalid();
+
+ CGUIWindowPVRBase::SetInvalid();
+}
+
+void CGUIWindowPVRGuideBase::GetContextButtons(int itemNumber, CContextButtons& buttons)
+{
+ CGUIWindowPVRBase::GetContextButtons(itemNumber, buttons);
+
+ buttons.Add(CONTEXT_BUTTON_NAVIGATE, 19326); // Navigate...
+}
+
+void CGUIWindowPVRGuideBase::UpdateSelectedItemPath()
+{
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ {
+ const std::shared_ptr<CPVRChannelGroupMember> groupMember =
+ epgGridContainer->GetSelectedChannelGroupMember();
+ if (groupMember)
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().SetSelectedChannelPath(
+ m_bRadio, groupMember->Path());
+ }
+}
+
+void CGUIWindowPVRGuideBase::UpdateButtons()
+{
+ CGUIWindowPVRBase::UpdateButtons();
+
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, g_localizeStrings.Get(19032));
+
+ const std::shared_ptr<CPVRChannelGroup> group = GetChannelGroup();
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, group ? group->GroupName() : "");
+}
+
+bool CGUIWindowPVRGuideBase::Update(const std::string& strDirectory,
+ bool updateFilterPath /* = true */)
+{
+ if (m_bUpdating)
+ {
+ // Prevent concurrent updates. Instead, let the timeline items refresh thread pick it up later.
+ m_bRefreshTimelineItems = true;
+ return true;
+ }
+
+ bool bReturn = CGUIWindowPVRBase::Update(strDirectory, updateFilterPath);
+
+ if (bReturn && !m_bChannelSelectionRestored)
+ {
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ m_bChannelSelectionRestored = epgGridContainer->SetChannel(
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetSelectedChannelPath(
+ m_bRadio));
+ }
+
+ return bReturn;
+}
+
+bool CGUIWindowPVRGuideBase::GetDirectory(const std::string& strDirectory, CFileItemList& items)
+{
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_cachedChannelGroup && *m_cachedChannelGroup != *GetChannelGroup())
+ {
+ // channel group change and not very first open of this window. force immediate update.
+ m_bSyncRefreshTimelineItems = true;
+ }
+ }
+
+ if (m_bSyncRefreshTimelineItems)
+ m_refreshTimelineItemsThread->DoRefresh(true);
+
+ const CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ {
+ const std::unique_ptr<CFileItemList> newTimeline = GetGridControl()->GetCurrentTimeLineItems();
+ items.RemoveDiscCache(GetID());
+ items.Assign(*newTimeline, false);
+ }
+
+ return true;
+}
+
+void CGUIWindowPVRGuideBase::FormatAndSort(CFileItemList& items)
+{
+ // Speedup: Nothing to do here as sorting was already done in RefreshTimelineItems
+ return;
+}
+
+CFileItemPtr CGUIWindowPVRGuideBase::GetCurrentListItem(int offset /*= 0*/)
+{
+ const CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ return epgGridContainer->GetSelectedGridItem(offset);
+
+ return {};
+}
+
+int CGUIWindowPVRGuideBase::GetCurrentListItemIndex(const std::shared_ptr<CFileItem>& item)
+{
+ return item ? item->GetProperty("TimelineIndex").asInteger() : -1;
+}
+
+bool CGUIWindowPVRGuideBase::ShouldNavigateToGridContainer(int iAction)
+{
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ CGUIControl* control = GetControl(CONTROL_LSTCHANNELGROUPS);
+ if (epgGridContainer && control && GetFocusedControlID() == control->GetID())
+ {
+ int iNavigationId = control->GetAction(iAction).GetNavigation();
+ if (iNavigationId > 0)
+ {
+ control = epgGridContainer;
+ while (control !=
+ this) // navigation target could be the grid control or one of its parent controls.
+ {
+ if (iNavigationId == control->GetID())
+ {
+ // channel group selector control's target for the action is the grid control
+ return true;
+ }
+ control = control->GetParentControl();
+ }
+ }
+ }
+ return false;
+}
+
+bool CGUIWindowPVRGuideBase::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_UP:
+ case ACTION_MOVE_DOWN:
+ case ACTION_MOVE_LEFT:
+ case ACTION_MOVE_RIGHT:
+ {
+ // Check whether grid container is configured as channel group selector's navigation target for the given action.
+ if (ShouldNavigateToGridContainer(action.GetID()))
+ {
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ {
+ CGUIWindowPVRBase::OnAction(action);
+
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_UP:
+ epgGridContainer->GoToBottom();
+ return true;
+ case ACTION_MOVE_DOWN:
+ epgGridContainer->GoToTop();
+ return true;
+ case ACTION_MOVE_LEFT:
+ epgGridContainer->GoToMostRight();
+ return true;
+ case ACTION_MOVE_RIGHT:
+ epgGridContainer->GoToMostLeft();
+ return true;
+ default:
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case REMOTE_0:
+ if (GetCurrentDigitCount() == 0)
+ {
+ // single zero input is handled by epg grid container
+ break;
+ }
+ // fall-thru is intended
+ [[fallthrough]];
+ 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:
+ AppendChannelNumberCharacter(static_cast<char>(action.GetID() - REMOTE_0) + '0');
+ return true;
+
+ case ACTION_CHANNEL_NUMBER_SEP:
+ AppendChannelNumberCharacter(CPVRChannelNumber::SEPARATOR);
+ return true;
+ }
+
+ return CGUIWindowPVRBase::OnAction(action);
+}
+
+void CGUIWindowPVRGuideBase::RefreshView(CGUIMessage& message, bool bInitGridControl)
+{
+ CGUIWindowPVRBase::OnMessage(message);
+
+ // force grid data update
+ m_bSyncRefreshTimelineItems = true;
+
+ if (bInitGridControl)
+ InitEpgGridControl();
+
+ Refresh(true);
+}
+
+bool CGUIWindowPVRGuideBase::OnMessage(CGUIMessage& message)
+{
+ bool bReturn = false;
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_WINDOW_INIT:
+ {
+ const CPVRChannelsPath path(message.GetStringParam(0));
+ if (path.IsValid() && path.IsChannelGroup())
+ {
+ // if a path to a channel group is given we must init
+ // that group instead of last played/selected group
+ m_channelGroupPath = message.GetStringParam(0);
+ }
+ break;
+ }
+
+ case GUI_MSG_ITEM_SELECTED:
+ message.SetParam1(GetCurrentListItemIndex(GetCurrentListItem()));
+ bReturn = true;
+ break;
+
+ case GUI_MSG_CLICKED:
+ {
+ if (message.GetSenderId() == m_viewControl.GetCurrentControl())
+ {
+ if (message.GetParam1() == ACTION_SELECT_ITEM ||
+ message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)
+ {
+ // If direct channel number input is active, select the entered channel.
+ if (CServiceBroker::GetPVRManager()
+ .Get<PVR::GUI::Channels>()
+ .GetChannelNumberInputHandler()
+ .CheckInputAndExecuteAction())
+ {
+ bReturn = true;
+ break;
+ }
+ }
+
+ const std::shared_ptr<CFileItem> pItem = GetCurrentListItem();
+ if (pItem)
+ {
+ switch (message.GetParam1())
+ {
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ switch (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_EPG_SELECTACTION))
+ {
+ case EPG_SELECT_ACTION_CONTEXT_MENU:
+ OnPopupMenu(GetCurrentListItemIndex(pItem));
+ bReturn = true;
+ break;
+ case EPG_SELECT_ACTION_SWITCH:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(*pItem,
+ true);
+ bReturn = true;
+ break;
+ case EPG_SELECT_ACTION_PLAY_RECORDING:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayRecording(*pItem,
+ true);
+ bReturn = true;
+ break;
+ case EPG_SELECT_ACTION_INFO:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ShowEPGInfo(*pItem);
+ bReturn = true;
+ break;
+ case EPG_SELECT_ACTION_RECORD:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().ToggleTimer(*pItem);
+ bReturn = true;
+ break;
+ case EPG_SELECT_ACTION_SMART_SELECT:
+ {
+ const std::shared_ptr<CPVREpgInfoTag> tag(pItem->GetEPGInfoTag());
+ if (tag)
+ {
+ const CDateTime start(tag->StartAsUTC());
+ const CDateTime end(tag->EndAsUTC());
+ const CDateTime now(CDateTime::GetUTCDateTime());
+
+ if (start <= now && now <= end)
+ {
+ // current event
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ *pItem, true);
+ }
+ else if (now < start)
+ {
+ // future event
+ if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(tag))
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().EditTimer(*pItem);
+ else
+ {
+ bool bCanRecord = true;
+ const std::shared_ptr<CPVRChannel> channel = CPVRItem(pItem).GetChannel();
+ if (channel)
+ bCanRecord = channel->CanRecord();
+
+ const int iTextID =
+ bCanRecord
+ ? 19302 // "Do you want to record the selected programme or to switch to the current programme?"
+ : 19344; // "Do you want to set a reminder for the selected programme or to switch to the current programme?"
+ const int iNoButtonID = bCanRecord ? 264 // No => "Record"
+ : 826; // "Set reminder"
+
+ HELPERS::DialogResponse ret =
+ HELPERS::ShowYesNoDialogText(CVariant{19096}, // "Smart select"
+ CVariant{iTextID}, CVariant{iNoButtonID},
+ CVariant{19165}); // Yes => "Switch"
+ if (ret == HELPERS::DialogResponse::CHOICE_NO)
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(*pItem,
+ false);
+ else if (ret == HELPERS::DialogResponse::CHOICE_YES)
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(
+ *pItem, true);
+ }
+ }
+ else
+ {
+ // past event
+ if (CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(tag))
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayRecording(
+ *pItem, true);
+ else if (tag->IsPlayable())
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayEpgTag(
+ *pItem);
+ else
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ShowEPGInfo(*pItem);
+ }
+ bReturn = true;
+ }
+ break;
+ }
+ }
+ break;
+ case ACTION_SHOW_INFO:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ShowEPGInfo(*pItem);
+ bReturn = true;
+ break;
+ case ACTION_PLAYER_PLAY:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(*pItem,
+ true);
+ bReturn = true;
+ break;
+ case ACTION_RECORD:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().ToggleTimer(*pItem);
+ bReturn = true;
+ break;
+ case ACTION_PVR_SHOW_TIMER_RULE:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimerRule(*pItem, true,
+ false);
+ bReturn = true;
+ break;
+ case ACTION_CONTEXT_MENU:
+ case ACTION_MOUSE_RIGHT_CLICK:
+ OnPopupMenu(GetCurrentListItemIndex(pItem));
+ bReturn = true;
+ break;
+ }
+ }
+ }
+ else if (message.GetSenderId() == CONTROL_BTNVIEWASICONS ||
+ message.GetSenderId() == CONTROL_BTNSORTBY ||
+ message.GetSenderId() == CONTROL_BTNSORTASC)
+ {
+ RefreshView(message, false);
+ bReturn = true;
+ }
+ break;
+ }
+ case GUI_MSG_CHANGE_SORT_DIRECTION:
+ case GUI_MSG_CHANGE_SORT_METHOD:
+ case GUI_MSG_CHANGE_VIEW_MODE:
+ {
+ RefreshView(message, message.GetMessage() == GUI_MSG_CHANGE_VIEW_MODE);
+ bReturn = true;
+ break;
+ }
+ case GUI_MSG_REFRESH_LIST:
+ {
+ switch (static_cast<PVREvent>(message.GetParam1()))
+ {
+ case PVREvent::ManagerStarted:
+ if (InitChannelGroup())
+ InitEpgGridControl();
+ break;
+
+ case PVREvent::ChannelGroup:
+ case PVREvent::ChannelGroupInvalidated:
+ case PVREvent::ClientsInvalidated:
+ case PVREvent::ChannelPlaybackStopped:
+ case PVREvent::Epg:
+ case PVREvent::EpgContainer:
+ if (InitChannelGroup())
+ Refresh(true);
+ break;
+
+ case PVREvent::Timers:
+ case PVREvent::TimersInvalidated:
+ SetInvalid();
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ case GUI_MSG_SYSTEM_WAKE:
+ GotoCurrentProgramme();
+ bReturn = true;
+ break;
+ }
+
+ return bReturn || CGUIWindowPVRBase::OnMessage(message);
+}
+
+bool CGUIWindowPVRGuideBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
+{
+ if (OnContextButtonNavigate(button))
+ return true;
+
+ if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
+ return false;
+
+ return CGUIMediaWindow::OnContextButton(itemNumber, button);
+}
+
+namespace
+{
+
+template<typename A>
+class CContextMenuFunctions : public CContextButtons
+{
+public:
+ explicit CContextMenuFunctions(A* instance) : m_instance(instance) {}
+
+ void Add(bool (A::*function)(), unsigned int resId)
+ {
+ CContextButtons::Add(size(), resId);
+ m_functions.emplace_back(std::bind(function, m_instance));
+ }
+
+ bool Call(int idx)
+ {
+ if (idx < 0 || idx >= static_cast<int>(m_functions.size()))
+ return false;
+
+ return m_functions[idx]();
+ }
+
+private:
+ A* m_instance = nullptr;
+ std::vector<std::function<bool()>> m_functions;
+};
+
+} // unnamed namespace
+
+bool CGUIWindowPVRGuideBase::OnContextButtonNavigate(CONTEXT_BUTTON button)
+{
+ bool bReturn = false;
+
+ if (button == CONTEXT_BUTTON_NAVIGATE)
+ {
+ if (g_SkinInfo->HasSkinFile("DialogPVRGuideControls.xml"))
+ {
+ // use controls dialog
+ CGUIDialog* dialog =
+ CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_PVR_GUIDE_CONTROLS);
+ if (dialog && !dialog->IsDialogRunning())
+ {
+ dialog->Open();
+ }
+ }
+ else
+ {
+ // use context menu
+ CContextMenuFunctions<CGUIWindowPVRGuideBase> buttons(this);
+ buttons.Add(&CGUIWindowPVRGuideBase::GotoBegin, 19063); // First programme
+ buttons.Add(&CGUIWindowPVRGuideBase::Go12HoursBack, 19317); // 12 hours back
+ buttons.Add(&CGUIWindowPVRGuideBase::GotoCurrentProgramme, 19070); // Current programme
+ buttons.Add(&CGUIWindowPVRGuideBase::Go12HoursForward, 19318); // 12 hours forward
+ buttons.Add(&CGUIWindowPVRGuideBase::GotoEnd, 19064); // Last programme
+ buttons.Add(&CGUIWindowPVRGuideBase::OpenDateSelectionDialog, 19288); // Date selector
+ buttons.Add(&CGUIWindowPVRGuideBase::GotoFirstChannel, 19322); // First channel
+ if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV() ||
+ CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio() ||
+ CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingEpgTag())
+ buttons.Add(&CGUIWindowPVRGuideBase::GotoPlayingChannel, 19323); // Playing channel
+ buttons.Add(&CGUIWindowPVRGuideBase::GotoLastChannel, 19324); // Last channel
+ buttons.Add(&CGUIWindowPVRBase::ActivatePreviousChannelGroup, 19319); // Previous group
+ buttons.Add(&CGUIWindowPVRBase::ActivateNextChannelGroup, 19320); // Next group
+ buttons.Add(&CGUIWindowPVRBase::OpenChannelGroupSelectionDialog, 19321); // Group selector
+
+ int buttonIdx = 0;
+ int lastButtonIdx = 2; // initially select "Current programme"
+
+ // loop until canceled
+ while (buttonIdx >= 0)
+ {
+ buttonIdx = CGUIDialogContextMenu::Show(buttons, lastButtonIdx);
+ lastButtonIdx = buttonIdx;
+ buttons.Call(buttonIdx);
+ }
+ }
+ bReturn = true;
+ }
+
+ return bReturn;
+}
+
+bool CGUIWindowPVRGuideBase::RefreshTimelineItems()
+{
+ if (m_bRefreshTimelineItems || m_bSyncRefreshTimelineItems)
+ {
+ m_bRefreshTimelineItems = false;
+ m_bSyncRefreshTimelineItems = false;
+
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ if (epgGridContainer)
+ {
+ const std::shared_ptr<CPVRChannelGroup> group(GetChannelGroup());
+ if (!group)
+ return false;
+
+ CPVREpgContainer& epgContainer = CServiceBroker::GetPVRManager().EpgContainer();
+
+ const std::pair<CDateTime, CDateTime> dates = epgContainer.GetFirstAndLastEPGDate();
+ CDateTime startDate = dates.first;
+ CDateTime endDate = dates.second;
+ const CDateTime currentDate = CDateTime::GetUTCDateTime();
+
+ if (!startDate.IsValid())
+ startDate = currentDate;
+
+ if (!endDate.IsValid() || endDate < startDate)
+ endDate = startDate;
+
+ // limit start to past days to display
+ const int iPastDays = epgContainer.GetPastDaysToDisplay();
+ const CDateTime maxPastDate(currentDate - CDateTimeSpan(iPastDays, 0, 0, 0));
+ if (startDate < maxPastDate)
+ startDate = maxPastDate;
+
+ // limit end to future days to display
+ const int iFutureDays = epgContainer.GetFutureDaysToDisplay();
+ const CDateTime maxFutureDate(currentDate + CDateTimeSpan(iFutureDays, 0, 0, 0));
+ if (endDate > maxFutureDate)
+ endDate = maxFutureDate;
+
+ std::unique_ptr<CFileItemList> channels(new CFileItemList);
+ const std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers =
+ group->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE);
+
+ for (const auto& groupMember : groupMembers)
+ {
+ channels->Add(std::make_shared<CFileItem>(groupMember));
+ }
+
+ if (m_guiState)
+ channels->Sort(m_guiState->GetSortMethod());
+
+ epgGridContainer->SetTimelineItems(channels, startDate, endDate);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_cachedChannelGroup = group;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIWindowPVRGuideBase::GotoBegin()
+{
+ GetGridControl()->GoToBegin();
+ return true;
+}
+
+bool CGUIWindowPVRGuideBase::GotoEnd()
+{
+ GetGridControl()->GoToEnd();
+ return true;
+}
+
+bool CGUIWindowPVRGuideBase::GotoCurrentProgramme()
+{
+ const CPVRManager& mgr = CServiceBroker::GetPVRManager();
+ std::shared_ptr<CPVRChannel> channel = mgr.PlaybackState()->GetPlayingChannel();
+
+ if (!channel)
+ {
+ const std::shared_ptr<CPVREpgInfoTag> playingTag = mgr.PlaybackState()->GetPlayingEpgTag();
+ if (playingTag)
+ channel = mgr.ChannelGroups()->GetChannelForEpgTag(playingTag);
+ }
+
+ if (channel)
+ GetGridControl()->GoToDate(
+ mgr.PlaybackState()->GetPlaybackTime(channel->ClientID(), channel->UniqueID()));
+ else
+ GetGridControl()->GoToNow();
+
+ return true;
+}
+
+bool CGUIWindowPVRGuideBase::OpenDateSelectionDialog()
+{
+ bool bReturn = false;
+
+ KODI::TIME::SystemTime date;
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ epgGridContainer->GetSelectedDate().GetAsSystemTime(date);
+
+ if (CGUIDialogNumeric::ShowAndGetDate(date, g_localizeStrings.Get(19288))) /* Go to date */
+ {
+ epgGridContainer->GoToDate(CDateTime(date));
+ bReturn = true;
+ }
+
+ return bReturn;
+}
+
+bool CGUIWindowPVRGuideBase::Go12HoursBack()
+{
+ return GotoDate(-12);
+}
+
+bool CGUIWindowPVRGuideBase::Go12HoursForward()
+{
+ return GotoDate(+12);
+}
+
+bool CGUIWindowPVRGuideBase::GotoDate(int deltaHours)
+{
+ CGUIEPGGridContainer* epgGridContainer = GetGridControl();
+ epgGridContainer->GoToDate(epgGridContainer->GetSelectedDate() +
+ CDateTimeSpan(0, deltaHours, 0, 0));
+ return true;
+}
+
+bool CGUIWindowPVRGuideBase::GotoFirstChannel()
+{
+ GetGridControl()->GoToFirstChannel();
+ return true;
+}
+
+bool CGUIWindowPVRGuideBase::GotoLastChannel()
+{
+ GetGridControl()->GoToLastChannel();
+ return true;
+}
+
+bool CGUIWindowPVRGuideBase::GotoPlayingChannel()
+{
+ const CPVRManager& mgr = CServiceBroker::GetPVRManager();
+ std::shared_ptr<CPVRChannel> channel = mgr.PlaybackState()->GetPlayingChannel();
+
+ if (!channel)
+ {
+ const std::shared_ptr<CPVREpgInfoTag> playingTag = mgr.PlaybackState()->GetPlayingEpgTag();
+ if (playingTag)
+ channel = mgr.ChannelGroups()->GetChannelForEpgTag(playingTag);
+ }
+
+ if (channel)
+ {
+ GetGridControl()->SetChannel(channel);
+ return true;
+ }
+ return false;
+}
+
+void CGUIWindowPVRGuideBase::OnInputDone()
+{
+ const CPVRChannelNumber channelNumber = GetChannelNumber();
+ if (channelNumber.IsValid())
+ {
+ GetGridControl()->SetChannel(channelNumber);
+ }
+}
+
+void CGUIWindowPVRGuideBase::GetChannelNumbers(std::vector<std::string>& channelNumbers)
+{
+ const std::shared_ptr<CPVRChannelGroup> group = GetChannelGroup();
+ if (group)
+ group->GetChannelNumbers(channelNumbers);
+}
+
+CPVRRefreshTimelineItemsThread::CPVRRefreshTimelineItemsThread(CGUIWindowPVRGuideBase* pGuideWindow)
+ : CThread("epg-grid-refresh-timeline-items"),
+ m_pGuideWindow(pGuideWindow),
+ m_ready(true),
+ m_done(false)
+{
+}
+
+CPVRRefreshTimelineItemsThread::~CPVRRefreshTimelineItemsThread()
+{
+ // Note: CThread dtor will also call StopThread(true), but if thread worker function exits that
+ // late, it might access member variables of this which are already destroyed. Thus, stop
+ // the thread worker here and synchronously, while all members of this are still alive.
+ StopThread(true);
+}
+
+void CPVRRefreshTimelineItemsThread::Stop()
+{
+ StopThread(false);
+ m_ready.Set(); // wake up the worker thread to let it exit
+}
+
+void CPVRRefreshTimelineItemsThread::DoRefresh(bool bWait)
+{
+ m_ready.Set(); // wake up the worker thread
+
+ if (bWait)
+ {
+ m_done.Reset();
+ CGUIDialogBusy::WaitOnEvent(m_done, 100, false);
+ }
+}
+
+void CPVRRefreshTimelineItemsThread::Process()
+{
+ static const int BOOSTED_SLEEPS_THRESHOLD = 4;
+
+ int iLastEpgItemsCount = 0;
+ int iUpdatesWithoutChange = 0;
+
+ while (!m_bStop)
+ {
+ m_done.Reset();
+
+ if (m_pGuideWindow->RefreshTimelineItems() && !m_bStop)
+ {
+ CGUIMessage m(GUI_MSG_REFRESH_LIST, m_pGuideWindow->GetID(), 0,
+ static_cast<int>(PVREvent::Epg));
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(m);
+ }
+
+ if (m_bStop)
+ break;
+
+ m_done.Set();
+
+ // in order to fill the guide window asap, use a short update interval until we the
+ // same amount of epg events for BOOSTED_SLEEPS_THRESHOLD + 1 times in a row .
+ if (iUpdatesWithoutChange < BOOSTED_SLEEPS_THRESHOLD)
+ {
+ int iCurrentEpgItemsCount = m_pGuideWindow->CurrentDirectory().Size();
+
+ if (iCurrentEpgItemsCount == iLastEpgItemsCount)
+ iUpdatesWithoutChange++;
+ else
+ iUpdatesWithoutChange = 0; // reset
+
+ iLastEpgItemsCount = iCurrentEpgItemsCount;
+
+ m_ready.Wait(1000ms); // boosted update cycle
+ }
+ else
+ {
+ m_ready.Wait(5000ms); // normal update cycle
+ }
+
+ m_ready.Reset();
+ }
+
+ m_ready.Reset();
+ m_done.Set();
+}
+
+std::string CGUIWindowPVRTVGuide::GetRootPath() const
+{
+ return "pvr://guide/tv/";
+}
+
+std::string CGUIWindowPVRRadioGuide::GetRootPath() const
+{
+ return "pvr://guide/radio/";
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRGuide.h b/xbmc/pvr/windows/GUIWindowPVRGuide.h
new file mode 100644
index 0000000..87477ca
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRGuide.h
@@ -0,0 +1,129 @@
+/*
+ * 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 "pvr/PVRChannelNumberInputHandler.h"
+#include "pvr/windows/GUIWindowPVRBase.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+class CFileItemList;
+class CGUIMessage;
+
+namespace PVR
+{
+enum class PVREvent;
+
+class CPVRChannelGroup;
+class CGUIEPGGridContainer;
+class CPVRRefreshTimelineItemsThread;
+
+class CGUIWindowPVRGuideBase : public CGUIWindowPVRBase, public CPVRChannelNumberInputHandler
+{
+public:
+ CGUIWindowPVRGuideBase(bool bRadio, int id, const std::string& xmlFile);
+ ~CGUIWindowPVRGuideBase() override;
+
+ void OnInitWindow() override;
+ void OnDeinitWindow(int nextWindowID) override;
+ bool OnMessage(CGUIMessage& message) override;
+ bool OnAction(const CAction& action) override;
+ void GetContextButtons(int itemNumber, CContextButtons& buttons) override;
+ bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override;
+ void UpdateButtons() override;
+ void SetInvalid() override;
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+
+ void NotifyEvent(const PVREvent& event) override;
+
+ bool RefreshTimelineItems();
+
+ // CPVRChannelNumberInputHandler implementation
+ void GetChannelNumbers(std::vector<std::string>& channelNumbers) override;
+ void OnInputDone() override;
+
+ bool GotoBegin();
+ bool GotoEnd();
+ bool GotoCurrentProgramme();
+ bool GotoDate(int deltaHours);
+ bool OpenDateSelectionDialog();
+ bool Go12HoursBack();
+ bool Go12HoursForward();
+ bool GotoFirstChannel();
+ bool GotoLastChannel();
+ bool GotoPlayingChannel();
+
+protected:
+ void UpdateSelectedItemPath() override;
+ std::string GetDirectoryPath() override { return ""; }
+ bool GetDirectory(const std::string& strDirectory, CFileItemList& items) override;
+ void FormatAndSort(CFileItemList& items) override;
+ CFileItemPtr GetCurrentListItem(int offset = 0) override;
+
+ void ClearData() override;
+
+private:
+ CGUIEPGGridContainer* GetGridControl();
+ void InitEpgGridControl();
+
+ bool OnContextButtonNavigate(CONTEXT_BUTTON button);
+
+ bool ShouldNavigateToGridContainer(int iAction);
+
+ void StartRefreshTimelineItemsThread();
+ void StopRefreshTimelineItemsThread();
+
+ void RefreshView(CGUIMessage& message, bool bInitGridControl);
+
+ int GetCurrentListItemIndex(const std::shared_ptr<CFileItem>& item);
+
+ std::unique_ptr<CPVRRefreshTimelineItemsThread> m_refreshTimelineItemsThread;
+ std::atomic_bool m_bRefreshTimelineItems{false};
+ std::atomic_bool m_bSyncRefreshTimelineItems{false};
+
+ std::shared_ptr<CPVRChannelGroup> m_cachedChannelGroup;
+
+ bool m_bChannelSelectionRestored{false};
+};
+
+class CGUIWindowPVRTVGuide : public CGUIWindowPVRGuideBase
+{
+public:
+ CGUIWindowPVRTVGuide() : CGUIWindowPVRGuideBase(false, WINDOW_TV_GUIDE, "MyPVRGuide.xml") {}
+ std::string GetRootPath() const override;
+};
+
+class CGUIWindowPVRRadioGuide : public CGUIWindowPVRGuideBase
+{
+public:
+ CGUIWindowPVRRadioGuide() : CGUIWindowPVRGuideBase(true, WINDOW_RADIO_GUIDE, "MyPVRGuide.xml") {}
+ std::string GetRootPath() const override;
+};
+
+class CPVRRefreshTimelineItemsThread : public CThread
+{
+public:
+ explicit CPVRRefreshTimelineItemsThread(CGUIWindowPVRGuideBase* pGuideWindow);
+ ~CPVRRefreshTimelineItemsThread() override;
+
+ void Process() override;
+
+ void DoRefresh(bool bWait);
+ void Stop();
+
+private:
+ CGUIWindowPVRGuideBase* m_pGuideWindow;
+ CEvent m_ready;
+ CEvent m_done;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp
new file mode 100644
index 0000000..d127217
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp
@@ -0,0 +1,440 @@
+/*
+ * 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 "GUIWindowPVRRecordings.h"
+
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIRadioButtonControl.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "pvr/PVRManager.h"
+#include "pvr/guilib/PVRGUIActionsPlayback.h"
+#include "pvr/guilib/PVRGUIActionsRecordings.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/recordings/PVRRecordingsPath.h"
+#include "settings/MediaSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+#include "video/VideoLibraryQueue.h"
+#include "video/VideoUtils.h"
+#include "video/windows/GUIWindowVideoBase.h"
+#include "video/windows/GUIWindowVideoNav.h"
+
+#include <memory>
+#include <mutex>
+#include <string>
+
+using namespace PVR;
+
+CGUIWindowPVRRecordingsBase::CGUIWindowPVRRecordingsBase(bool bRadio,
+ int id,
+ const std::string& xmlFile)
+ : CGUIWindowPVRBase(bRadio, id, xmlFile),
+ m_settings(
+ {CSettings::SETTING_PVRRECORD_GROUPRECORDINGS, CSettings::SETTING_MYVIDEOS_SELECTACTION})
+{
+}
+
+CGUIWindowPVRRecordingsBase::~CGUIWindowPVRRecordingsBase() = default;
+
+void CGUIWindowPVRRecordingsBase::OnWindowLoaded()
+{
+ CONTROL_SELECT(CONTROL_BTNGROUPITEMS);
+}
+
+std::string CGUIWindowPVRRecordingsBase::GetDirectoryPath()
+{
+ const std::string basePath = CPVRRecordingsPath(m_bShowDeletedRecordings, m_bRadio);
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath()
+ : basePath;
+}
+
+void CGUIWindowPVRRecordingsBase::GetContextButtons(int itemNumber, CContextButtons& buttons)
+{
+ if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
+ return;
+ CFileItemPtr pItem = m_vecItems->Get(itemNumber);
+
+ if (pItem->IsParentFolder())
+ {
+ // No context menu for ".." items
+ return;
+ }
+
+ bool isDeletedRecording = false;
+
+ std::shared_ptr<CPVRRecording> recording(pItem->GetPVRRecordingInfoTag());
+ if (recording)
+ {
+ isDeletedRecording = recording->IsDeleted();
+
+ if (isDeletedRecording)
+ {
+ if (m_vecItems->GetObjectCount() > 1)
+ buttons.Add(CONTEXT_BUTTON_DELETE_ALL, 19292); /* Delete all permanently */
+ }
+ }
+
+ if (!isDeletedRecording)
+ CGUIWindowPVRBase::GetContextButtons(itemNumber, buttons);
+}
+
+bool CGUIWindowPVRRecordingsBase::OnAction(const CAction& action)
+{
+ if (action.GetID() == ACTION_PARENT_DIR || action.GetID() == ACTION_NAV_BACK)
+ {
+ CPVRRecordingsPath path(m_vecItems->GetPath());
+ if (path.IsValid() && !path.IsRecordingsRoot())
+ {
+ GoParentFolder();
+ return true;
+ }
+ }
+ else if (action.GetID() == ACTION_TOGGLE_WATCHED)
+ {
+ const std::shared_ptr<CFileItem> pItem = m_vecItems->Get(m_viewControl.GetSelectedItem());
+ if (!pItem || pItem->IsParentFolder())
+ return false;
+
+ bool bUnWatched = false;
+ if (pItem->HasPVRRecordingInfoTag())
+ bUnWatched = pItem->GetPVRRecordingInfoTag()->GetPlayCount() == 0;
+ else if (pItem->m_bIsFolder)
+ bUnWatched = pItem->GetProperty("unwatchedepisodes").asInteger() > 0;
+ else
+ return false;
+
+ CVideoLibraryQueue::GetInstance().MarkAsWatched(pItem, bUnWatched);
+ return true;
+ }
+
+ return CGUIWindowPVRBase::OnAction(action);
+}
+
+bool CGUIWindowPVRRecordingsBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
+{
+ if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
+ return false;
+ CFileItemPtr pItem = m_vecItems->Get(itemNumber);
+
+ return OnContextButtonDeleteAll(pItem.get(), button) ||
+ CGUIMediaWindow::OnContextButton(itemNumber, button);
+}
+
+bool CGUIWindowPVRRecordingsBase::Update(const std::string& strDirectory,
+ bool updateFilterPath /* = true */)
+{
+ m_thumbLoader.StopThread();
+
+ int iOldCount = m_vecItems->GetObjectCount();
+ const std::string oldPath = m_vecItems->GetPath();
+
+ bool bReturn = CGUIWindowPVRBase::Update(strDirectory);
+
+ if (bReturn)
+ {
+ // TODO: does it make sense to show the non-deleted recordings, although user wants
+ // to see the deleted recordings? Or is this just another hack to avoid misbehavior
+ // of CGUIMediaWindow if it has no content?
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ /* empty list for deleted recordings */
+ if (m_vecItems->GetObjectCount() == 0 && m_bShowDeletedRecordings)
+ {
+ /* show the normal recordings instead */
+ m_bShowDeletedRecordings = false;
+ lock.unlock();
+ Update(GetDirectoryPath());
+ return bReturn;
+ }
+ }
+
+ if (bReturn && iOldCount > 0 && m_vecItems->GetObjectCount() == 0 &&
+ oldPath == m_vecItems->GetPath())
+ {
+ /* go to the parent folder if we're in a subdirectory and for instance just deleted the last item */
+ const CPVRRecordingsPath path(m_vecItems->GetPath());
+ if (path.IsValid() && !path.IsRecordingsRoot())
+ GoParentFolder();
+ }
+ return bReturn;
+}
+
+void CGUIWindowPVRRecordingsBase::UpdateButtons()
+{
+ int iWatchMode = CMediaSettings::GetInstance().GetWatchedMode("recordings");
+ int iStringId = 257; // "Error"
+
+ if (iWatchMode == WatchedModeAll)
+ iStringId = 22015; // "All recordings"
+ else if (iWatchMode == WatchedModeUnwatched)
+ iStringId = 16101; // "Unwatched"
+ else if (iWatchMode == WatchedModeWatched)
+ iStringId = 16102; // "Watched"
+
+ SET_CONTROL_LABEL(CONTROL_BTNSHOWMODE, g_localizeStrings.Get(iStringId));
+
+ bool bGroupRecordings = m_settings.GetBoolValue(CSettings::SETTING_PVRRECORD_GROUPRECORDINGS);
+ SET_CONTROL_SELECTED(GetID(), CONTROL_BTNGROUPITEMS, bGroupRecordings);
+
+ CGUIRadioButtonControl* btnShowDeleted =
+ static_cast<CGUIRadioButtonControl*>(GetControl(CONTROL_BTNSHOWDELETED));
+ if (btnShowDeleted)
+ {
+ btnShowDeleted->SetVisible(
+ m_bRadio ? CServiceBroker::GetPVRManager().Recordings()->HasDeletedRadioRecordings()
+ : CServiceBroker::GetPVRManager().Recordings()->HasDeletedTVRecordings());
+ btnShowDeleted->SetSelected(m_bShowDeletedRecordings);
+ }
+
+ CGUIWindowPVRBase::UpdateButtons();
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, m_bShowDeletedRecordings
+ ? g_localizeStrings.Get(19179)
+ : ""); /* Deleted recordings trash */
+
+ const CPVRRecordingsPath path(m_vecItems->GetPath());
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2,
+ bGroupRecordings && path.IsValid() ? path.GetUnescapedDirectoryPath() : "");
+}
+
+bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message)
+{
+ bool bReturn = false;
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_CLICKED:
+ if (message.GetSenderId() == m_viewControl.GetCurrentControl())
+ {
+ int iItem = m_viewControl.GetSelectedItem();
+ if (iItem >= 0 && iItem < m_vecItems->Size())
+ {
+ const CFileItemPtr item(m_vecItems->Get(iItem));
+ switch (message.GetParam1())
+ {
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ case ACTION_PLAYER_PLAY:
+ {
+ const CPVRRecordingsPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsRecordingsRoot() && item->IsParentFolder())
+ {
+ // handle special 'go home' item.
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
+ bReturn = true;
+ break;
+ }
+
+ if (!item->IsParentFolder() && message.GetParam1() == ACTION_PLAYER_PLAY)
+ {
+ if (item->m_bIsFolder)
+ {
+ if (CGUIWindowVideoNav::ShowResumeMenu(*item))
+ VIDEO_UTILS::PlayItem(item);
+ }
+ else
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayRecording(
+ *item, true /* check resume */);
+
+ bReturn = true;
+ }
+ else if (item->m_bIsFolder)
+ {
+ // recording folders and ".." folders in subfolders are handled by base class.
+ bReturn = false;
+ }
+ else
+ {
+ switch (m_settings.GetIntValue(CSettings::SETTING_MYVIDEOS_SELECTACTION))
+ {
+ case SELECT_ACTION_CHOOSE:
+ OnPopupMenu(iItem);
+ bReturn = true;
+ break;
+ case SELECT_ACTION_PLAY_OR_RESUME:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayRecording(
+ *item, true /* check resume */);
+ bReturn = true;
+ break;
+ case SELECT_ACTION_RESUME:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().ResumePlayRecording(
+ *item, true /* fall back to play if no resume possible */);
+ bReturn = true;
+ break;
+ case SELECT_ACTION_INFO:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().ShowRecordingInfo(
+ *item);
+ bReturn = true;
+ break;
+ default:
+ bReturn = false;
+ break;
+ }
+ }
+ break;
+ }
+ case ACTION_CONTEXT_MENU:
+ case ACTION_MOUSE_RIGHT_CLICK:
+ OnPopupMenu(iItem);
+ bReturn = true;
+ break;
+ case ACTION_SHOW_INFO:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().ShowRecordingInfo(*item);
+ bReturn = true;
+ break;
+ case ACTION_DELETE_ITEM:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().DeleteRecording(*item);
+ bReturn = true;
+ break;
+ default:
+ bReturn = false;
+ break;
+ }
+ }
+ }
+ else if (message.GetSenderId() == CONTROL_BTNGROUPITEMS)
+ {
+ const std::shared_ptr<CSettings> settings =
+ CServiceBroker::GetSettingsComponent()->GetSettings();
+ settings->ToggleBool(CSettings::SETTING_PVRRECORD_GROUPRECORDINGS);
+ settings->Save();
+ Refresh(true);
+ }
+ else if (message.GetSenderId() == CONTROL_BTNSHOWDELETED)
+ {
+ CGUIRadioButtonControl* radioButton =
+ static_cast<CGUIRadioButtonControl*>(GetControl(CONTROL_BTNSHOWDELETED));
+ if (radioButton)
+ {
+ m_bShowDeletedRecordings = radioButton->IsSelected();
+ Update(GetDirectoryPath());
+ }
+ bReturn = true;
+ }
+ else if (message.GetSenderId() == CONTROL_BTNSHOWMODE)
+ {
+ CMediaSettings::GetInstance().CycleWatchedMode("recordings");
+ CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
+ OnFilterItems(GetProperty("filter").asString());
+ UpdateButtons();
+ return true;
+ }
+ break;
+ case GUI_MSG_REFRESH_LIST:
+ {
+ switch (static_cast<PVREvent>(message.GetParam1()))
+ {
+ case PVREvent::CurrentItem:
+ case PVREvent::Epg:
+ case PVREvent::EpgActiveItem:
+ case PVREvent::EpgContainer:
+ case PVREvent::Timers:
+ SetInvalid();
+ break;
+
+ case PVREvent::RecordingsInvalidated:
+ case PVREvent::TimersInvalidated:
+ Refresh(true);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+
+ return bReturn || CGUIWindowPVRBase::OnMessage(message);
+}
+
+bool CGUIWindowPVRRecordingsBase::OnContextButtonDeleteAll(CFileItem* item, CONTEXT_BUTTON button)
+{
+ if (button == CONTEXT_BUTTON_DELETE_ALL)
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().DeleteAllRecordingsFromTrash();
+ return true;
+ }
+
+ return false;
+}
+
+void CGUIWindowPVRRecordingsBase::OnPrepareFileItems(CFileItemList& items)
+{
+ if (items.IsEmpty())
+ return;
+
+ CFileItemList files;
+ for (const auto& item : items)
+ {
+ if (!item->m_bIsFolder)
+ files.Add(item);
+ }
+
+ if (!files.IsEmpty())
+ {
+ if (m_database.Open())
+ {
+ CGUIWindowVideoBase::LoadVideoInfo(files, m_database, false);
+ m_database.Close();
+ }
+ m_thumbLoader.Load(files);
+ }
+
+ CGUIWindowPVRBase::OnPrepareFileItems(items);
+}
+
+bool CGUIWindowPVRRecordingsBase::GetFilteredItems(const std::string& filter, CFileItemList& items)
+{
+ bool listchanged = CGUIWindowPVRBase::GetFilteredItems(filter, items);
+
+ int watchMode = CMediaSettings::GetInstance().GetWatchedMode("recordings");
+
+ CFileItemPtr item;
+ for (int i = 0; i < items.Size(); i++)
+ {
+ item = items.Get(i);
+
+ if (item->IsParentFolder()) // Don't delete the go to parent folder
+ continue;
+
+ if (!item->HasPVRRecordingInfoTag())
+ continue;
+
+ int iPlayCount = item->GetPVRRecordingInfoTag()->GetPlayCount();
+ if ((watchMode == WatchedModeWatched && iPlayCount == 0) ||
+ (watchMode == WatchedModeUnwatched && iPlayCount > 0))
+ {
+ items.Remove(i);
+ i--;
+ listchanged = true;
+ }
+ }
+
+ // Remove the parent folder item, if it's the only item in the folder.
+ if (items.GetObjectCount() == 0 && items.GetFileCount() > 0 && items.Get(0)->IsParentFolder())
+ items.Remove(0);
+
+ return listchanged;
+}
+
+std::string CGUIWindowPVRTVRecordings::GetRootPath() const
+{
+ return CPVRRecordingsPath(m_bShowDeletedRecordings, false);
+}
+
+std::string CGUIWindowPVRRadioRecordings::GetRootPath() const
+{
+ return CPVRRecordingsPath(m_bShowDeletedRecordings, true);
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.h b/xbmc/pvr/windows/GUIWindowPVRRecordings.h
new file mode 100644
index 0000000..5091fde
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.h
@@ -0,0 +1,71 @@
+/*
+ * 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 "dialogs/GUIDialogContextMenu.h"
+#include "pvr/settings/PVRSettings.h"
+#include "pvr/windows/GUIWindowPVRBase.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoThumbLoader.h"
+
+#include <string>
+
+class CFileItem;
+
+namespace PVR
+{
+class CGUIWindowPVRRecordingsBase : public CGUIWindowPVRBase
+{
+public:
+ CGUIWindowPVRRecordingsBase(bool bRadio, int id, const std::string& xmlFile);
+ ~CGUIWindowPVRRecordingsBase() override;
+
+ void OnWindowLoaded() override;
+ bool OnMessage(CGUIMessage& message) override;
+ bool OnAction(const CAction& action) override;
+ void GetContextButtons(int itemNumber, CContextButtons& buttons) override;
+ bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override;
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+ void UpdateButtons() override;
+
+protected:
+ std::string GetDirectoryPath() override;
+ void OnPrepareFileItems(CFileItemList& items) override;
+ bool GetFilteredItems(const std::string& filter, CFileItemList& items) override;
+
+ bool m_bShowDeletedRecordings{false};
+
+private:
+ bool OnContextButtonDeleteAll(CFileItem* item, CONTEXT_BUTTON button);
+
+ CVideoThumbLoader m_thumbLoader;
+ CVideoDatabase m_database;
+ CPVRSettings m_settings;
+};
+
+class CGUIWindowPVRTVRecordings : public CGUIWindowPVRRecordingsBase
+{
+public:
+ CGUIWindowPVRTVRecordings()
+ : CGUIWindowPVRRecordingsBase(false, WINDOW_TV_RECORDINGS, "MyPVRRecordings.xml")
+ {
+ }
+ std::string GetRootPath() const override;
+};
+
+class CGUIWindowPVRRadioRecordings : public CGUIWindowPVRRecordingsBase
+{
+public:
+ CGUIWindowPVRRadioRecordings()
+ : CGUIWindowPVRRecordingsBase(true, WINDOW_RADIO_RECORDINGS, "MyPVRRecordings.xml")
+ {
+ }
+ std::string GetRootPath() const override;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp
new file mode 100644
index 0000000..f0cd3fe
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp
@@ -0,0 +1,544 @@
+/*
+ * 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 "GUIWindowPVRSearch.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRItem.h"
+#include "pvr/PVRManager.h"
+#include "pvr/dialogs/GUIDialogPVRGuideSearch.h"
+#include "pvr/epg/Epg.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/epg/EpgSearchFilter.h"
+#include "pvr/epg/EpgSearchPath.h"
+#include "pvr/guilib/PVRGUIActionsEPG.h"
+#include "pvr/guilib/PVRGUIActionsTimers.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "threads/IRunnable.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+using namespace PVR;
+using namespace KODI::MESSAGING;
+
+namespace
+{
+class AsyncSearchAction : private IRunnable
+{
+public:
+ AsyncSearchAction() = delete;
+ AsyncSearchAction(CFileItemList* items, CPVREpgSearchFilter* filter)
+ : m_items(items), m_filter(filter)
+ {
+ }
+ bool Execute();
+
+private:
+ // IRunnable implementation
+ void Run() override;
+
+ CFileItemList* m_items;
+ CPVREpgSearchFilter* m_filter;
+};
+
+bool AsyncSearchAction::Execute()
+{
+ CGUIDialogBusy::Wait(this, 100, false);
+ return true;
+}
+
+void AsyncSearchAction::Run()
+{
+ std::vector<std::shared_ptr<CPVREpgInfoTag>> results =
+ CServiceBroker::GetPVRManager().EpgContainer().GetTags(m_filter->GetEpgSearchData());
+ m_filter->SetEpgSearchDataFiltered();
+
+ // Tags can still contain false positives, for search criteria that cannot be handled via
+ // database. So, run extended search filters on what we got from the database.
+ for (auto it = results.begin(); it != results.end();)
+ {
+ it = results.erase(std::remove_if(results.begin(), results.end(),
+ [this](const std::shared_ptr<CPVREpgInfoTag>& entry) {
+ return !m_filter->FilterEntry(entry);
+ }),
+ results.end());
+ }
+
+ if (m_filter->ShouldRemoveDuplicates())
+ m_filter->RemoveDuplicates(results);
+
+ m_filter->SetLastExecutedDateTime(CDateTime::GetUTCDateTime());
+
+ for (const auto& tag : results)
+ {
+ m_items->Add(std::make_shared<CFileItem>(tag));
+ }
+}
+} // unnamed namespace
+
+CGUIWindowPVRSearchBase::CGUIWindowPVRSearchBase(bool bRadio, int id, const std::string& xmlFile)
+ : CGUIWindowPVRBase(bRadio, id, xmlFile), m_bSearchConfirmed(false)
+{
+}
+
+CGUIWindowPVRSearchBase::~CGUIWindowPVRSearchBase()
+{
+}
+
+void CGUIWindowPVRSearchBase::GetContextButtons(int itemNumber, CContextButtons& buttons)
+{
+ if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
+ return;
+
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ const bool bIsSavedSearchesRoot = (path.IsValid() && path.IsSavedSearchesRoot());
+ if (!bIsSavedSearchesRoot)
+ buttons.Add(CONTEXT_BUTTON_CLEAR, 19232); // "Clear search results"
+
+ CGUIWindowPVRBase::GetContextButtons(itemNumber, buttons);
+}
+
+bool CGUIWindowPVRSearchBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
+{
+ if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
+ return false;
+ CFileItemPtr pItem = m_vecItems->Get(itemNumber);
+
+ return OnContextButtonClear(pItem.get(), button) ||
+ CGUIMediaWindow::OnContextButton(itemNumber, button);
+}
+
+void CGUIWindowPVRSearchBase::SetItemToSearch(const CFileItem& item)
+{
+ if (item.HasEPGSearchFilter())
+ {
+ SetSearchFilter(item.GetEPGSearchFilter());
+ }
+ else if (item.IsUsablePVRRecording())
+ {
+ SetSearchFilter(std::make_shared<CPVREpgSearchFilter>(m_bRadio));
+ m_searchfilter->SetSearchPhrase(item.GetPVRRecordingInfoTag()->m_strTitle);
+ }
+ else
+ {
+ SetSearchFilter(std::make_shared<CPVREpgSearchFilter>(m_bRadio));
+
+ const std::shared_ptr<CPVREpgInfoTag> epgTag(CPVRItem(item).GetEpgInfoTag());
+ if (epgTag && !CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
+ m_searchfilter->SetSearchPhrase(epgTag->Title());
+ }
+
+ ExecuteSearch();
+}
+
+void CGUIWindowPVRSearchBase::OnPrepareFileItems(CFileItemList& items)
+{
+ if (m_bSearchConfirmed)
+ {
+ items.Clear();
+ }
+
+ if (items.IsEmpty())
+ {
+ auto item = std::make_shared<CFileItem>(CPVREpgSearchPath::PATH_SEARCH_DIALOG, false);
+ item->SetLabel(g_localizeStrings.Get(
+ m_searchfilter == nullptr ? 19335 : 19336)); // "New search..." / "Edit search..."
+ item->SetLabelPreformatted(true);
+ item->SetSpecialSort(SortSpecialOnTop);
+ item->SetArt("icon", "DefaultPVRSearch.png");
+ items.Add(item);
+
+ item = std::make_shared<CFileItem>(m_bRadio ? CPVREpgSearchPath::PATH_RADIO_SAVEDSEARCHES
+ : CPVREpgSearchPath::PATH_TV_SAVEDSEARCHES,
+ true);
+ item->SetLabel(g_localizeStrings.Get(19337)); // "Saved searches"
+ item->SetLabelPreformatted(true);
+ item->SetSpecialSort(SortSpecialOnTop);
+ item->SetArt("icon", "DefaultFolder.png");
+ items.Add(item);
+ }
+
+ if (m_bSearchConfirmed)
+ {
+ const int itemCount = items.GetObjectCount();
+
+ AsyncSearchAction(&items, m_searchfilter.get()).Execute();
+
+ if (items.GetObjectCount() == itemCount)
+ {
+ HELPERS::ShowOKDialogText(CVariant{284}, // "No results found"
+ m_searchfilter->GetSearchTerm());
+ }
+ }
+}
+
+bool CGUIWindowPVRSearchBase::OnAction(const CAction& action)
+{
+ if (action.GetID() == ACTION_PARENT_DIR || action.GetID() == ACTION_NAV_BACK)
+ {
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsSavedSearchesRoot())
+ {
+ // Go to root dir and show previous search results if any
+ m_bSearchConfirmed = (m_searchfilter != nullptr);
+ GoParentFolder();
+ return true;
+ }
+ }
+ return CGUIWindowPVRBase::OnAction(action);
+}
+
+bool CGUIWindowPVRSearchBase::OnMessage(CGUIMessage& message)
+{
+ if (message.GetMessage() == GUI_MSG_CLICKED)
+ {
+ if (message.GetSenderId() == m_viewControl.GetCurrentControl())
+ {
+ int iItem = m_viewControl.GetSelectedItem();
+ if (iItem >= 0 && iItem < m_vecItems->Size())
+ {
+ CFileItemPtr pItem = m_vecItems->Get(iItem);
+
+ /* process actions */
+ switch (message.GetParam1())
+ {
+ case ACTION_SHOW_INFO:
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ const CPVREpgSearchPath path(pItem->GetPath());
+ const bool bIsSavedSearch = (path.IsValid() && path.IsSavedSearch());
+ const bool bIsSavedSearchesRoot = (path.IsValid() && path.IsSavedSearchesRoot());
+
+ if (message.GetParam1() != ACTION_SHOW_INFO)
+ {
+ if (pItem->IsParentFolder())
+ {
+ // Go to root dir and show previous search results if any
+ m_bSearchConfirmed = (m_searchfilter != nullptr);
+ break; // handled by base class
+ }
+
+ if (bIsSavedSearchesRoot)
+ {
+ // List saved searches
+ m_bSearchConfirmed = false;
+ break; // handled by base class
+ }
+
+ if (bIsSavedSearch)
+ {
+ // Execute selected saved search
+ SetSearchFilter(pItem->GetEPGSearchFilter());
+ ExecuteSearch();
+ return true;
+ }
+ }
+
+ if (bIsSavedSearch)
+ {
+ OpenDialogSearch(*pItem);
+ }
+ else if (pItem->GetPath() == CPVREpgSearchPath::PATH_SEARCH_DIALOG)
+ {
+ OpenDialogSearch(m_searchfilter);
+ }
+ else
+ {
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ShowEPGInfo(*pItem);
+ }
+ return true;
+ }
+
+ case ACTION_CONTEXT_MENU:
+ case ACTION_MOUSE_RIGHT_CLICK:
+ OnPopupMenu(iItem);
+ return true;
+
+ case ACTION_RECORD:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().ToggleTimer(*pItem);
+ return true;
+ }
+ }
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
+ {
+ if (static_cast<PVREvent>(message.GetParam1()) == PVREvent::SavedSearchesInvalidated)
+ {
+ Refresh(true);
+
+ // Refresh triggered by deleted saved search?
+ if (m_searchfilter)
+ {
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ const bool bIsSavedSearchesRoot = (path.IsValid() && path.IsSavedSearchesRoot());
+ if (bIsSavedSearchesRoot)
+ {
+ const std::string filterPath = m_searchfilter->GetPath();
+ bool bFound = false;
+ for (const auto& item : *m_vecItems)
+ {
+ const auto filter = item->GetEPGSearchFilter();
+ if (filter && filter->GetPath() == filterPath)
+ {
+ bFound = true;
+ break;
+ }
+ }
+ if (!bFound)
+ SetSearchFilter(nullptr);
+ }
+ }
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_WINDOW_INIT)
+ {
+ const CPVREpgSearchPath path(message.GetStringParam(0));
+ if (path.IsValid() && path.IsSavedSearch())
+ {
+ const std::shared_ptr<CPVREpgSearchFilter> filter =
+ CServiceBroker::GetPVRManager().EpgContainer().GetSavedSearchById(path.IsRadio(),
+ path.GetId());
+ if (filter)
+ {
+ SetSearchFilter(filter);
+ m_bSearchConfirmed = true;
+ }
+ }
+ }
+
+ return CGUIWindowPVRBase::OnMessage(message);
+}
+
+bool CGUIWindowPVRSearchBase::Update(const std::string& strDirectory,
+ bool updateFilterPath /* = true */)
+{
+ if (m_vecItems->GetObjectCount() > 0)
+ {
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsSavedSearchesRoot())
+ {
+ const std::string oldPath = m_vecItems->GetPath();
+
+ const bool bReturn = CGUIWindowPVRBase::Update(strDirectory);
+
+ if (bReturn && oldPath == m_vecItems->GetPath() && m_vecItems->GetObjectCount() == 0)
+ {
+ // Go to parent folder if we're in a subdir and for instance just deleted the last item
+ GoParentFolder();
+ }
+ return bReturn;
+ }
+ }
+ return CGUIWindowPVRBase::Update(strDirectory);
+}
+
+void CGUIWindowPVRSearchBase::UpdateButtons()
+{
+ CGUIWindowPVRBase::UpdateButtons();
+
+ bool bSavedSearchesRoot = false;
+ std::string header;
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsSavedSearchesRoot())
+ {
+ bSavedSearchesRoot = true;
+ header = g_localizeStrings.Get(19337); // "Saved searches"
+ }
+
+ if (header.empty() && m_searchfilter)
+ {
+ header = m_searchfilter->GetTitle();
+ if (header.empty())
+ {
+ header = m_searchfilter->GetSearchTerm();
+ StringUtils::Trim(header, "\"");
+ }
+ }
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, header);
+
+ if (!bSavedSearchesRoot && m_searchfilter && m_searchfilter->IsChanged())
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, g_localizeStrings.Get(19342)); // "[not saved]"
+ else if (!bSavedSearchesRoot && m_searchfilter)
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, g_localizeStrings.Get(19343)); // "[saved]"
+ else
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, "");
+}
+
+bool CGUIWindowPVRSearchBase::OnContextButtonClear(CFileItem* item, CONTEXT_BUTTON button)
+{
+ bool bReturn = false;
+
+ if (button == CONTEXT_BUTTON_CLEAR)
+ {
+ bReturn = true;
+
+ m_bSearchConfirmed = false;
+ SetSearchFilter(nullptr);
+
+ Refresh(true);
+ }
+
+ return bReturn;
+}
+
+CGUIDialogPVRGuideSearch::Result CGUIWindowPVRSearchBase::OpenDialogSearch(const CFileItem& item)
+{
+ const auto searchFilter = item.GetEPGSearchFilter();
+ if (!searchFilter)
+ return CGUIDialogPVRGuideSearch::Result::CANCEL;
+
+ return OpenDialogSearch(searchFilter);
+}
+
+CGUIDialogPVRGuideSearch::Result CGUIWindowPVRSearchBase::OpenDialogSearch(
+ const std::shared_ptr<CPVREpgSearchFilter>& searchFilter)
+{
+ CGUIDialogPVRGuideSearch* dlgSearch =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPVRGuideSearch>(
+ WINDOW_DIALOG_PVR_GUIDE_SEARCH);
+
+ if (!dlgSearch)
+ return CGUIDialogPVRGuideSearch::Result::CANCEL;
+
+ const std::shared_ptr<CPVREpgSearchFilter> tmpSearchFilter =
+ searchFilter != nullptr ? std::make_shared<CPVREpgSearchFilter>(*searchFilter)
+ : std::make_shared<CPVREpgSearchFilter>(m_bRadio);
+
+ dlgSearch->SetFilterData(tmpSearchFilter);
+
+ /* Open dialog window */
+ dlgSearch->Open();
+
+ const CGUIDialogPVRGuideSearch::Result result = dlgSearch->GetResult();
+ if (result == CGUIDialogPVRGuideSearch::Result::SEARCH)
+ {
+ SetSearchFilter(tmpSearchFilter);
+ ExecuteSearch();
+ }
+ else if (result == CGUIDialogPVRGuideSearch::Result::SAVE)
+ {
+ CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*tmpSearchFilter);
+ if (searchFilter)
+ searchFilter->SetDatabaseId(tmpSearchFilter->GetDatabaseId());
+
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsSearchRoot())
+ {
+ SetSearchFilter(tmpSearchFilter);
+ ExecuteSearch();
+ }
+ }
+
+ return result;
+}
+
+void CGUIWindowPVRSearchBase::ExecuteSearch()
+{
+ m_bSearchConfirmed = true;
+
+ const CPVREpgSearchPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsSavedSearchesRoot())
+ {
+ GoParentFolder();
+ }
+ else if (IsActive())
+ {
+ Refresh(true);
+ }
+
+ // Save if not a transient search
+ if (m_searchfilter->GetDatabaseId() != -1)
+ CServiceBroker::GetPVRManager().EpgContainer().UpdateSavedSearchLastExecuted(*m_searchfilter);
+}
+
+void CGUIWindowPVRSearchBase::SetSearchFilter(
+ const std::shared_ptr<CPVREpgSearchFilter>& searchFilter)
+{
+ if (m_searchfilter && m_searchfilter->IsChanged() &&
+ (!searchFilter || m_searchfilter->GetPath() != searchFilter->GetPath()))
+ {
+ bool bCanceled = false;
+ if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{14117}, // "Warning"
+ CVariant{19341}, // "Save the current search?"
+ CVariant{},
+ CVariant{!m_searchfilter->GetTitle().empty()
+ ? m_searchfilter->GetTitle()
+ : m_searchfilter->GetSearchTerm()},
+ bCanceled, CVariant{107}, // "Yes"
+ CVariant{106}, // "No"
+ CGUIDialogYesNo::NO_TIMEOUT) &&
+ !bCanceled)
+ {
+ std::string title = m_searchfilter->GetTitle();
+ if (title.empty())
+ {
+ title = m_searchfilter->GetSearchTerm();
+ if (title.empty())
+ title = g_localizeStrings.Get(137); // "Search"
+ else
+ StringUtils::Trim(title, "\"");
+
+ m_searchfilter->SetTitle(title);
+ }
+ CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*m_searchfilter);
+ }
+ }
+ m_searchfilter = searchFilter;
+}
+
+std::string CGUIWindowPVRTVSearch::GetRootPath() const
+{
+ return CPVREpgSearchPath::PATH_TV_SEARCH;
+}
+
+std::string CGUIWindowPVRTVSearch::GetStartFolder(const std::string& dir)
+{
+ return CPVREpgSearchPath::PATH_TV_SEARCH;
+}
+
+std::string CGUIWindowPVRTVSearch::GetDirectoryPath()
+{
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), CPVREpgSearchPath::PATH_TV_SEARCH)
+ ? m_vecItems->GetPath()
+ : CPVREpgSearchPath::PATH_TV_SEARCH;
+}
+
+std::string CGUIWindowPVRRadioSearch::GetRootPath() const
+{
+ return CPVREpgSearchPath::PATH_RADIO_SEARCH;
+}
+
+std::string CGUIWindowPVRRadioSearch::GetStartFolder(const std::string& dir)
+{
+ return CPVREpgSearchPath::PATH_RADIO_SEARCH;
+}
+
+std::string CGUIWindowPVRRadioSearch::GetDirectoryPath()
+{
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), CPVREpgSearchPath::PATH_RADIO_SEARCH)
+ ? m_vecItems->GetPath()
+ : CPVREpgSearchPath::PATH_RADIO_SEARCH;
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRSearch.h b/xbmc/pvr/windows/GUIWindowPVRSearch.h
new file mode 100644
index 0000000..d6f6a35
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRSearch.h
@@ -0,0 +1,90 @@
+/*
+ * 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 "dialogs/GUIDialogContextMenu.h"
+#include "pvr/dialogs/GUIDialogPVRGuideSearch.h"
+#include "pvr/windows/GUIWindowPVRBase.h"
+
+#include <memory>
+#include <string>
+
+class CFileItem;
+
+namespace PVR
+{
+class CPVREpgSearchFilter;
+
+class CGUIWindowPVRSearchBase : public CGUIWindowPVRBase
+{
+public:
+ CGUIWindowPVRSearchBase(bool bRadio, int id, const std::string& xmlFile);
+ ~CGUIWindowPVRSearchBase() override;
+
+ bool OnAction(const CAction& action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void GetContextButtons(int itemNumber, CContextButtons& buttons) override;
+ bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override;
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+ void UpdateButtons() override;
+
+ /*!
+ * @brief set the item to search similar events for.
+ * @param item the epg event to search similar events for.
+ */
+ void SetItemToSearch(const CFileItem& item);
+
+ /*!
+ * @brief Open the search dialog for the given search filter item.
+ * @param item the epg search filter.
+ * @return The result of the dialog
+ */
+ CGUIDialogPVRGuideSearch::Result OpenDialogSearch(const CFileItem& item);
+
+protected:
+ void OnPrepareFileItems(CFileItemList& items) override;
+
+private:
+ bool OnContextButtonClear(CFileItem* item, CONTEXT_BUTTON button);
+
+ CGUIDialogPVRGuideSearch::Result OpenDialogSearch(
+ const std::shared_ptr<CPVREpgSearchFilter>& searchFilter);
+
+ void ExecuteSearch();
+
+ void SetSearchFilter(const std::shared_ptr<CPVREpgSearchFilter>& searchFilter);
+
+ bool m_bSearchConfirmed;
+ std::shared_ptr<CPVREpgSearchFilter> m_searchfilter;
+};
+
+class CGUIWindowPVRTVSearch : public CGUIWindowPVRSearchBase
+{
+public:
+ CGUIWindowPVRTVSearch() : CGUIWindowPVRSearchBase(false, WINDOW_TV_SEARCH, "MyPVRSearch.xml") {}
+
+protected:
+ std::string GetRootPath() const override;
+ std::string GetStartFolder(const std::string& dir) override;
+ std::string GetDirectoryPath() override;
+};
+
+class CGUIWindowPVRRadioSearch : public CGUIWindowPVRSearchBase
+{
+public:
+ CGUIWindowPVRRadioSearch() : CGUIWindowPVRSearchBase(true, WINDOW_RADIO_SEARCH, "MyPVRSearch.xml")
+ {
+ }
+
+protected:
+ std::string GetRootPath() const override;
+ std::string GetStartFolder(const std::string& dir) override;
+ std::string GetDirectoryPath() override;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRTimerRules.cpp b/xbmc/pvr/windows/GUIWindowPVRTimerRules.cpp
new file mode 100644
index 0000000..1b203d4
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRTimerRules.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "GUIWindowPVRTimerRules.h"
+
+#include "FileItem.h"
+#include "pvr/timers/PVRTimersPath.h"
+#include "utils/URIUtils.h"
+
+using namespace PVR;
+
+CGUIWindowPVRTVTimerRules::CGUIWindowPVRTVTimerRules()
+: CGUIWindowPVRTimersBase(false, WINDOW_TV_TIMER_RULES, "MyPVRTimers.xml")
+{
+}
+
+std::string CGUIWindowPVRTVTimerRules::GetRootPath() const
+{
+ return CPVRTimersPath::PATH_TV_TIMER_RULES;
+}
+
+std::string CGUIWindowPVRTVTimerRules::GetDirectoryPath()
+{
+ const std::string basePath(CPVRTimersPath(false, true).GetPath());
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath() : basePath;
+}
+
+CGUIWindowPVRRadioTimerRules::CGUIWindowPVRRadioTimerRules()
+: CGUIWindowPVRTimersBase(true, WINDOW_RADIO_TIMER_RULES, "MyPVRTimers.xml")
+{
+}
+
+std::string CGUIWindowPVRRadioTimerRules::GetRootPath() const
+{
+ return CPVRTimersPath::PATH_RADIO_TIMER_RULES;
+}
+
+std::string CGUIWindowPVRRadioTimerRules::GetDirectoryPath()
+{
+ const std::string basePath(CPVRTimersPath(true, true).GetPath());
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath() : basePath;
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRTimerRules.h b/xbmc/pvr/windows/GUIWindowPVRTimerRules.h
new file mode 100644
index 0000000..dc3f050
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRTimerRules.h
@@ -0,0 +1,38 @@
+/*
+ * 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 "pvr/windows/GUIWindowPVRTimersBase.h"
+
+#include <string>
+
+namespace PVR
+{
+ class CGUIWindowPVRTVTimerRules : public CGUIWindowPVRTimersBase
+ {
+ public:
+ CGUIWindowPVRTVTimerRules();
+ ~CGUIWindowPVRTVTimerRules() override = default;
+
+ protected:
+ std::string GetRootPath() const override;
+ std::string GetDirectoryPath() override;
+ };
+
+ class CGUIWindowPVRRadioTimerRules : public CGUIWindowPVRTimersBase
+ {
+ public:
+ CGUIWindowPVRRadioTimerRules();
+ ~CGUIWindowPVRRadioTimerRules() override = default;
+
+ protected:
+ std::string GetRootPath() const override;
+ std::string GetDirectoryPath() override;
+ };
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRTimers.cpp b/xbmc/pvr/windows/GUIWindowPVRTimers.cpp
new file mode 100644
index 0000000..610571b
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRTimers.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "GUIWindowPVRTimers.h"
+
+#include "FileItem.h"
+#include "pvr/timers/PVRTimersPath.h"
+#include "utils/URIUtils.h"
+
+using namespace PVR;
+
+CGUIWindowPVRTVTimers::CGUIWindowPVRTVTimers()
+: CGUIWindowPVRTimersBase(false, WINDOW_TV_TIMERS, "MyPVRTimers.xml")
+{
+}
+
+std::string CGUIWindowPVRTVTimers::GetRootPath() const
+{
+ return CPVRTimersPath::PATH_TV_TIMERS;
+}
+
+std::string CGUIWindowPVRTVTimers::GetDirectoryPath()
+{
+ const std::string basePath(CPVRTimersPath(false, false).GetPath());
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath() : basePath;
+}
+
+CGUIWindowPVRRadioTimers::CGUIWindowPVRRadioTimers()
+: CGUIWindowPVRTimersBase(true, WINDOW_RADIO_TIMERS, "MyPVRTimers.xml")
+{
+}
+
+std::string CGUIWindowPVRRadioTimers::GetRootPath() const
+{
+ return CPVRTimersPath::PATH_RADIO_TIMERS;
+}
+
+std::string CGUIWindowPVRRadioTimers::GetDirectoryPath()
+{
+ const std::string basePath(CPVRTimersPath(true, false).GetPath());
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath() : basePath;
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRTimers.h b/xbmc/pvr/windows/GUIWindowPVRTimers.h
new file mode 100644
index 0000000..2193ac1
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRTimers.h
@@ -0,0 +1,36 @@
+/*
+ * 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 "pvr/windows/GUIWindowPVRTimersBase.h"
+
+#include <string>
+
+namespace PVR
+{
+ class CGUIWindowPVRTVTimers : public CGUIWindowPVRTimersBase
+ {
+ public:
+ CGUIWindowPVRTVTimers();
+
+ protected:
+ std::string GetRootPath() const override;
+ std::string GetDirectoryPath() override;
+ };
+
+ class CGUIWindowPVRRadioTimers : public CGUIWindowPVRTimersBase
+ {
+ public:
+ CGUIWindowPVRRadioTimers();
+
+ protected:
+ std::string GetRootPath() const override;
+ std::string GetDirectoryPath() override;
+ };
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRTimersBase.cpp b/xbmc/pvr/windows/GUIWindowPVRTimersBase.cpp
new file mode 100644
index 0000000..41677eb
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRTimersBase.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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 "GUIWindowPVRTimersBase.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIMessage.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "pvr/PVRManager.h"
+#include "pvr/guilib/PVRGUIActionsTimers.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimersPath.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+
+#include <memory>
+#include <string>
+
+using namespace PVR;
+
+CGUIWindowPVRTimersBase::CGUIWindowPVRTimersBase(bool bRadio, int id, const std::string& xmlFile)
+ : CGUIWindowPVRBase(bRadio, id, xmlFile)
+{
+}
+
+CGUIWindowPVRTimersBase::~CGUIWindowPVRTimersBase() = default;
+
+bool CGUIWindowPVRTimersBase::OnAction(const CAction& action)
+{
+ if (action.GetID() == ACTION_PARENT_DIR || action.GetID() == ACTION_NAV_BACK)
+ {
+ CPVRTimersPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsTimerRule())
+ {
+ m_currentFileItem.reset();
+ GoParentFolder();
+ return true;
+ }
+ }
+ return CGUIWindowPVRBase::OnAction(action);
+}
+
+void CGUIWindowPVRTimersBase::OnPrepareFileItems(CFileItemList& items)
+{
+ const CPVRTimersPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsTimersRoot())
+ {
+ const auto item = std::make_shared<CFileItem>(CPVRTimersPath::PATH_ADDTIMER, false);
+ item->SetLabel(g_localizeStrings.Get(19026)); // "Add timer..."
+ item->SetLabelPreformatted(true);
+ item->SetSpecialSort(SortSpecialOnTop);
+ item->SetArt("icon", "DefaultTVShows.png");
+
+ items.AddFront(item, 0);
+ }
+}
+
+bool CGUIWindowPVRTimersBase::Update(const std::string& strDirectory,
+ bool updateFilterPath /* = true */)
+{
+ int iOldCount = m_vecItems->GetObjectCount();
+ const std::string oldPath = m_vecItems->GetPath();
+
+ bool bReturn = CGUIWindowPVRBase::Update(strDirectory);
+
+ if (bReturn && iOldCount > 0 && m_vecItems->GetObjectCount() == 0 &&
+ oldPath == m_vecItems->GetPath())
+ {
+ /* go to the parent folder if we're in a subdirectory and for instance just deleted the last item */
+ const CPVRTimersPath path(m_vecItems->GetPath());
+ if (path.IsValid() && path.IsTimerRule())
+ {
+ m_currentFileItem.reset();
+ GoParentFolder();
+ }
+ }
+
+ return bReturn;
+}
+
+void CGUIWindowPVRTimersBase::UpdateButtons()
+{
+ SET_CONTROL_SELECTED(GetID(), CONTROL_BTNHIDEDISABLEDTIMERS,
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRTIMERS_HIDEDISABLEDTIMERS));
+
+ CGUIWindowPVRBase::UpdateButtons();
+
+ std::string strHeaderTitle;
+ if (m_currentFileItem && m_currentFileItem->HasPVRTimerInfoTag())
+ {
+ std::shared_ptr<CPVRTimerInfoTag> timer = m_currentFileItem->GetPVRTimerInfoTag();
+ strHeaderTitle = timer->Title();
+ }
+
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, strHeaderTitle);
+}
+
+bool CGUIWindowPVRTimersBase::OnMessage(CGUIMessage& message)
+{
+ bool bReturn = false;
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_CLICKED:
+ if (message.GetSenderId() == m_viewControl.GetCurrentControl())
+ {
+ int iItem = m_viewControl.GetSelectedItem();
+ if (iItem >= 0 && iItem < m_vecItems->Size())
+ {
+ bReturn = true;
+ switch (message.GetParam1())
+ {
+ case ACTION_SHOW_INFO:
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ CFileItemPtr item(m_vecItems->Get(iItem));
+ if (item->m_bIsFolder && (message.GetParam1() != ACTION_SHOW_INFO))
+ {
+ m_currentFileItem = item;
+ bReturn = false; // folders are handled by base class
+ }
+ else
+ {
+ m_currentFileItem.reset();
+ ActionShowTimer(*item);
+ }
+ break;
+ }
+ case ACTION_CONTEXT_MENU:
+ case ACTION_MOUSE_RIGHT_CLICK:
+ OnPopupMenu(iItem);
+ break;
+ case ACTION_DELETE_ITEM:
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().DeleteTimer(
+ *(m_vecItems->Get(iItem)));
+ break;
+ default:
+ bReturn = false;
+ break;
+ }
+ }
+ }
+ else if (message.GetSenderId() == CONTROL_BTNHIDEDISABLEDTIMERS)
+ {
+ const std::shared_ptr<CSettings> settings =
+ CServiceBroker::GetSettingsComponent()->GetSettings();
+ settings->ToggleBool(CSettings::SETTING_PVRTIMERS_HIDEDISABLEDTIMERS);
+ settings->Save();
+ Update(GetDirectoryPath());
+ bReturn = true;
+ }
+ break;
+ case GUI_MSG_REFRESH_LIST:
+ {
+ switch (static_cast<PVREvent>(message.GetParam1()))
+ {
+ case PVREvent::CurrentItem:
+ case PVREvent::Epg:
+ case PVREvent::EpgActiveItem:
+ case PVREvent::EpgContainer:
+ case PVREvent::Timers:
+ SetInvalid();
+ break;
+
+ case PVREvent::TimersInvalidated:
+ Refresh(true);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+
+ return bReturn || CGUIWindowPVRBase::OnMessage(message);
+}
+
+bool CGUIWindowPVRTimersBase::ActionShowTimer(const CFileItem& item)
+{
+ bool bReturn = false;
+
+ /* Check if "Add timer..." entry is selected, if yes
+ create a new timer and open settings dialog, otherwise
+ open settings for selected timer entry */
+ if (URIUtils::PathEquals(item.GetPath(), CPVRTimersPath::PATH_ADDTIMER))
+ bReturn = CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(m_bRadio);
+ else
+ bReturn = CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().EditTimer(item);
+
+ return bReturn;
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRTimersBase.h b/xbmc/pvr/windows/GUIWindowPVRTimersBase.h
new file mode 100644
index 0000000..2f4232d
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRTimersBase.h
@@ -0,0 +1,36 @@
+/*
+ * 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 "pvr/windows/GUIWindowPVRBase.h"
+
+#include <memory>
+
+class CFileItem;
+
+namespace PVR
+{
+class CGUIWindowPVRTimersBase : public CGUIWindowPVRBase
+{
+public:
+ CGUIWindowPVRTimersBase(bool bRadio, int id, const std::string& xmlFile);
+ ~CGUIWindowPVRTimersBase() override;
+
+ bool OnMessage(CGUIMessage& message) override;
+ bool OnAction(const CAction& action) override;
+ void OnPrepareFileItems(CFileItemList& items) override;
+ bool Update(const std::string& strDirectory, bool updateFilterPath = true) override;
+ void UpdateButtons() override;
+
+private:
+ bool ActionShowTimer(const CFileItem& item);
+
+ std::shared_ptr<CFileItem> m_currentFileItem;
+};
+} // namespace PVR