2 * Copyright (C) 2005-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 "GUIDialogSmartPlaylistRule.h"
12 #include "FileItemList.h"
13 #include "GUIDialogFileBrowser.h"
14 #include "GUIDialogSelect.h"
15 #include "ServiceBroker.h"
16 #include "filesystem/Directory.h"
17 #include "guilib/GUIComponent.h"
18 #include "guilib/GUIEditControl.h"
19 #include "guilib/GUIWindowManager.h"
20 #include "guilib/LocalizeStrings.h"
21 #include "music/MusicDatabase.h"
22 #include "settings/MediaSourceSettings.h"
23 #include "settings/Settings.h"
24 #include "settings/SettingsComponent.h"
25 #include "storage/MediaManager.h"
26 #include "utils/LabelFormatter.h"
27 #include "utils/StringUtils.h"
28 #include "utils/Variant.h"
29 #include "video/VideoDatabase.h"
35 #define CONTROL_FIELD 15
36 #define CONTROL_OPERATOR 16
37 #define CONTROL_VALUE 17
39 #define CONTROL_CANCEL 19
40 #define CONTROL_BROWSE 20
42 CGUIDialogSmartPlaylistRule::CGUIDialogSmartPlaylistRule(void)
43 : CGUIDialog(WINDOW_DIALOG_SMART_PLAYLIST_RULE
, "SmartPlaylistRule.xml")
46 m_loadType
= KEEP_IN_MEMORY
;
49 CGUIDialogSmartPlaylistRule::~CGUIDialogSmartPlaylistRule() = default;
51 bool CGUIDialogSmartPlaylistRule::OnBack(int actionID
)
54 return CGUIDialog::OnBack(actionID
);
57 bool CGUIDialogSmartPlaylistRule::OnMessage(CGUIMessage
& message
)
59 switch ( message
.GetMessage() )
63 int iControl
= message
.GetSenderId();
64 if (iControl
== CONTROL_OK
)
66 else if (iControl
== CONTROL_CANCEL
)
68 else if (iControl
== CONTROL_VALUE
)
70 std::string parameter
;
71 OnEditChanged(iControl
, parameter
);
72 m_rule
.SetParameter(parameter
);
74 else if (iControl
== CONTROL_OPERATOR
)
76 else if (iControl
== CONTROL_FIELD
)
78 else if (iControl
== CONTROL_BROWSE
)
84 case GUI_MSG_VALIDITY_CHANGED
:
85 CONTROL_ENABLE_ON_CONDITION(CONTROL_OK
, message
.GetParam1());
88 return CGUIDialog::OnMessage(message
);
91 void CGUIDialogSmartPlaylistRule::OnOK()
97 void CGUIDialogSmartPlaylistRule::OnBrowse()
100 CMusicDatabase database
;
102 CVideoDatabase videodatabase
;
103 videodatabase
.Open();
105 std::string basePath
;
106 if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type
))
107 basePath
= "musicdb://";
109 basePath
= "videodb://";
111 VideoDbContentType type
= VideoDbContentType::MOVIES
;
112 if (m_type
== "movies")
113 basePath
+= "movies/";
114 else if (m_type
== "tvshows")
116 type
= VideoDbContentType::TVSHOWS
;
117 basePath
+= "tvshows/";
119 else if (m_type
== "musicvideos")
121 type
= VideoDbContentType::MUSICVIDEOS
;
122 basePath
+= "musicvideos/";
124 else if (m_type
== "episodes")
126 if (m_rule
.m_field
== FieldGenre
|| m_rule
.m_field
== FieldYear
||
127 m_rule
.m_field
== FieldStudio
)
128 type
= VideoDbContentType::TVSHOWS
;
130 type
= VideoDbContentType::EPISODES
;
131 basePath
+= "tvshows/";
135 if (m_rule
.m_field
== FieldGenre
)
137 if (m_type
== "tvshows" ||
138 m_type
== "episodes" ||
140 videodatabase
.GetGenresNav(basePath
+ "genres/", items
, type
);
141 else if (m_type
== "songs" ||
142 m_type
== "albums" ||
143 m_type
== "artists" ||
145 database
.GetGenresNav("musicdb://genres/",items
);
146 if (m_type
== "musicvideos" ||
149 CFileItemList items2
;
150 videodatabase
.GetGenresNav("videodb://musicvideos/genres/", items2
,
151 VideoDbContentType::MUSICVIDEOS
);
152 items
.Append(items2
);
156 else if (m_rule
.m_field
== FieldSource
)
158 if (m_type
== "songs" ||
159 m_type
== "albums" ||
160 m_type
== "artists" ||
163 database
.GetSourcesNav("musicdb://sources/", items
);
167 else if (m_rule
.m_field
== FieldRole
)
169 if (m_type
== "artists" || m_type
== "mixed")
171 database
.GetRolesNav("musicdb://songs/", items
);
175 else if (m_rule
.m_field
== FieldCountry
)
177 videodatabase
.GetCountriesNav(basePath
, items
, type
);
180 else if (m_rule
.m_field
== FieldArtist
|| m_rule
.m_field
== FieldAlbumArtist
)
182 if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type
))
183 database
.GetArtistsNav("musicdb://artists/", items
, m_rule
.m_field
== FieldAlbumArtist
, -1);
184 if (m_type
== "musicvideos" ||
187 CFileItemList items2
;
188 videodatabase
.GetMusicVideoArtistsByName("", items2
);
189 items
.Append(items2
);
193 else if (m_rule
.m_field
== FieldAlbum
)
195 if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type
))
196 database
.GetAlbumsNav("musicdb://albums/", items
);
197 if (m_type
== "musicvideos" ||
200 CFileItemList items2
;
201 videodatabase
.GetMusicVideoAlbumsByName("", items2
);
202 items
.Append(items2
);
206 else if (m_rule
.m_field
== FieldActor
)
208 videodatabase
.GetActorsNav(basePath
+ "actors/",items
,type
);
211 else if (m_rule
.m_field
== FieldYear
)
213 if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type
))
214 database
.GetYearsNav("musicdb://years/", items
);
215 if (PLAYLIST::CSmartPlaylist::IsVideoType(m_type
))
217 CFileItemList items2
;
218 videodatabase
.GetYearsNav(basePath
+ "years/", items2
, type
);
219 items
.Append(items2
);
223 else if (m_rule
.m_field
== FieldOrigYear
)
225 database
.GetYearsNav("musicdb://originalyears/", items
);
228 else if (m_rule
.m_field
== FieldDirector
)
230 videodatabase
.GetDirectorsNav(basePath
+ "directors/", items
, type
);
233 else if (m_rule
.m_field
== FieldStudio
)
235 videodatabase
.GetStudiosNav(basePath
+ "studios/", items
, type
);
238 else if (m_rule
.m_field
== FieldWriter
)
240 videodatabase
.GetWritersNav(basePath
, items
, type
);
243 else if (m_rule
.m_field
== FieldTvShowTitle
||
244 (m_type
== "tvshows" && m_rule
.m_field
== FieldTitle
))
246 videodatabase
.GetTvShowsNav(basePath
+ "titles/", items
);
249 else if (m_rule
.m_field
== FieldTitle
)
251 if (m_type
== "songs" || m_type
== "mixed")
253 database
.GetSongsNav("musicdb://songs/", items
, -1, -1, -1);
256 if (m_type
== "movies")
258 videodatabase
.GetMoviesNav(basePath
+ "titles/", items
);
261 if (m_type
== "episodes")
263 videodatabase
.GetEpisodesNav(basePath
+ "titles/-1/-1/", items
);
264 // we need to replace the db label (<season>x<episode> <title>) with the title only
265 CLabelFormatter
format("%T", "");
266 for (int i
= 0; i
< items
.Size(); i
++)
267 format
.FormatLabel(items
[i
].get());
270 if (m_type
== "musicvideos" || m_type
== "mixed")
272 videodatabase
.GetMusicVideosNav(basePath
+ "titles/", items
);
276 else if (m_rule
.m_field
== FieldPlaylist
|| m_rule
.m_field
== FieldVirtualFolder
)
278 // use filebrowser to grab another smart playlist
280 // Note: This can cause infinite loops (playlist that refers to the same playlist) but I don't
281 // think there's any decent way to deal with this, as the infinite loop may be an arbitrary
282 // number of playlists deep, eg playlist1 -> playlist2 -> playlist3 ... -> playlistn -> playlist1
283 if (PLAYLIST::CSmartPlaylist::IsVideoType(m_type
))
284 XFILE::CDirectory::GetDirectory("special://videoplaylists/", items
, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS
);
285 if (PLAYLIST::CSmartPlaylist::IsMusicType(m_type
))
287 CFileItemList items2
;
288 XFILE::CDirectory::GetDirectory("special://musicplaylists/", items2
, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS
);
289 items
.Append(items2
);
292 for (int i
= 0; i
< items
.Size(); i
++)
294 CFileItemPtr item
= items
[i
];
295 PLAYLIST::CSmartPlaylist playlist
;
296 // don't list unloadable smartplaylists or any referenceable smartplaylists
297 // which do not match the type of the current smartplaylist
298 if (!playlist
.Load(item
->GetPath()) ||
299 (m_rule
.m_field
== FieldPlaylist
&&
300 (!PLAYLIST::CSmartPlaylist::CheckTypeCompatibility(m_type
, playlist
.GetType()) ||
301 (!playlist
.GetGroup().empty() || playlist
.IsGroupMixed()))))
308 if (!playlist
.GetName().empty())
309 item
->SetLabel(playlist
.GetName());
313 else if (m_rule
.m_field
== FieldPath
)
316 if (m_type
== "songs" || m_type
== "mixed")
317 sources
= *CMediaSourceSettings::GetInstance().GetSources("music");
318 if (PLAYLIST::CSmartPlaylist::IsVideoType(m_type
))
320 VECSOURCES sources2
= *CMediaSourceSettings::GetInstance().GetSources("video");
321 sources
.insert(sources
.end(),sources2
.begin(),sources2
.end());
323 CServiceBroker::GetMediaManager().GetLocalDrives(sources
);
325 std::string path
= m_rule
.GetParameter();
326 CGUIDialogFileBrowser::ShowAndGetDirectory(sources
, g_localizeStrings
.Get(657), path
, false);
327 if (!m_rule
.m_parameter
.empty())
328 m_rule
.m_parameter
.clear();
330 m_rule
.m_parameter
.emplace_back(std::move(path
));
335 else if (m_rule
.m_field
== FieldSet
)
337 videodatabase
.GetSetsNav("videodb://movies/sets/", items
, VideoDbContentType::MOVIES
);
340 else if (m_rule
.m_field
== FieldTag
)
342 VideoDbContentType type
= VideoDbContentType::MOVIES
;
343 if (m_type
== "tvshows" ||
344 m_type
== "episodes")
345 type
= VideoDbContentType::TVSHOWS
;
346 else if (m_type
== "musicvideos")
347 type
= VideoDbContentType::MUSICVIDEOS
;
348 else if (m_type
!= "movies")
351 videodatabase
.GetTagsNav(basePath
+ "tags/", items
, type
);
355 { //! @todo Add browseability in here.
360 items
.Sort(SortByLabel
, SortOrderAscending
, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
) ? SortAttributeIgnoreArticle
: SortAttributeNone
);
362 CGUIDialogSelect
* pDialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(WINDOW_DIALOG_SELECT
);
364 pDialog
->SetItems(items
);
365 std::string strHeading
=
366 StringUtils::Format(g_localizeStrings
.Get(13401), g_localizeStrings
.Get(iLabel
));
367 pDialog
->SetHeading(CVariant
{std::move(strHeading
)});
368 pDialog
->SetMultiSelection(m_rule
.m_field
!= FieldPlaylist
&& m_rule
.m_field
!= FieldVirtualFolder
);
370 if (!m_rule
.m_parameter
.empty())
371 pDialog
->SetSelected(m_rule
.m_parameter
);
374 if (pDialog
->IsConfirmed())
376 m_rule
.m_parameter
.clear();
377 for (int i
: pDialog
->GetSelectedItems())
378 m_rule
.m_parameter
.push_back(items
.Get(i
)->GetLabel());
385 std::pair
<std::string
, int> OperatorLabel(CDatabaseQueryRule::SEARCH_OPERATOR op
)
387 return std::make_pair(PLAYLIST::CSmartPlaylistRule::GetLocalizedOperator(op
), op
);
390 std::vector
<std::pair
<std::string
, int>> CGUIDialogSmartPlaylistRule::GetValidOperators(
391 const PLAYLIST::CSmartPlaylistRule
& rule
)
393 std::vector
< std::pair
<std::string
, int> > labels
;
394 switch (rule
.GetFieldType(rule
.m_field
))
396 case CDatabaseQueryRule::TEXT_FIELD
:
397 // text fields - add the usual comparisons
398 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS
));
399 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL
));
400 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_CONTAINS
));
401 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_CONTAIN
));
402 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_STARTS_WITH
));
403 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_ENDS_WITH
));
406 case CDatabaseQueryRule::REAL_FIELD
:
407 case CDatabaseQueryRule::NUMERIC_FIELD
:
408 case CDatabaseQueryRule::SECONDS_FIELD
:
409 // numerical fields - less than greater than
410 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS
));
411 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL
));
412 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_GREATER_THAN
));
413 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_LESS_THAN
));
416 case CDatabaseQueryRule::DATE_FIELD
:
418 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_AFTER
));
419 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_BEFORE
));
420 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_IN_THE_LAST
));
421 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST
));
424 case CDatabaseQueryRule::PLAYLIST_FIELD
:
425 CONTROL_ENABLE(CONTROL_BROWSE
);
426 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS
));
427 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL
));
430 case CDatabaseQueryRule::BOOLEAN_FIELD
:
431 CONTROL_DISABLE(CONTROL_VALUE
);
432 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_TRUE
));
433 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_FALSE
));
436 case CDatabaseQueryRule::TEXTIN_FIELD
:
437 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS
));
438 labels
.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL
));
444 void CGUIDialogSmartPlaylistRule::OnCancel()
450 void CGUIDialogSmartPlaylistRule::OnField()
452 const auto fields
= PLAYLIST::CSmartPlaylistRule::GetFields(m_type
);
453 CGUIDialogSelect
* dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(WINDOW_DIALOG_SELECT
);
455 dialog
->SetHeading(CVariant
{20427});
457 for (auto field
= fields
.begin(); field
!= fields
.end(); field
++)
459 dialog
->Add(PLAYLIST::CSmartPlaylistRule::GetLocalizedField(*field
));
460 if (*field
== m_rule
.m_field
)
461 selected
= std::distance(fields
.begin(), field
);
464 dialog
->SetSelected(selected
);
466 int newSelected
= dialog
->GetSelectedItem();
467 // check if selection has changed
468 if (!dialog
->IsConfirmed() || newSelected
< 0 || newSelected
== selected
)
471 m_rule
.m_field
= fields
[newSelected
];
472 // check if operator is still valid. if not, reset to first valid one
473 std::vector
< std::pair
<std::string
, int> > validOperators
= GetValidOperators(m_rule
);
474 bool isValid
= false;
475 for (auto op
: validOperators
)
476 if (std::get
<0>(op
) == std::get
<0>(OperatorLabel(m_rule
.m_operator
)))
479 m_rule
.m_operator
= (CDatabaseQueryRule::SEARCH_OPERATOR
)std::get
<1>(validOperators
[0]);
481 m_rule
.SetParameter("");
485 void CGUIDialogSmartPlaylistRule::OnOperator()
487 const auto labels
= GetValidOperators(m_rule
);
488 CGUIDialogSelect
* dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(WINDOW_DIALOG_SELECT
);
490 dialog
->SetHeading(CVariant
{ 16023 });
491 for (auto label
: labels
)
492 dialog
->Add(std::get
<0>(label
));
493 dialog
->SetSelected(PLAYLIST::CSmartPlaylistRule::GetLocalizedOperator(m_rule
.m_operator
));
495 int newSelected
= dialog
->GetSelectedItem();
496 // check if selection has changed
497 if (!dialog
->IsConfirmed() || newSelected
< 0)
500 m_rule
.m_operator
= (CDatabaseQueryRule::SEARCH_OPERATOR
)std::get
<1>(labels
[newSelected
]);
504 void CGUIDialogSmartPlaylistRule::UpdateButtons()
506 if (m_rule
.m_field
== 0)
507 m_rule
.m_field
= PLAYLIST::CSmartPlaylistRule::GetFields(m_type
)[0];
508 SET_CONTROL_LABEL(CONTROL_FIELD
, PLAYLIST::CSmartPlaylistRule::GetLocalizedField(m_rule
.m_field
));
510 CONTROL_ENABLE(CONTROL_VALUE
);
511 if (PLAYLIST::CSmartPlaylistRule::IsFieldBrowseable(m_rule
.m_field
))
512 CONTROL_ENABLE(CONTROL_BROWSE
);
514 CONTROL_DISABLE(CONTROL_BROWSE
);
515 SET_CONTROL_LABEL(CONTROL_OPERATOR
, std::get
<0>(OperatorLabel(m_rule
.m_operator
)));
517 // update label2 appropriately
518 SET_CONTROL_LABEL2(CONTROL_VALUE
, m_rule
.GetParameter());
519 CGUIEditControl::INPUT_TYPE type
= CGUIEditControl::INPUT_TYPE_TEXT
;
520 CDatabaseQueryRule::FIELD_TYPE fieldType
= m_rule
.GetFieldType(m_rule
.m_field
);
523 case CDatabaseQueryRule::TEXT_FIELD
:
524 case CDatabaseQueryRule::PLAYLIST_FIELD
:
525 case CDatabaseQueryRule::TEXTIN_FIELD
:
526 case CDatabaseQueryRule::REAL_FIELD
:
527 case CDatabaseQueryRule::NUMERIC_FIELD
:
528 type
= CGUIEditControl::INPUT_TYPE_TEXT
;
530 case CDatabaseQueryRule::DATE_FIELD
:
531 if (m_rule
.m_operator
== CDatabaseQueryRule::OPERATOR_IN_THE_LAST
||
532 m_rule
.m_operator
== CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST
)
533 type
= CGUIEditControl::INPUT_TYPE_TEXT
;
535 type
= CGUIEditControl::INPUT_TYPE_DATE
;
537 case CDatabaseQueryRule::SECONDS_FIELD
:
538 type
= CGUIEditControl::INPUT_TYPE_SECONDS
;
540 case CDatabaseQueryRule::BOOLEAN_FIELD
:
541 type
= CGUIEditControl::INPUT_TYPE_NUMBER
;
544 SendMessage(GUI_MSG_SET_TYPE
, CONTROL_VALUE
, type
, 21420);
547 void CGUIDialogSmartPlaylistRule::OnInitWindow()
549 CGUIDialog::OnInitWindow();
553 CGUIEditControl
*editControl
= dynamic_cast<CGUIEditControl
*>(GetControl(CONTROL_VALUE
));
554 if (editControl
!= NULL
)
555 editControl
->SetInputValidation(PLAYLIST::CSmartPlaylistRule::Validate
, &m_rule
);
558 void CGUIDialogSmartPlaylistRule::OnDeinitWindow(int nextWindowID
)
560 CGUIDialog::OnDeinitWindow(nextWindowID
);
562 // reset field spincontrolex
563 SendMessage(GUI_MSG_LABEL_RESET
, CONTROL_FIELD
);
564 // reset operator spincontrolex
565 SendMessage(GUI_MSG_LABEL_RESET
, CONTROL_OPERATOR
);
568 bool CGUIDialogSmartPlaylistRule::EditRule(PLAYLIST::CSmartPlaylistRule
& rule
,
569 const std::string
& type
)
571 CGUIDialogSmartPlaylistRule
*editor
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSmartPlaylistRule
>(WINDOW_DIALOG_SMART_PLAYLIST_RULE
);
572 if (!editor
) return false;
574 editor
->m_rule
= rule
;
575 editor
->m_type
= type
;
577 rule
= editor
->m_rule
;
578 return !editor
->m_cancelled
;