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