FIX: Properly handle Pictures Play/Stop notif (fixes #13501; fixes #13503)
[vuplus_xbmc] / xbmc / pictures / GUIWindowSlideShow.cpp
1 /*
2  *      Copyright (C) 2005-2012 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "system.h"
23 #include "GUIWindowSlideShow.h"
24 #include "Application.h"
25 #include "ApplicationMessenger.h"
26 #include "utils/URIUtils.h"
27 #include "URL.h"
28 #include "guilib/TextureManager.h"
29 #include "guilib/GUILabelControl.h"
30 #include "GUIInfoManager.h"
31 #include "filesystem/Directory.h"
32 #include "GUIDialogPictureInfo.h"
33 #include "GUIUserMessages.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "settings/Settings.h"
36 #include "settings/GUISettings.h"
37 #include "FileItem.h"
38 #include "guilib/Texture.h"
39 #include "windowing/WindowingFactory.h"
40 #include "guilib/Texture.h"
41 #include "guilib/LocalizeStrings.h"
42 #include "threads/SingleLock.h"
43 #include "utils/log.h"
44 #include "utils/TimeUtils.h"
45 #include "interfaces/AnnouncementManager.h"
46 #include "pictures/PictureInfoTag.h"
47
48 using namespace XFILE;
49
50 #define MAX_ZOOM_FACTOR                     10
51 #define MAX_PICTURE_SIZE             2048*2048
52
53 #define IMMEDIATE_TRANSISTION_TIME          20
54
55 #define PICTURE_MOVE_AMOUNT              0.02f
56 #define PICTURE_MOVE_AMOUNT_ANALOG       0.01f
57 #define PICTURE_MOVE_AMOUNT_TOUCH        0.002f
58 #define PICTURE_VIEW_BOX_COLOR      0xffffff00 // YELLOW
59 #define PICTURE_VIEW_BOX_BACKGROUND 0xff000000 // BLACK
60
61 #define ROTATION_SNAP_RANGE              10.0f
62
63 #define FPS                                 25
64
65 #define BAR_IMAGE                            1
66 #define LABEL_ROW1                          10
67 #define LABEL_ROW2                          11
68 #define LABEL_ROW2_EXTRA                    12
69 #define CONTROL_PAUSE                       13
70
71 static float zoomamount[10] = { 1.0f, 1.2f, 1.5f, 2.0f, 2.8f, 4.0f, 6.0f, 9.0f, 13.5f, 20.0f };
72
73 CBackgroundPicLoader::CBackgroundPicLoader() : CThread("CBackgroundPicLoader")
74 {
75   m_pCallback = NULL;
76   m_isLoading = false;
77 }
78
79 CBackgroundPicLoader::~CBackgroundPicLoader()
80 {
81   StopThread();
82 }
83
84 void CBackgroundPicLoader::Create(CGUIWindowSlideShow *pCallback)
85 {
86   m_pCallback = pCallback;
87   m_isLoading = false;
88   CThread::Create(false);
89 }
90
91 void CBackgroundPicLoader::Process()
92 {
93   unsigned int totalTime = 0;
94   unsigned int count = 0;
95   while (!m_bStop)
96   { // loop around forever, waiting for the app to call LoadPic
97     if (AbortableWait(m_loadPic,10) == WAIT_SIGNALED)
98     {
99       if (m_pCallback)
100       {
101         unsigned int start = XbmcThreads::SystemClockMillis();
102         CBaseTexture* texture = CTexture::LoadFromFile(m_strFileName, m_maxWidth, m_maxHeight, g_guiSettings.GetBool("pictures.useexifrotation"));
103         totalTime += XbmcThreads::SystemClockMillis() - start;
104         count++;
105         // tell our parent
106         bool bFullSize = false;
107         if (texture)
108         {
109           bFullSize = ((int)texture->GetWidth() < m_maxWidth) && ((int)texture->GetHeight() < m_maxHeight);
110           if (!bFullSize)
111           {
112             int iSize = texture->GetWidth() * texture->GetHeight() - MAX_PICTURE_SIZE;
113             if ((iSize + (int)texture->GetWidth() > 0) || (iSize + (int)texture->GetHeight() > 0))
114               bFullSize = true;
115             if (!bFullSize && texture->GetWidth() == g_Windowing.GetMaxTextureSize())
116               bFullSize = true;
117             if (!bFullSize && texture->GetHeight() == g_Windowing.GetMaxTextureSize())
118               bFullSize = true;
119           }
120         }
121         m_pCallback->OnLoadPic(m_iPic, m_iSlideNumber, texture, bFullSize);
122         m_isLoading = false;
123       }
124     }
125   }
126   if (count > 0)
127     CLog::Log(LOGDEBUG, "Time for loading %u images: %u ms, average %u ms",
128               count, totalTime, totalTime / count);
129 }
130
131 void CBackgroundPicLoader::LoadPic(int iPic, int iSlideNumber, const CStdString &strFileName, const int maxWidth, const int maxHeight)
132 {
133   m_iPic = iPic;
134   m_iSlideNumber = iSlideNumber;
135   m_strFileName = strFileName;
136   m_maxWidth = maxWidth;
137   m_maxHeight = maxHeight;
138   m_isLoading = true;
139   m_loadPic.Set();
140 }
141
142 CGUIWindowSlideShow::CGUIWindowSlideShow(void)
143     : CGUIWindow(WINDOW_SLIDESHOW, "SlideShow.xml")
144 {
145   m_pBackgroundLoader = NULL;
146   m_slides = new CFileItemList;
147   m_Resolution = RES_INVALID;
148   m_loadType = KEEP_IN_MEMORY;
149   Reset();
150 }
151
152 CGUIWindowSlideShow::~CGUIWindowSlideShow(void)
153 {
154   Reset();
155   delete m_slides;
156 }
157
158 void CGUIWindowSlideShow::AnnouncePlayerPlay(const CFileItemPtr& item)
159 {
160   CVariant param;
161   param["player"]["speed"] = 1;
162   param["player"]["playerid"] = PLAYLIST_PICTURE;
163   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPlay", item, param);
164 }
165
166 void CGUIWindowSlideShow::AnnouncePlayerPause(const CFileItemPtr& item)
167 {
168   CVariant param;
169   param["player"]["speed"] = 0;
170   param["player"]["playerid"] = PLAYLIST_PICTURE;
171   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPause", item, param);
172 }
173
174 void CGUIWindowSlideShow::AnnouncePlayerStop(const CFileItemPtr& item)
175 {
176   CVariant param;
177   param["player"]["playerid"] = PLAYLIST_PICTURE;
178   param["end"] = true;
179   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnStop", item, param);
180 }
181
182 void CGUIWindowSlideShow::AnnouncePlaylistRemove(int pos)
183 {
184   CVariant data;
185   data["playlistid"] = PLAYLIST_PICTURE;
186   data["position"] = pos;
187   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Playlist, "xbmc", "OnRemove", data);
188 }
189
190 void CGUIWindowSlideShow::AnnouncePlaylistClear()
191 {
192   CVariant data;
193   data["playlistid"] = PLAYLIST_PICTURE;
194   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Playlist, "xbmc", "OnClear", data);
195 }
196
197 void CGUIWindowSlideShow::AnnouncePlaylistAdd(const CFileItemPtr& item, int pos)
198 {
199   CVariant data;
200   data["playlistid"] = PLAYLIST_PICTURE;
201   data["position"] = pos;
202   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Playlist, "xbmc", "OnAdd", item, data);
203 }
204
205 void CGUIWindowSlideShow::AnnouncePropertyChanged(const std::string &strProperty, const CVariant &value)
206 {
207   if (strProperty.empty() || value.isNull())
208     return;
209
210   CVariant data;
211   data["player"]["playerid"] = PLAYLIST_PICTURE;
212   data["property"][strProperty] = value;
213   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPropertyChanged", data);
214 }
215
216 bool CGUIWindowSlideShow::IsPlaying() const
217 {
218   return m_Image[m_iCurrentPic].IsLoaded();
219 }
220
221 void CGUIWindowSlideShow::Reset()
222 {
223   g_infoManager.SetShowCodec(false);
224   m_bSlideShow = false;
225   m_bShuffled = false;
226   m_bPause = false;
227   m_bPlayingVideo = false;
228   m_bErrorMessage = false;
229   m_bReloadImage = false;
230   m_bScreensaver = false;
231   m_Image[0].UnLoad();
232   m_Image[0].Close();
233   m_Image[1].UnLoad();
234   m_Image[1].Close();
235
236   m_fRotate = 0.0f;
237   m_fInitialRotate = 0.0f;
238   m_iZoomFactor = 1;
239   m_fZoom = 1.0f;
240   m_fInitialZoom = 0.0f;
241   m_iCurrentSlide = 0;
242   m_iNextSlide = 1;
243   m_iCurrentPic = 0;
244   m_iDirection = 1;
245   CSingleLock lock(m_slideSection);
246   m_slides->Clear();
247   AnnouncePlaylistClear();
248   m_Resolution = g_graphicsContext.GetVideoResolution();
249 }
250
251 void CGUIWindowSlideShow::OnDeinitWindow(int nextWindowID)
252
253   if (m_Resolution != g_guiSettings.m_LookAndFeelResolution)
254   {
255     //FIXME: Use GUI resolution for now
256     //g_graphicsContext.SetVideoResolution(g_guiSettings.m_LookAndFeelResolution, TRUE);
257   }
258
259   //   Reset();
260   if (nextWindowID != WINDOW_PICTURES)
261     m_ImageLib.Unload();
262
263   g_windowManager.ShowOverlay(OVERLAY_STATE_SHOWN);
264
265   // wait for any outstanding picture loads
266   if (m_pBackgroundLoader)
267   {
268     // sleep until the loader finishes loading the current pic
269     CLog::Log(LOGDEBUG,"Waiting for BackgroundLoader thread to close");
270     while (m_pBackgroundLoader->IsLoading())
271       Sleep(10);
272     // stop the thread
273     CLog::Log(LOGDEBUG,"Stopping BackgroundLoader thread");
274     m_pBackgroundLoader->StopThread();
275     delete m_pBackgroundLoader;
276     m_pBackgroundLoader = NULL;
277   }
278   // and close the images.
279   m_Image[0].Close();
280   m_Image[1].Close();
281   g_infoManager.ResetCurrentSlide();
282
283   CGUIWindow::OnDeinitWindow(nextWindowID);
284 }
285
286 void CGUIWindowSlideShow::Add(const CFileItem *picture)
287 {
288   CFileItemPtr item(new CFileItem(*picture));
289   if (!item->HasVideoInfoTag() && !item->HasPictureInfoTag())
290   {
291     // item without tag; assume it is a picture and force tag generation
292     item->GetPictureInfoTag();
293   }
294   AnnouncePlaylistAdd(item, m_slides->Size());
295
296   m_slides->Add(item);
297 }
298
299 void CGUIWindowSlideShow::ShowNext()
300 {
301   if (m_slides->Size() == 1)
302     return;
303
304   m_iNextSlide = m_iCurrentSlide + 1;
305   if (m_iNextSlide >= m_slides->Size())
306     m_iNextSlide = 0;
307
308   m_iDirection   = 1;
309   m_iZoomFactor  = 1;
310   m_fZoom        = 1.0f;
311   m_fRotate      = 0.0f;
312   m_bLoadNextPic = true;
313 }
314
315 void CGUIWindowSlideShow::ShowPrevious()
316 {
317   if (m_slides->Size() == 1)
318     return;
319
320   m_iNextSlide = m_iCurrentSlide - 1;
321   if (m_iNextSlide < 0)
322     m_iNextSlide = m_slides->Size() - 1;
323
324   m_iDirection   = -1;
325   m_iZoomFactor  = 1;
326   m_fZoom        = 1.0f;
327   m_fRotate      = 0.0f;
328   m_bLoadNextPic = true;
329 }
330
331
332 void CGUIWindowSlideShow::Select(const CStdString& strPicture)
333 {
334   for (int i = 0; i < m_slides->Size(); ++i)
335   {
336     const CFileItemPtr item = m_slides->Get(i);
337     if (item->GetPath() == strPicture)
338     {
339       m_iDirection = 1;
340       if (IsActive())
341         m_iNextSlide = i;
342       else
343       {
344         m_iCurrentSlide = i;
345         m_iNextSlide = GetNextSlide();
346       }
347       m_bLoadNextPic = true;
348       return ;
349     }
350   }
351 }
352
353 const CFileItemList &CGUIWindowSlideShow::GetSlideShowContents()
354 {
355   return *m_slides;
356 }
357
358 void CGUIWindowSlideShow::GetSlideShowContents(CFileItemList &list)
359 {
360   for (int index = 0; index < m_slides->Size(); index++)
361     list.Add(CFileItemPtr(new CFileItem(*m_slides->Get(index))));
362 }
363
364 const CFileItemPtr CGUIWindowSlideShow::GetCurrentSlide()
365 {
366   if (m_iCurrentSlide >= 0 && m_iCurrentSlide < m_slides->Size())
367     return m_slides->Get(m_iCurrentSlide);
368   return CFileItemPtr();
369 }
370
371 bool CGUIWindowSlideShow::InSlideShow() const
372 {
373   return m_bSlideShow;
374 }
375
376 void CGUIWindowSlideShow::StartSlideShow(bool screensaver)
377 {
378   m_bSlideShow = true;
379   m_iDirection = 1;
380   m_bScreensaver = screensaver;
381   if (m_slides->Size())
382     AnnouncePlayerPlay(m_slides->Get(m_iCurrentSlide));
383 }
384
385 void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &regions)
386 {
387   // reset the screensaver if we're in a slideshow
388   // (unless we are the screensaver!)
389   if (m_bSlideShow && !g_application.IsInScreenSaver())
390     g_application.ResetScreenSaver();
391   int iSlides = m_slides->Size();
392   if (!iSlides) return ;
393
394   // if we haven't rendered yet, we should mark the whole screen
395   if (!m_hasRendered)
396     regions.push_back(CRect(0.0f, 0.0f, (float)g_graphicsContext.GetWidth(), (float)g_graphicsContext.GetHeight()));
397
398   if (m_iNextSlide < 0 || m_iNextSlide >= m_slides->Size())
399     m_iNextSlide = 0;
400   if (m_iCurrentSlide < 0 || m_iCurrentSlide >= m_slides->Size())
401     m_iCurrentSlide = 0;
402
403   // Create our background loader if necessary
404   if (!m_pBackgroundLoader)
405   {
406     m_pBackgroundLoader = new CBackgroundPicLoader();
407
408     if (!m_pBackgroundLoader)
409     {
410       throw 1;
411     }
412     m_pBackgroundLoader->Create(this);
413   }
414
415   bool bSlideShow = m_bSlideShow && !m_bPause && !m_bPlayingVideo;
416
417   if (m_bErrorMessage)
418   { // we have an error when loading either the current or next picture
419     // check to see if we have a picture loaded
420     CLog::Log(LOGDEBUG, "We have an error loading a picture!");
421     if (m_Image[m_iCurrentPic].IsLoaded())
422     { // Yes.  Let's let it transistion out, wait for it to be released, then try loading again.
423       CLog::Log(LOGERROR, "Error loading the next image %s", m_slides->Get(m_iNextSlide)->GetPath().c_str());
424       if (!bSlideShow)
425       { // tell the pic to start transistioning out now
426         m_Image[m_iCurrentPic].StartTransistion();
427         m_Image[m_iCurrentPic].SetTransistionTime(1, IMMEDIATE_TRANSISTION_TIME); // only 20 frames for the transistion
428       }
429       m_bWaitForNextPic = true;
430       m_bErrorMessage = false;
431     }
432     else
433     { // No.  Not much we can do here.  If we're in a slideshow, we mayaswell move on to the next picture
434       // change to next image
435       if (bSlideShow)
436       {
437         CLog::Log(LOGERROR, "Error loading the current image %s", m_slides->Get(m_iCurrentSlide)->GetPath().c_str());
438         m_iCurrentSlide = m_iNextSlide;
439         m_iNextSlide    = GetNextSlide();
440         ShowNext();
441         m_bErrorMessage = false;
442       }
443       else if (m_bLoadNextPic)
444       {
445         m_iCurrentSlide = m_iNextSlide;
446         m_iNextSlide    = GetNextSlide();
447         m_bErrorMessage = false;
448       }
449       // else just drop through - there's nothing we can do (error message will be displayed)
450     }
451   }
452
453   if (m_bErrorMessage)
454   { // hack, just mark it all
455     regions.push_back(CRect(0.0f, 0.0f, (float)g_graphicsContext.GetWidth(), (float)g_graphicsContext.GetHeight()));
456     return;
457   }
458
459   if (!m_Image[m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading())
460   { // load first image
461     CLog::Log(LOGDEBUG, "Loading the current image %s", m_slides->Get(m_iCurrentSlide)->GetPath().c_str());
462     m_bWaitForNextPic = false;
463     m_bLoadNextPic = false;
464     // load using the background loader
465     int maxWidth, maxHeight;
466     GetCheckedSize((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom,
467                    (float)g_settings.m_ResInfo[m_Resolution].iHeight * m_fZoom,
468                     maxWidth, maxHeight);
469     if (!m_slides->Get(m_iCurrentSlide)->IsVideo()) 
470       m_pBackgroundLoader->LoadPic(m_iCurrentPic, m_iCurrentSlide, m_slides->Get(m_iCurrentSlide)->GetPath(), maxWidth, maxHeight);
471   }
472
473   // check if we should discard an already loaded next slide
474   if (m_bLoadNextPic && m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() != m_iNextSlide)
475     m_Image[1 - m_iCurrentPic].Close();
476
477   // if we're reloading an image (for better res on zooming we need to close any open ones as well)
478   if (m_bReloadImage && m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() != m_iCurrentSlide)
479     m_Image[1 - m_iCurrentPic].Close();
480
481   if (m_bReloadImage)
482   {
483     if (m_Image[m_iCurrentPic].IsLoaded() && !m_Image[1 - m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading() && !m_bWaitForNextPic)
484     { // reload the image if we need to
485       CLog::Log(LOGDEBUG, "Reloading the current image %s at zoom level %i", m_slides->Get(m_iCurrentSlide)->GetPath().c_str(), m_iZoomFactor);
486       // first, our maximal size for this zoom level
487       int maxWidth = (int)((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom);
488       int maxHeight = (int)((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom);
489
490       // the actual maximal size of the image to optimize the sizing based on the known sizing (aspect ratio)
491       int width, height;
492       GetCheckedSize((float)m_Image[m_iCurrentPic].GetOriginalWidth(), (float)m_Image[m_iCurrentPic].GetOriginalHeight(), width, height);
493
494       // use the smaller of the two (no point zooming in more than we have to)
495       if (maxWidth < width)
496         width = maxWidth;
497       if (maxHeight < height)
498         height = maxHeight;
499
500       m_pBackgroundLoader->LoadPic(m_iCurrentPic, m_iCurrentSlide, m_slides->Get(m_iCurrentSlide)->GetPath(), width, height);
501     }
502   }
503   else
504   {
505     if (m_iNextSlide != m_iCurrentSlide && m_Image[m_iCurrentPic].IsLoaded() && !m_Image[1 - m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading() && !m_bWaitForNextPic)
506     { // load the next image
507       CLog::Log(LOGDEBUG, "Loading the next image %s", m_slides->Get(m_iNextSlide)->GetPath().c_str());
508       int maxWidth, maxHeight;
509       GetCheckedSize((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom,
510                      (float)g_settings.m_ResInfo[m_Resolution].iHeight * m_fZoom,
511                      maxWidth, maxHeight);
512       if (!m_slides->Get(m_iNextSlide)->IsVideo())
513         m_pBackgroundLoader->LoadPic(1 - m_iCurrentPic, m_iNextSlide, m_slides->Get(m_iNextSlide)->GetPath(), maxWidth, maxHeight);
514     }
515   }
516
517   // render the current image
518   if (m_Image[m_iCurrentPic].IsLoaded())
519   {
520     m_Image[m_iCurrentPic].SetInSlideshow(m_bSlideShow);
521     m_Image[m_iCurrentPic].Pause(m_bPause);
522     m_Image[m_iCurrentPic].Process(currentTime, regions);
523   }
524
525   if (m_slides->Get(m_iCurrentSlide)->IsVideo() && bSlideShow)
526   { 
527     CLog::Log(LOGDEBUG, "Playing slide %s as video", m_slides->Get(m_iCurrentSlide)->GetPath().c_str());
528     m_bPlayingVideo = true;
529     CApplicationMessenger::Get().PlayFile(*m_slides->Get(m_iCurrentSlide));
530     m_iCurrentSlide = m_iNextSlide;
531     m_iNextSlide    = GetNextSlide();
532   }
533
534   // Check if we should be transistioning immediately
535   if (m_bLoadNextPic)
536   {
537     CLog::Log(LOGDEBUG, "Starting immediate transistion due to user wanting slide %s", m_slides->Get(m_iNextSlide)->GetPath().c_str());
538     if (m_Image[m_iCurrentPic].StartTransistion())
539     {
540       m_Image[m_iCurrentPic].SetTransistionTime(1, IMMEDIATE_TRANSISTION_TIME); // only 20 frames for the transistion
541       m_bLoadNextPic = false;
542     }
543   }
544
545   // render the next image
546   if (m_Image[m_iCurrentPic].DrawNextImage())
547   {
548     if (m_Image[1 - m_iCurrentPic].IsLoaded())
549     {
550       // set the appropriate transistion time
551       m_Image[1 - m_iCurrentPic].SetTransistionTime(0, m_Image[m_iCurrentPic].GetTransistionTime(1));
552       m_Image[1 - m_iCurrentPic].Pause(m_bPause);
553       m_Image[1 - m_iCurrentPic].Process(currentTime, regions);
554     }
555     else // next pic isn't loaded.  We should hang around if it is in progress
556     {
557       if (m_pBackgroundLoader->IsLoading())
558       {
559 //        CLog::Log(LOGDEBUG, "Having to hold the current image (%s) while we load %s", m_vecSlides[m_iCurrentSlide].c_str(), m_vecSlides[m_iNextSlide].c_str());
560         m_Image[m_iCurrentPic].Keep();
561       }
562     }
563   }
564
565   // check if we should swap images now
566   if (m_Image[m_iCurrentPic].IsFinished())
567   {
568     CLog::Log(LOGDEBUG, "Image %s is finished rendering, switching to %s", m_slides->Get(m_iCurrentSlide)->GetPath().c_str(), m_slides->Get(m_iNextSlide)->GetPath().c_str());
569     m_Image[m_iCurrentPic].Close();
570     if (m_Image[1 - m_iCurrentPic].IsLoaded())
571       m_iCurrentPic = 1 - m_iCurrentPic;
572
573     m_iCurrentSlide = m_iNextSlide;
574     m_iNextSlide    = GetNextSlide();
575     AnnouncePlayerPlay(m_slides->Get(m_iCurrentSlide));
576
577     m_iZoomFactor = 1;
578     m_fZoom = 1.0f;
579     m_fRotate = 0.0f;
580   }
581
582   if (m_Image[m_iCurrentPic].IsLoaded())
583     g_infoManager.SetCurrentSlide(*m_slides->Get(m_iCurrentSlide));
584
585   RenderPause();
586   CGUIWindow::Process(currentTime, regions);
587 }
588
589 void CGUIWindowSlideShow::Render()
590 {
591   if (m_Image[m_iCurrentPic].IsLoaded())
592     m_Image[m_iCurrentPic].Render();
593
594   if (m_Image[m_iCurrentPic].DrawNextImage() && m_Image[1 - m_iCurrentPic].IsLoaded())
595     m_Image[1 - m_iCurrentPic].Render();
596
597   RenderErrorMessage();
598   CGUIWindow::Render();
599 }
600
601 int CGUIWindowSlideShow::GetNextSlide()
602 {
603   if (m_slides->Size() <= 1)
604     return m_iCurrentSlide;
605   if (m_bSlideShow || m_iDirection >= 0)
606     return (m_iCurrentSlide + 1) % m_slides->Size();
607
608   return (m_iCurrentSlide - 1 + m_slides->Size()) % m_slides->Size();
609 }
610
611 EVENT_RESULT CGUIWindowSlideShow::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
612 {
613   if (event.m_id == ACTION_GESTURE_NOTIFY)
614   {
615     if (m_iZoomFactor == 1) //zoomed out - no inertial scrolling
616       return EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA;
617
618     return EVENT_RESULT_PAN_HORIZONTAL;
619   }  
620   else if (event.m_id == ACTION_GESTURE_BEGIN)
621   {
622     m_firstGesturePoint = point;
623     m_fInitialZoom = m_fZoom;
624     m_fInitialRotate = m_fRotate;
625     return EVENT_RESULT_HANDLED;
626   }
627   else if (event.m_id == ACTION_GESTURE_PAN)
628   { // on zoomlevel 1 just detect swipe left and right
629     if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
630     {
631       if (m_firstGesturePoint.x > 0 && fabs(point.x - m_firstGesturePoint.x) > 100)
632       {
633         if (point.x < m_firstGesturePoint.x)
634           OnAction(CAction(ACTION_NEXT_PICTURE));
635         else 
636           OnAction(CAction(ACTION_PREV_PICTURE));
637
638         m_firstGesturePoint.x = 0;
639       }
640     }
641     else //zoomed in - free move mode
642     {
643       Move(PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.x - point.x), PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.y - point.y));
644       m_firstGesturePoint = point;
645     }
646     return EVENT_RESULT_HANDLED;
647   }
648   else if (event.m_id == ACTION_GESTURE_END)
649   {
650     if (m_fRotate != 0.0f)
651     {
652       // "snap" to nearest of 0, 90, 180 and 270 if the
653       // difference in angle is +/-10 degrees
654       float reminder = fmodf(m_fRotate, 90.0f);
655       if (fabs(reminder) < ROTATION_SNAP_RANGE)
656         Rotate(-reminder);
657       else if (reminder > 90.0f - ROTATION_SNAP_RANGE)
658         Rotate(90.0f - reminder);
659       else if (-reminder > 90.0f - ROTATION_SNAP_RANGE)
660         Rotate(-90.0f - reminder);
661     }
662
663     m_fInitialZoom = 0.0f;
664     m_fInitialRotate = 0.0f;
665     return EVENT_RESULT_HANDLED;
666   }
667   else if (event.m_id == ACTION_GESTURE_ZOOM)
668   {
669     ZoomRelative(m_fInitialZoom * event.m_offsetX, true);
670     return EVENT_RESULT_HANDLED;
671   }
672   else if (event.m_id == ACTION_GESTURE_ROTATE)
673   {
674     Rotate(m_fInitialRotate + event.m_offsetX - m_fRotate, true);
675     return EVENT_RESULT_HANDLED;
676   }
677   return EVENT_RESULT_UNHANDLED;
678 }
679
680 bool CGUIWindowSlideShow::OnAction(const CAction &action)
681 {
682   if (m_bScreensaver)
683   {
684     g_windowManager.PreviousWindow();
685     return true;
686   }
687
688   switch (action.GetID())
689   {
690   case ACTION_SHOW_CODEC:
691     {
692       CGUIDialogPictureInfo *pictureInfo = (CGUIDialogPictureInfo *)g_windowManager.GetWindow(WINDOW_DIALOG_PICTURE_INFO);
693       if (pictureInfo)
694       {
695         // no need to set the picture here, it's done in Render()
696         pictureInfo->DoModal();
697       }
698     }
699     break;
700
701   case ACTION_PREVIOUS_MENU:
702   case ACTION_NAV_BACK:
703   case ACTION_STOP:
704     if (m_slides->Size())
705       AnnouncePlayerStop(m_slides->Get(m_iCurrentSlide));
706     g_windowManager.PreviousWindow();
707     break;
708
709   case ACTION_NEXT_PICTURE:
710       ShowNext();
711     break;
712
713   case ACTION_PREV_PICTURE:
714       ShowPrevious();
715     break;
716
717   case ACTION_MOVE_RIGHT:
718     if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
719       ShowNext();
720     else
721       Move(PICTURE_MOVE_AMOUNT, 0);
722     break;
723
724   case ACTION_MOVE_LEFT:
725     if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
726       ShowPrevious();
727     else
728       Move( -PICTURE_MOVE_AMOUNT, 0);
729     break;
730
731   case ACTION_MOVE_DOWN:
732     Move(0, PICTURE_MOVE_AMOUNT);
733     break;
734
735   case ACTION_MOVE_UP:
736     Move(0, -PICTURE_MOVE_AMOUNT);
737     break;
738
739   case ACTION_PAUSE:
740     if (m_bSlideShow)
741     {
742       m_bPause = !m_bPause;
743       if (m_slides->Size())
744       {
745         if (m_bPause)
746           AnnouncePlayerPause(m_slides->Get(m_iCurrentSlide));
747         else
748           AnnouncePlayerPlay(m_slides->Get(m_iCurrentSlide));
749       }
750     }
751     break;
752
753   case ACTION_PLAYER_PLAY:
754     if (!m_bSlideShow)
755     {
756       m_bSlideShow = true;
757       m_bPause = false;
758     }
759     else if (m_bPause)
760     {
761       m_bPause = false;
762       if (m_slides->Size())
763         AnnouncePlayerPlay(m_slides->Get(m_iCurrentSlide));
764     }
765     break;
766
767   case ACTION_ZOOM_OUT:
768     Zoom(m_iZoomFactor - 1);
769     break;
770
771   case ACTION_ZOOM_IN:
772     Zoom(m_iZoomFactor + 1);
773     break;
774
775   case ACTION_ROTATE_PICTURE_CW:
776     Rotate(90.0f);
777     break;
778
779   case ACTION_ROTATE_PICTURE_CCW:
780     Rotate(-90.0f);
781     break;
782
783   case ACTION_ZOOM_LEVEL_NORMAL:
784   case ACTION_ZOOM_LEVEL_1:
785   case ACTION_ZOOM_LEVEL_2:
786   case ACTION_ZOOM_LEVEL_3:
787   case ACTION_ZOOM_LEVEL_4:
788   case ACTION_ZOOM_LEVEL_5:
789   case ACTION_ZOOM_LEVEL_6:
790   case ACTION_ZOOM_LEVEL_7:
791   case ACTION_ZOOM_LEVEL_8:
792   case ACTION_ZOOM_LEVEL_9:
793     Zoom((action.GetID() - ACTION_ZOOM_LEVEL_NORMAL) + 1);
794     break;
795
796   case ACTION_ANALOG_MOVE:
797     Move(action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, -action.GetAmount(1)*PICTURE_MOVE_AMOUNT_ANALOG);
798     break;
799
800   default:
801     return CGUIWindow::OnAction(action);
802   }
803   return true;
804 }
805
806 void CGUIWindowSlideShow::RenderErrorMessage()
807 {
808   if (!m_bErrorMessage)
809     return ;
810
811   const CGUIControl *control = GetControl(LABEL_ROW1);
812   if (NULL == control || control->GetControlType() != CGUIControl::GUICONTROL_LABEL)
813   {
814      CLog::Log(LOGERROR,"CGUIWindowSlideShow::RenderErrorMessage - cant get label control!");
815      return;
816   }
817
818   CGUIFont *pFont = ((CGUILabelControl *)control)->GetLabelInfo().font;
819   CGUITextLayout::DrawText(pFont, 0.5f*g_graphicsContext.GetWidth(), 0.5f*g_graphicsContext.GetHeight(), 0xffffffff, 0, g_localizeStrings.Get(747), XBFONT_CENTER_X | XBFONT_CENTER_Y);
820 }
821
822 bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message)
823 {
824   switch ( message.GetMessage() )
825   {
826   case GUI_MSG_WINDOW_INIT:
827     {
828       m_Resolution = (RESOLUTION) g_guiSettings.GetInt("pictures.displayresolution");
829
830       //FIXME: Use GUI resolution for now
831       if (0 /*m_Resolution != g_guiSettings.m_LookAndFeelResolution && m_Resolution != INVALID && m_Resolution!=AUTORES*/)
832         g_graphicsContext.SetVideoResolution(m_Resolution);
833       else
834         m_Resolution = g_graphicsContext.GetVideoResolution();
835
836       CGUIWindow::OnMessage(message);
837       if (message.GetParam1() != WINDOW_PICTURES)
838         m_ImageLib.Load();
839
840       g_windowManager.ShowOverlay(OVERLAY_STATE_HIDDEN);
841
842       // turn off slideshow if we only have 1 image
843       if (m_slides->Size() <= 1)
844         m_bSlideShow = false;
845
846       return true;
847     }
848     break;
849
850   case GUI_MSG_START_SLIDESHOW:
851     {
852       CStdString strFolder = message.GetStringParam();
853       unsigned int iParams = message.GetParam1();
854       //decode params
855       bool bRecursive = false;
856       bool bRandom = false;
857       bool bNotRandom = false;
858       if (iParams > 0)
859       {
860         if ((iParams & 1) == 1)
861           bRecursive = true;
862         if ((iParams & 2) == 2)
863           bRandom = true;
864         if ((iParams & 4) == 4)
865           bNotRandom = true;
866       }
867       RunSlideShow(strFolder, bRecursive, bRandom, bNotRandom);
868     }
869     break;
870
871     case GUI_MSG_PLAYLISTPLAYER_STOPPED:
872       {
873         m_bPlayingVideo = false;
874         if (m_bSlideShow)
875           g_windowManager.ActivateWindow(WINDOW_SLIDESHOW);
876       }
877       break;
878
879     case GUI_MSG_PLAYBACK_STARTED:
880       {
881         if (m_bSlideShow && m_bPlayingVideo)
882           g_windowManager.ActivateWindow(WINDOW_FULLSCREEN_VIDEO);
883       }
884       break;
885
886     case GUI_MSG_PLAYBACK_STOPPED:
887       {
888         if (m_bSlideShow && m_bPlayingVideo)
889         {
890           m_bSlideShow = false;
891           g_windowManager.PreviousWindow();
892         }
893       }
894       break;
895   }
896   return CGUIWindow::OnMessage(message);
897 }
898
899 void CGUIWindowSlideShow::RenderPause()
900 { // display the pause icon
901   if (m_bPause)
902   {
903     SET_CONTROL_VISIBLE(CONTROL_PAUSE);
904   }
905   else
906   {
907     SET_CONTROL_HIDDEN(CONTROL_PAUSE);
908   }
909   /*
910    static DWORD dwCounter=0;
911    dwCounter++;
912    if (dwCounter > 25)
913    {
914     dwCounter=0;
915    }
916    if (!m_bPause) return;
917    if (dwCounter <13) return;*/
918
919 }
920
921 void CGUIWindowSlideShow::Rotate(float fAngle, bool immediate /* = false */)
922 {
923   if (m_Image[m_iCurrentPic].DrawNextImage())
924     return;
925
926   m_fRotate += fAngle;
927
928   m_Image[m_iCurrentPic].Rotate(fAngle, immediate);
929 }
930
931 void CGUIWindowSlideShow::Zoom(int iZoom)
932 {
933   if (iZoom > MAX_ZOOM_FACTOR || iZoom < 1)
934     return;
935
936   ZoomRelative(zoomamount[iZoom - 1]);
937 }
938
939 void CGUIWindowSlideShow::ZoomRelative(float fZoom, bool immediate /* = false */)
940 {
941   if (fZoom < zoomamount[0])
942     fZoom = zoomamount[0];
943   else if (fZoom > zoomamount[MAX_ZOOM_FACTOR - 1])
944     fZoom = zoomamount[MAX_ZOOM_FACTOR - 1];
945
946   if (m_Image[m_iCurrentPic].DrawNextImage())
947     return;
948
949   m_fZoom = fZoom;
950
951   // find the nearest zoom factor
952 #ifdef RELOAD_ON_ZOOM
953   int iOldZoomFactor = m_iZoomFactor;
954 #endif
955   for (unsigned int i = 1; i < MAX_ZOOM_FACTOR; i++)
956   {
957     if (m_fZoom > zoomamount[i])
958       continue;
959
960     if (fabs(m_fZoom - zoomamount[i - 1]) < fabs(m_fZoom - zoomamount[i]))
961       m_iZoomFactor = i;
962     else
963       m_iZoomFactor = i + 1;
964
965     break;
966   }
967
968   // set the zoom amount and then set so that the image is reloaded at the higher (or lower)
969   // resolution as necessary
970   m_Image[m_iCurrentPic].Zoom(m_fZoom, immediate);
971
972 #ifdef RELOAD_ON_ZOOM
973   if (m_iZoomFactor == 1 || (iZoomFactor > iOldZoomFactor && !m_Image[m_iCurrentPic].FullSize()))
974     m_bReloadImage = true;
975 #endif
976 }
977
978 void CGUIWindowSlideShow::Move(float fX, float fY)
979 {
980   if (m_Image[m_iCurrentPic].IsLoaded() && m_Image[m_iCurrentPic].GetZoom() > 1)
981   { // we move in the opposite direction, due to the fact we are moving
982     // the viewing window, not the picture.
983     m_Image[m_iCurrentPic].Move( -fX, -fY);
984   }
985 }
986
987 void CGUIWindowSlideShow::OnLoadPic(int iPic, int iSlideNumber, CBaseTexture* pTexture, bool bFullSize)
988 {
989   if (pTexture)
990   {
991     // set the pic's texture + size etc.
992     CSingleLock lock(m_slideSection);
993     if (iSlideNumber >= m_slides->Size())
994     { // throw this away - we must have cleared the slideshow while we were still loading
995       delete pTexture;
996       return;
997     }
998     CLog::Log(LOGDEBUG, "Finished background loading %s", m_slides->Get(iSlideNumber)->GetPath().c_str());
999     if (m_bReloadImage)
1000     {
1001       if (m_Image[m_iCurrentPic].IsLoaded() && m_Image[m_iCurrentPic].SlideNumber() != iSlideNumber)
1002       { // wrong image (ie we finished loading the next image, not the current image)
1003         delete pTexture;
1004         return;
1005       }
1006       m_Image[m_iCurrentPic].UpdateTexture(pTexture);
1007       m_Image[m_iCurrentPic].SetOriginalSize(pTexture->GetOriginalWidth(), pTexture->GetOriginalHeight(), bFullSize);
1008       m_bReloadImage = false;
1009     }
1010     else
1011     {
1012       if (m_bSlideShow)
1013         m_Image[iPic].SetTexture(iSlideNumber, pTexture, g_guiSettings.GetBool("slideshow.displayeffects") ? CSlideShowPic::EFFECT_RANDOM : CSlideShowPic::EFFECT_NONE);
1014       else
1015         m_Image[iPic].SetTexture(iSlideNumber, pTexture, CSlideShowPic::EFFECT_NO_TIMEOUT);
1016       m_Image[iPic].SetOriginalSize(pTexture->GetOriginalWidth(), pTexture->GetOriginalHeight(), bFullSize);
1017
1018       m_Image[iPic].m_bIsComic = false;
1019       if (URIUtils::IsInRAR(m_slides->Get(m_iCurrentSlide)->GetPath()) || URIUtils::IsInZIP(m_slides->Get(m_iCurrentSlide)->GetPath())) // move to top for cbr/cbz
1020       {
1021         CURL url(m_slides->Get(m_iCurrentSlide)->GetPath());
1022         CStdString strHostName = url.GetHostName();
1023         if (URIUtils::GetExtension(strHostName).Equals(".cbr", false) || URIUtils::GetExtension(strHostName).Equals(".cbz", false))
1024         {
1025           m_Image[iPic].m_bIsComic = true;
1026           m_Image[iPic].Move((float)m_Image[iPic].GetOriginalWidth(),(float)m_Image[iPic].GetOriginalHeight());
1027         }
1028       }
1029     }
1030   }
1031   else
1032   { // Failed to load image.  What should be done??
1033     // We should wait for the current pic to finish rendering, then transistion it out,
1034     // release the texture, and try and reload this pic from scratch
1035     m_bErrorMessage = true;
1036   }
1037 }
1038
1039 void CGUIWindowSlideShow::Shuffle()
1040 {
1041   m_slides->Randomize();
1042   m_iCurrentSlide = 0;
1043   m_iNextSlide = 1;
1044   m_bShuffled = true;
1045
1046   AnnouncePropertyChanged("shuffled", true);
1047 }
1048
1049 int CGUIWindowSlideShow::NumSlides() const
1050 {
1051   return m_slides->Size();
1052 }
1053
1054 int CGUIWindowSlideShow::CurrentSlide() const
1055 {
1056   return m_iCurrentSlide + 1;
1057 }
1058
1059 void CGUIWindowSlideShow::AddFromPath(const CStdString &strPath,
1060                                       bool bRecursive, 
1061                                       SORT_METHOD method, SortOrder order, const CStdString &strExtensions)
1062 {
1063   if (strPath!="")
1064   {
1065     // reset the slideshow
1066     Reset();
1067     m_strExtensions = strExtensions;
1068     if (bRecursive)
1069     {
1070       path_set recursivePaths;
1071       AddItems(strPath, &recursivePaths, method, order);
1072     }
1073     else
1074       AddItems(strPath, NULL, method, order);
1075   }
1076 }
1077
1078 void CGUIWindowSlideShow::RunSlideShow(const CStdString &strPath, 
1079                                        bool bRecursive /* = false */, bool bRandom /* = false */, 
1080                                        bool bNotRandom /* = false */, SORT_METHOD method /* = SORT_METHOD_LABEL */, 
1081                                        SortOrder order /* = SortOrderAscending */, const CStdString &strExtensions)
1082 {
1083   // stop any video
1084   if (g_application.IsPlayingVideo())
1085     g_application.StopPlaying();
1086
1087   AddFromPath(strPath, bRecursive, method, order, strExtensions);
1088
1089   // mutually exclusive options
1090   // if both are set, clear both and use the gui setting
1091   if (bRandom && bNotRandom)
1092     bRandom = bNotRandom = false;
1093
1094   // NotRandom overrides the window setting
1095   if ((!bNotRandom && g_guiSettings.GetBool("slideshow.shuffle")) || bRandom)
1096     Shuffle();
1097
1098   StartSlideShow();
1099   if (NumSlides())
1100     g_windowManager.ActivateWindow(WINDOW_SLIDESHOW);
1101 }
1102
1103 void CGUIWindowSlideShow::AddItems(const CStdString &strPath, path_set *recursivePaths, SORT_METHOD method, SortOrder order)
1104 {
1105   // check whether we've already added this path
1106   if (recursivePaths)
1107   {
1108     CStdString path(strPath);
1109     URIUtils::RemoveSlashAtEnd(path);
1110     if (recursivePaths->find(path) != recursivePaths->end())
1111       return;
1112     recursivePaths->insert(path);
1113   }
1114
1115   // fetch directory and sort accordingly
1116   CFileItemList items;
1117   if (!CDirectory::GetDirectory(strPath, items, m_strExtensions.IsEmpty()?g_settings.m_pictureExtensions:m_strExtensions,DIR_FLAG_NO_FILE_DIRS,true))
1118     return;
1119
1120   items.Sort(method, order);
1121
1122   // need to go into all subdirs
1123   for (int i = 0; i < items.Size(); i++)
1124   {
1125     CFileItemPtr item = items[i];
1126     if (item->m_bIsFolder && recursivePaths)
1127     {
1128       AddItems(item->GetPath(), recursivePaths);
1129     }
1130     else if (!item->m_bIsFolder && !URIUtils::IsRAR(item->GetPath()) && !URIUtils::IsZIP(item->GetPath()))
1131     { // add to the slideshow
1132       Add(item.get());
1133     }
1134   }
1135 }
1136
1137 void CGUIWindowSlideShow::GetCheckedSize(float width, float height, int &maxWidth, int &maxHeight)
1138 {
1139 #ifdef RELOAD_ON_ZOOM
1140   if (width * height > MAX_PICTURE_SIZE)
1141   {
1142     float fScale = sqrt((float)MAX_PICTURE_SIZE / (width * height));
1143     width = fScale * width;
1144     height = fScale * height;
1145   }
1146   maxWidth = (int)width;
1147   maxHeight = (int)height;
1148   if (maxWidth > (int)g_Windowing.GetMaxTextureSize())
1149     maxWidth = g_Windowing.GetMaxTextureSize();
1150   if (maxHeight > (int)g_Windowing.GetMaxTextureSize())
1151     maxHeight = g_Windowing.GetMaxTextureSize();
1152 #else
1153   maxWidth = g_Windowing.GetMaxTextureSize();
1154   maxHeight = g_Windowing.GetMaxTextureSize();
1155 #endif
1156 }
1157
1158