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/EpgSearch.h"
30 #include "pvr/epg/EpgSearchFilter.h"
31 #include "pvr/epg/EpgSearchPath.h"
32 #include "pvr/guilib/PVRGUIActionsEPG.h"
33 #include "pvr/guilib/PVRGUIActionsTimers.h"
34 #include "pvr/recordings/PVRRecording.h"
35 #include "threads/IRunnable.h"
36 #include "utils/StringUtils.h"
37 #include "utils/URIUtils.h"
38 #include "utils/Variant.h"
45 using namespace KODI::MESSAGING
;
49 class AsyncSearchAction
: private IRunnable
52 AsyncSearchAction() = delete;
53 AsyncSearchAction(CFileItemList
* items
, CPVREpgSearchFilter
* filter
)
54 : m_items(items
), m_filter(filter
)
60 // IRunnable implementation
63 CFileItemList
* m_items
;
64 CPVREpgSearchFilter
* m_filter
;
67 bool AsyncSearchAction::Execute()
69 CGUIDialogBusy::Wait(this, 100, false);
73 void AsyncSearchAction::Run()
75 CPVREpgSearch
search(*m_filter
);
77 const auto tags
{search
.GetResults()};
78 for (const auto& tag
: tags
)
80 m_items
->Add(std::make_shared
<CFileItem
>(tag
));
83 } // unnamed namespace
85 CGUIWindowPVRSearchBase::CGUIWindowPVRSearchBase(bool bRadio
, int id
, const std::string
& xmlFile
)
86 : CGUIWindowPVRBase(bRadio
, id
, xmlFile
)
90 CGUIWindowPVRSearchBase::~CGUIWindowPVRSearchBase()
94 void CGUIWindowPVRSearchBase::GetContextButtons(int itemNumber
, CContextButtons
& buttons
)
96 if (itemNumber
< 0 || itemNumber
>= m_vecItems
->Size())
99 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
100 const bool bIsSavedSearchesRoot
= (path
.IsValid() && path
.IsSavedSearchesRoot());
101 if (!bIsSavedSearchesRoot
)
102 buttons
.Add(CONTEXT_BUTTON_CLEAR
, 19232); // "Clear search results"
104 CGUIWindowPVRBase::GetContextButtons(itemNumber
, buttons
);
107 bool CGUIWindowPVRSearchBase::OnContextButton(int itemNumber
, CONTEXT_BUTTON button
)
109 if (itemNumber
< 0 || itemNumber
>= m_vecItems
->Size())
111 CFileItemPtr pItem
= m_vecItems
->Get(itemNumber
);
113 return OnContextButtonClear(pItem
.get(), button
) ||
114 CGUIMediaWindow::OnContextButton(itemNumber
, button
);
117 void CGUIWindowPVRSearchBase::SetItemToSearch(const CFileItem
& item
)
119 if (item
.HasEPGSearchFilter())
121 SetSearchFilter(item
.GetEPGSearchFilter());
123 else if (item
.IsUsablePVRRecording())
125 SetSearchFilter(std::make_shared
<CPVREpgSearchFilter
>(m_bRadio
));
126 m_searchfilter
->SetSearchPhrase(item
.GetPVRRecordingInfoTag()->m_strTitle
);
130 SetSearchFilter(std::make_shared
<CPVREpgSearchFilter
>(m_bRadio
));
132 const std::shared_ptr
<const CPVREpgInfoTag
> epgTag(CPVRItem(item
).GetEpgInfoTag());
133 if (epgTag
&& !CServiceBroker::GetPVRManager().IsParentalLocked(epgTag
))
134 m_searchfilter
->SetSearchPhrase(epgTag
->Title());
140 void CGUIWindowPVRSearchBase::OnPrepareFileItems(CFileItemList
& items
)
142 if (m_bSearchConfirmed
)
149 auto item
= std::make_shared
<CFileItem
>(CPVREpgSearchPath::PATH_SEARCH_DIALOG
, false);
150 item
->SetLabel(g_localizeStrings
.Get(
151 m_searchfilter
== nullptr ? 19335 : 19336)); // "New search..." / "Edit search..."
152 item
->SetLabelPreformatted(true);
153 item
->SetSpecialSort(SortSpecialOnTop
);
154 item
->SetArt("icon", "DefaultPVRSearch.png");
157 item
= std::make_shared
<CFileItem
>(m_bRadio
? CPVREpgSearchPath::PATH_RADIO_SAVEDSEARCHES
158 : CPVREpgSearchPath::PATH_TV_SAVEDSEARCHES
,
160 item
->SetLabel(g_localizeStrings
.Get(19337)); // "Saved searches"
161 item
->SetLabelPreformatted(true);
162 item
->SetSpecialSort(SortSpecialOnTop
);
163 item
->SetArt("icon", "DefaultFolder.png");
167 if (m_bSearchConfirmed
)
169 const int itemCount
= items
.GetObjectCount();
171 AsyncSearchAction(&items
, m_searchfilter
.get()).Execute();
173 if (items
.GetObjectCount() == itemCount
)
175 HELPERS::ShowOKDialogText(CVariant
{284}, // "No results found"
176 m_searchfilter
->GetSearchTerm());
181 bool CGUIWindowPVRSearchBase::OnAction(const CAction
& action
)
183 if (action
.GetID() == ACTION_PARENT_DIR
|| action
.GetID() == ACTION_NAV_BACK
)
185 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
186 if (path
.IsValid() && path
.IsSavedSearchesRoot())
188 // Go to root dir and show previous search results if any
189 m_bSearchConfirmed
= (m_searchfilter
!= nullptr);
194 return CGUIWindowPVRBase::OnAction(action
);
197 bool CGUIWindowPVRSearchBase::OnMessage(CGUIMessage
& message
)
199 if (message
.GetMessage() == GUI_MSG_CLICKED
)
201 if (message
.GetSenderId() == m_viewControl
.GetCurrentControl())
203 int iItem
= m_viewControl
.GetSelectedItem();
204 if (iItem
>= 0 && iItem
< m_vecItems
->Size())
206 CFileItemPtr pItem
= m_vecItems
->Get(iItem
);
208 /* process actions */
209 switch (message
.GetParam1())
211 case ACTION_SHOW_INFO
:
212 case ACTION_SELECT_ITEM
:
213 case ACTION_MOUSE_LEFT_CLICK
:
215 const CPVREpgSearchPath
path(pItem
->GetPath());
216 const bool bIsSavedSearch
= (path
.IsValid() && path
.IsSavedSearch());
217 const bool bIsSavedSearchesRoot
= (path
.IsValid() && path
.IsSavedSearchesRoot());
219 if (message
.GetParam1() != ACTION_SHOW_INFO
)
221 if (pItem
->IsParentFolder())
223 // Go to root dir and show previous search results if any
224 m_bSearchConfirmed
= (m_searchfilter
!= nullptr);
225 break; // handled by base class
228 if (bIsSavedSearchesRoot
)
230 // List saved searches
231 m_bSearchConfirmed
= false;
232 break; // handled by base class
237 // Execute selected saved search
238 SetSearchFilter(pItem
->GetEPGSearchFilter());
246 OpenDialogSearch(*pItem
);
248 else if (pItem
->GetPath() == CPVREpgSearchPath::PATH_SEARCH_DIALOG
)
250 OpenDialogSearch(m_searchfilter
);
254 CServiceBroker::GetPVRManager().Get
<PVR::GUI::EPG
>().ShowEPGInfo(*pItem
);
259 case ACTION_CONTEXT_MENU
:
260 case ACTION_MOUSE_RIGHT_CLICK
:
265 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Timers
>().ToggleTimer(*pItem
);
271 else if (message
.GetMessage() == GUI_MSG_REFRESH_LIST
)
273 if (static_cast<PVREvent
>(message
.GetParam1()) == PVREvent::SavedSearchesInvalidated
)
277 // Refresh triggered by deleted saved search?
280 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
281 const bool bIsSavedSearchesRoot
= (path
.IsValid() && path
.IsSavedSearchesRoot());
282 if (bIsSavedSearchesRoot
)
284 const std::string filterPath
= m_searchfilter
->GetPath();
286 for (const auto& item
: *m_vecItems
)
288 const auto filter
= item
->GetEPGSearchFilter();
289 if (filter
&& filter
->GetPath() == filterPath
)
296 SetSearchFilter(nullptr);
301 else if (message
.GetMessage() == GUI_MSG_WINDOW_INIT
)
303 const CPVREpgSearchPath
path(message
.GetStringParam(0));
304 if (path
.IsValid() && path
.IsSavedSearch())
306 const std::shared_ptr
<CPVREpgSearchFilter
> filter
=
307 CServiceBroker::GetPVRManager().EpgContainer().GetSavedSearchById(path
.IsRadio(),
311 SetSearchFilter(filter
);
312 m_bSearchConfirmed
= true;
317 return CGUIWindowPVRBase::OnMessage(message
);
320 bool CGUIWindowPVRSearchBase::Update(const std::string
& strDirectory
,
321 bool updateFilterPath
/* = true */)
323 if (m_vecItems
->GetObjectCount() > 0)
325 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
326 if (path
.IsValid() && path
.IsSavedSearchesRoot())
328 const std::string oldPath
= m_vecItems
->GetPath();
330 const bool bReturn
= CGUIWindowPVRBase::Update(strDirectory
);
332 if (bReturn
&& oldPath
== m_vecItems
->GetPath() && m_vecItems
->GetObjectCount() == 0)
334 // Go to parent folder if we're in a subdir and for instance just deleted the last item
340 return CGUIWindowPVRBase::Update(strDirectory
);
343 void CGUIWindowPVRSearchBase::UpdateButtons()
345 CGUIWindowPVRBase::UpdateButtons();
347 bool bSavedSearchesRoot
= false;
349 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
350 if (path
.IsValid() && path
.IsSavedSearchesRoot())
352 bSavedSearchesRoot
= true;
353 header
= g_localizeStrings
.Get(19337); // "Saved searches"
356 if (header
.empty() && m_searchfilter
)
358 header
= m_searchfilter
->GetTitle();
361 header
= m_searchfilter
->GetSearchTerm();
362 StringUtils::Trim(header
, "\"");
365 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1
, header
);
367 if (!bSavedSearchesRoot
&& m_searchfilter
&& m_searchfilter
->IsChanged())
368 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
, g_localizeStrings
.Get(19342)); // "[not saved]"
369 else if (!bSavedSearchesRoot
&& m_searchfilter
)
370 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
, g_localizeStrings
.Get(19343)); // "[saved]"
372 SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2
, "");
375 bool CGUIWindowPVRSearchBase::OnContextButtonClear(CFileItem
* item
, CONTEXT_BUTTON button
)
377 bool bReturn
= false;
379 if (button
== CONTEXT_BUTTON_CLEAR
)
383 m_bSearchConfirmed
= false;
384 SetSearchFilter(nullptr);
392 CGUIDialogPVRGuideSearch::Result
CGUIWindowPVRSearchBase::OpenDialogSearch(const CFileItem
& item
)
394 const auto searchFilter
= item
.GetEPGSearchFilter();
396 return CGUIDialogPVRGuideSearch::Result::CANCEL
;
398 return OpenDialogSearch(searchFilter
);
401 CGUIDialogPVRGuideSearch::Result
CGUIWindowPVRSearchBase::OpenDialogSearch(
402 const std::shared_ptr
<CPVREpgSearchFilter
>& searchFilter
)
404 CGUIDialogPVRGuideSearch
* dlgSearch
=
405 CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogPVRGuideSearch
>(
406 WINDOW_DIALOG_PVR_GUIDE_SEARCH
);
409 return CGUIDialogPVRGuideSearch::Result::CANCEL
;
411 const std::shared_ptr
<CPVREpgSearchFilter
> tmpSearchFilter
=
412 searchFilter
!= nullptr ? std::make_shared
<CPVREpgSearchFilter
>(*searchFilter
)
413 : std::make_shared
<CPVREpgSearchFilter
>(m_bRadio
);
415 dlgSearch
->SetFilterData(tmpSearchFilter
);
417 /* Open dialog window */
420 const CGUIDialogPVRGuideSearch::Result result
= dlgSearch
->GetResult();
421 if (result
== CGUIDialogPVRGuideSearch::Result::SEARCH
)
423 SetSearchFilter(tmpSearchFilter
);
426 else if (result
== CGUIDialogPVRGuideSearch::Result::SAVE
)
428 CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*tmpSearchFilter
);
430 searchFilter
->SetDatabaseId(tmpSearchFilter
->GetDatabaseId());
432 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
433 if (path
.IsValid() && path
.IsSearchRoot())
435 SetSearchFilter(tmpSearchFilter
);
443 void CGUIWindowPVRSearchBase::ExecuteSearch()
445 m_bSearchConfirmed
= true;
447 const CPVREpgSearchPath
path(m_vecItems
->GetPath());
448 if (path
.IsValid() && path
.IsSavedSearchesRoot())
457 // Save if not a transient search
458 if (m_searchfilter
->GetDatabaseId() != PVR_EPG_SEARCH_INVALID_DATABASE_ID
)
459 CServiceBroker::GetPVRManager().EpgContainer().UpdateSavedSearchLastExecuted(*m_searchfilter
);
462 void CGUIWindowPVRSearchBase::SetSearchFilter(
463 const std::shared_ptr
<CPVREpgSearchFilter
>& searchFilter
)
465 if (m_searchfilter
&& m_searchfilter
->IsChanged() &&
466 (!searchFilter
|| m_searchfilter
->GetPath() != searchFilter
->GetPath()))
468 bool bCanceled
= false;
469 if (!CGUIDialogYesNo::ShowAndGetInput(CVariant
{14117}, // "Warning"
470 CVariant
{19341}, // "Save the current search?"
472 CVariant
{!m_searchfilter
->GetTitle().empty()
473 ? m_searchfilter
->GetTitle()
474 : m_searchfilter
->GetSearchTerm()},
475 bCanceled
, CVariant
{107}, // "Yes"
476 CVariant
{106}, // "No"
477 CGUIDialogYesNo::NO_TIMEOUT
) &&
480 std::string title
= m_searchfilter
->GetTitle();
483 title
= m_searchfilter
->GetSearchTerm();
485 title
= g_localizeStrings
.Get(137); // "Search"
487 StringUtils::Trim(title
, "\"");
489 m_searchfilter
->SetTitle(title
);
491 CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*m_searchfilter
);
494 m_searchfilter
= searchFilter
;
497 std::string
CGUIWindowPVRTVSearch::GetRootPath() const
499 return CPVREpgSearchPath::PATH_TV_SEARCH
;
502 std::string
CGUIWindowPVRTVSearch::GetStartFolder(const std::string
& dir
)
504 return CPVREpgSearchPath::PATH_TV_SEARCH
;
507 std::string
CGUIWindowPVRTVSearch::GetDirectoryPath()
509 return URIUtils::PathHasParent(m_vecItems
->GetPath(), CPVREpgSearchPath::PATH_TV_SEARCH
)
510 ? m_vecItems
->GetPath()
511 : CPVREpgSearchPath::PATH_TV_SEARCH
;
514 std::string
CGUIWindowPVRRadioSearch::GetRootPath() const
516 return CPVREpgSearchPath::PATH_RADIO_SEARCH
;
519 std::string
CGUIWindowPVRRadioSearch::GetStartFolder(const std::string
& dir
)
521 return CPVREpgSearchPath::PATH_RADIO_SEARCH
;
524 std::string
CGUIWindowPVRRadioSearch::GetDirectoryPath()
526 return URIUtils::PathHasParent(m_vecItems
->GetPath(), CPVREpgSearchPath::PATH_RADIO_SEARCH
)
527 ? m_vecItems
->GetPath()
528 : CPVREpgSearchPath::PATH_RADIO_SEARCH
;