diff options
Diffstat (limited to 'xbmc/video/windows')
-rw-r--r-- | xbmc/video/windows/CMakeLists.txt | 14 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowFullScreen.cpp | 437 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowFullScreen.h | 45 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowFullScreenDefines.h | 14 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoBase.cpp | 1589 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoBase.h | 148 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoNav.cpp | 1171 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoNav.h | 68 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoPlaylist.cpp | 609 | ||||
-rw-r--r-- | xbmc/video/windows/GUIWindowVideoPlaylist.h | 43 | ||||
-rw-r--r-- | xbmc/video/windows/VideoFileItemListModifier.cpp | 135 | ||||
-rw-r--r-- | xbmc/video/windows/VideoFileItemListModifier.h | 24 |
12 files changed, 4297 insertions, 0 deletions
diff --git a/xbmc/video/windows/CMakeLists.txt b/xbmc/video/windows/CMakeLists.txt new file mode 100644 index 0000000..0695663 --- /dev/null +++ b/xbmc/video/windows/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SOURCES GUIWindowFullScreen.cpp + GUIWindowVideoBase.cpp + GUIWindowVideoNav.cpp + GUIWindowVideoPlaylist.cpp + VideoFileItemListModifier.cpp) + +set(HEADERS GUIWindowFullScreen.h + GUIWindowFullScreenDefines.h + GUIWindowVideoBase.h + GUIWindowVideoNav.h + GUIWindowVideoPlaylist.h + VideoFileItemListModifier.h) + +core_add_library(video_windows) diff --git a/xbmc/video/windows/GUIWindowFullScreen.cpp b/xbmc/video/windows/GUIWindowFullScreen.cpp new file mode 100644 index 0000000..7cebc5f --- /dev/null +++ b/xbmc/video/windows/GUIWindowFullScreen.cpp @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2005-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 "GUIWindowFullScreen.h" + +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "GUIWindowFullScreenDefines.h" +#include "ServiceBroker.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/IPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "video/ViewModeSettings.h" +#include "video/dialogs/GUIDialogFullScreenInfo.h" +#include "video/dialogs/GUIDialogSubtitleSettings.h" +#include "windowing/WinSystem.h" + +#include <algorithm> +#include <stdio.h> +#if defined(TARGET_DARWIN) +#include "platform/posix/PosixResourceCounter.h" +#endif + +using namespace KODI::GUILIB; +using namespace KODI::MESSAGING; + +#if defined(TARGET_DARWIN) +static CPosixResourceCounter m_resourceCounter; +#endif + +CGUIWindowFullScreen::CGUIWindowFullScreen() + : CGUIWindow(WINDOW_FULLSCREEN_VIDEO, "VideoFullScreen.xml") +{ + m_viewModeChanged = true; + m_dwShowViewModeTimeout = {}; + m_bShowCurrentTime = false; + m_loadType = KEEP_IN_MEMORY; + // audio + // - language + // - volume + // - stream + + // video + // - Create Bookmark (294) + // - Cycle bookmarks (295) + // - Clear bookmarks (296) + // - jump to specific time + // - slider + // - av delay + + // subtitles + // - delay + // - language + + m_controlStats = new GUICONTROLSTATS; +} + +CGUIWindowFullScreen::~CGUIWindowFullScreen(void) +{ + delete m_controlStats; +} + +bool CGUIWindowFullScreen::OnAction(const CAction &action) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + switch (action.GetID()) + { + case ACTION_SHOW_OSD: + ToggleOSD(); + return true; + + case ACTION_TRIGGER_OSD: + TriggerOSD(); + return true; + + case ACTION_MOUSE_MOVE: + if (action.GetAmount(2) || action.GetAmount(3)) + { + if (!appPlayer->IsInMenu()) + { + TriggerOSD(); + return true; + } + } + break; + + case ACTION_MOUSE_LEFT_CLICK: + if (!appPlayer->IsInMenu()) + { + TriggerOSD(); + return true; + } + break; + + case ACTION_SHOW_GUI: + { + // switch back to the menu + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return true; + } + break; + + case ACTION_SHOW_OSD_TIME: + m_bShowCurrentTime = !m_bShowCurrentTime; + CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().SetShowTime(m_bShowCurrentTime); + return true; + break; + + case ACTION_SHOW_INFO: + { + CGUIDialogFullScreenInfo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogFullScreenInfo>(WINDOW_DIALOG_FULLSCREEN_INFO); + if (pDialog) + { + CFileItem item(g_application.CurrentFileItem()); + pDialog->Open(); + return true; + } + break; + } + + case ACTION_ASPECT_RATIO: + { // toggle the aspect ratio mode (only if the info is onscreen) + if (m_dwShowViewModeTimeout.time_since_epoch().count() != 0) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_ViewMode = CViewModeSettings::GetNextQuickCycleViewMode(vs.m_ViewMode); + appPlayer->SetRenderViewMode(vs.m_ViewMode, vs.m_CustomZoomAmount, vs.m_CustomPixelRatio, + vs.m_CustomVerticalShift, vs.m_CustomNonLinStretch); + } + else + m_viewModeChanged = true; + m_dwShowViewModeTimeout = std::chrono::steady_clock::now(); + } + return true; + break; + case ACTION_SHOW_PLAYLIST: + { + CFileItem item(g_application.CurrentFileItem()); + if (item.HasPVRChannelInfoTag()) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_PVR_OSD_CHANNELS); + else if (item.HasVideoInfoTag()) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST); + else if (item.HasMusicInfoTag()) + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST); + } + return true; + break; + case ACTION_BROWSE_SUBTITLE: + { + std::string path = CGUIDialogSubtitleSettings::BrowseForSubtitle(); + if (!path.empty()) + appPlayer->AddSubtitle(path); + return true; + } + default: + break; + } + + return CGUIWindow::OnAction(action); +} + +void CGUIWindowFullScreen::ClearBackground() +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsRenderingVideoLayer()) + CServiceBroker::GetWinSystem()->GetGfxContext().Clear(0); +} + +void CGUIWindowFullScreen::OnWindowLoaded() +{ + CGUIWindow::OnWindowLoaded(); + // override the clear colour - we must never clear fullscreen + m_clearBackground = 0; +} + +bool CGUIWindowFullScreen::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + // check whether we've come back here from a window during which time we've actually + // stopped playing videos + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (message.GetParam1() == WINDOW_INVALID && !appPlayer->IsPlayingVideo()) + { // why are we here if nothing is playing??? + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return true; + } + + GUIINFO::CPlayerGUIInfo& guiInfo = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider(); + guiInfo.SetShowInfo(false); + m_bShowCurrentTime = false; + + // switch resolution + CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(true); + + // now call the base class to load our windows + CGUIWindow::OnMessage(message); + + m_dwShowViewModeTimeout = {}; + m_viewModeChanged = true; + + + return true; + } + case GUI_MSG_WINDOW_DEINIT: + { + // close all active modal dialogs + CServiceBroker::GetGUI()->GetWindowManager().CloseInternalModalDialogs(true); + + CGUIWindow::OnMessage(message); + + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(false); + + return true; + } + case GUI_MSG_SETFOCUS: + case GUI_MSG_LOSTFOCUS: + if (message.GetSenderId() != WINDOW_FULLSCREEN_VIDEO) return true; + break; + } + + return CGUIWindow::OnMessage(message); +} + +EVENT_RESULT CGUIWindowFullScreen::OnMouseEvent(const CPoint &point, const CMouseEvent &event) +{ + if (event.m_id == ACTION_MOUSE_RIGHT_CLICK) + { // no control found to absorb this click - go back to GUI + OnAction(CAction(ACTION_SHOW_GUI)); + return EVENT_RESULT_HANDLED; + } + if (event.m_id == ACTION_MOUSE_WHEEL_UP) + { + return g_application.OnAction(CAction(ACTION_ANALOG_SEEK_FORWARD, 0.5f)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED; + } + if (event.m_id == ACTION_MOUSE_WHEEL_DOWN) + { + return g_application.OnAction(CAction(ACTION_ANALOG_SEEK_BACK, 0.5f)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED; + } + if (event.m_id >= ACTION_GESTURE_NOTIFY && event.m_id <= ACTION_GESTURE_END) // gestures + return EVENT_RESULT_UNHANDLED; + return EVENT_RESULT_UNHANDLED; +} + +void CGUIWindowFullScreen::FrameMove() +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->HasPlayer()) + return; + + //---------------------- + // ViewMode Information + //---------------------- + + auto now = std::chrono::steady_clock::now(); + auto duration = + std::chrono::duration_cast<std::chrono::milliseconds>(now - m_dwShowViewModeTimeout); + + if (m_dwShowViewModeTimeout.time_since_epoch().count() != 0 && duration.count() > 2500) + { + m_dwShowViewModeTimeout = {}; + m_viewModeChanged = true; + } + + if (m_dwShowViewModeTimeout.time_since_epoch().count() != 0) + { + RESOLUTION_INFO res = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(); + + { + // get the "View Mode" string + const std::string& strTitle = g_localizeStrings.Get(629); + const auto& vs = appPlayer->GetVideoSettings(); + int sId = CViewModeSettings::GetViewModeStringIndex(vs.m_ViewMode); + const std::string& strMode = g_localizeStrings.Get(sId); + std::string strInfo = StringUtils::Format("{} : {}", strTitle, strMode); + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), LABEL_ROW1); + msg.SetLabel(strInfo); + OnMessage(msg); + } + // show sizing information + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(CURRENT_STREAM, info); + { + // Splitres scaling factor + float xscale = (float)res.iScreenWidth / (float)res.iWidth; + float yscale = (float)res.iScreenHeight / (float)res.iHeight; + + std::string strSizing = StringUtils::Format( + g_localizeStrings.Get(245), (int)info.SrcRect.Width(), (int)info.SrcRect.Height(), + (int)(info.DestRect.Width() * xscale), (int)(info.DestRect.Height() * yscale), + CDisplaySettings::GetInstance().GetZoomAmount(), + info.videoAspectRatio * CDisplaySettings::GetInstance().GetPixelRatio(), + CDisplaySettings::GetInstance().GetPixelRatio(), + CDisplaySettings::GetInstance().GetVerticalShift()); + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), LABEL_ROW2); + msg.SetLabel(strSizing); + OnMessage(msg); + } + // show resolution information + { + std::string strStatus; + if (CServiceBroker::GetWinSystem()->IsFullScreen()) + strStatus = StringUtils::Format("{} {}x{}@{:.2f}Hz - {}", g_localizeStrings.Get(13287), + res.iScreenWidth, res.iScreenHeight, res.fRefreshRate, + g_localizeStrings.Get(244)); + else + strStatus = + StringUtils::Format("{} {}x{} - {}", g_localizeStrings.Get(13287), res.iScreenWidth, + res.iScreenHeight, g_localizeStrings.Get(242)); + + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), LABEL_ROW3); + msg.SetLabel(strStatus); + OnMessage(msg); + } + } + + if (m_viewModeChanged) + { + if (m_dwShowViewModeTimeout.time_since_epoch().count() != 0) + { + SET_CONTROL_VISIBLE(LABEL_ROW1); + SET_CONTROL_VISIBLE(LABEL_ROW2); + SET_CONTROL_VISIBLE(LABEL_ROW3); + SET_CONTROL_VISIBLE(BLUE_BAR); + } + else + { + SET_CONTROL_HIDDEN(LABEL_ROW1); + SET_CONTROL_HIDDEN(LABEL_ROW2); + SET_CONTROL_HIDDEN(LABEL_ROW3); + SET_CONTROL_HIDDEN(BLUE_BAR); + } + m_viewModeChanged = false; + } +} + +void CGUIWindowFullScreen::Process(unsigned int currentTime, CDirtyRegionList &dirtyregion) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsRenderingGuiLayer()) + MarkDirtyRegion(); + + m_controlStats->Reset(); + + CGUIWindow::Process(currentTime, dirtyregion); + + //! @todo This isn't quite optimal - ideally we'd only be dirtying up the actual video render rect + //! which is probably the job of the renderer as it can more easily track resizing etc. + m_renderRegion.SetRect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); +} + +void CGUIWindowFullScreen::Render() +{ + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), false); + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->Render(true, 255); + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_coordsRes, m_needsScaling); + CGUIWindow::Render(); +} + +void CGUIWindowFullScreen::RenderEx() +{ + CGUIWindow::RenderEx(); + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), false); + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->Render(false, 255, false); + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_coordsRes, m_needsScaling); +} + +void CGUIWindowFullScreen::SeekChapter(int iChapter) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->SeekChapter(iChapter); +} + +void CGUIWindowFullScreen::ToggleOSD() +{ + CGUIDialog *pOSD = GetOSD(); + if (pOSD) + { + if (pOSD->IsDialogRunning()) + pOSD->Close(); + else + pOSD->Open(); + } + + MarkDirtyRegion(); +} + +void CGUIWindowFullScreen::TriggerOSD() +{ + CGUIDialog *pOSD = GetOSD(); + if (pOSD && !pOSD->IsDialogRunning()) + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlayingGame()) + pOSD->SetAutoClose(3000); + pOSD->Open(); + } +} + +bool CGUIWindowFullScreen::HasVisibleControls() +{ + return m_controlStats->nCountVisible > 0; +} + +CGUIDialog* CGUIWindowFullScreen::GetOSD() +{ + return CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_VIDEO_OSD); +} diff --git a/xbmc/video/windows/GUIWindowFullScreen.h b/xbmc/video/windows/GUIWindowFullScreen.h new file mode 100644 index 0000000..de4e38d --- /dev/null +++ b/xbmc/video/windows/GUIWindowFullScreen.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005-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. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +#include <chrono> + +class CGUIDialog; + +class CGUIWindowFullScreen : public CGUIWindow +{ +public: + CGUIWindowFullScreen(); + ~CGUIWindowFullScreen(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + void ClearBackground() override; + void FrameMove() override; + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregion) override; + void Render() override; + void RenderEx() override; + void OnWindowLoaded() override; + bool HasVisibleControls() override; + +protected: + EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override; + +private: + void SeekChapter(int iChapter); + void ToggleOSD(); + void TriggerOSD(); + CGUIDialog *GetOSD(); + + bool m_viewModeChanged; + std::chrono::time_point<std::chrono::steady_clock> m_dwShowViewModeTimeout; + + bool m_bShowCurrentTime; +}; diff --git a/xbmc/video/windows/GUIWindowFullScreenDefines.h b/xbmc/video/windows/GUIWindowFullScreenDefines.h new file mode 100644 index 0000000..cf67af5 --- /dev/null +++ b/xbmc/video/windows/GUIWindowFullScreenDefines.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#define BLUE_BAR 0 +#define LABEL_ROW1 10 +#define LABEL_ROW2 11 +#define LABEL_ROW3 12 diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp new file mode 100644 index 0000000..94164ef --- /dev/null +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -0,0 +1,1589 @@ +/* + * Copyright (C) 2005-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 "GUIWindowVideoBase.h" + +#include "Autorun.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "addons/gui/GUIDialogAddonInfo.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogSelect.h" +#include "dialogs/GUIDialogSmartPlaylistEditor.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/Directory.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/StackDirectory.h" +#include "filesystem/VideoDatabaseDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/dialogs/GUIDialogMusicInfo.h" +#include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/dialogs/GUIDialogContentSettings.h" +#include "settings/lib/Setting.h" +#include "storage/MediaManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/GroupUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoInfoScanner.h" +#include "video/VideoLibraryQueue.h" +#include "video/VideoUtils.h" +#include "video/dialogs/GUIDialogVideoInfo.h" +#include "view/GUIViewState.h" + +using namespace XFILE; +using namespace VIDEODATABASEDIRECTORY; +using namespace VIDEO; +using namespace ADDON; +using namespace PVR; +using namespace KODI::MESSAGING; + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 + +#define CONTROL_PLAY_DVD 6 + +#define PROPERTY_GROUP_BY "group.by" +#define PROPERTY_GROUP_MIXED "group.mixed" + +static constexpr int SETTING_AUTOPLAYNEXT_MUSICVIDEOS = 0; +static constexpr int SETTING_AUTOPLAYNEXT_EPISODES = 2; +static constexpr int SETTING_AUTOPLAYNEXT_MOVIES = 3; +static constexpr int SETTING_AUTOPLAYNEXT_UNCATEGORIZED = 4; + +CGUIWindowVideoBase::CGUIWindowVideoBase(int id, const std::string &xmlFile) + : CGUIMediaWindow(id, xmlFile.c_str()) +{ + m_thumbLoader.SetObserver(this); + m_stackingAvailable = true; + m_dlgProgress = NULL; +} + +CGUIWindowVideoBase::~CGUIWindowVideoBase() = default; + +bool CGUIWindowVideoBase::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_SCAN_ITEM) + return OnContextButton(m_viewControl.GetSelectedItem(),CONTEXT_BUTTON_SCAN); + else if (action.GetID() == ACTION_SHOW_PLAYLIST) + { + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO || + CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_VIDEO).size() > 0) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST); + return true; + } + } + + return CGUIMediaWindow::OnAction(action); +} + +bool CGUIWindowVideoBase::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + m_database.Close(); + break; + + case GUI_MSG_WINDOW_INIT: + { + m_database.Open(); + m_dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + return CGUIMediaWindow::OnMessage(message); + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); +#if defined(HAS_DVD_DRIVE) + if (iControl == CONTROL_PLAY_DVD) + { + // play movie... + MEDIA_DETECT::CAutorun::PlayDiscAskResume( + CServiceBroker::GetMediaManager().TranslateDevicePath("")); + } + else +#endif + if (m_viewControl.HasControl(iControl)) // list/thumb control + { + // get selected item + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + + // iItem is checked for validity inside these routines + if (iAction == ACTION_QUEUE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK) + { + OnQueueItem(iItem); + return true; + } + else if (iAction == ACTION_QUEUE_ITEM_NEXT) + { + OnQueueItem(iItem, true); + return true; + } + else if (iAction == ACTION_SHOW_INFO) + { + return OnItemInfo(iItem); + } + else if (iAction == ACTION_PLAYER_PLAY) + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // if playback is paused or playback speed != 1, return + if (appPlayer->IsPlayingVideo()) + { + if (appPlayer->IsPausedPlayback()) + return false; + if (appPlayer->GetPlaySpeed() != 1) + return false; + } + + // not playing video, or playback speed == 1 + return OnResumeItem(iItem); + } + else if (iAction == ACTION_DELETE_ITEM) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // is delete allowed? + if (profileManager->GetCurrentProfile().canWriteDatabases()) + { + // must be at the title window + if (GetID() == WINDOW_VIDEO_NAV) + OnDeleteItem(iItem); + + // or be at the video playlists location + else if (m_vecItems->IsPath("special://videoplaylists/")) + OnDeleteItem(iItem); + else + return false; + + return true; + } + } + } + } + break; + case GUI_MSG_SEARCH: + OnSearch(); + break; + } + return CGUIMediaWindow::OnMessage(message); +} + +bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper) +{ + if (fileItem.IsParentFolder() || fileItem.m_bIsShareOrDrive || fileItem.IsPath("add") || + (fileItem.IsPlayList() && !URIUtils::HasExtension(fileItem.GetDynPath(), ".strm"))) + return false; + + CFileItem item(fileItem); + bool fromDB = false; + if ((item.IsVideoDb() && item.HasVideoInfoTag()) || + (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId != -1)) + { + if (item.GetVideoInfoTag()->m_type == MediaTypeSeason) + { // clear out the art - we're really grabbing the info on the show here + item.ClearArt(); + item.GetVideoInfoTag()->m_iDbId = item.GetVideoInfoTag()->m_iIdShow; + } + item.SetPath(item.GetVideoInfoTag()->GetPath()); + fromDB = true; + } + else + { + if (item.m_bIsFolder && scraper && scraper->Content() != CONTENT_TVSHOWS) + { + CFileItemList items; + CDirectory::GetDirectory(item.GetPath(), items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), + DIR_FLAG_DEFAULTS); + + // Check for cases 1_dir/1_dir/.../file (e.g. by packages where have a extra folder) + while (items.Size() == 1 && items[0]->m_bIsFolder) + { + const std::string path = items[0]->GetPath(); + items.Clear(); + CDirectory::GetDirectory(path, items, + CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), + DIR_FLAG_DEFAULTS); + } + + items.Stack(); + + // check for media files + bool bFoundFile(false); + for (int i = 0; i < items.Size(); ++i) + { + CFileItemPtr item2 = items[i]; + + if (item2->IsVideo() && !item2->IsPlayList() && + !CUtil::ExcludeFileOrFolder(item2->GetPath(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_moviesExcludeFromScanRegExps)) + { + item.SetPath(item2->GetPath()); + item.m_bIsFolder = false; + bFoundFile = true; + break; + } + } + + // no video file in this folder + if (!bFoundFile) + { + HELPERS::ShowOKDialogText(CVariant{13346}, CVariant{20349}); + return false; + } + } + } + + // we need to also request any thumbs be applied to the folder item + if (fileItem.m_bIsFolder) + item.SetProperty("set_folder_thumb", fileItem.GetPath()); + + bool modified = ShowIMDB(CFileItemPtr(new CFileItem(item)), scraper, fromDB); + if (modified && + (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV)) // since we can be called from the music library we need this check + { + int itemNumber = m_viewControl.GetSelectedItem(); + Refresh(); + m_viewControl.SetSelectedItem(itemNumber); + } + return true; +} + +// ShowIMDB is called as follows: +// 1. To lookup info on a file. +// 2. To lookup info on a folder (which may or may not contain a file) +// 3. To lookup info just for fun (no file or folder related) + +// We just need the item object for this. +// A "blank" item object is sent for 3. +// If a folder is sent, currently it sets strFolder and bFolder +// this is only used for setting the folder thumb, however. + +// Steps should be: + +// 1. Check database to see if we have this information already +// 2. Else, check for a nfoFile to get the URL +// 3. Run a loop to check for refresh +// 4. If no URL is present do a search to get the URL +// 4. Once we have the URL, download the details +// 5. Once we have the details, add to the database if necessary (case 1,2) +// and show the information. +// 6. Check for a refresh, and if so, go to 3. + +bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, bool fromDB) +{ + /* + CLog::Log(LOGDEBUG,"CGUIWindowVideoBase::ShowIMDB"); + CLog::Log(LOGDEBUG," strMovie = [{}]", strMovie); + CLog::Log(LOGDEBUG," strFile = [{}]", strFile); + CLog::Log(LOGDEBUG," strFolder = [{}]", strFolder); + CLog::Log(LOGDEBUG," bFolder = [{}]", ((int)bFolder ? "true" : "false")); + */ + + CGUIDialogProgress* pDlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + CGUIDialogVideoInfo* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogVideoInfo>(WINDOW_DIALOG_VIDEO_INFO); + + const ScraperPtr& info(info2); // use this as nfo might change it.. + + if (!pDlgProgress) return false; + if (!pDlgSelect) return false; + if (!pDlgInfo) return false; + + // 1. Check for already downloaded information, and if we have it, display our dialog + // Return if no Refresh is needed. + bool bHasInfo=false; + + CVideoInfoTag movieDetails; + if (info) + { + m_database.Open(); // since we can be called from the music library + + int dbId = item->HasVideoInfoTag() ? item->GetVideoInfoTag()->m_iDbId : -1; + if (info->Content() == CONTENT_MOVIES) + { + bHasInfo = m_database.GetMovieInfo(item->GetPath(), movieDetails, dbId); + } + if (info->Content() == CONTENT_TVSHOWS) + { + if (item->m_bIsFolder) + { + bHasInfo = m_database.GetTvShowInfo(item->GetPath(), movieDetails, dbId); + } + else + { + bHasInfo = m_database.GetEpisodeInfo(item->GetPath(), movieDetails, dbId); + if (!bHasInfo) + { + // !! WORKAROUND !! + // As we cannot add an episode to a non-existing tvshow entry, we have to check the parent directory + // to see if it`s already in our video database. If it's not yet part of the database we will exit here. + // (Ticket #4764) + // + // NOTE: This will fail for episodes on multipath shares, as the parent path isn't what is stored in the + // database. Possible solutions are to store the paths in the db separately and rely on the show + // stacking stuff, or to modify GetTvShowId to do support multipath:// shares + std::string strParentDirectory; + URIUtils::GetParentPath(item->GetPath(), strParentDirectory); + if (m_database.GetTvShowId(strParentDirectory) < 0) + { + CLog::Log(LOGERROR, "{}: could not add episode [{}]. tvshow does not exist yet..", + __FUNCTION__, item->GetPath()); + return false; + } + } + } + } + if (info->Content() == CONTENT_MUSICVIDEOS) + { + bHasInfo = m_database.GetMusicVideoInfo(item->GetPath(), movieDetails); + } + m_database.Close(); + } + else if(item->HasVideoInfoTag()) + { + bHasInfo = true; + movieDetails = *item->GetVideoInfoTag(); + } + + bool needsRefresh = false; + if (bHasInfo) + { + if (!info || info->Content() == CONTENT_NONE) // disable refresh button + item->SetProperty("xxuniqueid", "xx" + movieDetails.GetUniqueID()); + *item->GetVideoInfoTag() = movieDetails; + pDlgInfo->SetMovie(item.get()); + pDlgInfo->Open(); + if (pDlgInfo->HasUpdatedUserrating()) + return true; + needsRefresh = pDlgInfo->NeedRefresh(); + if (!needsRefresh) + return pDlgInfo->HasUpdatedThumb(); + // check if the item in the video info dialog has changed and if so, get the new item + else if (pDlgInfo->GetCurrentListItem() != NULL) + { + item = pDlgInfo->GetCurrentListItem(); + + if (item->IsVideoDb() && item->HasVideoInfoTag()) + item->SetPath(item->GetVideoInfoTag()->GetPath()); + } + } + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // quietly return if Internet lookups are disabled + if (!profileManager->GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser) + return false; + + if (!info) + return false; + + if (CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + { + HELPERS::ShowOKDialogText(CVariant{13346}, CVariant{14057}); + return false; + } + + bool listNeedsUpdating = false; + // 3. Run a loop so that if we Refresh we re-run this block + do + { + if (!CVideoLibraryQueue::GetInstance().RefreshItemModal(item, needsRefresh, pDlgInfo->RefreshAll())) + return listNeedsUpdating; + + // remove directory caches and reload images + CUtil::DeleteVideoDatabaseDirectoryCache(); + CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS); + OnMessage(reload); + + pDlgInfo->SetMovie(item.get()); + pDlgInfo->Open(); + item->SetArt("thumb", pDlgInfo->GetThumbnail()); + needsRefresh = pDlgInfo->NeedRefresh(); + listNeedsUpdating = true; + } while (needsRefresh); + + return listNeedsUpdating; +} + +void CGUIWindowVideoBase::OnQueueItem(int iItem, bool first) +{ + // don't re-queue items from playlist window + if (GetID() == WINDOW_VIDEO_PLAYLIST) + return; + + if (iItem < 0 || iItem >= m_vecItems->Size()) + return; + + // add item 2 playlist + const auto item = m_vecItems->Get(iItem); + + if (item->IsRAR() || item->IsZIP()) + return; + + VIDEO_UTILS::QueueItem(item, first ? VIDEO_UTILS::QueuePosition::POSITION_BEGIN + : VIDEO_UTILS::QueuePosition::POSITION_END); + + // select next item + m_viewControl.SetSelectedItem(iItem + 1); +} + +bool CGUIWindowVideoBase::OnClick(int iItem, const std::string &player) +{ + return CGUIMediaWindow::OnClick(iItem, player); +} + +bool CGUIWindowVideoBase::OnSelect(int iItem) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return false; + + CFileItemPtr item = m_vecItems->Get(iItem); + + std::string path = item->GetPath(); + if (!item->m_bIsFolder && path != "add" && + !StringUtils::StartsWith(path, "newsmartplaylist://") && + !StringUtils::StartsWith(path, "newplaylist://") && + !StringUtils::StartsWith(path, "newtag://") && + !StringUtils::StartsWith(path, "script://")) + return OnFileAction(iItem, CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION), ""); + + return CGUIMediaWindow::OnSelect(iItem); +} + +bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& player) +{ + CFileItemPtr item = m_vecItems->Get(iItem); + if (!item) + { + return false; + } + + // Reset the current start offset. The actual resume + // option is set in the switch, based on the action passed. + item->SetStartOffset(0); + + switch (action) + { + case SELECT_ACTION_CHOOSE: + { + CContextButtons choices; + + if (item->IsVideoDb()) + { + std::string itemPath(item->GetPath()); + itemPath = item->GetVideoInfoTag()->m_strFileNameAndPath; + if (URIUtils::IsStack(itemPath) && CFileItem(CStackDirectory::GetFirstStackedFile(itemPath),false).IsDiscImage()) + choices.Add(SELECT_ACTION_PLAYPART, 20324); // Play Part + } + + std::string resumeString = GetResumeString(*item); + if (!resumeString.empty()) + { + choices.Add(SELECT_ACTION_RESUME, resumeString); + choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning + } + else + choices.Add(SELECT_ACTION_PLAY, 208); // Play + + choices.Add(SELECT_ACTION_INFO, 22081); // Info + choices.Add(SELECT_ACTION_MORE, 22082); // More + int value = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (value < 0) + return true; + + return OnFileAction(iItem, value, player); + } + break; + case SELECT_ACTION_PLAY_OR_RESUME: + return OnResumeItem(iItem, player); + case SELECT_ACTION_INFO: + return OnItemInfo(iItem); + case SELECT_ACTION_MORE: + OnPopupMenu(iItem); + return true; + case SELECT_ACTION_RESUME: + item->SetStartOffset(STARTOFFSET_RESUME); + if (item->m_bIsFolder) + { + PlayItem(iItem, player); + return true; + } + break; + case SELECT_ACTION_PLAYPART: + if (!OnPlayStackPart(iItem)) + return false; + break; + case SELECT_ACTION_QUEUE: + OnQueueItem(iItem); + return true; + case SELECT_ACTION_PLAY: + if (item->m_bIsFolder) + { + PlayItem(iItem, player); + return true; + } + break; + default: + break; + } + return OnClick(iItem, player); +} + +bool CGUIWindowVideoBase::OnItemInfo(int iItem) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return false; + + CFileItemPtr item = m_vecItems->Get(iItem); + + if (item->IsPath("add") || item->IsParentFolder() || + (item->IsPlayList() && !URIUtils::HasExtension(item->GetDynPath(), ".strm"))) + return false; + + if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript())) + return CGUIDialogAddonInfo::ShowForItem(item); + + if (item->m_bIsFolder && + item->IsVideoDb() && + StringUtils::StartsWith(item->GetPath(), "videodb://movies/sets/")) + return ShowIMDB(item, nullptr, true); + + ADDON::ScraperPtr scraper; + + // Match visibility test of CMusicInfo::IsVisible + if (item->IsVideoDb() && item->HasVideoInfoTag() && + (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid"))) + { + CGUIDialogMusicInfo::ShowFor(item.get()); + return true; + } + if (!m_vecItems->IsPlugin() && !m_vecItems->IsRSS() && !m_vecItems->IsLiveTV()) + { + std::string strDir; + if (item->IsVideoDb() && + item->HasVideoInfoTag() && + !item->GetVideoInfoTag()->m_strPath.empty()) + { + strDir = item->GetVideoInfoTag()->m_strPath; + } + else + strDir = URIUtils::GetDirectory(item->GetPath()); + + SScanSettings settings; + bool foundDirectly = false; + scraper = m_database.GetScraperForPath(strDir, settings, foundDirectly); + + if (!scraper && + !(m_database.HasMovieInfo(item->GetDynPath()) || m_database.HasTvShowInfo(strDir) || + m_database.HasEpisodeInfo(item->GetDynPath()))) + { + HELPERS::ShowOKDialogText(CVariant{20176}, // Show video information + CVariant{19055}); // no information available + return false; + } + + if (scraper && scraper->Content() == CONTENT_TVSHOWS && foundDirectly && !settings.parent_name_root) // dont lookup on root tvshow folder + return true; + } + + return OnItemInfo(*item, scraper); +} + +void CGUIWindowVideoBase::OnRestartItem(int iItem, const std::string &player) +{ + CGUIMediaWindow::OnClick(iItem, player); +} + +void CGUIWindowVideoBase::LoadVideoInfo(CFileItemList& items, + CVideoDatabase& database, + bool allowReplaceLabels) +{ + //! @todo this could possibly be threaded as per the music info loading, + //! we could also cache the info + if (!items.GetContent().empty() && !items.IsPlugin()) + return; // don't load for listings that have content set and weren't created from plugins + + std::string content = items.GetContent(); + // determine content only if it isn't set + if (content.empty()) + { + content = database.GetContentForPath(items.GetPath()); + items.SetContent((content.empty() && !items.IsPlugin()) ? "files" : content); + } + + /* + If we have a matching item in the library, so we can assign the metadata to it. In addition, we can choose + * whether the item is stacked down (eg in the case of folders representing a single item) + * whether or not we assign the library's labels to the item, or leave the item as is. + + As certain users (read: certain developers) don't want either of these to occur, we compromise by stacking + items down only if stacking is available and enabled. + + Similarly, we assign the "clean" library labels to the item only if the "Replace filenames with library titles" + setting is enabled. + */ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + const bool stackItems = + items.GetProperty("isstacked").asBoolean() || + (StackingAvailable(items) && settings->GetBool(CSettings::SETTING_MYVIDEOS_STACKVIDEOS)); + const bool replaceLabels = + allowReplaceLabels && settings->GetBool(CSettings::SETTING_MYVIDEOS_REPLACELABELS); + + CFileItemList dbItems; + /* NOTE: In the future when GetItemsForPath returns all items regardless of whether they're "in the library" + we won't need the fetchedPlayCounts code, and can "simply" do this directly on absence of content. */ + bool fetchedPlayCounts = false; + if (!content.empty()) + { + database.GetItemsForPath(content, items.GetPath(), dbItems); + dbItems.SetFastLookup(true); + } + + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr pItem = items[i]; + CFileItemPtr match; + + if (pItem->m_bIsFolder && !pItem->IsParentFolder()) + { + // we need this for enabling the right context menu entries, like mark watched / unwatched + pItem->SetProperty("IsVideoFolder", true); + } + + if (!content + .empty()) /* optical media will be stacked down, so it's path won't match the base path */ + { + std::string pathToMatch = + pItem->IsOpticalMediaFile() ? pItem->GetLocalMetadataPath() : pItem->GetPath(); + if (URIUtils::IsMultiPath(pathToMatch)) + pathToMatch = CMultiPathDirectory::GetFirstPath(pathToMatch); + match = dbItems.Get(pathToMatch); + } + if (match) + { + pItem->UpdateInfo(*match, replaceLabels); + + if (stackItems) + { + if (match->m_bIsFolder) + pItem->SetPath(match->GetVideoInfoTag()->m_strPath); + else + pItem->SetPath(match->GetVideoInfoTag()->m_strFileNameAndPath); + // if we switch from a file to a folder item it means we really shouldn't be sorting files and + // folders separately + if (pItem->m_bIsFolder != match->m_bIsFolder) + { + items.SetSortIgnoreFolders(true); + pItem->m_bIsFolder = match->m_bIsFolder; + } + } + } + else + { + /* NOTE: Currently we GetPlayCounts on our items regardless of whether content is set + as if content is set, GetItemsForPaths doesn't return anything not in the content tables. + This code can be removed once the content tables are always filled */ + if (!pItem->m_bIsFolder && !fetchedPlayCounts) + { + database.GetPlayCounts(items.GetPath(), items); + fetchedPlayCounts = true; + } + + // set the watched overlay + if (pItem->IsVideo()) + pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, + pItem->HasVideoInfoTag() && + pItem->GetVideoInfoTag()->GetPlayCount() > 0); + } + } +} + +std::string CGUIWindowVideoBase::GetResumeString(const CFileItem &item) +{ + const VIDEO_UTILS::ResumeInformation resumeInfo = VIDEO_UTILS::GetItemResumeInformation(item); + if (resumeInfo.isResumable) + { + if (resumeInfo.startOffset > 0) + { + std::string resumeString = StringUtils::Format( + g_localizeStrings.Get(12022), + StringUtils::SecondsToTimeString( + static_cast<long>(CUtil::ConvertMilliSecsToSecsInt(resumeInfo.startOffset)), + TIME_FORMAT_HH_MM_SS)); + if (resumeInfo.partNumber > 0) + { + const std::string partString = + StringUtils::Format(g_localizeStrings.Get(23051), resumeInfo.partNumber); + resumeString += " (" + partString + ")"; + } + return resumeString; + } + else + { + return g_localizeStrings.Get(13362); // Continue watching + } + } + return {}; +} + +bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item) +{ + if (!item.IsLiveTV()) + { + std::string resumeString = GetResumeString(item); + if (!resumeString.empty()) + { // prompt user whether they wish to resume + CContextButtons choices; + choices.Add(1, resumeString); + choices.Add(2, 12021); // Play from beginning + int retVal = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (retVal < 0) + return false; // don't do anything + if (retVal == 1) + item.SetStartOffset(STARTOFFSET_RESUME); + } + } + return true; +} + +bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) return true; + CFileItemPtr item = m_vecItems->Get(iItem); + + std::string resumeString = GetResumeString(*item); + + if (!resumeString.empty()) + { + CContextButtons choices; + choices.Add(SELECT_ACTION_RESUME, resumeString); + choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning + int value = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (value < 0) + return true; + return OnFileAction(iItem, value, player); + } + + if (item->m_bIsFolder) + { + // resuming directories isn't fully supported yet. play all of its content. + PlayItem(iItem, player); + return true; + } + + return OnFileAction(iItem, SELECT_ACTION_PLAY, player); +} + +void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + + // contextual buttons + if (item) + { + if (!item->IsParentFolder()) + { + std::string path(item->GetPath()); + if (item->IsVideoDb() && item->HasVideoInfoTag()) + path = item->GetVideoInfoTag()->m_strFileNameAndPath; + + if (!item->IsPath("add") && !item->IsPlugin() && + !item->IsScript() && !item->IsAddonsPath() && !item->IsLiveTV()) + { + if (URIUtils::IsStack(path)) + { + std::vector<uint64_t> times; + if (m_database.GetStackTimes(path,times) || CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage()) + buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324); + } + } + + if (!item->m_bIsFolder && !(item->IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) + { + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // get players + std::vector<std::string> players; + if (item->IsVideoDb()) + { + CFileItem item2(item->GetVideoInfoTag()->m_strFileNameAndPath, false); + playerCoreFactory.GetPlayers(item2, players); + } + else + playerCoreFactory.GetPlayers(*item, players); + + if (players.size() > 1) + buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); + } + if (item->IsSmartPlayList()) + { + buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode + } + + // if the item isn't a folder or script, is not explicitly marked as not playable, + // is a member of a list rather than a single item and we're not on the last element of the list, + // then add either 'play from here' or 'play only this' depending on default behaviour + if (!(item->m_bIsFolder || item->IsScript()) && + (!item->HasProperty("IsPlayable") || item->GetProperty("IsPlayable").asBoolean()) && + m_vecItems->Size() > 1 && itemNumber < m_vecItems->Size() - 1) + { + int settingValue = SETTING_AUTOPLAYNEXT_UNCATEGORIZED; + + if (item->IsVideoDb() && item->HasVideoInfoTag()) + { + const std::string mediaType = item->GetVideoInfoTag()->m_type; + + if (mediaType == MediaTypeMusicVideo) + settingValue = SETTING_AUTOPLAYNEXT_MUSICVIDEOS; + else if (mediaType == MediaTypeEpisode) + settingValue = SETTING_AUTOPLAYNEXT_EPISODES; + else if (mediaType == MediaTypeMovie) + settingValue = SETTING_AUTOPLAYNEXT_MOVIES; + } + + const auto setting = std::dynamic_pointer_cast<CSettingList>( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting( + CSettings::SETTING_VIDEOPLAYER_AUTOPLAYNEXTITEM)); + + if (setting && CSettingUtils::FindIntInList(setting, settingValue)) + buttons.Add(CONTEXT_BUTTON_PLAY_ONLY_THIS, 13434); + else + buttons.Add(CONTEXT_BUTTON_PLAY_AND_QUEUE, 13412); + } + if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList()) + buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586); + } + } + CGUIMediaWindow::GetContextButtons(itemNumber, buttons); +} + +bool CGUIWindowVideoBase::OnPlayStackPart(int iItem) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return false; + + CFileItemPtr stack = m_vecItems->Get(iItem); + std::string path(stack->GetPath()); + if (stack->IsVideoDb()) + path = stack->GetVideoInfoTag()->m_strFileNameAndPath; + + if (!URIUtils::IsStack(path)) + return false; + + CFileItemList parts; + CDirectory::GetDirectory(path, parts, "", DIR_FLAG_DEFAULTS); + + for (int i = 0; i < parts.Size(); i++) + parts[i]->SetLabel(StringUtils::Format(g_localizeStrings.Get(23051), i + 1)); + + CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + + pDialog->Reset(); + pDialog->SetHeading(CVariant{20324}); + pDialog->SetItems(parts); + pDialog->Open(); + + if (!pDialog->IsConfirmed()) + return false; + + int selectedFile = pDialog->GetSelectedItem(); + if (selectedFile >= 0) + { + // ISO stack + if (CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage()) + { + std::string resumeString = CGUIWindowVideoBase::GetResumeString(*(parts[selectedFile].get())); + stack->SetStartOffset(0); + if (!resumeString.empty()) + { + CContextButtons choices; + choices.Add(SELECT_ACTION_RESUME, resumeString); + choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning + int value = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (value == SELECT_ACTION_RESUME) + { + const VIDEO_UTILS::ResumeInformation resumeInfo = + VIDEO_UTILS::GetItemResumeInformation(*parts[selectedFile]); + stack->SetStartOffset(resumeInfo.startOffset); + stack->m_lStartPartNumber = resumeInfo.partNumber; + } + else if (value != SELECT_ACTION_PLAY) + return false; // if not selected PLAY, then we changed our mind so return + } + stack->m_lStartPartNumber = selectedFile + 1; + } + // regular stack + else + { + if (selectedFile > 0) + { + std::vector<uint64_t> times; + if (m_database.GetStackTimes(path,times)) + stack->SetStartOffset(times[selectedFile - 1]); + } + else + stack->SetStartOffset(0); + } + + + } + + return true; +} + +bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + switch (button) + { + case CONTEXT_BUTTON_SET_CONTENT: + { + OnAssignContent(item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strPath.empty() ? item->GetVideoInfoTag()->m_strPath : item->GetPath()); + return true; + } + case CONTEXT_BUTTON_PLAY_PART: + { + if (OnPlayStackPart(itemNumber)) + { + // call CGUIMediaWindow::OnClick() as otherwise autoresume will kick in + CGUIMediaWindow::OnClick(itemNumber); + return true; + } + else + return false; + } + case CONTEXT_BUTTON_PLAY_WITH: + { + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + std::vector<std::string> players; + if (item->IsVideoDb()) + { + CFileItem item2(*item->GetVideoInfoTag()); + playerCoreFactory.GetPlayers(item2, players); + } + else + playerCoreFactory.GetPlayers(*item, players); + + std:: string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + { + // any other select actions but play or resume, resume, play or playpart + // don't make any sense here since the user already decided that he'd + // like to play the item (just with a specific player) + VideoSelectAction selectAction = (VideoSelectAction)CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION); + if (selectAction != SELECT_ACTION_PLAY_OR_RESUME && + selectAction != SELECT_ACTION_RESUME && + selectAction != SELECT_ACTION_PLAY && + selectAction != SELECT_ACTION_PLAYPART) + selectAction = SELECT_ACTION_PLAY_OR_RESUME; + return OnFileAction(itemNumber, selectAction, player); + } + return true; + } + + case CONTEXT_BUTTON_PLAY_PARTYMODE: + g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO, m_vecItems->Get(itemNumber)->GetPath()); + return true; + + case CONTEXT_BUTTON_SCAN: + { + if( !item) + return false; + ADDON::ScraperPtr info; + SScanSettings settings; + GetScraperForItem(item.get(), info, settings); + std::string strPath = item->GetPath(); + if (item->IsVideoDb() && (!item->m_bIsFolder || item->GetVideoInfoTag()->m_strPath.empty())) + return false; + + if (item->IsVideoDb()) + strPath = item->GetVideoInfoTag()->m_strPath; + + if (!info || info->Content() == CONTENT_NONE) + return false; + + if (item->m_bIsFolder) + { + OnScan(strPath, true); + } + else + OnItemInfo(*item, info); + + return true; + } + case CONTEXT_BUTTON_DELETE: + OnDeleteItem(itemNumber); + return true; + case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST: + { + std::string playlist = m_vecItems->Get(itemNumber)->IsSmartPlayList() ? m_vecItems->Get(itemNumber)->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "video")) + Refresh(true); // need to update + return true; + } + case CONTEXT_BUTTON_RENAME: + OnRenameItem(itemNumber); + return true; + case CONTEXT_BUTTON_PLAY_AND_QUEUE: + return OnPlayAndQueueMedia(item); + case CONTEXT_BUTTON_PLAY_ONLY_THIS: + return OnPlayMedia(itemNumber); + default: + break; + } + return CGUIMediaWindow::OnContextButton(itemNumber, button); +} + +bool CGUIWindowVideoBase::OnPlayMedia(int iItem, const std::string &player) +{ + if ( iItem < 0 || iItem >= m_vecItems->Size() ) + return false; + + CFileItemPtr pItem = m_vecItems->Get(iItem); + + // party mode + if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO)) + { + PLAYLIST::CPlayList playlistTemp; + playlistTemp.Add(pItem); + g_partyModeManager.AddUserSongs(playlistTemp, true); + return true; + } + + // Reset Playlistplayer, playback started now does + // not use the playlistplayer. + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); + + CFileItem item(*pItem); + if (pItem->IsVideoDb()) + { + item.SetPath(pItem->GetVideoInfoTag()->m_strFileNameAndPath); + item.SetProperty("original_listitem_url", pItem->GetPath()); + } + CLog::Log(LOGDEBUG, "{} {}", __FUNCTION__, CURL::GetRedacted(item.GetPath())); + + item.SetProperty("playlist_type_hint", m_guiState->GetPlaylist()); + + PlayMovie(&item, player); + + return true; +} + +bool CGUIWindowVideoBase::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player) +{ + // Get the current playlist and make sure it is not shuffled + PLAYLIST::Id playlistId = m_guiState->GetPlaylist(); + if (playlistId != PLAYLIST::TYPE_NONE && + CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId)) + { + CServiceBroker::GetPlaylistPlayer().SetShuffle(playlistId, false); + } + + CFileItemPtr movieItem(new CFileItem(*item)); + + // Call the base method to actually queue the items + // and start playing the given item + return CGUIMediaWindow::OnPlayAndQueueMedia(movieItem, player); +} + +void CGUIWindowVideoBase::PlayMovie(const CFileItem *item, const std::string &player) +{ + if(m_thumbLoader.IsLoading()) + m_thumbLoader.StopAsync(); + + CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(*item), player); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlayingVideo()) + m_thumbLoader.Load(*m_vecItems); +} + +void CGUIWindowVideoBase::OnDeleteItem(int iItem) +{ + if ( iItem < 0 || iItem >= m_vecItems->Size()) + return; + + OnDeleteItem(m_vecItems->Get(iItem)); + + Refresh(true); + m_viewControl.SetSelectedItem(iItem); +} + +void CGUIWindowVideoBase::OnDeleteItem(const CFileItemPtr& item) +{ + // HACK: stacked files need to be treated as folders in order to be deleted + if (item->IsStack()) + item->m_bIsFolder = true; + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && + profileManager->GetCurrentProfile().filesLocked()) + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return; + } + + if ((CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) || + m_vecItems->IsPath("special://videoplaylists/")) && + CUtil::SupportsWriteFileOperations(item->GetPath())) + { + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui && gui->ConfirmDelete(item->GetPath())) + CFileUtils::DeleteItem(item); + } +} + +void CGUIWindowVideoBase::LoadPlayList(const std::string& strPlayList, + PLAYLIST::Id playlistId /* = PLAYLIST::TYPE_VIDEO */) +{ + // if partymode is active, we disable it + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + + // load a playlist like .m3u, .pls + // first get correct factory to load playlist + std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList)); + if (pPlayList) + { + // load it + if (!pPlayList->Load(strPlayList)) + { + HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477}); + return; //hmmm unable to load playlist? + } + } + + if (g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, playlistId)) + { + if (m_guiState) + m_guiState->SetPlaylistDirectory("playlistvideo://"); + } +} + +void CGUIWindowVideoBase::PlayItem(int iItem, const std::string &player) +{ + // restrictions should be placed in the appropriate window code + // only call the base code if the item passes since this clears + // the currently playing temp playlist + + const CFileItemPtr pItem = m_vecItems->Get(iItem); + // if its a folder, build a temp playlist + if (pItem->m_bIsFolder && !pItem->IsPlugin()) + { + // take a copy so we can alter the queue state + CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem))); + + // Allow queuing of unqueueable items + // when we try to queue them directly + if (!item->CanQueue()) + item->SetCanQueue(true); + + // skip ".." + if (item->IsParentFolder()) + return; + + // recursively add items to list + CFileItemList queuedItems; + VIDEO_UTILS::GetItemsForPlayList(item, queuedItems); + + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::TYPE_VIDEO); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::TYPE_VIDEO, queuedItems); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); + CServiceBroker::GetPlaylistPlayer().Play(); + } + else if (pItem->IsPlayList()) + { + // load the playlist the old way + LoadPlayList(pItem->GetPath(), PLAYLIST::TYPE_VIDEO); + } + else + { + // single item, play it + OnClick(iItem, player); + } +} + +bool CGUIWindowVideoBase::Update(const std::string &strDirectory, bool updateFilterPath /* = true */) +{ + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + + if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath)) + return false; + + // might already be running from GetGroupedItems + if (!m_thumbLoader.IsLoading()) + m_thumbLoader.Load(*m_vecItems); + + return true; +} + +bool CGUIWindowVideoBase::GetDirectory(const std::string &strDirectory, CFileItemList &items) +{ + bool bResult = CGUIMediaWindow::GetDirectory(strDirectory, items); + + // add in the "New Playlist" item if we're in the playlists folder + if ((items.GetPath() == "special://videoplaylists/") && !items.Contains("newplaylist://")) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + CFileItemPtr newPlaylist(new CFileItem(profileManager->GetUserDataItem("PartyMode-Video.xsp"),false)); + newPlaylist->SetLabel(g_localizeStrings.Get(16035)); + newPlaylist->SetLabelPreformatted(true); + newPlaylist->SetArt("icon", "DefaultPartyMode.png"); + newPlaylist->m_bIsFolder = true; + items.Add(newPlaylist); + +/* newPlaylist.reset(new CFileItem("newplaylist://", false)); + newPlaylist->SetLabel(g_localizeStrings.Get(525)); + newPlaylist->SetLabelPreformatted(true); + items.Add(newPlaylist); +*/ + newPlaylist.reset(new CFileItem("newsmartplaylist://video", false)); + newPlaylist->SetLabel(g_localizeStrings.Get(21437)); // "new smart playlist..." + newPlaylist->SetArt("icon", "DefaultAddSource.png"); + newPlaylist->SetLabelPreformatted(true); + items.Add(newPlaylist); + } + + m_stackingAvailable = StackingAvailable(items); + // we may also be in a tvshow files listing + // (ideally this should be removed, and our stack regexps tidied up if necessary + // No "normal" episodes should stack, and multi-parts should be supported) + ADDON::ScraperPtr info = m_database.GetScraperForPath(strDirectory); + if (info && info->Content() == CONTENT_TVSHOWS) + m_stackingAvailable = false; + + if (m_stackingAvailable && !items.IsStack() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_STACKVIDEOS)) + items.Stack(); + + return bResult; +} + +bool CGUIWindowVideoBase::StackingAvailable(const CFileItemList &items) +{ + CURL url(items.GetPath()); + return !(items.IsPlugin() || items.IsAddonsPath() || + items.IsRSS() || items.IsInternetStream() || + items.IsVideoDb() || url.IsProtocol("playlistvideo")); +} + +void CGUIWindowVideoBase::GetGroupedItems(CFileItemList &items) +{ + CGUIMediaWindow::GetGroupedItems(items); + + std::string group; + bool mixed = false; + if (items.HasProperty(PROPERTY_GROUP_BY)) + group = items.GetProperty(PROPERTY_GROUP_BY).asString(); + if (items.HasProperty(PROPERTY_GROUP_MIXED)) + mixed = items.GetProperty(PROPERTY_GROUP_MIXED).asBoolean(); + + // group == "none" completely suppresses any grouping + if (!StringUtils::EqualsNoCase(group, "none")) + { + CQueryParams params; + CVideoDatabaseDirectory dir; + dir.GetQueryParams(items.GetPath(), params); + VIDEODATABASEDIRECTORY::NODE_TYPE nodeType = CVideoDatabaseDirectory::GetDirectoryChildType(m_strFilterPath); + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (items.GetContent() == "movies" && params.GetSetId() <= 0 && + nodeType == NODE_TYPE_TITLE_MOVIES && + (settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS) || (StringUtils::EqualsNoCase(group, "sets") && mixed))) + { + CFileItemList groupedItems; + GroupAttribute groupAttributes = settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS) ? GroupAttributeNone : GroupAttributeIgnoreSingleItems; + if (GroupUtils::GroupAndMix(GroupBySet, m_strFilterPath, items, groupedItems, groupAttributes)) + { + items.ClearItems(); + items.Append(groupedItems); + } + } + } + + // reload thumbs after filtering and grouping + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + + m_thumbLoader.Load(items); +} + +bool CGUIWindowVideoBase::CheckFilterAdvanced(CFileItemList &items) const +{ + const std::string& content = items.GetContent(); + if ((items.IsVideoDb() || CanContainFilter(m_strFilterPath)) && + (StringUtils::EqualsNoCase(content, "movies") || + StringUtils::EqualsNoCase(content, "tvshows") || + StringUtils::EqualsNoCase(content, "episodes") || + StringUtils::EqualsNoCase(content, "musicvideos"))) + return true; + + return false; +} + +bool CGUIWindowVideoBase::CanContainFilter(const std::string &strDirectory) const +{ + return URIUtils::IsProtocol(strDirectory, "videodb://"); +} + +/// \brief Search the current directory for a string got from the virtual keyboard +void CGUIWindowVideoBase::OnSearch() +{ + std::string strSearch; + if (!CGUIKeyboardFactory::ShowAndGetInput(strSearch, CVariant{g_localizeStrings.Get(16017)}, false)) + return ; + + StringUtils::ToLower(strSearch); + if (m_dlgProgress) + { + m_dlgProgress->SetHeading(CVariant{194}); + m_dlgProgress->SetLine(0, CVariant{strSearch}); + m_dlgProgress->SetLine(1, CVariant{""}); + m_dlgProgress->SetLine(2, CVariant{""}); + m_dlgProgress->Open(); + m_dlgProgress->Progress(); + } + CFileItemList items; + DoSearch(strSearch, items); + + if (m_dlgProgress) + m_dlgProgress->Close(); + + if (items.Size()) + { + CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + pDlgSelect->Reset(); + pDlgSelect->SetHeading(CVariant{283}); + + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr pItem = items[i]; + pDlgSelect->Add(pItem->GetLabel()); + } + + pDlgSelect->Open(); + + int iItem = pDlgSelect->GetSelectedItem(); + if (iItem < 0) + return; + + OnSearchItemFound(items[iItem].get()); + } + else + { + HELPERS::ShowOKDialogText(CVariant{194}, CVariant{284}); + } +} + +/// \brief React on the selected search item +/// \param pItem Search result item +void CGUIWindowVideoBase::OnSearchItemFound(const CFileItem* pSelItem) +{ + if (pSelItem->m_bIsFolder) + { + std::string strPath = pSelItem->GetPath(); + std::string strParentPath; + URIUtils::GetParentPath(strPath, strParentPath); + + Update(strParentPath); + + if (pSelItem->IsVideoDb() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN)) + SetHistoryForPath(""); + else + SetHistoryForPath(strParentPath); + + strPath = pSelItem->GetPath(); + CURL url(strPath); + if (pSelItem->IsSmb() && !URIUtils::HasSlashAtEnd(strPath)) + strPath += "/"; + + for (int i = 0; i < m_vecItems->Size(); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + if (pItem->GetPath() == strPath) + { + m_viewControl.SetSelectedItem(i); + break; + } + } + } + else + { + std::string strPath = URIUtils::GetDirectory(pSelItem->GetPath()); + + Update(strPath); + + if (pSelItem->IsVideoDb() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN)) + SetHistoryForPath(""); + else + SetHistoryForPath(strPath); + + for (int i = 0; i < m_vecItems->Size(); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + CURL url(pItem->GetPath()); + if (pSelItem->IsVideoDb()) + url.SetOptions(""); + if (url.Get() == pSelItem->GetPath()) + { + m_viewControl.SetSelectedItem(i); + break; + } + } + } + m_viewControl.SetFocused(); +} + +int CGUIWindowVideoBase::GetScraperForItem(CFileItem *item, ADDON::ScraperPtr &info, SScanSettings& settings) +{ + if (!item) + return 0; + + if (m_vecItems->IsPlugin() || m_vecItems->IsRSS()) + { + info.reset(); + return 0; + } + else if(m_vecItems->IsLiveTV()) + { + info.reset(); + return 0; + } + + bool foundDirectly = false; + info = m_database.GetScraperForPath(item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strPath.empty() ? item->GetVideoInfoTag()->m_strPath : item->GetPath(), settings, foundDirectly); + return foundDirectly ? 1 : 0; +} + +void CGUIWindowVideoBase::OnScan(const std::string& strPath, bool scanAll) +{ + CVideoLibraryQueue::GetInstance().ScanLibrary(strPath, scanAll, true); +} + +std::string CGUIWindowVideoBase::GetStartFolder(const std::string &dir) +{ + std::string lower(dir); StringUtils::ToLower(lower); + if (lower == "$playlists" || lower == "playlists") + return "special://videoplaylists/"; + else if (lower == "plugins" || lower == "addons") + return "addons://sources/video/"; + return CGUIMediaWindow::GetStartFolder(dir); +} + +void CGUIWindowVideoBase::AppendAndClearSearchItems(CFileItemList &searchItems, const std::string &prependLabel, CFileItemList &results) +{ + if (!searchItems.Size()) + return; + + searchItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + for (int i = 0; i < searchItems.Size(); i++) + searchItems[i]->SetLabel(prependLabel + searchItems[i]->GetLabel()); + results.Append(searchItems); + + searchItems.Clear(); +} + +bool CGUIWindowVideoBase::OnUnAssignContent(const std::string &path, int header, int text) +{ + bool bCanceled; + CVideoDatabase db; + db.Open(); + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{header}, CVariant{text}, bCanceled, CVariant{ "" }, CVariant{ "" }, CGUIDialogYesNo::NO_TIMEOUT)) + { + CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + db.RemoveContentForPath(path, progress); + db.Close(); + CUtil::DeleteVideoDatabaseDirectoryCache(); + return true; + } + else + { + if (!bCanceled) + { + ADDON::ScraperPtr info; + SScanSettings settings; + settings.exclude = true; + db.SetScraperForPath(path,info,settings); + } + } + db.Close(); + + return false; +} + +void CGUIWindowVideoBase::OnAssignContent(const std::string &path) +{ + bool bScan=false; + CVideoDatabase db; + db.Open(); + + SScanSettings settings; + ADDON::ScraperPtr info = db.GetScraperForPath(path, settings); + + ADDON::ScraperPtr info2(info); + + if (CGUIDialogContentSettings::Show(info, settings)) + { + if(settings.exclude || (!info && info2)) + { + OnUnAssignContent(path, 20375, 20340); + } + else if (info != info2) + { + if (OnUnAssignContent(path, 20442, 20443)) + bScan = true; + } + db.SetScraperForPath(path, info, settings); + } + + if (bScan) + { + CVideoLibraryQueue::GetInstance().ScanLibrary(path, true, true); + } +} diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h new file mode 100644 index 0000000..0c5acf4 --- /dev/null +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2005-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. + */ + +#pragma once + +#include "playlists/PlayListTypes.h" +#include "video/VideoDatabase.h" +#include "video/VideoThumbLoader.h" +#include "windows/GUIMediaWindow.h" + +enum VideoSelectAction +{ + SELECT_ACTION_CHOOSE = 0, + SELECT_ACTION_PLAY_OR_RESUME, + SELECT_ACTION_RESUME, + SELECT_ACTION_INFO, + SELECT_ACTION_MORE, + SELECT_ACTION_PLAY, + SELECT_ACTION_PLAYPART, + SELECT_ACTION_QUEUE +}; + +class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObserver +{ +public: + CGUIWindowVideoBase(int id, const std::string &xmlFile); + ~CGUIWindowVideoBase(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + + void PlayMovie(const CFileItem* item, const std::string& player = ""); + + /*! \brief Gets called to process the "info" action for the given file item + Default implementation shows a dialog containing information for the movie/episode/... + represented by the file item. + \param fileItem the item for which information is to be presented. + \param scraper a scraper addon instance that can be used to obtain additional information for + the given item + \return true if information was presented, false otherwise. + */ + virtual bool OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper); + + /*! \brief Show the resume menu for this item (if it has a resume bookmark) + If a resume bookmark is found, we set the item's m_lStartOffset to STARTOFFSET_RESUME. + Note that we do this in favour of setting the resume point, as we need additional + information from the database (in particular, the playerState) when resuming some items + (eg ISO/VIDEO_TS). + \param item item to check for a resume bookmark + \return true if an option was chosen, false if the resume menu was cancelled. + */ + static bool ShowResumeMenu(CFileItem &item); + + /*! \brief Append a set of search items to a results list using a specific prepend label + Sorts the search items first, then appends with the given prependLabel to the results list. + Then empty the search item list so it can be refilled. + \param searchItems The search items to append. + \param prependLabel the label that should be prepended to all search results. + \param results the fileitemlist to append the search results to. + \sa DoSearch + */ + static void AppendAndClearSearchItems(CFileItemList &searchItems, const std::string &prependLabel, CFileItemList &results); + + /*! \brief Prompt the user for assigning content to a path. + Based on changes, we then call OnUnassignContent, update or refresh scraper information in the database + and optionally start a scan + \param path the path to assign content for + */ + static void OnAssignContent(const std::string &path); + + /*! \brief checks the database for a resume position and puts together a string + \param item selected item + \return string containing the resume position or an empty string if there is no resume position + */ + static std::string GetResumeString(const CFileItem &item); + + /*! \brief Load video information from the database for these items (public static version) + Useful for grabbing information for file listings, from watched status to full metadata + \param items the items to load information for. + \param database open database object to retrieve the data from + \param allowReplaceLabels allow label replacement if according GUI setting is enabled + */ + static void LoadVideoInfo(CFileItemList& items, + CVideoDatabase& database, + bool allowReplaceLabels = true); + +protected: + void OnScan(const std::string& strPath, bool scanAll = false); + bool Update(const std::string &strDirectory, bool updateFilterPath = true) override; + bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; + void OnItemLoaded(CFileItem* pItem) override {}; + void GetGroupedItems(CFileItemList &items) override; + + bool CheckFilterAdvanced(CFileItemList &items) const override; + bool CanContainFilter(const std::string &strDirectory) const override; + + void GetContextButtons(int itemNumber, CContextButtons &buttons) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + virtual void OnQueueItem(int iItem, bool first = false); + virtual void OnDeleteItem(const CFileItemPtr& pItem); + void OnDeleteItem(int iItem) override; + virtual void DoSearch(const std::string& strSearch, CFileItemList& items) {} + std::string GetStartFolder(const std::string &dir) override; + + bool OnClick(int iItem, const std::string &player = "") override; + bool OnSelect(int iItem) override; + /*! \brief react to an Info action on a view item + \param item the selected item + \return true if the action is performed, false otherwise + */ + bool OnItemInfo(int item); + /*! \brief perform a given action on a file + \param item the selected item + \param action the action to perform + \return true if the action is performed, false otherwise + */ + bool OnFileAction(int item, int action, const std::string& player); + + void OnRestartItem(int iItem, const std::string &player = ""); + bool OnResumeItem(int iItem, const std::string &player = ""); + void PlayItem(int iItem, const std::string &player = ""); + bool OnPlayMedia(int iItem, const std::string &player = "") override; + bool OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player = "") override; + using CGUIMediaWindow::LoadPlayList; + void LoadPlayList(const std::string& strPlayList, PLAYLIST::Id playlistId = PLAYLIST::TYPE_VIDEO); + + bool ShowIMDB(CFileItemPtr item, const ADDON::ScraperPtr& content, bool fromDB); + + void OnSearch(); + void OnSearchItemFound(const CFileItem* pSelItem); + int GetScraperForItem(CFileItem *item, ADDON::ScraperPtr &info, VIDEO::SScanSettings& settings); + + static bool OnUnAssignContent(const std::string &path, int header, int text); + + static bool StackingAvailable(const CFileItemList &items); + + bool OnPlayStackPart(int item); + + CGUIDialogProgress* m_dlgProgress; + CVideoDatabase m_database; + + CVideoThumbLoader m_thumbLoader; + bool m_stackingAvailable; +}; 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; +} diff --git a/xbmc/video/windows/GUIWindowVideoNav.h b/xbmc/video/windows/GUIWindowVideoNav.h new file mode 100644 index 0000000..bdf4987 --- /dev/null +++ b/xbmc/video/windows/GUIWindowVideoNav.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-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. + */ + +#pragma once + +#include "GUIWindowVideoBase.h" + +class CFileItemList; + +enum SelectFirstUnwatchedItem +{ + NEVER = 0, + ON_FIRST_ENTRY = 1, + ALWAYS = 2 +}; + +enum IncludeAllSeasonsAndSpecials +{ + NEITHER = 0, + BOTH = 1, + ALL_SEASONS = 2, + SPECIALS = 3 +}; + +class CGUIWindowVideoNav : public CGUIWindowVideoBase +{ +public: + + CGUIWindowVideoNav(void); + ~CGUIWindowVideoNav(void) override; + + bool OnAction(const CAction &action) override; + bool OnMessage(CGUIMessage& message) override; + + bool OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& info) override; + +protected: + bool ApplyWatchedFilter(CFileItemList &items); + bool GetFilteredItems(const std::string &filter, CFileItemList &items) override; + + void OnItemLoaded(CFileItem* pItem) override {}; + + // override base class methods + bool Update(const std::string &strDirectory, bool updateFilterPath = true) override; + bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; + void UpdateButtons() override; + void DoSearch(const std::string& strSearch, CFileItemList& items) override; + virtual void PlayItem(int iItem); + void OnDeleteItem(const CFileItemPtr& pItem) override; + void GetContextButtons(int itemNumber, CContextButtons &buttons) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + bool OnAddMediaSource() override; + bool OnClick(int iItem, const std::string &player = "") override; + std::string GetStartFolder(const std::string &dir) override; + + VECSOURCES m_shares; + +private: + virtual SelectFirstUnwatchedItem GetSettingSelectFirstUnwatchedItem(); + virtual IncludeAllSeasonsAndSpecials GetSettingIncludeAllSeasonsAndSpecials(); + virtual int GetFirstUnwatchedItemIndex(bool includeAllSeasons, bool includeSpecials); + void SelectFirstUnwatched(); +}; diff --git a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp new file mode 100644 index 0000000..b27582d --- /dev/null +++ b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2005-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 "GUIWindowVideoPlaylist.h" + +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "dialogs/GUIDialogSmartPlaylistEditor.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "playlists/PlayListM3U.h" +#include "settings/MediaSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 + +#define CONTROL_BTNSHUFFLE 20 +#define CONTROL_BTNSAVE 21 +#define CONTROL_BTNCLEAR 22 + +#define CONTROL_BTNPLAY 23 +#define CONTROL_BTNNEXT 24 +#define CONTROL_BTNPREVIOUS 25 +#define CONTROL_BTNREPEAT 26 + +CGUIWindowVideoPlaylist::CGUIWindowVideoPlaylist() +: CGUIWindowVideoBase(WINDOW_VIDEO_PLAYLIST, "MyPlaylist.xml") +{ + m_movingFrom = -1; +} + +CGUIWindowVideoPlaylist::~CGUIWindowVideoPlaylist() = default; + +void CGUIWindowVideoPlaylist::OnPrepareFileItems(CFileItemList& items) +{ + CGUIWindowVideoBase::OnPrepareFileItems(items); + + if (items.IsEmpty()) + return; + + if (!items.IsVideoDb() && !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); + } +} + +bool CGUIWindowVideoPlaylist::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_PLAYLISTPLAYER_REPEAT: + { + UpdateButtons(); + } + break; + + case GUI_MSG_PLAYLISTPLAYER_RANDOM: + case GUI_MSG_PLAYLIST_CHANGED: + { + // global playlist changed outside playlist window + UpdateButtons(); + Refresh(true); + + if (m_viewControl.HasControl(m_iLastControl) && m_vecItems->Size() <= 0) + { + m_iLastControl = CONTROL_BTNVIEWASICONS; + SET_CONTROL_FOCUS(m_iLastControl, 0); + } + + } + break; + + case GUI_MSG_WINDOW_DEINIT: + { + m_movingFrom = -1; + } + break; + + case GUI_MSG_WINDOW_INIT: + { + m_vecItems->SetPath("playlistvideo://"); + + if (!CGUIWindowVideoBase::OnMessage(message)) + return false; + + if (m_vecItems->Size() <= 0) + { + m_iLastControl = CONTROL_BTNVIEWASICONS; + SET_CONTROL_FOCUS(m_iLastControl, 0); + } + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlayingVideo() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO) + { + int iSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (iSong >= 0 && iSong <= m_vecItems->Size()) + m_viewControl.SetSelectedItem(iSong); + } + + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTNSHUFFLE) + { + if (!g_partyModeManager.IsEnabled()) + { + CServiceBroker::GetPlaylistPlayer().SetShuffle( + PLAYLIST::TYPE_VIDEO, + !(CServiceBroker::GetPlaylistPlayer().IsShuffled(PLAYLIST::TYPE_VIDEO))); + CMediaSettings::GetInstance().SetVideoPlaylistShuffled( + CServiceBroker::GetPlaylistPlayer().IsShuffled(PLAYLIST::TYPE_VIDEO)); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + UpdateButtons(); + Refresh(); + } + } + else if (iControl == CONTROL_BTNSAVE) + { + SavePlayList(); + } + else if (iControl == CONTROL_BTNCLEAR) + { + ClearPlayList(); + } + else if (iControl == CONTROL_BTNPLAY) + { + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().Play(m_viewControl.GetSelectedItem(), ""); + UpdateButtons(); + } + else if (iControl == CONTROL_BTNNEXT) + { + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); + CServiceBroker::GetPlaylistPlayer().PlayNext(); + } + else if (iControl == CONTROL_BTNPREVIOUS) + { + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); + CServiceBroker::GetPlaylistPlayer().PlayPrevious(); + } + else if (iControl == CONTROL_BTNREPEAT) + { + // increment repeat state + PLAYLIST::RepeatState state = + CServiceBroker::GetPlaylistPlayer().GetRepeat(PLAYLIST::TYPE_VIDEO); + if (state == PLAYLIST::RepeatState::NONE) + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_VIDEO, + PLAYLIST::RepeatState::ALL); + else if (state == PLAYLIST::RepeatState::ALL) + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_VIDEO, + PLAYLIST::RepeatState::ONE); + else + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_VIDEO, + PLAYLIST::RepeatState::NONE); + + // save settings + CMediaSettings::GetInstance().SetVideoPlaylistRepeat( + CServiceBroker::GetPlaylistPlayer().GetRepeat(PLAYLIST::TYPE_VIDEO) == + PLAYLIST::RepeatState::ALL); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + UpdateButtons(); + } + else if (m_viewControl.HasControl(iControl)) // list/thumb control + { + int iAction = message.GetParam1(); + int iItem = m_viewControl.GetSelectedItem(); + if (iAction == ACTION_DELETE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK) + { + RemovePlayListItem(iItem); + MarkPlaying(); + } + } + } + break; + } + return CGUIWindowVideoBase::OnMessage(message); +} + +bool CGUIWindowVideoPlaylist::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_PARENT_DIR) + { + // Playlist has no parent dirs + return true; + } + if (action.GetID() == ACTION_SHOW_PLAYLIST) + { + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return true; + } + if ((action.GetID() == ACTION_MOVE_ITEM_UP) || (action.GetID() == ACTION_MOVE_ITEM_DOWN)) + { + int iItem = -1; + int iFocusedControl = GetFocusedControlID(); + if (m_viewControl.HasControl(iFocusedControl)) + iItem = m_viewControl.GetSelectedItem(); + OnMove(iItem, action.GetID()); + return true; + } + return CGUIWindowVideoBase::OnAction(action); +} + +bool CGUIWindowVideoPlaylist::OnBack(int actionID) +{ + if (actionID == ACTION_NAV_BACK) + return CGUIWindow::OnBack(actionID); // base class goes up a folder, but none to go up + return CGUIWindowVideoBase::OnBack(actionID); +} + +bool CGUIWindowVideoPlaylist::MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate /* = true */) +{ + int iSelected = iItem; + int iNew = iSelected; + if (iAction == ACTION_MOVE_ITEM_UP) + iNew--; + else + iNew++; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // is the currently playing item affected? + bool bFixCurrentSong = false; + if ((CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO) && + appPlayer->IsPlayingVideo() && + ((CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iSelected) || + (CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iNew))) + bFixCurrentSong = true; + + PLAYLIST::CPlayList& playlist = + CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_VIDEO); + if (playlist.Swap(iSelected, iNew)) + { + // Correct the current playing song in playlistplayer + if (bFixCurrentSong) + { + int iCurrentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (iSelected == iCurrentSong) + iCurrentSong = iNew; + else if (iNew == iCurrentSong) + iCurrentSong = iSelected; + CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iCurrentSong); + } + + if (bUpdate) + Refresh(); + return true; + } + + return false; +} + + +void CGUIWindowVideoPlaylist::ClearPlayList() +{ + ClearFileItems(); + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST::TYPE_VIDEO); + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO) + { + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); + } + m_viewControl.SetItems(*m_vecItems); + UpdateButtons(); + SET_CONTROL_FOCUS(CONTROL_BTNVIEWASICONS, 0); +} + +void CGUIWindowVideoPlaylist::UpdateButtons() +{ + // Update playlist buttons + if (m_vecItems->Size() ) + { + CONTROL_ENABLE(CONTROL_BTNCLEAR); + CONTROL_ENABLE(CONTROL_BTNSAVE); + CONTROL_ENABLE(CONTROL_BTNPLAY); + CONTROL_ENABLE(CONTROL_BTNSHUFFLE); + CONTROL_ENABLE(CONTROL_BTNREPEAT); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlayingVideo() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO) + { + CONTROL_ENABLE(CONTROL_BTNNEXT); + CONTROL_ENABLE(CONTROL_BTNPREVIOUS); + } + else + { + CONTROL_DISABLE(CONTROL_BTNNEXT); + CONTROL_DISABLE(CONTROL_BTNPREVIOUS); + } + } + else + { + CONTROL_DISABLE(CONTROL_BTNCLEAR); + CONTROL_DISABLE(CONTROL_BTNSAVE); + CONTROL_DISABLE(CONTROL_BTNSHUFFLE); + CONTROL_DISABLE(CONTROL_BTNPLAY); + CONTROL_DISABLE(CONTROL_BTNNEXT); + CONTROL_DISABLE(CONTROL_BTNPREVIOUS); + CONTROL_DISABLE(CONTROL_BTNREPEAT); + } + + CGUIMediaWindow::UpdateButtons(); + + // update buttons + CONTROL_DESELECT(CONTROL_BTNSHUFFLE); + if (CServiceBroker::GetPlaylistPlayer().IsShuffled(PLAYLIST::TYPE_VIDEO)) + CONTROL_SELECT(CONTROL_BTNSHUFFLE); + + // update repeat button + PLAYLIST::RepeatState repState = + CServiceBroker::GetPlaylistPlayer().GetRepeat(PLAYLIST::TYPE_VIDEO); + int iLocalizedString; + if (repState == PLAYLIST::RepeatState::NONE) + iLocalizedString = 595; // Repeat: Off + else if (repState == PLAYLIST::RepeatState::ONE) + iLocalizedString = 596; // Repeat: One + else + iLocalizedString = 597; // Repeat: All + + SET_CONTROL_LABEL(CONTROL_BTNREPEAT, g_localizeStrings.Get(iLocalizedString)); + + MarkPlaying(); +} + +bool CGUIWindowVideoPlaylist::OnPlayMedia(int iItem, const std::string &player) +{ + if ( iItem < 0 || iItem >= m_vecItems->Size() ) return false; + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Play(iItem); + else + { + CFileItemPtr pItem = m_vecItems->Get(iItem); + std::string strPath = pItem->GetPath(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); + // need to update Playlist FileItem's startOffset and resumePoint based on GUIWindowVideoPlaylist FileItem + if (pItem->GetStartOffset() == STARTOFFSET_RESUME) + { + CFileItemPtr pPlaylistItem = + CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_VIDEO)[iItem]; + pPlaylistItem->SetStartOffset(pItem->GetStartOffset()); + if (pPlaylistItem->HasVideoInfoTag() && pItem->HasVideoInfoTag()) + pPlaylistItem->GetVideoInfoTag()->SetResumePoint(pItem->GetVideoInfoTag()->GetResumePoint()); + } + // now play item + CServiceBroker::GetPlaylistPlayer().Play(iItem, player); + } + return true; +} + +void CGUIWindowVideoPlaylist::RemovePlayListItem(int iItem) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + // The current playing song can't be removed + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO && + appPlayer->IsPlayingVideo() && CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iItem) + return ; + + CServiceBroker::GetPlaylistPlayer().Remove(PLAYLIST::TYPE_VIDEO, iItem); + + Refresh(); + + if (m_vecItems->Size() <= 0) + { + SET_CONTROL_FOCUS(CONTROL_BTNVIEWASICONS, 0); + } + else + { + m_viewControl.SetSelectedItem(iItem - 1); + } + + g_partyModeManager.OnSongChange(); +} + +/// \brief Save current playlist to playlist folder +void CGUIWindowVideoPlaylist::SavePlayList() +{ + std::string strNewFileName; + if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, false)) + { + // need 2 rename it + strNewFileName = CUtil::MakeLegalFileName(strNewFileName); + strNewFileName += ".m3u"; + std::string strPath = URIUtils::AddFileToFolder( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), + "video", + strNewFileName); + + PLAYLIST::CPlayListM3U playlist; + playlist.Add(*m_vecItems); + + CLog::Log(LOGDEBUG, "Saving video playlist: [{}]", strPath); + playlist.Save(strPath); + } +} + +void CGUIWindowVideoPlaylist::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + int itemPlaying = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (m_movingFrom >= 0) + { + if (itemNumber != m_movingFrom && (!g_partyModeManager.IsEnabled() || itemNumber > itemPlaying)) + buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here + buttons.Add(CONTEXT_BUTTON_CANCEL_MOVE, 13253); + + } + else + { + if (itemNumber > -1) + { + CFileItemPtr item = m_vecItems->Get(itemNumber); + + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // check what players we have, if we have multiple display play with option + std::vector<std::string> players; + if (item->IsVideoDb()) + { + CFileItem item2(item->GetVideoInfoTag()->m_strFileNameAndPath, false); + playerCoreFactory.GetPlayers(item2, players); + } + else + playerCoreFactory.GetPlayers(*item, players); + + if (players.size() > 1) + buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... + } + if (itemNumber > (g_partyModeManager.IsEnabled() ? 1 : 0)) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_UP, 13332); + if (itemNumber + 1 < m_vecItems->Size()) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_DOWN, 13333); + if (!g_partyModeManager.IsEnabled() || itemNumber != itemPlaying) + buttons.Add(CONTEXT_BUTTON_MOVE_ITEM, 13251); + + if (itemNumber != itemPlaying) + buttons.Add(CONTEXT_BUTTON_DELETE, 15015); + } + if (g_partyModeManager.IsEnabled()) + { + buttons.Add(CONTEXT_BUTTON_EDIT_PARTYMODE, 21439); + buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode + } +} + +bool CGUIWindowVideoPlaylist::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + switch (button) + { + case CONTEXT_BUTTON_PLAY_WITH: + { + CFileItemPtr item; + if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) + item = m_vecItems->Get(itemNumber); + if (!item) + break; + + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + std::vector<std::string> players; + if (item->IsVideoDb()) + { + CFileItem item2(*item->GetVideoInfoTag()); + playerCoreFactory.GetPlayers(item2, players); + } + else + playerCoreFactory.GetPlayers(*item, players); + + std::string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + OnClick(itemNumber, player); + return true; + } + + case CONTEXT_BUTTON_MOVE_ITEM: + m_movingFrom = itemNumber; + return true; + + case CONTEXT_BUTTON_MOVE_HERE: + if (m_movingFrom >= 0) + MoveItem(m_movingFrom, itemNumber); + m_movingFrom = -1; + return true; + + case CONTEXT_BUTTON_CANCEL_MOVE: + m_movingFrom = -1; + return true; + + case CONTEXT_BUTTON_MOVE_ITEM_UP: + OnMove(itemNumber, ACTION_MOVE_ITEM_UP); + return true; + + case CONTEXT_BUTTON_MOVE_ITEM_DOWN: + OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN); + return true; + + case CONTEXT_BUTTON_DELETE: + RemovePlayListItem(itemNumber); + return true; + case CONTEXT_BUTTON_CANCEL_PARTYMODE: + g_partyModeManager.Disable(); + return true; + case CONTEXT_BUTTON_EDIT_PARTYMODE: + { + std::string playlist = "special://profile/PartyMode-Video.xsp"; + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist)) + { + // apply new rules + g_partyModeManager.Disable(); + g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO); + } + return true; + } + default: + break; + } + + return CGUIWindowVideoBase::OnContextButton(itemNumber, button); +} + +void CGUIWindowVideoPlaylist::OnMove(int iItem, int iAction) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) return; + MoveCurrentPlayListItem(iItem, iAction); +} + +void CGUIWindowVideoPlaylist::MoveItem(int iStart, int iDest) +{ + if (iStart < 0 || iStart >= m_vecItems->Size()) return; + if (iDest < 0 || iDest >= m_vecItems->Size()) return; + + // default to move up + int iAction = ACTION_MOVE_ITEM_UP; + int iDirection = -1; + // are we moving down? + if (iStart < iDest) + { + iAction = ACTION_MOVE_ITEM_DOWN; + iDirection = 1; + } + + // keep swapping until you get to the destination or you + // hit the currently playing song + int i = iStart; + while (i != iDest) + { + // try to swap adjacent items + if (MoveCurrentPlayListItem(i, iAction, false)) + i = i + (1 * iDirection); + // we hit currently playing song, so abort + else + break; + } + Refresh(); +} + +void CGUIWindowVideoPlaylist::MarkPlaying() +{ + /* // clear markings + for (int i = 0; i < m_vecItems->Size(); i++) + m_vecItems->Get(i)->Select(false); + + // mark the currently playing item + if ((CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == TYPE_VIDEO) && (g_application.GetAppPlayer().IsPlayingVideo())) + { + int iSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + if (iSong >= 0 && iSong <= m_vecItems->Size()) + m_vecItems->Get(iSong)->Select(true); + }*/ +} + diff --git a/xbmc/video/windows/GUIWindowVideoPlaylist.h b/xbmc/video/windows/GUIWindowVideoPlaylist.h new file mode 100644 index 0000000..ea04fb0 --- /dev/null +++ b/xbmc/video/windows/GUIWindowVideoPlaylist.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-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. + */ + +#pragma once + +#include "GUIWindowVideoBase.h" + +class CGUIWindowVideoPlaylist : public CGUIWindowVideoBase +{ +public: + CGUIWindowVideoPlaylist(void); + ~CGUIWindowVideoPlaylist(void) override; + + void OnPrepareFileItems(CFileItemList& items) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + +protected: + bool OnPlayMedia(int iItem, const std::string &player = "") override; + void UpdateButtons() override; + void MarkPlaying(); + + void GetContextButtons(int itemNumber, CContextButtons &buttons) override; + bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + + void OnMove(int iItem, int iAction); + + void ClearPlayList(); + void RemovePlayListItem(int iItem); + bool MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate = true); + void MoveItem(int iStart, int iDest); + + void SavePlayList(); + + int m_movingFrom; + VECSOURCES m_shares; +}; diff --git a/xbmc/video/windows/VideoFileItemListModifier.cpp b/xbmc/video/windows/VideoFileItemListModifier.cpp new file mode 100644 index 0000000..4d26af3 --- /dev/null +++ b/xbmc/video/windows/VideoFileItemListModifier.cpp @@ -0,0 +1,135 @@ +/* + * 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 "VideoFileItemListModifier.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "filesystem/VideoDatabaseDirectory/DirectoryNode.h" +#include "guilib/LocalizeStrings.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "video/VideoDatabase.h" +#include "video/VideoDbUrl.h" + +using namespace XFILE::VIDEODATABASEDIRECTORY; + +bool CVideoFileItemListModifier::CanModify(const CFileItemList &items) const +{ + if (items.IsVideoDb()) + return true; + + return false; +} + +bool CVideoFileItemListModifier::Modify(CFileItemList &items) const +{ + AddQueuingFolder(items); + return true; +} + +// Add an "* All ..." folder to the CFileItemList +// depending on the child node +void CVideoFileItemListModifier::AddQueuingFolder(CFileItemList& items) +{ + if (!items.IsVideoDb()) + return; + + auto directoryNode = CDirectoryNode::ParseURL(items.GetPath()); + + CFileItemPtr pItem; + + // always show "all" items by default + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWALLITEMS)) + return; + + // no need for "all" item when only one item + if (items.GetObjectCount() <= 1) + return; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(directoryNode->BuildPath())) + return; + + // hack - as the season node might return episodes + std::unique_ptr<CDirectoryNode> pNode(directoryNode); + + switch (pNode->GetChildType()) + { + case NODE_TYPE_SEASONS: + { + const std::string& strLabel = g_localizeStrings.Get(20366); + pItem.reset(new CFileItem(strLabel)); // "All Seasons" + videoUrl.AppendPath("-1/"); + pItem->SetPath(videoUrl.ToString()); + // set the number of watched and unwatched items accordingly + int watched = 0; + int unwatched = 0; + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + watched += static_cast<int>(item->GetProperty("watchedepisodes").asInteger()); + unwatched += static_cast<int>(item->GetProperty("unwatchedepisodes").asInteger()); + } + const int totalEpisodes = watched + unwatched; + pItem->SetProperty("totalepisodes", totalEpisodes); + pItem->SetProperty("numepisodes", + totalEpisodes); // will be changed later to reflect watchmode setting + pItem->SetProperty("watchedepisodes", watched); + pItem->SetProperty("unwatchedepisodes", unwatched); + pItem->SetProperty("watchedepisodepercent", + totalEpisodes > 0 ? watched * 100 / totalEpisodes : 0); + + // @note: The items list may contain additional items that do not belong to the show. + // This is the case of the up directory (..) or movies linked to the tvshow. + // Iterate through the list till the first season type is found and the infotag can safely be copied. + + if (items.Size() > 1) + { + for (int i = 1; i < items.Size(); i++) + { + if (items[i]->GetVideoInfoTag() && items[i]->GetVideoInfoTag()->m_type == MediaTypeSeason && + items[i]->GetVideoInfoTag()->m_iSeason > 0) + { + *pItem->GetVideoInfoTag() = *items[i]->GetVideoInfoTag(); + pItem->GetVideoInfoTag()->m_iSeason = -1; + break; + } + } + } + + pItem->GetVideoInfoTag()->m_strTitle = strLabel; + pItem->GetVideoInfoTag()->m_iEpisode = watched + unwatched; + pItem->GetVideoInfoTag()->SetPlayCount((unwatched == 0) ? 1 : 0); + CVideoDatabase db; + if (db.Open()) + { + pItem->GetVideoInfoTag()->m_iDbId = db.GetSeasonId(pItem->GetVideoInfoTag()->m_iIdShow, -1); + db.Close(); + } + pItem->GetVideoInfoTag()->m_type = MediaTypeSeason; + } + break; + case NODE_TYPE_MUSICVIDEOS_ALBUM: + pItem.reset(new CFileItem("* " + g_localizeStrings.Get(16100))); // "* All Videos" + videoUrl.AppendPath("-1/"); + pItem->SetPath(videoUrl.ToString()); + break; + default: + break; + } + + if (pItem) + { + pItem->m_bIsFolder = true; + pItem->SetSpecialSort(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bVideoLibraryAllItemsOnBottom ? SortSpecialOnBottom : SortSpecialOnTop); + pItem->SetCanQueue(false); + items.Add(pItem); + } +} diff --git a/xbmc/video/windows/VideoFileItemListModifier.h b/xbmc/video/windows/VideoFileItemListModifier.h new file mode 100644 index 0000000..c4727f9 --- /dev/null +++ b/xbmc/video/windows/VideoFileItemListModifier.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#pragma once + +#include "IFileItemListModifier.h" + +class CVideoFileItemListModifier : public IFileItemListModifier +{ +public: + CVideoFileItemListModifier() = default; + ~CVideoFileItemListModifier() override = default; + + bool CanModify(const CFileItemList &items) const override; + bool Modify(CFileItemList &items) const override; + +private: + static void AddQueuingFolder(CFileItemList & items); +}; |