MythTV  master
imageview.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 /* ============================================================
3  * File : imageview.cpp
4  * Description :
5  *
6  * Copyright 2004-2006 Renchi Raju, Daniel Kristjansson
7  *
8  * This program is free software; you can redistribute it
9  * and/or modify it under the terms of the GNU General
10  * Public License as published bythe Free Software Foundation;
11  * either version 2, or (at your option)
12  * any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * ============================================================ */
20 
21 // STL headers
22 #include <algorithm>
23 #include <cmath>
24 #include <cstdlib>
25 
26 // Qt headers
27 #include <QDateTime>
28 #include <QDir>
29 #include <QMutex>
30 #include <QMutexLocker>
31 #include <QRunnable>
32 #include <QWaitCondition>
33 #include <random>
34 
35 // MythTV plugin headers
36 #include <mythcontext.h>
37 #include <lcddevice.h>
38 #include <mthread.h>
39 #include <mythuihelper.h>
40 
41 // MythGallery headers
42 #include "imageview.h"
43 #include "galleryutil.h"
44 #include "thumbgenerator.h"
45 
46 // Tuning parameter for seasonal weights, between 0 and 1, where lower numbers
47 // give greater weight to seasonal photos. The leading beta shape controls
48 // dates that are approaching and the trailing beta shape controls dates that
49 // just passed. When these are set to 0.175 and 0.31, respectively, about one
50 // quarter of the photos are from the upcoming week in prior years and about one
51 // quarter of the photos are from the preceding month in prior years.
52 const double LEADING_BETA_SHAPE = 0.175;
53 const double TRAILING_BETA_SHAPE = 0.31;
54 // Photos without a timestamp will default to the mode of the beta distribution.
55 const double DEFAULT_WEIGHT = std::pow(0.5, TRAILING_BETA_SHAPE - 1) *
56  std::pow(0.5, LEADING_BETA_SHAPE - 1);
57 // The edges of the distribution get clipped to avoid a singularity.
58 const qint64 BETA_CLIP = 60 * 60 * 24;
59 
60 class ImageView::LoadAlbumRunnable : public QRunnable {
65  QMutex m_isAliveLock;
66  bool m_isAlive;
67 
68 public:
69  LoadAlbumRunnable(ImageView *parent, const ThumbList &roots, int sortorder,
70  int slideshow_sequencing);
71 
72  void abort();
73  void run() override; // QRunnable
74 
76  static void filterDirectories(const ThumbList &input,
77  ThumbList &fileList, ThumbList &dirList);
78 };
79 
81  int *pos, int slideShow, int sortorder)
82  : m_screenSize(640,480),
83  m_wmult(1.0f),
84  m_hmult(1.0f),
85  m_pos(*pos),
86  m_savedPos(pos),
87  m_movieState(0),
88  m_zoom(1.0f),
89 
90  // Info variables
91  m_info_show(false),
92  m_info_show_short(false),
93 
94  // Common slideshow variables
95  m_slideshow_running(false),
96  m_slideshow_sequencing(slideShow),
97  m_slideshow_frame_delay(2),
98  m_slideshow_frame_delay_state(m_slideshow_frame_delay * 1000),
99  m_slideshow_timer(nullptr),
100 
101  // Common effect state variables
102  m_effect_running(false),
103  m_effect_current_frame(0),
104  m_effect_random(false),
105 
106  m_loaderRunnable(nullptr),
107  m_listener(this),
108  m_loaderThread(nullptr),
109  m_slideshow_sequence(ComposeSlideshowSequence(slideShow)),
110  m_finishedLoading(false)
111 {
112 
113  int xbase, ybase, screenwidth, screenheight;
114  GetMythUI()->GetScreenSettings(xbase, screenwidth, m_wmult,
115  ybase, screenheight, m_hmult);
116  m_screenSize = QSize(screenwidth, screenheight);
117 
118  // --------------------------------------------------------------------
119 
120  bool recurse = gCoreContext->GetBoolSetting("GalleryRecursiveSlideshow", false);
121 
122  ThumbItem *origItem = nullptr;
123  if (m_pos < itemList.size())
124  origItem = itemList.at(m_pos);
125 
126  ThumbList fileList, dirList;
127  LoadAlbumRunnable::filterDirectories(itemList, fileList, dirList);
128  AddItems(fileList);
129 
130  if (recurse)
131  {
132  // Load pictures from all directories on a different thread.
133  m_loaderRunnable = new LoadAlbumRunnable(this, dirList, sortorder,
135  m_loaderThread = new MThread("LoadAlbum", m_loaderRunnable);
136  QObject::connect(m_loaderThread->qthread(), SIGNAL(finished()),
137  &m_listener, SLOT(FinishLoading()));
139 
140  // Wait for at least one image to be loaded.
141  {
142  QMutexLocker guard(&m_itemListLock);
143  while (m_itemList.empty() && !m_finishedLoading)
144  {
146  }
147  }
148  }
149 
150  // --------------------------------------------------------------------
151 
152  // since we remove dirs item position might have changed
153  if (origItem)
154  m_pos = m_itemList.indexOf(origItem);
155 
156  m_pos = (!origItem || (m_pos == -1)) ? 0 : m_pos;
158 
159  // --------------------------------------------------------------------
160 
161  m_slideshow_frame_delay = gCoreContext->GetNumSetting("SlideshowDelay", 0);
165 
166  // --------------------------------------------------------------------
167 
168  if (slideShow == 2)
169  {
170  m_slideshow_mode = QT_TR_NOOP("Random Slideshow");
171  }
172  else if (slideShow == 3)
173  {
174  m_slideshow_mode = QT_TR_NOOP("Seasonal Slideshow");
175  }
176  else
177  {
178  m_slideshow_mode = QT_TR_NOOP("Slideshow");
179  }
180 }
181 
183 {
184  UpdateLCD(nullptr);
186  {
188  m_loaderThread->wait();
189  }
190 
192  {
193  delete m_slideshow_sequence;
194  m_slideshow_sequence = nullptr;
195  }
196 
197  if (m_loaderRunnable)
198  {
199  delete m_loaderRunnable;
200  m_loaderRunnable = nullptr;
201  }
202 
203  if (m_loaderThread)
204  {
205  delete m_loaderThread;
206  m_loaderThread = nullptr;
207  }
208 
209  *m_savedPos = m_pos;
210 }
211 
213  switch (slideshow_sequencing)
214  {
215  case 2:
216  return new SequenceShuffle();
217  case 3:
218  return new SequenceWeighted();
219  default:
220  return new SequenceInc();
221  }
222 }
223 
224 QString ImageView::GetRandomEffect(void) const
225 {
226  QMap<QString,QString> tmpMap = m_effect_map;
227  tmpMap.remove("none");
228  tmpMap.remove("Ken Burns (gl)");
229  QStringList t = tmpMap.keys();
230  int i = (int) ( (float)(t.count()) * random() / (RAND_MAX + 1.0f) );
231  return tmpMap[t[i]];
232 }
233 
235 {
236  LCD *lcd = LCD::Get();
237  if (!lcd)
238  return;
239 
240  if (!item)
241  {
242  lcd->setFunctionLEDs(FUNC_PHOTO, false);
243  lcd->switchToTime();
244  return;
245  }
246  lcd->setFunctionLEDs(FUNC_PHOTO, true);
247 
248  QString name = item->GetName();
249  QString desc = QString::number(m_pos + 1) + " / " +
250  QString::number(m_itemList.size());
251 
252  QList<LCDTextItem> textItems;
253  textItems.append(LCDTextItem(
254  1, ALIGN_CENTERED, name, "Generic", true));
255  textItems.append(LCDTextItem(
256  2, ALIGN_CENTERED, desc, "Generic", false));
257 
258  lcd->switchToGeneric(textItems);
259 }
260 
262 {
264  return " [" + tr(m_slideshow_mode) + "]";
265 
266  return "";
267 }
268 
269 void ImageView::GetScreenShot(QImage& image, const ThumbItem *item)
270 {
271  QFileInfo fi(item->GetPath());
272  QString screenshot = QString("%1%2-screenshot.jpg")
273  .arg(ThumbGenerator::getThumbcacheDir(fi.path()))
274  .arg(item->GetName());
275 
276  if (QFile::exists(screenshot))
277  {
278  QImage img(screenshot);
279  image = img;
280  }
281  else
282  {
283  QString movie("gallery-moviethumb.png");
284  if (GetMythUI()->FindThemeFile(movie))
285  image.load(movie);
286  }
287 }
288 
289 void ImageView::AddItems(const ThumbList &itemList)
290 {
291  QMutexLocker guard(&m_itemListLock);
292 
293  m_itemList.append(itemList);
294 
295  m_slideshow_sequence->extend(itemList.size());
296 
297  if (m_slideshow_sequencing == 3)
298  {
299  for (int i = 0; i < itemList.size(); ++i)
300  {
301  ThumbItem *item = itemList.at(i);
302  double weight = GetSeasonalWeight(item);
303  static_cast<SequenceWeighted *>(m_slideshow_sequence)->add(weight);
304  }
305  }
306 
307  if (!m_itemList.empty())
308  {
309  m_imagesLoaded.wakeAll();
310  }
311 }
312 
314 {
315  QMutexLocker guard(&m_itemListLock);
316  return m_itemList.at(m_pos);
317 }
318 
320 {
321  QMutexLocker guard(&m_itemListLock);
323  return m_itemList.at(m_pos);
324 }
325 
327 {
328  QMutexLocker guard(&m_itemListLock);
330  return m_itemList.at(m_pos);
331 }
332 
334 {
335  QMutexLocker guard(&m_itemListLock);
336  m_finishedLoading = true;
337  m_imagesLoaded.wakeAll();
338 }
339 
348  item->InitTimestamp();
349  if (item->HasTimestamp())
350  {
351  QDateTime timestamp = item->GetTimestamp();
352  QDateTime now = QDateTime::currentDateTime();
353  QDateTime curYearAnniversary = QDateTime(QDate(
354  now.date().year(),
355  timestamp.date().month(),
356  timestamp.date().day()),
357  timestamp.time());
358  bool isAnniversaryPast = curYearAnniversary < now;
359  QDateTime adjacentYearAnniversary = QDateTime(QDate(
360  now.date().year() + (isAnniversaryPast ? 1 : -1),
361  timestamp.date().month(),
362  timestamp.date().day()),
363  timestamp.time());
364  double range = std::abs(
365  curYearAnniversary.secsTo(adjacentYearAnniversary)) + BETA_CLIP;
366  // This calculation is not normalized, because that would require the
367  // beta function, which isn't part of the C++98 libraries. Weights
368  // that aren't normalized work just as well relative to each other.
369  double weight = std::pow(abs(now.secsTo(
370  isAnniversaryPast ? curYearAnniversary : adjacentYearAnniversary
371  ) + BETA_CLIP) / range,
372  TRAILING_BETA_SHAPE - 1) *
373  std::pow(abs(now.secsTo(
374  isAnniversaryPast ? adjacentYearAnniversary : curYearAnniversary
375  ) + BETA_CLIP) / range,
376  LEADING_BETA_SHAPE - 1);
377  return weight;
378  }
379  else
380  {
381  return DEFAULT_WEIGHT;
382  }
383 }
384 
386  ImageView *parent, const ThumbList &roots, int sortorder,
387  int slideshow_sequencing)
388  : m_parent(parent),
389  m_dirList(roots),
390  m_sortorder(sortorder),
391  m_slideshow_sequencing(slideshow_sequencing),
392  m_isAlive(true)
393 {
394 }
395 
398 {
399  QMutexLocker guard(&m_isAliveLock);
400  m_isAlive = false;
401 }
402 
404 {
405  while (!m_dirList.empty())
406  {
407  ThumbItem *dir = m_dirList.takeFirst();
408  ThumbList children;
409  GalleryUtil::LoadDirectory(children, dir->GetPath(),
410  GalleryFilter(m_sortorder != 0),
411  false, nullptr, nullptr);
412 
413  {
414  QMutexLocker guard(&m_isAliveLock);
415  if (!m_isAlive)
416  {
417  break;
418  }
419  }
420 
421  // The first images should not always come from the first directory.
422  if (m_slideshow_sequencing > 1)
423  {
424  std::shuffle(children.begin(), children.end(),
425  std::mt19937(std::random_device()()));
426  }
427 
428  ThumbList fileList;
429  filterDirectories(children, fileList, m_dirList);
430  if (!fileList.empty())
431  {
432  m_parent->AddItems(fileList);
433  }
434  }
435 }
436 
439  ThumbList &fileList, ThumbList &dirList)
440 {
441  for (int i = 0; i < input.size(); ++i)
442  {
443  ThumbItem *item = input.at(i);
444  ThumbList &targetList = item->IsDir() ? dirList : fileList;
445  targetList.append(item);
446  }
447 }
448 
450  : m_parent(parent)
451 {
452 }
453 
455 {
457 }
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:295
void GetScreenShot(QImage &image, const ThumbItem *item)
Definition: imageview.cpp:269
int m_pos
Definition: imageview.h:95
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
static SequenceBase * ComposeSlideshowSequence(int slideshow_sequencing)
Definition: imageview.cpp:212
int * m_savedPos
Definition: imageview.h:96
virtual void AddItems(const ThumbList &itemList)
Definition: imageview.cpp:289
float m_hmult
Definition: imageview.h:94
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:91
ImageView(const ThumbList &itemList, int *pos, int slideShow, int sortorder)
Definition: imageview.cpp:80
bool m_finishedLoading
Definition: imageview.h:130
void setFunctionLEDs(enum LCDFunctionSet video, bool on)
Definition: lcddevice.cpp:450
void switchToGeneric(QList< LCDTextItem > &textItems)
Definition: lcddevice.cpp:654
int m_slideshow_frame_delay_state
Definition: imageview.h:107
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:312
LoadAlbumRunnable * m_loaderRunnable
Definition: imageview.h:120
ThumbItem * getCurrentItem() const
Definition: imageview.cpp:313
LoadAlbumListener(ImageView *parent)
Definition: imageview.cpp:449
int m_slideshow_frame_delay
Definition: imageview.h:106
static bool LoadDirectory(ThumbList &itemList, const QString &dir, const GalleryFilter &flt, bool recurse, ThumbHash *itemHash, ThumbGenerator *thumbGen)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QList< ThumbItem * > ThumbList
Definition: thumbview.h:80
size_t prev()
Definition: sequence.h:66
const double TRAILING_BETA_SHAPE
Definition: imageview.cpp:53
void UpdateLCD(const ThumbItem *item)
Definition: imageview.cpp:234
size_t next()
Definition: sequence.h:56
QDateTime GetTimestamp(void) const
Definition: thumbview.h:59
QString GetDescriptionStatus(void) const
Definition: imageview.cpp:261
QMap< QString, QString > m_effect_map
Definition: imageview.h:115
static LCD * Get(void)
Definition: lcddevice.cpp:86
ThumbList m_itemList
Definition: imageview.h:128
ThumbItem * retreatItem()
Definition: imageview.cpp:326
QString GetName(void) const
Definition: thumbview.h:55
bool IsDir(void) const
Definition: thumbview.h:62
virtual QString GetRandomEffect(void) const
Definition: imageview.cpp:224
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
ImageView * m_parent
Definition: imageview.h:44
SequenceBase * m_slideshow_sequence
Definition: imageview.h:129
const qint64 BETA_CLIP
Definition: imageview.cpp:58
const int m_slideshow_sequencing
Definition: imageview.h:105
unsigned char t
Definition: ParseText.cpp:340
static QString getThumbcacheDir(const QString &inDir)
virtual ~ImageView()
Definition: imageview.cpp:182
void FinishLoading()
Definition: imageview.cpp:333
bool HasTimestamp(void) const
Definition: thumbview.h:58
QSize m_screenSize
Definition: imageview.h:92
virtual void extend(size_t items)
Definition: sequence.h:51
const double DEFAULT_WEIGHT
Definition: imageview.cpp:55
const char * name
Definition: ParseText.cpp:339
MythUIHelper * GetMythUI()
void switchToTime()
Definition: lcddevice.cpp:578
float m_wmult
Definition: imageview.h:93
const char * m_slideshow_mode
Definition: imageview.h:109
QString GetPath(void) const
Definition: thumbview.h:61
int GetNumSetting(const QString &key, int defaultval=0)
void FinishLoading() const
Definition: imageview.cpp:454
QWaitCondition m_imagesLoaded
Definition: imageview.h:125
const double LEADING_BETA_SHAPE
Definition: imageview.cpp:52
bool GetBoolSetting(const QString &key, bool defaultval=false)
LoadAlbumListener m_listener
Definition: imageview.h:123
virtual void set(size_t _idx)=0
MThread * m_loaderThread
Definition: imageview.h:124
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:245
Definition: lcddevice.h:165
double GetSeasonalWeight(ThumbItem *item)
This method calculates a weight for the item based on how closely it was taken to the current time of...
Definition: imageview.cpp:347
QMutex m_itemListLock
Definition: imageview.h:127
static long int random(void)
Definition: compat.h:147
static void filterDirectories(const ThumbList &input, ThumbList &fileList, ThumbList &dirList)
Separate the input into files and directories.
Definition: imageview.cpp:438
void abort()
Request that all processing stop.
Definition: imageview.cpp:397
LoadAlbumRunnable(ImageView *parent, const ThumbList &roots, int sortorder, int slideshow_sequencing)
Definition: imageview.cpp:385
ThumbItem * advanceItem()
Definition: imageview.cpp:319
bool m_slideshow_running
Definition: imageview.h:104
void GetScreenSettings(float &wmult, float &hmult)
void InitTimestamp()
Definition: thumbview.cpp:52