MythTV master
gallerythumbview.cpp
Go to the documentation of this file.
1// C++
2#include <algorithm>
3#include <chrono> // for milliseconds
4#include <thread> // for sleep_for
5#include <utility>
6
7// Qt
8#include <QApplication>
9
10// MythTV
11#include "libmythbase/compat.h"
20
21// MythFrontend
22#include "galleryconfig.h"
23#include "gallerythumbview.h"
24
25#define LOC QString("Thumbview: ")
26
27// EXIF tag 0x9286 UserComment can contain garbage
28static QString clean_comment(const QString &comment)
29{
30 QString result;
31 std::ranges::copy_if(std::as_const(comment), std::back_inserter(result),
32 [](QChar x) { return x.isPrint(); } );
33 return result;
34}
35
37class ShellThread: public MThread
38{
39public:
40 ShellThread(QString cmd, QString path)
41 : MThread("Import"), m_command(std::move(cmd)), m_path(std::move(path)) {}
42
43 int GetResult(void) const { return m_result; }
44
45protected:
46 void run() override // MThread
47 {
48 RunProlog();
49
50 QString cmd = QString("cd %1 && %2").arg(m_path, m_command);
51 LOG(VB_GENERAL, LOG_INFO, QString("Executing \"%1\"").arg(cmd));
52
53 m_result = myth_system(cmd);
54
55 LOG(VB_GENERAL, LOG_INFO, QString(" ...with result %1").arg(m_result));
56
57 RunEpilog();
58 }
59
60private:
61 int m_result {0};
62 QString m_command;
63 QString m_path;
64};
65
66
68class TransferThread : public MThread
69{
70 Q_DECLARE_TR_FUNCTIONS(FileTransferWorker);
71public:
72 using TransferMap = QMap<ImagePtrK, QString>;
73 using ImageSet = QSet<ImagePtrK>;
74
76 : MThread("FileTransfer"),
77 m_move(move), m_files(std::move(files)), m_dialog(dialog) {}
78
79 ImageSet GetResult(void) { return m_failed; }
80
81protected:
82 void run() override // MThread
83 {
84 RunProlog();
85
86 QString action = m_move ? tr("Moving") : tr("Copying");
87
88 // Sum file sizes
89 auto keys = m_files.keys();
90 auto add_size = [](int t, const ImagePtrK & im){ return t + im->m_size; };
91 int total = std::accumulate(keys.cbegin(), keys.cend(), 0, add_size);
92
93 int progressSize = 0;
94 for (auto it = m_files.constKeyValueBegin();
95 it != m_files.constKeyValueEnd(); it++)
96 {
97 const ImagePtrK & im = it->first;
98 QString newPath = it->second;
99 // Update progress dialog
100 if (m_dialog)
101 {
102 QString message = QString("%1 %2\n%3")
103 .arg(action, QFileInfo(im->m_url).fileName(),
104 ImageAdapterBase::FormatSize(im->m_size / 1024));
105
106 auto *pue = new ProgressUpdateEvent(progressSize, total, message);
107 QApplication::postEvent(m_dialog, pue);
108 }
109
110 LOG(VB_FILE, LOG_INFO, QString("%2 %3 -> %4")
111 .arg(action, im->m_url, newPath));
112
113 bool success = m_move ? RemoteFile::MoveFile(im->m_url, newPath)
114 : RemoteFile::CopyFile(im->m_url, newPath,
115 false, true);
116 if (!success)
117 {
118 // Flag failures
119 m_failed.insert(im);
120
121 LOG(VB_GENERAL, LOG_ERR,
122 QString("%1: Failed to copy/move %2 -> %3")
123 .arg(objectName(), im->m_url, m_files[im]));
124 }
125
126 progressSize += im->m_size;
127 }
128
129 // Update progress dialog
130 if (m_dialog)
131 {
132 auto *pue =
133 new ProgressUpdateEvent(progressSize, total, tr("Complete"));
134 QApplication::postEvent(m_dialog, pue);
135 }
136
137 RunEpilog();
138 }
139
140private:
141 bool m_move;
145};
146
147
152static void WaitUntilDone(MThread &worker)
153{
154 worker.start();
155 while (!worker.isFinished())
156 {
157 std::this_thread::sleep_for(1ms);
158 QCoreApplication::processEvents();
159 }
160}
161
162
169 : MythScreenType(parent, name),
170 m_popupStack(*GetMythMainWindow()->GetStack("popup stack")),
171 m_mgr(ImageManagerFe::getInstance()),
172 // This screen uses a single fixed view (Parent dir, ordered dirs, ordered images)
173 m_view(new DirectoryView(kOrdered)),
174 m_infoList(*this),
175 // Start in edit mode unless a password exists
176 m_editsAllowed(gCoreContext->GetSetting("GalleryPassword").isEmpty())
177{
178 // Hide hidden when edits disallowed
179 if (!m_editsAllowed)
180 m_mgr.SetVisibility(false);
181}
182
183
188{
189 LOG(VB_GUI, LOG_DEBUG, LOC + "Exiting Gallery");
190 delete m_view;
191}
192
193
198{
199 LOG(VB_GUI, LOG_DEBUG, LOC + "Closing Gallery");
200
202
203 // Cleanup local devices
205
206 // Cleanup view
207 m_view->Clear();
208
210}
211
212
217{
218 if (!LoadWindowFromXML("image-ui.xml", "gallery", this))
219 return false;
220
221 // Determine zoom levels supported by theme
222 // images0 must exist; images1, images2 etc. are optional and enable zoom
223 int zoom = 0;
224 QString name = QString("images%1").arg(zoom++);
225 auto *widget = dynamic_cast<MythUIButtonList *>(this->GetChild(name));
226 while (widget)
227 {
228 m_zoomWidgets.append(widget);
229 widget->SetVisible(false);
230
231 name = QString("images%1").arg(zoom++);
232 widget = dynamic_cast<MythUIButtonList *>(this->GetChild(name));
233 }
234
235 if (m_zoomWidgets.isEmpty())
236 {
237 LOG(VB_GENERAL, LOG_ERR, LOC + "Screen 'Gallery' is missing 'images0'");
238 return false;
239 }
240 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen 'Gallery' found %1 zoom levels")
241 .arg(m_zoomWidgets.size()));
242
243 // File details list is managed elsewhere
244 if (!m_infoList.Create(false))
245 {
246 LOG(VB_GENERAL, LOG_ERR, LOC + "Cannot load 'Info buttonlist'");
247 return false;
248 }
249
250 UIUtilW::Assign(this, m_captionText, "caption");
251 UIUtilW::Assign(this, m_emptyText, "noimages");
252 UIUtilW::Assign(this, m_positionText, "position");
253 UIUtilW::Assign(this, m_crumbsText, "breadcrumbs");
254 UIUtilW::Assign(this, m_hideFilterText, "hidefilter");
255 UIUtilW::Assign(this, m_typeFilterText, "typefilter");
256 UIUtilW::Assign(this, m_scanProgressText, "scanprogresstext");
257 UIUtilW::Assign(this, m_scanProgressBar, "scanprogressbar");
258
263
265
266 // Initialise list widget with appropriate zoom level for this theme.
267 m_zoomLevel = gCoreContext->GetNumSetting("GalleryZoomLevel", 0);
269
270 return true;
271}
272
273
279{
280 if (GetFocusWidget()->keyPressEvent(event))
281 return true;
282
283 QStringList actions;
284 bool handled = GetMythMainWindow()->TranslateKeyPress("Images", event, actions);
285
286 for (int i = 0; i < actions.size() && !handled; i++)
287 {
288 const QString& action = actions[i];
289 handled = true;
290
291 if (action == "MENU")
292 {
293 MenuMain();
294 }
295 else if (action == "INFO")
296 {
297 ShowDetails();
298 }
299 else if (action == "ZOOMIN")
300 {
301 ZoomIn();
302 }
303 else if (action == "ZOOMOUT")
304 {
305 ZoomOut();
306 }
307 else if (action == "ROTRIGHT")
308 {
309 RotateCW();
310 }
311 else if (action == "ROTLEFT")
312 {
313 RotateCCW();
314 }
315 else if (action == "FLIPHORIZONTAL")
316 {
318 }
319 else if (action == "FLIPVERTICAL")
320 {
321 FlipVertical();
322 }
323 else if (action == "COVER")
324 {
326 if (m_editsAllowed && im)
327 {
328 if (im == m_view->GetParent())
329 {
330 // Reset dir
331 m_mgr.SetCover(im->m_id, 0);
332 }
333 else
334 {
335 // Set parent cover
336 m_mgr.SetCover(im->m_parentId, im->m_id);
337 }
338 }
339 }
340 else if (action == "PLAY")
341 {
342 Slideshow();
343 }
344 else if (action == "RECURSIVESHOW")
345 {
347 if (im && im->IsDirectory())
349 }
350 else if (action == "MARK")
351 {
353 if (m_editsAllowed && im && im != m_view->GetParent())
354 DoMarkItem(!m_view->IsMarked(im->m_id));
355 }
356 else if (action == "ESCAPE" && !GetMythMainWindow()->IsExitingToMain())
357 {
358 // Exit info list, if shown
359 handled = m_infoList.Hide();
360
361 // Ascend the tree unless parent is root,
362 // or a device and multiple devices/imports exist
363 if (!handled)
364 {
365 ImagePtrK node = m_view->GetParent();
366 if (node && node->m_id != GALLERY_DB_ID
367 && (!node->IsDevice() || m_mgr.DeviceCount() > 0))
368 handled = DirSelectUp();
369 }
370 }
371 else
372 {
373 handled = false;
374 }
375 }
376
377 if (!handled)
378 handled = MythScreenType::keyPressEvent(event);
379
380 return handled;
381}
382
383
389{
390
391 if (event->type() == MythEvent::kMythEventMessage)
392 {
393 auto *me = dynamic_cast<MythEvent *>(event);
394 if (me == nullptr)
395 return;
396
397 const QString& mesg = me->Message();
398 QStringList extra = me->ExtraDataList();
399
400 // Internal messages contain a hostname. Ignore other FE messages
401 QStringList token = mesg.split(' ');
402 if (token.size() >= 2 && token[1] != gCoreContext->GetHostName())
403 return;
404
405 if (token[0] == "IMAGE_METADATA")
406 {
407 int id = extra[0].toInt();
408 ImagePtrK selected = m_view->GetSelected();
409
410 if (selected && selected->m_id == id)
411 m_infoList.Display(*selected, extra.mid(1));
412 }
413 else if (token[0] == "THUMB_AVAILABLE")
414 {
415 int id = extra[0].toInt();
416
417 // Note existance of all thumbs
418 m_thumbExists.insert(id);
419
420 // Get all buttons waiting for this thumbnail
421 QList<ThumbLocation> affected = m_pendingMap.values(id);
422
423 // Only concerned with thumbnails we've requested
424 if (affected.isEmpty())
425 return;
426
427 LOG(VB_GENERAL, LOG_DEBUG, LOC +
428 QString("Rx %1 : %2").arg(token[0], extra.join(",")));
429
430 // Thumb url was cached when request was sent
431 QString url = m_view->GetCachedThumbUrl(id);
432
433 // Set thumbnail for each button now it exists
434 for (const ThumbLocation & location : std::as_const(affected))
435 {
436 MythUIButtonListItem *button = location.first;
437 int index = location.second;
438
439 auto im = button->GetData().value<ImagePtrK>();
440 if (im)
441 UpdateThumbnail(button, im, url, index);
442 }
443
444 // Cancel pending request
445 m_pendingMap.remove(id);
446 }
447 else if (token[0] == "IMAGE_DB_CHANGED")
448 {
449 // Expects csv list of deleted ids, csv list of changed ids
450 LOG(VB_GENERAL, LOG_DEBUG, LOC +
451 QString("Rx %1 : %2").arg(token[0], extra.join(",")));
452
453 if (!extra.isEmpty())
454 {
455 QStringList idDeleted =
456 extra[0].split(",", Qt::SkipEmptyParts);
457 RemoveImages(idDeleted);
458 }
459 if (extra.size() >= 2)
460 {
461 QStringList idChanged =
462 extra[1].split(",", Qt::SkipEmptyParts);
463 RemoveImages(idChanged, false);
464 }
465
466 // Refresh display
468 }
469 else if (token[0] == "IMAGE_DEVICE_CHANGED")
470 {
471 // Expects list of url prefixes
472 LOG(VB_GENERAL, LOG_DEBUG, LOC +
473 QString("Rx %1 : %2").arg(token[0], extra.join(",")));
474
475 // Clear everything. Local devices will be rebuilt
476 m_view->Clear();
477 m_thumbExists.clear();
478
479 // Remove thumbs & images from image cache using supplied prefixes
480 for (const QString & url : std::as_const(extra))
482
483 // Refresh display
485 }
486 else if (token[0] == "IMAGE_SCAN_STATUS" && extra.size() == 3)
487 {
488 // Expects scanner id, scanned#, total#
489 UpdateScanProgress(extra[0], extra[1].toInt(), extra[2].toInt());
490 }
491 }
492 else if (event->type() == DialogCompletionEvent::kEventType)
493 {
494 auto *dce = (DialogCompletionEvent *)event;
495
496 QString resultid = dce->GetId();
497 int buttonnum = dce->GetResult();
498
499 if (resultid == "FileRename")
500 {
501 QString newName = dce->GetResultText();
503 {
505 newName);
506 if (!err.isEmpty())
507 ShowOkPopup(err);
508 }
509 }
510 else if (resultid == "MakeDir")
511 {
513 {
514 // Prohibit subtrees
515 QString name = dce->GetResultText();
516 QString err = name.contains("/")
517 ? tr("Invalid Name")
519 QStringList(name));
520 if (!err.isEmpty())
521 ShowOkPopup(err);
522 }
523 }
524 else if (resultid == "SlideOrderMenu")
525 {
526 SlideOrderType slideOrder = kOrdered;
527
528 switch (buttonnum)
529 {
530 case 0: slideOrder = kOrdered; break;
531 case 1: slideOrder = kShuffle; break;
532 case 2: slideOrder = kRandom; break;
533 case 3: slideOrder = kSeasonal; break;
534 }
535 gCoreContext->SaveSetting("GallerySlideOrder", slideOrder);
536 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Order %1").arg(slideOrder));
537 }
538 else if (resultid == "ImageCaptionMenu")
539 {
540 ImageCaptionType captions = kNoCaption;
541
542 switch (buttonnum)
543 {
544 case 0: captions = kNameCaption; break;
545 case 1: captions = kDateCaption; break;
546 case 2: captions = kUserCaption; break;
547 case 3: captions = kNoCaption; break;
548 }
549 gCoreContext->SaveSetting("GalleryImageCaption", captions);
551 }
552 else if (resultid == "DirCaptionMenu")
553 {
554 ImageCaptionType captions = kNoCaption;
555
556 switch (buttonnum)
557 {
558 case 0: captions = kNameCaption; break;
559 case 1: captions = kDateCaption; break;
560 case 2: captions = kNoCaption; break;
561 }
562 gCoreContext->SaveSetting("GalleryDirCaption", captions);
564 }
565 else if (resultid == "Password")
566 {
567 QString password = dce->GetResultText();
568 m_editsAllowed = (password == gCoreContext->GetSetting("GalleryPassword"));
569 }
570 else if (buttonnum == 1)
571 {
572 // Confirm current file deletion
573 QString err;
574 if (resultid == "ConfirmDelete" && m_menuState.m_selected)
575 {
577 err = m_mgr.DeleteFiles(ids);
578 }
579 // Confirm marked file deletion
580 else if (resultid == "ConfirmDeleteMarked")
581 {
583 }
584 else
585 {
586 return;
587 }
588
589 if (!err.isEmpty())
590 ShowOkPopup(err);
591 }
592 }
593}
594
595
601void GalleryThumbView::RemoveImages(const QStringList &ids, bool deleted)
602{
603 for (const QString & id : std::as_const(ids))
604 {
605 // Remove image from view
606 QStringList urls = m_view->RemoveImage(id.toInt(), deleted);
607 // Cleanup url lookup
608 m_thumbExists.remove(id.toInt());
609
610 // Remove thumbs & images from image cache
611 for (const QString & url : std::as_const(urls))
612 {
613 LOG(VB_FILE, LOG_DEBUG, LOC +
614 QString("Clearing image cache of '%1'").arg(url));
615
617 }
618 }
619}
620
621
626{
627 // Detect any running BE scans
628 // Expects OK, scanner id, current#, total#
629 QStringList message = ImageManagerFe::ScanQuery();
630 if (message.size() == 4 && message[0] == "OK")
631 {
632 UpdateScanProgress(message[1], message[2].toInt(), message[3].toInt());
633 }
634
635 // Only receive events after device/scan status has been established
637
638 // Start at Root if devices exist. Otherwise go straight to SG node
640
641 LoadData(start);
642}
643
644
650{
652
653 // Load view for parent directory
654 if (m_view->LoadFromDb(parent))
655 {
656 m_imageList->SetVisible(true);
657 if (m_emptyText)
658 {
659 m_emptyText->SetVisible(false);
661 }
662
663 // Construct the buttonlist
665 }
666 else
667 {
669 m_imageList->SetVisible(false);
670 if (m_emptyText)
671 {
672 m_emptyText->SetVisible(true);
673 m_emptyText->SetText(tr("No images found.\n"
674 "Scan storage group using menu,\n"
675 "or insert/mount local media.\n"));
676 }
677 }
678}
679
680
685{
687 m_pendingMap.clear();
688
689 // Get parent & all children
690 ImageListK nodes = m_view->GetAllNodes();
691 ImagePtrK selected = m_view->GetSelected();
692
693 // go through the entire list and update
694 for (const ImagePtrK & im : std::as_const(nodes))
695 {
696 if (im)
697 {
698 // Data must be set by constructor: First item is automatically
699 // selected and must have data available for selection event, as
700 // subsequent reselection of same item will always fail.
701 auto *item = new MythUIButtonListItem(m_imageList, "",
702 QVariant::fromValue(im));
703
704 item->setCheckable(true);
705 item->setChecked(MythUIButtonListItem::NotChecked);
706
707 // assign and display all information about
708 // the current item, like title and subdirectory count
709 UpdateImageItem(item);
710
711 // Treat parent differently
712 if (im == nodes[0])
713 {
714 // Only non-root parents can ascend
715 if (im->m_id != GALLERY_DB_ID)
716 item->DisplayState("upfolder", "parenttype");
717 }
718 else if (im == selected)
719 {
720 // Reinstate the active button item. Note this would fail for parent
722 }
723 }
724 }
725}
726
727
733{
734 auto im = item->GetData().value<ImagePtrK >();
735 if (!im)
736 return;
737
738 // Allow themes to distinguish between roots, folders, pics, videos
739 switch (im->m_type)
740 {
741 case kDevice:
742 case kCloneDir:
743 case kDirectory:
744 if (im->m_dirCount > 0)
745 {
746 item->SetText(QString("%1/%2")
747 .arg(im->m_fileCount).arg(im->m_dirCount),
748 "childcount");
749 }
750 else
751 {
752 item->SetText(QString::number(im->m_fileCount), "childcount");
753 }
754
755 item->DisplayState(im->IsDevice() ? "device" : "subfolder", "buttontype");
756 break;
757
758 case kImageFile:
759 item->DisplayState("image", "buttontype");
760 break;
761
762 case kVideoFile:
763 item->DisplayState("video", "buttontype");
764 break;
765
766 default:
767 break;
768 }
769
770 // Allow theme to distinguish visible/hidden nodes
771 QString hideState = (im->m_isHidden) ? "hidden" : "visible";
772 item->DisplayState(hideState, "buttonstate");
773
774 // Caption
775 QString text;
777 im->IsFile() ? "GalleryImageCaption"
778 : "GalleryDirCaption");
779 switch (show)
780 {
781 case kNameCaption: text = m_mgr.CrumbName(*im); break;
782 case kDateCaption: text = m_mgr.ShortDateOf(im); break;
783 case kUserCaption: text = clean_comment(im->m_comment); break;
784 default:
785 case kNoCaption: text = ""; break;
786 }
787 item->SetText(text);
788
789 // Set marked state
791 = m_view->IsMarked(im->m_id)
794
795 item->setChecked(state);
796
797 // Thumbnails required
798 ImageIdList request;
799
800 if (im->m_thumbNails.size() == 1)
801 {
802 // Single thumbnail
803 QString url = CheckThumbnail(item, im, request, 0);
804
805 if (!url.isEmpty())
806 UpdateThumbnail(item, im, url, 0);
807 }
808 else
809 {
810 // Dir showing up to 4 thumbs. Set them all at same time
811 InfoMap thumbMap;
812 for (int index = 0; index < im->m_thumbNails.size(); ++index)
813 {
814 QString url = CheckThumbnail(item, im, request, index);
815 if (!url.isEmpty())
816 thumbMap.insert(QString("thumbimage%1").arg(index), url);
817 }
818 if (!thumbMap.isEmpty())
819 item->SetImageFromMap(thumbMap);
820 }
821
822 // Request creation/verification of unknown thumbnails.
823 if (!request.isEmpty())
824 m_mgr.CreateThumbnails(request, im->IsDirectory());
825}
826
827
840 ImageIdList &request, int index)
841{
842 ThumbPair thumb(im->m_thumbNails.at(index));
843 int id = thumb.first;
844
845 if (m_thumbExists.contains(id))
846 return thumb.second;
847
848 // Request BE thumbnail check if it is not already pending
849 if (!m_pendingMap.contains(id))
850 request << id;
851
852 // Note this button is awaiting an update
853 m_pendingMap.insert(id, qMakePair(item, index));
854
855 return "";
856}
857
858
867 const ImagePtrK& im, const QString &url,
868 int index)
869{
870 if (im->m_thumbNails.size() == 1)
871 {
872 // Pics, dirs & videos use separate widgets
873 switch (im->m_type)
874 {
875 case kImageFile: button->SetImage(url); break;
876 case kVideoFile: button->SetImage(url, "videoimage"); break;
877 default: button->SetImage(url, "folderimage"); break;
878 }
879 }
880 else
881 {
882 // Dir with 4 thumbnails
883 button->SetImage(url, QString("thumbimage%1").arg(index));
884 }
885}
886
887
895void GalleryThumbView::UpdateScanProgress(const QString &scanner,
896 int current, int total)
897{
898 // Scan update
899 m_scanProgress.insert(scanner, qMakePair(current, total));
900
901 // Detect end of this scan
902 if (current >= total)
903 {
904 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Finished %1 %2/%3")
905 .arg(scanner).arg(current).arg(total));
906
907 // Mark inactive scanner
908 m_scanActive.remove(scanner);
909
910 // Detect end of last scan
911 if (m_scanActive.isEmpty())
912 {
914 {
917 }
919 {
922 }
923
924 m_scanProgress.clear();
925
926 return;
927 }
928 }
929 else
930 {
931 // Detect first scan update
932 if (m_scanActive.isEmpty())
933 {
934 // Show progressbar when first scan starts
936 {
939 }
942 }
943
944 if (!m_scanActive.contains(scanner))
945 {
946 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Started %1 %2/%3")
947 .arg(scanner).arg(current).arg(total));
948
949 // Mark active scanner
950 m_scanActive.insert(scanner);
951 }
952 }
953
954 // Aggregate all running scans
955 int currentAgg = 0;
956 int totalAgg = 0;
957 for (IntPair scan : std::as_const(m_scanProgress))
958 {
959 currentAgg += scan.first;
960 totalAgg += scan.second;
961 }
962
964 {
965 m_scanProgressBar->SetUsed(currentAgg);
966 m_scanProgressBar->SetTotal(totalAgg);
967 }
969 m_scanProgressText->SetText(tr("%L1 of %L3").arg(currentAgg).arg(totalAgg));
970}
971
972
977{
978 if (m_positionText)
980
981 if (m_captionText)
983
984 if (m_crumbsText)
986
989
992}
993
994
1000{
1001 auto im = item->GetData().value<ImagePtrK >();
1002 if (im)
1003 {
1004 // update the position in the node list
1005 m_view->Select(im->m_id);
1006
1007 // show the name/path of the image
1008 if (m_crumbsText)
1009 m_crumbsText->SetText(m_mgr.CrumbName(*im, true));
1010
1011 if (m_captionText)
1012 {
1013 // show the date & comment of non-root nodes
1014 QStringList text;
1015 if (im->m_id != GALLERY_DB_ID)
1016 {
1017 if (im->IsFile() || im->IsDevice())
1018 text << ImageManagerFe::LongDateOf(im);
1019
1020 QString comment = clean_comment(im->m_comment);
1021 if (!comment.isEmpty())
1022 text << comment;
1023 }
1024 m_captionText->SetText(text.join(" - "));
1025 }
1026
1027 if (m_hideFilterText)
1028 {
1029 m_hideFilterText->SetText(m_mgr.GetVisibility() ? tr("Hidden") : "");
1030 }
1031
1032 if (m_typeFilterText)
1033 {
1034 QString text = "";
1035 switch (m_mgr.GetType())
1036 {
1037 case kPicAndVideo : text = ""; break;
1038 case kPicOnly : text = tr("Pictures"); break;
1039 case kVideoOnly : text = tr("Videos"); break;
1040 }
1042 }
1043
1044 // show the position of the image
1045 if (m_positionText)
1047
1048 // Update any file details information
1049 m_infoList.Update(im);
1050 }
1051}
1052
1053
1058{
1059 // Create the main menu
1060 auto *menu = new MythMenu(tr("Gallery Options"), this, "mainmenu");
1061
1062 // Menu options depend on the marked files and the current node
1064
1066 {
1067 if (m_editsAllowed)
1068 {
1070 MenuPaste(menu);
1073 }
1075 MenuShow(menu);
1076 if (!m_editsAllowed)
1077 menu->AddItem(tr("Enable Edits"), &GalleryThumbView::ShowPassword);
1078 }
1079
1080 // Depends on current status of backend scanner - string(number(isBackend()))
1081 if (m_scanActive.contains("1"))
1082 menu->AddItem(tr("Stop Scan"), &GalleryThumbView::StopScan);
1083 else
1084 menu->AddItem(tr("Scan Storage Group"), &GalleryThumbView::StartScan);
1085
1086 menu->AddItem(tr("Settings"), &GalleryThumbView::ShowSettings);
1087
1088 auto *popup = new MythDialogBox(menu, &m_popupStack, "menuPopup");
1089 if (popup->Create())
1090 m_popupStack.AddScreen(popup);
1091 else
1092 delete popup;
1093}
1094
1095
1101{
1102 ImagePtrK parent = m_view->GetParent();
1103
1104 if (m_menuState.m_childCount == 0 || parent.isNull())
1105 return;
1106
1107 QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1108 auto *menu = new MythMenu(title, this, "markmenu");
1109
1110 // Mark/unmark selected
1111 if (m_menuState.m_selected->IsFile())
1112 {
1114 menu->AddItem(tr("Unmark File"), &GalleryThumbView::UnmarkItem);
1115 else
1116 menu->AddItem(tr("Mark File"), &GalleryThumbView::MarkItem);
1117 }
1118 // Cannot mark/unmark parent dir from this level
1119 else if (!m_menuState.m_selected->IsDevice()
1120 && m_menuState.m_selected != parent)
1121 {
1123 menu->AddItem(tr("Unmark Directory"), &GalleryThumbView::UnmarkItem);
1124 else
1125 menu->AddItem(tr("Mark Directory"), &GalleryThumbView::MarkItem);
1126 }
1127
1128 if (parent->m_id != GALLERY_DB_ID)
1129 {
1130 // Mark All if unmarked files exist
1132 menu->AddItem(tr("Mark All"), &GalleryThumbView::MarkAll);
1133
1134 // Unmark All if marked files exist
1135 if (!m_menuState.m_markedId.isEmpty())
1136 {
1137 menu->AddItem(tr("Unmark All"), &GalleryThumbView::UnmarkAll);
1138 menu->AddItem(tr("Invert Marked"), &GalleryThumbView::MarkInvertAll);
1139 }
1140 }
1141
1142 if (menu->IsEmpty())
1143 delete menu;
1144 else
1145 mainMenu->AddItem(tr("Mark"), nullptr, menu);
1146}
1147
1148
1154{
1155 // Can only copy/move into non-root dirs
1156 if (m_menuState.m_selected->IsDirectory()
1158 {
1159 // Operate on current marked files, if any
1161 if (files.isEmpty())
1163 if (files.isEmpty())
1164 return;
1165
1166 QString title = tr("%L1 marked").arg(files.size());
1167
1168 auto *menu = new MythMenu(title, this, "pastemenu");
1169
1170 menu->AddItem(tr("Move Marked Into"), &GalleryThumbView::Move);
1171 menu->AddItem(tr("Copy Marked Into"), qOverload<>(&GalleryThumbView::Copy));
1172
1173 mainMenu->AddItem(tr("Paste"), nullptr, menu);
1174 }
1175}
1176
1177
1183{
1184 // Operate on marked files, if any, otherwise selected node
1185 if (!m_menuState.m_markedId.isEmpty())
1186 {
1187 QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1188
1189 auto *menu = new MythMenu(title, this, "");
1190
1191 menu->AddItem(tr("Rotate Marked CW"), &GalleryThumbView::RotateCWMarked);
1192 menu->AddItem(tr("Rotate Marked CCW"), &GalleryThumbView::RotateCCWMarked);
1193 menu->AddItem(tr("Flip Marked Horizontal"), &GalleryThumbView::FlipHorizontalMarked);
1194 menu->AddItem(tr("Flip Marked Vertical"), &GalleryThumbView::FlipVerticalMarked);
1195 menu->AddItem(tr("Reset Marked to Exif"), &GalleryThumbView::ResetExifMarked);
1196
1197 mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1198 }
1199 else if (m_menuState.m_selected->IsFile())
1200 {
1201 auto *menu = new MythMenu(m_menuState.m_selected->m_baseName, this, "");
1202
1203 menu->AddItem(tr("Rotate CW"), &GalleryThumbView::RotateCW);
1204 menu->AddItem(tr("Rotate CCW"), &GalleryThumbView::RotateCCW);
1205 menu->AddItem(tr("Flip Horizontal"), &GalleryThumbView::FlipHorizontal);
1206 menu->AddItem(tr("Flip Vertical"), &GalleryThumbView::FlipVertical);
1207 menu->AddItem(tr("Reset to Exif"), &GalleryThumbView::ResetExif);
1208
1209 mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1210 }
1211}
1212
1213
1219{
1220 MythMenu *menu = nullptr;
1221 ImagePtrK selected = m_menuState.m_selected;
1222
1223 // Operate on current marked files, if any
1224 if (!m_menuState.m_markedId.empty())
1225 {
1226 QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1227
1228 menu = new MythMenu(title, this, "actionmenu");
1229
1230 // Only offer Hide/Unhide if relevant
1232 menu->AddItem(tr("Hide Marked"), &GalleryThumbView::HideMarked);
1234 menu->AddItem(tr("Unhide Marked"), &GalleryThumbView::UnhideMarked);
1235
1236 menu->AddItem(tr("Delete Marked"), &GalleryThumbView::DeleteMarked);
1237 }
1238 else
1239 {
1240 // Operate on selected file/dir
1241 menu = new MythMenu(selected->m_baseName, this, "actionmenu");
1242
1243 // Prohibit actions on devices and parent dirs
1244 if (!selected->IsDevice() && selected != m_view->GetParent())
1245 {
1246 if (selected->m_isHidden)
1247 menu->AddItem(tr("Unhide"), &GalleryThumbView::Unhide);
1248 else
1249 menu->AddItem(tr("Hide"), &GalleryThumbView::HideItem);
1250
1251 menu->AddItem(tr("Use as Cover"), &GalleryThumbView::SetCover);
1252 menu->AddItem(tr("Delete"), &GalleryThumbView::DeleteItem);
1253 menu->AddItem(tr("Rename"), &GalleryThumbView::ShowRenameInput);
1254 }
1255 else if (selected->m_userThumbnail)
1256 {
1257 menu->AddItem(tr("Reset Cover"), &GalleryThumbView::ResetCover);
1258 }
1259 }
1260
1261 // Can only mkdir in a non-root dir
1262 if (selected->IsDirectory()
1263 && selected->m_id != GALLERY_DB_ID)
1264 menu->AddItem(tr("Create Directory"), &GalleryThumbView::MakeDir);
1265
1266 // Only show import command on root, when defined
1267 if (selected->m_id == GALLERY_DB_ID
1268 && !gCoreContext->GetSetting("GalleryImportCmd").isEmpty())
1269 menu->AddItem(tr("Import"), &GalleryThumbView::Import);
1270
1271 // Only show eject when devices (excluding import) exist
1272 if (selected->IsDevice() && selected->IsLocal())
1273 menu->AddItem(tr("Eject media"), &GalleryThumbView::Eject);
1274
1275 if (menu->IsEmpty())
1276 delete menu;
1277 else
1278 mainMenu->AddItem(tr("Actions"), nullptr, menu);
1279}
1280
1281
1287{
1288 int order = gCoreContext->GetNumSetting("GallerySlideOrder", kOrdered);
1289
1290 QString ordering;
1291 switch (order)
1292 {
1293 case kShuffle : ordering = tr("Shuffled"); break;
1294 case kRandom : ordering = tr("Random"); break;
1295 case kSeasonal : ordering = tr("Seasonal"); break;
1296 default:
1297 case kOrdered : ordering = tr("Ordered"); break;
1298 }
1299
1300 auto *menu = new MythMenu(tr("Slideshow") + " (" + ordering + ")",
1301 this, "SlideshowMenu");
1302
1303 // Use selected dir or parent, if image selected
1304 if (m_menuState.m_selected->IsDirectory())
1305 {
1306 if (m_menuState.m_selected->m_fileCount > 0)
1307 menu->AddItem(tr("Directory"), &GalleryThumbView::Slideshow);
1308
1309 if (m_menuState.m_selected->m_dirCount > 0)
1310 menu->AddItem(tr("Recursive"), &GalleryThumbView::RecursiveSlideshow);
1311 }
1312 else
1313 {
1314 menu->AddItem(tr("Current Directory"), &GalleryThumbView::Slideshow);
1315 }
1316
1317 auto *orderMenu = new MythMenu(tr("Slideshow Order"), this, "SlideOrderMenu");
1318
1319 orderMenu->AddItem(tr("Ordered"), nullptr, nullptr, order == kOrdered);
1320 orderMenu->AddItem(tr("Shuffled"), nullptr, nullptr, order == kShuffle);
1321 orderMenu->AddItem(tr("Random"), nullptr, nullptr, order == kRandom);
1322 orderMenu->AddItem(tr("Seasonal"), nullptr, nullptr, order == kSeasonal);
1323
1324 menu->AddItem(tr("Change Order"), nullptr, orderMenu);
1325
1326 if (gCoreContext->GetBoolSetting("GalleryRepeat", false))
1327 menu->AddItem(tr("Turn Repeat Off"), &GalleryThumbView::RepeatOff);
1328 else
1329 menu->AddItem(tr("Turn Repeat On"), &GalleryThumbView::RepeatOn);
1330
1331 mainMenu->AddItem(tr("Slideshow"), nullptr, menu);
1332}
1333
1334
1340{
1341 auto *menu = new MythMenu(tr("Show Options"), this, "showmenu");
1342
1343 int type = m_mgr.GetType();
1344 if (type == kPicAndVideo)
1345 {
1346 menu->AddItem(tr("Hide Pictures"), &GalleryThumbView::HidePictures);
1347 menu->AddItem(tr("Hide Videos"), &GalleryThumbView::HideVideos);
1348 }
1349 else
1350 {
1351 menu->AddItem(type == kPicOnly ? tr("Show Videos") : tr("Show Pictures"),
1353 }
1354
1355 int show = gCoreContext->GetNumSetting("GalleryImageCaption");
1356 auto *captionMenu = new MythMenu(tr("Image Captions"), this,
1357 "ImageCaptionMenu");
1358
1359 captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1360 captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1361 captionMenu->AddItem(tr("Comment"), nullptr, nullptr, show == kUserCaption);
1362 captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1363
1364 menu->AddItem(tr("Image Captions"), nullptr, captionMenu);
1365
1366 show = gCoreContext->GetNumSetting("GalleryDirCaption");
1367 captionMenu = new MythMenu(tr("Directory Captions"), this, "DirCaptionMenu");
1368
1369 captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1370 captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1371 captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1372
1373 menu->AddItem(tr("Directory Captions"), nullptr, captionMenu);
1374
1375 if (m_editsAllowed)
1376 {
1377 if (m_mgr.GetVisibility())
1378 menu->AddItem(tr("Hide Hidden Items"), &GalleryThumbView::HideHidden);
1379 else
1380 menu->AddItem(tr("Show Hidden Items"), &GalleryThumbView::ShowHidden);
1381 }
1382
1383 if (m_zoomLevel > 0)
1384 menu->AddItem(tr("Zoom Out"), &GalleryThumbView::ZoomOut);
1385 if (m_zoomLevel < m_zoomWidgets.size() - 1)
1386 menu->AddItem(tr("Zoom In"), &GalleryThumbView::ZoomIn);
1387
1388 QString details = m_infoList.GetState() == kNoInfo
1389 ? tr("Show Details") : tr("Hide Details");
1390
1391 menu->AddItem(details, &GalleryThumbView::ShowDetails);
1392
1393 mainMenu->AddItem(tr("Show"), nullptr, menu);
1394}
1395
1396
1402{
1403 // Only update selection if image is currently displayed
1404 if (m_view->Select(id, -1))
1406}
1407
1408
1414{
1415 if (!item)
1416 return;
1417
1418 auto im = item->GetData().value<ImagePtrK>();
1419 if (!im)
1420 return;
1421
1422 switch (im->m_type)
1423 {
1424 case kDevice:
1425 case kCloneDir:
1426 case kDirectory:
1427 if (im == m_view->GetParent())
1428 DirSelectUp();
1429 else
1430 DirSelectDown();
1431 break;
1432
1433 case kImageFile:
1434 case kVideoFile:
1436 };
1437}
1438
1439
1445{
1446 QString err = m_mgr.ScanImagesAction(start);
1447 if (!err.isEmpty())
1448 ShowOkPopup(err);
1449}
1450
1451
1457{
1458 ImagePtrK selected = m_view->GetSelected();
1459 if (!selected)
1460 return;
1461
1463 auto *slide = new GallerySlideView(mainStack, "galleryslideview",
1465 if (slide->Create())
1466 {
1467 mainStack->AddScreen(slide);
1468
1469 // Update selected item when slideshow exits
1470 connect(slide, &GallerySlideView::ImageSelected,
1472
1473 if (selected->IsDirectory())
1474 {
1475 // Show selected dir
1476 slide->Start(mode, selected->m_id);
1477 }
1478 else
1479 {
1480 // Show current dir starting at selection
1481 slide->Start(mode, selected->m_parentId, selected->m_id);
1482 }
1483 }
1484 else
1485 {
1486 delete slide;
1487 }
1488}
1489
1490
1495{
1496 ImagePtrK im = m_view->GetParent();
1497 if (im)
1498 {
1499 LOG(VB_GUI, LOG_DEBUG, LOC +
1500 QString("Going up from %1").arg(im->m_filePath));
1501
1502 // Select the upfolder in the higher dir
1503 m_view->Select(im->m_id);
1504
1505 // Create tree rooted at parent of the kUpFolder directory node
1506 LoadData(im->m_parentId);
1507 }
1508 return true;
1509}
1510
1511
1516{
1518 if (im)
1519 {
1520 LOG(VB_GUI, LOG_DEBUG, LOC +
1521 QString("Going down to %1").arg(im->m_filePath));
1522
1523 // Create tree rooted at selected item
1524 LoadData(im->m_id);
1525 }
1526}
1527
1528
1534{
1536 if (im)
1537 {
1538 // Mark/unmark selected item
1539 m_view->Mark(im->m_id, mark);
1540
1541 // Redisplay buttonlist as a parent dir may have been unmarked
1543 }
1544}
1545
1546
1552{
1553 if (mark)
1554 m_view->MarkAll();
1555 else
1557
1558 // Redisplay buttonlist
1560}
1561
1562
1567{
1569
1570 // Redisplay buttonlist
1572}
1573
1574
1580{
1582 if (im && m_editsAllowed)
1583 {
1584 ImageIdList ids;
1585 ids.append(im->m_id);
1586 QString err = m_mgr.ChangeOrientation(transform, ids);
1587 if (!err.isEmpty())
1588 ShowOkPopup(err);
1589 }
1590}
1591
1592
1598{
1599 QString err = m_mgr.ChangeOrientation(transform, m_menuState.m_markedId);
1600 if (!err.isEmpty())
1601 ShowOkPopup(err);
1602}
1603
1604
1610{
1612 {
1613 ImageIdList ids;
1614 ids.append(m_menuState.m_selected->m_id);
1615
1616 QString err = m_mgr.HideFiles(hide, ids);
1617 if (!err.isEmpty())
1618 {
1619 ShowOkPopup(err);
1620 }
1621 else if (hide && !m_mgr.GetVisibility())
1622 {
1623 // Unmark invisible file
1624 m_view->Mark(m_menuState.m_selected->m_id, false);
1625 }
1626 }
1627}
1628
1629
1635{
1636 QString err = m_mgr.HideFiles(hide, m_menuState.m_markedId);
1637 if (!err.isEmpty())
1638 {
1639 ShowOkPopup(err);
1640 }
1641 else if (hide && !m_mgr.GetVisibility())
1642 {
1643 // Unmark invisible files
1644 for (int id : std::as_const(m_menuState.m_markedId))
1645 m_view->Mark(id, false);
1646 }
1647}
1648
1649
1654{
1656 ShowDialog(tr("Do you want to delete\n%1 ?")
1657 .arg(m_menuState.m_selected->m_baseName), "ConfirmDelete");
1658}
1659
1660
1665{
1666 ShowDialog(tr("Do you want to delete all marked files ?"),
1667 "ConfirmDeleteMarked");
1668}
1669
1670
1675{
1676 // Show settings dialog
1677 auto *config = new GallerySettings(m_editsAllowed);
1679 auto *ssd = new StandardSettingDialog(mainStack, "gallerysettings", config);
1680 if (!ssd->Create())
1681 {
1682 delete ssd;
1683 return;
1684 }
1685
1686 mainStack->AddScreen(ssd);
1687
1688 // Effect setting changes when dialog saves on exit
1689
1690 connect(config, &GallerySettings::ClearDbPressed,
1692
1693 connect(config, &GallerySettings::OrderChanged,
1694 this, [this]()
1695 {
1696 // Update db view, reset cover cache & reload
1697 int sortIm = gCoreContext->GetNumSetting("GalleryImageOrder");
1698 int sortDir = gCoreContext->GetNumSetting("GalleryDirOrder");
1699 m_mgr.SetSortOrder(sortIm, sortDir);
1700 m_view->ClearCache();
1702 });
1703
1704 connect(config, &GallerySettings::DateChanged,
1705 this, [this]()
1706 {
1707 QString date = gCoreContext->GetSetting("GalleryDateFormat");
1708 m_mgr.SetDateFormat(date);
1710 });
1711
1712 connect(config, &GallerySettings::ExclusionsChanged,
1713 this, [this]()
1714 {
1715 // Request rescan
1716 QString exclusions = gCoreContext->GetSetting("GalleryIgnoreFilter");
1717 m_view->ClearCache();
1718 ImageManagerFe::IgnoreDirs(exclusions);
1719 });
1720}
1721
1722
1728{
1729 gCoreContext->SaveBoolSetting("GalleryShowHidden", show);
1730
1731 // Update Db(s)
1733
1734 // Reset dir thumbnail cache
1735 m_view->ClearCache();;
1736
1738}
1739
1740
1746void GalleryThumbView::ShowDialog(const QString& msg, const QString& event)
1747{
1748 auto *popup = new MythConfirmationDialog(&m_popupStack, msg, true);
1749
1750 if (popup->Create())
1751 {
1752 popup->SetReturnEvent(this, event);
1753 m_popupStack.AddScreen(popup);
1754 }
1755 else
1756 {
1757 delete popup;
1758 }
1759}
1760
1761
1766{
1768 {
1769 QString base = QFileInfo(m_menuState.m_selected->m_baseName).completeBaseName();
1770 QString msg = tr("Enter a new name:");
1771 auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone,
1772 false, base);
1773 if (popup->Create())
1774 {
1775 popup->SetReturnEvent(this, "FileRename");
1776 m_popupStack.AddScreen(popup);
1777 }
1778 else
1779 {
1780 delete popup;
1781 }
1782 }
1783}
1784
1785
1790{
1792}
1793
1794
1799{
1800 QString msg = tr("Enter password:");
1801 auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone, true);
1802 if (popup->Create())
1803 {
1804 popup->SetReturnEvent(this, "Password");
1805 m_popupStack.AddScreen(popup);
1806 }
1807 else
1808 {
1809 delete popup;
1810 }
1811}
1812
1813
1818{
1819 gCoreContext->SaveSetting("GalleryShowType", type);
1820
1821 // Update Db(s)
1823
1824 // Reset dir thumbnail cache
1825 m_view->ClearCache();
1826
1828}
1829
1830
1836{
1838 {
1839 QString err = reset ? m_mgr.SetCover(m_menuState.m_selected->m_id, 0)
1840 : m_mgr.SetCover(m_menuState.m_selected->m_parentId,
1841 m_menuState.m_selected->m_id);
1842 if (!err.isEmpty())
1843 ShowOkPopup(err);
1844 }
1845}
1846
1847
1852{
1853 SelectZoomWidget(-1);
1855}
1856
1857
1862{
1865}
1866
1867
1873{
1874 m_zoomLevel += change;
1875
1876 // constrain to zoom levels supported by theme
1877 m_zoomLevel = std::max(m_zoomLevel, 0);
1878 if (m_zoomLevel >= m_zoomWidgets.size())
1879 m_zoomLevel = m_zoomWidgets.size() - 1;
1880
1881 // Store any requested change, but not constraining adjustments
1882 // Thus, changing to a theme with fewer zoom levels will not overwrite the
1883 // setting
1884 if (change != 0)
1885 gCoreContext->SaveSetting("GalleryZoomLevel", m_zoomLevel);
1886
1887 // dump the current list widget
1888 if (m_imageList)
1889 {
1890 m_imageList->SetVisible(false);
1891 disconnect(m_imageList, nullptr, this, nullptr);
1892 }
1893
1894 // initialise new list widget
1896
1897 m_imageList->SetVisible(true);
1899
1900 // Monitor list actions (after focus events have been ignored)
1905}
1906
1907
1912{
1913 auto *popup = new MythTextInputDialog(&m_popupStack,
1914 tr("Enter name of new directory"),
1915 FilterNone, false);
1916 if (popup->Create())
1917 {
1918 popup->SetReturnEvent(this, "MakeDir");
1919 m_popupStack.AddScreen(popup);
1920 }
1921 else
1922 {
1923 delete popup;
1924 }
1925}
1926
1927
1932{
1934 if (dir)
1935 m_mgr.CloseDevices(dir->m_device, true);
1936}
1937
1938
1948void GalleryThumbView::Copy(bool deleteAfter)
1949{
1950 // Destination must be a dir
1952 if (!destDir || destDir->IsFile())
1953 return;
1954
1955 // Use current markings, if any. Otherwise use previous markings
1957 if (markedIds.isEmpty())
1958 {
1959 markedIds = m_menuState.m_prevMarkedId;
1960 if (markedIds.isEmpty())
1961 {
1962 ShowOkPopup(tr("No files specified"));
1963 return;
1964 }
1965 }
1966
1967 // Get all files/dirs in subtree(s). Only files are copied
1968 ImageList files;
1969 ImageList dirs;
1970 m_mgr.GetDescendants(markedIds, files, dirs);
1971
1972 if (dirs.isEmpty() && files.isEmpty())
1973 {
1974 ShowOkPopup(tr("No images"));
1975 // Nothing to clean up
1976 return;
1977 }
1978
1979 // Child dirs appear before their subdirs. If no dirs, images are all direct children
1980 ImagePtrK aChild = dirs.isEmpty() ? files[0] : dirs[0];
1981
1982 // Determine parent path including trailing /
1983 int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
1984
1985 // Update filepaths for Db & generate URLs for filesystem copy
1986 // Only copy files, destination dirs will be created automatically
1988 for (const ImagePtr & im : std::as_const(files))
1989 {
1990 // Replace base path with destination path
1991 im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath,
1992 im->m_filePath.mid(basePathSize));
1993
1994 transfers.insert(im, m_mgr.BuildTransferUrl(im->m_filePath,
1995 destDir->IsLocal()));
1996 }
1997
1998 // Create progress dialog
1999 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2000 auto *progress = new MythUIProgressDialog(tr("Copying files"), popupStack,
2001 "copydialog");
2002 if (progress->Create())
2003 {
2004 popupStack->AddScreen(progress, false);
2005 }
2006 else
2007 {
2008 delete progress;
2009 progress = nullptr;
2010 }
2011
2012 // Copy files in a servant thread
2013 TransferThread copy(transfers, false, progress);
2015 TransferThread::ImageSet failed = copy.GetResult();
2016
2017 if (progress)
2018 progress->Close();
2019
2020 if (!failed.isEmpty())
2021 ShowOkPopup(tr("Failed to copy %L1/%Ln file(s)", nullptr, transfers.size())
2022 .arg(failed.size()));
2023
2024 // Don't update Db for files that failed
2025 for (const ImagePtrK & im : std::as_const(failed))
2026 transfers.remove(im);
2027
2028 ImageListK newImages = transfers.keys();
2029
2030 // Include dirs
2031 QStringList dirPaths;
2032 for (const ImagePtr & im : std::as_const(dirs))
2033 {
2034 QString relPath = im->m_filePath.mid(basePathSize);
2035
2036 dirPaths << relPath;
2037
2038 // Replace base path with destination path
2039 im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath, relPath);
2040
2041 // Append dirs so that hidden state & cover is preserved for new dirs
2042 // Pre-existing dirs will take precedance over these.
2043 newImages.append(im);
2044 }
2045
2046 // Copy empty dirs as well (will fail for non-empty dirs)
2047 if (!dirPaths.isEmpty())
2048 m_mgr.MakeDir(destDir->m_id, dirPaths, false);
2049
2050 if (!newImages.isEmpty())
2051 {
2052 // Update Db
2053 m_mgr.CreateImages(destDir->m_id, newImages);
2054
2055 if (deleteAfter)
2056 {
2057 // Delete files/dirs that have been successfully copied
2058 // Will fail for dirs containing images that failed to copy
2059 ImageIdList ids;
2060 for (const ImagePtrK & im : std::as_const(newImages))
2061 ids << im->m_id;
2062
2063 m_mgr.DeleteFiles(ids);
2064 }
2065 }
2066}
2067
2068
2079{
2080 // Destination must be a dir
2082 if (!destDir || destDir->IsFile())
2083 return;
2084
2085 // Use current markings, if any. Otherwise use previous markings
2087 if (markedIds.isEmpty())
2088 {
2089 markedIds = m_menuState.m_prevMarkedId;
2090 if (markedIds.isEmpty())
2091 {
2092 ShowOkPopup(tr("No files specified"));
2093 return;
2094 }
2095 }
2096
2097 // Note UI mandates that transferees are either all local or all remote
2098 if (destDir->IsLocal() != ImageItem::IsLocalId(markedIds[0]))
2099 {
2100 // Moves between hosts require copy/delete
2101 Copy(true);
2102 return;
2103 }
2104
2105 // Get marked images. Each file and dir will be renamed
2106 ImageList files;
2107 ImageList dirs;
2108 if (m_mgr.GetImages(markedIds, files, dirs) <= 0)
2109 {
2110 ShowOkPopup(tr("No images specified"));
2111 // Nothing to clean up
2112 return;
2113 }
2114 ImageList images;
2115 if (!dirs.isEmpty())
2116 images += dirs;
2117 if (!files.isEmpty())
2118 images += files;
2119
2120 // Determine parent from first dir or pic
2121 ImagePtr aChild = images[0];
2122
2123 // Determine parent path including trailing /
2124 // Note UI mandates that transferees all have same parent.
2125 int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
2126 QString parentPath = aChild->m_filePath.left(basePathSize);
2127
2128 // Determine destination URLs
2130 for (const QSharedPointer<ImageItem> & im : std::as_const(images))
2131 {
2132 // Replace base path with destination path
2133 QString newPath = ImageManagerFe::ConstructPath(destDir->m_filePath,
2134 im->m_filePath.mid(basePathSize));
2135
2136 transfers.insert(im, m_mgr.BuildTransferUrl(newPath, aChild->IsLocal()));
2137 }
2138
2139 // Create progress dialog
2140 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2141 auto *progress = new MythUIProgressDialog(tr("Moving files"), popupStack,
2142 "movedialog");
2143
2144 if (progress->Create())
2145 {
2146 popupStack->AddScreen(progress, false);
2147 }
2148 else
2149 {
2150 delete progress;
2151 progress = nullptr;
2152 }
2153
2154 // Move files in a servant thread
2155 TransferThread move(transfers, true, progress);
2156 WaitUntilDone(move);
2157 TransferThread::ImageSet failed = move.GetResult();
2158
2159 if (progress)
2160 progress->Close();
2161
2162 if (!failed.isEmpty())
2163 ShowOkPopup(tr("Failed to move %L1/%Ln file(s)", nullptr, transfers.size())
2164 .arg(failed.size()));
2165
2166 // Don't update Db for files that failed
2167 for (const ImagePtrK & im : std::as_const(failed))
2168 transfers.remove(im);
2169
2170 if (!transfers.isEmpty())
2171 {
2172 ImageListK moved = transfers.keys();
2173
2174 // Unmark moved files
2175 for (const ImagePtrK & im : std::as_const(moved))
2176 m_view->Mark(im->m_id, false);
2177
2178 // Update Db
2179 m_mgr.MoveDbImages(destDir, moved, parentPath);
2180 }
2181}
2182
2183
2188{
2189 QString path = m_mgr.CreateImport();
2190 if (path.isEmpty())
2191 {
2192 ShowOkPopup(tr("Failed to create temporary directory."));
2193 return;
2194 }
2195
2196 // Replace placeholder in command
2197 QString cmd = gCoreContext->GetSetting("GalleryImportCmd");
2198 cmd.replace("%TMPDIR%", path);
2199
2200 // Run command in a separate thread
2201 MythUIBusyDialog *busy =
2202 ShowBusyPopup(tr("Running Import command.\nPlease wait..."));
2203
2204 ShellThread thread(cmd, path);
2205 WaitUntilDone(thread);
2206
2207 if (busy)
2208 busy->Close();
2209
2210 int error = thread.GetResult();
2211 if (error != 0)
2212 ShowOkPopup(tr("Import command failed.\nError: %1").arg(error));
2213
2214 // Rescan local devices
2215 QString err = m_mgr.ScanImagesAction(true, true);
2216 if (!err.isEmpty())
2217 LOG(VB_GENERAL, LOG_ERR, LOC + err);
2218}
2219
2221{
2222 gCoreContext->SaveSetting("GalleryRepeat", on);
2223}
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:40
static const Type kEventType
Definition: mythdialogbox.h:55
A datastore of images for display by a screen. Provides an ordered list of dirs & images from a singl...
Definition: galleryviews.h:174
ImagePtrK GetParent() const
Definition: galleryviews.h:178
QString GetPosition() const
Get positional status.
void ClearMarked()
Unmark all items.
void Clear(bool resetParent=true)
Resets view.
MenuSubjects GetMenuSubjects()
Determine current selection, markings & various info to support menu display.
bool IsMarked(int id) const
Definition: galleryviews.h:191
void MarkAll()
Mark all images/dirs.
bool LoadFromDb(int parentId) override
Populate view from database as images/subdirs of a directory. View is ordered: Parent dir,...
void ClearCache()
Clears UI cache.
QStringList RemoveImage(int id, bool deleted=false)
Clear file/dir and all its ancestors from UI cache so that ancestor thumbnails are recalculated....
void InvertMarked()
Mark all unmarked items, unmark all marked items.
void Mark(int id, bool mark)
Mark/unmark an image/dir.
int GetParentId() const
Definition: galleryviews.h:108
bool Select(int id, int fallback=0)
Selects first occurrence of an image.
ImagePtrK GetSelected() const
Get current selection.
ImageListK GetAllNodes() const
Get all images/dirs in view.
QString GetCachedThumbUrl(int id) const
Definition: galleryviews.h:124
void ExclusionsChanged()
void ClearDbPressed()
Slideshow screen.
void ImageSelected(int)
void ShowDialog(const QString &msg, const QString &event="")
Show a confirmation dialog.
void ShowSettings()
Show configuration screen.
static void RepeatOff()
bool Create() override
Initialises and shows the graphical elements.
DirectoryView * m_view
List of images comprising the view.
static void UpdateThumbnail(MythUIButtonListItem *button, const ImagePtrK &im, const QString &url, int index)
Update the buttonlist item with a thumbnail.
void DirSelectDown()
Goes one directory level down.
void DoScanAction(bool start)
Action scan request.
void DoMarkAll(bool mark=true)
Mark or unmark all items.
void SelectImage(int id)
Select item if it is displayed.
MythUIText * m_crumbsText
~GalleryThumbView() override
Destructor.
MythUIText * m_typeFilterText
QPair< int, int > IntPair
bool m_editsAllowed
Edit privileges.
static void ClearSgDb()
void DoMarkItem(bool mark)
Mark or unmark a single item.
void DoShowHidden(bool show=true)
Show or hide hidden files.
void MenuMain()
Shows the main menu when the MENU button was pressed.
void MenuTransform(MythMenu *mainMenu)
Add a Transform submenu.
void Start()
Start Thumbnail screen.
bool keyPressEvent(QKeyEvent *event) override
Handle keypresses.
void MenuSlideshow(MythMenu *mainMenu)
Add a Slideshow submenu.
void ZoomOut()
Use larger buttonlist widgets.
void LoadData(int parent)
Loads & displays images from database.
MythUIProgressBar * m_scanProgressBar
MythUIText * m_emptyText
MythScreenStack & m_popupStack
void ResetUiSelection()
Clears all text widgets for selected item.
void TransformItem(ImageFileTransform tran=kRotateCW)
Apply transform to an image.
QPair< MythUIButtonListItem *, int > ThumbLocation
void DoSetCover(bool reset=false)
Set or reset thumbnails to use for a directory cover.
void DoHideMarked(bool hide=true)
Hide or unhide marked items.
MythUIText * m_hideFilterText
void customEvent(QEvent *event) override
Handle custom events.
void Move()
Move marked images to selected dir. If no marked files, use previously marked files....
void Close() override
Exit Gallery.
void RemoveImages(const QStringList &ids, bool deleted=true)
Cleanup UI & image caches when a device is removed.
void DoHideItem(bool hide=true)
Hide or unhide item.
void ZoomIn()
Use smaller buttonlist widgets.
void MakeDir()
Show dialog to input new directory name.
QHash< QString, IntPair > m_scanProgress
Last scan updates received from scanners.
void MenuMarked(MythMenu *mainMenu)
Adds a Marking submenu.
void SetUiSelection(MythUIButtonListItem *item)
Updates text widgets for selected item.
void Eject()
Remove local device (or Import) from Gallery.
void TransformMarked(ImageFileTransform tran=kRotateCW)
Apply transform to marked images.
void MenuPaste(MythMenu *mainMenu)
Add a Paste submenu.
ImageManagerFe & m_mgr
Manages the images.
MythUIText * m_captionText
void DeleteItem()
Confirm user deletion of an item.
QSet< QString > m_scanActive
Scanners currently scanning.
GalleryThumbView(MythScreenStack *parent, const char *name)
Constructor.
void Import()
Executes user 'Import command'.
bool DirSelectUp()
Goes up one directory level.
static void DoRepeat(int on)
MenuSubjects m_menuState
Current selection/marked files when menu is invoked.
void MenuAction(MythMenu *mainMenu)
Add a Action submenu.
void ShowPassword()
Displays dialog to accept password.
void BuildImageList()
Displays all images in current view.
QMultiHash< int, ThumbLocation > m_pendingMap
Buttons waiting for thumbnails to be created.
MythUIText * m_scanProgressText
void MarkInvertAll()
Invert all marked items.
void DoShowType(int type)
Show/hide pictures or videos.
MythUIButtonList * m_imageList
void ItemClicked(MythUIButtonListItem *item)
Action item click.
void StartSlideshow(ImageSlideShowType mode)
Start slideshow screen.
MythUIText * m_positionText
InfoList m_infoList
Image details overlay.
void DeleteMarked()
Confirm user deletion of marked files.
void UpdateImageItem(MythUIButtonListItem *item)
Initialises a single buttonlist item.
QSet< int > m_thumbExists
Images where thumbnails are known to exist.
static void RepeatOn()
void SelectZoomWidget(int change)
Change buttonlist to use a different size.
QList< MythUIButtonList * > m_zoomWidgets
Theme buttonlist widgets implementing zoom levels.
void ShowRenameInput()
Show dialog to allow input.
void UpdateScanProgress(const QString &scanner, int current, int total)
Update progressbar with scan status.
QString CheckThumbnail(MythUIButtonListItem *item, const ImagePtrK &im, ImageIdList &request, int index)
Verify thumbnail is known to exist.
void ShowDetails()
Shows exif info/details about an item.
void MenuShow(MythMenu *mainMenu)
Add a Show submenu.
static QString FormatSize(int sizeKib)
Definition: imagemanager.h:143
void GetDescendants(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Return all (local or remote) images that are direct children of a dir.
int GetType() const
Definition: imagemanager.h:404
bool GetVisibility() const
Definition: imagemanager.h:405
void SetType(int showType)
Definition: imagemanager.h:407
void SetVisibility(bool showHidden)
Definition: imagemanager.h:413
void SetSortOrder(int order, int dirOrder)
Definition: imagemanager.h:410
int GetImages(const ImageIdList &ids, ImageList &files, ImageList &dirs) const
Returns images (local or remote but not a combination)
static bool IsLocalId(int id)
Determine image type (local/remote) from its id. Root/Gallery is remote.
Definition: imagetypes.h:122
The image manager for use by Frontends.
Definition: imagemanager.h:456
QString ShortDateOf(const ImagePtrK &im) const
Return a short datestamp for thumbnail captions.
QString ChangeOrientation(ImageFileTransform transform, const ImageIdList &ids)
Apply an orientation transform to images.
QString CreateImages(int destId, const ImageListK &images)
Copies database images (but not the files themselves).
QString MoveDbImages(const ImagePtrK &destDir, ImageListK &images, const QString &srcPath)
Moves database images (but not the files themselves).
QString CrumbName(ImageItemK &im, bool getPath=false) const
Return a displayable name (with optional path) for an image.
static QString LongDateOf(const ImagePtrK &im)
Return a timestamp/datestamp for an image or dir.
static QString ConstructPath(const QString &path, const QString &name)
Assembles a canonical file path without corrupting its absolute/relative nature.
Definition: imagemanager.h:132
static QStringList ScanQuery()
Returns storage group scanner status.
QString DeleteFiles(const ImageIdList &ids)
Delete images.
QString MakeDir(int parent, const QStringList &names, bool rescan=true)
Create directories.
QString SetCover(int parent, int cover)
Set image to use as a cover thumbnail(s)
QString CreateImport()
QString HideFiles(bool hidden, const ImageIdList &ids)
Hide/unhide images.
void CloseDevices(int devId=DEVICE_INVALID, bool eject=false)
static QString IgnoreDirs(const QString &excludes)
Set directories to ignore during scans of the storage group.
void CreateThumbnails(const ImageIdList &ids, bool forFolder)
Create thumbnails or verify that they already exist.
QString ScanImagesAction(bool start, bool local=false)
Handle scanner start/stop commands.
void SetDateFormat(const QString &format)
Definition: imagemanager.h:488
int DeviceCount() const
Definition: imagemanager.h:100
QString BuildTransferUrl(const QString &path, bool local) const
Generate Myth URL for a local or remote path.
Definition: imagemanager.h:495
QString RenameFile(const ImagePtrK &im, const QString &name)
Rename an image.
bool DetectLocalDevices()
Detect and scan local devices.
void Display(ImageItemK &im, const QStringList &tagStrings)
Build list of metadata tags.
void Update(const ImagePtrK &im)
Populates available exif details for the current image/dir.
InfoVisibleState GetState() const
Definition: galleryinfo.h:34
bool Hide()
Remove infolist from display.
void Toggle(const ImagePtrK &im)
Toggle infolist state for an image. Focusable widgets toggle between Basic & Full info....
Definition: galleryinfo.cpp:86
bool Create(bool focusable)
Initialise buttonlist from XML.
Definition: galleryinfo.cpp:68
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
bool isFinished(void) const
Definition: mthread.cpp:242
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
QString objectName(void) const
Definition: mthread.cpp:227
ImageIdList m_prevMarkedId
Ids of marked items in previous dir.
Definition: galleryviews.h:72
ImageIdList m_markedId
Ids of all marked items.
Definition: galleryviews.h:71
ssize_t m_childCount
Number of images & dirs excl parent.
Definition: galleryviews.h:73
bool m_hiddenMarked
Is any marked item hidden ?
Definition: galleryviews.h:74
ImagePtrK m_selected
Selected item.
Definition: galleryviews.h:69
bool m_selectedMarked
Is selected item marked ?
Definition: galleryviews.h:70
bool m_unhiddenMarked
Is any marked item unhidden ?
Definition: galleryviews.h:75
Dialog asking for user confirmation.
void SaveBoolSetting(const QString &key, bool newValue)
QString GetHostName(void)
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
bool GetBoolSetting(const QString &key, bool defaultval=false)
Basic menu dialog, message and a list of options.
This class is used as a container for messages.
Definition: mythevent.h:17
const QString & Message() const
Definition: mythevent.h:65
static const Type kMythEventMessage
Definition: mythevent.h:79
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
void AddItem(const QString &title)
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual void Close()
Dialog prompting the user to enter a text string.
void DisplayState(const QString &state, const QString &name)
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
void setChecked(CheckState state)
void SetImageFromMap(const InfoMap &imageMap)
void SetText(const QString &text, const QString &name="", const QString &state="")
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
void SetItemCurrent(MythUIButtonListItem *item)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
void itemClicked(MythUIButtonListItem *item)
void itemSelected(MythUIButtonListItem *item)
void SetUsed(int value)
void SetStart(int value)
void SetTotal(int value)
void SetVisible(bool visible) override
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:65
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
void RemoveFromCacheByFile(const QString &File)
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
Definition: remotefile.cpp:589
static bool MoveFile(const QString &src, const QString &dst, bool overwrite=false)
Definition: remotefile.cpp:679
Worker thread for running import.
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
ShellThread(QString cmd, QString path)
int GetResult(void) const
Worker thread for copying/moving files.
bool m_move
Copy if false, Move if true.
MythUIProgressDialog * m_dialog
Images for which copy/move failed.
QSet< ImagePtrK > ImageSet
TransferThread(TransferMap files, bool move, MythUIProgressDialog *dialog)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Q_DECLARE_TR_FUNCTIONS(FileTransferWorker)
ImageSet GetResult(void)
QMap< ImagePtrK, QString > TransferMap
TransferMap m_files
Maps source filepath to destination filepath.
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
Provides Gallery configuration screens.
@ kNoInfo
Details not displayed.
Definition: galleryinfo.h:17
ImageSlideShowType
Type of slide show.
@ kBrowseSlides
#define LOC
static void WaitUntilDone(MThread &worker)
Runs a worker thread and waits for it to finish.
static QString clean_comment(const QString &comment)
Implements Gallery Thumbnail screen.
ImageCaptionType
Type of captions to display.
@ kDateCaption
Dates.
@ kNameCaption
Filenames.
@ kNoCaption
None.
@ kUserCaption
Exif comments.
SlideOrderType
Order of images in slideshow.
Definition: galleryviews.h:24
@ kSeasonal
Biased random selection so that images are more likely to appear on anniversaries.
Definition: galleryviews.h:28
@ kShuffle
Each image appears exactly once, but in random order.
Definition: galleryviews.h:26
@ kRandom
Random selection from view. An image may be absent or appear multiple times.
Definition: galleryviews.h:27
@ kOrdered
Ordered as per user setting GallerySortOrder.
Definition: galleryviews.h:25
@ kVideoOnly
Hide pictures.
Definition: imagemanager.h:80
@ kPicAndVideo
Show Pictures & Videos.
Definition: imagemanager.h:78
@ kPicOnly
Hide videos.
Definition: imagemanager.h:79
ImageFileTransform
Image transformations.
Definition: imagemetadata.h:46
QVector< ImagePtr > ImageList
Definition: imagetypes.h:160
QList< ImagePtrK > ImageListK
Definition: imagetypes.h:166
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:165
QPair< int, QString > ThumbPair
Definition: imagetypes.h:64
static constexpr int GALLERY_DB_ID
Definition: imagetypes.h:27
static constexpr int PHOTO_DB_ID
Definition: imagetypes.h:29
@ kDevice
Storage Group and local mounted media.
Definition: imagetypes.h:36
@ kCloneDir
A device sub dir comprised from multiple SG dirs.
Definition: imagetypes.h:37
@ kDirectory
A device sub directory.
Definition: imagetypes.h:38
@ kImageFile
A picture.
Definition: imagetypes.h:39
@ kVideoFile
A video.
Definition: imagetypes.h:40
QSharedPointer< ImageItem > ImagePtr
Definition: imagetypes.h:159
QList< int > ImageIdList
Definition: imagetypes.h:60
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
MythUIBusyDialog * ShowBusyPopup(const QString &message)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
static MythThemedMenu * menu
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
MythUIHelper * GetMythUI()
@ FilterNone
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
def scan(profile, smoonURL, gate)
Definition: scan.py:54
def error(message)
Definition: smolt.py:409
static void show(uint8_t *buf, int length)
Definition: ringbuffer.cpp:339
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27