diff options
Diffstat (limited to 'xbmc/video/windows/GUIWindowVideoNav.cpp')
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoNav.cpp | 1171 |
1 files changed, 1171 insertions, 0 deletions
diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp new file mode 100644 index 0000000..d53c105 --- /dev/null +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -0,0 +1,1171 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowVideoNav.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "PartyModeManager.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "dialogs/GUIDialogMediaSource.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/Directory.h" +#include "filesystem/VideoDatabaseDirectory.h" +#include "filesystem/VideoDatabaseFile.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicDatabase.h" +#include "profiles/ProfileManager.h" +#include "pvr/recordings/PVRRecording.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDbUrl.h" +#include "video/VideoInfoScanner.h" +#include "video/VideoLibraryQueue.h" +#include "video/dialogs/GUIDialogVideoInfo.h" +#include "view/GUIViewState.h" + +#include <utility> + +using namespace XFILE; +using namespace VIDEODATABASEDIRECTORY; +using namespace KODI::MESSAGING; + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_BTNSEARCH 8 +#define CONTROL_LABELFILES 12 + +#define CONTROL_BTN_FILTER 19 +#define CONTROL_BTNSHOWMODE 10 +#define CONTROL_BTNSHOWALL 14 +#define CONTROL_UNLOCK 11 + +#define CONTROL_FILTER 15 +#define CONTROL_BTNPARTYMODE 16 +#define CONTROL_LABELEMPTY 18 + +#define CONTROL_UPDATE_LIBRARY 20 + +CGUIWindowVideoNav::CGUIWindowVideoNav(void) + : CGUIWindowVideoBase(WINDOW_VIDEO_NAV, "MyVideoNav.xml") +{ + m_thumbLoader.SetObserver(this); +} + +CGUIWindowVideoNav::~CGUIWindowVideoNav(void) = default; + +bool CGUIWindowVideoNav::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_TOGGLE_WATCHED) + { + CFileItemPtr pItem = m_vecItems->Get(m_viewControl.GetSelectedItem()); + if (pItem->IsParentFolder()) + return false; + + if (pItem && pItem->HasVideoInfoTag()) + { + CVideoLibraryQueue::GetInstance().MarkAsWatched(pItem, pItem->GetVideoInfoTag()->GetPlayCount() == 0); + return true; + } + } + return CGUIWindowVideoBase::OnAction(action); +} + +bool CGUIWindowVideoNav::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_RESET: + m_vecItems->SetPath(""); + break; + case GUI_MSG_WINDOW_DEINIT: + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + break; + case GUI_MSG_WINDOW_INIT: + { + /* We don't want to show Autosourced items (ie removable pendrives, memorycards) in Library mode */ + m_rootDir.AllowNonLocalSources(false); + + SetProperty("flattened", CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN)); + if (message.GetNumStringParams() && StringUtils::EqualsNoCase(message.GetStringParam(0), "Files") && + CMediaSourceSettings::GetInstance().GetSources("video")->empty()) + { + message.SetStringParam(""); + } + + if (!CGUIWindowVideoBase::OnMessage(message)) + return false; + + + if (message.GetStringParam(0) != "") + { + CURL url(message.GetStringParam(0)); + + int i = 0; + for (; i < m_vecItems->Size(); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + + // skip ".." + if (pItem->IsParentFolder()) + continue; + + if (URIUtils::PathEquals(pItem->GetPath(), message.GetStringParam(0), true, true)) + { + m_viewControl.SetSelectedItem(i); + i = -1; + if (url.GetOption("showinfo") == "true") + { + ADDON::ScraperPtr scrapper; + OnItemInfo(*pItem, scrapper); + } + break; + } + } + if (i >= m_vecItems->Size()) + { + SelectFirstUnwatched(); + + if (url.GetOption("showinfo") == "true") + { + // We are here if the item is filtered out in the nav window + const std::string& path = message.GetStringParam(0); + CFileItem item(path, URIUtils::HasSlashAtEnd(path)); + if (item.IsVideoDb()) + { + *(item.GetVideoInfoTag()) = XFILE::CVideoDatabaseFile::GetVideoTag(CURL(item.GetPath())); + if (!item.GetVideoInfoTag()->IsEmpty()) + { + item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath); + ADDON::ScraperPtr scrapper; + OnItemInfo(item, scrapper); + } + } + } + } + } + else + { + // This needs to be done again, because the initialization of CGUIWindow overwrites it with default values + // Mostly affects cases where GUIWindowVideoNav is constructed and we're already in a show, e.g. entering from the homescreen + SelectFirstUnwatched(); + } + + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTNPARTYMODE) + { + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + else + { + if (!g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO)) + { + SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE,false); + return false; + } + + // Playlist directory is the root of the playlist window + if (m_guiState) + m_guiState->SetPlaylistDirectory("playlistvideo://"); + + return true; + } + UpdateButtons(); + } + + if (iControl == CONTROL_BTNSEARCH) + { + OnSearch(); + } + else if (iControl == CONTROL_BTNSHOWMODE) + { + CMediaSettings::GetInstance().CycleWatchedMode(m_vecItems->GetContent()); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + OnFilterItems(GetProperty("filter").asString()); + UpdateButtons(); + return true; + } + else if (iControl == CONTROL_BTNSHOWALL) + { + if (CMediaSettings::GetInstance().GetWatchedMode(m_vecItems->GetContent()) == WatchedModeAll) + CMediaSettings::GetInstance().SetWatchedMode(m_vecItems->GetContent(), WatchedModeUnwatched); + else + CMediaSettings::GetInstance().SetWatchedMode(m_vecItems->GetContent(), WatchedModeAll); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + OnFilterItems(GetProperty("filter").asString()); + UpdateButtons(); + return true; + } + else if (iControl == CONTROL_UPDATE_LIBRARY) + { + if (!CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + OnScan(""); + else + CVideoLibraryQueue::GetInstance().StopLibraryScanning(); + return true; + } + } + break; + // update the display + case GUI_MSG_REFRESH_THUMBS: + Refresh(); + break; + } + return CGUIWindowVideoBase::OnMessage(message); +} + +SelectFirstUnwatchedItem CGUIWindowVideoNav::GetSettingSelectFirstUnwatchedItem() +{ + if (m_vecItems->IsVideoDb()) + { + NODE_TYPE nodeType = CVideoDatabaseDirectory::GetDirectoryChildType(m_vecItems->GetPath()); + + if (nodeType == NODE_TYPE_SEASONS || nodeType == NODE_TYPE_EPISODES) + { + int iValue = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOLIBRARY_TVSHOWSSELECTFIRSTUNWATCHEDITEM); + if (iValue >= SelectFirstUnwatchedItem::NEVER && iValue <= SelectFirstUnwatchedItem::ALWAYS) + return (SelectFirstUnwatchedItem)iValue; + } + } + + return SelectFirstUnwatchedItem::NEVER; +} + +IncludeAllSeasonsAndSpecials CGUIWindowVideoNav::GetSettingIncludeAllSeasonsAndSpecials() +{ + int iValue = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOLIBRARY_TVSHOWSINCLUDEALLSEASONSANDSPECIALS); + if (iValue >= IncludeAllSeasonsAndSpecials::NEITHER && iValue <= IncludeAllSeasonsAndSpecials::SPECIALS) + return (IncludeAllSeasonsAndSpecials)iValue; + + return IncludeAllSeasonsAndSpecials::NEITHER; +} + +int CGUIWindowVideoNav::GetFirstUnwatchedItemIndex(bool includeAllSeasons, bool includeSpecials) +{ + int iIndex = 0; + int iUnwatchedSeason = INT_MAX; + int iUnwatchedEpisode = INT_MAX; + NODE_TYPE nodeType = CVideoDatabaseDirectory::GetDirectoryChildType(m_vecItems->GetPath()); + + // Run through the list of items and find the first unwatched season/episode + for (int i = 0; i < m_vecItems->Size(); ++i) + { + CFileItemPtr pItem = m_vecItems->Get(i); + if (pItem->IsParentFolder() || !pItem->HasVideoInfoTag()) + continue; + + CVideoInfoTag *pTag = pItem->GetVideoInfoTag(); + + if ((!includeAllSeasons && pTag->m_iSeason < 0) || (!includeSpecials && pTag->m_iSeason == 0)) + continue; + + // Use the special sort values if they're available + int iSeason = pTag->m_iSpecialSortSeason >= 0 ? pTag->m_iSpecialSortSeason : pTag->m_iSeason; + int iEpisode = pTag->m_iSpecialSortEpisode >= 0 ? pTag->m_iSpecialSortEpisode : pTag->m_iEpisode; + + if (nodeType == NODE_TYPE::NODE_TYPE_SEASONS) + { + // Is the season unwatched, and is its season number lower than the currently identified + // first unwatched season + if (pTag->GetPlayCount() == 0 && iSeason < iUnwatchedSeason) + { + iUnwatchedSeason = iSeason; + iIndex = i; + } + } + + if (nodeType == NODE_TYPE::NODE_TYPE_EPISODES) + { + // Is the episode unwatched, and is its season number lower + // or is its episode number lower within the current season + if (pTag->GetPlayCount() == 0 && (iSeason < iUnwatchedSeason || (iSeason == iUnwatchedSeason && iEpisode < iUnwatchedEpisode))) + { + iUnwatchedSeason = iSeason; + iUnwatchedEpisode = iEpisode; + iIndex = i; + } + } + } + + return iIndex; +} + +bool CGUIWindowVideoNav::Update(const std::string &strDirectory, bool updateFilterPath /* = true */) +{ + if (!CGUIWindowVideoBase::Update(strDirectory, updateFilterPath)) + return false; + + SelectFirstUnwatched(); + + return true; +} + +void CGUIWindowVideoNav::SelectFirstUnwatched() { + // Check if we should select the first unwatched item + SelectFirstUnwatchedItem selectFirstUnwatched = GetSettingSelectFirstUnwatchedItem(); + if (selectFirstUnwatched != SelectFirstUnwatchedItem::NEVER) + { + bool bIsItemSelected = (m_viewControl.GetSelectedItem() > 0); + + if (selectFirstUnwatched == SelectFirstUnwatchedItem::ALWAYS || + (selectFirstUnwatched == SelectFirstUnwatchedItem::ON_FIRST_ENTRY && !bIsItemSelected)) + { + IncludeAllSeasonsAndSpecials incAllSeasonsSpecials = GetSettingIncludeAllSeasonsAndSpecials(); + + bool bIncludeAllSeasons = (incAllSeasonsSpecials == IncludeAllSeasonsAndSpecials::BOTH || incAllSeasonsSpecials == IncludeAllSeasonsAndSpecials::ALL_SEASONS); + bool bIncludeSpecials = (incAllSeasonsSpecials == IncludeAllSeasonsAndSpecials::BOTH || incAllSeasonsSpecials == IncludeAllSeasonsAndSpecials::SPECIALS); + + int iIndex = GetFirstUnwatchedItemIndex(bIncludeAllSeasons, bIncludeSpecials); + m_viewControl.SetSelectedItem(iIndex); + } + } +} + +bool CGUIWindowVideoNav::GetDirectory(const std::string &strDirectory, CFileItemList &items) +{ + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + + items.ClearArt(); + items.ClearProperties(); + + bool bResult = CGUIWindowVideoBase::GetDirectory(strDirectory, items); + if (bResult) + { + if (items.IsVideoDb()) + { + XFILE::CVideoDatabaseDirectory dir; + CQueryParams params; + dir.GetQueryParams(items.GetPath(),params); + VIDEODATABASEDIRECTORY::NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); + + int iFlatten = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOLIBRARY_FLATTENTVSHOWS); + int itemsSize = items.GetObjectCount(); + int firstIndex = items.Size() - itemsSize; + + // perform the flattening logic for tvshows with a single (unwatched) season (+ optional special season) + if (node == NODE_TYPE_SEASONS && !items.IsEmpty()) + { + // check if the last item is the "All seasons" item which should be ignored for flattening + if (!items[items.Size() - 1]->HasVideoInfoTag() || items[items.Size() - 1]->GetVideoInfoTag()->m_iSeason < 0) + itemsSize -= 1; + + bool bFlatten = (itemsSize == 1 && iFlatten == 1) || iFlatten == 2 || // flatten if one one season or if always flatten is enabled + (itemsSize == 2 && iFlatten == 1 && // flatten if one season + specials + (items[firstIndex]->GetVideoInfoTag()->m_iSeason == 0 || items[firstIndex + 1]->GetVideoInfoTag()->m_iSeason == 0)); + + if (iFlatten > 0 && !bFlatten && (WatchedMode)CMediaSettings::GetInstance().GetWatchedMode("tvshows") == WatchedModeUnwatched) + { + int count = 0; + for(int i = 0; i < items.Size(); i++) + { + const CFileItemPtr item = items.Get(i); + if (item->GetProperty("unwatchedepisodes").asInteger() != 0 && item->GetVideoInfoTag()->m_iSeason > 0) + count++; + } + bFlatten = (count < 2); // flatten if there is only 1 unwatched season (not counting specials) + } + + if (bFlatten) + { // flatten if one season or flatten always + items.Clear(); + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(items.GetPath())) + return false; + + videoUrl.AppendPath("-2/"); + return GetDirectory(videoUrl.ToString(), items); + } + } + + if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_EPISODES || + node == NODE_TYPE_SEASONS || + node == NODE_TYPE_RECENTLY_ADDED_EPISODES) + { + CLog::Log(LOGDEBUG, "WindowVideoNav::GetDirectory"); + // grab the show thumb + CVideoInfoTag details; + m_database.GetTvShowInfo("", details, params.GetTvShowId()); + std::map<std::string, std::string> art; + if (m_database.GetArtForItem(details.m_iDbId, details.m_type, art)) + { + items.AppendArt(art, details.m_type); + items.SetArtFallback("fanart", "tvshow.fanart"); + if (node == NODE_TYPE_SEASONS) + { // set an art fallback for "thumb" + if (items.HasArt("tvshow.poster")) + items.SetArtFallback("thumb", "tvshow.poster"); + else if (items.HasArt("tvshow.banner")) + items.SetArtFallback("thumb", "tvshow.banner"); + } + } + + // Grab fanart data + items.SetProperty("fanart_color1", details.m_fanart.GetColor(0)); + items.SetProperty("fanart_color2", details.m_fanart.GetColor(1)); + items.SetProperty("fanart_color3", details.m_fanart.GetColor(2)); + + // save the show description (showplot) + items.SetProperty("showplot", details.m_strPlot); + items.SetProperty("showtitle", details.m_strShowTitle); + + // the container folder thumb is the parent (i.e. season or show) + if (itemsSize && (node == NODE_TYPE_EPISODES || node == NODE_TYPE_RECENTLY_ADDED_EPISODES)) + { + int seasonID = -1; + int seasonParam = params.GetSeason(); + + // grab all season art when flatten always + if (seasonParam == -2 && iFlatten == 2) + seasonParam = -1; + + if (seasonParam >= -1) + seasonID = m_database.GetSeasonId(details.m_iDbId, seasonParam); + else + seasonID = items[firstIndex]->GetVideoInfoTag()->m_iIdSeason; + + CGUIListItem::ArtMap seasonArt; + if (seasonID > -1 && m_database.GetArtForItem(seasonID, MediaTypeSeason, seasonArt)) + { + items.AppendArt(seasonArt, MediaTypeSeason); + // set an art fallback for "thumb" + if (items.HasArt("season.poster")) + items.SetArtFallback("thumb", "season.poster"); + else if (items.HasArt("season.banner")) + items.SetArtFallback("thumb", "season.banner"); + } + } + } + else if (node == NODE_TYPE_TITLE_MOVIES || + node == NODE_TYPE_RECENTLY_ADDED_MOVIES) + { + if (params.GetSetId() > 0) + { + CGUIListItem::ArtMap setArt; + if (m_database.GetArtForItem(params.GetSetId(), MediaTypeVideoCollection, setArt)) + { + items.AppendArt(setArt, MediaTypeVideoCollection); + items.SetArtFallback("fanart", "set.fanart"); + if (items.HasArt("set.poster")) + items.SetArtFallback("thumb", "set.poster"); + } + } + } + } + else if (URIUtils::PathEquals(items.GetPath(), "special://videoplaylists/")) + items.SetContent("playlists"); + else if (!items.IsVirtualDirectoryRoot()) + { // load info from the database + std::string label; + if (items.GetLabel().empty() && m_rootDir.IsSource(items.GetPath(), CMediaSourceSettings::GetInstance().GetSources("video"), &label)) + items.SetLabel(label); + if (!items.IsSourcesPath() && !items.IsLibraryFolder()) + LoadVideoInfo(items, m_database); + } + + CVideoDbUrl videoUrl; + if (videoUrl.FromString(items.GetPath()) && items.GetContent() == "tags" && + !items.Contains("newtag://" + videoUrl.GetType())) + { + CFileItemPtr newTag(new CFileItem("newtag://" + videoUrl.GetType(), false)); + newTag->SetLabel(g_localizeStrings.Get(20462)); + newTag->SetLabelPreformatted(true); + newTag->SetSpecialSort(SortSpecialOnTop); + items.Add(newTag); + } + } + return bResult; +} + +void CGUIWindowVideoNav::UpdateButtons() +{ + CGUIWindowVideoBase::UpdateButtons(); + + // Update object count + int iItems = m_vecItems->Size(); + if (iItems) + { + // check for parent dir and "all" items + // should always be the first two items + for (int i = 0; i <= (iItems>=2 ? 1 : 0); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + if (pItem->IsParentFolder()) iItems--; + if (StringUtils::StartsWith(pItem->GetPath(), "/-1/")) iItems--; + } + // or the last item + if (m_vecItems->Size() > 2 && + StringUtils::StartsWith(m_vecItems->Get(m_vecItems->Size()-1)->GetPath(), "/-1/")) + iItems--; + } + std::string items = StringUtils::Format("{} {}", iItems, g_localizeStrings.Get(127)); + SET_CONTROL_LABEL(CONTROL_LABELFILES, items); + + // set the filter label + std::string strLabel; + + // "Playlists" + if (m_vecItems->IsPath("special://videoplaylists/")) + strLabel = g_localizeStrings.Get(136); + // "{Playlist Name}" + else if (m_vecItems->IsPlayList()) + { + // get playlist name from path + std::string strDummy; + URIUtils::Split(m_vecItems->GetPath(), strDummy, strLabel); + } + else if (m_vecItems->IsPath("sources://video/")) + strLabel = g_localizeStrings.Get(744); + // everything else is from a videodb:// path + else if (m_vecItems->IsVideoDb()) + { + CVideoDatabaseDirectory dir; + dir.GetLabel(m_vecItems->GetPath(), strLabel); + } + else + strLabel = URIUtils::GetFileName(m_vecItems->GetPath()); + + SET_CONTROL_LABEL(CONTROL_FILTER, strLabel); + + int watchMode = CMediaSettings::GetInstance().GetWatchedMode(m_vecItems->GetContent()); + SET_CONTROL_LABEL(CONTROL_BTNSHOWMODE, g_localizeStrings.Get(16100 + watchMode)); + + SET_CONTROL_SELECTED(GetID(), CONTROL_BTNSHOWALL, watchMode != WatchedModeAll); + + SET_CONTROL_SELECTED(GetID(),CONTROL_BTNPARTYMODE, g_partyModeManager.IsEnabled()); + + CONTROL_ENABLE_ON_CONDITION(CONTROL_UPDATE_LIBRARY, !m_vecItems->IsAddonsPath() && !m_vecItems->IsPlugin() && !m_vecItems->IsScript()); +} + +bool CGUIWindowVideoNav::GetFilteredItems(const std::string &filter, CFileItemList &items) +{ + bool listchanged = CGUIMediaWindow::GetFilteredItems(filter, items); + listchanged |= ApplyWatchedFilter(items); + + return listchanged; +} + +/// \brief Search for names, genres, artists, directors, and plots with search string \e strSearch in the +/// \brief video databases and return the found \e items +/// \param strSearch The search string +/// \param items Items Found +void CGUIWindowVideoNav::DoSearch(const std::string& strSearch, CFileItemList& items) +{ + CFileItemList tempItems; + const std::string& strGenre = g_localizeStrings.Get(515); // Genre + const std::string& strActor = g_localizeStrings.Get(20337); // Actor + const std::string& strDirector = g_localizeStrings.Get(20339); // Director + + //get matching names + m_database.GetMoviesByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20338) + "] ", items); + + m_database.GetEpisodesByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20359) + "] ", items); + + m_database.GetTvShowsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20364) + "] ", items); + + m_database.GetMusicVideosByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20391) + "] ", items); + + m_database.GetMusicVideosByAlbum(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(558) + "] ", items); + + // get matching genres + m_database.GetMovieGenresByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20342) + "] ", items); + + m_database.GetTvShowGenresByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20343) + "] ", items); + + m_database.GetMusicVideoGenresByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20389) + "] ", items); + + //get actors/artists + m_database.GetMovieActorsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20342) + "] ", items); + + m_database.GetTvShowsActorsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20343) + "] ", items); + + m_database.GetMusicVideoArtistsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20389) + "] ", items); + + //directors + m_database.GetMovieDirectorsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20342) + "] ", items); + + m_database.GetTvShowsDirectorsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20343) + "] ", items); + + m_database.GetMusicVideoDirectorsByName(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20389) + "] ", items); + + //plot + m_database.GetEpisodesByPlot(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20365) + "] ", items); + + m_database.GetMoviesByPlot(strSearch, tempItems); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20323) + "] ", items); +} + +void CGUIWindowVideoNav::PlayItem(int iItem) +{ + // unlike additemtoplaylist, we need to check the items here + // before calling it since the current playlist will be stopped + // and cleared! + + // root is not allowed + if (m_vecItems->IsVirtualDirectoryRoot()) + return; + + CGUIWindowVideoBase::PlayItem(iItem); +} + +bool CGUIWindowVideoNav::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper) +{ + if (!scraper || scraper->Content() == CONTENT_NONE) + { + m_database.Open(); // since we can be called from the music library without being inited + if (fileItem.IsVideoDb()) + scraper = m_database.GetScraperForPath(fileItem.GetVideoInfoTag()->m_strPath); + else + { + std::string strPath,strFile; + URIUtils::Split(fileItem.GetPath(),strPath,strFile); + scraper = m_database.GetScraperForPath(strPath); + } + m_database.Close(); + } + return CGUIWindowVideoBase::OnItemInfo(fileItem, scraper); +} + +void CGUIWindowVideoNav::OnDeleteItem(const CFileItemPtr& pItem) +{ + if (m_vecItems->IsParentFolder()) + return; + + if (!m_vecItems->IsVideoDb() && !pItem->IsVideoDb()) + { + if (!pItem->IsPath("newsmartplaylist://video") && + !pItem->IsPath("special://videoplaylists/") && + !pItem->IsPath("sources://video/") && + !URIUtils::IsProtocol(pItem->GetPath(), "newtag")) + CGUIWindowVideoBase::OnDeleteItem(pItem); + } + else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "videodb://movies/sets/") && + pItem->GetPath().size() > 22 && pItem->m_bIsFolder) + { + CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO); + + if (!pDialog) + return; + + pDialog->SetHeading(CVariant{432}); + std::string strLabel = StringUtils::Format(g_localizeStrings.Get(433), pItem->GetLabel()); + pDialog->SetLine(1, CVariant{std::move(strLabel)}); + pDialog->SetLine(2, CVariant{""}); + pDialog->Open(); + if (pDialog->IsConfirmed()) + { + CFileItemList items; + CDirectory::GetDirectory(pItem->GetPath(),items,"",DIR_FLAG_NO_FILE_DIRS); + for (int i=0;i<items.Size();++i) + OnDeleteItem(items[i]); + + CVideoDatabaseDirectory dir; + CQueryParams params; + dir.GetQueryParams(pItem->GetPath(),params); + m_database.DeleteSet(params.GetSetId()); + } + } + else if (m_vecItems->IsPath(CUtil::VideoPlaylistsLocation()) || + m_vecItems->IsPath("special://videoplaylists/")) + { + pItem->m_bIsFolder = false; + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui && gui->ConfirmDelete(pItem->GetPath())) + CFileUtils::DeleteItem(pItem); + } + else + { + if (!CGUIDialogVideoInfo::DeleteVideoItem(pItem)) + return; + } + int itemNumber = m_viewControl.GetSelectedItem(); + int select = itemNumber >= m_vecItems->Size()-1 ? itemNumber-1 : itemNumber; + m_viewControl.SetSelectedItem(select); + + CUtil::DeleteVideoDatabaseDirectoryCache(); +} + +void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + + CGUIWindowVideoBase::GetContextButtons(itemNumber, buttons); + + CVideoDatabaseDirectory dir; + NODE_TYPE node = dir.GetDirectoryChildType(m_vecItems->GetPath()); + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (!item) + { + // nothing to do here + } + else if (m_vecItems->IsPath("sources://video/")) + { + // get the usual shares + CGUIDialogContextMenu::GetContextButtons("video", item, buttons); + if (!item->IsDVD() && item->GetPath() != "add" && !item->IsParentFolder() && + (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser)) + { + CVideoDatabase database; + database.Open(); + ADDON::ScraperPtr info = database.GetScraperForPath(item->GetPath()); + + if (!item->IsLiveTV() && !item->IsAddonsPath() && !URIUtils::IsUPnP(item->GetPath())) + { + if (info && info->Content() != CONTENT_NONE) + { + buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20442); + buttons.Add(CONTEXT_BUTTON_SCAN, 13349); + } + else + buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20333); + } + } + } + else + { + // are we in the playlists location? + bool inPlaylists = m_vecItems->IsPath(CUtil::VideoPlaylistsLocation()) || + m_vecItems->IsPath("special://videoplaylists/"); + + if (item->HasVideoInfoTag() && item->HasProperty("artist_musicid")) + buttons.Add(CONTEXT_BUTTON_GO_TO_ARTIST, 20396); + + if (item->HasVideoInfoTag() && item->HasProperty("album_musicid")) + buttons.Add(CONTEXT_BUTTON_GO_TO_ALBUM, 20397); + + if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strAlbum.empty() && + !item->GetVideoInfoTag()->m_artist.empty() && + !item->GetVideoInfoTag()->m_strTitle.empty()) + { + CMusicDatabase database; + database.Open(); + if (database.GetSongByArtistAndAlbumAndTitle(StringUtils::Join(item->GetVideoInfoTag()->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator), + item->GetVideoInfoTag()->m_strAlbum, + item->GetVideoInfoTag()->m_strTitle) > -1) + { + buttons.Add(CONTEXT_BUTTON_PLAY_OTHER, 20398); + } + } + if (!item->IsParentFolder()) + { + ADDON::ScraperPtr info; + VIDEO::SScanSettings settings; + GetScraperForItem(item.get(), info, settings); + + // can we update the database? + if (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) + { + if (!CVideoLibraryQueue::GetInstance().IsScanningLibrary() && item->IsVideoDb() && + item->HasVideoInfoTag() && + (item->GetVideoInfoTag()->m_type == MediaTypeMovie || // movies + item->GetVideoInfoTag()->m_type == MediaTypeTvShow || // tvshows + item->GetVideoInfoTag()->m_type == MediaTypeSeason || // seasons + item->GetVideoInfoTag()->m_type == MediaTypeEpisode || // episodes + item->GetVideoInfoTag()->m_type == MediaTypeMusicVideo || // musicvideos + item->GetVideoInfoTag()->m_type == "tag" || // tags + item->GetVideoInfoTag()->m_type == MediaTypeVideoCollection)) // sets + { + buttons.Add(CONTEXT_BUTTON_EDIT, 16106); + } + if (node == NODE_TYPE_TITLE_TVSHOWS) + { + buttons.Add(CONTEXT_BUTTON_SCAN, 13349); + } + + if (node == NODE_TYPE_ACTOR && !dir.IsAllItem(item->GetPath()) && item->m_bIsFolder) + { + if (StringUtils::StartsWithNoCase(m_vecItems->GetPath(), "videodb://musicvideos")) // mvids + buttons.Add(CONTEXT_BUTTON_SET_ARTIST_THUMB, 13359); + else + buttons.Add(CONTEXT_BUTTON_SET_ACTOR_THUMB, 20403); + } + } + + if (!m_vecItems->IsVideoDb() && !m_vecItems->IsVirtualDirectoryRoot()) + { // non-video db items, file operations are allowed + if ((CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) && + CUtil::SupportsWriteFileOperations(item->GetPath())) || + (inPlaylists && URIUtils::GetFileName(item->GetPath()) != "PartyMode-Video.xsp" + && (item->IsPlayList() || item->IsSmartPlayList()))) + { + buttons.Add(CONTEXT_BUTTON_DELETE, 117); + buttons.Add(CONTEXT_BUTTON_RENAME, 118); + } + // add "Set/Change content" to folders + if (item->m_bIsFolder && !item->IsVideoDb() && !item->IsPlayList() && !item->IsSmartPlayList() && !item->IsLibraryFolder() && !item->IsLiveTV() && !item->IsPlugin() && !item->IsAddonsPath() && !URIUtils::IsUPnP(item->GetPath())) + { + if (info && info->Content() != CONTENT_NONE) + buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20442); + else + buttons.Add(CONTEXT_BUTTON_SET_CONTENT, 20333); + + if (info && info->Content() != CONTENT_NONE) + buttons.Add(CONTEXT_BUTTON_SCAN, 13349); + } + } + + if ((!item->HasVideoInfoTag() || item->GetVideoInfoTag()->m_iDbId == -1) && info && info->Content() != CONTENT_NONE) + buttons.Add(CONTEXT_BUTTON_SCAN_TO_LIBRARY, 21845); + + } + } +} + +bool CGUIWindowVideoNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + if (CGUIDialogContextMenu::OnContextButton("video", item, button)) + { + if (button == CONTEXT_BUTTON_REMOVE_SOURCE && !item->IsLiveTV() + &&!item->IsRSS() && !URIUtils::IsUPnP(item->GetPath())) + { + // if the source has been properly removed, remove the cached source list because the list has changed + if (OnUnAssignContent(item->GetPath(), 20375, 20340)) + m_vecItems->RemoveDiscCache(GetID()); + } + Refresh(); + return true; + } + switch (button) + { + case CONTEXT_BUTTON_EDIT: + { + CONTEXT_BUTTON ret = (CONTEXT_BUTTON)CGUIDialogVideoInfo::ManageVideoItem(item); + if (ret != CONTEXT_BUTTON_CANCELLED) + { + Refresh(true); + if (ret == CONTEXT_BUTTON_DELETE) + { + int select = itemNumber >= m_vecItems->Size()-1 ? itemNumber-1:itemNumber; + m_viewControl.SetSelectedItem(select); + } + } + return true; + } + + case CONTEXT_BUTTON_SET_ACTOR_THUMB: + case CONTEXT_BUTTON_SET_ARTIST_THUMB: + { + std::string type = MediaTypeSeason; + if (button == CONTEXT_BUTTON_SET_ACTOR_THUMB) + type = "actor"; + else if (button == CONTEXT_BUTTON_SET_ARTIST_THUMB) + type = MediaTypeArtist; + + bool result = CGUIDialogVideoInfo::ManageVideoItemArtwork(m_vecItems->Get(itemNumber), type); + Refresh(); + + return result; + } + case CONTEXT_BUTTON_GO_TO_ARTIST: + { + std::string strPath; + strPath = StringUtils::Format("musicdb://artists/{}/", + item->GetProperty("artist_musicid").asInteger()); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_NAV, strPath); + return true; + } + case CONTEXT_BUTTON_GO_TO_ALBUM: + { + std::string strPath; + strPath = StringUtils::Format("musicdb://albums/{}/", + item->GetProperty("album_musicid").asInteger()); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_NAV, strPath); + return true; + } + case CONTEXT_BUTTON_PLAY_OTHER: + { + CMusicDatabase database; + database.Open(); + CSong song; + if (database.GetSong(database.GetSongByArtistAndAlbumAndTitle(StringUtils::Join(m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator),m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_strAlbum, + m_vecItems->Get(itemNumber)->GetVideoInfoTag()->m_strTitle), + song)) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, + static_cast<void*>(new CFileItem(song))); + } + return true; + } + case CONTEXT_BUTTON_SCAN_TO_LIBRARY: + CGUIDialogVideoInfo::ShowFor(*item); + return true; + + default: + break; + + } + return CGUIWindowVideoBase::OnContextButton(itemNumber, button); +} + +bool CGUIWindowVideoNav::OnAddMediaSource() +{ + return CGUIDialogMediaSource::ShowAndAddMediaSource("video"); +} + +bool CGUIWindowVideoNav::OnClick(int iItem, const std::string &player) +{ + CFileItemPtr item = m_vecItems->Get(iItem); + if (!item->m_bIsFolder && item->IsVideoDb() && !item->Exists()) + { + CLog::Log(LOGDEBUG, "{} called on '{}' but file doesn't exist", __FUNCTION__, item->GetPath()); + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) + { + if (!CGUIDialogVideoInfo::DeleteVideoItemFromDatabase(item, true)) + return true; + + // update list + Refresh(true); + m_viewControl.SetSelectedItem(iItem); + return true; + } + else + { + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{662}); + return true; + } + } + else if (StringUtils::StartsWithNoCase(item->GetPath(), "newtag://")) + { + // dont allow update while scanning + if (CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + { + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057}); + return true; + } + + //Get the new title + std::string strTag; + if (!CGUIKeyboardFactory::ShowAndGetInput(strTag, CVariant{g_localizeStrings.Get(20462)}, false)) + return true; + + CVideoDatabase videodb; + if (!videodb.Open()) + return true; + + // get the media type and convert from plural to singular (by removing the trailing "s") + std::string mediaType = item->GetPath().substr(9); + mediaType = mediaType.substr(0, mediaType.size() - 1); + std::string localizedType = CGUIDialogVideoInfo::GetLocalizedVideoType(mediaType); + if (localizedType.empty()) + return true; + + if (!videodb.GetSingleValue("tag", "tag.tag_id", videodb.PrepareSQL("tag.name = '%s' AND tag.tag_id IN (SELECT tag_link.tag_id FROM tag_link WHERE tag_link.media_type = '%s')", strTag.c_str(), mediaType.c_str())).empty()) + { + std::string strError = StringUtils::Format(g_localizeStrings.Get(20463), strTag); + HELPERS::ShowOKDialogText(CVariant{20462}, CVariant{std::move(strError)}); + return true; + } + + int idTag = videodb.AddTag(strTag); + CFileItemList items; + std::string strLabel = StringUtils::Format(g_localizeStrings.Get(20464), localizedType); + if (CGUIDialogVideoInfo::GetItemsForTag(strLabel, mediaType, items, idTag)) + { + for (int index = 0; index < items.Size(); index++) + { + if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0) + continue; + + videodb.AddTagToItem(items[index]->GetVideoInfoTag()->m_iDbId, idTag, mediaType); + } + } + + Refresh(true); + return true; + } + + return CGUIWindowVideoBase::OnClick(iItem, player); +} + +std::string CGUIWindowVideoNav::GetStartFolder(const std::string &dir) +{ + std::string lower(dir); StringUtils::ToLower(lower); + if (lower == "moviegenres") + return "videodb://movies/genres/"; + else if (lower == "movietitles") + return "videodb://movies/titles/"; + else if (lower == "movieyears") + return "videodb://movies/years/"; + else if (lower == "movieactors") + return "videodb://movies/actors/"; + else if (lower == "moviedirectors") + return "videodb://movies/directors/"; + else if (lower == "moviestudios") + return "videodb://movies/studios/"; + else if (lower == "moviesets") + return "videodb://movies/sets/"; + else if (lower == "moviecountries") + return "videodb://movies/countries/"; + else if (lower == "movietags") + return "videodb://movies/tags/"; + else if (lower == "movies") + return "videodb://movies/"; + else if (lower == "tvshowgenres") + return "videodb://tvshows/genres/"; + else if (lower == "tvshowtitles") + return "videodb://tvshows/titles/"; + else if (lower == "tvshowyears") + return "videodb://tvshows/years/"; + else if (lower == "tvshowactors") + return "videodb://tvshows/actors/"; + else if (lower == "tvshowstudios") + return "videodb://tvshows/studios/"; + else if (lower == "tvshowtags") + return "videodb://tvshows/tags/"; + else if (lower == "tvshows") + return "videodb://tvshows/"; + else if (lower == "musicvideogenres") + return "videodb://musicvideos/genres/"; + else if (lower == "musicvideotitles") + return "videodb://musicvideos/titles/"; + else if (lower == "musicvideoyears") + return "videodb://musicvideos/years/"; + else if (lower == "musicvideoartists") + return "videodb://musicvideos/artists/"; + else if (lower == "musicvideoalbums") + return "videodb://musicvideos/albums/"; + else if (lower == "musicvideodirectors") + return "videodb://musicvideos/directors/"; + else if (lower == "musicvideostudios") + return "videodb://musicvideos/studios/"; + else if (lower == "musicvideotags") + return "videodb://musicvideos/tags/"; + else if (lower == "musicvideos") + return "videodb://musicvideos/"; + else if (lower == "recentlyaddedmovies") + return "videodb://recentlyaddedmovies/"; + else if (lower == "recentlyaddedepisodes") + return "videodb://recentlyaddedepisodes/"; + else if (lower == "recentlyaddedmusicvideos") + return "videodb://recentlyaddedmusicvideos/"; + else if (lower == "inprogresstvshows") + return "videodb://inprogresstvshows/"; + else if (lower == "files") + return "sources://video/"; + return CGUIWindowVideoBase::GetStartFolder(dir); +} + +bool CGUIWindowVideoNav::ApplyWatchedFilter(CFileItemList &items) +{ + bool listchanged = false; + CVideoDatabaseDirectory dir; + NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); + + // now filter watched items as necessary + bool filterWatched=false; + if (node == NODE_TYPE_EPISODES + || node == NODE_TYPE_SEASONS + || node == NODE_TYPE_SETS + || node == NODE_TYPE_TAGS + || node == NODE_TYPE_TITLE_MOVIES + || node == NODE_TYPE_TITLE_TVSHOWS + || node == NODE_TYPE_TITLE_MUSICVIDEOS + || node == NODE_TYPE_RECENTLY_ADDED_EPISODES + || node == NODE_TYPE_RECENTLY_ADDED_MOVIES + || node == NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS) + filterWatched = true; + if (!items.IsVideoDb()) + filterWatched = true; + if (items.GetContent() == "tvshows" && + (items.IsSmartPlayList() || items.IsLibraryFolder())) + node = NODE_TYPE_TITLE_TVSHOWS; // so that the check below works + + int watchMode = CMediaSettings::GetInstance().GetWatchedMode(m_vecItems->GetContent()); + + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items.Get(i); + + if(item->HasVideoInfoTag() && (node == NODE_TYPE_TITLE_TVSHOWS || node == NODE_TYPE_SEASONS)) + { + if (watchMode == WatchedModeUnwatched) + item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("unwatchedepisodes").asInteger(); + if (watchMode == WatchedModeWatched) + item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("watchedepisodes").asInteger(); + if (watchMode == WatchedModeAll) + item->GetVideoInfoTag()->m_iEpisode = (int)item->GetProperty("totalepisodes").asInteger(); + item->SetProperty("numepisodes", item->GetVideoInfoTag()->m_iEpisode); + listchanged = true; + } + + if (filterWatched) + { + if(!item->IsParentFolder() && // Don't delete the go to parent folder + ((watchMode == WatchedModeWatched && item->GetVideoInfoTag()->GetPlayCount() == 0) || + (watchMode == WatchedModeUnwatched && item->GetVideoInfoTag()->GetPlayCount() > 0))) + { + items.Remove(i); + i--; + listchanged = true; + } + } + } + + // Remove the parent folder icon, if it's the only thing in the folder. This is needed for hiding seasons. + if (items.GetObjectCount() == 0 && items.GetFileCount() > 0 && items.Get(0)->IsParentFolder()) + items.Remove(0); + + if(node == NODE_TYPE_TITLE_TVSHOWS || node == NODE_TYPE_SEASONS) + { + // the watched filter may change the "numepisodes" property which is reflected in the TV_SHOWS and SEASONS nodes + // therefore, the items labels have to be refreshed, and possibly the list needs resorting as well. + items.ClearSortState(); // this is needed to force resorting even if sort method did not change + FormatAndSort(items); + } + + return listchanged; +} |