[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / dialogs / GUIDialogMediaFilter.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIDialogMediaFilter.h"
22 #include "FileItem.h"
23 #include "GUIUserMessages.h"
24 #include "XBDateTime.h"
25 #include "dialogs/GUIDialogSelect.h"
26 #include "guilib/GUIWindowManager.h"
27 #include "guilib/LocalizeStrings.h"
28 #include "music/MusicDatabase.h"
29 #include "playlists/SmartPlayList.h"
30 #include "utils/log.h"
31 #include "utils/MathUtils.h"
32 #include "utils/StringUtils.h"
33 #include "video/VideoDatabase.h"
34
35 #define TIMEOUT_DELAY             500
36
37 // list of controls
38 #define CONTROL_HEADING             2
39 // list of controls from CGUIDialogSettings
40 #define CONTROL_GROUP_LIST          5
41 #define CONTROL_DEFAULT_BUTTON      7
42 #define CONTROL_DEFAULT_RADIOBUTTON 8
43 #define CONTROL_DEFAULT_SPIN        9
44 #define CONTROL_DEFAULT_SLIDER     10
45
46 #define CONTROL_CLEAR_BUTTON       27
47 #define CONTROL_OKAY_BUTTON        28
48 #define CONTROL_CANCEL_BUTTON      29
49 #define CONTROL_START              30
50
51 #define CHECK_ALL                  -1
52 #define CHECK_NO                    0
53 #define CHECK_YES                   1
54 #define CHECK_LABEL_ALL           593
55 #define CHECK_LABEL_NO            106
56 #define CHECK_LABEL_YES           107
57
58 using namespace std;
59
60 static const CGUIDialogMediaFilter::Filter filterList[] = {
61   { "movies",       FieldTitle,         556,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
62   { "movies",       FieldRating,        563,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
63   //{ "movies",       FieldTime,          180,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
64   { "movies",       FieldInProgress,    575,    SettingInfo::CHECK,       CDatabaseQueryRule::OPERATOR_FALSE },
65   { "movies",       FieldYear,          562,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
66   { "movies",       FieldTag,           20459,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
67   { "movies",       FieldGenre,         515,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
68   { "movies",       FieldActor,         20337,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
69   { "movies",       FieldDirector,      20339,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
70   { "movies",       FieldStudio,        572,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
71   //{ "movies",       FieldLastPlayed,    568,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
72   //{ "movies",       FieldDateAdded,     570,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
73
74   { "tvshows",      FieldTitle,         556,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
75   //{ "tvshows",      FieldTvShowStatus,  126,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
76   { "tvshows",      FieldRating,        563,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
77   { "tvshows",      FieldInProgress,    575,    SettingInfo::CHECK,       CDatabaseQueryRule::OPERATOR_FALSE },
78   { "tvshows",      FieldYear,          562,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
79   { "tvshows",      FieldTag,           20459,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
80   { "tvshows",      FieldGenre,         515,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
81   { "tvshows",      FieldActor,         20337,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
82   { "tvshows",      FieldDirector,      20339,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
83   { "tvshows",      FieldStudio,        572,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
84   //{ "tvshows",      FieldDateAdded,     570,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
85
86   { "episodes",     FieldTitle,         556,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
87   { "episodes",     FieldRating,        563,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
88   { "episodes",     FieldAirDate,       20416,  SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
89   { "episodes",     FieldInProgress,    575,    SettingInfo::CHECK,       CDatabaseQueryRule::OPERATOR_FALSE },
90   { "episodes",     FieldActor,         20337,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
91   { "episodes",     FieldDirector,      20339,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
92   //{ "episodes",     FieldLastPlayed,    568,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
93   //{ "episodes",     FieldDateAdded,     570,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
94
95   { "musicvideos",  FieldTitle,         556,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
96   { "musicvideos",  FieldArtist,        557,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
97   { "musicvideos",  FieldAlbum,         558,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
98   //{ "musicvideos",  FieldTime,          180,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
99   { "musicvideos",  FieldYear,          562,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
100   { "musicvideos",  FieldTag,           20459,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
101   { "musicvideos",  FieldGenre,         515,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
102   { "musicvideos",  FieldDirector,      20339,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
103   { "musicvideos",  FieldStudio,        572,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
104   //{ "musicvideos",  FieldLastPlayed,    568,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
105   //{ "musicvideos",  FieldDateAdded,     570,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
106
107   { "artists",      FieldArtist,        557,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
108   { "artists",      FieldGenre,         515,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
109
110   { "albums",       FieldAlbum,         556,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
111   { "albums",       FieldArtist,        557,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
112   { "albums",       FieldRating,        563,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
113   { "albums",       FieldAlbumType,     564,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
114   { "albums",       FieldYear,          562,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
115   { "albums",       FieldGenre,         515,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
116   { "albums",       FieldMusicLabel,    21899,  SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
117
118   { "songs",        FieldTitle,         556,    SettingInfo::EDIT,        CDatabaseQueryRule::OPERATOR_CONTAINS },
119   { "songs",        FieldAlbum,         558,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
120   { "songs",        FieldArtist,        557,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
121   { "songs",        FieldTime,          180,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
122   { "songs",        FieldRating,        563,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
123   { "songs",        FieldYear,          562,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
124   { "songs",        FieldGenre,         515,    SettingInfo::BUTTON,      CDatabaseQueryRule::OPERATOR_EQUALS },
125   { "songs",        FieldPlaycount,     567,    SettingInfo::RANGE,       CDatabaseQueryRule::OPERATOR_BETWEEN },
126   //{ "songs",        FieldLastPlayed,    568,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
127   //{ "songs",        FieldDateAdded,     570,    SettingInfo::TODO,        CDatabaseQueryRule::TODO },
128 };
129
130 #define NUM_FILTERS sizeof(filterList) / sizeof(CGUIDialogMediaFilter::Filter)
131
132 CGUIDialogMediaFilter::CGUIDialogMediaFilter()
133     : CGUIDialogSettings(WINDOW_DIALOG_MEDIA_FILTER, "DialogMediaFilter.xml"),
134       m_dbUrl(NULL),
135       m_filter(NULL)
136 {
137   m_delayTimer = new CTimer(this);
138 }
139
140 CGUIDialogMediaFilter::~CGUIDialogMediaFilter()
141 {
142   Reset();
143   delete m_delayTimer;
144 }
145
146 bool CGUIDialogMediaFilter::OnMessage(CGUIMessage& message)
147 {
148   switch (message.GetMessage())
149   {
150     case GUI_MSG_CLICKED:
151     {
152       int control = message.GetSenderId();
153
154       if (control == CONTROL_CLEAR_BUTTON)
155       {
156         m_filter->Reset();
157         m_filter->SetType(m_mediaType);
158         if (m_delayTimer && m_delayTimer->IsRunning())
159           m_delayTimer->Stop();
160
161         for (map<uint32_t, Filter>::iterator filter = m_filters.begin(); filter != m_filters.end(); filter++)
162         {
163           filter->second.rule = NULL;
164           
165           switch (filter->second.type)
166           {
167             case SettingInfo::STRING:
168             case SettingInfo::EDIT:
169               ((CStdString *)filter->second.data)->clear();
170               break;
171
172             case SettingInfo::CHECK:
173               *(int *)filter->second.data = CHECK_ALL;
174               break;
175
176             case SettingInfo::BUTTON:
177               ((CStdString *)filter->second.data)->clear();
178               SET_CONTROL_LABEL2(filter->second.controlIndex, *(CStdString *)filter->second.data);
179               break;
180
181             case SettingInfo::RANGE:
182               *(((float **)filter->second.data)[0]) = m_settings[filter->second.controlIndex - CONTROL_START].min;
183               *(((float **)filter->second.data)[1]) = m_settings[filter->second.controlIndex - CONTROL_START].max;
184               break;
185
186             default:
187               continue;
188           }
189
190           UpdateSetting(filter->first);
191         }
192
193         TriggerFilter();
194         return true;
195       }
196       break;
197     }
198
199     case GUI_MSG_REFRESH_LIST:
200     {
201       TriggerFilter();
202       UpdateControls();
203       break;
204     }
205
206     case GUI_MSG_WINDOW_DEINIT:
207     {
208       Reset();
209       break;
210     }
211
212     default:
213       break;
214   }
215
216   return CGUIDialogSettings::OnMessage(message);
217 }
218
219 void CGUIDialogMediaFilter::ShowAndEditMediaFilter(const std::string &path, CSmartPlaylist &filter)
220 {
221   CGUIDialogMediaFilter *dialog = (CGUIDialogMediaFilter *)g_windowManager.GetWindow(WINDOW_DIALOG_MEDIA_FILTER);
222   if (dialog == NULL)
223     return;
224
225   // initialize and show the dialog
226   dialog->Initialize();
227   dialog->m_filter = &filter;
228   // must be called after setting the filter/smartplaylist
229   if (!dialog->SetPath(path))
230     return;
231
232   dialog->DoModal();
233 }
234
235 void CGUIDialogMediaFilter::OnWindowLoaded()
236 {
237   CGUIDialogSettings::OnWindowLoaded();
238   // we don't need the cancel button so let's hide it
239   SET_CONTROL_HIDDEN(CONTROL_CANCEL_BUTTON);
240 }
241
242 void CGUIDialogMediaFilter::CreateSettings()
243 {
244   if (m_filter == NULL)
245     return;
246
247   m_settings.clear();
248   int handledRules = 0;
249   for (unsigned int index = 0; index < NUM_FILTERS; index++)
250   {
251     if (filterList[index].mediaType != m_mediaType)
252       continue;
253
254     Filter filter = filterList[index];
255     filter.controlIndex = CONTROL_START + m_settings.size();
256
257     // check the smartplaylist if it contains a matching rule
258     for (CDatabaseQueryRules::iterator rule = m_filter->m_ruleCombination.m_rules.begin(); rule != m_filter->m_ruleCombination.m_rules.end(); rule++)
259     {
260       if ((*rule)->m_field == filter.field)
261       {
262         filter.rule = (CSmartPlaylistRule *)rule->get();
263         handledRules++;
264         break;
265       }
266     }
267
268     switch (filter.type)
269     {
270       case SettingInfo::STRING:
271       case SettingInfo::EDIT:
272       {
273         if (filter.rule != NULL && filter.rule->m_parameter.size() == 1)
274           filter.data = new CStdString(filter.rule->m_parameter.at(0));
275         else
276           filter.data = new CStdString();
277
278         if (filter.type == SettingInfo::STRING)
279           AddString(filter.field, filter.label, (CStdString *)filter.data);
280         else
281           AddEdit(filter.field, filter.label, (CStdString *)filter.data);
282         break;
283       }
284       
285       case SettingInfo::CHECK:
286       {
287         if (filter.rule == NULL)
288           filter.data = new int(CHECK_ALL);
289         else
290           filter.data = new int(filter.rule->m_operator == CDatabaseQueryRule::OPERATOR_TRUE ? CHECK_YES : CHECK_NO);
291
292         vector<pair<int, int> > entries;
293         entries.push_back(pair<int, int>(CHECK_ALL, CHECK_LABEL_ALL));
294         entries.push_back(pair<int, int>(CHECK_NO,  CHECK_LABEL_NO));
295         entries.push_back(pair<int, int>(CHECK_YES, CHECK_LABEL_YES));
296         AddSpin(filter.field, filter.label, (int *)filter.data, entries);
297         break;
298       }
299
300       case SettingInfo::BUTTON:
301       {
302         CStdString *values = new CStdString();
303         if (filter.rule != NULL && filter.rule->m_parameter.size() > 0)
304           *values = filter.rule->GetParameter();
305         filter.data = values;
306
307         AddButton(filter.field, filter.label);
308         break;
309       }
310
311       case SettingInfo::RANGE:
312       {
313         float min = 0, interval = 0, max = 0;
314         RANGEFORMATFUNCTION format;
315         GetRange(filter, min, interval, max, format);
316
317         // don't create the filter if there's no real range
318         if (min == max)
319           break;
320
321         float *valueLower = new float();
322         float *valueUpper = new float();
323         if (filter.rule != NULL && filter.rule->m_parameter.size() == 2)
324         {
325           *valueLower = (float)strtod(filter.rule->m_parameter.at(0), NULL);
326           *valueUpper = (float)strtod(filter.rule->m_parameter.at(1), NULL);
327         }
328         else
329         {
330           *valueLower = min;
331           *valueUpper = max;
332
333           if (filter.rule != NULL)
334           {
335             DeleteRule(filter.field);
336             filter.rule = NULL;
337           }
338         }
339
340         AddRangeSlider(filter.field, filter.label, valueLower, valueUpper, min, interval, max, format);
341         filter.data = m_settings[filter.controlIndex - CONTROL_START].data;
342         break;
343       }
344
345       default:
346         filter.controlIndex = -1;
347         if (filter.rule != NULL)
348           handledRules--;
349         continue;
350     }
351
352     m_filters[filter.field] = filter;
353   }
354
355   // make sure that no change in capacity size is needed when adding new rules
356   // which would copy around the rules and our pointers in the Filter struct
357   // wouldn't work anymore
358   m_filter->m_ruleCombination.m_rules.reserve(m_filters.size() + (m_filter->m_ruleCombination.m_rules.size() - handledRules));
359 }
360
361 void CGUIDialogMediaFilter::SetupPage()
362 {
363   CGUIDialogSettings::SetupPage();
364
365   // set the heading label based on the media type
366   uint32_t localizedMediaId = 0; 
367   if (m_mediaType == "movies")
368     localizedMediaId = 20342;
369   else if (m_mediaType == "tvshows")
370     localizedMediaId = 20343;
371   else if (m_mediaType == "episodes")
372     localizedMediaId = 20360;
373   else if (m_mediaType == "musicvideos")
374     localizedMediaId = 20389;
375   else if (m_mediaType == "artists")
376     localizedMediaId = 133;
377   else if (m_mediaType == "albums")
378     localizedMediaId = 132;
379   else if (m_mediaType == "songs")
380     localizedMediaId = 134;
381
382   CStdString format = StringUtils::Format(g_localizeStrings.Get(1275).c_str(), g_localizeStrings.Get(localizedMediaId).c_str());
383   SET_CONTROL_LABEL(CONTROL_HEADING, format);
384
385   // now we can finally set the label/values of the button settings (genre, actors etc)
386   for (map<uint32_t, Filter>::const_iterator filter = m_filters.begin(); filter != m_filters.end(); filter++)
387   {
388     if (filter->second.type == SettingInfo::BUTTON &&
389         filter->second.controlIndex >= 0 && filter->second.data != NULL)
390       SET_CONTROL_LABEL2(filter->second.controlIndex, *(CStdString *)filter->second.data);
391   }
392
393   UpdateControls();
394 }
395
396 void CGUIDialogMediaFilter::OnTimeout()
397 {
398   CGUIMessage msg(GUI_MSG_REFRESH_LIST, GetID(), 0);
399   g_windowManager.SendThreadMessage(msg, WINDOW_DIALOG_MEDIA_FILTER);
400 }
401
402 void CGUIDialogMediaFilter::OnSettingChanged(SettingInfo &setting)
403 {
404   map<uint32_t, Filter>::iterator it = m_filters.find(setting.id);
405   if (it == m_filters.end())
406     return;
407
408   bool changed = true;
409   bool delay = false;
410   bool remove = false;
411   Filter& filter = it->second;
412
413   switch (filter.type)
414   {
415     case SettingInfo::STRING:
416     case SettingInfo::EDIT:
417     {
418       CStdString *str = static_cast<CStdString*>(filter.data);
419       if (!str->empty())
420       {
421         if (filter.rule == NULL)
422           filter.rule = AddRule(filter.field, filter.ruleOperator);
423         filter.rule->m_parameter.clear();
424         filter.rule->m_parameter.push_back(*str);
425         // trigger the live filtering with a delay in case the user
426         // types several characters in a short time
427         delay = true;
428       }
429       else
430         remove = true;
431         
432       break;
433     }
434     
435     case SettingInfo::CHECK:
436     {
437       int choice = *(int *)setting.data;
438       if (choice > CHECK_ALL)
439       {
440         CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator = choice == CHECK_YES ? CDatabaseQueryRule::OPERATOR_TRUE : CDatabaseQueryRule::OPERATOR_FALSE;
441         if (filter.rule == NULL)
442           filter.rule = AddRule(filter.field, ruleOperator);
443         else
444           filter.rule->m_operator = ruleOperator;
445       }
446       else
447         remove = true;
448
449       break;
450     }
451
452     case SettingInfo::BUTTON:
453     {
454       CFileItemList items;
455       OnBrowse(filter, items);
456       
457       if (items.Size() > 0)
458       {
459         if (filter.rule == NULL)
460           filter.rule = AddRule(filter.field, filter.ruleOperator);
461
462         filter.rule->m_parameter.clear();
463         for (int index = 0; index < items.Size(); index++)
464           filter.rule->m_parameter.push_back(items[index]->GetLabel());
465
466         *(CStdString *)filter.data = filter.rule->GetParameter();
467       }
468       else
469       {
470         remove = true;
471         *(CStdString *)filter.data = "";
472       }
473
474       SET_CONTROL_LABEL2(filter.controlIndex, *(CStdString *)filter.data);
475       break;
476     }
477
478     case SettingInfo::RANGE:
479     {
480       SettingInfo &setting = m_settings[filter.controlIndex - CONTROL_START];
481       float *valueLower = ((float **)filter.data)[0];
482       float *valueUpper = ((float **)filter.data)[1];
483
484       if (*valueLower > setting.min || *valueUpper < setting.max)
485       {
486         if (filter.rule == NULL)
487           filter.rule = AddRule(filter.field, filter.ruleOperator);
488
489         filter.rule->m_parameter.clear();
490         if (filter.field == FieldAirDate)
491         {
492           CDateTime lower = (time_t)*valueLower;
493           CDateTime upper = (time_t)*valueUpper;
494           filter.rule->m_parameter.push_back(lower.GetAsDBDate());
495           filter.rule->m_parameter.push_back(upper.GetAsDBDate());
496         }
497         else
498         {
499           CStdString tmp = StringUtils::Format("%.1f", *valueLower);
500           filter.rule->m_parameter.push_back(tmp);
501           tmp.clear();
502           tmp = StringUtils::Format("%.1f", *valueUpper);
503           filter.rule->m_parameter.push_back(tmp);
504         }
505       }
506       else
507       {
508         remove = true;
509         *((float **)filter.data)[0] = setting.min;
510         *((float **)filter.data)[1] = setting.max;
511       }
512
513       // trigger the live filtering with a delay in case the user
514       // moves the slider several steps in a short time
515       delay = true;
516       break;
517     }
518
519     default:
520       changed = false;
521       break;
522   }
523
524   // we need to remove the existing rule for the title
525   if (remove && filter.rule != NULL)
526   {
527     DeleteRule(filter.field);
528     filter.rule = NULL;
529   }
530
531   if (changed)
532   {
533     if (!delay)
534     {
535       CGUIMessage msg(GUI_MSG_REFRESH_LIST, GetID(), 0);
536       OnMessage(msg);
537     }
538     else if (m_delayTimer)
539     {
540       if (m_delayTimer->IsRunning())
541         m_delayTimer->Restart();
542       else
543         m_delayTimer->Start(TIMEOUT_DELAY, false);
544     }
545   }
546 }
547
548 void CGUIDialogMediaFilter::Reset()
549 {
550   if (m_delayTimer && m_delayTimer->IsRunning())
551     m_delayTimer->Stop();
552
553   delete m_dbUrl;
554   m_dbUrl = NULL;
555
556   // delete all the setting's data
557   for (map<uint32_t, Filter>::iterator filter = m_filters.begin(); filter != m_filters.end(); filter++)
558   {
559     switch (filter->second.type)
560     {
561       case SettingInfo::STRING:
562       case SettingInfo::EDIT:
563       case SettingInfo::BUTTON:
564         delete (CStdString *)filter->second.data;
565         break;
566
567       case SettingInfo::CHECK:
568         delete (int *)filter->second.data;
569         break;
570
571       case SettingInfo::RANGE:
572         if (filter->second.data != NULL)
573         {
574           delete ((float **)filter->second.data)[0];
575           delete ((float **)filter->second.data)[1];
576         }
577         delete (float *)filter->second.data;
578         break;
579
580       default:
581         continue;
582     }
583   }
584
585   m_filters.clear();
586 }
587
588 bool CGUIDialogMediaFilter::SetPath(const std::string &path)
589 {
590   if (path.empty() || m_filter == NULL)
591   {
592     CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath(%s): invalid path or filter", path.c_str());
593     return false;
594   }
595
596   delete m_dbUrl;
597   bool video = false;
598   if (path.find("videodb://") == 0)
599   {
600     m_dbUrl = new CVideoDbUrl();
601     video = true;
602   }
603   else if (path.find("musicdb://") == 0)
604     m_dbUrl = new CMusicDbUrl();
605   else
606   {
607     CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath(%s): invalid path (neither videodb:// nor musicdb://)", path.c_str());
608     return false;
609   }
610
611   if (!m_dbUrl->FromString(path) ||
612      (video && m_dbUrl->GetType() != "movies" && m_dbUrl->GetType() != "tvshows" && m_dbUrl->GetType() != "episodes" && m_dbUrl->GetType() != "musicvideos") ||
613      (!video && m_dbUrl->GetType() != "artists" && m_dbUrl->GetType() != "albums" && m_dbUrl->GetType() != "songs"))
614   {
615     CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath(%s): invalid media type", path.c_str());
616     return false;
617   }
618
619   // remove "filter" option
620   if (m_dbUrl->HasOption("filter"))
621     m_dbUrl->RemoveOption("filter");
622
623   if (video)
624     m_mediaType = ((CVideoDbUrl*)m_dbUrl)->GetItemType();
625   else
626     m_mediaType = m_dbUrl->GetType();
627
628   m_filter->SetType(m_mediaType);
629   return true;
630 }
631
632 void CGUIDialogMediaFilter::UpdateControls()
633 {
634   for (map<uint32_t, Filter>::iterator itFilter = m_filters.begin(); itFilter != m_filters.end(); itFilter++)
635   {
636     if (itFilter->second.type == SettingInfo::BUTTON)
637     {
638       CFileItemList items;
639       OnBrowse(itFilter->second, items, true);
640
641       int size = items.Size();
642       if (items.Size() == 1 && items[0]->HasProperty("total"))
643         size = (int)items[0]->GetProperty("total").asInteger();
644
645       CStdString label = g_localizeStrings.Get(itFilter->second.label);
646       if (size <= 0 ||
647          (size == 1 && itFilter->second.field != FieldSet && itFilter->second.field != FieldTag))
648         CONTROL_DISABLE(itFilter->second.controlIndex);
649       else
650       {
651         CONTROL_ENABLE(itFilter->second.controlIndex);
652         label = StringUtils::Format("%s [%d]", label.c_str(), size);
653       }
654       SET_CONTROL_LABEL(itFilter->second.controlIndex, label);
655     }
656   }
657 }
658
659 void CGUIDialogMediaFilter::TriggerFilter() const
660 {
661   if (m_filter == NULL)
662     return;
663
664   CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 10); // 10 for advanced
665   g_windowManager.SendThreadMessage(message);
666 }
667
668 void CGUIDialogMediaFilter::OnBrowse(const Filter &filter, CFileItemList &items, bool countOnly /* = false */)
669 {
670   CFileItemList selectItems;
671   if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos")
672   {
673     CVideoDatabase videodb;
674     if (!videodb.Open())
675       return;
676
677     CSmartPlaylist tmpFilter = *m_filter;
678     for (CDatabaseQueryRules::iterator rule = tmpFilter.m_ruleCombination.m_rules.begin(); rule != tmpFilter.m_ruleCombination.m_rules.end(); rule++)
679     {
680       if ((*rule)->m_field == filter.field)
681       {
682         tmpFilter.m_ruleCombination.m_rules.erase(rule);
683         break;
684       }
685     }
686
687     std::set<CStdString> playlists;
688     CDatabase::Filter dbfilter;
689     dbfilter.where = tmpFilter.GetWhereClause(videodb, playlists);
690
691     VIDEODB_CONTENT_TYPE type = VIDEODB_CONTENT_MOVIES;    
692     if (m_mediaType == "tvshows")
693       type = VIDEODB_CONTENT_TVSHOWS;
694     else if (m_mediaType == "episodes")
695       type = VIDEODB_CONTENT_EPISODES;
696     else if (m_mediaType == "musicvideos")
697       type = VIDEODB_CONTENT_MUSICVIDEOS;
698
699     if (filter.field == FieldGenre)
700       videodb.GetGenresNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
701     else if (filter.field == FieldActor || filter.field == FieldArtist)
702       videodb.GetActorsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
703     else if (filter.field == FieldDirector)
704       videodb.GetDirectorsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
705     else if (filter.field == FieldStudio)
706       videodb.GetStudiosNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
707     else if (filter.field == FieldAlbum)
708       videodb.GetMusicVideoAlbumsNav(m_dbUrl->ToString(), selectItems, -1, dbfilter, countOnly);
709     else if (filter.field == FieldTag)
710       videodb.GetTagsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly);
711   }
712   else if (m_mediaType == "artists" || m_mediaType == "albums" || m_mediaType == "songs")
713   {
714     CMusicDatabase musicdb;
715     if (!musicdb.Open())
716       return;
717
718     CSmartPlaylist tmpFilter = *m_filter;
719     for (CDatabaseQueryRules::iterator rule = tmpFilter.m_ruleCombination.m_rules.begin(); rule != tmpFilter.m_ruleCombination.m_rules.end(); rule++)
720     {
721       if ((*rule)->m_field == filter.field)
722       {
723         tmpFilter.m_ruleCombination.m_rules.erase(rule);
724         break;
725       }
726     }
727
728     std::set<CStdString> playlists;
729     CDatabase::Filter dbfilter;
730     dbfilter.where = tmpFilter.GetWhereClause(musicdb, playlists);
731     
732     if (filter.field == FieldGenre)
733       musicdb.GetGenresNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
734     else if (filter.field == FieldArtist)
735       musicdb.GetArtistsNav(m_dbUrl->ToString(), selectItems, m_mediaType == "albums", -1, -1, -1, dbfilter, SortDescription(), countOnly);
736     else if (filter.field == FieldAlbum)
737       musicdb.GetAlbumsNav(m_dbUrl->ToString(), selectItems, -1, -1, dbfilter, SortDescription(), countOnly);
738     else if (filter.field == FieldAlbumType)
739       musicdb.GetAlbumTypesNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
740     else if (filter.field == FieldMusicLabel)
741       musicdb.GetMusicLabelsNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly);
742   }
743
744   if (selectItems.Size() <= 0)
745     return;
746
747   if (countOnly)
748   {
749     items.Copy(selectItems);
750     return;
751   }
752
753   // sort the items
754   selectItems.Sort(SortByLabel, SortOrderAscending);
755
756   CGUIDialogSelect* pDialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
757   pDialog->Reset();
758   pDialog->SetItems(&selectItems);
759   CStdString strHeading = StringUtils::Format(g_localizeStrings.Get(13401), g_localizeStrings.Get(filter.label).c_str());
760   pDialog->SetHeading(strHeading);
761   pDialog->SetMultiSelection(true);
762
763   if (filter.rule != NULL && !filter.rule->m_parameter.empty())
764     pDialog->SetSelected(filter.rule->m_parameter);
765
766   pDialog->DoModal();
767   if (pDialog->IsConfirmed())
768     items.Copy(pDialog->GetSelectedItems());
769   else
770     items.Clear();
771   pDialog->Reset();
772 }
773
774 CSmartPlaylistRule* CGUIDialogMediaFilter::AddRule(Field field, CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator /* = CDatabaseQueryRule::OPERATOR_CONTAINS */)
775 {
776   CSmartPlaylistRule rule;
777   rule.m_field = field;
778   rule.m_operator = ruleOperator;
779
780   m_filter->m_ruleCombination.AddRule(rule);
781   return (CSmartPlaylistRule *)m_filter->m_ruleCombination.m_rules.back().get();
782 }
783
784 void CGUIDialogMediaFilter::DeleteRule(Field field)
785 {
786   for (CDatabaseQueryRules::iterator rule = m_filter->m_ruleCombination.m_rules.begin(); rule != m_filter->m_ruleCombination.m_rules.end(); rule++)
787   {
788     if ((*rule)->m_field == field)
789     {
790       m_filter->m_ruleCombination.m_rules.erase(rule);
791       break;
792     }
793   }
794 }
795
796 void CGUIDialogMediaFilter::GetRange(const Filter &filter, float &min, float &interval, float &max, RANGEFORMATFUNCTION &formatFunction)
797 {
798   if (filter.field == FieldRating)
799   {
800     if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes")
801     {
802       min = 0.0f;
803       interval = 0.1f;
804       max = 10.0f;
805       formatFunction = RangeAsFloat;
806     }
807     else if (m_mediaType == "albums" || m_mediaType == "songs")
808     {
809       min = 0.0f;
810       interval = 1.0f;
811       max = 5.0f;
812       formatFunction = RangeAsInt;
813     }
814   }
815   else if (filter.field == FieldYear)
816   {
817     formatFunction = RangeAsInt;
818     min = 0.0f;
819     interval = 1.0f;
820     max = 0.0f;
821
822     if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "musicvideos")
823     {
824       CStdString table;
825       CStdString year;
826       if (m_mediaType == "movies")
827       {
828         table = "movieview";
829         year = DatabaseUtils::GetField(FieldYear, MediaTypeMovie, DatabaseQueryPartWhere);
830       }
831       else if (m_mediaType == "tvshows")
832       {
833         table = "tvshowview";
834         year = StringUtils::Format("strftime(\"%%Y\", %s)", DatabaseUtils::GetField(FieldYear, MediaTypeTvShow, DatabaseQueryPartWhere).c_str());
835       }
836       else if (m_mediaType == "musicvideos")
837       {
838         table = "musicvideoview";
839         year = DatabaseUtils::GetField(FieldYear, MediaTypeMusicVideo, DatabaseQueryPartWhere);
840       }
841
842       CDatabase::Filter filter;
843       filter.where = year + " > 0";
844       GetMinMax(table, year, min, max, filter);
845     }
846     else if (m_mediaType == "albums" || m_mediaType == "songs")
847     {
848       CStdString table;
849       MediaType mediaType;
850       if (m_mediaType == "albums")
851       {
852         table = "albumview";
853         mediaType = MediaTypeAlbum;
854       }
855       else if (m_mediaType == "songs")
856       {
857         table = "songview";
858         mediaType = MediaTypeSong;
859       }
860       else
861         return;
862
863       CDatabase::Filter filter;
864       filter.where = DatabaseUtils::GetField(FieldYear, mediaType, DatabaseQueryPartWhere) + " > 0";
865       GetMinMax(table, DatabaseUtils::GetField(FieldYear, mediaType, DatabaseQueryPartSelect), min, max, filter);
866     }
867   }
868   else if (filter.field == FieldAirDate)
869   {
870     formatFunction = RangeAsDate;
871     min = 0.0f;
872     interval = 1.0f;
873     max = 0.0f;
874
875     if (m_mediaType == "episodes")
876     {
877       CStdString field = StringUtils::Format("CAST(strftime(\"%%s\", c%02d) AS INTEGER)", VIDEODB_ID_EPISODE_AIRED);
878       
879       GetMinMax("episodeview", field, min, max);
880       interval = 60 * 60 * 24 * 7; // 1 week
881     }
882   }
883   else if (filter.field == FieldTime)
884   {
885     formatFunction = RangeAsTime;
886     min = 0.0f;
887     interval = 10.0f;
888     max = 0.0f;
889
890     if (m_mediaType == "songs")
891       GetMinMax("songview", "iDuration", min, max);
892   }
893   else if (filter.field == FieldPlaycount)
894   {
895     formatFunction = RangeAsInt;
896     min = 0.0f;
897     interval = 1.0f;
898     max = 0.0f;
899
900     if (m_mediaType == "songs")
901       GetMinMax("songview", "iTimesPlayed", min, max);
902   }
903 }
904
905 bool CGUIDialogMediaFilter::GetMinMax(const CStdString &table, const CStdString &field, float &min, float &max, const CDatabase::Filter &filter /* = CDatabase::Filter() */)
906 {
907   if (table.empty() || field.empty())
908     return false;
909
910   CDatabase *db = NULL;
911   CDbUrl *dbUrl = NULL;
912   if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos")
913   {
914     CVideoDatabase *videodb = new CVideoDatabase();
915     if (!videodb->Open())
916     {
917       delete videodb;
918       return false;
919     }
920
921     db = videodb;
922     dbUrl = new CVideoDbUrl();
923   }
924   else if (m_mediaType == "artists" || m_mediaType == "albums" || m_mediaType == "songs")
925   {
926     CMusicDatabase *musicdb = new CMusicDatabase();
927     if (!musicdb->Open())
928     {
929       delete musicdb;
930       return false;
931     }
932
933     db = musicdb;
934     dbUrl = new CMusicDbUrl();
935   }
936
937   if (db == NULL || !db->IsOpen() || dbUrl == NULL)
938   {
939     delete db;
940     delete dbUrl;
941     return false;
942   }
943
944   CDatabase::Filter extFilter = filter;
945   CStdString strSQLExtra;
946   if (!db->BuildSQL(m_dbUrl->ToString(), strSQLExtra, extFilter, strSQLExtra, *dbUrl))
947   {
948     delete db;
949     delete dbUrl;
950     return false;
951   }
952
953   CStdString strSQL = "SELECT %s FROM %s ";
954
955   min = (float)strtod(db->GetSingleValue(db->PrepareSQL(strSQL, CStdString("MIN(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL);
956   max = (float)strtod(db->GetSingleValue(db->PrepareSQL(strSQL, CStdString("MAX(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL);
957
958   db->Close();
959   delete db;
960   delete dbUrl;
961
962   return true;
963 }
964
965 CStdString CGUIDialogMediaFilter::RangeAsFloat(float valueLower, float valueUpper, float minimum)
966 {
967   CStdString text;
968   if (valueLower != valueUpper)
969     text = StringUtils::Format(g_localizeStrings.Get(21467).c_str(), valueLower, valueUpper);
970   else
971     text = StringUtils::Format("%.1f", valueLower);
972   return text;
973 }
974
975 CStdString CGUIDialogMediaFilter::RangeAsInt(float valueLower, float valueUpper, float minimum)
976 {
977   CStdString text;
978   if (valueLower != valueUpper)
979     text = StringUtils::Format(g_localizeStrings.Get(21468).c_str(),
980                                MathUtils::round_int((double)valueLower),
981                                MathUtils::round_int((double)valueUpper));
982   else
983     text = StringUtils::Format("%d", MathUtils::round_int((double)valueLower));
984   return text;
985 }
986
987 CStdString CGUIDialogMediaFilter::RangeAsDate(float valueLower, float valueUpper, float minimum)
988 {
989   CDateTime from = (time_t)valueLower;
990   CDateTime to = (time_t)valueUpper;
991   CStdString text;
992   if (valueLower != valueUpper)
993     text = StringUtils::Format(g_localizeStrings.Get(21469).c_str(),
994                                from.GetAsLocalizedDate().c_str(),
995                                to.GetAsLocalizedDate().c_str());
996   else
997     text = StringUtils::Format("%s",
998                                from.GetAsLocalizedDate().c_str());
999   return text;
1000 }
1001
1002 CStdString CGUIDialogMediaFilter::RangeAsTime(float valueLower, float valueUpper, float minimum)
1003 {
1004   CDateTime from = (time_t)valueLower;
1005   CDateTime to = (time_t)valueUpper;
1006   CStdString text;
1007   if (valueLower != valueUpper)
1008     text = StringUtils::Format(g_localizeStrings.Get(21469).c_str(),
1009                                from.GetAsLocalizedTime("mm:ss").c_str(),
1010                                to.GetAsLocalizedTime("mm:ss").c_str());
1011   else
1012     text = StringUtils::Format("%s", from.GetAsLocalizedTime("mm:ss").c_str());
1013   return text;
1014 }