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 "GUIWindowPVRRecordings.h"
11 #include "FileItemList.h"
12 #include "GUIInfoManager.h"
13 #include "ServiceBroker.h"
15 #include "guilib/GUIComponent.h"
16 #include "guilib/GUIMessage.h"
17 #include "guilib/GUIRadioButtonControl.h"
18 #include "guilib/GUIWindowManager.h"
19 #include "guilib/LocalizeStrings.h"
20 #include "input/actions/Action.h"
21 #include "input/actions/ActionIDs.h"
22 #include "pvr/PVRManager.h"
23 #include "pvr/guilib/PVRGUIActionsRecordings.h"
24 #include "pvr/recordings/PVRRecording.h"
25 #include "pvr/recordings/PVRRecordings.h"
26 #include "pvr/recordings/PVRRecordingsPath.h"
27 #include "pvr/utils/PVRPathUtils.h"
28 #include "settings/MediaSettings.h"
29 #include "settings/Settings.h"
30 #include "settings/SettingsComponent.h"
31 #include "utils/URIUtils.h"
32 #include "video/VideoLibraryQueue.h"
33 #include "video/guilib/VideoPlayActionProcessor.h"
34 #include "video/guilib/VideoSelectActionProcessor.h"
35 #include "video/windows/GUIWindowVideoBase.h"
44 CGUIWindowPVRRecordingsBase::CGUIWindowPVRRecordingsBase(bool bRadio
,
46 const std::string
& xmlFile
)
47 : CGUIWindowPVRBase(bRadio
, id
, xmlFile
),
48 m_settings({CSettings::SETTING_PVRRECORD_GROUPRECORDINGS
})
52 CGUIWindowPVRRecordingsBase::~CGUIWindowPVRRecordingsBase() = default;
54 void CGUIWindowPVRRecordingsBase::OnWindowLoaded()
56 CONTROL_SELECT(CONTROL_BTNGROUPITEMS
);
59 void CGUIWindowPVRRecordingsBase::OnDeinitWindow(int nextWindowID
)
61 if (UTILS::HasClientAndProvider(m_vecItems
->GetPath()))
62 m_vecItems
->SetPath(""); // Open default listing next time.
64 CGUIWindowPVRBase::OnDeinitWindow(nextWindowID
);
67 std::string
CGUIWindowPVRRecordingsBase::GetRootPath() const
69 const CURL url
{m_vecItems
->GetPath()};
70 std::string rootPath
{CPVRRecordingsPath(m_bShowDeletedRecordings
, m_bRadio
)};
71 rootPath
+= url
.GetOptions();
75 std::string
CGUIWindowPVRRecordingsBase::GetDirectoryPath()
77 const std::string basePath
= CPVRRecordingsPath(m_bShowDeletedRecordings
, m_bRadio
);
78 return URIUtils::PathHasParent(m_vecItems
->GetPath(), basePath
) ? m_vecItems
->GetPath()
82 void CGUIWindowPVRRecordingsBase::GetContextButtons(int itemNumber
, CContextButtons
& buttons
)
84 if (itemNumber
< 0 || itemNumber
>= m_vecItems
->Size())
86 CFileItemPtr pItem
= m_vecItems
->Get(itemNumber
);
88 if (pItem
->IsParentFolder())
90 // No context menu for ".." items
94 bool isDeletedRecording
= false;
96 std::shared_ptr
<CPVRRecording
> recording(pItem
->GetPVRRecordingInfoTag());
99 isDeletedRecording
= recording
->IsDeleted();
101 if (isDeletedRecording
)
103 if (m_vecItems
->GetObjectCount() > 1)
104 buttons
.Add(CONTEXT_BUTTON_DELETE_ALL
, 19292); /* Delete all permanently */
108 if (!isDeletedRecording
)
109 CGUIWindowPVRBase::GetContextButtons(itemNumber
, buttons
);
112 bool CGUIWindowPVRRecordingsBase::OnAction(const CAction
& action
)
114 if (action
.GetID() == ACTION_PARENT_DIR
|| action
.GetID() == ACTION_NAV_BACK
)
116 CPVRRecordingsPath
path(m_vecItems
->GetPath());
117 if (path
.IsValid() && !path
.IsRecordingsRoot())
123 else if (action
.GetID() == ACTION_TOGGLE_WATCHED
)
125 const std::shared_ptr
<CFileItem
> pItem
= m_vecItems
->Get(m_viewControl
.GetSelectedItem());
126 if (!pItem
|| pItem
->IsParentFolder())
129 bool bUnWatched
= false;
130 if (pItem
->HasPVRRecordingInfoTag())
131 bUnWatched
= pItem
->GetPVRRecordingInfoTag()->GetPlayCount() == 0;
132 else if (pItem
->m_bIsFolder
)
133 bUnWatched
= pItem
->GetProperty("unwatchedepisodes").asInteger() > 0;
137 CVideoLibraryQueue::GetInstance().MarkAsWatched(pItem
, bUnWatched
);
141 return CGUIWindowPVRBase::OnAction(action
);
144 bool CGUIWindowPVRRecordingsBase::OnPopupMenu(int iItem
)
146 if (iItem
>= 0 && iItem
< m_vecItems
->Size())
148 const auto item
= m_vecItems
->Get(iItem
);
149 item
->SetProperty("CheckAutoPlayNextItem", true);
152 return CGUIWindowPVRBase::OnPopupMenu(iItem
);
155 bool CGUIWindowPVRRecordingsBase::OnContextButton(int itemNumber
, CONTEXT_BUTTON button
)
157 if (itemNumber
< 0 || itemNumber
>= m_vecItems
->Size())
159 CFileItemPtr pItem
= m_vecItems
->Get(itemNumber
);
161 return OnContextButtonDeleteAll(pItem
.get(), button
) ||
162 CGUIMediaWindow::OnContextButton(itemNumber
, button
);
165 bool CGUIWindowPVRRecordingsBase::Update(const std::string
& strDirectory
,
166 bool updateFilterPath
/* = true */)
168 m_thumbLoader
.StopThread();
170 int iOldCount
= m_vecItems
->GetObjectCount();
171 const std::string oldPath
= m_vecItems
->GetPath();
173 bool bReturn
= CGUIWindowPVRBase::Update(strDirectory
);
177 // TODO: does it make sense to show the non-deleted recordings, although user wants
178 // to see the deleted recordings? Or is this just another hack to avoid misbehavior
179 // of CGUIMediaWindow if it has no content?
181 std::unique_lock
<CCriticalSection
> lock(m_critSection
);
183 /* empty list for deleted recordings */
184 if (m_vecItems
->GetObjectCount() == 0 && m_bShowDeletedRecordings
)
186 /* show the normal recordings instead */
187 m_bShowDeletedRecordings
= false;
189 Update(GetDirectoryPath());
194 if (bReturn
&& iOldCount
> 0 && m_vecItems
->GetObjectCount() == 0 &&
195 oldPath
== m_vecItems
->GetPath())
197 /* go to the parent folder if we're in a subdirectory and for instance just deleted the last item */
198 const CPVRRecordingsPath
path(m_vecItems
->GetPath());
199 if (path
.IsValid() && !path
.IsRecordingsRoot())
205 void CGUIWindowPVRRecordingsBase::UpdateButtons()
207 int iWatchMode
= CMediaSettings::GetInstance().GetWatchedMode("recordings");
208 int iStringId
= 257; // "Error"
210 if (iWatchMode
== WatchedModeAll
)
211 iStringId
= 22015; // "All recordings"
212 else if (iWatchMode
== WatchedModeUnwatched
)
213 iStringId
= 16101; // "Unwatched"
214 else if (iWatchMode
== WatchedModeWatched
)
215 iStringId
= 16102; // "Watched"
217 SET_CONTROL_LABEL(CONTROL_BTNSHOWMODE
, g_localizeStrings
.Get(iStringId
));
219 bool bGroupRecordings
= m_settings
.GetBoolValue(CSettings::SETTING_PVRRECORD_GROUPRECORDINGS
);
220 SET_CONTROL_SELECTED(GetID(), CONTROL_BTNGROUPITEMS
, bGroupRecordings
);
222 CGUIRadioButtonControl
* btnShowDeleted
=
223 static_cast<CGUIRadioButtonControl
*>(GetControl(CONTROL_BTNSHOWDELETED
));
226 btnShowDeleted
->SetVisible(
227 m_bRadio
? CServiceBroker::GetPVRManager().Recordings()->HasDeletedRadioRecordings()
228 : CServiceBroker::GetPVRManager().Recordings()->HasDeletedTVRecordings());
229 btnShowDeleted
->SetSelected(m_bShowDeletedRecordings
);
232 CGUIWindowPVRBase::UpdateButtons();
234 // If we are filtering by client id / provider id, expose provider's name.
235 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1
, UTILS::GetProviderNameFromPath(m_vecItems
->GetPath()));
237 const CPVRRecordingsPath
path(m_vecItems
->GetPath());
238 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
,
239 bGroupRecordings
&& path
.IsValid() ? path
.GetUnescapedDirectoryPath() : "");
244 class CVideoSelectActionProcessor
: public VIDEO::GUILIB::CVideoSelectActionProcessor
247 CVideoSelectActionProcessor(CGUIWindowPVRRecordingsBase
& window
,
248 const std::shared_ptr
<CFileItem
>& item
,
250 : VIDEO::GUILIB::CVideoSelectActionProcessor(item
), m_window(window
), m_itemIndex(itemIndex
)
255 bool OnChooseSelected() override
257 m_window
.OnPopupMenu(m_itemIndex
);
262 CGUIWindowPVRRecordingsBase
& m_window
;
263 const int m_itemIndex
{-1};
267 bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage
& message
)
269 bool bReturn
= false;
270 switch (message
.GetMessage())
272 case GUI_MSG_CLICKED
:
273 if (message
.GetSenderId() == m_viewControl
.GetCurrentControl())
275 int iItem
= m_viewControl
.GetSelectedItem();
276 if (iItem
>= 0 && iItem
< m_vecItems
->Size())
278 const CFileItemPtr
item(m_vecItems
->Get(iItem
));
279 switch (message
.GetParam1())
281 case ACTION_SELECT_ITEM
:
282 case ACTION_MOUSE_LEFT_CLICK
:
283 case ACTION_PLAYER_PLAY
:
285 const CPVRRecordingsPath
path(m_vecItems
->GetPath());
286 if (path
.IsValid() && path
.IsRecordingsRoot() && item
->IsParentFolder())
288 // handle .. item, which is only visible if list of recordings is empty.
289 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME
);
294 if (!item
->IsParentFolder() && message
.GetParam1() == ACTION_PLAYER_PLAY
)
296 KODI::VIDEO::GUILIB::CVideoPlayActionProcessor proc
{item
};
297 bReturn
= proc
.ProcessDefaultAction();
299 else if (item
->m_bIsFolder
)
301 // recording folders and ".." folders in subfolders are handled by base class.
306 CVideoSelectActionProcessor
proc(*this, item
, iItem
);
307 bReturn
= proc
.ProcessDefaultAction();
311 case ACTION_CONTEXT_MENU
:
312 case ACTION_MOUSE_RIGHT_CLICK
:
316 case ACTION_SHOW_INFO
:
317 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Recordings
>().ShowRecordingInfo(*item
);
320 case ACTION_DELETE_ITEM
:
321 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Recordings
>().DeleteRecording(*item
);
330 else if (message
.GetSenderId() == CONTROL_BTNGROUPITEMS
)
332 const std::shared_ptr
<CSettings
> settings
=
333 CServiceBroker::GetSettingsComponent()->GetSettings();
334 settings
->ToggleBool(CSettings::SETTING_PVRRECORD_GROUPRECORDINGS
);
338 else if (message
.GetSenderId() == CONTROL_BTNSHOWDELETED
)
340 CGUIRadioButtonControl
* radioButton
=
341 static_cast<CGUIRadioButtonControl
*>(GetControl(CONTROL_BTNSHOWDELETED
));
344 m_bShowDeletedRecordings
= radioButton
->IsSelected();
345 Update(GetDirectoryPath());
349 else if (message
.GetSenderId() == CONTROL_BTNSHOWMODE
)
351 CMediaSettings::GetInstance().CycleWatchedMode("recordings");
352 CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
353 OnFilterItems(GetProperty("filter").asString());
358 case GUI_MSG_REFRESH_LIST
:
360 switch (static_cast<PVREvent
>(message
.GetParam1()))
362 case PVREvent::CurrentItem
:
364 case PVREvent::EpgActiveItem
:
365 case PVREvent::EpgContainer
:
366 case PVREvent::Timers
:
370 case PVREvent::RecordingsInvalidated
:
371 case PVREvent::TimersInvalidated
:
382 return bReturn
|| CGUIWindowPVRBase::OnMessage(message
);
385 bool CGUIWindowPVRRecordingsBase::OnContextButtonDeleteAll(CFileItem
* item
, CONTEXT_BUTTON button
)
387 if (button
== CONTEXT_BUTTON_DELETE_ALL
)
389 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Recordings
>().DeleteAllRecordingsFromTrash();
396 void CGUIWindowPVRRecordingsBase::OnPrepareFileItems(CFileItemList
& items
)
402 for (const auto& item
: items
)
404 if (!item
->m_bIsFolder
)
408 if (!files
.IsEmpty())
410 if (m_database
.Open())
412 CGUIWindowVideoBase::LoadVideoInfo(files
, m_database
, false);
415 m_thumbLoader
.Load(files
);
418 CGUIWindowPVRBase::OnPrepareFileItems(items
);
421 bool CGUIWindowPVRRecordingsBase::GetFilteredItems(const std::string
& filter
, CFileItemList
& items
)
423 bool listchanged
= CGUIWindowPVRBase::GetFilteredItems(filter
, items
);
425 int watchMode
= CMediaSettings::GetInstance().GetWatchedMode("recordings");
428 for (int i
= 0; i
< items
.Size(); i
++)
432 if (item
->IsParentFolder()) // Don't delete the go to parent folder
435 if (!item
->HasPVRRecordingInfoTag())
438 int iPlayCount
= item
->GetPVRRecordingInfoTag()->GetPlayCount();
439 if ((watchMode
== WatchedModeWatched
&& iPlayCount
== 0) ||
440 (watchMode
== WatchedModeUnwatched
&& iPlayCount
> 0))
448 // Remove the parent folder item, if it's the only item in the folder.
449 if (items
.GetObjectCount() == 0 && items
.GetFileCount() > 0 && items
.Get(0)->IsParentFolder())