2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "GUIWindowPVRBase.h"
12 #include "FileItemList.h"
13 #include "GUIUserMessages.h"
14 #include "ServiceBroker.h"
16 #include "addons/AddonManager.h"
17 #include "addons/addoninfo/AddonType.h"
18 #include "dialogs/GUIDialogExtendedProgressBar.h"
19 #include "dialogs/GUIDialogSelect.h"
20 #include "guilib/GUIComponent.h"
21 #include "guilib/GUIMessage.h"
22 #include "guilib/GUIWindowManager.h"
23 #include "guilib/LocalizeStrings.h"
24 #include "input/actions/Action.h"
25 #include "input/actions/ActionIDs.h"
26 #include "messaging/ApplicationMessenger.h"
27 #include "messaging/helpers/DialogOKHelper.h"
28 #include "pvr/PVRManager.h"
29 #include "pvr/PVRPlaybackState.h"
30 #include "pvr/PVRThumbLoader.h"
31 #include "pvr/addons/PVRClient.h"
32 #include "pvr/addons/PVRClients.h"
33 #include "pvr/channels/PVRChannelGroup.h"
34 #include "pvr/channels/PVRChannelGroups.h"
35 #include "pvr/channels/PVRChannelGroupsContainer.h"
36 #include "pvr/filesystem/PVRGUIDirectory.h"
37 #include "pvr/guilib/PVRGUIActionsChannels.h"
38 #include "utils/Variant.h"
39 #include "utils/log.h"
48 using namespace std::chrono_literals
;
50 #define MAX_INVALIDATION_FREQUENCY 2000ms // limit to one invalidation per X milliseconds
53 using namespace KODI::MESSAGING
;
58 class CGUIPVRChannelGroupsSelector
61 virtual ~CGUIPVRChannelGroupsSelector() = default;
63 bool Initialize(CGUIWindow
* parent
, bool bRadio
);
65 bool HasFocus() const;
66 std::shared_ptr
<CPVRChannelGroup
> GetSelectedChannelGroup() const;
67 bool SelectChannelGroup(const std::shared_ptr
<CPVRChannelGroup
>& newGroup
);
70 CGUIControl
* m_control
= nullptr;
71 std::vector
<std::shared_ptr
<CPVRChannelGroup
>> m_channelGroups
;
76 bool CGUIPVRChannelGroupsSelector::Initialize(CGUIWindow
* parent
, bool bRadio
)
78 CGUIControl
* control
= parent
->GetControl(CONTROL_LSTCHANNELGROUPS
);
79 if (control
&& control
->IsContainer())
82 m_channelGroups
= CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio
)->GetMembers(true);
84 CFileItemList channelGroupItems
;
85 CPVRGUIDirectory::GetChannelGroupsDirectory(bRadio
, true, channelGroupItems
);
87 CGUIMessage
msg(GUI_MSG_LABEL_BIND
, m_control
->GetID(), CONTROL_LSTCHANNELGROUPS
, 0, 0, &channelGroupItems
);
88 m_control
->OnMessage(msg
);
94 bool CGUIPVRChannelGroupsSelector::HasFocus() const
96 return m_control
&& m_control
->HasFocus();
99 std::shared_ptr
<CPVRChannelGroup
> CGUIPVRChannelGroupsSelector::GetSelectedChannelGroup() const
103 CGUIMessage
msg(GUI_MSG_ITEM_SELECTED
, m_control
->GetID(), CONTROL_LSTCHANNELGROUPS
);
104 m_control
->OnMessage(msg
);
106 const auto it
= std::next(m_channelGroups
.begin(), msg
.GetParam1());
107 if (it
!= m_channelGroups
.end())
112 return std::shared_ptr
<CPVRChannelGroup
>();
115 bool CGUIPVRChannelGroupsSelector::SelectChannelGroup(const std::shared_ptr
<CPVRChannelGroup
>& newGroup
)
117 if (m_control
&& newGroup
)
120 for (const auto& group
: m_channelGroups
)
122 if (*newGroup
== *group
)
124 CGUIMessage
msg(GUI_MSG_ITEM_SELECT
, m_control
->GetID(), CONTROL_LSTCHANNELGROUPS
, iIndex
);
125 m_control
->OnMessage(msg
);
134 CGUIWindowPVRBase::CGUIWindowPVRBase(bool bRadio
, int id
, const std::string
& xmlFile
)
135 : CGUIMediaWindow(id
, xmlFile
.c_str()),
137 m_channelGroupsSelector(new CGUIPVRChannelGroupsSelector
)
139 // prevent removable drives to appear in directory listing (base class default behavior).
140 m_rootDir
.AllowNonLocalSources(false);
142 CServiceBroker::GetPVRManager().Events().Subscribe(this, &CGUIWindowPVRBase::Notify
);
145 CGUIWindowPVRBase::~CGUIWindowPVRBase()
148 m_channelGroup
->Events().Unsubscribe(this);
150 CServiceBroker::GetPVRManager().Events().Unsubscribe(this);
153 void CGUIWindowPVRBase::UpdateSelectedItemPath()
155 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Channels
>().SetSelectedChannelPath(
156 m_bRadio
, m_viewControl
.GetSelectedItemPath());
159 void CGUIWindowPVRBase::Notify(const PVREvent
& event
)
161 // call virtual event handler function
165 void CGUIWindowPVRBase::NotifyEvent(const PVREvent
& event
)
167 if (event
== PVREvent::ManagerStopped
)
173 if (event
== PVREvent::SystemSleep
)
175 CGUIMessage
m(GUI_MSG_SYSTEM_SLEEP
, GetID(), 0, static_cast<int>(event
));
176 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
178 else if (event
== PVREvent::SystemWake
)
180 CGUIMessage
m(GUI_MSG_SYSTEM_WAKE
, GetID(), 0, static_cast<int>(event
));
181 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
185 CGUIMessage
m(GUI_MSG_REFRESH_LIST
, GetID(), 0, static_cast<int>(event
));
186 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
191 bool CGUIWindowPVRBase::OnAction(const CAction
& action
)
193 switch (action
.GetID())
195 case ACTION_PREVIOUS_CHANNELGROUP
:
196 ActivatePreviousChannelGroup();
199 case ACTION_NEXT_CHANNELGROUP
:
200 ActivateNextChannelGroup();
203 case ACTION_MOVE_RIGHT
:
204 case ACTION_MOVE_LEFT
:
206 if (m_channelGroupsSelector
->HasFocus() && CGUIMediaWindow::OnAction(action
))
208 SetChannelGroup(m_channelGroupsSelector
->GetSelectedChannelGroup());
214 return CGUIMediaWindow::OnAction(action
);
217 bool CGUIWindowPVRBase::ActivatePreviousChannelGroup()
219 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
222 const std::shared_ptr
<const CPVRChannelGroups
> groups
{
223 CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup
->IsRadio())};
226 SetChannelGroup(groups
->GetPreviousGroup(*channelGroup
));
233 bool CGUIWindowPVRBase::ActivateNextChannelGroup()
235 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
238 const std::shared_ptr
<const CPVRChannelGroups
> groups
{
239 CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup
->IsRadio())};
242 SetChannelGroup(groups
->GetNextGroup(*channelGroup
));
249 void CGUIWindowPVRBase::ClearData()
251 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
252 m_channelGroup
.reset();
253 m_channelGroupsSelector
= std::make_unique
<CGUIPVRChannelGroupsSelector
>();
256 void CGUIWindowPVRBase::OnInitWindow()
258 SetProperty("IsRadio", m_bRadio
? "true" : "");
260 if (InitChannelGroup())
262 m_channelGroupsSelector
->Initialize(this, m_bRadio
);
264 CGUIMediaWindow::OnInitWindow();
266 // mark item as selected by channel path
267 m_viewControl
.SetSelectedItem(
268 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Channels
>().GetSelectedChannelPath(m_bRadio
));
270 // This has to be done after base class OnInitWindow to restore correct selection
271 m_channelGroupsSelector
->SelectChannelGroup(GetChannelGroup());
275 CGUIWindow::OnInitWindow(); // do not call CGUIMediaWindow as it will do a Refresh which in no case works in this state (no channelgroup!)
276 ShowProgressDialog(g_localizeStrings
.Get(19235), 0); // PVR manager is starting up
280 void CGUIWindowPVRBase::OnDeinitWindow(int nextWindowID
)
282 HideProgressDialog();
283 UpdateSelectedItemPath();
284 CGUIMediaWindow::OnDeinitWindow(nextWindowID
);
287 bool CGUIWindowPVRBase::OnMessage(CGUIMessage
& message
)
289 bool bReturn
= false;
290 switch (message
.GetMessage())
292 case GUI_MSG_CLICKED
:
294 switch (message
.GetSenderId())
296 case CONTROL_BTNCHANNELGROUPS
:
297 return OpenChannelGroupSelectionDialog();
299 case CONTROL_LSTCHANNELGROUPS
:
301 switch (message
.GetParam1())
303 case ACTION_SELECT_ITEM
:
304 case ACTION_MOUSE_LEFT_CLICK
:
306 SetChannelGroup(m_channelGroupsSelector
->GetSelectedChannelGroup());
316 case GUI_MSG_REFRESH_LIST
:
318 switch (static_cast<PVREvent
>(message
.GetParam1()))
320 case PVREvent::ManagerStarted
:
321 case PVREvent::ClientsInvalidated
:
322 case PVREvent::ChannelGroupsInvalidated
:
324 if (InitChannelGroup())
326 m_channelGroupsSelector
->Initialize(this, m_bRadio
);
327 m_channelGroupsSelector
->SelectChannelGroup(GetChannelGroup());
328 HideProgressDialog();
330 m_viewControl
.SetFocused();
340 // Only the active window must set the selected item path which is shared
341 // between all PVR windows, not the last notified window (observer).
342 UpdateSelectedItemPath();
348 case GUI_MSG_NOTIFY_ALL
:
350 switch (message
.GetParam1())
352 case GUI_MSG_UPDATE_SOURCES
:
354 // removable drive connected/disconnected. base class triggers a window
355 // content refresh, which makes no sense for pvr windows.
361 if (IsActive() && m_bUpdating
)
363 // no concurrent updates
364 CLog::LogF(LOGWARNING
, "GUI_MSG_UPDATE: Updating in progress");
374 return bReturn
|| CGUIMediaWindow::OnMessage(message
);
377 void CGUIWindowPVRBase::SetInvalid()
379 if (m_refreshTimeout
.IsTimePast())
381 for (const auto& item
: *m_vecItems
)
384 CGUIMediaWindow::SetInvalid();
385 m_refreshTimeout
.Set(MAX_INVALIDATION_FREQUENCY
);
389 bool CGUIWindowPVRBase::CanBeActivated() const
391 // check if there is at least one enabled PVR add-on
392 if (!CServiceBroker::GetAddonMgr().HasAddons(ADDON::AddonType::PVRDLL
))
394 HELPERS::ShowOKDialogText(CVariant
{19296}, CVariant
{19272}); // No PVR add-on enabled, You need a tuner, backend software...
401 bool CGUIWindowPVRBase::OpenChannelGroupSelectionDialog()
403 CGUIDialogSelect
* dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(WINDOW_DIALOG_SELECT
);
407 CFileItemList options
;
408 CPVRGUIDirectory::GetChannelGroupsDirectory(m_bRadio
, true, options
);
411 dialog
->SetHeading(CVariant
{g_localizeStrings
.Get(19146)});
412 dialog
->SetMultiSelection(false);
413 dialog
->SetItems(options
);
415 auto& pvrMgr
= CServiceBroker::GetPVRManager();
416 const bool useDetails
= pvrMgr
.Clients()->CreatedClientAmount() > 1;
417 dialog
->SetUseDetails(useDetails
);
420 std::string selectedGroup
;
421 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
423 selectedGroup
= channelGroup
->GetPath();
425 CPVRThumbLoader loader
;
427 for (auto& group
: options
)
429 // set client name as label2
430 const std::shared_ptr
<const CPVRClient
> client
= pvrMgr
.GetClient(*group
);
432 group
->SetLabel2(client
->GetFullClientName());
435 loader
.LoadItem(group
.get());
437 // if not yet done, find and select currently active channel group
440 if (group
->GetPath() == selectedGroup
)
442 dialog
->SetSelected(idx
);
454 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
458 const std::string selectedGroup
{channelGroup
->GetPath()};
459 for (auto& group
: options
)
461 // select currently active channel group
462 if (group
->GetPath() == selectedGroup
)
464 dialog
->SetSelected(idx
);
474 if (!dialog
->IsConfirmed())
477 const CFileItemPtr item
= dialog
->GetSelectedFileItem();
481 SetChannelGroup(pvrMgr
.ChannelGroups()->Get(m_bRadio
)->GetGroupByPath(item
->GetPath()));
486 bool CGUIWindowPVRBase::InitChannelGroup()
488 if (!CServiceBroker::GetPVRManager().IsStarted())
491 std::shared_ptr
<CPVRChannelGroup
> group
;
492 if (m_channelGroupPath
.empty())
494 group
= CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(m_bRadio
);
498 group
= CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bRadio
)->GetGroupByPath(m_channelGroupPath
);
500 CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(group
);
502 CLog::LogF(LOGERROR
, "Found no {} channel group with path '{}'!", m_bRadio
? "radio" : "TV",
505 m_channelGroupPath
.clear();
510 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
511 if (m_channelGroup
!= group
)
513 m_viewControl
.SetSelectedItem(0);
514 SetChannelGroup(std::move(group
), false);
516 // Path might have changed since last init. Set it always, not just on group change.
517 m_vecItems
->SetPath(GetDirectoryPath());
523 std::shared_ptr
<CPVRChannelGroup
> CGUIWindowPVRBase::GetChannelGroup()
525 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
526 return m_channelGroup
;
529 void CGUIWindowPVRBase::SetChannelGroup(std::shared_ptr
<CPVRChannelGroup
> &&group
, bool bUpdate
/* = true */)
534 std::shared_ptr
<CPVRChannelGroup
> updateChannelGroup
;
536 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
537 if (m_channelGroup
!= group
)
540 m_channelGroup
->Events().Unsubscribe(this);
541 m_channelGroup
= std::move(group
);
542 // we need to register the window to receive changes from the new group
543 m_channelGroup
->Events().Subscribe(this, &CGUIWindowPVRBase::Notify
);
545 updateChannelGroup
= m_channelGroup
;
549 if (updateChannelGroup
)
551 CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(updateChannelGroup
);
552 Update(GetDirectoryPath());
556 void CGUIWindowPVRBase::SetChannelGroupPath(const std::string
& path
)
558 const CURL url
{path
};
559 const std::string pathWithoutOptions
{url
.GetWithoutOptions()};
561 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
562 if (m_channelGroupPath
!= pathWithoutOptions
)
564 m_channelGroupPath
= pathWithoutOptions
;
568 bool CGUIWindowPVRBase::Update(const std::string
& strDirectory
, bool updateFilterPath
/*= true*/)
572 // no concurrent updates
573 CLog::LogF(LOGWARNING
, "Updating in progress");
577 CUpdateGuard
guard(m_bUpdating
);
579 if (!GetChannelGroup())
581 // no updates before fully initialized
585 int iOldCount
= m_vecItems
->Size();
586 int iSelectedItem
= m_viewControl
.GetSelectedItem();
587 const std::string oldPath
= m_vecItems
->GetPath();
589 bool bReturn
= CGUIMediaWindow::Update(strDirectory
, updateFilterPath
);
592 iSelectedItem
!= -1) // something must have been selected
594 int iNewCount
= m_vecItems
->Size();
595 if (iOldCount
> iNewCount
&& // at least one item removed by Update()
596 oldPath
== m_vecItems
->GetPath()) // update not due changing into another folder
598 // restore selected item if we just deleted one or more items.
599 if (iSelectedItem
>= iNewCount
)
600 iSelectedItem
= iNewCount
- 1;
602 m_viewControl
.SetSelectedItem(iSelectedItem
);
609 void CGUIWindowPVRBase::UpdateButtons()
611 CGUIMediaWindow::UpdateButtons();
613 const std::shared_ptr
<CPVRChannelGroup
> channelGroup
= GetChannelGroup();
616 SET_CONTROL_LABEL(CONTROL_BTNCHANNELGROUPS
, g_localizeStrings
.Get(19141) + ": " + channelGroup
->GroupName());
619 m_channelGroupsSelector
->SelectChannelGroup(channelGroup
);
622 void CGUIWindowPVRBase::ShowProgressDialog(const std::string
& strText
, int iProgress
)
624 if (!m_progressHandle
)
626 CGUIDialogExtendedProgressBar
* loadingProgressDialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogExtendedProgressBar
>(WINDOW_DIALOG_EXT_PROGRESS
);
627 if (!loadingProgressDialog
)
629 CLog::LogF(LOGERROR
, "Unable to get WINDOW_DIALOG_EXT_PROGRESS!");
632 m_progressHandle
= loadingProgressDialog
->GetHandle(g_localizeStrings
.Get(19235)); // PVR manager is starting up
635 m_progressHandle
->SetPercentage(static_cast<float>(iProgress
));
636 m_progressHandle
->SetText(strText
);
639 void CGUIWindowPVRBase::HideProgressDialog()
641 if (m_progressHandle
)
643 m_progressHandle
->MarkFinished();
644 m_progressHandle
= nullptr;