summaryrefslogtreecommitdiffstats
path: root/xbmc/video/windows
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/video/windows')
-rw-r--r--xbmc/video/windows/CMakeLists.txt14
-rw-r--r--xbmc/video/windows/GUIWindowFullScreen.cpp437
-rw-r--r--xbmc/video/windows/GUIWindowFullScreen.h45
-rw-r--r--xbmc/video/windows/GUIWindowFullScreenDefines.h14
-rw-r--r--xbmc/video/windows/GUIWindowVideoBase.cpp1589
-rw-r--r--xbmc/video/windows/GUIWindowVideoBase.h148
-rw-r--r--xbmc/video/windows/GUIWindowVideoNav.cpp1171
-rw-r--r--xbmc/video/windows/GUIWindowVideoNav.h68
-rw-r--r--xbmc/video/windows/GUIWindowVideoPlaylist.cpp609
-rw-r--r--xbmc/video/windows/GUIWindowVideoPlaylist.h43
-rw-r--r--xbmc/video/windows/VideoFileItemListModifier.cpp135
-rw-r--r--xbmc/video/windows/VideoFileItemListModifier.h24
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);
+};