summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/windows/GUIWindowPVRSearch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/pvr/windows/GUIWindowPVRSearch.cpp')
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRSearch.cpp544
1 files changed, 544 insertions, 0 deletions
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;
+}