Merge pull request #26273 from 78andyp/blurayfixes2
[xbmc.git] / xbmc / pvr / windows / GUIWindowPVRRecordings.cpp
blob900c5b71c0140361fac03bba0345e373bd007b65
1 /*
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.
7 */
9 #include "GUIWindowPVRRecordings.h"
11 #include "FileItemList.h"
12 #include "GUIInfoManager.h"
13 #include "ServiceBroker.h"
14 #include "URL.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"
37 #include <memory>
38 #include <mutex>
39 #include <string>
41 using namespace KODI;
42 using namespace PVR;
44 CGUIWindowPVRRecordingsBase::CGUIWindowPVRRecordingsBase(bool bRadio,
45 int id,
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();
72 return rootPath;
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()
79 : basePath;
82 void CGUIWindowPVRRecordingsBase::GetContextButtons(int itemNumber, CContextButtons& buttons)
84 if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
85 return;
86 CFileItemPtr pItem = m_vecItems->Get(itemNumber);
88 if (pItem->IsParentFolder())
90 // No context menu for ".." items
91 return;
94 bool isDeletedRecording = false;
96 std::shared_ptr<CPVRRecording> recording(pItem->GetPVRRecordingInfoTag());
97 if (recording)
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())
119 GoParentFolder();
120 return true;
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())
127 return false;
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;
134 else
135 return false;
137 CVideoLibraryQueue::GetInstance().MarkAsWatched(pItem, bUnWatched);
138 return true;
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())
158 return false;
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);
175 if (bReturn)
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;
188 lock.unlock();
189 Update(GetDirectoryPath());
190 return bReturn;
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())
200 GoParentFolder();
202 return bReturn;
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));
224 if (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() : "");
242 namespace
244 class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProcessor
246 public:
247 CVideoSelectActionProcessor(CGUIWindowPVRRecordingsBase& window,
248 const std::shared_ptr<CFileItem>& item,
249 int itemIndex)
250 : VIDEO::GUILIB::CVideoSelectActionProcessor(item), m_window(window), m_itemIndex(itemIndex)
254 protected:
255 bool OnChooseSelected() override
257 m_window.OnPopupMenu(m_itemIndex);
258 return true;
261 private:
262 CGUIWindowPVRRecordingsBase& m_window;
263 const int m_itemIndex{-1};
265 } // namespace
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);
290 bReturn = true;
291 break;
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.
302 bReturn = false;
304 else
306 CVideoSelectActionProcessor proc(*this, item, iItem);
307 bReturn = proc.ProcessDefaultAction();
309 break;
311 case ACTION_CONTEXT_MENU:
312 case ACTION_MOUSE_RIGHT_CLICK:
313 OnPopupMenu(iItem);
314 bReturn = true;
315 break;
316 case ACTION_SHOW_INFO:
317 CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().ShowRecordingInfo(*item);
318 bReturn = true;
319 break;
320 case ACTION_DELETE_ITEM:
321 CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().DeleteRecording(*item);
322 bReturn = true;
323 break;
324 default:
325 bReturn = false;
326 break;
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);
335 settings->Save();
336 Refresh(true);
338 else if (message.GetSenderId() == CONTROL_BTNSHOWDELETED)
340 CGUIRadioButtonControl* radioButton =
341 static_cast<CGUIRadioButtonControl*>(GetControl(CONTROL_BTNSHOWDELETED));
342 if (radioButton)
344 m_bShowDeletedRecordings = radioButton->IsSelected();
345 Update(GetDirectoryPath());
347 bReturn = true;
349 else if (message.GetSenderId() == CONTROL_BTNSHOWMODE)
351 CMediaSettings::GetInstance().CycleWatchedMode("recordings");
352 CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
353 OnFilterItems(GetProperty("filter").asString());
354 UpdateButtons();
355 return true;
357 break;
358 case GUI_MSG_REFRESH_LIST:
360 switch (static_cast<PVREvent>(message.GetParam1()))
362 case PVREvent::CurrentItem:
363 case PVREvent::Epg:
364 case PVREvent::EpgActiveItem:
365 case PVREvent::EpgContainer:
366 case PVREvent::Timers:
367 SetInvalid();
368 break;
370 case PVREvent::RecordingsInvalidated:
371 case PVREvent::TimersInvalidated:
372 Refresh(true);
373 break;
375 default:
376 break;
378 break;
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();
390 return true;
393 return false;
396 void CGUIWindowPVRRecordingsBase::OnPrepareFileItems(CFileItemList& items)
398 if (items.IsEmpty())
399 return;
401 CFileItemList files;
402 for (const auto& item : items)
404 if (!item->m_bIsFolder)
405 files.Add(item);
408 if (!files.IsEmpty())
410 if (m_database.Open())
412 CGUIWindowVideoBase::LoadVideoInfo(files, m_database, false);
413 m_database.Close();
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");
427 CFileItemPtr item;
428 for (int i = 0; i < items.Size(); i++)
430 item = items.Get(i);
432 if (item->IsParentFolder()) // Don't delete the go to parent folder
433 continue;
435 if (!item->HasPVRRecordingInfoTag())
436 continue;
438 int iPlayCount = item->GetPVRRecordingInfoTag()->GetPlayCount();
439 if ((watchMode == WatchedModeWatched && iPlayCount == 0) ||
440 (watchMode == WatchedModeUnwatched && iPlayCount > 0))
442 items.Remove(i);
443 i--;
444 listchanged = true;
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())
450 items.Remove(0);
452 return listchanged;