1 /*
2  *  Copyright (C) 2012-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIDialogMediaFilter.h"
10 
11 #include "DbUrl.h"
12 #include "FileItem.h"
13 #include "GUIUserMessages.h"
14 #include "ServiceBroker.h"
15 #include "XBDateTime.h"
16 #include "guilib/GUIComponent.h"
17 #include "guilib/GUIWindowManager.h"
18 #include "guilib/LocalizeStrings.h"
19 #include "music/MusicDatabase.h"
20 #include "music/MusicDbUrl.h"
21 #include "playlists/SmartPlayList.h"
22 #include "settings/SettingUtils.h"
23 #include "settings/lib/Setting.h"
24 #include "settings/lib/SettingDefinitions.h"
25 #include "settings/windows/GUIControlSettings.h"
26 #include "utils/SortUtils.h"
27 #include "utils/StringUtils.h"
28 #include "utils/Variant.h"
29 #include "utils/log.h"
30 #include "video/VideoDatabase.h"
31 #include "video/VideoDbUrl.h"
32 
33 #define CONTROL_HEADING             2
34 
35 #define CONTROL_OKAY_BUTTON        28
36 #define CONTROL_CANCEL_BUTTON      29
37 #define CONTROL_CLEAR_BUTTON       30
38 
39 #define CHECK_ALL                  -1
40 #define CHECK_NO                    0
41 #define CHECK_YES                   1
42 #define CHECK_LABEL_ALL           593
43 #define CHECK_LABEL_NO            106
44 #define CHECK_LABEL_YES           107
45 
46 static const CGUIDialogMediaFilter::Filter filterList[] = {
47   { "movies",       FieldTitle,         556,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
48   { "movies",       FieldRating,        563,    SettingType::Number,  "range",  "number",   CDatabaseQueryRule::OPERATOR_BETWEEN },
49   { "movies",       FieldUserRating,    38018,  SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
50   //{ "movies",       FieldTime,          180,    SettingType::Integer, "range",  "time",     CDatabaseQueryRule::OPERATOR_BETWEEN },
51   { "movies",       FieldInProgress,    575,    SettingType::Integer, "toggle", "",         CDatabaseQueryRule::OPERATOR_FALSE },
52   { "movies",       FieldYear,          562,    SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
53   { "movies",       FieldTag,           20459,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
54   { "movies",       FieldGenre,         515,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
55   { "movies",       FieldActor,         20337,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
56   { "movies",       FieldDirector,      20339,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
57   { "movies",       FieldStudio,        572,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
58 
59   { "tvshows",      FieldTitle,         556,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
60   //{ "tvshows",      FieldTvShowStatus,  126,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
61   { "tvshows",      FieldRating,        563,    SettingType::Number,  "range",  "number",   CDatabaseQueryRule::OPERATOR_BETWEEN },
62   { "tvshows",      FieldUserRating,    38018,  SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
63   { "tvshows",      FieldInProgress,    575,    SettingType::Integer, "toggle", "",         CDatabaseQueryRule::OPERATOR_FALSE },
64   { "tvshows",      FieldYear,          562,    SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
65   { "tvshows",      FieldTag,           20459,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
66   { "tvshows",      FieldGenre,         515,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
67   { "tvshows",      FieldActor,         20337,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
68   { "tvshows",      FieldDirector,      20339,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
69   { "tvshows",      FieldStudio,        572,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
70 
71   { "episodes",     FieldTitle,         556,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
72   { "episodes",     FieldRating,        563,    SettingType::Number,  "range",  "number",   CDatabaseQueryRule::OPERATOR_BETWEEN },
73   { "episodes",     FieldUserRating,    38018,  SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
74   { "episodes",     FieldAirDate,       20416,  SettingType::Integer, "range",  "date",     CDatabaseQueryRule::OPERATOR_BETWEEN },
75   { "episodes",     FieldInProgress,    575,    SettingType::Integer, "toggle", "",         CDatabaseQueryRule::OPERATOR_FALSE },
76   { "episodes",     FieldActor,         20337,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
77   { "episodes",     FieldDirector,      20339,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
78 
79   { "musicvideos",  FieldTitle,         556,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
80   { "musicvideos",  FieldRating,        563,    SettingType::Number,  "range",  "number",   CDatabaseQueryRule::OPERATOR_BETWEEN },
81   { "musicvideos",  FieldUserRating,    38018,  SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
82   { "musicvideos",  FieldArtist,        557,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
83   { "musicvideos",  FieldAlbum,         558,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
84   //{ "musicvideos",  FieldTime,          180,    SettingType::Integer, "range",  "time",  CDatabaseQueryRule::OPERATOR_BETWEEN },
85   { "musicvideos",  FieldYear,          562,    SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
86   { "musicvideos",  FieldTag,           20459,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
87   { "musicvideos",  FieldGenre,         515,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
88   { "musicvideos",  FieldDirector,      20339,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
89   { "musicvideos",  FieldStudio,        572,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
90 
91   { "artists",      FieldArtist,        557,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
92   { "artists",      FieldSource,      39030,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
93   { "artists",      FieldGenre,         515,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
94   { "artists",      FieldMoods,         175,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
95   { "artists",      FieldStyles,        176,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
96   { "artists",      FieldInstruments, 21892,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
97   { "artists",      FieldArtistType,    564,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
98   { "artists",      FieldGender,      39025,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
99   { "artists",      FieldDisambiguation, 39026, SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
100   { "artists",      FieldBiography,   21887,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
101   { "artists",      FieldBorn,        21893,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
102   { "artists",      FieldBandFormed,  21894,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
103   { "artists",      FieldDisbanded,   21896,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
104   { "artists",      FieldDied,        21897,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
105 
106   { "albums",       FieldAlbum,         556,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
107 //  { "albums",       FieldArtist,        557,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
108   { "albums",       FieldDiscTitle,     38076,  SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
109   { "albums",       FieldAlbumArtist,   566,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
110   { "albums",       FieldSource,      39030,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
111   { "albums",       FieldRating,        563,    SettingType::Number,  "range",  "number",   CDatabaseQueryRule::OPERATOR_BETWEEN },
112   { "albums",       FieldUserRating,    38018,  SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
113   { "albums",       FieldAlbumType,     564,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
114   { "albums",       FieldYear,          562,    SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
115   { "albums",       FieldGenre,         515,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
116   { "albums",       FieldMusicLabel,    21899,  SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
117   { "albums",       FieldCompilation,   204,    SettingType::Boolean, "toggle", "",         CDatabaseQueryRule::OPERATOR_FALSE },
118   { "albums",       FieldIsBoxset,      38074,  SettingType::Boolean, "toggle", "",         CDatabaseQueryRule::OPERATOR_FALSE },
119   { "albums",       FieldOrigYear,      38078,  SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
120 
121   { "songs",        FieldTitle,         556,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
122   { "songs",        FieldAlbum,         558,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
123   { "songs",        FieldDiscTitle,   38076,    SettingType::String,  "edit",   "string",   CDatabaseQueryRule::OPERATOR_CONTAINS },
124   { "songs",        FieldArtist,        557,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
125   { "songs",        FieldTime,          180,    SettingType::Integer, "range",  "time",     CDatabaseQueryRule::OPERATOR_BETWEEN },
126   { "songs",        FieldRating,        563,    SettingType::Number,  "range",  "number",   CDatabaseQueryRule::OPERATOR_BETWEEN },
127   { "songs",        FieldUserRating,    38018,  SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
128   { "songs",        FieldYear,          562,    SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
129   { "songs",        FieldGenre,         515,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS },
130   { "songs",        FieldPlaycount,     567,    SettingType::Integer, "range",  "integer",  CDatabaseQueryRule::OPERATOR_BETWEEN },
131   { "songs",        FieldSource,      39030,    SettingType::List,    "list",   "string",   CDatabaseQueryRule::OPERATOR_EQUALS }
132 };
133 
CGUIDialogMediaFilter()134 CGUIDialogMediaFilter::CGUIDialogMediaFilter()
135   : CGUIDialogSettingsManualBase(WINDOW_DIALOG_MEDIA_FILTER, "DialogSettings.xml"),
136     m_dbUrl(NULL),
137     m_filter(NULL)
138 { }
139 
~CGUIDialogMediaFilter()140 CGUIDialogMediaFilter::~CGUIDialogMediaFilter()
141 {
142   Reset();
143 }
144 
OnMessage(CGUIMessage & message)145 bool CGUIDialogMediaFilter::OnMessage(CGUIMessage& message)
146 {
147   switch (message.GetMessage())
148   {
149     case GUI_MSG_CLICKED:
150     {
151       if (message.GetSenderId()== CONTROL_CLEAR_BUTTON)
152       {
153         m_filter->Reset();
154         m_filter->SetType(m_mediaType);
155 
156         for (auto& filter : m_filters)
157         {
158           filter.second.rule = nullptr;
159           filter.second.setting->Reset();
160         }
161 
162         TriggerFilter();
163         return true;
164       }
165       break;
166     }
167 
168     case GUI_MSG_REFRESH_LIST:
169     {
170       TriggerFilter();
171       UpdateControls();
172       break;
173     }
174 
175     case GUI_MSG_WINDOW_DEINIT:
176     {
177       Reset();
178       break;
179     }
180 
181     default:
182       break;
183   }
184 
185   return CGUIDialogSettingsManualBase::OnMessage(message);
186 }
187 
ShowAndEditMediaFilter(const std::string & path,CSmartPlaylist & filter)188 void CGUIDialogMediaFilter::ShowAndEditMediaFilter(const std::string &path, CSmartPlaylist &filter)
189 {
190   CGUIDialogMediaFilter *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogMediaFilter>(WINDOW_DIALOG_MEDIA_FILTER);
191   if (dialog == NULL)
192     return;
193 
194   // initialize and show the dialog
195   dialog->Initialize();
196   dialog->m_filter = &filter;
197 
198   // must be called after setting the filter/smartplaylist
199   if (!dialog->SetPath(path))
200     return;
201 
202   dialog->Open();
203 }
204 
OnWindowLoaded()205 void CGUIDialogMediaFilter::OnWindowLoaded()
206 {
207   CGUIDialogSettingsManualBase::OnWindowLoaded();
208 
209   // we don't need the cancel button so let's hide it
210   SET_CONTROL_HIDDEN(CONTROL_CANCEL_BUTTON);
211 }
212 
OnInitWindow()213 void CGUIDialogMediaFilter::OnInitWindow()
214 {
215   CGUIDialogSettingsManualBase::OnInitWindow();
216 
217   UpdateControls();
218 }
219 
OnSettingChanged(const std::shared_ptr<const CSetting> & setting)220 void CGUIDialogMediaFilter::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
221 {
222   CGUIDialogSettingsManualBase::OnSettingChanged(setting);
223 
224   std::map<std::string, Filter>::iterator it = m_filters.find(setting->GetId());
225   if (it == m_filters.end())
226     return;
227 
228   bool remove = false;
229   Filter& filter = it->second;
230 
231   if (filter.controlType == "edit")
232   {
233     std::string value = setting->ToString();
234     if (!value.empty())
235     {
236       if (filter.rule == NULL)
237         filter.rule = AddRule(filter.field, filter.ruleOperator);
238       filter.rule->m_parameter.clear();
239       filter.rule->m_parameter.push_back(value);
240     }
241     else
242       remove = true;
243   }
244   else if (filter.controlType == "toggle")
245   {
246     int choice = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
247     if (choice > CHECK_ALL)
248     {
249       CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator = choice == CHECK_YES ? CDatabaseQueryRule::OPERATOR_TRUE : CDatabaseQueryRule::OPERATOR_FALSE;
250       if (filter.rule == NULL)
251         filter.rule = AddRule(filter.field, ruleOperator);
252       else
253         filter.rule->m_operator = ruleOperator;
254     }
255     else
256       remove = true;
257   }
258   else if (filter.controlType == "list")
259   {
260     std::vector<CVariant> values = CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting));
261     if (!values.empty())
262     {
263       if (filter.rule == NULL)
264         filter.rule = AddRule(filter.field, filter.ruleOperator);
265 
266       filter.rule->m_parameter.clear();
267       for (const auto& itValue : values)
268         filter.rule->m_parameter.push_back(itValue.asString());
269     }
270     else
271       remove = true;
272   }
273   else if (filter.controlType == "range")
274   {
275     const std::shared_ptr<const CSettingList> settingList = std::static_pointer_cast<const CSettingList>(setting);
276     std::vector<CVariant> values = CSettingUtils::GetList(settingList);
277     if (values.size() != 2)
278       return;
279 
280     std::string strValueLower, strValueUpper;
281 
282     SettingConstPtr definition = settingList->GetDefinition();
283     if (definition->GetType() == SettingType::Integer)
284     {
285       const std::shared_ptr<const CSettingInt> definitionInt = std::static_pointer_cast<const CSettingInt>(definition);
286       int valueLower = static_cast<int>(values.at(0).asInteger());
287       int valueUpper = static_cast<int>(values.at(1).asInteger());
288 
289       if (valueLower > definitionInt->GetMinimum() ||
290           valueUpper < definitionInt->GetMaximum())
291       {
292         if (filter.controlFormat == "date")
293         {
294           strValueLower = CDateTime(static_cast<time_t>(valueLower)).GetAsDBDate();
295           strValueUpper = CDateTime(static_cast<time_t>(valueUpper)).GetAsDBDate();
296         }
297         else
298         {
299           strValueLower = values.at(0).asString();
300           strValueUpper = values.at(1).asString();
301         }
302       }
303     }
304     else if (definition->GetType() == SettingType::Number)
305     {
306       const std::shared_ptr<const CSettingNumber> definitionNumber = std::static_pointer_cast<const CSettingNumber>(definition);
307       float valueLower = values.at(0).asFloat();
308       float valueUpper = values.at(1).asFloat();
309 
310       if (valueLower > definitionNumber->GetMinimum() ||
311           valueUpper < definitionNumber->GetMaximum())
312       {
313         strValueLower = values.at(0).asString();
314         strValueUpper = values.at(1).asString();
315       }
316     }
317     else
318       return;
319 
320     if (!strValueLower.empty() && !strValueUpper.empty())
321     {
322       // prepare the filter rule
323       if (filter.rule == NULL)
324         filter.rule = AddRule(filter.field, filter.ruleOperator);
325       filter.rule->m_parameter.clear();
326 
327       filter.rule->m_parameter.push_back(strValueLower);
328       filter.rule->m_parameter.push_back(strValueUpper);
329     }
330     else
331       remove = true;
332   }
333   else
334     return;
335 
336   // we need to remove the existing rule for the title
337   if (remove && filter.rule != NULL)
338   {
339     DeleteRule(filter.field);
340     filter.rule = NULL;
341   }
342 
343   CGUIMessage msg(GUI_MSG_REFRESH_LIST, GetID(), 0);
344   CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, WINDOW_DIALOG_MEDIA_FILTER);
345 }
346 
SetupView()347 void CGUIDialogMediaFilter::SetupView()
348 {
349   CGUIDialogSettingsManualBase::SetupView();
350 
351   // set the heading label based on the media type
352   uint32_t localizedMediaId = 0;
353   if (m_mediaType == "movies")
354     localizedMediaId = 20342;
355   else if (m_mediaType == "tvshows")
356     localizedMediaId = 20343;
357   else if (m_mediaType == "episodes")
358     localizedMediaId = 20360;
359   else if (m_mediaType == "musicvideos")
360     localizedMediaId = 20389;
361   else if (m_mediaType == "artists")
362     localizedMediaId = 133;
363   else if (m_mediaType == "albums")
364     localizedMediaId = 132;
365   else if (m_mediaType == "songs")
366     localizedMediaId = 134;
367 
368   // set the heading
369   SET_CONTROL_LABEL(CONTROL_HEADING, StringUtils::Format(g_localizeStrings.Get(1275).c_str(), g_localizeStrings.Get(localizedMediaId).c_str()));
370 
371   SET_CONTROL_LABEL(CONTROL_OKAY_BUTTON, 186);
372   SET_CONTROL_LABEL(CONTROL_CLEAR_BUTTON, 192);
373 }
374 
InitializeSettings()375 void CGUIDialogMediaFilter::InitializeSettings()
376 {
377   CGUIDialogSettingsManualBase::InitializeSettings();
378 
379   if (m_filter == NULL)
380     return;
381 
382   Reset(true);
383 
384   int handledRules = 0;
385 
386   const std::shared_ptr<CSettingCategory> category = AddCategory("filter", -1);
387   if (category == NULL)
388   {
389     CLog::Log(LOGERROR, "CGUIDialogMediaFilter: unable to setup filters");
390     return;
391   }
392 
393   const std::shared_ptr<CSettingGroup> group = AddGroup(category);
394   if (group == NULL)
395   {
396     CLog::Log(LOGERROR, "CGUIDialogMediaFilter: unable to setup filters");
397     return;
398   }
399 
400   for (const Filter& f : filterList)
401   {
402     if (f.mediaType != m_mediaType)
403       continue;
404 
405     Filter filter = f;
406 
407     // check the smartplaylist if it contains a matching rule
408     for (auto& rule : m_filter->m_ruleCombination.m_rules)
409     {
410       if (rule->m_field == filter.field)
411       {
412         filter.rule = static_cast<CSmartPlaylistRule*>(rule.get());
413         handledRules++;
414         break;
415       }
416     }
417 
418     std::string settingId = StringUtils::Format("filter.%s.%d", filter.mediaType.c_str(), filter.field);
419     if (filter.controlType == "edit")
420     {
421       CVariant data;
422       if (filter.rule != NULL && filter.rule->m_parameter.size() == 1)
423         data = filter.rule->m_parameter.at(0);
424 
425       if (filter.settingType == SettingType::String)
426         filter.setting = AddEdit(group, settingId, filter.label, SettingLevel::Basic, data.asString(), true, false, filter.label, true);
427       else if (filter.settingType == SettingType::Integer)
428         filter.setting = AddEdit(group, settingId, filter.label, SettingLevel::Basic, static_cast<int>(data.asInteger()), 0, 1, 0, false,  static_cast<int>(filter.label), true);
429       else if (filter.settingType == SettingType::Number)
430         filter.setting = AddEdit(group, settingId, filter.label, SettingLevel::Basic, data.asFloat(), 0.0f, 1.0f, 0.0f, false, filter.label, true);
431     }
432     else if (filter.controlType == "toggle")
433     {
434       int value = CHECK_ALL;
435       if (filter.rule != NULL)
436         value = filter.rule->m_operator == CDatabaseQueryRule::OPERATOR_TRUE ? CHECK_YES : CHECK_NO;
437 
438       TranslatableIntegerSettingOptions entries;
439       entries.push_back(TranslatableIntegerSettingOption(CHECK_LABEL_ALL, CHECK_ALL));
440       entries.push_back(TranslatableIntegerSettingOption(CHECK_LABEL_NO, CHECK_NO));
441       entries.push_back(TranslatableIntegerSettingOption(CHECK_LABEL_YES, CHECK_YES));
442 
443       filter.setting = AddSpinner(group, settingId, filter.label, SettingLevel::Basic, value, entries, true);
444     }
445     else if (filter.controlType == "list")
446     {
447       std::vector<std::string> values;
448       if (filter.rule != NULL && !filter.rule->m_parameter.empty())
449       {
450         values = StringUtils::Split(filter.rule->GetParameter(), DATABASEQUERY_RULE_VALUE_SEPARATOR);
451         if (values.size() == 1 && values.at(0).empty())
452           values.erase(values.begin());
453       }
454 
455       filter.setting = AddList(group, settingId, filter.label, SettingLevel::Basic, values, GetStringListOptions, filter.label);
456     }
457     else if (filter.controlType == "range")
458     {
459       CVariant valueLower, valueUpper;
460       if (filter.rule != NULL)
461       {
462         if (filter.rule->m_parameter.size() == 2)
463         {
464           valueLower = filter.rule->m_parameter.at(0);
465           valueUpper = filter.rule->m_parameter.at(1);
466         }
467         else
468         {
469           DeleteRule(filter.field);
470           filter.rule = NULL;
471         }
472       }
473 
474       if (filter.settingType == SettingType::Integer)
475       {
476         int min, interval, max;
477         GetRange(filter, min, interval, max);
478 
479         // don't create the filter if there's no real range
480         if (min == max)
481           continue;
482 
483         int iValueLower = valueLower.isNull() ? min : static_cast<int>(valueLower.asInteger());
484         int iValueUpper = valueUpper.isNull() ? max : static_cast<int>(valueUpper.asInteger());
485 
486         if (filter.controlFormat == "integer")
487           filter.setting = AddRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, min, interval, max, -1, 21469, true);
488         else if (filter.controlFormat == "percentage")
489           filter.setting = AddPercentageRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, -1, 1, 21469, true);
490         else if (filter.controlFormat == "date")
491           filter.setting = AddDateRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, min, interval, max, -1, 21469, true);
492         else if (filter.controlFormat == "time")
493           filter.setting = AddTimeRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, min, interval, max, -1, 21469, true);
494       }
495       else if (filter.settingType == SettingType::Number)
496       {
497         float min, interval, max;
498         GetRange(filter, min, interval, max);
499 
500         // don't create the filter if there's no real range
501         if (min == max)
502           continue;
503 
504         float fValueLower = valueLower.isNull() ? min : valueLower.asFloat();
505         float fValueUpper = valueUpper.isNull() ? max : valueUpper.asFloat();
506 
507         filter.setting = AddRange(group, settingId, filter.label, SettingLevel::Basic, fValueLower, fValueUpper, min, interval, max, -1, 21469, true);
508       }
509     }
510     else
511     {
512       if (filter.rule != NULL)
513         handledRules--;
514 
515       CLog::Log(LOGWARNING, "CGUIDialogMediaFilter: filter %d of media type %s with unknown control type '%s'",
516                 filter.field, filter.mediaType.c_str(), filter.controlType.c_str());
517       continue;
518     }
519 
520     if (filter.setting == NULL)
521     {
522       if (filter.rule != NULL)
523         handledRules--;
524 
525       CLog::Log(LOGWARNING, "CGUIDialogMediaFilter: failed to create filter %d of media type %s with control type '%s'",
526                 filter.field, filter.mediaType.c_str(), filter.controlType.c_str());
527       continue;
528     }
529 
530     m_filters.insert(make_pair(settingId, filter));
531   }
532 
533   // make sure that no change in capacity size is needed when adding new rules
534   // which would copy around the rules and our pointers in the Filter struct
535   // wouldn't work anymore
536   m_filter->m_ruleCombination.m_rules.reserve(m_filters.size() + (m_filter->m_ruleCombination.m_rules.size() - handledRules));
537 }
538 
SetPath(const std::string & path)539 bool CGUIDialogMediaFilter::SetPath(const std::string &path)
540 {
541   if (path.empty() || m_filter == NULL)
542   {
543     CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath(%s): invalid path or filter", path.c_str());
544     return false;
545   }
546 
547   delete m_dbUrl;
548   bool video = false;
549   if (path.find("videodb://") == 0)
550   {
551     m_dbUrl = new CVideoDbUrl();
552     video = true;
553   }
554   else if (path.find("musicdb://") == 0)
555     m_dbUrl = new CMusicDbUrl();
556   else
557   {
558     CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath(%s): invalid path (neither videodb:// nor musicdb://)", path.c_str());
559     return false;
560   }
561 
562   if (!m_dbUrl->FromString(path) ||
563      (video && m_dbUrl->GetType() != "movies" && m_dbUrl->GetType() != "tvshows" && m_dbUrl->GetType() != "episodes" && m_dbUrl->GetType() != "musicvideos") ||
564      (!video && m_dbUrl->GetType() != "artists" && m_dbUrl->GetType() != "albums" && m_dbUrl->GetType() != "songs"))
565   {
566     CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath(%s): invalid media type", path.c_str());
567     return false;
568   }
569 
570   // remove "filter" option
571   if (m_dbUrl->HasOption("filter"))
572     m_dbUrl->RemoveOption("filter");
573 
574   if (video)
575     m_mediaType = ((CVideoDbUrl*)m_dbUrl)->GetItemType();
576   else
577     m_mediaType = m_dbUrl->GetType();
578 
579   m_filter->SetType(m_mediaType);
580   return true;
581 }
582 
UpdateControls()583 void CGUIDialogMediaFilter::UpdateControls()
584 {
585   for (const auto& itFilter : m_filters)
586   {
587     if (itFilter.second.controlType != "list")
588       continue;
589 
590     std::vector<std::string> items;
591     int size = GetItems(itFilter.second, items, true);
592 
593     std::string label = g_localizeStrings.Get(itFilter.second.label);
594     BaseSettingControlPtr control = GetSettingControl(itFilter.second.setting->GetId());
595     if (control == NULL)
596       continue;
597 
598     if (size <= 0 ||
599         (size == 1 && itFilter.second.field != FieldSet && itFilter.second.field != FieldTag))
600       CONTROL_DISABLE(control->GetID());
601     else
602     {
603       CONTROL_ENABLE(control->GetID());
604       label = StringUtils::Format(g_localizeStrings.Get(21470).c_str(), label.c_str(), size);
605     }
606     SET_CONTROL_LABEL(control->GetID(), label);
607   }
608 }
609 
TriggerFilter() const610 void CGUIDialogMediaFilter::TriggerFilter() const
611 {
612   if (m_filter == NULL)
613     return;
614 
615   CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 10); // 10 for advanced
616   CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message);
617 }
618 
Reset(bool filtersOnly)619 void CGUIDialogMediaFilter::Reset(bool filtersOnly /* = false */)
620 {
621   if (!filtersOnly)
622   {
623     delete m_dbUrl;
624     m_dbUrl = NULL;
625   }
626 
627   m_filters.clear();
628 }
629 
GetItems(const Filter & filter,std::vector<std::string> & items,bool countOnly)630 int CGUIDialogMediaFilter::GetItems(const Filter &filter, std::vector<std::string> &items, bool countOnly /* = false */)
631 {
632   CFileItemList selectItems;
633 
634   // remove the rule for the field of the filter we want to retrieve items for
635   CSmartPlaylist tmpFilter = *m_filter;
636   for (CDatabaseQueryRules::iterator rule = tmpFilter.m_ruleCombination.m_rules.begin(); rule != tmpFilter.m_ruleCombination.m_rules.end(); rule++)
637   {
638     if ((*rule)->m_field == filter.field)
639     {
640       tmpFilter.m_ruleCombination.m_rules.erase(rule);
641       break;
642     }
643   }
644 
645   if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos")
646   {
647     CVideoDatabase videodb;
648     if (!videodb.Open())
649       return -1;
650 
651     std::set<std::string> playlists;
652     CDatabase::Filter dbfilter;
653     dbfilter.where = tmpFilter.GetWhereClause(videodb, playlists);
654 
655     VIDEODB_CONTENT_TYPE type = VIDEODB_CONTENT_MOVIES;
656     if (m_mediaType == "tvshows")
657       type = VIDEODB_CONTENT_TVSHOWS;
658     else if (m_mediaType == "episodes")
659       type = VIDEODB_CONTENT_EPISODES;
660     else if (m_mediaType == "musicvideos")
661       type = VIDEODB_CONTENT_MUSICVIDEOS;
662 
663     if (filter.field == FieldGenre)
664       videodb.GetGenresNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
665     else if (filter.field == FieldActor || filter.field == FieldArtist)
666       videodb.GetActorsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
667     else if (filter.field == FieldDirector)
668       videodb.GetDirectorsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
669     else if (filter.field == FieldStudio)
670       videodb.GetStudiosNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
671     else if (filter.field == FieldAlbum)
672       videodb.GetMusicVideoAlbumsNav(m_dbUrl->ToString(), selectItems, -1, dbfilter, countOnly);
673     else if (filter.field == FieldTag)
674       videodb.GetTagsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
675   }
676   else if (m_mediaType == "artists" || m_mediaType == "albums" || m_mediaType == "songs")
677   {
678     CMusicDatabase musicdb;
679     if (!musicdb.Open())
680       return -1;
681 
682     std::set<std::string> playlists;
683     CDatabase::Filter dbfilter;
684     dbfilter.where = tmpFilter.GetWhereClause(musicdb, playlists);
685 
686     if (filter.field == FieldGenre)
687       musicdb.GetGenresNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
688     else if (filter.field == FieldArtist || filter.field == FieldAlbumArtist)
689       musicdb.GetArtistsNav(m_dbUrl->ToString(), selectItems, m_mediaType == "albums", -1, -1, -1, dbfilter, SortDescription(), countOnly);
690     else if (filter.field == FieldAlbum)
691       musicdb.GetAlbumsNav(m_dbUrl->ToString(), selectItems, -1, -1, dbfilter, SortDescription(), countOnly);
692     else if (filter.field == FieldAlbumType)
693       musicdb.GetAlbumTypesNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
694     else if (filter.field == FieldMusicLabel)
695       musicdb.GetMusicLabelsNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
696     if (filter.field == FieldSource)
697       musicdb.GetSourcesNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
698   }
699 
700   int size = selectItems.Size();
701   if (size <= 0)
702     return 0;
703 
704   if (countOnly)
705   {
706     if (size == 1 && selectItems.Get(0)->HasProperty("total"))
707       return (int)selectItems.Get(0)->GetProperty("total").asInteger();
708     return 0;
709   }
710 
711   // sort the items
712   selectItems.Sort(SortByLabel, SortOrderAscending);
713 
714   for (int index = 0; index < size; ++index)
715     items.push_back(selectItems.Get(index)->GetLabel());
716 
717   return items.size();
718 }
719 
AddRule(Field field,CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator)720 CSmartPlaylistRule* CGUIDialogMediaFilter::AddRule(Field field, CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator /* = CDatabaseQueryRule::OPERATOR_CONTAINS */)
721 {
722   CSmartPlaylistRule rule;
723   rule.m_field = field;
724   rule.m_operator = ruleOperator;
725 
726   m_filter->m_ruleCombination.AddRule(rule);
727   return (CSmartPlaylistRule *)m_filter->m_ruleCombination.m_rules.back().get();
728 }
729 
DeleteRule(Field field)730 void CGUIDialogMediaFilter::DeleteRule(Field field)
731 {
732   for (CDatabaseQueryRules::iterator rule = m_filter->m_ruleCombination.m_rules.begin(); rule != m_filter->m_ruleCombination.m_rules.end(); rule++)
733   {
734     if ((*rule)->m_field == field)
735     {
736       m_filter->m_ruleCombination.m_rules.erase(rule);
737       break;
738     }
739   }
740 }
741 
GetStringListOptions(const SettingConstPtr & setting,std::vector<StringSettingOption> & list,std::string & current,void * data)742 void CGUIDialogMediaFilter::GetStringListOptions(const SettingConstPtr& setting,
743                                                  std::vector<StringSettingOption>& list,
744                                                  std::string& current,
745                                                  void* data)
746 {
747   if (setting == NULL || data == NULL)
748     return;
749 
750   CGUIDialogMediaFilter *mediaFilter = static_cast<CGUIDialogMediaFilter*>(data);
751 
752   std::map<std::string, Filter>::const_iterator itFilter = mediaFilter->m_filters.find(setting->GetId());
753   if (itFilter == mediaFilter->m_filters.end())
754     return;
755 
756   std::vector<std::string> items;
757   if (mediaFilter->GetItems(itFilter->second, items, false) <= 0)
758     return;
759 
760   for (const auto& item : items)
761     list.emplace_back(item, item);
762 }
763 
GetRange(const Filter & filter,int & min,int & interval,int & max)764 void CGUIDialogMediaFilter::GetRange(const Filter &filter, int &min, int &interval, int &max)
765 {
766   if (filter.field == FieldUserRating &&
767      (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes"|| m_mediaType == "musicvideos" || m_mediaType == "albums" || m_mediaType == "songs"))
768   {
769     min = 0;
770     interval = 1;
771     max = 10;
772   }
773   else if (filter.field == FieldYear)
774   {
775     min = 0;
776     interval = 1;
777     max = 0;
778 
779     if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "musicvideos")
780     {
781       std::string table;
782       std::string year;
783       if (m_mediaType == "movies")
784       {
785         table = "movie_view";
786         year = DatabaseUtils::GetField(FieldYear, MediaTypeMovie, DatabaseQueryPartWhere);
787       }
788       else if (m_mediaType == "tvshows")
789       {
790         table = "tvshow_view";
791         year = StringUtils::Format("strftime(\"%%Y\", %s)", DatabaseUtils::GetField(FieldYear, MediaTypeTvShow, DatabaseQueryPartWhere).c_str());
792       }
793       else if (m_mediaType == "musicvideos")
794       {
795         table = "musicvideo_view";
796         year = DatabaseUtils::GetField(FieldYear, MediaTypeMusicVideo, DatabaseQueryPartWhere);
797       }
798 
799       CDatabase::Filter filter;
800       filter.where = year + " > 0";
801       GetMinMax(table, year, min, max, filter);
802     }
803     else if (m_mediaType == "albums" || m_mediaType == "songs")
804     {
805       std::string table;
806       if (m_mediaType == "albums")
807         table = "albumview";
808       else if (m_mediaType == "songs")
809         table = "songview";
810       else
811         return;
812 
813       CDatabase::Filter filter;
814       filter.where = DatabaseUtils::GetField(FieldYear, CMediaTypes::FromString(m_mediaType), DatabaseQueryPartWhere) + " > 0";
815       GetMinMax(table, DatabaseUtils::GetField(FieldYear, CMediaTypes::FromString(m_mediaType), DatabaseQueryPartSelect), min, max, filter);
816     }
817   }
818   else if (filter.field == FieldAirDate)
819   {
820     min = 0;
821     interval = 1;
822     max = 0;
823 
824     if (m_mediaType == "episodes")
825     {
826       std::string field = StringUtils::Format("CAST(strftime(\"%%s\", c%02d) AS INTEGER)", VIDEODB_ID_EPISODE_AIRED);
827 
828       GetMinMax("episode_view", field, min, max);
829       interval = 60 * 60 * 24 * 7; // 1 week
830     }
831   }
832   else if (filter.field == FieldTime)
833   {
834     min = 0;
835     interval = 10;
836     max = 0;
837 
838     if (m_mediaType == "songs")
839       GetMinMax("songview", "iDuration", min, max);
840   }
841   else if (filter.field == FieldPlaycount)
842   {
843     min = 0;
844     interval = 1;
845     max = 0;
846 
847     if (m_mediaType == "songs")
848       GetMinMax("songview", "iTimesPlayed", min, max);
849   }
850 }
851 
GetRange(const Filter & filter,float & min,float & interval,float & max)852 void CGUIDialogMediaFilter::GetRange(const Filter &filter, float &min, float &interval, float &max)
853 {
854   if (filter.field == FieldRating &&
855      (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos" || m_mediaType == "albums" || m_mediaType == "songs"))
856   {
857     min = 0.0f;
858     interval = 0.1f;
859     max = 10.0f;
860   }
861 }
862 
GetMinMax(const std::string & table,const std::string & field,int & min,int & max,const CDatabase::Filter & filter)863 bool CGUIDialogMediaFilter::GetMinMax(const std::string &table, const std::string &field, int &min, int &max, const CDatabase::Filter &filter /* = CDatabase::Filter() */)
864 {
865   if (table.empty() || field.empty())
866     return false;
867 
868   CDatabase *db = NULL;
869   CDbUrl *dbUrl = NULL;
870   if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos")
871   {
872     CVideoDatabase *videodb = new CVideoDatabase();
873     if (!videodb->Open())
874     {
875       delete videodb;
876       return false;
877     }
878 
879     db = videodb;
880     dbUrl = new CVideoDbUrl();
881   }
882   else if (m_mediaType == "artists" || m_mediaType == "albums" || m_mediaType == "songs")
883   {
884     CMusicDatabase *musicdb = new CMusicDatabase();
885     if (!musicdb->Open())
886     {
887       delete musicdb;
888       return false;
889     }
890 
891     db = musicdb;
892     dbUrl = new CMusicDbUrl();
893   }
894 
895   if (db == NULL || !db->IsOpen() || dbUrl == NULL)
896   {
897     delete db;
898     delete dbUrl;
899     return false;
900   }
901 
902   CDatabase::Filter extFilter = filter;
903   std::string strSQLExtra;
904   if (!db->BuildSQL(m_dbUrl->ToString(), strSQLExtra, extFilter, strSQLExtra, *dbUrl))
905   {
906     delete db;
907     delete dbUrl;
908     return false;
909   }
910 
911   std::string strSQL = "SELECT %s FROM %s ";
912 
913   min = static_cast<int>(strtol(db->GetSingleValue(db->PrepareSQL(strSQL, ("MIN(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL, 0));
914   max = static_cast<int>(strtol(db->GetSingleValue(db->PrepareSQL(strSQL, ("MAX(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL, 0));
915 
916   db->Close();
917   delete db;
918   delete dbUrl;
919 
920   return true;
921 }
922