diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/windows | |
parent | Initial commit. (diff) | |
download | kodi-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.txt | 21 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIViewStatePVR.cpp | 182 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIViewStatePVR.h | 78 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRBase.cpp | 563 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRBase.h | 138 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRChannels.cpp | 414 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRChannels.h | 65 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRGuide.cpp | 973 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRGuide.h | 129 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 440 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRRecordings.h | 71 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRSearch.cpp | 544 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRSearch.h | 90 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRTimerRules.cpp | 47 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRTimerRules.h | 38 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRTimers.cpp | 47 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRTimers.h | 36 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRTimersBase.cpp | 204 | ||||
-rw-r--r-- | xbmc/pvr/windows/GUIWindowPVRTimersBase.h | 36 |
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 |