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"
15 #include "addons/AddonManager.h"
16 #include "addons/addoninfo/AddonType.h"
17 #include "dialogs/GUIDialogExtendedProgressBar.h"
18 #include "dialogs/GUIDialogSelect.h"
19 #include "guilib/GUIComponent.h"
20 #include "guilib/GUIMessage.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "guilib/LocalizeStrings.h"
23 #include "input/actions/Action.h"
24 #include "input/actions/ActionIDs.h"
25 #include "messaging/ApplicationMessenger.h"
26 #include "messaging/helpers/DialogOKHelper.h"
27 #include "pvr/PVRManager.h"
28 #include "pvr/PVRPlaybackState.h"
29 #include "pvr/PVRThumbLoader.h"
30 #include "pvr/addons/PVRClient.h"
31 #include "pvr/addons/PVRClients.h"
32 #include "pvr/channels/PVRChannelGroup.h"
33 #include "pvr/channels/PVRChannelGroups.h"
34 #include "pvr/channels/PVRChannelGroupsContainer.h"
35 #include "pvr/filesystem/PVRGUIDirectory.h"
36 #include "pvr/guilib/PVRGUIActionsChannels.h"
37 #include "utils/Variant.h"
38 #include "utils/log.h"
47 using namespace std::chrono_literals
;
49 #define MAX_INVALIDATION_FREQUENCY 2000ms // limit to one invalidation per X milliseconds
52 using namespace KODI::MESSAGING
;
57 class CGUIPVRChannelGroupsSelector
60 virtual ~CGUIPVRChannelGroupsSelector() = default;
62 bool Initialize(CGUIWindow
* parent
, bool bRadio
);
64 bool HasFocus() const;
65 std::shared_ptr
<CPVRChannelGroup
> GetSelectedChannelGroup() const;
66 bool SelectChannelGroup(const std::shared_ptr
<CPVRChannelGroup
>& newGroup
);
69 CGUIControl
* m_control
= nullptr;
70 std::vector
<std::shared_ptr
<CPVRChannelGroup
>> m_channelGroups
;
75 bool CGUIPVRChannelGroupsSelector::Initialize(CGUIWindow
* parent
, bool bRadio
)
77 CGUIControl
* control
= parent
->GetControl(CONTROL_LSTCHANNELGROUPS
);
78 if (control
&& control
->IsContainer())
81 m_channelGroups
= CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio
)->GetMembers(true);
83 CFileItemList channelGroupItems
;
84 CPVRGUIDirectory::GetChannelGroupsDirectory(bRadio
, true, channelGroupItems
);
86 CGUIMessage
msg(GUI_MSG_LABEL_BIND
, m_control
->GetID(), CONTROL_LSTCHANNELGROUPS
, 0, 0, &channelGroupItems
);
87 m_control
->OnMessage(msg
);
93 bool CGUIPVRChannelGroupsSelector::HasFocus() const
95 return m_control
&& m_control
->HasFocus();
98 std::shared_ptr
<CPVRChannelGroup
> CGUIPVRChannelGroupsSelector::GetSelectedChannelGroup() const
102 CGUIMessage
msg(GUI_MSG_ITEM_SELECTED
, m_control
->GetID(), CONTROL_LSTCHANNELGROUPS
);
103 m_control
->OnMessage(msg
);
105 const auto it
= std::next(m_channelGroups
.begin(), msg
.GetParam1());
106 if (it
!= m_channelGroups
.end())
111 return std::shared_ptr
<CPVRChannelGroup
>();
114 bool CGUIPVRChannelGroupsSelector::SelectChannelGroup(const std::shared_ptr
<CPVRChannelGroup
>& newGroup
)
116 if (m_control
&& newGroup
)
119 for (const auto& group
: m_channelGroups
)
121 if (*newGroup
== *group
)
123 CGUIMessage
msg(GUI_MSG_ITEM_SELECT
, m_control
->GetID(), CONTROL_LSTCHANNELGROUPS
, iIndex
);
124 m_control
->OnMessage(msg
);
133 CGUIWindowPVRBase::CGUIWindowPVRBase(bool bRadio
, int id
, const std::string
& xmlFile
)
134 : CGUIMediaWindow(id
, xmlFile
.c_str()),
136 m_channelGroupsSelector(new CGUIPVRChannelGroupsSelector
)
138 // prevent removable drives to appear in directory listing (base class default behavior).
139 m_rootDir
.AllowNonLocalSources(false);
141 CServiceBroker::GetPVRManager().Events().Subscribe(this, &CGUIWindowPVRBase::Notify
);
144 CGUIWindowPVRBase::~CGUIWindowPVRBase()
147 m_channelGroup
->Events().Unsubscribe(this);
149 CServiceBroker::GetPVRManager().Events().Unsubscribe(this);
152 void CGUIWindowPVRBase::UpdateSelectedItemPath()
154 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Channels
>().SetSelectedChannelPath(
155 m_bRadio
, m_viewControl
.GetSelectedItemPath());
158 void CGUIWindowPVRBase::Notify(const PVREvent
& event
)
160 // call virtual event handler function
164 void CGUIWindowPVRBase::NotifyEvent(const PVREvent
& event
)
166 if (event
== PVREvent::ManagerStopped
)
172 if (event
== PVREvent::SystemSleep
)
174 CGUIMessage
m(GUI_MSG_SYSTEM_SLEEP
, GetID(), 0, static_cast<int>(event
));
175 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
177 else if (event
== PVREvent::SystemWake
)
179 CGUIMessage
m(GUI_MSG_SYSTEM_WAKE
, GetID(), 0, static_cast<int>(event
));
180 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
184 CGUIMessage
m(GUI_MSG_REFRESH_LIST
, GetID(), 0, static_cast<int>(event
));
185 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
190 bool CGUIWindowPVRBase::OnAction(const CAction
& action
)
192 switch (action
.GetID())
194 case ACTION_PREVIOUS_CHANNELGROUP
:
195 ActivatePreviousChannelGroup();
198 case ACTION_NEXT_CHANNELGROUP
:
199 ActivateNextChannelGroup();
202 case ACTION_MOVE_RIGHT
:
203 case ACTION_MOVE_LEFT
:
205 if (m_channelGroupsSelector
->HasFocus() && CGUIMediaWindow::OnAction(action
))
207 SetChannelGroup(m_channelGroupsSelector
->GetSelectedChannelGroup());
213 return CGUIMediaWindow::OnAction(action
);
216 bool CGUIWindowPVRBase::ActivatePreviousChannelGroup()
218 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
221 const CPVRChannelGroups
* groups
= CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup
->IsRadio());
224 SetChannelGroup(groups
->GetPreviousGroup(*channelGroup
));
231 bool CGUIWindowPVRBase::ActivateNextChannelGroup()
233 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
236 const CPVRChannelGroups
* groups
= CServiceBroker::GetPVRManager().ChannelGroups()->Get(channelGroup
->IsRadio());
239 SetChannelGroup(groups
->GetNextGroup(*channelGroup
));
246 void CGUIWindowPVRBase::ClearData()
248 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
249 m_channelGroup
.reset();
250 m_channelGroupsSelector
= std::make_unique
<CGUIPVRChannelGroupsSelector
>();
253 void CGUIWindowPVRBase::OnInitWindow()
255 SetProperty("IsRadio", m_bRadio
? "true" : "");
257 if (InitChannelGroup())
259 m_channelGroupsSelector
->Initialize(this, m_bRadio
);
261 CGUIMediaWindow::OnInitWindow();
263 // mark item as selected by channel path
264 m_viewControl
.SetSelectedItem(
265 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Channels
>().GetSelectedChannelPath(m_bRadio
));
267 // This has to be done after base class OnInitWindow to restore correct selection
268 m_channelGroupsSelector
->SelectChannelGroup(GetChannelGroup());
272 CGUIWindow::OnInitWindow(); // do not call CGUIMediaWindow as it will do a Refresh which in no case works in this state (no channelgroup!)
273 ShowProgressDialog(g_localizeStrings
.Get(19235), 0); // PVR manager is starting up
277 void CGUIWindowPVRBase::OnDeinitWindow(int nextWindowID
)
279 HideProgressDialog();
280 UpdateSelectedItemPath();
281 CGUIMediaWindow::OnDeinitWindow(nextWindowID
);
284 bool CGUIWindowPVRBase::OnMessage(CGUIMessage
& message
)
286 bool bReturn
= false;
287 switch (message
.GetMessage())
289 case GUI_MSG_CLICKED
:
291 switch (message
.GetSenderId())
293 case CONTROL_BTNCHANNELGROUPS
:
294 return OpenChannelGroupSelectionDialog();
296 case CONTROL_LSTCHANNELGROUPS
:
298 switch (message
.GetParam1())
300 case ACTION_SELECT_ITEM
:
301 case ACTION_MOUSE_LEFT_CLICK
:
303 SetChannelGroup(m_channelGroupsSelector
->GetSelectedChannelGroup());
313 case GUI_MSG_REFRESH_LIST
:
315 switch (static_cast<PVREvent
>(message
.GetParam1()))
317 case PVREvent::ManagerStarted
:
318 case PVREvent::ClientsInvalidated
:
319 case PVREvent::ChannelGroupsInvalidated
:
321 if (InitChannelGroup())
323 m_channelGroupsSelector
->Initialize(this, m_bRadio
);
324 m_channelGroupsSelector
->SelectChannelGroup(GetChannelGroup());
325 HideProgressDialog();
327 m_viewControl
.SetFocused();
337 // Only the active window must set the selected item path which is shared
338 // between all PVR windows, not the last notified window (observer).
339 UpdateSelectedItemPath();
345 case GUI_MSG_NOTIFY_ALL
:
347 switch (message
.GetParam1())
349 case GUI_MSG_UPDATE_SOURCES
:
351 // removable drive connected/disconnected. base class triggers a window
352 // content refresh, which makes no sense for pvr windows.
358 if (IsActive() && m_bUpdating
)
360 // no concurrent updates
361 CLog::LogF(LOGWARNING
, "GUI_MSG_UPDATE: Updating in progress");
371 return bReturn
|| CGUIMediaWindow::OnMessage(message
);
374 void CGUIWindowPVRBase::SetInvalid()
376 if (m_refreshTimeout
.IsTimePast())
378 for (const auto& item
: *m_vecItems
)
381 CGUIMediaWindow::SetInvalid();
382 m_refreshTimeout
.Set(MAX_INVALIDATION_FREQUENCY
);
386 bool CGUIWindowPVRBase::CanBeActivated() const
388 // check if there is at least one enabled PVR add-on
389 if (!CServiceBroker::GetAddonMgr().HasAddons(ADDON::AddonType::PVRDLL
))
391 HELPERS::ShowOKDialogText(CVariant
{19296}, CVariant
{19272}); // No PVR add-on enabled, You need a tuner, backend software...
398 bool CGUIWindowPVRBase::OpenChannelGroupSelectionDialog()
400 CGUIDialogSelect
* dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(WINDOW_DIALOG_SELECT
);
404 CFileItemList options
;
405 CPVRGUIDirectory::GetChannelGroupsDirectory(m_bRadio
, true, options
);
408 dialog
->SetHeading(CVariant
{g_localizeStrings
.Get(19146)});
409 dialog
->SetMultiSelection(false);
411 auto& pvrMgr
= CServiceBroker::GetPVRManager();
412 const bool useDetails
= pvrMgr
.Clients()->CreatedClientAmount() > 1;
413 dialog
->SetUseDetails(useDetails
);
416 std::string selectedName
;
417 std::string selectedClient
;
419 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
422 selectedName
= channelGroup
->GroupName();
424 auto client
= pvrMgr
.GetClient(channelGroup
->GetClientID());
426 selectedClient
= client
->GetFullClientName();
429 CPVRThumbLoader loader
;
431 for (auto& group
: options
)
433 // set client name as label2
434 const std::shared_ptr
<const CPVRClient
> client
= pvrMgr
.GetClient(*group
);
436 group
->SetLabel2(client
->GetFullClientName());
439 loader
.LoadItem(group
.get());
441 // if not yet done, find and select currently active channel group
444 if (group
->GetLabel() == selectedName
&& group
->GetLabel2() == selectedClient
)
446 dialog
->SetSelected(idx
);
458 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
= GetChannelGroup();
462 const std::string selectedName
= channelGroup
->GroupName();
463 for (auto& group
: options
)
465 // select currently active channel group
466 if (group
->GetLabel() == selectedName
)
468 dialog
->SetSelected(idx
);
476 dialog
->SetItems(options
);
480 if (!dialog
->IsConfirmed())
483 const CFileItemPtr item
= dialog
->GetSelectedFileItem();
487 SetChannelGroup(pvrMgr
.ChannelGroups()->Get(m_bRadio
)->GetGroupByPath(item
->GetPath()));
492 bool CGUIWindowPVRBase::InitChannelGroup()
494 if (!CServiceBroker::GetPVRManager().IsStarted())
497 std::shared_ptr
<CPVRChannelGroup
> group
;
498 if (m_channelGroupPath
.empty())
500 group
= CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(m_bRadio
);
504 group
= CServiceBroker::GetPVRManager().ChannelGroups()->Get(m_bRadio
)->GetGroupByPath(m_channelGroupPath
);
506 CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(group
);
508 CLog::LogF(LOGERROR
, "Found no {} channel group with path '{}'!", m_bRadio
? "radio" : "TV",
511 m_channelGroupPath
.clear();
516 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
517 if (m_channelGroup
!= group
)
519 m_viewControl
.SetSelectedItem(0);
520 SetChannelGroup(std::move(group
), false);
522 // Path might have changed since last init. Set it always, not just on group change.
523 m_vecItems
->SetPath(GetDirectoryPath());
529 std::shared_ptr
<CPVRChannelGroup
> CGUIWindowPVRBase::GetChannelGroup()
531 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
532 return m_channelGroup
;
535 void CGUIWindowPVRBase::SetChannelGroup(std::shared_ptr
<CPVRChannelGroup
> &&group
, bool bUpdate
/* = true */)
540 std::shared_ptr
<CPVRChannelGroup
> updateChannelGroup
;
542 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
543 if (m_channelGroup
!= group
)
546 m_channelGroup
->Events().Unsubscribe(this);
547 m_channelGroup
= std::move(group
);
548 // we need to register the window to receive changes from the new group
549 m_channelGroup
->Events().Subscribe(this, &CGUIWindowPVRBase::Notify
);
551 updateChannelGroup
= m_channelGroup
;
555 if (updateChannelGroup
)
557 CServiceBroker::GetPVRManager().PlaybackState()->SetActiveChannelGroup(updateChannelGroup
);
558 Update(GetDirectoryPath());
562 bool CGUIWindowPVRBase::Update(const std::string
& strDirectory
, bool updateFilterPath
/*= true*/)
566 // no concurrent updates
567 CLog::LogF(LOGWARNING
, "Updating in progress");
571 CUpdateGuard
guard(m_bUpdating
);
573 if (!GetChannelGroup())
575 // no updates before fully initialized
579 int iOldCount
= m_vecItems
->Size();
580 int iSelectedItem
= m_viewControl
.GetSelectedItem();
581 const std::string oldPath
= m_vecItems
->GetPath();
583 bool bReturn
= CGUIMediaWindow::Update(strDirectory
, updateFilterPath
);
586 iSelectedItem
!= -1) // something must have been selected
588 int iNewCount
= m_vecItems
->Size();
589 if (iOldCount
> iNewCount
&& // at least one item removed by Update()
590 oldPath
== m_vecItems
->GetPath()) // update not due changing into another folder
592 // restore selected item if we just deleted one or more items.
593 if (iSelectedItem
>= iNewCount
)
594 iSelectedItem
= iNewCount
- 1;
596 m_viewControl
.SetSelectedItem(iSelectedItem
);
603 void CGUIWindowPVRBase::UpdateButtons()
605 CGUIMediaWindow::UpdateButtons();
607 const std::shared_ptr
<CPVRChannelGroup
> channelGroup
= GetChannelGroup();
610 SET_CONTROL_LABEL(CONTROL_BTNCHANNELGROUPS
, g_localizeStrings
.Get(19141) + ": " + channelGroup
->GroupName());
613 m_channelGroupsSelector
->SelectChannelGroup(channelGroup
);
616 void CGUIWindowPVRBase::ShowProgressDialog(const std::string
& strText
, int iProgress
)
618 if (!m_progressHandle
)
620 CGUIDialogExtendedProgressBar
* loadingProgressDialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogExtendedProgressBar
>(WINDOW_DIALOG_EXT_PROGRESS
);
621 if (!loadingProgressDialog
)
623 CLog::LogF(LOGERROR
, "Unable to get WINDOW_DIALOG_EXT_PROGRESS!");
626 m_progressHandle
= loadingProgressDialog
->GetHandle(g_localizeStrings
.Get(19235)); // PVR manager is starting up
629 m_progressHandle
->SetPercentage(static_cast<float>(iProgress
));
630 m_progressHandle
->SetText(strText
);
633 void CGUIWindowPVRBase::HideProgressDialog()
635 if (m_progressHandle
)
637 m_progressHandle
->MarkFinished();
638 m_progressHandle
= nullptr;