MythTV  master
gallerythumbview.cpp
Go to the documentation of this file.
1 #include "gallerythumbview.h"
2 
3 #include <chrono> // for milliseconds
4 #include <thread> // for sleep_for
5 
6 #include <QApplication>
7 #include <utility>
8 
9 #include "compat.h"
10 
11 #include "mythuitext.h"
12 #include "mythprogressdialog.h"
13 #include "mythuiprogressbar.h"
14 #include "remotefile.h"
15 #include "mythsystemlegacy.h"
16 #include "mythdialogbox.h"
17 
18 #include "galleryconfig.h"
19 
20 #define LOC QString("Thumbview: ")
21 
22 
24 class ShellThread: public MThread
25 {
26 public:
27  ShellThread(QString cmd, QString path)
28  : MThread("Import"), m_command(std::move(cmd)), m_path(std::move(path)) {}
29 
30  int GetResult(void) { return m_result; }
31 
32  void run() override // MThread
33  {
34  RunProlog();
35 
36  QString cmd = QString("cd %1 && %2").arg(m_path, m_command);
37  LOG(VB_GENERAL, LOG_INFO, QString("Executing \"%1\"").arg(cmd));
38 
39  m_result = myth_system(cmd);
40 
41  LOG(VB_GENERAL, LOG_INFO, QString(" ...with result %1").arg(m_result));
42 
43  RunEpilog();
44  }
45 
46 private:
47  int m_result {0};
48  QString m_command;
49  QString m_path;
50 };
51 
52 
54 class TransferThread : public MThread
55 {
56  Q_DECLARE_TR_FUNCTIONS(FileTransferWorker);
57 public:
58  using TransferMap = QMap<ImagePtrK, QString>;
59  using ImageSet = QSet<ImagePtrK>;
60 
61  TransferThread(TransferMap files, bool move, MythUIProgressDialog *dialog)
62  : MThread("FileTransfer"),
63  m_move(move), m_files(std::move(files)), m_dialog(dialog) {}
64 
65  ImageSet GetResult(void) { return m_failed; }
66 
67  void run() override // MThread
68  {
69  RunProlog();
70 
71  QString action = m_move ? tr("Moving") : tr("Copying");
72 
73  // Sum file sizes
74  int total = 0;
75  foreach (ImagePtrK im, m_files.keys())
76  total += im->m_size;
77 
78  int progressSize = 0;
79  foreach (ImagePtrK im, m_files.keys())
80  {
81  // Update progress dialog
82  if (m_dialog)
83  {
84  QString message = QString("%1 %2\n%3")
85  .arg(action, QFileInfo(im->m_url).fileName(),
86  ImageAdapterBase::FormatSize(im->m_size / 1024));
87 
88  auto *pue = new ProgressUpdateEvent(progressSize, total, message);
89  QApplication::postEvent(m_dialog, pue);
90  }
91 
92  QString newPath = m_files.value(im);
93  LOG(VB_FILE, LOG_INFO, QString("%2 %3 -> %4")
94  .arg(action, im->m_url, newPath));
95 
96  bool success = m_move ? RemoteFile::MoveFile(im->m_url, newPath)
97  : RemoteFile::CopyFile(im->m_url, newPath,
98  false, true);
99  if (!success)
100  {
101  // Flag failures
102  m_failed.insert(im);
103 
104  LOG(VB_GENERAL, LOG_ERR,
105  QString("%1: Failed to copy/move %2 -> %3")
106  .arg(objectName(), im->m_url, m_files[im]));
107  }
108 
109  progressSize += im->m_size;
110  }
111 
112  // Update progress dialog
113  if (m_dialog)
114  {
115  auto *pue =
116  new ProgressUpdateEvent(progressSize, total, tr("Complete"));
117  QApplication::postEvent(m_dialog, pue);
118  }
119 
120  RunEpilog();
121  }
122 
123 private:
124  bool m_move;
128 };
129 
130 
135 static void WaitUntilDone(MThread &worker)
136 {
137  worker.start();
138  while (!worker.isFinished())
139  {
140  std::this_thread::sleep_for(std::chrono::milliseconds(1));
141  qApp->processEvents();
142  }
143 }
144 
145 
152  : MythScreenType(parent, name),
153  m_popupStack(*GetMythMainWindow()->GetStack("popup stack")),
154  m_mgr(ImageManagerFe::getInstance()),
155  // This screen uses a single fixed view (Parent dir, ordered dirs, ordered images)
156  m_view(new DirectoryView(kOrdered)),
157  m_infoList(*this),
158  // Start in edit mode unless a password exists
159  m_editsAllowed(gCoreContext->GetSetting("GalleryPassword").isEmpty())
160 {
161  // Hide hidden when edits disallowed
162  if (!m_editsAllowed)
163  m_mgr.SetVisibility(false);
164 }
165 
166 
171 {
172  LOG(VB_GUI, LOG_DEBUG, LOC + "Exiting Gallery");
173  delete m_view;
174 }
175 
176 
181 {
182  LOG(VB_GUI, LOG_DEBUG, LOC + "Closing Gallery");
183 
185 
186  // Cleanup local devices
188 
189  // Cleanup view
190  m_view->Clear();
191 
193 }
194 
195 
200 {
201  if (!LoadWindowFromXML("image-ui.xml", "gallery", this))
202  return false;
203 
204  // Determine zoom levels supported by theme
205  // images0 must exist; images1, images2 etc. are optional and enable zoom
206  int zoom = 0;
207  MythUIButtonList *widget = nullptr;
208  do
209  {
210  QString name = QString("images%1").arg(zoom++);
211  widget = dynamic_cast<MythUIButtonList *>(this->GetChild(name));
212  if (widget)
213  {
214  m_zoomWidgets.append(widget);
215  widget->SetVisible(false);
216  }
217  }
218  while (widget);
219 
220  if (m_zoomWidgets.isEmpty())
221  {
222  LOG(VB_GENERAL, LOG_ERR, LOC + "Screen 'Gallery' is missing 'images0'");
223  return false;
224  }
225  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen 'Gallery' found %1 zoom levels")
226  .arg(m_zoomWidgets.size()));
227 
228  // File details list is managed elsewhere
229  if (!m_infoList.Create(false))
230  {
231  LOG(VB_GENERAL, LOG_ERR, LOC + "Cannot load 'Info buttonlist'");
232  return false;
233  }
234 
235  UIUtilW::Assign(this, m_captionText, "caption");
236  UIUtilW::Assign(this, m_emptyText, "noimages");
237  UIUtilW::Assign(this, m_positionText, "position");
238  UIUtilW::Assign(this, m_crumbsText, "breadcrumbs");
239  UIUtilW::Assign(this, m_hideFilterText, "hidefilter");
240  UIUtilW::Assign(this, m_typeFilterText, "typefilter");
241  UIUtilW::Assign(this, m_scanProgressText, "scanprogresstext");
242  UIUtilW::Assign(this, m_scanProgressBar, "scanprogressbar");
243 
244  if (m_scanProgressText)
246  if (m_scanProgressBar)
248 
249  BuildFocusList();
250 
251  // Initialise list widget with appropriate zoom level for this theme.
252  m_zoomLevel = gCoreContext->GetNumSetting("GalleryZoomLevel", 0);
253  SelectZoomWidget(0);
254 
255  return true;
256 }
257 
258 
263 bool GalleryThumbView::keyPressEvent(QKeyEvent *event)
264 {
265  if (GetFocusWidget()->keyPressEvent(event))
266  return true;
267 
268  QStringList actions;
269  bool handled = GetMythMainWindow()->TranslateKeyPress("Images", event, actions);
270 
271  for (int i = 0; i < actions.size() && !handled; i++)
272  {
273  QString action = actions[i];
274  handled = true;
275 
276  if (action == "MENU")
277  MenuMain();
278  else if (action == "INFO")
279  ShowDetails();
280  else if (action == "ZOOMIN")
281  ZoomIn();
282  else if (action == "ZOOMOUT")
283  ZoomOut();
284  else if (action == "ROTRIGHT")
285  RotateCW();
286  else if (action == "ROTLEFT")
287  RotateCCW();
288  else if (action == "FLIPHORIZONTAL")
289  FlipHorizontal();
290  else if (action == "FLIPVERTICAL")
291  FlipVertical();
292  else if (action == "COVER")
293  {
294  ImagePtrK im = m_view->GetSelected();
295  if (m_editsAllowed && im)
296  {
297  if (im == m_view->GetParent())
298  {
299  // Reset dir
300  m_mgr.SetCover(im->m_id, 0);
301  }
302  else
303  {
304  // Set parent cover
305  m_mgr.SetCover(im->m_parentId, im->m_id);
306  }
307  }
308  }
309  else if (action == "PLAY")
310  Slideshow();
311  else if (action == "RECURSIVESHOW")
312  {
313  ImagePtrK im = m_view->GetSelected();
314  if (im && im->IsDirectory())
316  }
317  else if (action == "MARK")
318  {
319  ImagePtrK im = m_view->GetSelected();
320  if (m_editsAllowed && im && im != m_view->GetParent())
321  MarkItem(!m_view->IsMarked(im->m_id));
322  }
323  else if (action == "ESCAPE" && !GetMythMainWindow()->IsExitingToMain())
324  {
325  // Exit info list, if shown
326  handled = m_infoList.Hide();
327 
328  // Ascend the tree unless parent is root,
329  // or a device and multiple devices/imports exist
330  if (!handled)
331  {
332  ImagePtrK node = m_view->GetParent();
333  if (node && node->m_id != GALLERY_DB_ID
334  && (!node->IsDevice() || m_mgr.DeviceCount() > 0))
335  handled = DirSelectUp();
336  }
337  }
338  else
339  handled = false;
340  }
341 
342  if (!handled)
343  handled = MythScreenType::keyPressEvent(event);
344 
345  return handled;
346 }
347 
348 
353 void GalleryThumbView::customEvent(QEvent *event)
354 {
355 
356  if (event->type() == MythEvent::MythEventMessage)
357  {
358  auto *me = dynamic_cast<MythEvent *>(event);
359  if (me == nullptr)
360  return;
361 
362  const QString& mesg = me->Message();
363  QStringList extra = me->ExtraDataList();
364 
365  // Internal messages contain a hostname. Ignore other FE messages
366  QStringList token = mesg.split(' ');
367  if (token.size() >= 2 && token[1] != gCoreContext->GetHostName())
368  return;
369 
370  if (token[0] == "IMAGE_METADATA")
371  {
372  int id = extra[0].toInt();
373  ImagePtrK selected = m_view->GetSelected();
374 
375  if (selected && selected->m_id == id)
376  m_infoList.Display(*selected, extra.mid(1));
377  }
378  else if (token[0] == "THUMB_AVAILABLE")
379  {
380  int id = extra[0].toInt();
381 
382  // Note existance of all thumbs
383  m_thumbExists.insert(id);
384 
385  // Get all buttons waiting for this thumbnail
386  QList<ThumbLocation> affected = m_pendingMap.values(id);
387 
388  // Only concerned with thumbnails we've requested
389  if (affected.isEmpty())
390  return;
391 
392  LOG(VB_GENERAL, LOG_DEBUG, LOC +
393  QString("Rx %1 : %2").arg(token[0], extra.join(",")));
394 
395  // Thumb url was cached when request was sent
396  QString url = m_view->GetCachedThumbUrl(id);
397 
398  // Set thumbnail for each button now it exists
399  foreach(const ThumbLocation &location, affected)
400  {
401  MythUIButtonListItem *button = location.first;
402  int index = location.second;
403 
404  ImagePtrK im = button->GetData().value<ImagePtrK>();
405  if (im)
406  UpdateThumbnail(button, im, url, index);
407  }
408 
409  // Cancel pending request
410  m_pendingMap.remove(id);
411  }
412  else if (token[0] == "IMAGE_DB_CHANGED")
413  {
414  // Expects csv list of deleted ids, csv list of changed ids
415  LOG(VB_GENERAL, LOG_DEBUG, LOC +
416  QString("Rx %1 : %2").arg(token[0], extra.join(",")));
417 
418  if (!extra.isEmpty())
419  {
420  QStringList idDeleted =
421  extra[0].split(",", QString::SkipEmptyParts);
422 
423  RemoveImages(idDeleted);
424  }
425  if (extra.size() >= 2)
426  {
427  QStringList idChanged =
428  extra[1].split(",", QString::SkipEmptyParts);
429  RemoveImages(idChanged, false);
430  }
431 
432  // Refresh display
434  }
435  else if (token[0] == "IMAGE_DEVICE_CHANGED")
436  {
437  // Expects list of url prefixes
438  LOG(VB_GENERAL, LOG_DEBUG, LOC +
439  QString("Rx %1 : %2").arg(token[0], extra.join(",")));
440 
441  // Clear everything. Local devices will be rebuilt
442  m_view->Clear();
443  m_thumbExists.clear();
444 
445  // Remove thumbs & images from image cache using supplied prefixes
446  foreach(const QString &url, extra)
448 
449  // Refresh display
451  }
452  else if (token[0] == "IMAGE_SCAN_STATUS" && extra.size() == 3)
453  {
454  // Expects scanner id, scanned#, total#
455  UpdateScanProgress(extra[0], extra[1].toInt(), extra[2].toInt());
456  }
457  }
458  else if (event->type() == DialogCompletionEvent::kEventType)
459  {
460  auto *dce = (DialogCompletionEvent *)(event);
461 
462  QString resultid = dce->GetId();
463  int buttonnum = dce->GetResult();
464 
465  if (resultid == "FileRename")
466  {
467  QString newName = dce->GetResultText();
469  {
470  QString err = m_mgr.RenameFile(m_menuState.m_selected,
471  newName);
472  if (!err.isEmpty())
473  ShowOkPopup(err);
474  }
475  }
476  else if (resultid == "MakeDir")
477  {
479  {
480  // Prohibit subtrees
481  QString name = dce->GetResultText();
482  QString err = name.contains("/")
483  ? tr("Invalid Name")
485  QStringList(name));
486  if (!err.isEmpty())
487  ShowOkPopup(err);
488  }
489  }
490  else if (resultid == "SlideOrderMenu")
491  {
492  SlideOrderType slideOrder = kOrdered;
493 
494  switch (buttonnum)
495  {
496  case 0: slideOrder = kOrdered; break;
497  case 1: slideOrder = kShuffle; break;
498  case 2: slideOrder = kRandom; break;
499  case 3: slideOrder = kSeasonal; break;
500  }
501  gCoreContext->SaveSetting("GallerySlideOrder", slideOrder);
502  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Order %1").arg(slideOrder));
503  }
504  else if (resultid == "ImageCaptionMenu")
505  {
506  ImageCaptionType captions = kNoCaption;
507 
508  switch (buttonnum)
509  {
510  case 0: captions = kNameCaption; break;
511  case 1: captions = kDateCaption; break;
512  case 2: captions = kUserCaption; break;
513  case 3: captions = kNoCaption; break;
514  }
515  gCoreContext->SaveSetting("GalleryImageCaption", captions);
516  BuildImageList();
517  }
518  else if (resultid == "DirCaptionMenu")
519  {
520  ImageCaptionType captions = kNoCaption;
521 
522  switch (buttonnum)
523  {
524  case 0: captions = kNameCaption; break;
525  case 1: captions = kDateCaption; break;
526  case 2: captions = kNoCaption; break;
527  }
528  gCoreContext->SaveSetting("GalleryDirCaption", captions);
529  BuildImageList();
530  }
531  else if (resultid == "Password")
532  {
533  QString password = dce->GetResultText();
534  m_editsAllowed = (password == gCoreContext->GetSetting("GalleryPassword"));
535  }
536  else if (buttonnum == 1)
537  {
538  // Confirm current file deletion
539  QString err;
540  if (resultid == "ConfirmDelete" && m_menuState.m_selected)
541  {
543  err = m_mgr.DeleteFiles(ids);
544  }
545  // Confirm marked file deletion
546  else if (resultid == "ConfirmDeleteMarked")
547  {
549  }
550  else
551  return;
552 
553  if (!err.isEmpty())
554  ShowOkPopup(err);
555  }
556  }
557 }
558 
559 
565 void GalleryThumbView::RemoveImages(const QStringList &ids, bool deleted)
566 {
567  foreach (const QString &id, ids)
568  {
569  // Remove image from view
570  QStringList urls = m_view->RemoveImage(id.toInt(), deleted);
571  // Cleanup url lookup
572  m_thumbExists.remove(id.toInt());
573 
574  // Remove thumbs & images from image cache
575  foreach(const QString &url, urls)
576  {
577  LOG(VB_FILE, LOG_DEBUG, LOC +
578  QString("Clearing image cache of '%1'").arg(url));
579 
581  }
582  }
583 }
584 
585 
590 {
591  // Detect any running BE scans
592  // Expects OK, scanner id, current#, total#
593  QStringList message = ImageManagerFe::ScanQuery();
594  if (message.size() == 4 && message[0] == "OK")
595  {
596  UpdateScanProgress(message[1], message[2].toInt(), message[3].toInt());
597  }
598 
599  // Only receive events after device/scan status has been established
600  gCoreContext->addListener(this);
601 
602  // Start at Root if devices exist. Otherwise go straight to SG node
604 
605  LoadData(start);
606 }
607 
608 
614 {
616 
617  // Load view for parent directory
618  if (m_view->LoadFromDb(parent))
619  {
620  m_imageList->SetVisible(true);
621  if (m_emptyText)
622  {
623  m_emptyText->SetVisible(false);
624  m_emptyText->Reset();
625  }
626 
627  // Construct the buttonlist
628  BuildImageList();
629  }
630  else
631  {
632  m_infoList.Hide();
633  m_imageList->SetVisible(false);
634  if (m_emptyText)
635  {
636  m_emptyText->SetVisible(true);
637  m_emptyText->SetText(tr("No images found.\n"
638  "Scan storage group using menu,\n"
639  "or insert/mount local media.\n"));
640  }
641  }
642 }
643 
644 
649 {
650  m_imageList->Reset();
651  m_pendingMap.clear();
652 
653  // Get parent & all children
654  ImageListK nodes = m_view->GetAllNodes();
655  ImagePtrK selected = m_view->GetSelected();
656 
657  // go through the entire list and update
658  foreach(const ImagePtrK &im, nodes)
659  if (im)
660  {
661  // Data must be set by constructor: First item is automatically
662  // selected and must have data available for selection event, as
663  // subsequent reselection of same item will always fail.
664  auto *item = new MythUIButtonListItem(m_imageList, "",
665  QVariant::fromValue(im));
666 
667  item->setCheckable(true);
668  item->setChecked(MythUIButtonListItem::NotChecked);
669 
670  // assign and display all information about
671  // the current item, like title and subdirectory count
672  UpdateImageItem(item);
673 
674  // Treat parent differently
675  if (im == nodes[0])
676  {
677  // Only non-root parents can ascend
678  if (im->m_id != GALLERY_DB_ID)
679  item->DisplayState("upfolder", "parenttype");
680  }
681  else if (im == selected)
682  // Reinstate the active button item. Note this would fail for parent
684  }
685 }
686 
687 
693 {
694  ImagePtrK im = item->GetData().value<ImagePtrK >();
695  if (!im)
696  return;
697 
698  // Allow themes to distinguish between roots, folders, pics, videos
699  switch (im->m_type)
700  {
701  case kDevice:
702  case kCloneDir:
703  case kDirectory:
704  if (im->m_dirCount > 0)
705  {
706  item->SetText(QString("%1/%2")
707  .arg(im->m_fileCount).arg(im->m_dirCount),
708  "childcount");
709  }
710  else
711  {
712  item->SetText(QString::number(im->m_fileCount), "childcount");
713  }
714 
715  item->DisplayState(im->IsDevice() ? "device" : "subfolder", "buttontype");
716  break;
717 
718  case kImageFile:
719  item->DisplayState("image", "buttontype");
720  break;
721 
722  case kVideoFile:
723  item->DisplayState("video", "buttontype");
724  break;
725 
726  default:
727  break;
728  }
729 
730  // Allow theme to distinguish visible/hidden nodes
731  QString hideState = (im->m_isHidden) ? "hidden" : "visible";
732  item->DisplayState(hideState, "buttonstate");
733 
734  // Caption
735  QString text;
737  im->IsFile() ? "GalleryImageCaption"
738  : "GalleryDirCaption");
739  switch (show)
740  {
741  case kNameCaption: text = m_mgr.CrumbName(*im); break;
742  case kDateCaption: text = m_mgr.ShortDateOf(im); break;
743  case kUserCaption: text = im->m_comment; break;
744  default:
745  case kNoCaption: text = ""; break;
746  }
747  item->SetText(text);
748 
749  // Set marked state
751  = m_view->IsMarked(im->m_id)
754 
755  item->setChecked(state);
756 
757  // Thumbnails required
758  ImageIdList request;
759 
760  if (im->m_thumbNails.size() == 1)
761  {
762  // Single thumbnail
763  QString url = CheckThumbnail(item, im, request, 0);
764 
765  if (!url.isEmpty())
766  UpdateThumbnail(item, im, url, 0);
767  }
768  else
769  {
770  // Dir showing up to 4 thumbs. Set them all at same time
771  InfoMap thumbMap;
772  for (int index = 0; index < im->m_thumbNails.size(); ++index)
773  {
774  QString url = CheckThumbnail(item, im, request, index);
775  if (!url.isEmpty())
776  thumbMap.insert(QString("thumbimage%1").arg(index), url);
777  }
778  if (!thumbMap.isEmpty())
779  item->SetImageFromMap(thumbMap);
780  }
781 
782  // Request creation/verification of unknown thumbnails.
783  if (!request.isEmpty())
784  m_mgr.CreateThumbnails(request, im->IsDirectory());
785 }
786 
787 
800  ImageIdList &request, int index)
801 {
802  ThumbPair thumb(im->m_thumbNails.at(index));
803  int id = thumb.first;
804 
805  if (m_thumbExists.contains(id))
806  return thumb.second;
807 
808  // Request BE thumbnail check if it is not already pending
809  if (!m_pendingMap.contains(id))
810  request << id;
811 
812  // Note this button is awaiting an update
813  m_pendingMap.insertMulti(id, qMakePair(item, index));
814 
815  return "";
816 }
817 
818 
827  const ImagePtrK& im, const QString &url,
828  int index)
829 {
830  if (im->m_thumbNails.size() == 1)
831  {
832  // Pics, dirs & videos use separate widgets
833  switch (im->m_type)
834  {
835  case kImageFile: button->SetImage(url); break;
836  case kVideoFile: button->SetImage(url, "videoimage"); break;
837  default: button->SetImage(url, "folderimage"); break;
838  }
839  }
840  else
841  // Dir with 4 thumbnails
842  button->SetImage(url, QString("thumbimage%1").arg(index));
843 }
844 
845 
853 void GalleryThumbView::UpdateScanProgress(const QString &scanner,
854  int current, int total)
855 {
856  // Scan update
857  m_scanProgress.insert(scanner, qMakePair(current, total));
858 
859  // Detect end of this scan
860  if (current >= total)
861  {
862  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Finished %1 %2/%3")
863  .arg(scanner).arg(current).arg(total));
864 
865  // Mark inactive scanner
866  m_scanActive.remove(scanner);
867 
868  // Detect end of last scan
869  if (m_scanActive.isEmpty())
870  {
871  if (m_scanProgressText)
872  {
875  }
876  if (m_scanProgressBar)
877  {
880  }
881 
882  m_scanProgress.clear();
883 
884  return;
885  }
886  }
887  else
888  {
889  // Detect first scan update
890  if (m_scanActive.isEmpty())
891  {
892  // Show progressbar when first scan starts
893  if (m_scanProgressBar)
894  {
897  }
898  if (m_scanProgressText)
900  }
901 
902  if (!m_scanActive.contains(scanner))
903  {
904  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Started %1 %2/%3")
905  .arg(scanner).arg(current).arg(total));
906 
907  // Mark active scanner
908  m_scanActive.insert(scanner);
909  }
910  }
911 
912  // Aggregate all running scans
913  int currentAgg = 0;
914  int totalAgg = 0;
915  foreach (IntPair scan, m_scanProgress.values())
916  {
917  currentAgg += scan.first;
918  totalAgg += scan.second;
919  }
920 
921  if (m_scanProgressBar)
922  {
923  m_scanProgressBar->SetUsed(currentAgg);
924  m_scanProgressBar->SetTotal(totalAgg);
925  }
926  if (m_scanProgressText)
927  m_scanProgressText->SetText(tr("%L1 of %L3").arg(currentAgg).arg(totalAgg));
928 }
929 
930 
935 {
936  if (m_positionText)
938 
939  if (m_captionText)
940  m_captionText->Reset();
941 
942  if (m_crumbsText)
943  m_crumbsText->Reset();
944 
945  if (m_hideFilterText)
947 
948  if (m_typeFilterText)
950 }
951 
952 
958 {
959  ImagePtrK im = item->GetData().value<ImagePtrK >();
960  if (im)
961  {
962  // update the position in the node list
963  m_view->Select(im->m_id);
964 
965  // show the name/path of the image
966  if (m_crumbsText)
967  m_crumbsText->SetText(m_mgr.CrumbName(*im, true));
968 
969  if (m_captionText)
970  {
971  // show the date & comment of non-root nodes
972  QStringList text;
973  if (im->m_id != GALLERY_DB_ID)
974  {
975  if (im->IsFile() || im->IsDevice())
976  text << ImageManagerFe::LongDateOf(im);
977 
978  if (!im->m_comment.isEmpty())
979  text << im->m_comment;
980  }
981  m_captionText->SetText(text.join(" - "));
982  }
983 
984  if (m_hideFilterText)
985  {
986  m_hideFilterText->SetText(m_mgr.GetVisibility() ? tr("Hidden") : "");
987  }
988 
989  if (m_typeFilterText)
990  {
991  QString text = "";
992  switch (m_mgr.GetType())
993  {
994  case kPicAndVideo : text = ""; break;
995  case kPicOnly : text = tr("Pictures"); break;
996  case kVideoOnly : text = tr("Videos"); break;
997  }
998  m_typeFilterText->SetText(text);
999  }
1000 
1001  // show the position of the image
1002  if (m_positionText)
1004 
1005  // Update any file details information
1006  m_infoList.Update(im);
1007  }
1008 }
1009 
1010 
1015 {
1016  // Create the main menu
1017  auto *menu = new MythMenu(tr("Gallery Options"), this, "mainmenu");
1018 
1019  // Menu options depend on the marked files and the current node
1021 
1022  if (m_menuState.m_selected)
1023  {
1024  if (m_editsAllowed)
1025  {
1026  MenuMarked(menu);
1027  MenuPaste(menu);
1029  MenuAction(menu);
1030  }
1032  MenuShow(menu);
1033  if (!m_editsAllowed)
1034  menu->AddItem(tr("Enable Edits"), SLOT(ShowPassword()));
1035  }
1036 
1037  // Depends on current status of backend scanner - string(number(isBackend()))
1038  if (m_scanActive.contains("1"))
1039  menu->AddItem(tr("Stop Scan"), SLOT(StopScan()));
1040  else
1041  menu->AddItem(tr("Scan Storage Group"), SLOT(StartScan()));
1042 
1043  menu->AddItem(tr("Settings"), SLOT(ShowSettings()));
1044 
1045  auto *popup = new MythDialogBox(menu, &m_popupStack, "menuPopup");
1046  if (popup->Create())
1047  m_popupStack.AddScreen(popup);
1048  else
1049  delete popup;
1050 }
1051 
1052 
1058 {
1059  ImagePtrK parent = m_view->GetParent();
1060 
1061  if (m_menuState.m_childCount == 0 || parent.isNull())
1062  return;
1063 
1064  QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1065  auto *menu = new MythMenu(title, this, "markmenu");
1066 
1067  // Mark/unmark selected
1068  if (m_menuState.m_selected->IsFile())
1069  {
1071  menu->AddItem(tr("Unmark File"), SLOT(UnmarkItem()));
1072  else
1073  menu->AddItem(tr("Mark File"), SLOT(MarkItem()));
1074  }
1075  // Cannot mark/unmark parent dir from this level
1076  else if (!m_menuState.m_selected->IsDevice()
1077  && m_menuState.m_selected != parent)
1078  {
1080  menu->AddItem(tr("Unmark Directory"), SLOT(UnmarkItem()));
1081  else
1082  menu->AddItem(tr("Mark Directory"), SLOT(MarkItem()));
1083  }
1084 
1085  if (parent->m_id != GALLERY_DB_ID)
1086  {
1087  // Mark All if unmarked files exist
1089  menu->AddItem(tr("Mark All"), SLOT(MarkAll()));
1090 
1091  // Unmark All if marked files exist
1092  if (!m_menuState.m_markedId.isEmpty())
1093  {
1094  menu->AddItem(tr("Unmark All"), SLOT(UnmarkAll()));
1095  menu->AddItem(tr("Invert Marked"), SLOT(MarkInvertAll()));
1096  }
1097  }
1098 
1099  if (menu->IsEmpty())
1100  delete menu;
1101  else
1102  mainMenu->AddItem(tr("Mark"), nullptr, menu);
1103 }
1104 
1105 
1111 {
1112  // Can only copy/move into non-root dirs
1113  if (m_menuState.m_selected->IsDirectory()
1114  && m_menuState.m_selected->m_id != GALLERY_DB_ID)
1115  {
1116  // Operate on current marked files, if any
1118  if (files.isEmpty())
1119  files = m_menuState.m_prevMarkedId;
1120  if (files.isEmpty())
1121  return;
1122 
1123  QString title = tr("%L1 marked").arg(files.size());
1124 
1125  auto *menu = new MythMenu(title, this, "pastemenu");
1126 
1127  menu->AddItem(tr("Move Marked Into"), SLOT(Move()));
1128  menu->AddItem(tr("Copy Marked Into"), SLOT(Copy()));
1129 
1130  mainMenu->AddItem(tr("Paste"), nullptr, menu);
1131  }
1132 }
1133 
1134 
1140 {
1141  // Operate on marked files, if any, otherwise selected node
1142  if (!m_menuState.m_markedId.isEmpty())
1143  {
1144  QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1145 
1146  auto *menu = new MythMenu(title, this, "");
1147 
1148  menu->AddItem(tr("Rotate Marked CW"), SLOT(RotateCWMarked()));
1149  menu->AddItem(tr("Rotate Marked CCW"), SLOT(RotateCCWMarked()));
1150  menu->AddItem(tr("Flip Marked Horizontal"), SLOT(FlipHorizontalMarked()));
1151  menu->AddItem(tr("Flip Marked Vertical"), SLOT(FlipVerticalMarked()));
1152  menu->AddItem(tr("Reset Marked to Exif"), SLOT(ResetExifMarked()));
1153 
1154  mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1155  }
1156  else if (m_menuState.m_selected->IsFile())
1157  {
1158  auto *menu = new MythMenu(m_menuState.m_selected->m_baseName, this, "");
1159 
1160  menu->AddItem(tr("Rotate CW"), SLOT(RotateCW()));
1161  menu->AddItem(tr("Rotate CCW"), SLOT(RotateCCW()));
1162  menu->AddItem(tr("Flip Horizontal"), SLOT(FlipHorizontal()));
1163  menu->AddItem(tr("Flip Vertical"), SLOT(FlipVertical()));
1164  menu->AddItem(tr("Reset to Exif"), SLOT(ResetExif()));
1165 
1166  mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1167  }
1168 }
1169 
1170 
1176 {
1177  MythMenu *menu = nullptr;
1178  ImagePtrK selected = m_menuState.m_selected;
1179 
1180  // Operate on current marked files, if any
1181  if (!m_menuState.m_markedId.empty())
1182  {
1183  QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1184 
1185  menu = new MythMenu(title, this, "actionmenu");
1186 
1187  // Only offer Hide/Unhide if relevant
1189  menu->AddItem(tr("Hide Marked"), SLOT(HideMarked()));
1191  menu->AddItem(tr("Unhide Marked"), SLOT(UnhideMarked()));
1192 
1193  menu->AddItem(tr("Delete Marked"), SLOT(DeleteMarked()));
1194  }
1195  else
1196  {
1197  // Operate on selected file/dir
1198  menu = new MythMenu(selected->m_baseName, this, "actionmenu");
1199 
1200  // Prohibit actions on devices and parent dirs
1201  if (!selected->IsDevice() && selected != m_view->GetParent())
1202  {
1203  if (selected->m_isHidden)
1204  menu->AddItem(tr("Unhide"), SLOT(Unhide()));
1205  else
1206  menu->AddItem(tr("Hide"), SLOT(HideItem()));
1207 
1208  menu->AddItem(tr("Use as Cover"), SLOT(SetCover()));
1209  menu->AddItem(tr("Delete"), SLOT(DeleteItem()));
1210  menu->AddItem(tr("Rename"), SLOT(ShowRenameInput()));
1211  }
1212  else if (selected->m_userThumbnail)
1213  menu->AddItem(tr("Reset Cover"), SLOT(ResetCover()));
1214  }
1215 
1216  // Can only mkdir in a non-root dir
1217  if (selected->IsDirectory()
1218  && selected->m_id != GALLERY_DB_ID)
1219  menu->AddItem(tr("Create Directory"), SLOT(MakeDir()));
1220 
1221  // Only show import command on root, when defined
1222  if (selected->m_id == GALLERY_DB_ID
1223  && !gCoreContext->GetSetting("GalleryImportCmd").isEmpty())
1224  menu->AddItem(tr("Import"), SLOT(Import()));
1225 
1226  // Only show eject when devices (excluding import) exist
1227  if (selected->IsDevice() && selected->IsLocal())
1228  menu->AddItem(tr("Eject media"), SLOT(Eject()));
1229 
1230  if (menu->IsEmpty())
1231  delete menu;
1232  else
1233  mainMenu->AddItem(tr("Actions"), nullptr, menu);
1234 }
1235 
1236 
1242 {
1243  int order = gCoreContext->GetNumSetting("GallerySlideOrder", kOrdered);
1244 
1245  QString ordering;
1246  switch (order)
1247  {
1248  case kShuffle : ordering = tr("Shuffled"); break;
1249  case kRandom : ordering = tr("Random"); break;
1250  case kSeasonal : ordering = tr("Seasonal"); break;
1251  default:
1252  case kOrdered : ordering = tr("Ordered"); break;
1253  }
1254 
1255  auto *menu = new MythMenu(tr("Slideshow") + " (" + ordering + ")",
1256  this, "SlideshowMenu");
1257 
1258  // Use selected dir or parent, if image selected
1259  if (m_menuState.m_selected->IsDirectory())
1260  {
1261  if (m_menuState.m_selected->m_fileCount > 0)
1262  menu->AddItem(tr("Directory"), SLOT(Slideshow()));
1263 
1264  if (m_menuState.m_selected->m_dirCount > 0)
1265  menu->AddItem(tr("Recursive"), SLOT(RecursiveSlideshow()));
1266  }
1267  else
1268  menu->AddItem(tr("Current Directory"), SLOT(Slideshow()));
1269 
1270  auto *orderMenu = new MythMenu(tr("Slideshow Order"), this, "SlideOrderMenu");
1271 
1272  orderMenu->AddItem(tr("Ordered"), nullptr, nullptr, order == kOrdered);
1273  orderMenu->AddItem(tr("Shuffled"), nullptr, nullptr, order == kShuffle);
1274  orderMenu->AddItem(tr("Random"), nullptr, nullptr, order == kRandom);
1275  orderMenu->AddItem(tr("Seasonal"), nullptr, nullptr, order == kSeasonal);
1276 
1277  menu->AddItem(tr("Change Order"), nullptr, orderMenu);
1278 
1279  if (gCoreContext->GetBoolSetting("GalleryRepeat", false))
1280  menu->AddItem(tr("Turn Repeat Off"), SLOT(RepeatOff()));
1281  else
1282  menu->AddItem(tr("Turn Repeat On"), SLOT(RepeatOn()));
1283 
1284  mainMenu->AddItem(tr("Slideshow"), nullptr, menu);
1285 }
1286 
1287 
1293 {
1294  auto *menu = new MythMenu(tr("Show Options"), this, "showmenu");
1295 
1296  int type = m_mgr.GetType();
1297  if (type == kPicAndVideo)
1298  {
1299  menu->AddItem(tr("Hide Pictures"), SLOT(HidePictures()));
1300  menu->AddItem(tr("Hide Videos"), SLOT(HideVideos()));
1301  }
1302  else
1303  menu->AddItem(type == kPicOnly ? tr("Show Videos") : tr("Show Pictures"),
1304  SLOT(ShowType()));
1305 
1306  int show = gCoreContext->GetNumSetting("GalleryImageCaption");
1307  auto *captionMenu = new MythMenu(tr("Image Captions"), this,
1308  "ImageCaptionMenu");
1309 
1310  captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1311  captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1312  captionMenu->AddItem(tr("Comment"), nullptr, nullptr, show == kUserCaption);
1313  captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1314 
1315  menu->AddItem(tr("Image Captions"), nullptr, captionMenu);
1316 
1317  show = gCoreContext->GetNumSetting("GalleryDirCaption");
1318  captionMenu = new MythMenu(tr("Directory Captions"), this, "DirCaptionMenu");
1319 
1320  captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1321  captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1322  captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1323 
1324  menu->AddItem(tr("Directory Captions"), nullptr, captionMenu);
1325 
1326  if (m_editsAllowed)
1327  {
1328  if (m_mgr.GetVisibility())
1329  menu->AddItem(tr("Hide Hidden Items"), SLOT(HideHidden()));
1330  else
1331  menu->AddItem(tr("Show Hidden Items"), SLOT(ShowHidden()));
1332  }
1333 
1334  if (m_zoomLevel > 0)
1335  menu->AddItem(tr("Zoom Out"), SLOT(ZoomOut()));
1336  if (m_zoomLevel < m_zoomWidgets.size() - 1)
1337  menu->AddItem(tr("Zoom In"), SLOT(ZoomIn()));
1338 
1339  QString details = m_infoList.GetState() == kNoInfo
1340  ? tr("Show Details") : tr("Hide Details");
1341 
1342  menu->AddItem(details, SLOT(ShowDetails()));
1343 
1344  mainMenu->AddItem(tr("Show"), nullptr, menu);
1345 }
1346 
1347 
1353 {
1354  // Only update selection if image is currently displayed
1355  if (m_view->Select(id, -1))
1356  BuildImageList();
1357 }
1358 
1359 
1365 {
1366  if (!item)
1367  return;
1368 
1369  ImagePtrK im = item->GetData().value<ImagePtrK>();
1370  if (!im)
1371  return;
1372 
1373  switch (im->m_type)
1374  {
1375  case kDevice:
1376  case kCloneDir:
1377  case kDirectory:
1378  if (im == m_view->GetParent())
1379  DirSelectUp();
1380  else
1381  DirSelectDown();
1382  break;
1383 
1384  case kImageFile:
1385  case kVideoFile:
1386  StartSlideshow(kBrowseSlides); break;
1387  };
1388 }
1389 
1390 
1396 {
1397  QString err = m_mgr.ScanImagesAction(start);
1398  if (!err.isEmpty())
1399  ShowOkPopup(err);
1400 }
1401 
1402 
1408 {
1409  ImagePtrK selected = m_view->GetSelected();
1410  if (!selected)
1411  return;
1412 
1414  auto *slide = new GallerySlideView(mainStack, "galleryslideview",
1415  m_editsAllowed);
1416  if (slide->Create())
1417  {
1418  mainStack->AddScreen(slide);
1419 
1420  // Update selected item when slideshow exits
1421  connect(slide, SIGNAL(ImageSelected(int)),
1422  this, SLOT(SelectImage(int)));
1423 
1424  if (selected->IsDirectory())
1425  {
1426  // Show selected dir
1427  slide->Start(mode, selected->m_id);
1428  }
1429  else
1430  {
1431  // Show current dir starting at selection
1432  slide->Start(mode, selected->m_parentId, selected->m_id);
1433  }
1434  }
1435  else
1436  delete slide;
1437 }
1438 
1439 
1444 {
1445  ImagePtrK im = m_view->GetParent();
1446  if (im)
1447  {
1448  LOG(VB_GUI, LOG_DEBUG, LOC +
1449  QString("Going up from %1").arg(im->m_filePath));
1450 
1451  // Select the upfolder in the higher dir
1452  m_view->Select(im->m_id);
1453 
1454  // Create tree rooted at parent of the kUpFolder directory node
1455  LoadData(im->m_parentId);
1456  }
1457  return true;
1458 }
1459 
1460 
1465 {
1466  ImagePtrK im = m_view->GetSelected();
1467  if (im)
1468  {
1469  LOG(VB_GUI, LOG_DEBUG, LOC +
1470  QString("Going down to %1").arg(im->m_filePath));
1471 
1472  // Create tree rooted at selected item
1473  LoadData(im->m_id);
1474  }
1475 }
1476 
1477 
1483 {
1484  ImagePtrK im = m_view->GetSelected();
1485  if (im)
1486  {
1487  // Mark/unmark selected item
1488  m_view->Mark(im->m_id, mark);
1489 
1490  // Redisplay buttonlist as a parent dir may have been unmarked
1491  BuildImageList();
1492  }
1493 }
1494 
1495 
1501 {
1502  if (mark)
1503  m_view->MarkAll();
1504  else
1505  m_view->ClearMarked();
1506 
1507  // Redisplay buttonlist
1508  BuildImageList();
1509 }
1510 
1511 
1516 {
1517  m_view->InvertMarked();
1518 
1519  // Redisplay buttonlist
1520  BuildImageList();
1521 }
1522 
1523 
1529 {
1530  ImagePtrK im = m_view->GetSelected();
1531  if (im && m_editsAllowed)
1532  {
1533  ImageIdList ids;
1534  ids.append(im->m_id);
1535  QString err = m_mgr.ChangeOrientation(transform, ids);
1536  if (!err.isEmpty())
1537  ShowOkPopup(err);
1538  }
1539 }
1540 
1541 
1547 {
1548  QString err = m_mgr.ChangeOrientation(transform, m_menuState.m_markedId);
1549  if (!err.isEmpty())
1550  ShowOkPopup(err);
1551 }
1552 
1553 
1559 {
1560  if (m_menuState.m_selected)
1561  {
1562  ImageIdList ids;
1563  ids.append(m_menuState.m_selected->m_id);
1564 
1565  QString err = m_mgr.HideFiles(hide, ids);
1566  if (!err.isEmpty())
1567  {
1568  ShowOkPopup(err);
1569  }
1570  else if (hide && !m_mgr.GetVisibility())
1571  {
1572  // Unmark invisible file
1573  m_view->Mark(m_menuState.m_selected->m_id, false);
1574  }
1575  }
1576 }
1577 
1578 
1584 {
1585  QString err = m_mgr.HideFiles(hide, m_menuState.m_markedId);
1586  if (!err.isEmpty())
1587  {
1588  ShowOkPopup(err);
1589  }
1590  else if (hide && !m_mgr.GetVisibility())
1591  {
1592  // Unmark invisible files
1593  foreach (int id, m_menuState.m_markedId)
1594  m_view->Mark(id, false);
1595  }
1596 }
1597 
1598 
1603 {
1604  if (m_menuState.m_selected)
1605  ShowDialog(tr("Do you want to delete\n%1 ?")
1606  .arg(m_menuState.m_selected->m_baseName), "ConfirmDelete");
1607 }
1608 
1609 
1614 {
1615  ShowDialog(tr("Do you want to delete all marked files ?"),
1616  "ConfirmDeleteMarked");
1617 }
1618 
1619 
1624 {
1625  // Show settings dialog
1626  auto *config = new GallerySettings(m_editsAllowed);
1628  auto *ssd = new StandardSettingDialog(mainStack, "gallerysettings", config);
1629  if (!ssd->Create())
1630  {
1631  delete ssd;
1632  return;
1633  }
1634 
1635  mainStack->AddScreen(ssd);
1636 
1637  // Effect setting changes when dialog saves on exit
1638 
1639  connect(config, &GallerySettings::ClearDbPressed,
1641 
1642  connect(config, &GallerySettings::OrderChanged,
1643  this, [this]()
1644  {
1645  // Update db view, reset cover cache & reload
1646  int sortIm = gCoreContext->GetNumSetting("GalleryImageOrder");
1647  int sortDir = gCoreContext->GetNumSetting("GalleryDirOrder");
1648  m_mgr.SetSortOrder(sortIm, sortDir);
1649  m_view->ClearCache();
1651  });
1652 
1653  connect(config, &GallerySettings::DateChanged,
1654  this, [this]()
1655  {
1656  QString date = gCoreContext->GetSetting("GalleryDateFormat");
1657  m_mgr.SetDateFormat(date);
1658  BuildImageList();
1659  });
1660 
1661  connect(config, &GallerySettings::ExclusionsChanged,
1662  this, [this]()
1663  {
1664  // Request rescan
1665  QString exclusions = gCoreContext->GetSetting("GalleryIgnoreFilter");
1666  m_view->ClearCache();
1667  ImageManagerFe::IgnoreDirs(exclusions);
1668  });
1669 }
1670 
1671 
1677 {
1678  gCoreContext->SaveBoolSetting("GalleryShowHidden", show);
1679 
1680  // Update Db(s)
1682 
1683  // Reset dir thumbnail cache
1684  m_view->ClearCache();;
1685 
1687 }
1688 
1689 
1695 void GalleryThumbView::ShowDialog(const QString& msg, const QString& event)
1696 {
1697  auto *popup = new MythConfirmationDialog(&m_popupStack, msg, true);
1698 
1699  if (popup->Create())
1700  {
1701  popup->SetReturnEvent(this, event);
1702  m_popupStack.AddScreen(popup);
1703  }
1704  else
1705  delete popup;
1706 }
1707 
1708 
1713 {
1714  if (m_menuState.m_selected)
1715  {
1716  QString base = QFileInfo(m_menuState.m_selected->m_baseName).completeBaseName();
1717  QString msg = tr("Enter a new name:");
1718  auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone,
1719  false, base);
1720  if (popup->Create())
1721  {
1722  popup->SetReturnEvent(this, "FileRename");
1723  m_popupStack.AddScreen(popup);
1724  }
1725  else
1726  delete popup;
1727  }
1728 }
1729 
1730 
1735 {
1737 }
1738 
1739 
1744 {
1745  QString msg = tr("Enter password:");
1746  auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone, true);
1747  if (popup->Create())
1748  {
1749  popup->SetReturnEvent(this, "Password");
1750  m_popupStack.AddScreen(popup);
1751  }
1752  else
1753  delete popup;
1754 }
1755 
1756 
1761 {
1762  gCoreContext->SaveSetting("GalleryShowType", type);
1763 
1764  // Update Db(s)
1765  m_mgr.SetType(type);
1766 
1767  // Reset dir thumbnail cache
1768  m_view->ClearCache();
1769 
1771 }
1772 
1773 
1779 {
1780  if (m_menuState.m_selected)
1781  {
1782  QString err = reset ? m_mgr.SetCover(m_menuState.m_selected->m_id, 0)
1783  : m_mgr.SetCover(m_menuState.m_selected->m_parentId,
1784  m_menuState.m_selected->m_id);
1785  if (!err.isEmpty())
1786  ShowOkPopup(err);
1787  }
1788 }
1789 
1790 
1795 {
1796  SelectZoomWidget(-1);
1797  BuildImageList();
1798 }
1799 
1800 
1805 {
1806  SelectZoomWidget(1);
1807  BuildImageList();
1808 }
1809 
1810 
1816 {
1817  m_zoomLevel += change;
1818 
1819  // constrain to zoom levels supported by theme
1820  if (m_zoomLevel < 0)
1821  m_zoomLevel = 0;
1822  if (m_zoomLevel >= m_zoomWidgets.size())
1823  m_zoomLevel = m_zoomWidgets.size() - 1;
1824 
1825  // Store any requested change, but not constraining adjustments
1826  // Thus, changing to a theme with fewer zoom levels will not overwrite the
1827  // setting
1828  if (change != 0)
1829  gCoreContext->SaveSetting("GalleryZoomLevel", m_zoomLevel);
1830 
1831  // dump the current list widget
1832  if (m_imageList)
1833  {
1834  m_imageList->SetVisible(false);
1835  disconnect(m_imageList, nullptr, this, nullptr);
1836  }
1837 
1838  // initialise new list widget
1840 
1841  m_imageList->SetVisible(true);
1843 
1844  // Monitor list actions (after focus events have been ignored)
1845  connect(m_imageList, SIGNAL(itemClicked(MythUIButtonListItem *)),
1847  connect(m_imageList, SIGNAL(itemSelected(MythUIButtonListItem *)),
1849 }
1850 
1851 
1856 {
1857  auto *popup = new MythTextInputDialog(&m_popupStack,
1858  tr("Enter name of new directory"),
1859  FilterNone, false);
1860  if (popup->Create())
1861  {
1862  popup->SetReturnEvent(this, "MakeDir");
1863  m_popupStack.AddScreen(popup);
1864  }
1865  else
1866  delete popup;
1867 }
1868 
1869 
1874 {
1876  if (dir)
1877  m_mgr.CloseDevices(dir->m_device, true);
1878 }
1879 
1880 
1890 void GalleryThumbView::Copy(bool deleteAfter)
1891 {
1892  // Destination must be a dir
1893  ImagePtrK destDir = m_menuState.m_selected;
1894  if (!destDir || destDir->IsFile())
1895  return;
1896 
1897  // Use current markings, if any. Otherwise use previous markings
1898  ImageIdList markedIds = m_menuState.m_markedId;
1899  if (markedIds.isEmpty())
1900  {
1901  markedIds = m_menuState.m_prevMarkedId;
1902  if (markedIds.isEmpty())
1903  {
1904  ShowOkPopup(tr("No files specified"));
1905  return;
1906  }
1907  }
1908 
1909  // Get all files/dirs in subtree(s). Only files are copied
1910  ImageList files;
1911  ImageList dirs;
1912  m_mgr.GetDescendants(markedIds, files, dirs);
1913 
1914  if (dirs.isEmpty() && files.isEmpty())
1915  {
1916  ShowOkPopup(tr("No images"));
1917  // Nothing to clean up
1918  return;
1919  }
1920 
1921  // Child dirs appear before their subdirs. If no dirs, images are all direct children
1922  ImagePtrK aChild = dirs.isEmpty() ? files[0] : dirs[0];
1923 
1924  // Determine parent path including trailing /
1925  int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
1926 
1927  // Update filepaths for Db & generate URLs for filesystem copy
1928  // Only copy files, destination dirs will be created automatically
1929  TransferThread::TransferMap transfers;
1930  foreach(ImagePtr im, files)
1931  {
1932  // Replace base path with destination path
1933  im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath,
1934  im->m_filePath.mid(basePathSize));
1935 
1936  transfers.insert(im, m_mgr.BuildTransferUrl(im->m_filePath,
1937  destDir->IsLocal()));
1938  }
1939 
1940  // Create progress dialog
1941  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1942  auto *progress = new MythUIProgressDialog(tr("Copying files"), popupStack,
1943  "copydialog");
1944  if (progress->Create())
1945  popupStack->AddScreen(progress, false);
1946  else
1947  {
1948  delete progress;
1949  progress = nullptr;
1950  }
1951 
1952  // Copy files in a servant thread
1953  TransferThread copy(transfers, false, progress);
1955  TransferThread::ImageSet failed = copy.GetResult();
1956 
1957  if (progress)
1958  progress->Close();
1959 
1960  if (!failed.isEmpty())
1961  ShowOkPopup(tr("Failed to copy %L1/%Ln file(s)", nullptr, transfers.size())
1962  .arg(failed.size()));
1963 
1964  // Don't update Db for files that failed
1965  foreach (ImagePtrK im, failed)
1966  transfers.remove(im);
1967 
1968  ImageListK newImages = transfers.keys();
1969 
1970  // Include dirs
1971  QStringList dirPaths;
1972  foreach(ImagePtr im, dirs)
1973  {
1974  QString relPath = im->m_filePath.mid(basePathSize);
1975 
1976  dirPaths << relPath;
1977 
1978  // Replace base path with destination path
1979  im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath, relPath);
1980 
1981  // Append dirs so that hidden state & cover is preserved for new dirs
1982  // Pre-existing dirs will take precedance over these.
1983  newImages.append(im);
1984  }
1985 
1986  // Copy empty dirs as well (will fail for non-empty dirs)
1987  if (!dirPaths.isEmpty())
1988  m_mgr.MakeDir(destDir->m_id, dirPaths, false);
1989 
1990  if (!newImages.isEmpty())
1991  {
1992  // Update Db
1993  m_mgr.CreateImages(destDir->m_id, newImages);
1994 
1995  if (deleteAfter)
1996  {
1997  // Delete files/dirs that have been successfully copied
1998  // Will fail for dirs containing images that failed to copy
1999  ImageIdList ids;
2000  foreach (ImagePtrK im, newImages)
2001  ids << im->m_id;
2002 
2003  m_mgr.DeleteFiles(ids);
2004  }
2005  }
2006 }
2007 
2008 
2019 {
2020  // Destination must be a dir
2021  ImagePtrK destDir = m_menuState.m_selected;
2022  if (!destDir || destDir->IsFile())
2023  return;
2024 
2025  // Use current markings, if any. Otherwise use previous markings
2026  ImageIdList markedIds = m_menuState.m_markedId;
2027  if (markedIds.isEmpty())
2028  {
2029  markedIds = m_menuState.m_prevMarkedId;
2030  if (markedIds.isEmpty())
2031  {
2032  ShowOkPopup(tr("No files specified"));
2033  return;
2034  }
2035  }
2036 
2037  // Note UI mandates that transferees are either all local or all remote
2038  if (destDir->IsLocal() != ImageItem::IsLocalId(markedIds[0]))
2039  {
2040  // Moves between hosts require copy/delete
2041  Copy(true);
2042  return;
2043  }
2044 
2045  // Get marked images. Each file and dir will be renamed
2046  ImageList files;
2047  ImageList dirs;
2048  if (m_mgr.GetImages(markedIds, files, dirs) <= 0)
2049  {
2050  ShowOkPopup(tr("No images specified"));
2051  // Nothing to clean up
2052  return;
2053  }
2054  ImageList images = dirs + files;
2055 
2056  // Determine parent from first dir or pic
2057  ImagePtr aChild = images[0];
2058 
2059  // Determine parent path including trailing /
2060  // Note UI mandates that transferees all have same parent.
2061  int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
2062  QString parentPath = aChild->m_filePath.left(basePathSize);
2063 
2064  // Determine destination URLs
2065  TransferThread::TransferMap transfers;
2066  foreach(ImagePtrK im, images)
2067  {
2068  // Replace base path with destination path
2069  QString newPath = ImageManagerFe::ConstructPath(destDir->m_filePath,
2070  im->m_filePath.mid(basePathSize));
2071 
2072  transfers.insert(im, m_mgr.BuildTransferUrl(newPath, aChild->IsLocal()));
2073  }
2074 
2075  // Create progress dialog
2076  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2077  auto *progress = new MythUIProgressDialog(tr("Moving files"), popupStack,
2078  "movedialog");
2079 
2080  if (progress->Create())
2081  popupStack->AddScreen(progress, false);
2082  else
2083  {
2084  delete progress;
2085  progress = nullptr;
2086  }
2087 
2088  // Move files in a servant thread
2089  TransferThread move(transfers, true, progress);
2090  WaitUntilDone(move);
2091  TransferThread::ImageSet failed = move.GetResult();
2092 
2093  if (progress)
2094  progress->Close();
2095 
2096  if (!failed.isEmpty())
2097  ShowOkPopup(tr("Failed to move %L1/%Ln file(s)", nullptr, transfers.size())
2098  .arg(failed.size()));
2099 
2100  // Don't update Db for files that failed
2101  foreach (ImagePtrK im, failed)
2102  transfers.remove(im);
2103 
2104  if (!transfers.isEmpty())
2105  {
2106  ImageListK moved = transfers.keys();
2107 
2108  // Unmark moved files
2109  foreach (ImagePtrK im, moved)
2110  m_view->Mark(im->m_id, false);
2111 
2112  // Update Db
2113  m_mgr.MoveDbImages(destDir, moved, parentPath);
2114  }
2115 }
2116 
2117 
2122 {
2123  QString path = m_mgr.CreateImport();
2124  if (path.isEmpty())
2125  {
2126  ShowOkPopup(tr("Failed to create temporary directory."));
2127  return;
2128  }
2129 
2130  // Replace placeholder in command
2131  QString cmd = gCoreContext->GetSetting("GalleryImportCmd");
2132  cmd.replace("%TMPDIR%", path);
2133 
2134  // Run command in a separate thread
2135  MythUIBusyDialog *busy =
2136  ShowBusyPopup(tr("Running Import command.\nPlease wait..."));
2137 
2138  ShellThread thread(cmd, path);
2139  WaitUntilDone(thread);
2140 
2141  if (busy)
2142  busy->Close();
2143 
2144  int error = thread.GetResult();
2145  if (error != 0)
2146  ShowOkPopup(tr("Import command failed.\nError: %1").arg(error));
2147 
2148  // Rescan local devices
2149  QString err = m_mgr.ScanImagesAction(true, true);
2150  if (!err.isEmpty())
2151  LOG(VB_GENERAL, LOG_ERR, LOC + err);
2152 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
ImageIdList m_markedId
Ids of all marked items.
Definition: galleryviews.h:68
void SelectImage(int id)
Select item if it is displayed.
void SetType(int showType)
Definition: imagemanager.h:408
ImageSlideShowType
Type of slide show.
A video.
Definition: imagetypes.h:39
static void WaitUntilDone(MThread &worker)
Runs a worker thread and waits for it to finish.
static QString FormatSize(int sizeKib)
Definition: imagemanager.h:143
QPair< int, QString > ThumbPair
Definition: imagetypes.h:63
QSharedPointer< ImageItem > ImagePtr
Definition: imagetypes.h:166
ImagePtrK m_selected
Selected item.
Definition: galleryviews.h:66
ImageManagerFe & m_mgr
Manages the images.
MenuSubjects m_menuState
Current selection/marked files when menu is invoked.
void SetUsed(int value)
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
MythUIText * m_crumbsText
def scan(profile, smoonURL, gate)
Definition: scan.py:57
void MenuShow(MythMenu *mainMenu)
Add a Show submenu.
void SetVisible(bool visible) override
TransferThread(TransferMap files, bool move, MythUIProgressDialog *dialog)
~GalleryThumbView() override
Destructor.
MythUIText * m_typeFilterText
Dialog asking for user confirmation.
bool Select(int id, int fallback=0)
Selects first occurrence of an image.
Worker thread for running import.
void Close() override
Exit Gallery.
Each image appears exactly once, but in random order.
Definition: galleryviews.h:23
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
Q_DECLARE_TR_FUNCTIONS(FileTransferWorker)
void SetDateFormat(const QString &format)
Definition: imagemanager.h:489
QString objectName(void) const
Definition: mthread.cpp:254
QString MakeDir(int parent, const QStringList &names, bool rescan=true)
Create directories.
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
DirectoryView * m_view
List of images comprising the view.
int DeviceCount() const
Definition: imagemanager.h:100
QString GetPosition() const
Get positional status.
bool Create(bool focusable)
Initialise buttonlist from XML.
Definition: galleryinfo.cpp:66
A device sub directory.
Definition: imagetypes.h:37
void UpdateImageItem(MythUIButtonListItem *item)
Initialises a single buttonlist item.
QString ChangeOrientation(ImageFileTransform transform, const ImageIdList &ids)
Apply an orientation transform to images.
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
static bool MoveFile(const QString &src, const QString &dst, bool overwrite=false)
Definition: remotefile.cpp:676
void SaveSetting(const QString &key, int newValue)
static Type MythEventMessage
Definition: mythevent.h:73
static void error(const char *str,...)
Definition: vbi.c:42
The image manager for use by Frontends.
Definition: imagemanager.h:456
void RemoveImages(const QStringList &ids, bool deleted=true)
Cleanup UI & image caches when a device is removed.
bool Create() override
Initialises and shows the graphical elements.
void removeListener(QObject *listener)
Remove a listener to the observable.
void SetImageFromMap(const InfoMap &imageMap)
Basic menu dialog, message and a list of options.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
bool LoadFromDb(int parentId) override
Populate view from database as images/subdirs of a directory. View is ordered: Parent dir,...
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:135
static QString LongDateOf(const ImagePtrK &im)
Return a timestamp/datestamp for an image or dir.
QSet< int > m_thumbExists
Images where thumbnails are known to exist.
QList< MythUIButtonList * > m_zoomWidgets
Theme buttonlist widgets implementing zoom levels.
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
Definition: remotefile.cpp:586
void TransformItem(ImageFileTransform tran=kRotateCW)
Apply transform to an image.
MythScreenStack * GetStack(const QString &stackname)
Show Pictures & Videos.
Definition: imagemanager.h:78
void ShowDialog(const QString &msg, const QString &event="")
Show a confirmation dialog.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: lang.c:20
bool GetVisibility()
Definition: imagemanager.h:406
MythScreenStack * GetMainStack()
QString CreateImages(int destId, const ImageListK &images)
Copies database images (but not the files themselves).
MythUIText * m_scanProgressText
void addListener(QObject *listener)
Add a listener to the observable.
ImageListK GetAllNodes() const
Get all images/dirs in view.
Biased random selection so that images are more likely to appear on anniversaries.
Definition: galleryviews.h:25
long long copy(QFile &dst, QFile &src, uint block_size)
Copies src file to dst file.
static MythThemedMenu * menu
void customEvent(QEvent *event) override
Handle custom events.
void Eject()
Remove local device (or Import) from Gallery.
void Move()
Move marked images to selected dir. If no marked files, use previously marked files....
QString DeleteFiles(const ImageIdList &ids)
Delete images.
bool IsMarked(int id) const
Definition: galleryviews.h:188
QString GetCachedThumbUrl(int id) const
Definition: galleryviews.h:121
Random selection from view. An image may be absent or appear multiple times.
Definition: galleryviews.h:24
void BuildFocusList(void)
void CreateThumbnails(const ImageIdList &ids, bool forFolder)
Create thumbnails or verify that they already exist.
int GetResult(void)
void ShowDetails()
Shows exif info/details about an item.
QPair< int, int > IntPair
static Type kEventType
Definition: mythdialogbox.h:56
void ItemClicked(MythUIButtonListItem *item)
Action item click.
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:172
static void RepeatOn(int on=1)
QString HideFiles(bool hidden, const ImageIdList &ids)
Hide/unhide images.
QHash< int, ThumbLocation > m_pendingMap
Buttons waiting for thumbnails to be created.
SlideOrderType
Order of images in slideshow.
Definition: galleryviews.h:21
void Start()
Start Thumbnail screen.
A datastore of images for display by a screen. Provides an ordered list of dirs & images from a singl...
Definition: galleryviews.h:170
GalleryThumbView(MythScreenStack *parent, const char *name)
Constructor.
bool m_editsAllowed
Edit privileges.
void MarkInvertAll()
Invert all marked items.
virtual void Close()
void HideItem(bool hide=true)
Hide or unhide item.
Hide videos.
Definition: imagemanager.h:79
Provides Gallery configuration screens.
void MarkAll(bool mark=true)
Mark or unmark all items.
QPair< MythUIButtonListItem *, int > ThumbLocation
void MarkAll()
Mark all images/dirs.
void SetStart(int value)
ImageFileTransform
Image transformations.
Definition: imagemetadata.h:46
virtual void SetVisible(bool visible)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
QString SetCover(int parent, int cover)
Set image to use as a cover thumbnail(s)
void Copy(bool deleteAfter=false)
Copy marked images to selected dir. If no marked files, use previously marked files....
static void show(uint8_t *buf, int length)
Definition: ringbuffer.c:318
#define LOC
InfoVisibleState GetState() const
Definition: galleryinfo.h:33
void LoadData(int parent)
Loads & displays images from database.
static void UpdateThumbnail(MythUIButtonListItem *button, const ImagePtrK &im, const QString &url, int index)
Update the buttonlist item with a thumbnail.
QString ScanImagesAction(bool start, bool local=false)
Handle scanner start/stop commands.
void Update(const ImagePtrK &im)
Populates available exif details for the current image/dir.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
ImagePtrK GetSelected() const
Get current selection.
void Clear(bool resetParent=true)
Resets view.
void MenuMain()
Shows the main menu when the MENU button was pressed.
static void RepeatOff()
MythUIText * m_captionText
static QString IgnoreDirs(const QString &excludes)
Set directories to ignore during scans of the storage group.
void ClearMarked()
Unmark all items.
bool IsExitingToMain(void) const
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:83
void DeleteItem()
Confirm user deletion of an item.
QString RenameFile(const ImagePtrK &im, const QString &name)
Rename an image.
int GetImages(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Returns images (local or remote but not a combination)
void SetTotal(int value)
void Mark(int id, bool mark)
Mark/unmark an image/dir.
ImageIdList m_prevMarkedId
Ids of marked items in previous dir.
Definition: galleryviews.h:69
void HideMarked(bool hide=true)
Hide or unhide marked items.
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
void ResetUiSelection()
Clears all text widgets for selected item.
void SetText(const QString &text, const QString &name="", const QString &state="")
int GetParentId() const
Definition: galleryviews.h:105
bool m_move
Copy if false, Move if true.
void MenuMarked(MythMenu *mainMenu)
Adds a Marking submenu.
Filenames.
void ShowSettings()
Show configuration screen.
MythUIProgressBar * m_scanProgressBar
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
uint myth_system(const QString &command, uint flags, uint timeout)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
static bool IsLocalId(int id)
Determine image type (local/remote) from its id. Root/Gallery is remote.
Definition: imagetypes.h:129
static QString ConstructPath(const QString &path, const QString &name)
Assembles a canonical file path without corrupting its absolute/relative nature.
Definition: imagemanager.h:132
void ClearCache()
Clears UI cache.
Details not displayed.
Definition: galleryinfo.h:16
bool m_selectedMarked
Is selected item marked ?
Definition: galleryviews.h:67
MythUIHelper * GetMythUI()
Slideshow screen.
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
void AddItem(const QString &title, QVariant data=0, MythMenu *subMenu=nullptr, bool selected=false, bool checked=false)
QList< ImagePtr > ImageList
Definition: imagetypes.h:167
MythUIType * GetFocusWidget(void) const
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
MythMainWindow * GetMythMainWindow(void)
A picture.
Definition: imagetypes.h:38
MythUIBusyDialog * ShowBusyPopup(const QString &message)
MythUIButtonList * m_imageList
void SelectZoomWidget(int change)
Change buttonlist to use a different size.
void StartSlideshow(ImageSlideShowType mode)
Start slideshow screen.
void BuildImageList()
Displays all images in current view.
void Toggle(const ImagePtrK &im)
Toggle infolist state for an image. Focusable widgets toggle between Basic & Full info....
Definition: galleryinfo.cpp:84
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void MenuTransform(MythMenu *mainMenu)
Add a Transform submenu.
int GetNumSetting(const QString &key, int defaultval=0)
Dialog prompting the user to enter a text string.
MythUIText * m_hideFilterText
void SetCover(bool reset=false)
Set or reset thumbnails to use for a directory cover.
QList< int > ImageIdList
Definition: imagetypes.h:59
void MenuAction(MythMenu *mainMenu)
Add a Action submenu.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
InfoList m_infoList
Image details overlay.
ImagePtrK GetParent() const
Definition: galleryviews.h:175
QSet< ImagePtrK > ImageSet
MythUIProgressDialog * m_dialog
Images for which copy/move failed.
bool GetBoolSetting(const QString &key, bool defaultval=false)
MythUIText * m_positionText
void ZoomOut()
Use larger buttonlist widgets.
static void ClearSgDb()
bool DirSelectUp()
Goes up one directory level.
void MakeDir()
Show dialog to input new directory name.
void ShowHidden(bool show=true)
Show or hide hidden files.
void CloseDevices(int devId=DEVICE_INVALID, bool eject=false)
MythScreenStack & m_popupStack
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
void UpdateScanProgress(const QString &scanner, int current, int total)
Update progressbar with scan status.
bool m_unhiddenMarked
Is any marked item unhidden ?
Definition: galleryviews.h:72
void StartScan(bool start=true)
Action scan request.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
QHash< QString, IntPair > m_scanProgress
Last scan updates received from scanners.
ImageCaptionType
Type of captions to display.
void DirSelectDown()
Goes one directory level down.
static QStringList ScanQuery()
Returns storage group scanner status.
void TransformMarked(ImageFileTransform tran=kRotateCW)
Apply transform to marked images.
void ShowType(int type=kPicAndVideo)
Show/hide pictures or videos.
bool DetectLocalDevices()
Detect and scan local devices.
void GetDescendants(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Return all (local or remote) images that are direct children of a dir.
bool Hide()
Remove infolist from display.
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
QStringList RemoveImage(int id, bool deleted=false)
Clear file/dir and all its ancestors from UI cache so that ancestor thumbnails are recalculated....
MenuSubjects GetMenuSubjects()
Determine current selection, markings & various info to support menu display.
QString BuildTransferUrl(const QString &path, bool local) const
Generate Myth URL for a local or remote path.
Definition: imagemanager.h:496
Worker thread for copying/moving files.
MythUIText * m_emptyText
void Import()
Executes user 'Import command'.
TransferMap m_files
Maps source filepath to destination filepath.
void SetItemCurrent(MythUIButtonListItem *item)
bool isFinished(void) const
Definition: mthread.cpp:269
#define PHOTO_DB_ID
Definition: imagetypes.h:28
Storage Group and local mounted media.
Definition: imagetypes.h:35
QMap< ImagePtrK, QString > TransferMap
void InvertMarked()
Mark all unmarked items, unmark all marked items.
bool SetFocusWidget(MythUIType *widget=nullptr)
void ZoomIn()
Use smaller buttonlist widgets.
QString MoveDbImages(const ImagePtrK &destDir, ImageListK &images, const QString &srcPath)
Moves database images (but not the files themselves).
QString CheckThumbnail(MythUIButtonListItem *item, const ImagePtrK &im, ImageIdList &request, int index)
Verify thumbnail is known to exist.
QString ShortDateOf(const ImagePtrK &im) const
Return a short datestamp for thumbnail captions.
Hide pictures.
Definition: imagemanager.h:80
ImageSet GetResult(void)
void ShowRenameInput()
Show dialog to allow input.
Exif comments.
void ExclusionsChanged()
Screen in which all other widgets are contained and rendered.
void ClearDbPressed()
QSet< QString > m_scanActive
Scanners currently scanning.
void SaveBoolSetting(const QString &key, bool newValue)
QString CreateImport()
void ShowPassword()
Displays dialog to accept password.
void Display(ImageItemK &im, const QStringList &tagStrings)
Build list of metadata tags.
void SetSortOrder(int order, int dirOrder)
Definition: imagemanager.h:411
QString GetHostName(void)
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
bool m_hiddenMarked
Is any marked item hidden ?
Definition: galleryviews.h:71
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
void MenuPaste(MythMenu *mainMenu)
Add a Paste submenu.
void DisplayState(const QString &state, const QString &name)
QString CrumbName(ImageItemK &im, bool getPath=false) const
Return a displayable name (with optional path) for an image.
void MenuSlideshow(MythMenu *mainMenu)
Add a Slideshow submenu.
Implements Gallery Thumbnail screen.
#define GALLERY_DB_ID
Definition: imagetypes.h:26
void DeleteMarked()
Confirm user deletion of marked files.
void MarkItem(bool mark=true)
Mark or unmark a single item.
void setChecked(CheckState state)
Ordered as per user setting GallerySortOrder.
Definition: galleryviews.h:22
int m_childCount
Number of images & dirs excl parent.
Definition: galleryviews.h:70
void SetVisibility(bool showHidden)
Definition: imagemanager.h:414
void RemoveFromCacheByFile(const QString &fname)
ShellThread(QString cmd, QString path)
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:173
void SetUiSelection(MythUIButtonListItem *item)
Updates text widgets for selected item.
bool keyPressEvent(QKeyEvent *event) override
Handle keypresses.
A device sub dir comprised from multiple SG dirs.
Definition: imagetypes.h:36