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 "GUIWindowPVRSearch.h"
12 #include "FileItemList.h"
13 #include "ServiceBroker.h"
14 #include "dialogs/GUIDialogBusy.h"
15 #include "dialogs/GUIDialogYesNo.h"
16 #include "guilib/GUIComponent.h"
17 #include "guilib/GUIMessage.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 "messaging/helpers/DialogOKHelper.h"
23 #include "pvr/PVRItem.h"
24 #include "pvr/PVRManager.h"
25 #include "pvr/dialogs/GUIDialogPVRGuideSearch.h"
26 #include "pvr/epg/Epg.h"
27 #include "pvr/epg/EpgContainer.h"
28 #include "pvr/epg/EpgInfoTag.h"
29 #include "pvr/epg/EpgSearchFilter.h"
30 #include "pvr/epg/EpgSearchPath.h"
31 #include "pvr/guilib/PVRGUIActionsEPG.h"
32 #include "pvr/guilib/PVRGUIActionsTimers.h"
33 #include "pvr/recordings/PVRRecording.h"
34 #include "threads/IRunnable.h"
35 #include "utils/StringUtils.h"
36 #include "utils/URIUtils.h"
37 #include "utils/Variant.h"
44 using namespace KODI::MESSAGING
;
48 class AsyncSearchAction
: private IRunnable
51 AsyncSearchAction() = delete;
52 AsyncSearchAction(CFileItemList
* items
, CPVREpgSearchFilter
* filter
)
53 : m_items(items
), m_filter(filter
)
59 // IRunnable implementation
62 CFileItemList
* m_items
;
63 CPVREpgSearchFilter
* m_filter
;
66 bool AsyncSearchAction::Execute()
68 CGUIDialogBusy::Wait(this, 100, false);
72 void AsyncSearchAction::Run()
74 std::vector
<std::shared_ptr
<CPVREpgInfoTag
>> results
=
75 CServiceBroker::GetPVRManager().EpgContainer().GetTags(m_filter
->GetEpgSearchData());
76 m_filter
->SetEpgSearchDataFiltered();
78 // Tags can still contain false positives, for search criteria that cannot be handled via
79 // database. So, run extended search filters on what we got from the database.
80 for (auto it
= results
.begin(); it
!= results
.end();)
82 it
= results
.erase(std::remove_if(results
.begin(), results
.end(),
83 [this](const std::shared_ptr
<CPVREpgInfoTag
>& entry
) {
84 return !m_filter
->FilterEntry(entry
);
89 if (m_filter
->ShouldRemoveDuplicates())
90 m_filter
->RemoveDuplicates(results
);
92 m_filter
->SetLastExecutedDateTime(CDateTime::GetUTCDateTime());
94 for (const auto& tag
: results
)
96 m_items
->Add(std::make_shared
<CFileItem
>(tag
));
99 } // unnamed namespace
101 CGUIWindowPVRSearchBase::CGUIWindowPVRSearchBase(bool bRadio
, int id
, const std::string
& xmlFile
)
102 : CGUIWindowPVRBase(bRadio
, id
, xmlFile
)
106 CGUIWindowPVRSearchBase::~CGUIWindowPVRSearchBase()
110 void CGUIWindowPVRSearchBase::GetContextButtons(int itemNumber
, CContextButtons
& buttons
)
112 if (itemNumber
< 0 || itemNumber
>= m_vecItems
->Size())
115 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
116 const bool bIsSavedSearchesRoot
= (path
.IsValid() && path
.IsSavedSearchesRoot());
117 if (!bIsSavedSearchesRoot
)
118 buttons
.Add(CONTEXT_BUTTON_CLEAR
, 19232); // "Clear search results"
120 CGUIWindowPVRBase::GetContextButtons(itemNumber
, buttons
);
123 bool CGUIWindowPVRSearchBase::OnContextButton(int itemNumber
, CONTEXT_BUTTON button
)
125 if (itemNumber
< 0 || itemNumber
>= m_vecItems
->Size())
127 CFileItemPtr pItem
= m_vecItems
->Get(itemNumber
);
129 return OnContextButtonClear(pItem
.get(), button
) ||
130 CGUIMediaWindow::OnContextButton(itemNumber
, button
);
133 void CGUIWindowPVRSearchBase::SetItemToSearch(const CFileItem
& item
)
135 if (item
.HasEPGSearchFilter())
137 SetSearchFilter(item
.GetEPGSearchFilter());
139 else if (item
.IsUsablePVRRecording())
141 SetSearchFilter(std::make_shared
<CPVREpgSearchFilter
>(m_bRadio
));
142 m_searchfilter
->SetSearchPhrase(item
.GetPVRRecordingInfoTag()->m_strTitle
);
146 SetSearchFilter(std::make_shared
<CPVREpgSearchFilter
>(m_bRadio
));
148 const std::shared_ptr
<const CPVREpgInfoTag
> epgTag(CPVRItem(item
).GetEpgInfoTag());
149 if (epgTag
&& !CServiceBroker::GetPVRManager().IsParentalLocked(epgTag
))
150 m_searchfilter
->SetSearchPhrase(epgTag
->Title());
156 void CGUIWindowPVRSearchBase::OnPrepareFileItems(CFileItemList
& items
)
158 if (m_bSearchConfirmed
)
165 auto item
= std::make_shared
<CFileItem
>(CPVREpgSearchPath::PATH_SEARCH_DIALOG
, false);
166 item
->SetLabel(g_localizeStrings
.Get(
167 m_searchfilter
== nullptr ? 19335 : 19336)); // "New search..." / "Edit search..."
168 item
->SetLabelPreformatted(true);
169 item
->SetSpecialSort(SortSpecialOnTop
);
170 item
->SetArt("icon", "DefaultPVRSearch.png");
173 item
= std::make_shared
<CFileItem
>(m_bRadio
? CPVREpgSearchPath::PATH_RADIO_SAVEDSEARCHES
174 : CPVREpgSearchPath::PATH_TV_SAVEDSEARCHES
,
176 item
->SetLabel(g_localizeStrings
.Get(19337)); // "Saved searches"
177 item
->SetLabelPreformatted(true);
178 item
->SetSpecialSort(SortSpecialOnTop
);
179 item
->SetArt("icon", "DefaultFolder.png");
183 if (m_bSearchConfirmed
)
185 const int itemCount
= items
.GetObjectCount();
187 AsyncSearchAction(&items
, m_searchfilter
.get()).Execute();
189 if (items
.GetObjectCount() == itemCount
)
191 HELPERS::ShowOKDialogText(CVariant
{284}, // "No results found"
192 m_searchfilter
->GetSearchTerm());
197 bool CGUIWindowPVRSearchBase::OnAction(const CAction
& action
)
199 if (action
.GetID() == ACTION_PARENT_DIR
|| action
.GetID() == ACTION_NAV_BACK
)
201 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
202 if (path
.IsValid() && path
.IsSavedSearchesRoot())
204 // Go to root dir and show previous search results if any
205 m_bSearchConfirmed
= (m_searchfilter
!= nullptr);
210 return CGUIWindowPVRBase::OnAction(action
);
213 bool CGUIWindowPVRSearchBase::OnMessage(CGUIMessage
& message
)
215 if (message
.GetMessage() == GUI_MSG_CLICKED
)
217 if (message
.GetSenderId() == m_viewControl
.GetCurrentControl())
219 int iItem
= m_viewControl
.GetSelectedItem();
220 if (iItem
>= 0 && iItem
< m_vecItems
->Size())
222 CFileItemPtr pItem
= m_vecItems
->Get(iItem
);
224 /* process actions */
225 switch (message
.GetParam1())
227 case ACTION_SHOW_INFO
:
228 case ACTION_SELECT_ITEM
:
229 case ACTION_MOUSE_LEFT_CLICK
:
231 const CPVREpgSearchPath
path(pItem
->GetPath());
232 const bool bIsSavedSearch
= (path
.IsValid() && path
.IsSavedSearch());
233 const bool bIsSavedSearchesRoot
= (path
.IsValid() && path
.IsSavedSearchesRoot());
235 if (message
.GetParam1() != ACTION_SHOW_INFO
)
237 if (pItem
->IsParentFolder())
239 // Go to root dir and show previous search results if any
240 m_bSearchConfirmed
= (m_searchfilter
!= nullptr);
241 break; // handled by base class
244 if (bIsSavedSearchesRoot
)
246 // List saved searches
247 m_bSearchConfirmed
= false;
248 break; // handled by base class
253 // Execute selected saved search
254 SetSearchFilter(pItem
->GetEPGSearchFilter());
262 OpenDialogSearch(*pItem
);
264 else if (pItem
->GetPath() == CPVREpgSearchPath::PATH_SEARCH_DIALOG
)
266 OpenDialogSearch(m_searchfilter
);
270 CServiceBroker::GetPVRManager().Get
<PVR::GUI::EPG
>().ShowEPGInfo(*pItem
);
275 case ACTION_CONTEXT_MENU
:
276 case ACTION_MOUSE_RIGHT_CLICK
:
281 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Timers
>().ToggleTimer(*pItem
);
287 else if (message
.GetMessage() == GUI_MSG_REFRESH_LIST
)
289 if (static_cast<PVREvent
>(message
.GetParam1()) == PVREvent::SavedSearchesInvalidated
)
293 // Refresh triggered by deleted saved search?
296 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
297 const bool bIsSavedSearchesRoot
= (path
.IsValid() && path
.IsSavedSearchesRoot());
298 if (bIsSavedSearchesRoot
)
300 const std::string filterPath
= m_searchfilter
->GetPath();
302 for (const auto& item
: *m_vecItems
)
304 const auto filter
= item
->GetEPGSearchFilter();
305 if (filter
&& filter
->GetPath() == filterPath
)
312 SetSearchFilter(nullptr);
317 else if (message
.GetMessage() == GUI_MSG_WINDOW_INIT
)
319 const CPVREpgSearchPath
path(message
.GetStringParam(0));
320 if (path
.IsValid() && path
.IsSavedSearch())
322 const std::shared_ptr
<CPVREpgSearchFilter
> filter
=
323 CServiceBroker::GetPVRManager().EpgContainer().GetSavedSearchById(path
.IsRadio(),
327 SetSearchFilter(filter
);
328 m_bSearchConfirmed
= true;
333 return CGUIWindowPVRBase::OnMessage(message
);
336 bool CGUIWindowPVRSearchBase::Update(const std::string
& strDirectory
,
337 bool updateFilterPath
/* = true */)
339 if (m_vecItems
->GetObjectCount() > 0)
341 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
342 if (path
.IsValid() && path
.IsSavedSearchesRoot())
344 const std::string oldPath
= m_vecItems
->GetPath();
346 const bool bReturn
= CGUIWindowPVRBase::Update(strDirectory
);
348 if (bReturn
&& oldPath
== m_vecItems
->GetPath() && m_vecItems
->GetObjectCount() == 0)
350 // Go to parent folder if we're in a subdir and for instance just deleted the last item
356 return CGUIWindowPVRBase::Update(strDirectory
);
359 void CGUIWindowPVRSearchBase::UpdateButtons()
361 CGUIWindowPVRBase::UpdateButtons();
363 bool bSavedSearchesRoot
= false;
365 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
366 if (path
.IsValid() && path
.IsSavedSearchesRoot())
368 bSavedSearchesRoot
= true;
369 header
= g_localizeStrings
.Get(19337); // "Saved searches"
372 if (header
.empty() && m_searchfilter
)
374 header
= m_searchfilter
->GetTitle();
377 header
= m_searchfilter
->GetSearchTerm();
378 StringUtils::Trim(header
, "\"");
381 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1
, header
);
383 if (!bSavedSearchesRoot
&& m_searchfilter
&& m_searchfilter
->IsChanged())
384 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
, g_localizeStrings
.Get(19342)); // "[not saved]"
385 else if (!bSavedSearchesRoot
&& m_searchfilter
)
386 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
, g_localizeStrings
.Get(19343)); // "[saved]"
388 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
, "");
391 bool CGUIWindowPVRSearchBase::OnContextButtonClear(CFileItem
* item
, CONTEXT_BUTTON button
)
393 bool bReturn
= false;
395 if (button
== CONTEXT_BUTTON_CLEAR
)
399 m_bSearchConfirmed
= false;
400 SetSearchFilter(nullptr);
408 CGUIDialogPVRGuideSearch::Result
CGUIWindowPVRSearchBase::OpenDialogSearch(const CFileItem
& item
)
410 const auto searchFilter
= item
.GetEPGSearchFilter();
412 return CGUIDialogPVRGuideSearch::Result::CANCEL
;
414 return OpenDialogSearch(searchFilter
);
417 CGUIDialogPVRGuideSearch::Result
CGUIWindowPVRSearchBase::OpenDialogSearch(
418 const std::shared_ptr
<CPVREpgSearchFilter
>& searchFilter
)
420 CGUIDialogPVRGuideSearch
* dlgSearch
=
421 CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogPVRGuideSearch
>(
422 WINDOW_DIALOG_PVR_GUIDE_SEARCH
);
425 return CGUIDialogPVRGuideSearch::Result::CANCEL
;
427 const std::shared_ptr
<CPVREpgSearchFilter
> tmpSearchFilter
=
428 searchFilter
!= nullptr ? std::make_shared
<CPVREpgSearchFilter
>(*searchFilter
)
429 : std::make_shared
<CPVREpgSearchFilter
>(m_bRadio
);
431 dlgSearch
->SetFilterData(tmpSearchFilter
);
433 /* Open dialog window */
436 const CGUIDialogPVRGuideSearch::Result result
= dlgSearch
->GetResult();
437 if (result
== CGUIDialogPVRGuideSearch::Result::SEARCH
)
439 SetSearchFilter(tmpSearchFilter
);
442 else if (result
== CGUIDialogPVRGuideSearch::Result::SAVE
)
444 CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*tmpSearchFilter
);
446 searchFilter
->SetDatabaseId(tmpSearchFilter
->GetDatabaseId());
448 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
449 if (path
.IsValid() && path
.IsSearchRoot())
451 SetSearchFilter(tmpSearchFilter
);
459 void CGUIWindowPVRSearchBase::ExecuteSearch()
461 m_bSearchConfirmed
= true;
463 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
464 if (path
.IsValid() && path
.IsSavedSearchesRoot())
473 // Save if not a transient search
474 if (m_searchfilter
->GetDatabaseId() != -1)
475 CServiceBroker::GetPVRManager().EpgContainer().UpdateSavedSearchLastExecuted(*m_searchfilter
);
478 void CGUIWindowPVRSearchBase::SetSearchFilter(
479 const std::shared_ptr
<CPVREpgSearchFilter
>& searchFilter
)
481 if (m_searchfilter
&& m_searchfilter
->IsChanged() &&
482 (!searchFilter
|| m_searchfilter
->GetPath() != searchFilter
->GetPath()))
484 bool bCanceled
= false;
485 if (!CGUIDialogYesNo::ShowAndGetInput(CVariant
{14117}, // "Warning"
486 CVariant
{19341}, // "Save the current search?"
488 CVariant
{!m_searchfilter
->GetTitle().empty()
489 ? m_searchfilter
->GetTitle()
490 : m_searchfilter
->GetSearchTerm()},
491 bCanceled
, CVariant
{107}, // "Yes"
492 CVariant
{106}, // "No"
493 CGUIDialogYesNo::NO_TIMEOUT
) &&
496 std::string title
= m_searchfilter
->GetTitle();
499 title
= m_searchfilter
->GetSearchTerm();
501 title
= g_localizeStrings
.Get(137); // "Search"
503 StringUtils::Trim(title
, "\"");
505 m_searchfilter
->SetTitle(title
);
507 CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*m_searchfilter
);
510 m_searchfilter
= searchFilter
;
513 std::string
CGUIWindowPVRTVSearch::GetRootPath() const
515 return CPVREpgSearchPath::PATH_TV_SEARCH
;
518 std::string
CGUIWindowPVRTVSearch::GetStartFolder(const std::string
& dir
)
520 return CPVREpgSearchPath::PATH_TV_SEARCH
;
523 std::string
CGUIWindowPVRTVSearch::GetDirectoryPath()
525 return URIUtils::PathHasParent(m_vecItems
->GetPath(), CPVREpgSearchPath::PATH_TV_SEARCH
)
526 ? m_vecItems
->GetPath()
527 : CPVREpgSearchPath::PATH_TV_SEARCH
;
530 std::string
CGUIWindowPVRRadioSearch::GetRootPath() const
532 return CPVREpgSearchPath::PATH_RADIO_SEARCH
;
535 std::string
CGUIWindowPVRRadioSearch::GetStartFolder(const std::string
& dir
)
537 return CPVREpgSearchPath::PATH_RADIO_SEARCH
;
540 std::string
CGUIWindowPVRRadioSearch::GetDirectoryPath()
542 return URIUtils::PathHasParent(m_vecItems
->GetPath(), CPVREpgSearchPath::PATH_RADIO_SEARCH
)
543 ? m_vecItems
->GetPath()
544 : CPVREpgSearchPath::PATH_RADIO_SEARCH
;