2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
21 #include "GUIUserMessages.h"
22 #include "GUIWindowVideoNav.h"
23 #include "music/windows/GUIWindowMusicNav.h"
24 #include "utils/FileUtils.h"
26 #include "utils/RegExp.h"
27 #include "PlayListPlayer.h"
28 #include "GUIPassword.h"
29 #include "dialogs/GUIDialogFileBrowser.h"
30 #include "filesystem/VideoDatabaseDirectory.h"
31 #include "playlists/PlayListFactory.h"
32 #include "dialogs/GUIDialogOK.h"
33 #include "addons/AddonManager.h"
34 #include "PartyModeManager.h"
35 #include "music/MusicDatabase.h"
36 #include "guilib/GUIWindowManager.h"
37 #include "dialogs/GUIDialogYesNo.h"
38 #include "dialogs/GUIDialogSelect.h"
39 #include "filesystem/Directory.h"
40 #include "filesystem/File.h"
42 #include "Application.h"
43 #include "ApplicationMessenger.h"
44 #include "profiles/ProfilesManager.h"
45 #include "settings/AdvancedSettings.h"
46 #include "settings/MediaSettings.h"
47 #include "settings/MediaSourceSettings.h"
48 #include "settings/Settings.h"
49 #include "guilib/Key.h"
50 #include "guilib/LocalizeStrings.h"
51 #include "storage/MediaManager.h"
52 #include "utils/LegacyPathTranslation.h"
53 #include "utils/log.h"
54 #include "utils/URIUtils.h"
55 #include "utils/StringUtils.h"
56 #include "TextureCache.h"
57 #include "guilib/GUIKeyboardFactory.h"
58 #include "video/VideoInfoScanner.h"
59 #include "video/dialogs/GUIDialogVideoInfo.h"
60 #include "pvr/recordings/PVRRecording.h"
62 using namespace XFILE;
63 using namespace VIDEODATABASEDIRECTORY;
66 #define CONTROL_BTNVIEWASICONS 2
67 #define CONTROL_BTNSORTBY 3
68 #define CONTROL_BTNSORTASC 4
69 #define CONTROL_BTNTYPE 5
70 #define CONTROL_BTNSEARCH 8
71 #define CONTROL_LABELFILES 12
73 #define CONTROL_BTN_FILTER 19
74 #define CONTROL_BTNSHOWMODE 10
75 #define CONTROL_BTNSHOWALL 14
76 #define CONTROL_UNLOCK 11
78 #define CONTROL_FILTER 15
79 #define CONTROL_BTNPARTYMODE 16
80 #define CONTROL_LABELEMPTY 18
82 #define CONTROL_UPDATE_LIBRARY 20
84 CGUIWindowVideoNav::CGUIWindowVideoNav(void)
85 : CGUIWindowVideoBase(WINDOW_VIDEO_NAV, "MyVideoNav.xml")
87 m_thumbLoader.SetObserver(this);
90 CGUIWindowVideoNav::~CGUIWindowVideoNav(void)
94 bool CGUIWindowVideoNav::OnAction(const CAction &action)
96 if (action.GetID() == ACTION_TOGGLE_WATCHED)
98 CFileItemPtr pItem = m_vecItems->Get(m_viewControl.GetSelectedItem());
99 if (pItem->IsParentFolder())
101 if (pItem && pItem->GetVideoInfoTag()->m_playCount == 0)
102 return OnContextButton(m_viewControl.GetSelectedItem(),CONTEXT_BUTTON_MARK_WATCHED);
103 if (pItem && pItem->GetVideoInfoTag()->m_playCount > 0)
104 return OnContextButton(m_viewControl.GetSelectedItem(),CONTEXT_BUTTON_MARK_UNWATCHED);
106 return CGUIWindowVideoBase::OnAction(action);
109 bool CGUIWindowVideoNav::OnMessage(CGUIMessage& message)
111 switch (message.GetMessage())
113 case GUI_MSG_WINDOW_RESET:
114 m_vecItems->SetPath("");
116 case GUI_MSG_WINDOW_DEINIT:
117 if (m_thumbLoader.IsLoading())
118 m_thumbLoader.StopThread();
120 case GUI_MSG_WINDOW_INIT:
122 /* We don't want to show Autosourced items (ie removable pendrives, memorycards) in Library mode */
123 m_rootDir.AllowNonLocalSources(false);
125 SetProperty("flattened", CSettings::Get().GetBool("myvideos.flatten"));
126 if (message.GetNumStringParams() && message.GetStringParam(0).Equals("Files") &&
127 CMediaSourceSettings::Get().GetSources("video")->empty())
129 message.SetStringParam("");
132 if (!CGUIWindowVideoBase::OnMessage(message))
139 case GUI_MSG_CLICKED:
141 int iControl = message.GetSenderId();
142 if (iControl == CONTROL_BTNPARTYMODE)
144 if (g_partyModeManager.IsEnabled())
145 g_partyModeManager.Disable();
148 if (!g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO))
150 SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE,false);
154 // Playlist directory is the root of the playlist window
155 if (m_guiState.get()) m_guiState->SetPlaylistDirectory("playlistvideo://");
162 if (iControl == CONTROL_BTNSEARCH)
166 else if (iControl == CONTROL_BTNSHOWMODE)
168 CMediaSettings::Get().CycleWatchedMode(m_vecItems->GetContent());
169 CSettings::Get().Save();
170 OnFilterItems(GetProperty("filter").asString());
173 else if (iControl == CONTROL_BTNSHOWALL)
175 if (CMediaSettings::Get().GetWatchedMode(m_vecItems->GetContent()) == WatchedModeAll)
176 CMediaSettings::Get().SetWatchedMode(m_vecItems->GetContent(), WatchedModeUnwatched);
178 CMediaSettings::Get().SetWatchedMode(m_vecItems->GetContent(), WatchedModeAll);
179 CSettings::Get().Save();
180 OnFilterItems(GetProperty("filter").asString());
183 else if (iControl == CONTROL_UPDATE_LIBRARY)
185 if (!g_application.IsVideoScanning())
188 g_application.StopVideoScan();
193 // update the display
194 case GUI_MSG_SCAN_FINISHED:
195 case GUI_MSG_REFRESH_THUMBS:
199 return CGUIWindowVideoBase::OnMessage(message);
202 CStdString CGUIWindowVideoNav::GetQuickpathName(const CStdString& strPath) const
204 CStdString path = CLegacyPathTranslation::TranslateVideoDbPath(strPath);
205 if (path.Equals("videodb://movies/genres/"))
206 return "MovieGenres";
207 else if (path.Equals("videodb://movies/titles/"))
208 return "MovieTitles";
209 else if (path.Equals("videodb://movies/years/"))
211 else if (path.Equals("videodb://movies/actors/"))
212 return "MovieActors";
213 else if (path.Equals("videodb://movies/directors/"))
214 return "MovieDirectors";
215 else if (path.Equals("videodb://movies/studios/"))
216 return "MovieStudios";
217 else if (path.Equals("videodb://movies/sets/"))
219 else if (path.Equals("videodb://movies/countries/"))
220 return "MovieCountries";
221 else if (path.Equals("videodb://movies/tags/"))
223 else if (path.Equals("videodb://movies/"))
225 else if (path.Equals("videodb://tvshows/genres/"))
226 return "TvShowGenres";
227 else if (path.Equals("videodb://tvshows/titles/"))
228 return "TvShowTitles";
229 else if (path.Equals("videodb://tvshows/years/"))
230 return "TvShowYears";
231 else if (path.Equals("videodb://tvshows/actors/"))
232 return "TvShowActors";
233 else if (path.Equals("videodb://tvshows/studios/"))
234 return "TvShowStudios";
235 else if (path.Equals("videodb://tvshows/tags/"))
237 else if (path.Equals("videodb://tvshows/"))
239 else if (path.Equals("videodb://musicvideos/genres/"))
240 return "MusicVideoGenres";
241 else if (path.Equals("videodb://musicvideos/titles/"))
242 return "MusicVideoTitles";
243 else if (path.Equals("videodb://musicvideos/years/"))
244 return "MusicVideoYears";
245 else if (path.Equals("videodb://musicvideos/artists/"))
246 return "MusicVideoArtists";
247 else if (path.Equals("videodb://musicvideos/albums/"))
248 return "MusicVideoDirectors";
249 else if (path.Equals("videodb://musicvideos/tags/"))
250 return "MusicVideoTags";
251 else if (path.Equals("videodb://musicvideos/"))
252 return "MusicVideos";
253 else if (path.Equals("videodb://recentlyaddedmovies/"))
254 return "RecentlyAddedMovies";
255 else if (path.Equals("videodb://recentlyaddedepisodes/"))
256 return "RecentlyAddedEpisodes";
257 else if (path.Equals("videodb://recentlyaddedmusicvideos/"))
258 return "RecentlyAddedMusicVideos";
259 else if (path.Equals("special://videoplaylists/"))
261 else if (path.Equals("sources://video/"))
265 CLog::Log(LOGERROR, " CGUIWindowVideoNav::GetQuickpathName: Unknown parameter (%s)", strPath.c_str());
270 bool CGUIWindowVideoNav::GetDirectory(const CStdString &strDirectory, CFileItemList &items)
272 if (m_thumbLoader.IsLoading())
273 m_thumbLoader.StopThread();
275 items.ClearProperties();
277 bool bResult = CGUIWindowVideoBase::GetDirectory(strDirectory, items);
280 if (items.IsVideoDb())
282 XFILE::CVideoDatabaseDirectory dir;
284 dir.GetQueryParams(items.GetPath(),params);
285 VIDEODATABASEDIRECTORY::NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath());
287 // perform the flattening logic for tvshows with a single (unwatched) season (+ optional special season)
288 if (node == NODE_TYPE_SEASONS)
290 int itemsSize = items.GetObjectCount();
291 int firstIndex = items.Size() - itemsSize;
292 // check if the last item is the "All seasons" item which should be ignored for flattening
293 if (items[items.Size() - 1]->GetVideoInfoTag()->m_iSeason < 0)
296 int iFlatten = CSettings::Get().GetInt("videolibrary.flattentvshows");
297 bool bFlatten = (itemsSize == 1 && iFlatten == 1) || iFlatten == 2 || // flatten if one one season or if always flatten is enabled
298 (itemsSize == 2 && iFlatten == 1 && // flatten if one season + specials
299 (items[firstIndex]->GetVideoInfoTag()->m_iSeason == 0 || items[firstIndex + 1]->GetVideoInfoTag()->m_iSeason == 0));
301 if (iFlatten > 0 && !bFlatten && (WatchedMode)CMediaSettings::Get().GetWatchedMode("tvshows") == WatchedModeUnwatched)
304 for(int i = 0; i < items.Size(); i++)
306 const CFileItemPtr item = items.Get(i);
307 if (item->GetProperty("unwatchedepisodes").asInteger() != 0 && item->GetVideoInfoTag()->m_iSeason > 0)
310 bFlatten = (count < 2); // flatten if there is only 1 unwatched season (not counting specials)
314 { // flatten if one season or flatten always
317 CVideoDbUrl videoUrl;
318 if (!videoUrl.FromString(items.GetPath()))
321 videoUrl.AppendPath("-2/");
322 return GetDirectory(videoUrl.ToString(), items);
326 items.SetArt("thumb", "");
327 if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_EPISODES ||
328 node == NODE_TYPE_SEASONS ||
329 node == NODE_TYPE_RECENTLY_ADDED_EPISODES)
331 CLog::Log(LOGDEBUG, "WindowVideoNav::GetDirectory");
332 // grab the show thumb
333 CVideoInfoTag details;
334 m_database.GetTvShowInfo("", details, params.GetTvShowId());
335 map<string, string> art;
336 if (m_database.GetArtForItem(details.m_iDbId, details.m_type, art))
338 items.AppendArt(art, "tvshow");
339 items.SetArtFallback("fanart", "tvshow.fanart");
340 if (node == NODE_TYPE_SEASONS)
341 { // set an art fallback for "thumb"
342 if (items.HasArt("tvshow.poster"))
343 items.SetArtFallback("thumb", "tvshow.poster");
344 else if (items.HasArt("tvshow.banner"))
345 items.SetArtFallback("thumb", "tvshow.banner");
350 items.SetProperty("fanart_color1", details.m_fanart.GetColor(0));
351 items.SetProperty("fanart_color2", details.m_fanart.GetColor(1));
352 items.SetProperty("fanart_color3", details.m_fanart.GetColor(2));
354 // save the show description (showplot)
355 items.SetProperty("showplot", details.m_strPlot);
357 // the container folder thumb is the parent (i.e. season or show)
358 if (node == NODE_TYPE_EPISODES || node == NODE_TYPE_RECENTLY_ADDED_EPISODES)
360 items.SetContent("episodes");
361 // grab the season thumb as the folder thumb
362 int seasonID = m_database.GetSeasonId(details.m_iDbId, params.GetSeason());
363 CGUIListItem::ArtMap seasonArt;
364 if (m_database.GetArtForItem(seasonID, "season", seasonArt))
366 items.AppendArt(art, "season");
367 // set an art fallback for "thumb"
368 if (items.HasArt("season.poster"))
369 items.SetArtFallback("thumb", "season.poster");
370 else if (items.HasArt("season.banner"))
371 items.SetArtFallback("thumb", "season.banner");
375 items.SetContent("seasons");
377 else if (node == NODE_TYPE_TITLE_MOVIES ||
378 node == NODE_TYPE_RECENTLY_ADDED_MOVIES)
379 items.SetContent("movies");
380 else if (node == NODE_TYPE_TITLE_TVSHOWS)
381 items.SetContent("tvshows");
382 else if (node == NODE_TYPE_TITLE_MUSICVIDEOS ||
383 node == NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS)
384 items.SetContent("musicvideos");
385 else if (node == NODE_TYPE_GENRE)
386 items.SetContent("genres");
387 else if (node == NODE_TYPE_COUNTRY)
388 items.SetContent("countries");
389 else if (node == NODE_TYPE_ACTOR)
391 if (params.GetContentType() == VIDEODB_CONTENT_MUSICVIDEOS)
392 items.SetContent("artists");
394 items.SetContent("actors");
396 else if (node == NODE_TYPE_DIRECTOR)
397 items.SetContent("directors");
398 else if (node == NODE_TYPE_STUDIO)
399 items.SetContent("studios");
400 else if (node == NODE_TYPE_YEAR)
401 items.SetContent("years");
402 else if (node == NODE_TYPE_MUSICVIDEOS_ALBUM)
403 items.SetContent("albums");
404 else if (node == NODE_TYPE_SETS)
405 items.SetContent("sets");
406 else if (node == NODE_TYPE_TAGS)
407 items.SetContent("tags");
409 items.SetContent("");
411 else if (!items.IsVirtualDirectoryRoot())
412 { // load info from the database
414 if (items.GetLabel().empty() && m_rootDir.IsSource(items.GetPath(), CMediaSourceSettings::Get().GetSources("video"), &label))
415 items.SetLabel(label);
416 if (!items.IsSourcesPath())
417 LoadVideoInfo(items);
420 CVideoDbUrl videoUrl;
421 if (videoUrl.FromString(items.GetPath()) && items.GetContent() == "tags" &&
422 !items.Contains("newtag://" + videoUrl.GetType()))
424 CFileItemPtr newTag(new CFileItem("newtag://" + videoUrl.GetType(), false));
425 newTag->SetLabel(g_localizeStrings.Get(20462));
426 newTag->SetLabelPreformated(true);
427 newTag->SetSpecialSort(SortSpecialOnTop);
434 void CGUIWindowVideoNav::LoadVideoInfo(CFileItemList &items)
436 LoadVideoInfo(items, m_database);
439 void CGUIWindowVideoNav::LoadVideoInfo(CFileItemList &items, CVideoDatabase &database, bool allowReplaceLabels)
441 // TODO: this could possibly be threaded as per the music info loading,
442 // we could also cache the info
443 if (!items.GetContent().empty() && !items.IsPlugin())
444 return; // don't load for listings that have content set and weren't created from plugins
446 CStdString content = items.GetContent();
447 // determine content only if it isn't set
450 content = database.GetContentForPath(items.GetPath());
451 items.SetContent(content.empty() ? "files" : content);
455 If we have a matching item in the library, so we can assign the metadata to it. In addition, we can choose
456 * whether the item is stacked down (eg in the case of folders representing a single item)
457 * whether or not we assign the library's labels to the item, or leave the item as is.
459 As certain users (read: certain developers) don't want either of these to occur, we compromise by stacking
460 items down only if stacking is available and enabled.
462 Similarly, we assign the "clean" library labels to the item only if the "Replace filenames with library titles"
465 const bool stackItems = items.GetProperty("isstacked").asBoolean() || (StackingAvailable(items) && CSettings::Get().GetBool("myvideos.stackvideos"));
466 const bool replaceLabels = allowReplaceLabels && CSettings::Get().GetBool("myvideos.replacelabels");
468 CFileItemList dbItems;
469 /* NOTE: In the future when GetItemsForPath returns all items regardless of whether they're "in the library"
470 we won't need the fetchedPlayCounts code, and can "simply" do this directly on absense of content. */
471 bool fetchedPlayCounts = false;
472 if (!content.empty())
474 database.GetItemsForPath(content, items.GetPath(), dbItems);
475 dbItems.SetFastLookup(true);
478 for (int i = 0; i < items.Size(); i++)
480 CFileItemPtr pItem = items[i];
482 if (!content.empty()) /* optical media will be stacked down, so it's path won't match the base path */
483 match = dbItems.Get(pItem->IsOpticalMediaFile() ? pItem->GetLocalMetadataPath() : pItem->GetPath());
486 pItem->UpdateInfo(*match, replaceLabels);
490 if (match->m_bIsFolder)
491 pItem->SetPath(match->GetVideoInfoTag()->m_strPath);
493 pItem->SetPath(match->GetVideoInfoTag()->m_strFileNameAndPath);
494 // if we switch from a file to a folder item it means we really shouldn't be sorting files and
495 // folders separately
496 if (pItem->m_bIsFolder != match->m_bIsFolder)
498 items.SetSortIgnoreFolders(true);
499 pItem->m_bIsFolder = match->m_bIsFolder;
505 /* NOTE: Currently we GetPlayCounts on our items regardless of whether content is set
506 as if content is set, GetItemsForPaths doesn't return anything not in the content tables.
507 This code can be removed once the content tables are always filled */
508 if (!pItem->m_bIsFolder && !fetchedPlayCounts)
510 database.GetPlayCounts(items.GetPath(), items);
511 fetchedPlayCounts = true;
514 // preferably use some information from PVR info tag if available
515 if (pItem->HasPVRRecordingInfoTag())
516 pItem->GetPVRRecordingInfoTag()->CopyClientInfo(pItem->GetVideoInfoTag());
518 // set the watched overlay
519 if (pItem->IsVideo())
520 pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_playCount > 0);
525 void CGUIWindowVideoNav::UpdateButtons()
527 CGUIWindowVideoBase::UpdateButtons();
529 // Update object count
530 int iItems = m_vecItems->Size();
533 // check for parent dir and "all" items
534 // should always be the first two items
535 for (int i = 0; i <= (iItems>=2 ? 1 : 0); i++)
537 CFileItemPtr pItem = m_vecItems->Get(i);
538 if (pItem->IsParentFolder()) iItems--;
539 if (StringUtils::StartsWith(pItem->GetPath(), "/-1/")) iItems--;
542 if (m_vecItems->Size() > 2 &&
543 StringUtils::StartsWith(m_vecItems->Get(m_vecItems->Size()-1)->GetPath(), "/-1/"))
546 CStdString items = StringUtils::Format("%i %s", iItems, g_localizeStrings.Get(127).c_str());
547 SET_CONTROL_LABEL(CONTROL_LABELFILES, items);
549 // set the filter label
553 if (m_vecItems->GetPath().Equals("special://videoplaylists/"))
554 strLabel = g_localizeStrings.Get(136);
556 else if (m_vecItems->IsPlayList())
558 // get playlist name from path
560 URIUtils::Split(m_vecItems->GetPath(), strDummy, strLabel);
562 else if (m_vecItems->GetPath().Equals("sources://video/"))
563 strLabel = g_localizeStrings.Get(744);
564 // everything else is from a videodb:// path
565 else if (m_vecItems->IsVideoDb())
567 CVideoDatabaseDirectory dir;
568 dir.GetLabel(m_vecItems->GetPath(), strLabel);
571 strLabel = URIUtils::GetFileName(m_vecItems->GetPath());
573 SET_CONTROL_LABEL(CONTROL_FILTER, strLabel);
575 int watchMode = CMediaSettings::Get().GetWatchedMode(m_vecItems->GetContent());
576 SET_CONTROL_LABEL(CONTROL_BTNSHOWMODE, g_localizeStrings.Get(16100 + watchMode));
578 SET_CONTROL_SELECTED(GetID(), CONTROL_BTNSHOWALL, watchMode != WatchedModeAll);
580 SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE, g_partyModeManager.IsEnabled());
582 CONTROL_ENABLE_ON_CONDITION(CONTROL_UPDATE_LIBRARY, !m_vecItems->IsAddonsPath() && !m_vecItems->IsPlugin() && !m_vecItems->IsScript());
585 bool CGUIWindowVideoNav::GetFilteredItems(const CStdString &filter, CFileItemList &items)
587 bool listchanged = CGUIMediaWindow::GetFilteredItems(filter, items);
588 listchanged |= ApplyWatchedFilter(items);
593 /// \brief Search for names, genres, artists, directors, and plots with search string \e strSearch in the
594 /// \brief video databases and return the found \e items
595 /// \param strSearch The search string
596 /// \param items Items Found
597 void CGUIWindowVideoNav::DoSearch(const CStdString& strSearch, CFileItemList& items)
599 CFileItemList tempItems;
600 CStdString strGenre = g_localizeStrings.Get(515); // Genre
601 CStdString strActor = g_localizeStrings.Get(20337); // Actor
602 CStdString strDirector = g_localizeStrings.Get(20339); // Director
605 m_database.GetMoviesByName(strSearch, tempItems);
606 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20338) + "] ", items);
608 m_database.GetEpisodesByName(strSearch, tempItems);
609 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20359) + "] ", items);
611 m_database.GetTvShowsByName(strSearch, tempItems);
612 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20364) + "] ", items);
614 m_database.GetMusicVideosByName(strSearch, tempItems);
615 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20391) + "] ", items);
617 m_database.GetMusicVideosByAlbum(strSearch, tempItems);
618 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(558) + "] ", items);
620 // get matching genres
621 m_database.GetMovieGenresByName(strSearch, tempItems);
622 AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20342) + "] ", items);
624 m_database.GetTvShowGenresByName(strSearch, tempItems);
625 AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20343) + "] ", items);
627 m_database.GetMusicVideoGenresByName(strSearch, tempItems);
628 AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20389) + "] ", items);
631 m_database.GetMovieActorsByName(strSearch, tempItems);
632 AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20342) + "] ", items);
634 m_database.GetTvShowsActorsByName(strSearch, tempItems);
635 AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20343) + "] ", items);
637 m_database.GetMusicVideoArtistsByName(strSearch, tempItems);
638 AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20389) + "] ", items);
641 m_database.GetMovieDirectorsByName(strSearch, tempItems);
642 AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20342) + "] ", items);
644 m_database.GetTvShowsDirectorsByName(strSearch, tempItems);
645 AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20343) + "] ", items);
647 m_database.GetMusicVideoDirectorsByName(strSearch, tempItems);
648 AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20389) + "] ", items);
651 m_database.GetEpisodesByPlot(strSearch, tempItems);
652 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20365) + "] ", items);
654 m_database.GetMoviesByPlot(strSearch, tempItems);
655 AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20323) + "] ", items);
658 void CGUIWindowVideoNav::PlayItem(int iItem)
660 // unlike additemtoplaylist, we need to check the items here
661 // before calling it since the current playlist will be stopped
664 // root is not allowed
665 if (m_vecItems->IsVirtualDirectoryRoot())
668 CGUIWindowVideoBase::PlayItem(iItem);
671 void CGUIWindowVideoNav::OnInfo(CFileItem* pItem, ADDON::ScraperPtr& scraper)
673 m_database.Open(); // since we can be called from the music library without being inited
674 if (pItem->IsVideoDb())
675 scraper = m_database.GetScraperForPath(pItem->GetVideoInfoTag()->m_strPath);
678 CStdString strPath,strFile;
679 URIUtils::Split(pItem->GetPath(),strPath,strFile);
680 scraper = m_database.GetScraperForPath(strPath);
683 CGUIWindowVideoBase::OnInfo(pItem,scraper);
686 void CGUIWindowVideoNav::OnDeleteItem(CFileItemPtr pItem)
688 if (m_vecItems->IsParentFolder())
691 if (!m_vecItems->IsVideoDb() && !pItem->IsVideoDb())
693 if (!pItem->GetPath().Equals("newsmartplaylist://video") &&
694 !pItem->GetPath().Equals("special://videoplaylists/") &&
695 !pItem->GetPath().Equals("sources://video/") &&
696 !StringUtils::StartsWithNoCase(pItem->GetPath(), "newtag://"))
697 CGUIWindowVideoBase::OnDeleteItem(pItem);
699 else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "videodb://movies/sets/") &&
700 pItem->GetPath().size() > 22 && pItem->m_bIsFolder)
702 CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
703 pDialog->SetHeading(432);
704 CStdString strLabel = StringUtils::Format(g_localizeStrings.Get(433),pItem->GetLabel().c_str());
705 pDialog->SetLine(1, strLabel);
706 pDialog->SetLine(2, "");;
708 if (pDialog->IsConfirmed())
711 CDirectory::GetDirectory(pItem->GetPath(),items,"",DIR_FLAG_NO_FILE_DIRS);
712 for (int i=0;i<items.Size();++i)
713 OnDeleteItem(items[i]);
715 CVideoDatabaseDirectory dir;
717 dir.GetQueryParams(pItem->GetPath(),params);
718 m_database.DeleteSet(params.GetSetId());
721 else if (m_vecItems->GetContent() == "tags" && pItem->m_bIsFolder)
723 CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
724 pDialog->SetHeading(432);
725 CStdString strLabel = StringUtils::Format(g_localizeStrings.Get(433), pItem->GetLabel().c_str());
726 pDialog->SetLine(1, strLabel);
727 pDialog->SetLine(2, "");
729 if (pDialog->IsConfirmed())
731 CVideoDatabaseDirectory dir;
733 dir.GetQueryParams(pItem->GetPath(), params);
734 m_database.DeleteTag(params.GetTagId(), (VIDEODB_CONTENT_TYPE)params.GetContentType());
737 else if (m_vecItems->GetPath().Equals(CUtil::VideoPlaylistsLocation()) ||
738 m_vecItems->GetPath().Equals("special://videoplaylists/"))
740 pItem->m_bIsFolder = false;
741 CFileUtils::DeleteItem(pItem);
745 if (!CGUIDialogVideoInfo::DeleteVideoItem(pItem))
749 CUtil::DeleteVideoDatabaseDirectoryCache();
752 void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &buttons)
755 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
756 item = m_vecItems->Get(itemNumber);
758 CGUIWindowVideoBase::GetContextButtons(itemNumber, buttons);
760 if (item && item->GetProperty("pluginreplacecontextitems").asBoolean())
763 CVideoDatabaseDirectory dir;
764 NODE_TYPE node = dir.GetDirectoryChildType(m_vecItems->GetPath());
768 // nothing to do here
770 else if (m_vecItems->GetPath().Equals("sources://video/"))
772 // get the usual shares
773 CGUIDialogContextMenu::GetContextButtons("video", item, buttons);
774 // add scan button somewhere here
775 if (g_application.IsVideoScanning())
776 buttons.Add(CONTEXT_BUTTON_STOP_SCANNING, 13353); // Stop Scanning
777 if (!item->IsDVD() && item->GetPath() != "add" && !item->IsParentFolder() &&
778 (CProfilesManager::Get().GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser))
780 CVideoDatabase database;
782 ADDON::ScraperPtr info = database.GetScraperForPath(item->GetPath());
784 if (!g_application.IsVideoScanning())
786 if (!item->IsLiveTV() && !item->IsPlugin() && !item->IsAddonsPath() && !URIUtils::IsUPnP(item->GetPath()))
788 if (info && info->Content() != CONTENT_NONE)
789 buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20442);
791 buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20333);
795 if (info && !g_application.IsVideoScanning())
796 buttons.Add(CONTEXT_BUTTON_SCAN, 13349);
801 // are we in the playlists location?
802 bool inPlaylists = m_vecItems->GetPath().Equals(CUtil::VideoPlaylistsLocation()) ||
803 m_vecItems->GetPath().Equals("special://videoplaylists/");
805 if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_artist.empty())
807 CMusicDatabase database;
809 if (database.GetArtistByName(StringUtils::Join(item->GetVideoInfoTag()->m_artist, g_advancedSettings.m_videoItemSeparator)) > -1)
810 buttons.Add(CONTEXT_BUTTON_GO_TO_ARTIST, 20396);
812 if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_strAlbum.size() > 0)
814 CMusicDatabase database;
816 if (database.GetAlbumByName(item->GetVideoInfoTag()->m_strAlbum) > -1)
817 buttons.Add(CONTEXT_BUTTON_GO_TO_ALBUM, 20397);
819 if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_strAlbum.size() > 0 &&
820 item->GetVideoInfoTag()->m_artist.size() > 0 &&
821 item->GetVideoInfoTag()->m_strTitle.size() > 0)
823 CMusicDatabase database;
825 if (database.GetSongByArtistAndAlbumAndTitle(StringUtils::Join(item->GetVideoInfoTag()->m_artist, g_advancedSettings.m_videoItemSeparator),
826 item->GetVideoInfoTag()->m_strAlbum,
827 item->GetVideoInfoTag()->m_strTitle) > -1)
829 buttons.Add(CONTEXT_BUTTON_PLAY_OTHER, 20398);
832 if (!item->IsParentFolder())
834 ADDON::ScraperPtr info;
835 VIDEO::SScanSettings settings;
836 GetScraperForItem(item.get(), info, settings);
838 if (info && info->Content() == CONTENT_TVSHOWS)
839 buttons.Add(CONTEXT_BUTTON_INFO, item->m_bIsFolder ? 20351 : 20352);
840 else if (info && info->Content() == CONTENT_MUSICVIDEOS)
841 buttons.Add(CONTEXT_BUTTON_INFO,20393);
842 else if (info && info->Content() == CONTENT_MOVIES)
843 buttons.Add(CONTEXT_BUTTON_INFO, 13346);
845 // can we update the database?
846 if (CProfilesManager::Get().GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser)
848 if (!item->IsPlugin() && !item->IsScript() && !item->IsLiveTV() && !item->IsAddonsPath() &&
849 item->GetPath() != "sources://video/" &&
850 item->GetPath() != "special://videoplaylists/" &&
851 !StringUtils::StartsWith(item->GetPath(), "newsmartplaylist://") &&
852 !StringUtils::StartsWith(item->GetPath(), "newplaylist://") &&
853 !StringUtils::StartsWith(item->GetPath(), "newtag://"))
855 if (item->m_bIsFolder)
857 // Have both options for folders since we don't know whether all childs are watched/unwatched
858 buttons.Add(CONTEXT_BUTTON_MARK_UNWATCHED, 16104); //Mark as UnWatched
859 buttons.Add(CONTEXT_BUTTON_MARK_WATCHED, 16103); //Mark as Watched
863 if (item->GetOverlayImage().Equals("OverlayWatched.png"))
864 buttons.Add(CONTEXT_BUTTON_MARK_UNWATCHED, 16104); //Mark as UnWatched
866 buttons.Add(CONTEXT_BUTTON_MARK_WATCHED, 16103); //Mark as Watched
869 if (!g_application.IsVideoScanning() && item->IsVideoDb() && item->HasVideoInfoTag() &&
870 (item->GetVideoInfoTag()->m_type == "movie" || // movies
871 item->GetVideoInfoTag()->m_type == "tvshow" || // tvshows
872 item->GetVideoInfoTag()->m_type == "episode" || // episodes
873 item->GetVideoInfoTag()->m_type == "musicvideo" || // musicvideos
874 item->GetVideoInfoTag()->m_type == "tag" || // tags
875 item->GetVideoInfoTag()->m_type == "set")) // sets
877 buttons.Add(CONTEXT_BUTTON_EDIT, 16106);
879 if (node == NODE_TYPE_TITLE_TVSHOWS)
881 if (g_application.IsVideoScanning())
882 buttons.Add(CONTEXT_BUTTON_STOP_SCANNING, 13353);
884 buttons.Add(CONTEXT_BUTTON_SCAN, 13349);
887 if (node == NODE_TYPE_SEASONS && item->m_bIsFolder)
888 buttons.Add(CONTEXT_BUTTON_SET_SEASON_ART, 13511);
890 if (node == NODE_TYPE_ACTOR && !dir.IsAllItem(item->GetPath()) && item->m_bIsFolder)
892 if (StringUtils::StartsWithNoCase(m_vecItems->GetPath(), "videodb://musicvideos")) // mvids
893 buttons.Add(CONTEXT_BUTTON_SET_ARTIST_THUMB, 13359);
895 buttons.Add(CONTEXT_BUTTON_SET_ACTOR_THUMB, 20403);
899 if (!m_vecItems->IsVideoDb() && !m_vecItems->IsVirtualDirectoryRoot())
900 { // non-video db items, file operations are allowed
901 if ((CSettings::Get().GetBool("filelists.allowfiledeletion") &&
902 CUtil::SupportsWriteFileOperations(item->GetPath())) ||
903 (inPlaylists && !URIUtils::GetFileName(item->GetPath()).Equals("PartyMode-Video.xsp")
904 && (item->IsPlayList() || item->IsSmartPlayList())))
906 buttons.Add(CONTEXT_BUTTON_DELETE, 117);
907 buttons.Add(CONTEXT_BUTTON_RENAME, 118);
909 // add "Set/Change content" to folders
910 if (item->m_bIsFolder && !item->IsVideoDb() && !item->IsPlayList() && !item->IsSmartPlayList() && !item->IsLibraryFolder() && !item->IsLiveTV() && !item->IsPlugin() && !item->IsAddonsPath() && !URIUtils::IsUPnP(item->GetPath()))
912 if (!g_application.IsVideoScanning())
914 if (info && info->Content() != CONTENT_NONE)
916 buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20442);
917 buttons.Add(CONTEXT_BUTTON_SCAN, 13349);
920 buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20333);
924 if (item->IsPlugin() || item->IsScript() || m_vecItems->IsPlugin())
925 buttons.Add(CONTEXT_BUTTON_PLUGIN_SETTINGS, 1045);
930 bool CGUIWindowVideoNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
933 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
934 item = m_vecItems->Get(itemNumber);
935 if (CGUIDialogContextMenu::OnContextButton("video", item, button))
937 //TODO should we search DB for entries from plugins?
938 if (button == CONTEXT_BUTTON_REMOVE_SOURCE && !item->IsPlugin()
939 && !item->IsLiveTV() &&!item->IsRSS() && !URIUtils::IsUPnP(item->GetPath()))
941 OnUnAssignContent(item->GetPath(),20375,20340,20341);
948 case CONTEXT_BUTTON_EDIT:
950 CONTEXT_BUTTON ret = (CONTEXT_BUTTON)CGUIDialogVideoInfo::ManageVideoItem(item);
953 if (ret == CONTEXT_BUTTON_MARK_WATCHED)
954 m_viewControl.SetSelectedItem(itemNumber + 1);
961 case CONTEXT_BUTTON_SET_SEASON_ART:
962 case CONTEXT_BUTTON_SET_ACTOR_THUMB:
963 case CONTEXT_BUTTON_SET_ARTIST_THUMB:
965 std::string type = "season";
966 if (button == CONTEXT_BUTTON_SET_ACTOR_THUMB)
968 else if (button == CONTEXT_BUTTON_SET_ARTIST_THUMB)
971 bool result = CGUIDialogVideoInfo::ManageVideoItemArtwork(m_vecItems->Get(itemNumber), type);
976 case CONTEXT_BUTTON_GO_TO_ARTIST:
979 CMusicDatabase database;
981 strPath = StringUtils::Format("musicdb://artists/%ld/",
982 database.GetArtistByName(StringUtils::Join(m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_artist, g_advancedSettings.m_videoItemSeparator)));
983 g_windowManager.ActivateWindow(WINDOW_MUSIC_NAV,strPath);
986 case CONTEXT_BUTTON_GO_TO_ALBUM:
989 CMusicDatabase database;
991 strPath = StringUtils::Format("musicdb://albums/%ld/",
992 database.GetAlbumByName(m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_strAlbum));
993 g_windowManager.ActivateWindow(WINDOW_MUSIC_NAV,strPath);
996 case CONTEXT_BUTTON_PLAY_OTHER:
998 CMusicDatabase database;
1001 if (database.GetSong(database.GetSongByArtistAndAlbumAndTitle(StringUtils::Join(m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_artist, g_advancedSettings.m_videoItemSeparator),m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_strAlbum,
1002 m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_strTitle),
1005 CApplicationMessenger::Get().PlayFile(song);
1014 return CGUIWindowVideoBase::OnContextButton(itemNumber, button);
1017 bool CGUIWindowVideoNav::OnClick(int iItem)
1019 CFileItemPtr item = m_vecItems->Get(iItem);
1020 if (!item->m_bIsFolder && item->IsVideoDb() && !item->Exists())
1022 CLog::Log(LOGDEBUG, "%s called on '%s' but file doesn't exist", __FUNCTION__, item->GetPath().c_str());
1023 if (!CGUIDialogVideoInfo::DeleteVideoItemFromDatabase(item, true))
1028 m_viewControl.SetSelectedItem(iItem);
1031 else if (StringUtils::StartsWithNoCase(item->GetPath(), "newtag://"))
1033 // dont allow update while scanning
1034 if (g_application.IsVideoScanning())
1036 CGUIDialogOK::ShowAndGetInput(257, 0, 14057, 0);
1042 if (!CGUIKeyboardFactory::ShowAndGetInput(strTag, g_localizeStrings.Get(20462), false))
1045 CVideoDatabase videodb;
1046 if (!videodb.Open())
1049 // get the media type and convert from plural to singular (by removing the trailing "s")
1050 CStdString mediaType = item->GetPath().substr(9);
1051 mediaType = mediaType.substr(0, mediaType.size() - 1);
1052 CStdString localizedType = CGUIDialogVideoInfo::GetLocalizedVideoType(mediaType);
1053 if (localizedType.empty())
1056 if (!videodb.GetSingleValue("tag", "tag.idTag", videodb.PrepareSQL("tag.strTag = '%s' AND tag.idTag IN (SELECT taglinks.idTag FROM taglinks WHERE taglinks.media_type = '%s')", strTag.c_str(), mediaType.c_str())).empty())
1058 CStdString strError = StringUtils::Format(g_localizeStrings.Get(20463), strTag.c_str());
1059 CGUIDialogOK::ShowAndGetInput(20462, "", strError, "");
1063 int idTag = videodb.AddTag(strTag);
1064 CFileItemList items;
1065 CStdString strLabel = StringUtils::Format(g_localizeStrings.Get(20464), localizedType.c_str());
1066 if (CGUIDialogVideoInfo::GetItemsForTag(strLabel, mediaType, items, idTag))
1068 for (int index = 0; index < items.Size(); index++)
1070 if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0)
1073 videodb.AddTagToItem(items[index]->GetVideoInfoTag()->m_iDbId, idTag, mediaType);
1081 return CGUIWindowVideoBase::OnClick(iItem);
1084 CStdString CGUIWindowVideoNav::GetStartFolder(const CStdString &dir)
1086 if (dir.Equals("MovieGenres"))
1087 return "videodb://movies/genres/";
1088 else if (dir.Equals("MovieTitles"))
1089 return "videodb://movies/titles/";
1090 else if (dir.Equals("MovieYears"))
1091 return "videodb://movies/years/";
1092 else if (dir.Equals("MovieActors"))
1093 return "videodb://movies/actors/";
1094 else if (dir.Equals("MovieDirectors"))
1095 return "videodb://movies/directors/";
1096 else if (dir.Equals("MovieStudios"))
1097 return "videodb://movies/studios/";
1098 else if (dir.Equals("MovieSets"))
1099 return "videodb://movies/sets/";
1100 else if (dir.Equals("MovieCountries"))
1101 return "videodb://movies/countries/";
1102 else if (dir.Equals("MovieTags"))
1103 return "videodb://movies/tags/";
1104 else if (dir.Equals("Movies"))
1105 return "videodb://movies/";
1106 else if (dir.Equals("TvShowGenres"))
1107 return "videodb://tvshows/genres/";
1108 else if (dir.Equals("TvShowTitles"))
1109 return "videodb://tvshows/titles/";
1110 else if (dir.Equals("TvShowYears"))
1111 return "videodb://tvshows/years/";
1112 else if (dir.Equals("TvShowActors"))
1113 return "videodb://tvshows/actors/";
1114 else if (dir.Equals("TvShowStudios"))
1115 return "videodb://tvshows/studios/";
1116 else if (dir.Equals("TvShowTags"))
1117 return "videodb://tvshows/tags/";
1118 else if (dir.Equals("TvShows"))
1119 return "videodb://tvshows/";
1120 else if (dir.Equals("MusicVideoGenres"))
1121 return "videodb://musicvideos/genres/";
1122 else if (dir.Equals("MusicVideoTitles"))
1123 return "videodb://musicvideos/titles/";
1124 else if (dir.Equals("MusicVideoYears"))
1125 return "videodb://musicvideos/years/";
1126 else if (dir.Equals("MusicVideoArtists"))
1127 return "videodb://musicvideos/artists/";
1128 else if (dir.Equals("MusicVideoAlbums"))
1129 return "videodb://musicvideos/albums/";
1130 else if (dir.Equals("MusicVideoDirectors"))
1131 return "videodb://musicvideos/directors/";
1132 else if (dir.Equals("MusicVideoStudios"))
1133 return "videodb://musicvideos/studios/";
1134 else if (dir.Equals("MusicVideoTags"))
1135 return "videodb://musicvideos/tags/";
1136 else if (dir.Equals("MusicVideos"))
1137 return "videodb://musicvideos/";
1138 else if (dir.Equals("RecentlyAddedMovies"))
1139 return "videodb://recentlyaddedmovies/";
1140 else if (dir.Equals("RecentlyAddedEpisodes"))
1141 return "videodb://recentlyaddedepisodes/";
1142 else if (dir.Equals("RecentlyAddedMusicVideos"))
1143 return "videodb://recentlyaddedmusicvideos/";
1144 else if (dir.Equals("Files"))
1145 return "sources://video/";
1146 return CGUIWindowVideoBase::GetStartFolder(dir);
1149 bool CGUIWindowVideoNav::ApplyWatchedFilter(CFileItemList &items)
1151 bool listchanged = false;
1152 CVideoDatabaseDirectory dir;
1153 NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath());
1155 // now filter watched items as necessary
1156 bool filterWatched=false;
1157 if (node == NODE_TYPE_EPISODES
1158 || node == NODE_TYPE_SEASONS
1159 || node == NODE_TYPE_SETS
1160 || node == NODE_TYPE_TAGS
1161 || node == NODE_TYPE_TITLE_MOVIES
1162 || node == NODE_TYPE_TITLE_TVSHOWS
1163 || node == NODE_TYPE_TITLE_MUSICVIDEOS
1164 || node == NODE_TYPE_RECENTLY_ADDED_EPISODES
1165 || node == NODE_TYPE_RECENTLY_ADDED_MOVIES
1166 || node == NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS)
1167 filterWatched = true;
1168 if (!items.IsVideoDb())
1169 filterWatched = true;
1170 if (items.GetContent() == "tvshows" &&
1171 (items.IsSmartPlayList() || items.IsLibraryFolder()))
1172 node = NODE_TYPE_TITLE_TVSHOWS; // so that the check below works
1174 int watchMode = CMediaSettings::Get().GetWatchedMode(m_vecItems->GetContent());
1176 for (int i = 0; i < items.Size(); i++)
1178 CFileItemPtr item = items.Get(i);
1180 if(item->HasVideoInfoTag() && (node == NODE_TYPE_TITLE_TVSHOWS || node == NODE_TYPE_SEASONS))
1182 if (watchMode == WatchedModeUnwatched)
1183 item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("unwatchedepisodes").asInteger();
1184 if (watchMode == WatchedModeWatched)
1185 item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("watchedepisodes").asInteger();
1186 if (watchMode == WatchedModeAll)
1187 item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("totalepisodes").asInteger();
1188 item->SetProperty("numepisodes", item->GetVideoInfoTag()->m_iEpisode);
1194 if((watchMode==WatchedModeWatched && item->GetVideoInfoTag()->m_playCount== 0)
1195 || (watchMode==WatchedModeUnwatched && item->GetVideoInfoTag()->m_playCount > 0))
1204 if(node == NODE_TYPE_TITLE_TVSHOWS || node == NODE_TYPE_SEASONS)
1206 // the watched filter may change the "numepisodes" property which is reflected in the TV_SHOWS and SEASONS nodes
1207 // therefore, the items labels have to be refreshed, and possibly the list needs resorting as well.
1208 items.ClearSortState(); // this is needed to force resorting even if sort method did not change
1209 FormatAndSort(items);