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) const { 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  for (const ImagePtrK & im : m_files.keys())
76  total += im->m_size;
77 
78  int progressSize = 0;
79  for (const 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  QCoreApplication::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  for (const ThumbLocation & location : qAsConst(affected))
400  {
401  MythUIButtonListItem *button = location.first;
402  int index = location.second;
403 
404  auto 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  for (const QString & url : qAsConst(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  for (const QString & id : qAsConst(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  for (const QString & url : qAsConst(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  for (const ImagePtrK & im : qAsConst(nodes))
659  {
660  if (im)
661  {
662  // Data must be set by constructor: First item is automatically
663  // selected and must have data available for selection event, as
664  // subsequent reselection of same item will always fail.
665  auto *item = new MythUIButtonListItem(m_imageList, "",
666  QVariant::fromValue(im));
667 
668  item->setCheckable(true);
669  item->setChecked(MythUIButtonListItem::NotChecked);
670 
671  // assign and display all information about
672  // the current item, like title and subdirectory count
673  UpdateImageItem(item);
674 
675  // Treat parent differently
676  if (im == nodes[0])
677  {
678  // Only non-root parents can ascend
679  if (im->m_id != GALLERY_DB_ID)
680  item->DisplayState("upfolder", "parenttype");
681  }
682  else if (im == selected)
683  // Reinstate the active button item. Note this would fail for parent
685  }
686  }
687 }
688 
689 
695 {
696  auto im = item->GetData().value<ImagePtrK >();
697  if (!im)
698  return;
699 
700  // Allow themes to distinguish between roots, folders, pics, videos
701  switch (im->m_type)
702  {
703  case kDevice:
704  case kCloneDir:
705  case kDirectory:
706  if (im->m_dirCount > 0)
707  {
708  item->SetText(QString("%1/%2")
709  .arg(im->m_fileCount).arg(im->m_dirCount),
710  "childcount");
711  }
712  else
713  {
714  item->SetText(QString::number(im->m_fileCount), "childcount");
715  }
716 
717  item->DisplayState(im->IsDevice() ? "device" : "subfolder", "buttontype");
718  break;
719 
720  case kImageFile:
721  item->DisplayState("image", "buttontype");
722  break;
723 
724  case kVideoFile:
725  item->DisplayState("video", "buttontype");
726  break;
727 
728  default:
729  break;
730  }
731 
732  // Allow theme to distinguish visible/hidden nodes
733  QString hideState = (im->m_isHidden) ? "hidden" : "visible";
734  item->DisplayState(hideState, "buttonstate");
735 
736  // Caption
737  QString text;
739  im->IsFile() ? "GalleryImageCaption"
740  : "GalleryDirCaption");
741  switch (show)
742  {
743  case kNameCaption: text = m_mgr.CrumbName(*im); break;
744  case kDateCaption: text = m_mgr.ShortDateOf(im); break;
745  case kUserCaption: text = im->m_comment; break;
746  default:
747  case kNoCaption: text = ""; break;
748  }
749  item->SetText(text);
750 
751  // Set marked state
753  = m_view->IsMarked(im->m_id)
756 
757  item->setChecked(state);
758 
759  // Thumbnails required
760  ImageIdList request;
761 
762  if (im->m_thumbNails.size() == 1)
763  {
764  // Single thumbnail
765  QString url = CheckThumbnail(item, im, request, 0);
766 
767  if (!url.isEmpty())
768  UpdateThumbnail(item, im, url, 0);
769  }
770  else
771  {
772  // Dir showing up to 4 thumbs. Set them all at same time
773  InfoMap thumbMap;
774  for (int index = 0; index < im->m_thumbNails.size(); ++index)
775  {
776  QString url = CheckThumbnail(item, im, request, index);
777  if (!url.isEmpty())
778  thumbMap.insert(QString("thumbimage%1").arg(index), url);
779  }
780  if (!thumbMap.isEmpty())
781  item->SetImageFromMap(thumbMap);
782  }
783 
784  // Request creation/verification of unknown thumbnails.
785  if (!request.isEmpty())
786  m_mgr.CreateThumbnails(request, im->IsDirectory());
787 }
788 
789 
802  ImageIdList &request, int index)
803 {
804  ThumbPair thumb(im->m_thumbNails.at(index));
805  int id = thumb.first;
806 
807  if (m_thumbExists.contains(id))
808  return thumb.second;
809 
810  // Request BE thumbnail check if it is not already pending
811  if (!m_pendingMap.contains(id))
812  request << id;
813 
814  // Note this button is awaiting an update
815  m_pendingMap.insertMulti(id, qMakePair(item, index));
816 
817  return "";
818 }
819 
820 
829  const ImagePtrK& im, const QString &url,
830  int index)
831 {
832  if (im->m_thumbNails.size() == 1)
833  {
834  // Pics, dirs & videos use separate widgets
835  switch (im->m_type)
836  {
837  case kImageFile: button->SetImage(url); break;
838  case kVideoFile: button->SetImage(url, "videoimage"); break;
839  default: button->SetImage(url, "folderimage"); break;
840  }
841  }
842  else
843  // Dir with 4 thumbnails
844  button->SetImage(url, QString("thumbimage%1").arg(index));
845 }
846 
847 
855 void GalleryThumbView::UpdateScanProgress(const QString &scanner,
856  int current, int total)
857 {
858  // Scan update
859  m_scanProgress.insert(scanner, qMakePair(current, total));
860 
861  // Detect end of this scan
862  if (current >= total)
863  {
864  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Finished %1 %2/%3")
865  .arg(scanner).arg(current).arg(total));
866 
867  // Mark inactive scanner
868  m_scanActive.remove(scanner);
869 
870  // Detect end of last scan
871  if (m_scanActive.isEmpty())
872  {
873  if (m_scanProgressText)
874  {
877  }
878  if (m_scanProgressBar)
879  {
882  }
883 
884  m_scanProgress.clear();
885 
886  return;
887  }
888  }
889  else
890  {
891  // Detect first scan update
892  if (m_scanActive.isEmpty())
893  {
894  // Show progressbar when first scan starts
895  if (m_scanProgressBar)
896  {
899  }
900  if (m_scanProgressText)
902  }
903 
904  if (!m_scanActive.contains(scanner))
905  {
906  LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Started %1 %2/%3")
907  .arg(scanner).arg(current).arg(total));
908 
909  // Mark active scanner
910  m_scanActive.insert(scanner);
911  }
912  }
913 
914  // Aggregate all running scans
915  int currentAgg = 0;
916  int totalAgg = 0;
917  for (IntPair scan : m_scanProgress.values())
918  {
919  currentAgg += scan.first;
920  totalAgg += scan.second;
921  }
922 
923  if (m_scanProgressBar)
924  {
925  m_scanProgressBar->SetUsed(currentAgg);
926  m_scanProgressBar->SetTotal(totalAgg);
927  }
928  if (m_scanProgressText)
929  m_scanProgressText->SetText(tr("%L1 of %L3").arg(currentAgg).arg(totalAgg));
930 }
931 
932 
937 {
938  if (m_positionText)
940 
941  if (m_captionText)
942  m_captionText->Reset();
943 
944  if (m_crumbsText)
945  m_crumbsText->Reset();
946 
947  if (m_hideFilterText)
949 
950  if (m_typeFilterText)
952 }
953 
954 
960 {
961  auto im = item->GetData().value<ImagePtrK >();
962  if (im)
963  {
964  // update the position in the node list
965  m_view->Select(im->m_id);
966 
967  // show the name/path of the image
968  if (m_crumbsText)
969  m_crumbsText->SetText(m_mgr.CrumbName(*im, true));
970 
971  if (m_captionText)
972  {
973  // show the date & comment of non-root nodes
974  QStringList text;
975  if (im->m_id != GALLERY_DB_ID)
976  {
977  if (im->IsFile() || im->IsDevice())
978  text << ImageManagerFe::LongDateOf(im);
979 
980  if (!im->m_comment.isEmpty())
981  text << im->m_comment;
982  }
983  m_captionText->SetText(text.join(" - "));
984  }
985 
986  if (m_hideFilterText)
987  {
988  m_hideFilterText->SetText(m_mgr.GetVisibility() ? tr("Hidden") : "");
989  }
990 
991  if (m_typeFilterText)
992  {
993  QString text = "";
994  switch (m_mgr.GetType())
995  {
996  case kPicAndVideo : text = ""; break;
997  case kPicOnly : text = tr("Pictures"); break;
998  case kVideoOnly : text = tr("Videos"); break;
999  }
1000  m_typeFilterText->SetText(text);
1001  }
1002 
1003  // show the position of the image
1004  if (m_positionText)
1006 
1007  // Update any file details information
1008  m_infoList.Update(im);
1009  }
1010 }
1011 
1012 
1017 {
1018  // Create the main menu
1019  auto *menu = new MythMenu(tr("Gallery Options"), this, "mainmenu");
1020 
1021  // Menu options depend on the marked files and the current node
1023 
1024  if (m_menuState.m_selected)
1025  {
1026  if (m_editsAllowed)
1027  {
1028  MenuMarked(menu);
1029  MenuPaste(menu);
1031  MenuAction(menu);
1032  }
1034  MenuShow(menu);
1035  if (!m_editsAllowed)
1036  menu->AddItem(tr("Enable Edits"), SLOT(ShowPassword()));
1037  }
1038 
1039  // Depends on current status of backend scanner - string(number(isBackend()))
1040  if (m_scanActive.contains("1"))
1041  menu->AddItem(tr("Stop Scan"), SLOT(StopScan()));
1042  else
1043  menu->AddItem(tr("Scan Storage Group"), SLOT(StartScan()));
1044 
1045  menu->AddItem(tr("Settings"), SLOT(ShowSettings()));
1046 
1047  auto *popup = new MythDialogBox(menu, &m_popupStack, "menuPopup");
1048  if (popup->Create())
1049  m_popupStack.AddScreen(popup);
1050  else
1051  delete popup;
1052 }
1053 
1054 
1060 {
1061  ImagePtrK parent = m_view->GetParent();
1062 
1063  if (m_menuState.m_childCount == 0 || parent.isNull())
1064  return;
1065 
1066  QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1067  auto *menu = new MythMenu(title, this, "markmenu");
1068 
1069  // Mark/unmark selected
1070  if (m_menuState.m_selected->IsFile())
1071  {
1073  menu->AddItem(tr("Unmark File"), SLOT(UnmarkItem()));
1074  else
1075  menu->AddItem(tr("Mark File"), SLOT(MarkItem()));
1076  }
1077  // Cannot mark/unmark parent dir from this level
1078  else if (!m_menuState.m_selected->IsDevice()
1079  && m_menuState.m_selected != parent)
1080  {
1082  menu->AddItem(tr("Unmark Directory"), SLOT(UnmarkItem()));
1083  else
1084  menu->AddItem(tr("Mark Directory"), SLOT(MarkItem()));
1085  }
1086 
1087  if (parent->m_id != GALLERY_DB_ID)
1088  {
1089  // Mark All if unmarked files exist
1091  menu->AddItem(tr("Mark All"), SLOT(MarkAll()));
1092 
1093  // Unmark All if marked files exist
1094  if (!m_menuState.m_markedId.isEmpty())
1095  {
1096  menu->AddItem(tr("Unmark All"), SLOT(UnmarkAll()));
1097  menu->AddItem(tr("Invert Marked"), SLOT(MarkInvertAll()));
1098  }
1099  }
1100 
1101  if (menu->IsEmpty())
1102  delete menu;
1103  else
1104  mainMenu->AddItem(tr("Mark"), nullptr, menu);
1105 }
1106 
1107 
1113 {
1114  // Can only copy/move into non-root dirs
1115  if (m_menuState.m_selected->IsDirectory()
1116  && m_menuState.m_selected->m_id != GALLERY_DB_ID)
1117  {
1118  // Operate on current marked files, if any
1120  if (files.isEmpty())
1121  files = m_menuState.m_prevMarkedId;
1122  if (files.isEmpty())
1123  return;
1124 
1125  QString title = tr("%L1 marked").arg(files.size());
1126 
1127  auto *menu = new MythMenu(title, this, "pastemenu");
1128 
1129  menu->AddItem(tr("Move Marked Into"), SLOT(Move()));
1130  menu->AddItem(tr("Copy Marked Into"), SLOT(Copy()));
1131 
1132  mainMenu->AddItem(tr("Paste"), nullptr, menu);
1133  }
1134 }
1135 
1136 
1142 {
1143  // Operate on marked files, if any, otherwise selected node
1144  if (!m_menuState.m_markedId.isEmpty())
1145  {
1146  QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1147 
1148  auto *menu = new MythMenu(title, this, "");
1149 
1150  menu->AddItem(tr("Rotate Marked CW"), SLOT(RotateCWMarked()));
1151  menu->AddItem(tr("Rotate Marked CCW"), SLOT(RotateCCWMarked()));
1152  menu->AddItem(tr("Flip Marked Horizontal"), SLOT(FlipHorizontalMarked()));
1153  menu->AddItem(tr("Flip Marked Vertical"), SLOT(FlipVerticalMarked()));
1154  menu->AddItem(tr("Reset Marked to Exif"), SLOT(ResetExifMarked()));
1155 
1156  mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1157  }
1158  else if (m_menuState.m_selected->IsFile())
1159  {
1160  auto *menu = new MythMenu(m_menuState.m_selected->m_baseName, this, "");
1161 
1162  menu->AddItem(tr("Rotate CW"), SLOT(RotateCW()));
1163  menu->AddItem(tr("Rotate CCW"), SLOT(RotateCCW()));
1164  menu->AddItem(tr("Flip Horizontal"), SLOT(FlipHorizontal()));
1165  menu->AddItem(tr("Flip Vertical"), SLOT(FlipVertical()));
1166  menu->AddItem(tr("Reset to Exif"), SLOT(ResetExif()));
1167 
1168  mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1169  }
1170 }
1171 
1172 
1178 {
1179  MythMenu *menu = nullptr;
1180  ImagePtrK selected = m_menuState.m_selected;
1181 
1182  // Operate on current marked files, if any
1183  if (!m_menuState.m_markedId.empty())
1184  {
1185  QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1186 
1187  menu = new MythMenu(title, this, "actionmenu");
1188 
1189  // Only offer Hide/Unhide if relevant
1191  menu->AddItem(tr("Hide Marked"), SLOT(HideMarked()));
1193  menu->AddItem(tr("Unhide Marked"), SLOT(UnhideMarked()));
1194 
1195  menu->AddItem(tr("Delete Marked"), SLOT(DeleteMarked()));
1196  }
1197  else
1198  {
1199  // Operate on selected file/dir
1200  menu = new MythMenu(selected->m_baseName, this, "actionmenu");
1201 
1202  // Prohibit actions on devices and parent dirs
1203  if (!selected->IsDevice() && selected != m_view->GetParent())
1204  {
1205  if (selected->m_isHidden)
1206  menu->AddItem(tr("Unhide"), SLOT(Unhide()));
1207  else
1208  menu->AddItem(tr("Hide"), SLOT(HideItem()));
1209 
1210  menu->AddItem(tr("Use as Cover"), SLOT(SetCover()));
1211  menu->AddItem(tr("Delete"), SLOT(DeleteItem()));
1212  menu->AddItem(tr("Rename"), SLOT(ShowRenameInput()));
1213  }
1214  else if (selected->m_userThumbnail)
1215  menu->AddItem(tr("Reset Cover"), SLOT(ResetCover()));
1216  }
1217 
1218  // Can only mkdir in a non-root dir
1219  if (selected->IsDirectory()
1220  && selected->m_id != GALLERY_DB_ID)
1221  menu->AddItem(tr("Create Directory"), SLOT(MakeDir()));
1222 
1223  // Only show import command on root, when defined
1224  if (selected->m_id == GALLERY_DB_ID
1225  && !gCoreContext->GetSetting("GalleryImportCmd").isEmpty())
1226  menu->AddItem(tr("Import"), SLOT(Import()));
1227 
1228  // Only show eject when devices (excluding import) exist
1229  if (selected->IsDevice() && selected->IsLocal())
1230  menu->AddItem(tr("Eject media"), SLOT(Eject()));
1231 
1232  if (menu->IsEmpty())
1233  delete menu;
1234  else
1235  mainMenu->AddItem(tr("Actions"), nullptr, menu);
1236 }
1237 
1238 
1244 {
1245  int order = gCoreContext->GetNumSetting("GallerySlideOrder", kOrdered);
1246 
1247  QString ordering;
1248  switch (order)
1249  {
1250  case kShuffle : ordering = tr("Shuffled"); break;
1251  case kRandom : ordering = tr("Random"); break;
1252  case kSeasonal : ordering = tr("Seasonal"); break;
1253  default:
1254  case kOrdered : ordering = tr("Ordered"); break;
1255  }
1256 
1257  auto *menu = new MythMenu(tr("Slideshow") + " (" + ordering + ")",
1258  this, "SlideshowMenu");
1259 
1260  // Use selected dir or parent, if image selected
1261  if (m_menuState.m_selected->IsDirectory())
1262  {
1263  if (m_menuState.m_selected->m_fileCount > 0)
1264  menu->AddItem(tr("Directory"), SLOT(Slideshow()));
1265 
1266  if (m_menuState.m_selected->m_dirCount > 0)
1267  menu->AddItem(tr("Recursive"), SLOT(RecursiveSlideshow()));
1268  }
1269  else
1270  menu->AddItem(tr("Current Directory"), SLOT(Slideshow()));
1271 
1272  auto *orderMenu = new MythMenu(tr("Slideshow Order"), this, "SlideOrderMenu");
1273 
1274  orderMenu->AddItem(tr("Ordered"), nullptr, nullptr, order == kOrdered);
1275  orderMenu->AddItem(tr("Shuffled"), nullptr, nullptr, order == kShuffle);
1276  orderMenu->AddItem(tr("Random"), nullptr, nullptr, order == kRandom);
1277  orderMenu->AddItem(tr("Seasonal"), nullptr, nullptr, order == kSeasonal);
1278 
1279  menu->AddItem(tr("Change Order"), nullptr, orderMenu);
1280 
1281  if (gCoreContext->GetBoolSetting("GalleryRepeat", false))
1282  menu->AddItem(tr("Turn Repeat Off"), SLOT(RepeatOff()));
1283  else
1284  menu->AddItem(tr("Turn Repeat On"), SLOT(RepeatOn()));
1285 
1286  mainMenu->AddItem(tr("Slideshow"), nullptr, menu);
1287 }
1288 
1289 
1295 {
1296  auto *menu = new MythMenu(tr("Show Options"), this, "showmenu");
1297 
1298  int type = m_mgr.GetType();
1299  if (type == kPicAndVideo)
1300  {
1301  menu->AddItem(tr("Hide Pictures"), SLOT(HidePictures()));
1302  menu->AddItem(tr("Hide Videos"), SLOT(HideVideos()));
1303  }
1304  else
1305  menu->AddItem(type == kPicOnly ? tr("Show Videos") : tr("Show Pictures"),
1306  SLOT(ShowType()));
1307 
1308  int show = gCoreContext->GetNumSetting("GalleryImageCaption");
1309  auto *captionMenu = new MythMenu(tr("Image Captions"), this,
1310  "ImageCaptionMenu");
1311 
1312  captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1313  captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1314  captionMenu->AddItem(tr("Comment"), nullptr, nullptr, show == kUserCaption);
1315  captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1316 
1317  menu->AddItem(tr("Image Captions"), nullptr, captionMenu);
1318 
1319  show = gCoreContext->GetNumSetting("GalleryDirCaption");
1320  captionMenu = new MythMenu(tr("Directory Captions"), this, "DirCaptionMenu");
1321 
1322  captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1323  captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1324  captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1325 
1326  menu->AddItem(tr("Directory Captions"), nullptr, captionMenu);
1327 
1328  if (m_editsAllowed)
1329  {
1330  if (m_mgr.GetVisibility())
1331  menu->AddItem(tr("Hide Hidden Items"), SLOT(HideHidden()));
1332  else
1333  menu->AddItem(tr("Show Hidden Items"), SLOT(ShowHidden()));
1334  }
1335 
1336  if (m_zoomLevel > 0)
1337  menu->AddItem(tr("Zoom Out"), SLOT(ZoomOut()));
1338  if (m_zoomLevel < m_zoomWidgets.size() - 1)
1339  menu->AddItem(tr("Zoom In"), SLOT(ZoomIn()));
1340 
1341  QString details = m_infoList.GetState() == kNoInfo
1342  ? tr("Show Details") : tr("Hide Details");
1343 
1344  menu->AddItem(details, SLOT(ShowDetails()));
1345 
1346  mainMenu->AddItem(tr("Show"), nullptr, menu);
1347 }
1348 
1349 
1355 {
1356  // Only update selection if image is currently displayed
1357  if (m_view->Select(id, -1))
1358  BuildImageList();
1359 }
1360 
1361 
1367 {
1368  if (!item)
1369  return;
1370 
1371  auto im = item->GetData().value<ImagePtrK>();
1372  if (!im)
1373  return;
1374 
1375  switch (im->m_type)
1376  {
1377  case kDevice:
1378  case kCloneDir:
1379  case kDirectory:
1380  if (im == m_view->GetParent())
1381  DirSelectUp();
1382  else
1383  DirSelectDown();
1384  break;
1385 
1386  case kImageFile:
1387  case kVideoFile:
1388  StartSlideshow(kBrowseSlides); break;
1389  };
1390 }
1391 
1392 
1398 {
1399  QString err = m_mgr.ScanImagesAction(start);
1400  if (!err.isEmpty())
1401  ShowOkPopup(err);
1402 }
1403 
1404 
1410 {
1411  ImagePtrK selected = m_view->GetSelected();
1412  if (!selected)
1413  return;
1414 
1416  auto *slide = new GallerySlideView(mainStack, "galleryslideview",
1417  m_editsAllowed);
1418  if (slide->Create())
1419  {
1420  mainStack->AddScreen(slide);
1421 
1422  // Update selected item when slideshow exits
1423  connect(slide, SIGNAL(ImageSelected(int)),
1424  this, SLOT(SelectImage(int)));
1425 
1426  if (selected->IsDirectory())
1427  {
1428  // Show selected dir
1429  slide->Start(mode, selected->m_id);
1430  }
1431  else
1432  {
1433  // Show current dir starting at selection
1434  slide->Start(mode, selected->m_parentId, selected->m_id);
1435  }
1436  }
1437  else
1438  delete slide;
1439 }
1440 
1441 
1446 {
1447  ImagePtrK im = m_view->GetParent();
1448  if (im)
1449  {
1450  LOG(VB_GUI, LOG_DEBUG, LOC +
1451  QString("Going up from %1").arg(im->m_filePath));
1452 
1453  // Select the upfolder in the higher dir
1454  m_view->Select(im->m_id);
1455 
1456  // Create tree rooted at parent of the kUpFolder directory node
1457  LoadData(im->m_parentId);
1458  }
1459  return true;
1460 }
1461 
1462 
1467 {
1468  ImagePtrK im = m_view->GetSelected();
1469  if (im)
1470  {
1471  LOG(VB_GUI, LOG_DEBUG, LOC +
1472  QString("Going down to %1").arg(im->m_filePath));
1473 
1474  // Create tree rooted at selected item
1475  LoadData(im->m_id);
1476  }
1477 }
1478 
1479 
1485 {
1486  ImagePtrK im = m_view->GetSelected();
1487  if (im)
1488  {
1489  // Mark/unmark selected item
1490  m_view->Mark(im->m_id, mark);
1491 
1492  // Redisplay buttonlist as a parent dir may have been unmarked
1493  BuildImageList();
1494  }
1495 }
1496 
1497 
1503 {
1504  if (mark)
1505  m_view->MarkAll();
1506  else
1507  m_view->ClearMarked();
1508 
1509  // Redisplay buttonlist
1510  BuildImageList();
1511 }
1512 
1513 
1518 {
1519  m_view->InvertMarked();
1520 
1521  // Redisplay buttonlist
1522  BuildImageList();
1523 }
1524 
1525 
1531 {
1532  ImagePtrK im = m_view->GetSelected();
1533  if (im && m_editsAllowed)
1534  {
1535  ImageIdList ids;
1536  ids.append(im->m_id);
1537  QString err = m_mgr.ChangeOrientation(transform, ids);
1538  if (!err.isEmpty())
1539  ShowOkPopup(err);
1540  }
1541 }
1542 
1543 
1549 {
1550  QString err = m_mgr.ChangeOrientation(transform, m_menuState.m_markedId);
1551  if (!err.isEmpty())
1552  ShowOkPopup(err);
1553 }
1554 
1555 
1561 {
1562  if (m_menuState.m_selected)
1563  {
1564  ImageIdList ids;
1565  ids.append(m_menuState.m_selected->m_id);
1566 
1567  QString err = m_mgr.HideFiles(hide, ids);
1568  if (!err.isEmpty())
1569  {
1570  ShowOkPopup(err);
1571  }
1572  else if (hide && !m_mgr.GetVisibility())
1573  {
1574  // Unmark invisible file
1575  m_view->Mark(m_menuState.m_selected->m_id, false);
1576  }
1577  }
1578 }
1579 
1580 
1586 {
1587  QString err = m_mgr.HideFiles(hide, m_menuState.m_markedId);
1588  if (!err.isEmpty())
1589  {
1590  ShowOkPopup(err);
1591  }
1592  else if (hide && !m_mgr.GetVisibility())
1593  {
1594  // Unmark invisible files
1595  for (int id : qAsConst(m_menuState.m_markedId))
1596  m_view->Mark(id, false);
1597  }
1598 }
1599 
1600 
1605 {
1606  if (m_menuState.m_selected)
1607  ShowDialog(tr("Do you want to delete\n%1 ?")
1608  .arg(m_menuState.m_selected->m_baseName), "ConfirmDelete");
1609 }
1610 
1611 
1616 {
1617  ShowDialog(tr("Do you want to delete all marked files ?"),
1618  "ConfirmDeleteMarked");
1619 }
1620 
1621 
1626 {
1627  // Show settings dialog
1628  auto *config = new GallerySettings(m_editsAllowed);
1630  auto *ssd = new StandardSettingDialog(mainStack, "gallerysettings", config);
1631  if (!ssd->Create())
1632  {
1633  delete ssd;
1634  return;
1635  }
1636 
1637  mainStack->AddScreen(ssd);
1638 
1639  // Effect setting changes when dialog saves on exit
1640 
1641  connect(config, &GallerySettings::ClearDbPressed,
1643 
1644  connect(config, &GallerySettings::OrderChanged,
1645  this, [this]()
1646  {
1647  // Update db view, reset cover cache & reload
1648  int sortIm = gCoreContext->GetNumSetting("GalleryImageOrder");
1649  int sortDir = gCoreContext->GetNumSetting("GalleryDirOrder");
1650  m_mgr.SetSortOrder(sortIm, sortDir);
1651  m_view->ClearCache();
1653  });
1654 
1655  connect(config, &GallerySettings::DateChanged,
1656  this, [this]()
1657  {
1658  QString date = gCoreContext->GetSetting("GalleryDateFormat");
1659  m_mgr.SetDateFormat(date);
1660  BuildImageList();
1661  });
1662 
1663  connect(config, &GallerySettings::ExclusionsChanged,
1664  this, [this]()
1665  {
1666  // Request rescan
1667  QString exclusions = gCoreContext->GetSetting("GalleryIgnoreFilter");
1668  m_view->ClearCache();
1669  ImageManagerFe::IgnoreDirs(exclusions);
1670  });
1671 }
1672 
1673 
1679 {
1680  gCoreContext->SaveBoolSetting("GalleryShowHidden", show);
1681 
1682  // Update Db(s)
1684 
1685  // Reset dir thumbnail cache
1686  m_view->ClearCache();;
1687 
1689 }
1690 
1691 
1697 void GalleryThumbView::ShowDialog(const QString& msg, const QString& event)
1698 {
1699  auto *popup = new MythConfirmationDialog(&m_popupStack, msg, true);
1700 
1701  if (popup->Create())
1702  {
1703  popup->SetReturnEvent(this, event);
1704  m_popupStack.AddScreen(popup);
1705  }
1706  else
1707  delete popup;
1708 }
1709 
1710 
1715 {
1716  if (m_menuState.m_selected)
1717  {
1718  QString base = QFileInfo(m_menuState.m_selected->m_baseName).completeBaseName();
1719  QString msg = tr("Enter a new name:");
1720  auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone,
1721  false, base);
1722  if (popup->Create())
1723  {
1724  popup->SetReturnEvent(this, "FileRename");
1725  m_popupStack.AddScreen(popup);
1726  }
1727  else
1728  delete popup;
1729  }
1730 }
1731 
1732 
1737 {
1739 }
1740 
1741 
1746 {
1747  QString msg = tr("Enter password:");
1748  auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone, true);
1749  if (popup->Create())
1750  {
1751  popup->SetReturnEvent(this, "Password");
1752  m_popupStack.AddScreen(popup);
1753  }
1754  else
1755  delete popup;
1756 }
1757 
1758 
1763 {
1764  gCoreContext->SaveSetting("GalleryShowType", type);
1765 
1766  // Update Db(s)
1767  m_mgr.SetType(type);
1768 
1769  // Reset dir thumbnail cache
1770  m_view->ClearCache();
1771 
1773 }
1774 
1775 
1781 {
1782  if (m_menuState.m_selected)
1783  {
1784  QString err = reset ? m_mgr.SetCover(m_menuState.m_selected->m_id, 0)
1785  : m_mgr.SetCover(m_menuState.m_selected->m_parentId,
1786  m_menuState.m_selected->m_id);
1787  if (!err.isEmpty())
1788  ShowOkPopup(err);
1789  }
1790 }
1791 
1792 
1797 {
1798  SelectZoomWidget(-1);
1799  BuildImageList();
1800 }
1801 
1802 
1807 {
1808  SelectZoomWidget(1);
1809  BuildImageList();
1810 }
1811 
1812 
1818 {
1819  m_zoomLevel += change;
1820 
1821  // constrain to zoom levels supported by theme
1822  if (m_zoomLevel < 0)
1823  m_zoomLevel = 0;
1824  if (m_zoomLevel >= m_zoomWidgets.size())
1825  m_zoomLevel = m_zoomWidgets.size() - 1;
1826 
1827  // Store any requested change, but not constraining adjustments
1828  // Thus, changing to a theme with fewer zoom levels will not overwrite the
1829  // setting
1830  if (change != 0)
1831  gCoreContext->SaveSetting("GalleryZoomLevel", m_zoomLevel);
1832 
1833  // dump the current list widget
1834  if (m_imageList)
1835  {
1836  m_imageList->SetVisible(false);
1837  disconnect(m_imageList, nullptr, this, nullptr);
1838  }
1839 
1840  // initialise new list widget
1842 
1843  m_imageList->SetVisible(true);
1845 
1846  // Monitor list actions (after focus events have been ignored)
1847  connect(m_imageList, SIGNAL(itemClicked(MythUIButtonListItem *)),
1849  connect(m_imageList, SIGNAL(itemSelected(MythUIButtonListItem *)),
1851 }
1852 
1853 
1858 {
1859  auto *popup = new MythTextInputDialog(&m_popupStack,
1860  tr("Enter name of new directory"),
1861  FilterNone, false);
1862  if (popup->Create())
1863  {
1864  popup->SetReturnEvent(this, "MakeDir");
1865  m_popupStack.AddScreen(popup);
1866  }
1867  else
1868  delete popup;
1869 }
1870 
1871 
1876 {
1878  if (dir)
1879  m_mgr.CloseDevices(dir->m_device, true);
1880 }
1881 
1882 
1892 void GalleryThumbView::Copy(bool deleteAfter)
1893 {
1894  // Destination must be a dir
1895  ImagePtrK destDir = m_menuState.m_selected;
1896  if (!destDir || destDir->IsFile())
1897  return;
1898 
1899  // Use current markings, if any. Otherwise use previous markings
1900  ImageIdList markedIds = m_menuState.m_markedId;
1901  if (markedIds.isEmpty())
1902  {
1903  markedIds = m_menuState.m_prevMarkedId;
1904  if (markedIds.isEmpty())
1905  {
1906  ShowOkPopup(tr("No files specified"));
1907  return;
1908  }
1909  }
1910 
1911  // Get all files/dirs in subtree(s). Only files are copied
1912  ImageList files;
1913  ImageList dirs;
1914  m_mgr.GetDescendants(markedIds, files, dirs);
1915 
1916  if (dirs.isEmpty() && files.isEmpty())
1917  {
1918  ShowOkPopup(tr("No images"));
1919  // Nothing to clean up
1920  return;
1921  }
1922 
1923  // Child dirs appear before their subdirs. If no dirs, images are all direct children
1924  ImagePtrK aChild = dirs.isEmpty() ? files[0] : dirs[0];
1925 
1926  // Determine parent path including trailing /
1927  int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
1928 
1929  // Update filepaths for Db & generate URLs for filesystem copy
1930  // Only copy files, destination dirs will be created automatically
1931  TransferThread::TransferMap transfers;
1932  for (const ImagePtr & im : qAsConst(files))
1933  {
1934  // Replace base path with destination path
1935  im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath,
1936  im->m_filePath.mid(basePathSize));
1937 
1938  transfers.insert(im, m_mgr.BuildTransferUrl(im->m_filePath,
1939  destDir->IsLocal()));
1940  }
1941 
1942  // Create progress dialog
1943  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1944  auto *progress = new MythUIProgressDialog(tr("Copying files"), popupStack,
1945  "copydialog");
1946  if (progress->Create())
1947  popupStack->AddScreen(progress, false);
1948  else
1949  {
1950  delete progress;
1951  progress = nullptr;
1952  }
1953 
1954  // Copy files in a servant thread
1955  TransferThread copy(transfers, false, progress);
1957  TransferThread::ImageSet failed = copy.GetResult();
1958 
1959  if (progress)
1960  progress->Close();
1961 
1962  if (!failed.isEmpty())
1963  ShowOkPopup(tr("Failed to copy %L1/%Ln file(s)", nullptr, transfers.size())
1964  .arg(failed.size()));
1965 
1966  // Don't update Db for files that failed
1967  for (const ImagePtrK & im : qAsConst(failed))
1968  transfers.remove(im);
1969 
1970  ImageListK newImages = transfers.keys();
1971 
1972  // Include dirs
1973  QStringList dirPaths;
1974  for (const ImagePtr & im : qAsConst(dirs))
1975  {
1976  QString relPath = im->m_filePath.mid(basePathSize);
1977 
1978  dirPaths << relPath;
1979 
1980  // Replace base path with destination path
1981  im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath, relPath);
1982 
1983  // Append dirs so that hidden state & cover is preserved for new dirs
1984  // Pre-existing dirs will take precedance over these.
1985  newImages.append(im);
1986  }
1987 
1988  // Copy empty dirs as well (will fail for non-empty dirs)
1989  if (!dirPaths.isEmpty())
1990  m_mgr.MakeDir(destDir->m_id, dirPaths, false);
1991 
1992  if (!newImages.isEmpty())
1993  {
1994  // Update Db
1995  m_mgr.CreateImages(destDir->m_id, newImages);
1996 
1997  if (deleteAfter)
1998  {
1999  // Delete files/dirs that have been successfully copied
2000  // Will fail for dirs containing images that failed to copy
2001  ImageIdList ids;
2002  for (const ImagePtrK & im : qAsConst(newImages))
2003  ids << im->m_id;
2004 
2005  m_mgr.DeleteFiles(ids);
2006  }
2007  }
2008 }
2009 
2010 
2021 {
2022  // Destination must be a dir
2023  ImagePtrK destDir = m_menuState.m_selected;
2024  if (!destDir || destDir->IsFile())
2025  return;
2026 
2027  // Use current markings, if any. Otherwise use previous markings
2028  ImageIdList markedIds = m_menuState.m_markedId;
2029  if (markedIds.isEmpty())
2030  {
2031  markedIds = m_menuState.m_prevMarkedId;
2032  if (markedIds.isEmpty())
2033  {
2034  ShowOkPopup(tr("No files specified"));
2035  return;
2036  }
2037  }
2038 
2039  // Note UI mandates that transferees are either all local or all remote
2040  if (destDir->IsLocal() != ImageItem::IsLocalId(markedIds[0]))
2041  {
2042  // Moves between hosts require copy/delete
2043  Copy(true);
2044  return;
2045  }
2046 
2047  // Get marked images. Each file and dir will be renamed
2048  ImageList files;
2049  ImageList dirs;
2050  if (m_mgr.GetImages(markedIds, files, dirs) <= 0)
2051  {
2052  ShowOkPopup(tr("No images specified"));
2053  // Nothing to clean up
2054  return;
2055  }
2056  ImageList images = dirs + files;
2057 
2058  // Determine parent from first dir or pic
2059  ImagePtr aChild = images[0];
2060 
2061  // Determine parent path including trailing /
2062  // Note UI mandates that transferees all have same parent.
2063  int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
2064  QString parentPath = aChild->m_filePath.left(basePathSize);
2065 
2066  // Determine destination URLs
2067  TransferThread::TransferMap transfers;
2068  for (const QSharedPointer<ImageItem> & im : qAsConst(images))
2069  {
2070  // Replace base path with destination path
2071  QString newPath = ImageManagerFe::ConstructPath(destDir->m_filePath,
2072  im->m_filePath.mid(basePathSize));
2073 
2074  transfers.insert(im, m_mgr.BuildTransferUrl(newPath, aChild->IsLocal()));
2075  }
2076 
2077  // Create progress dialog
2078  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2079  auto *progress = new MythUIProgressDialog(tr("Moving files"), popupStack,
2080  "movedialog");
2081 
2082  if (progress->Create())
2083  popupStack->AddScreen(progress, false);
2084  else
2085  {
2086  delete progress;
2087  progress = nullptr;
2088  }
2089 
2090  // Move files in a servant thread
2091  TransferThread move(transfers, true, progress);
2092  WaitUntilDone(move);
2093  TransferThread::ImageSet failed = move.GetResult();
2094 
2095  if (progress)
2096  progress->Close();
2097 
2098  if (!failed.isEmpty())
2099  ShowOkPopup(tr("Failed to move %L1/%Ln file(s)", nullptr, transfers.size())
2100  .arg(failed.size()));
2101 
2102  // Don't update Db for files that failed
2103  for (const ImagePtrK & im : qAsConst(failed))
2104  transfers.remove(im);
2105 
2106  if (!transfers.isEmpty())
2107  {
2108  ImageListK moved = transfers.keys();
2109 
2110  // Unmark moved files
2111  for (const ImagePtrK & im : qAsConst(moved))
2112  m_view->Mark(im->m_id, false);
2113 
2114  // Update Db
2115  m_mgr.MoveDbImages(destDir, moved, parentPath);
2116  }
2117 }
2118 
2119 
2124 {
2125  QString path = m_mgr.CreateImport();
2126  if (path.isEmpty())
2127  {
2128  ShowOkPopup(tr("Failed to create temporary directory."));
2129  return;
2130  }
2131 
2132  // Replace placeholder in command
2133  QString cmd = gCoreContext->GetSetting("GalleryImportCmd");
2134  cmd.replace("%TMPDIR%", path);
2135 
2136  // Run command in a separate thread
2137  MythUIBusyDialog *busy =
2138  ShowBusyPopup(tr("Running Import command.\nPlease wait..."));
2139 
2140  ShellThread thread(cmd, path);
2141  WaitUntilDone(thread);
2142 
2143  if (busy)
2144  busy->Close();
2145 
2146  int error = thread.GetResult();
2147  if (error != 0)
2148  ShowOkPopup(tr("Import command failed.\nError: %1").arg(error));
2149 
2150  // Rescan local devices
2151  QString err = m_mgr.ScanImagesAction(true, true);
2152  if (!err.isEmpty())
2153  LOG(VB_GENERAL, LOG_ERR, LOC + err);
2154 }
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
return token
Definition: musicutils.cpp:74
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.
static void error(const char *str,...)
Definition: vbi.cpp:42
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:62
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:677
void SaveSetting(const QString &key, int newValue)
static Type MythEventMessage
Definition: mythevent.h:73
int GetType() const
Definition: imagemanager.h:405
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:587
t<< doc.toString(4);f.close();LOG(VB_JOBQUEUE, LOG_INFO, "Copying video file");bool res=copyFile(filename, saveDirectory+title+"/"+baseName);if(!res) return 0;if(QFile::exists(filename+".png")) { LOG(VB_JOBQUEUE, LOG_INFO, "Copying preview image");res=copyFile(filename+".png", saveDirectory+title+"/"+baseName+".png");if(!res) return 0;} LOG(VB_JOBQUEUE, LOG_INFO, "Item Archived OK");return 1;}int NativeArchive::exportVideo(QDomElement &itemNode, const QString &saveDirectory){ QString dbVersion=gCoreContext-> GetSetting("DBSchemaVer", "")
void TransformItem(ImageFileTransform tran=kRotateCW)
Apply transform to an image.
bool GetVisibility() const
Definition: imagemanager.h:406
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.cpp:20
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.
void ShowDetails()
Shows exif info/details about an item.
QPair< int, int > IntPair
static Type kEventType
Definition: mythdialogbox.h:57
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.
if(query.exec() &&query.next())
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)
for(int x=0;x< dirList.count() - 1;x++)
void Copy(bool deleteAfter=false)
Copy marked images to selected dir. If no marked files, use previously marked files....
#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()
int GetResult(void) const
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:80
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.
InfoList m_infoList
Image details overlay.
ImagePtrK GetParent() const
Definition: galleryviews.h:175
static void show(uint8_t *buf, int length)
Definition: ringbuffer.cpp:319
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
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23