/* * 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 "GUIWindowSlideShow.h" #include "FileItem.h" #include "GUIDialogPictureInfo.h" #include "GUIInfoManager.h" #include "GUIUserMessages.h" #include "ServiceBroker.h" #include "TextureDatabase.h" #include "URL.h" #include "application/Application.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" #include "application/ApplicationPowerHandling.h" #include "filesystem/Directory.h" #include "guilib/GUIComponent.h" #include "guilib/GUILabelControl.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "guilib/Texture.h" #include "input/Key.h" #include "interfaces/AnnouncementManager.h" #include "pictures/GUIViewStatePictures.h" #include "pictures/PictureThumbLoader.h" #include "playlists/PlayListTypes.h" #include "rendering/RenderSystem.h" #include "settings/DisplaySettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/Random.h" #include "utils/URIUtils.h" #include "utils/Variant.h" #include "utils/XTimeUtils.h" #include "utils/log.h" #include using namespace XFILE; using namespace KODI::MESSAGING; using namespace std::chrono_literals; #define MAX_ZOOM_FACTOR 10 #define MAX_PICTURE_SIZE 2048*2048 #define IMMEDIATE_TRANSITION_TIME 1 #define PICTURE_MOVE_AMOUNT 0.02f #define PICTURE_MOVE_AMOUNT_ANALOG 0.01f #define PICTURE_MOVE_AMOUNT_TOUCH 0.002f #define PICTURE_VIEW_BOX_COLOR 0xffffff00 // YELLOW #define PICTURE_VIEW_BOX_BACKGROUND 0xff000000 // BLACK #define ROTATION_SNAP_RANGE 10.0f #define LABEL_ROW1 10 #define CONTROL_PAUSE 13 static float zoomamount[10] = { 1.0f, 1.2f, 1.5f, 2.0f, 2.8f, 4.0f, 6.0f, 9.0f, 13.5f, 20.0f }; CBackgroundPicLoader::CBackgroundPicLoader() : CThread("BgPicLoader") , m_iPic{0} , m_iSlideNumber{0} , m_maxWidth{0} , m_maxHeight{0} , m_isLoading{false} , m_pCallback{nullptr} { } CBackgroundPicLoader::~CBackgroundPicLoader() { StopThread(); } void CBackgroundPicLoader::Create(CGUIWindowSlideShow *pCallback) { m_pCallback = pCallback; m_isLoading = false; CThread::Create(false); } void CBackgroundPicLoader::Process() { auto totalTime = std::chrono::milliseconds(0); unsigned int count = 0; while (!m_bStop) { // loop around forever, waiting for the app to call LoadPic if (AbortableWait(m_loadPic, 10ms) == WAIT_SIGNALED) { if (m_pCallback) { auto start = std::chrono::steady_clock::now(); std::unique_ptr texture = CTexture::LoadFromFile(m_strFileName, m_maxWidth, m_maxHeight); auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); totalTime += duration; count++; // tell our parent bool bFullSize = false; if (texture) { bFullSize = ((int)texture->GetWidth() < m_maxWidth) && ((int)texture->GetHeight() < m_maxHeight); if (!bFullSize) { int iSize = texture->GetWidth() * texture->GetHeight() - MAX_PICTURE_SIZE; if ((iSize + (int)texture->GetWidth() > 0) || (iSize + (int)texture->GetHeight() > 0)) bFullSize = true; if (!bFullSize && texture->GetWidth() == CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) bFullSize = true; if (!bFullSize && texture->GetHeight() == CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) bFullSize = true; } } m_pCallback->OnLoadPic(m_iPic, m_iSlideNumber, m_strFileName, std::move(texture), bFullSize); m_isLoading = false; } } } if (count > 0) CLog::Log(LOGDEBUG, "Time for loading {} images: {} ms, average {} ms", count, totalTime.count(), totalTime.count() / count); } void CBackgroundPicLoader::LoadPic(int iPic, int iSlideNumber, const std::string &strFileName, const int maxWidth, const int maxHeight) { m_iPic = iPic; m_iSlideNumber = iSlideNumber; m_strFileName = strFileName; m_maxWidth = maxWidth; m_maxHeight = maxHeight; m_isLoading = true; m_loadPic.Set(); } CGUIWindowSlideShow::CGUIWindowSlideShow(void) : CGUIDialog(WINDOW_SLIDESHOW, "SlideShow.xml") { m_Resolution = RES_INVALID; m_loadType = KEEP_IN_MEMORY; m_bLoadNextPic = false; Reset(); } void CGUIWindowSlideShow::AnnouncePlayerPlay(const CFileItemPtr& item) { CVariant param; param["player"]["speed"] = m_bSlideShow && !m_bPause ? 1 : 0; param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay", item, param); } void CGUIWindowSlideShow::AnnouncePlayerPause(const CFileItemPtr& item) { CVariant param; param["player"]["speed"] = 0; param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPause", item, param); } void CGUIWindowSlideShow::AnnouncePlayerStop(const CFileItemPtr& item) { CVariant param; param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE; param["end"] = true; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnStop", item, param); } void CGUIWindowSlideShow::AnnouncePlaylistClear() { CVariant data; data["playlistid"] = PLAYLIST::TYPE_PICTURE; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Playlist, "OnClear", data); } void CGUIWindowSlideShow::AnnouncePlaylistAdd(const CFileItemPtr& item, int pos) { CVariant data; data["playlistid"] = PLAYLIST::TYPE_PICTURE; data["position"] = pos; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Playlist, "OnAdd", item, data); } void CGUIWindowSlideShow::AnnouncePropertyChanged(const std::string &strProperty, const CVariant &value) { if (strProperty.empty() || value.isNull()) return; CVariant data; data["player"]["playerid"] = PLAYLIST::TYPE_PICTURE; data["property"][strProperty] = value; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPropertyChanged", data); } bool CGUIWindowSlideShow::IsPlaying() const { return m_Image[m_iCurrentPic].IsLoaded(); } void CGUIWindowSlideShow::Reset() { m_bSlideShow = false; m_bShuffled = false; m_bPause = false; m_bPlayingVideo = false; m_bErrorMessage = false; m_Image[0].UnLoad(); m_Image[0].Close(); m_Image[1].UnLoad(); m_Image[1].Close(); m_fRotate = 0.0f; m_fInitialRotate = 0.0f; m_iZoomFactor = 1; m_fZoom = 1.0f; m_fInitialZoom = 0.0f; m_iCurrentSlide = 0; m_iNextSlide = 1; m_iCurrentPic = 0; m_iDirection = 1; m_iLastFailedNextSlide = -1; m_slides.clear(); AnnouncePlaylistClear(); m_Resolution = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(); } void CGUIWindowSlideShow::OnDeinitWindow(int nextWindowID) { if (m_Resolution != CDisplaySettings::GetInstance().GetCurrentResolution()) { //FIXME: Use GUI resolution for now //CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(CDisplaySettings::GetInstance().GetCurrentResolution(), true); } if (nextWindowID != WINDOW_FULLSCREEN_VIDEO && nextWindowID != WINDOW_FULLSCREEN_GAME) { // wait for any outstanding picture loads if (m_pBackgroundLoader) { // sleep until the loader finishes loading the current pic CLog::Log(LOGDEBUG,"Waiting for BackgroundLoader thread to close"); while (m_pBackgroundLoader->IsLoading()) KODI::TIME::Sleep(10ms); // stop the thread CLog::Log(LOGDEBUG,"Stopping BackgroundLoader thread"); m_pBackgroundLoader->StopThread(); m_pBackgroundLoader.reset(); } // and close the images. m_Image[0].Close(); m_Image[1].Close(); } CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().SetCurrentSlide(nullptr); m_bSlideShow = false; CGUIDialog::OnDeinitWindow(nextWindowID); } void CGUIWindowSlideShow::Add(const CFileItem *picture) { CFileItemPtr item(new CFileItem(*picture)); if (!item->HasVideoInfoTag() && !item->HasPictureInfoTag()) { // item without tag; get mimetype then we can tell whether it's video item item->FillInMimeType(); if (!item->IsVideo()) // then it is a picture and force tag generation item->GetPictureInfoTag(); } AnnouncePlaylistAdd(item, m_slides.size()); m_slides.emplace_back(std::move(item)); } void CGUIWindowSlideShow::ShowNext() { if (m_slides.size() == 1) return; m_iDirection = 1; m_iNextSlide = GetNextSlide(); m_iZoomFactor = 1; m_fZoom = 1.0f; m_fRotate = 0.0f; m_bLoadNextPic = true; } void CGUIWindowSlideShow::ShowPrevious() { if (m_slides.size() == 1) return; m_iDirection = -1; m_iNextSlide = GetNextSlide(); m_iZoomFactor = 1; m_fZoom = 1.0f; m_fRotate = 0.0f; m_bLoadNextPic = true; } void CGUIWindowSlideShow::Select(const std::string& strPicture) { for (size_t i = 0; i < m_slides.size(); ++i) { const CFileItemPtr item = m_slides.at(i); if (item->GetPath() == strPicture) { m_iDirection = 1; if (!m_Image[m_iCurrentPic].IsLoaded() && (!m_pBackgroundLoader || !m_pBackgroundLoader->IsLoading())) { // will trigger loading current slide when next Process call. m_iCurrentSlide = i; m_iNextSlide = GetNextSlide(); } else { m_iNextSlide = i; m_bLoadNextPic = true; } return ; } } } void CGUIWindowSlideShow::GetSlideShowContents(CFileItemList &list) { for (size_t index = 0; index < m_slides.size(); index++) list.Add(CFileItemPtr(new CFileItem(*m_slides.at(index)))); } std::shared_ptr CGUIWindowSlideShow::GetCurrentSlide() { if (m_iCurrentSlide >= 0 && m_iCurrentSlide < static_cast(m_slides.size())) return m_slides.at(m_iCurrentSlide); return CFileItemPtr(); } bool CGUIWindowSlideShow::InSlideShow() const { return m_bSlideShow; } void CGUIWindowSlideShow::StartSlideShow() { m_bSlideShow = true; m_iDirection = 1; if (m_slides.size()) AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide)); } void CGUIWindowSlideShow::SetDirection(int direction) { direction = direction >= 0 ? 1 : -1; if (m_iDirection != direction) { m_iDirection = direction; m_iNextSlide = GetNextSlide(); } } void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList ®ions) { const RESOLUTION_INFO res = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(); // reset the screensaver if we're in a slideshow // (unless we are the screensaver!) auto& components = CServiceBroker::GetAppComponents(); const auto appPower = components.GetComponent(); if (m_bSlideShow && !m_bPause && !appPower->IsInScreenSaver()) appPower->ResetScreenSaver(); int iSlides = m_slides.size(); if (!iSlides) return; // if we haven't processed yet, we should mark the whole screen if (!HasProcessed()) regions.push_back(CDirtyRegion(CRect(0.0f, 0.0f, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()))); if (m_iCurrentSlide < 0 || m_iCurrentSlide >= static_cast(m_slides.size())) m_iCurrentSlide = 0; if (m_iNextSlide < 0 || m_iNextSlide >= static_cast(m_slides.size())) m_iNextSlide = GetNextSlide(); // Create our background loader if necessary if (!m_pBackgroundLoader) { m_pBackgroundLoader.reset(new CBackgroundPicLoader()); m_pBackgroundLoader->Create(this); } bool bSlideShow = m_bSlideShow && !m_bPause && !m_bPlayingVideo; if (bSlideShow && m_slides.at(m_iCurrentSlide)->HasProperty("unplayable")) { m_iNextSlide = GetNextSlide(); if (m_iCurrentSlide == m_iNextSlide) return; m_iCurrentSlide = m_iNextSlide; m_iNextSlide = GetNextSlide(); } if (m_bErrorMessage) { // we have an error when loading either the current or next picture // check to see if we have a picture loaded CLog::Log(LOGDEBUG, "We have an error loading picture {}!", m_pBackgroundLoader->SlideNumber()); if (m_iCurrentSlide == m_pBackgroundLoader->SlideNumber()) { if (m_Image[m_iCurrentPic].IsLoaded()) { // current image was already loaded, so we can ignore this error. m_bErrorMessage = false; } else { CLog::Log(LOGERROR, "Error loading the current image {}: {}", m_iCurrentSlide, m_slides.at(m_iCurrentSlide)->GetPath()); if (!m_slides.at(m_iCurrentPic)->IsVideo()) { // try next if we are in slideshow CLog::Log(LOGINFO, "set image {} unplayable", m_slides.at(m_iCurrentSlide)->GetPath()); m_slides.at(m_iCurrentSlide)->SetProperty("unplayable", true); } if (m_bLoadNextPic || (bSlideShow && !m_bPause && !m_slides.at(m_iCurrentPic)->IsVideo())) { // change to next item, wait loading. m_iCurrentSlide = m_iNextSlide; m_iNextSlide = GetNextSlide(); m_bErrorMessage = false; } // else just drop through - there's nothing we can do (error message will be displayed) } } else if (m_iNextSlide == m_pBackgroundLoader->SlideNumber()) { CLog::Log(LOGERROR, "Error loading the next image {}: {}", m_iNextSlide, m_slides.at(m_iNextSlide)->GetPath()); // load next image failed, then skip to load next of next if next is not video. if (!m_slides.at(m_iNextSlide)->IsVideo()) { CLog::Log(LOGINFO, "set image {} unplayable", m_slides.at(m_iNextSlide)->GetPath()); m_slides.at(m_iNextSlide)->SetProperty("unplayable", true); // change to next item, wait loading. m_iNextSlide = GetNextSlide(); } else { // prevent reload the next pic and repeat fail. m_iLastFailedNextSlide = m_iNextSlide; } m_bErrorMessage = false; } else { // Non-current and non-next slide, just ignore error. CLog::Log(LOGERROR, "Error loading the non-current non-next image {}/{}: {}", m_iNextSlide, m_pBackgroundLoader->SlideNumber(), m_slides.at(m_iNextSlide)->GetPath()); m_bErrorMessage = false; } } if (m_bErrorMessage) { // hack, just mark it all regions.push_back(CDirtyRegion(CRect(0.0f, 0.0f, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()))); return; } if (!m_Image[m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading()) { // load first image CFileItemPtr item = m_slides.at(m_iCurrentSlide); std::string picturePath = GetPicturePath(item.get()); if (!picturePath.empty()) { if (item->IsVideo()) CLog::Log(LOGDEBUG, "Loading the thumb {} for current video {}: {}", picturePath, m_iCurrentSlide, item->GetPath()); else CLog::Log(LOGDEBUG, "Loading the current image {}: {}", m_iCurrentSlide, item->GetPath()); // load using the background loader int maxWidth, maxHeight; GetCheckedSize((float)res.iWidth * m_fZoom, (float)res.iHeight * m_fZoom, maxWidth, maxHeight); m_pBackgroundLoader->LoadPic(m_iCurrentPic, m_iCurrentSlide, picturePath, maxWidth, maxHeight); m_iLastFailedNextSlide = -1; m_bLoadNextPic = false; } } // check if we should discard an already loaded next slide if (m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() != m_iNextSlide) m_Image[1 - m_iCurrentPic].Close(); if (m_iNextSlide != m_iCurrentSlide && m_Image[m_iCurrentPic].IsLoaded() && !m_Image[1 - m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading() && m_iLastFailedNextSlide != m_iNextSlide) { // load the next image m_iLastFailedNextSlide = -1; CFileItemPtr item = m_slides.at(m_iNextSlide); std::string picturePath = GetPicturePath(item.get()); if (!picturePath.empty() && (!item->IsVideo() || !m_bSlideShow || m_bPause)) { if (item->IsVideo()) CLog::Log(LOGDEBUG, "Loading the thumb {} for next video {}: {}", picturePath, m_iNextSlide, item->GetPath()); else CLog::Log(LOGDEBUG, "Loading the next image {}: {}", m_iNextSlide, item->GetPath()); int maxWidth, maxHeight; GetCheckedSize((float)res.iWidth * m_fZoom, (float)res.iHeight * m_fZoom, maxWidth, maxHeight); m_pBackgroundLoader->LoadPic(1 - m_iCurrentPic, m_iNextSlide, picturePath, maxWidth, maxHeight); } } bool bPlayVideo = m_slides.at(m_iCurrentSlide)->IsVideo() && m_iVideoSlide != m_iCurrentSlide; if (bPlayVideo) bSlideShow = false; // render the current image if (m_Image[m_iCurrentPic].IsLoaded()) { m_Image[m_iCurrentPic].SetInSlideshow(bSlideShow); m_Image[m_iCurrentPic].Pause(!bSlideShow); m_Image[m_iCurrentPic].Process(currentTime, regions); } // Check if we should be transitioning immediately if (m_bLoadNextPic && m_Image[m_iCurrentPic].IsLoaded()) { CLog::Log(LOGDEBUG, "Starting immediate transition due to user wanting slide {}", m_slides.at(m_iNextSlide)->GetPath()); if (m_Image[m_iCurrentPic].StartTransition()) { m_Image[m_iCurrentPic].SetTransitionTime(1, IMMEDIATE_TRANSITION_TIME); m_bLoadNextPic = false; } } const auto appPlayer = components.GetComponent(); // render the next image if (m_Image[m_iCurrentPic].DrawNextImage()) { if (m_bSlideShow && !m_bPause && m_slides.at(m_iNextSlide)->IsVideo()) { // do not show thumb of video when playing slideshow } else if (m_Image[1 - m_iCurrentPic].IsLoaded()) { if (appPlayer->IsPlayingVideo()) appPlayer->ClosePlayer(); m_bPlayingVideo = false; m_iVideoSlide = -1; // first time render the next image, make sure using current display effect. if (!m_Image[1 - m_iCurrentPic].IsStarted()) { CSlideShowPic::DISPLAY_EFFECT effect = GetDisplayEffect(m_iNextSlide); if (m_Image[1 - m_iCurrentPic].DisplayEffectNeedChange(effect)) m_Image[1 - m_iCurrentPic].Reset(effect); } // set the appropriate transition time m_Image[1 - m_iCurrentPic].SetTransitionTime(0, m_Image[m_iCurrentPic].GetTransitionTime(1)); m_Image[1 - m_iCurrentPic].Pause(!m_bSlideShow || m_bPause || m_slides.at(m_iNextSlide)->IsVideo()); m_Image[1 - m_iCurrentPic].Process(currentTime, regions); } else // next pic isn't loaded. We should hang around if it is in progress { if (m_pBackgroundLoader->IsLoading()) { // CLog::Log(LOGDEBUG, "Having to hold the current image ({}) while we load {}", m_vecSlides[m_iCurrentSlide], m_vecSlides[m_iNextSlide]); m_Image[m_iCurrentPic].Keep(); } } } // check if we should swap images now if (m_Image[m_iCurrentPic].IsFinished() || (m_bLoadNextPic && !m_Image[m_iCurrentPic].IsLoaded())) { m_bLoadNextPic = false; if (m_Image[m_iCurrentPic].IsFinished()) CLog::Log(LOGDEBUG, "Image {} is finished rendering, switching to {}", m_slides.at(m_iCurrentSlide)->GetPath(), m_slides.at(m_iNextSlide)->GetPath()); else // what if it's bg loading? CLog::Log(LOGDEBUG, "Image {} is not loaded, switching to {}", m_slides.at(m_iCurrentSlide)->GetPath(), m_slides.at(m_iNextSlide)->GetPath()); if (m_Image[m_iCurrentPic].IsFinished() && m_iCurrentSlide == m_iNextSlide && m_Image[m_iCurrentPic].SlideNumber() == m_iNextSlide) m_Image[m_iCurrentPic].Reset(GetDisplayEffect(m_iCurrentSlide)); else { if (m_Image[m_iCurrentPic].IsLoaded()) m_Image[m_iCurrentPic].Reset(GetDisplayEffect(m_iCurrentSlide)); else m_Image[m_iCurrentPic].Close(); if ((m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() == m_iNextSlide) || (m_pBackgroundLoader->IsLoading() && m_pBackgroundLoader->SlideNumber() == m_iNextSlide && m_pBackgroundLoader->Pic() == 1 - m_iCurrentPic)) { m_iCurrentPic = 1 - m_iCurrentPic; } else { m_Image[1 - m_iCurrentPic].Close(); m_iCurrentPic = 1 - m_iCurrentPic; } m_iCurrentSlide = m_iNextSlide; m_iNextSlide = GetNextSlide(); bPlayVideo = m_slides.at(m_iCurrentSlide)->IsVideo() && m_iVideoSlide != m_iCurrentSlide; } AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide)); m_iZoomFactor = 1; m_fZoom = 1.0f; m_fRotate = 0.0f; } if (bPlayVideo && !PlayVideo()) return; if (m_Image[m_iCurrentPic].IsLoaded()) CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().SetCurrentSlide(m_slides.at(m_iCurrentSlide).get()); RenderPause(); if (m_slides.at(m_iCurrentSlide)->IsVideo() && appPlayer && appPlayer->IsRenderingGuiLayer()) { MarkDirtyRegion(); } CGUIWindow::Process(currentTime, regions); m_renderRegion.SetRect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); } void CGUIWindowSlideShow::Render() { if (m_slides.empty()) return; CGraphicContext& gfxCtx = CServiceBroker::GetWinSystem()->GetGfxContext(); gfxCtx.Clear(0xff000000); if (m_slides.at(m_iCurrentSlide)->IsVideo()) { gfxCtx.SetViewWindow(0, 0, m_coordsRes.iWidth, m_coordsRes.iHeight); gfxCtx.SetRenderingResolution(gfxCtx.GetVideoResolution(), false); auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent(); if (appPlayer->IsRenderingVideoLayer()) { const CRect old = gfxCtx.GetScissors(); CRect region = GetRenderRegion(); region.Intersect(old); gfxCtx.SetScissors(region); gfxCtx.Clear(0); gfxCtx.SetScissors(old); } else if (appPlayer) { const UTILS::COLOR::Color alpha = gfxCtx.MergeAlpha(0xff000000) >> 24; appPlayer->Render(false, alpha); } gfxCtx.SetRenderingResolution(m_coordsRes, m_needsScaling); } else { if (m_Image[m_iCurrentPic].IsLoaded()) m_Image[m_iCurrentPic].Render(); if (m_Image[m_iCurrentPic].DrawNextImage() && m_Image[1 - m_iCurrentPic].IsLoaded()) m_Image[1 - m_iCurrentPic].Render(); } RenderErrorMessage(); CGUIWindow::Render(); } void CGUIWindowSlideShow::RenderEx() { if (m_slides.at(m_iCurrentSlide)->IsVideo()) { auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent(); appPlayer->Render(false, 255, false); } CGUIWindow::RenderEx(); } int CGUIWindowSlideShow::GetNextSlide() { if (m_slides.size() <= 1) return m_iCurrentSlide; int step = m_iDirection >= 0 ? 1 : -1; int nextSlide = (m_iCurrentSlide + step + m_slides.size()) % m_slides.size(); while (nextSlide != m_iCurrentSlide) { if (!m_slides.at(nextSlide)->HasProperty("unplayable")) return nextSlide; nextSlide = (nextSlide + step + m_slides.size()) % m_slides.size(); } return m_iCurrentSlide; } EVENT_RESULT CGUIWindowSlideShow::OnMouseEvent(const CPoint &point, const CMouseEvent &event) { if (event.m_id == ACTION_GESTURE_NOTIFY) { int result = EVENT_RESULT_ROTATE | EVENT_RESULT_ZOOM; if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally) result |= EVENT_RESULT_SWIPE; else result |= EVENT_RESULT_PAN_HORIZONTAL; if (m_Image[m_iCurrentPic].m_bCanMoveVertically) result |= EVENT_RESULT_PAN_VERTICAL; return (EVENT_RESULT)result; } else if (event.m_id == ACTION_GESTURE_BEGIN) { m_firstGesturePoint = point; m_fInitialZoom = m_fZoom; m_fInitialRotate = m_fRotate; return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_PAN) { // zoomed in - free move mode if (m_iZoomFactor != 1 && (m_Image[m_iCurrentPic].m_bCanMoveHorizontally || m_Image[m_iCurrentPic].m_bCanMoveVertically)) { Move(PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.x - point.x), PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.y - point.y)); m_firstGesturePoint = point; } return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_SWIPE_LEFT || event.m_id == ACTION_GESTURE_SWIPE_RIGHT) { if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally) { // on zoomlevel 1 just detect swipe left and right if (event.m_id == ACTION_GESTURE_SWIPE_LEFT) OnAction(CAction(ACTION_NEXT_PICTURE)); else OnAction(CAction(ACTION_PREV_PICTURE)); } } else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT) { if (m_fRotate != 0.0f) { // "snap" to nearest of 0, 90, 180 and 270 if the // difference in angle is +/-10 degrees float reminder = fmodf(m_fRotate, 90.0f); if (fabs(reminder) < ROTATION_SNAP_RANGE) Rotate(-reminder); else if (reminder > 90.0f - ROTATION_SNAP_RANGE) Rotate(90.0f - reminder); else if (-reminder > 90.0f - ROTATION_SNAP_RANGE) Rotate(-90.0f - reminder); } m_fInitialZoom = 0.0f; m_fInitialRotate = 0.0f; return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_ZOOM) { ZoomRelative(m_fInitialZoom * event.m_offsetX, true); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_ROTATE) { Rotate(m_fInitialRotate + event.m_offsetX - m_fRotate, true); return EVENT_RESULT_HANDLED; } return EVENT_RESULT_UNHANDLED; } bool CGUIWindowSlideShow::OnAction(const CAction &action) { switch (action.GetID()) { case ACTION_SHOW_INFO: { CGUIDialogPictureInfo *pictureInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_PICTURE_INFO); if (pictureInfo) { // no need to set the picture here, it's done in Render() pictureInfo->Open(); } } break; case ACTION_STOP: { if (m_slides.size()) AnnouncePlayerStop(m_slides.at(m_iCurrentSlide)); auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent(); if (appPlayer->IsPlayingVideo()) appPlayer->ClosePlayer(); Close(); break; } case ACTION_NEXT_PICTURE: ShowNext(); break; case ACTION_PREV_PICTURE: ShowPrevious(); break; case ACTION_MOVE_RIGHT: if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally) ShowNext(); else Move(PICTURE_MOVE_AMOUNT, 0); break; case ACTION_MOVE_LEFT: if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally) ShowPrevious(); else Move( -PICTURE_MOVE_AMOUNT, 0); break; case ACTION_MOVE_DOWN: Move(0, PICTURE_MOVE_AMOUNT); break; case ACTION_MOVE_UP: Move(0, -PICTURE_MOVE_AMOUNT); break; case ACTION_PAUSE: case ACTION_PLAYER_PLAY: if (m_slides.size() == 0) break; if (m_slides.at(m_iCurrentSlide)->IsVideo()) { if (!m_bPlayingVideo) { if (m_bSlideShow) { SetDirection(1); m_bPause = false; } PlayVideo(); } } else if (!m_bSlideShow || m_bPause) { m_bSlideShow = true; m_bPause = false; SetDirection(1); if (m_Image[m_iCurrentPic].IsLoaded()) { CSlideShowPic::DISPLAY_EFFECT effect = GetDisplayEffect(m_iCurrentSlide); if (m_Image[m_iCurrentPic].DisplayEffectNeedChange(effect)) m_Image[m_iCurrentPic].Reset(effect); } AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide)); } else if (action.GetID() == ACTION_PAUSE) { m_bPause = true; AnnouncePlayerPause(m_slides.at(m_iCurrentSlide)); } break; case ACTION_ZOOM_OUT: Zoom(m_iZoomFactor - 1); break; case ACTION_ZOOM_IN: Zoom(m_iZoomFactor + 1); break; case ACTION_GESTURE_SWIPE_UP: case ACTION_GESTURE_SWIPE_DOWN: if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveVertically) { bool swipeOnLeft = action.GetAmount() < CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() / 2.0f; bool swipeUp = action.GetID() == ACTION_GESTURE_SWIPE_UP; if (swipeUp == swipeOnLeft) Rotate(90.0f); else Rotate(-90.0f); } break; case ACTION_ROTATE_PICTURE_CW: Rotate(90.0f); break; case ACTION_ROTATE_PICTURE_CCW: Rotate(-90.0f); break; case ACTION_ZOOM_LEVEL_NORMAL: case ACTION_ZOOM_LEVEL_1: case ACTION_ZOOM_LEVEL_2: case ACTION_ZOOM_LEVEL_3: case ACTION_ZOOM_LEVEL_4: case ACTION_ZOOM_LEVEL_5: case ACTION_ZOOM_LEVEL_6: case ACTION_ZOOM_LEVEL_7: case ACTION_ZOOM_LEVEL_8: case ACTION_ZOOM_LEVEL_9: Zoom((action.GetID() - ACTION_ZOOM_LEVEL_NORMAL) + 1); break; case ACTION_ANALOG_MOVE: // this action is used and works, when CAction object provides both x and y coordinates Move(action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, -action.GetAmount(1)*PICTURE_MOVE_AMOUNT_ANALOG); break; case ACTION_ANALOG_MOVE_X_LEFT: Move(-action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, 0.0f); break; case ACTION_ANALOG_MOVE_X_RIGHT: Move(action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, 0.0f); break; case ACTION_ANALOG_MOVE_Y_UP: Move(0.0f, -action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG); break; case ACTION_ANALOG_MOVE_Y_DOWN: Move(0.0f, action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG); break; default: return CGUIDialog::OnAction(action); } return true; } void CGUIWindowSlideShow::RenderErrorMessage() { if (!m_bErrorMessage) return ; const CGUIControl *control = GetControl(LABEL_ROW1); if (NULL == control || control->GetControlType() != CGUIControl::GUICONTROL_LABEL) { return; } CGUIFont *pFont = static_cast(control)->GetLabelInfo().font; CGUITextLayout::DrawText(pFont, 0.5f*CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), 0.5f*CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), 0xffffffff, 0, g_localizeStrings.Get(747), XBFONT_CENTER_X | XBFONT_CENTER_Y); } bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message) { switch ( message.GetMessage() ) { case GUI_MSG_WINDOW_INIT: { m_Resolution = (RESOLUTION) CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_PICTURES_DISPLAYRESOLUTION); //FIXME: Use GUI resolution for now if (false /*m_Resolution != CDisplaySettings::GetInstance().GetCurrentResolution() && m_Resolution != INVALID && m_Resolution!=AUTORES*/) CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(m_Resolution, false); else m_Resolution = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(); CGUIDialog::OnMessage(message); // turn off slideshow if we only have 1 image if (m_slides.size() <= 1) m_bSlideShow = false; return true; } break; case GUI_MSG_SHOW_PICTURE: { const std::string& strFile = message.GetStringParam(); Reset(); CFileItem item(strFile, false); Add(&item); RunSlideShow("", false, false, true, "", false); } break; case GUI_MSG_START_SLIDESHOW: { const std::string& strFolder = message.GetStringParam(); unsigned int iParams = message.GetParam1(); const std::string& beginSlidePath = message.GetStringParam(1); //decode params bool bRecursive = false; bool bRandom = false; bool bNotRandom = false; bool bPause = false; if (iParams > 0) { if ((iParams & 1) == 1) bRecursive = true; if ((iParams & 2) == 2) bRandom = true; if ((iParams & 4) == 4) bNotRandom = true; if ((iParams & 8) == 8) bPause = true; } RunSlideShow(strFolder, bRecursive, bRandom, bNotRandom, beginSlidePath, !bPause); } break; case GUI_MSG_PLAYLISTPLAYER_STOPPED: { } break; case GUI_MSG_PLAYBACK_STOPPED: { if (m_bPlayingVideo) { m_bPlayingVideo = false; m_iVideoSlide = -1; if (m_bSlideShow) m_bPause = true; } } break; case GUI_MSG_PLAYBACK_ENDED: { if (m_bPlayingVideo) { m_bPlayingVideo = false; m_iVideoSlide = -1; if (m_bSlideShow) { m_bPause = false; if (m_iCurrentSlide == m_iNextSlide) break; m_Image[m_iCurrentPic].Close(); m_iCurrentPic = 1 - m_iCurrentPic; m_iCurrentSlide = m_iNextSlide; m_iNextSlide = GetNextSlide(); AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide)); m_iZoomFactor = 1; m_fZoom = 1.0f; m_fRotate = 0.0f; } } } break; } return CGUIDialog::OnMessage(message); } void CGUIWindowSlideShow::RenderPause() { // display the pause icon if (m_bPause) { SET_CONTROL_VISIBLE(CONTROL_PAUSE); } else { SET_CONTROL_HIDDEN(CONTROL_PAUSE); } } void CGUIWindowSlideShow::Rotate(float fAngle, bool immediate /* = false */) { if (m_Image[m_iCurrentPic].DrawNextImage()) return; m_fRotate += fAngle; m_Image[m_iCurrentPic].Rotate(fAngle, immediate); } void CGUIWindowSlideShow::Zoom(int iZoom) { if (iZoom > MAX_ZOOM_FACTOR || iZoom < 1) return; ZoomRelative(zoomamount[iZoom - 1]); } void CGUIWindowSlideShow::ZoomRelative(float fZoom, bool immediate /* = false */) { if (fZoom < zoomamount[0]) fZoom = zoomamount[0]; else if (fZoom > zoomamount[MAX_ZOOM_FACTOR - 1]) fZoom = zoomamount[MAX_ZOOM_FACTOR - 1]; if (m_Image[m_iCurrentPic].DrawNextImage()) return; m_fZoom = fZoom; // find the nearest zoom factor for (unsigned int i = 1; i < MAX_ZOOM_FACTOR; i++) { if (m_fZoom > zoomamount[i]) continue; if (fabs(m_fZoom - zoomamount[i - 1]) < fabs(m_fZoom - zoomamount[i])) m_iZoomFactor = i; else m_iZoomFactor = i + 1; break; } m_Image[m_iCurrentPic].Zoom(m_fZoom, immediate); } void CGUIWindowSlideShow::Move(float fX, float fY) { if (m_Image[m_iCurrentPic].IsLoaded() && m_Image[m_iCurrentPic].GetZoom() > 1) { // we move in the opposite direction, due to the fact we are moving // the viewing window, not the picture. m_Image[m_iCurrentPic].Move( -fX, -fY); } } bool CGUIWindowSlideShow::PlayVideo() { CFileItemPtr item = m_slides.at(m_iCurrentSlide); if (!item || !item->IsVideo()) return false; CLog::Log(LOGDEBUG, "Playing current video slide {}", item->GetPath()); m_bPlayingVideo = true; m_iVideoSlide = m_iCurrentSlide; bool ret = g_application.PlayFile(*item, ""); if (ret == true) return true; else { CLog::Log(LOGINFO, "set video {} unplayable", item->GetPath()); item->SetProperty("unplayable", true); } m_bPlayingVideo = false; m_iVideoSlide = -1; return false; } CSlideShowPic::DISPLAY_EFFECT CGUIWindowSlideShow::GetDisplayEffect(int iSlideNumber) const { if (m_bSlideShow && !m_bPause && !m_slides.at(iSlideNumber)->IsVideo()) return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_DISPLAYEFFECTS) ? CSlideShowPic::EFFECT_RANDOM : CSlideShowPic::EFFECT_NONE; else return CSlideShowPic::EFFECT_NO_TIMEOUT; } void CGUIWindowSlideShow::OnLoadPic(int iPic, int iSlideNumber, const std::string& strFileName, std::unique_ptr pTexture, bool bFullSize) { if (pTexture) { // set the pic's texture + size etc. if (iSlideNumber >= static_cast(m_slides.size()) || GetPicturePath(m_slides.at(iSlideNumber).get()) != strFileName) { // throw this away - we must have cleared the slideshow while we were still loading return; } CLog::Log(LOGDEBUG, "Finished background loading slot {}, {}: {}", iPic, iSlideNumber, m_slides.at(iSlideNumber)->GetPath()); m_Image[iPic].SetOriginalSize(pTexture->GetOriginalWidth(), pTexture->GetOriginalHeight(), bFullSize); m_Image[iPic].SetTexture(iSlideNumber, std::move(pTexture), GetDisplayEffect(iSlideNumber)); m_Image[iPic].m_bIsComic = false; if (URIUtils::IsInRAR(m_slides.at(m_iCurrentSlide)->GetPath()) || URIUtils::IsInZIP(m_slides.at(m_iCurrentSlide)->GetPath())) // move to top for cbr/cbz { CURL url(m_slides.at(m_iCurrentSlide)->GetPath()); const std::string& strHostName = url.GetHostName(); if (URIUtils::HasExtension(strHostName, ".cbr|.cbz")) { m_Image[iPic].m_bIsComic = true; m_Image[iPic].Move((float)m_Image[iPic].GetOriginalWidth(),(float)m_Image[iPic].GetOriginalHeight()); } } } else if (iSlideNumber >= static_cast(m_slides.size()) || GetPicturePath(m_slides.at(iSlideNumber).get()) != strFileName) { // Failed to load image. and not match values calling LoadPic, then something is changed, ignore. CLog::Log(LOGDEBUG, "CGUIWindowSlideShow::OnLoadPic({}, {}, {}) on failure not match current state (cur " "{}, next {}, curpic {}, pic[0, 1].slidenumber={}, {}, {})", iPic, iSlideNumber, strFileName, m_iCurrentSlide, m_iNextSlide, m_iCurrentPic, m_Image[0].SlideNumber(), m_Image[1].SlideNumber(), iSlideNumber >= static_cast(m_slides.size()) ? "" : m_slides.at(iSlideNumber)->GetPath()); } else { // Failed to load image. What should be done?? // We should wait for the current pic to finish rendering, then transition it out, // release the texture, and try and reload this pic from scratch m_bErrorMessage = true; } MarkDirtyRegion(); } void CGUIWindowSlideShow::Shuffle() { KODI::UTILS::RandomShuffle(m_slides.begin(), m_slides.end()); m_iCurrentSlide = 0; m_iNextSlide = GetNextSlide(); m_bShuffled = true; AnnouncePropertyChanged("shuffled", true); } int CGUIWindowSlideShow::NumSlides() const { return m_slides.size(); } int CGUIWindowSlideShow::CurrentSlide() const { return m_iCurrentSlide + 1; } void CGUIWindowSlideShow::AddFromPath(const std::string &strPath, bool bRecursive, SortBy method, SortOrder order, SortAttribute sortAttributes, const std::string &strExtensions) { if (strPath!="") { // reset the slideshow Reset(); if (bRecursive) { path_set recursivePaths; AddItems(strPath, &recursivePaths, method, order, sortAttributes); } else AddItems(strPath, NULL, method, order, sortAttributes); } } void CGUIWindowSlideShow::RunSlideShow(const std::string &strPath, bool bRecursive /* = false */, bool bRandom /* = false */, bool bNotRandom /* = false */, const std::string &beginSlidePath /* = "" */, bool startSlideShow /* = true */, SortBy method /* = SortByLabel */, SortOrder order /* = SortOrderAscending */, SortAttribute sortAttributes /* = SortAttributeNone */, const std::string &strExtensions) { // stop any video const auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent(); if (appPlayer->IsPlayingVideo()) g_application.StopPlaying(); AddFromPath(strPath, bRecursive, method, order, sortAttributes, strExtensions); if (!NumSlides()) return; // mutually exclusive options // if both are set, clear both and use the gui setting if (bRandom && bNotRandom) bRandom = bNotRandom = false; // NotRandom overrides the window setting if ((!bNotRandom && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_SHUFFLE)) || bRandom) Shuffle(); if (!beginSlidePath.empty()) Select(beginSlidePath); if (startSlideShow) StartSlideShow(); else { CVariant param; param["player"]["speed"] = 0; param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay", GetCurrentSlide(), param); } CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW); } void CGUIWindowSlideShow::AddItems(const std::string &strPath, path_set *recursivePaths, SortBy method, SortOrder order, SortAttribute sortAttributes) { // check whether we've already added this path if (recursivePaths) { std::string path(strPath); URIUtils::RemoveSlashAtEnd(path); if (recursivePaths->find(path) != recursivePaths->end()) return; recursivePaths->insert(path); } CFileItemList items; CGUIViewStateWindowPictures viewState(items); // fetch directory and sort accordingly if (!CDirectory::GetDirectory(strPath, items, viewState.GetExtensions(), DIR_FLAG_NO_FILE_DIRS)) return; items.Sort(method, order, sortAttributes); // need to go into all subdirs for (int i = 0; i < items.Size(); i++) { CFileItemPtr item = items[i]; if (item->m_bIsFolder && recursivePaths) { AddItems(item->GetPath(), recursivePaths); } else if (!item->m_bIsFolder && !URIUtils::IsRAR(item->GetPath()) && !URIUtils::IsZIP(item->GetPath())) { // add to the slideshow Add(item.get()); } } } void CGUIWindowSlideShow::GetCheckedSize(float width, float height, int &maxWidth, int &maxHeight) { maxWidth = CServiceBroker::GetRenderSystem()->GetMaxTextureSize(); maxHeight = CServiceBroker::GetRenderSystem()->GetMaxTextureSize(); } std::string CGUIWindowSlideShow::GetPicturePath(CFileItem *item) { bool isVideo = item->IsVideo(); std::string picturePath = item->GetDynPath(); if (isVideo) { picturePath = item->GetArt("thumb"); if (picturePath.empty() && !item->HasProperty("nothumb")) { CPictureThumbLoader thumbLoader; thumbLoader.LoadItem(item); picturePath = item->GetArt("thumb"); if (picturePath.empty()) item->SetProperty("nothumb", true); } } return picturePath; } void CGUIWindowSlideShow::RunSlideShow(const std::vector& paths, int start /* = 0*/) { auto dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SLIDESHOW); if (dialog) { std::vector items; items.reserve(paths.size()); for (const auto& path : paths) items.push_back(std::make_shared(CTextureUtils::GetWrappedImageURL(path), false)); dialog->Reset(); dialog->m_bPause = true; dialog->m_bSlideShow = false; dialog->m_iDirection = 1; dialog->m_iCurrentSlide = start; dialog->m_iNextSlide = (start + 1) % items.size(); dialog->m_slides = std::move(items); dialog->Open(); } }