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
45 void run() override // MThread
46 {
47 RunProlog();
48
49 QString cmd = QString("cd %1 && %2").arg(m_path, m_command);
50 LOG(VB_GENERAL, LOG_INFO, QString("Executing \"%1\"").arg(cmd));
51
52 m_result = myth_system(cmd);
53
54 LOG(VB_GENERAL, LOG_INFO, QString(" ...with result %1").arg(m_result));
55
56 RunEpilog();
57 }
58
59private:
60 int m_result {0};
61 QString m_command;
62 QString m_path;
63};
64
65
67class TransferThread : public MThread
68{
69 Q_DECLARE_TR_FUNCTIONS(FileTransferWorker);
70public:
71 using TransferMap = QMap<ImagePtrK, QString>;
72 using ImageSet = QSet<ImagePtrK>;
73
75 : MThread("FileTransfer"),
76 m_move(move), m_files(std::move(files)), m_dialog(dialog) {}
77
78 ImageSet GetResult(void) { return m_failed; }
79
80 void run() override // MThread
81 {
82 RunProlog();
83
84 QString action = m_move ? tr("Moving") : tr("Copying");
85
86 // Sum file sizes
87 auto keys = m_files.keys();
88 auto add_size = [](int t, const ImagePtrK & im){ return t + im->m_size; };
89 int total = std::accumulate(keys.cbegin(), keys.cend(), 0, add_size);
90
91 int progressSize = 0;
92 for (auto it = m_files.constKeyValueBegin();
93 it != m_files.constKeyValueEnd(); it++)
94 {
95 const ImagePtrK & im = it->first;
96 QString newPath = it->second;
97 // Update progress dialog
98 if (m_dialog)
99 {
100 QString message = QString("%1 %2\n%3")
101 .arg(action, QFileInfo(im->m_url).fileName(),
102 ImageAdapterBase::FormatSize(im->m_size / 1024));
103
104 auto *pue = new ProgressUpdateEvent(progressSize, total, message);
105 QApplication::postEvent(m_dialog, pue);
106 }
107
108 LOG(VB_FILE, LOG_INFO, QString("%2 %3 -> %4")
109 .arg(action, im->m_url, newPath));
110
111 bool success = m_move ? RemoteFile::MoveFile(im->m_url, newPath)
112 : RemoteFile::CopyFile(im->m_url, newPath,
113 false, true);
114 if (!success)
115 {
116 // Flag failures
117 m_failed.insert(im);
118
119 LOG(VB_GENERAL, LOG_ERR,
120 QString("%1: Failed to copy/move %2 -> %3")
121 .arg(objectName(), im->m_url, m_files[im]));
122 }
123
124 progressSize += im->m_size;
125 }
126
127 // Update progress dialog
128 if (m_dialog)
129 {
130 auto *pue =
131 new ProgressUpdateEvent(progressSize, total, tr("Complete"));
132 QApplication::postEvent(m_dialog, pue);
133 }
134
135 RunEpilog();
136 }
137
138private:
139 bool m_move;
143};
144
145
150static void WaitUntilDone(MThread &worker)
151{
152 worker.start();
153 while (!worker.isFinished())
154 {
155 std::this_thread::sleep_for(1ms);
156 QCoreApplication::processEvents();
157 }
158}
159
160
167 : MythScreenType(parent, name),
168 m_popupStack(*GetMythMainWindow()->GetStack("popup stack")),
169 m_mgr(ImageManagerFe::getInstance()),
170 // This screen uses a single fixed view (Parent dir, ordered dirs, ordered images)
171 m_view(new DirectoryView(kOrdered)),
172 m_infoList(*this),
173 // Start in edit mode unless a password exists
174 m_editsAllowed(gCoreContext->GetSetting("GalleryPassword").isEmpty())
175{
176 // Hide hidden when edits disallowed
177 if (!m_editsAllowed)
178 m_mgr.SetVisibility(false);
179}
180
181
186{
187 LOG(VB_GUI, LOG_DEBUG, LOC + "Exiting Gallery");
188 delete m_view;
189}
190
191
196{
197 LOG(VB_GUI, LOG_DEBUG, LOC + "Closing Gallery");
198
200
201 // Cleanup local devices
203
204 // Cleanup view
205 m_view->Clear();
206
208}
209
210
215{
216 if (!LoadWindowFromXML("image-ui.xml", "gallery", this))
217 return false;
218
219 // Determine zoom levels supported by theme
220 // images0 must exist; images1, images2 etc. are optional and enable zoom
221 int zoom = 0;
222 QString name = QString("images%1").arg(zoom++);
223 auto *widget = dynamic_cast<MythUIButtonList *>(this->GetChild(name));
224 while (widget)
225 {
226 m_zoomWidgets.append(widget);
227 widget->SetVisible(false);
228
229 name = QString("images%1").arg(zoom++);
230 widget = dynamic_cast<MythUIButtonList *>(this->GetChild(name));
231 }
232
233 if (m_zoomWidgets.isEmpty())
234 {
235 LOG(VB_GENERAL, LOG_ERR, LOC + "Screen 'Gallery' is missing 'images0'");
236 return false;
237 }
238 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Screen 'Gallery' found %1 zoom levels")
239 .arg(m_zoomWidgets.size()));
240
241 // File details list is managed elsewhere
242 if (!m_infoList.Create(false))
243 {
244 LOG(VB_GENERAL, LOG_ERR, LOC + "Cannot load 'Info buttonlist'");
245 return false;
246 }
247
248 UIUtilW::Assign(this, m_captionText, "caption");
249 UIUtilW::Assign(this, m_emptyText, "noimages");
250 UIUtilW::Assign(this, m_positionText, "position");
251 UIUtilW::Assign(this, m_crumbsText, "breadcrumbs");
252 UIUtilW::Assign(this, m_hideFilterText, "hidefilter");
253 UIUtilW::Assign(this, m_typeFilterText, "typefilter");
254 UIUtilW::Assign(this, m_scanProgressText, "scanprogresstext");
255 UIUtilW::Assign(this, m_scanProgressBar, "scanprogressbar");
256
261
263
264 // Initialise list widget with appropriate zoom level for this theme.
265 m_zoomLevel = gCoreContext->GetNumSetting("GalleryZoomLevel", 0);
267
268 return true;
269}
270
271
277{
278 if (GetFocusWidget()->keyPressEvent(event))
279 return true;
280
281 QStringList actions;
282 bool handled = GetMythMainWindow()->TranslateKeyPress("Images", event, actions);
283
284 for (int i = 0; i < actions.size() && !handled; i++)
285 {
286 const QString& action = actions[i];
287 handled = true;
288
289 if (action == "MENU")
290 MenuMain();
291 else if (action == "INFO")
292 ShowDetails();
293 else if (action == "ZOOMIN")
294 ZoomIn();
295 else if (action == "ZOOMOUT")
296 ZoomOut();
297 else if (action == "ROTRIGHT")
298 RotateCW();
299 else if (action == "ROTLEFT")
300 RotateCCW();
301 else if (action == "FLIPHORIZONTAL")
303 else if (action == "FLIPVERTICAL")
304 FlipVertical();
305 else if (action == "COVER")
306 {
308 if (m_editsAllowed && im)
309 {
310 if (im == m_view->GetParent())
311 {
312 // Reset dir
313 m_mgr.SetCover(im->m_id, 0);
314 }
315 else
316 {
317 // Set parent cover
318 m_mgr.SetCover(im->m_parentId, im->m_id);
319 }
320 }
321 }
322 else if (action == "PLAY")
323 {
324 Slideshow();
325 }
326 else if (action == "RECURSIVESHOW")
327 {
329 if (im && im->IsDirectory())
331 }
332 else if (action == "MARK")
333 {
335 if (m_editsAllowed && im && im != m_view->GetParent())
336 DoMarkItem(!m_view->IsMarked(im->m_id));
337 }
338 else if (action == "ESCAPE" && !GetMythMainWindow()->IsExitingToMain())
339 {
340 // Exit info list, if shown
341 handled = m_infoList.Hide();
342
343 // Ascend the tree unless parent is root,
344 // or a device and multiple devices/imports exist
345 if (!handled)
346 {
347 ImagePtrK node = m_view->GetParent();
348 if (node && node->m_id != GALLERY_DB_ID
349 && (!node->IsDevice() || m_mgr.DeviceCount() > 0))
350 handled = DirSelectUp();
351 }
352 }
353 else
354 {
355 handled = false;
356 }
357 }
358
359 if (!handled)
360 handled = MythScreenType::keyPressEvent(event);
361
362 return handled;
363}
364
365
371{
372
373 if (event->type() == MythEvent::kMythEventMessage)
374 {
375 auto *me = dynamic_cast<MythEvent *>(event);
376 if (me == nullptr)
377 return;
378
379 const QString& mesg = me->Message();
380 QStringList extra = me->ExtraDataList();
381
382 // Internal messages contain a hostname. Ignore other FE messages
383 QStringList token = mesg.split(' ');
384 if (token.size() >= 2 && token[1] != gCoreContext->GetHostName())
385 return;
386
387 if (token[0] == "IMAGE_METADATA")
388 {
389 int id = extra[0].toInt();
390 ImagePtrK selected = m_view->GetSelected();
391
392 if (selected && selected->m_id == id)
393 m_infoList.Display(*selected, extra.mid(1));
394 }
395 else if (token[0] == "THUMB_AVAILABLE")
396 {
397 int id = extra[0].toInt();
398
399 // Note existance of all thumbs
400 m_thumbExists.insert(id);
401
402 // Get all buttons waiting for this thumbnail
403 QList<ThumbLocation> affected = m_pendingMap.values(id);
404
405 // Only concerned with thumbnails we've requested
406 if (affected.isEmpty())
407 return;
408
409 LOG(VB_GENERAL, LOG_DEBUG, LOC +
410 QString("Rx %1 : %2").arg(token[0], extra.join(",")));
411
412 // Thumb url was cached when request was sent
413 QString url = m_view->GetCachedThumbUrl(id);
414
415 // Set thumbnail for each button now it exists
416 for (const ThumbLocation & location : std::as_const(affected))
417 {
418 MythUIButtonListItem *button = location.first;
419 int index = location.second;
420
421 auto im = button->GetData().value<ImagePtrK>();
422 if (im)
423 UpdateThumbnail(button, im, url, index);
424 }
425
426 // Cancel pending request
427 m_pendingMap.remove(id);
428 }
429 else if (token[0] == "IMAGE_DB_CHANGED")
430 {
431 // Expects csv list of deleted ids, csv list of changed ids
432 LOG(VB_GENERAL, LOG_DEBUG, LOC +
433 QString("Rx %1 : %2").arg(token[0], extra.join(",")));
434
435 if (!extra.isEmpty())
436 {
437 QStringList idDeleted =
438 extra[0].split(",", Qt::SkipEmptyParts);
439 RemoveImages(idDeleted);
440 }
441 if (extra.size() >= 2)
442 {
443 QStringList idChanged =
444 extra[1].split(",", Qt::SkipEmptyParts);
445 RemoveImages(idChanged, false);
446 }
447
448 // Refresh display
450 }
451 else if (token[0] == "IMAGE_DEVICE_CHANGED")
452 {
453 // Expects list of url prefixes
454 LOG(VB_GENERAL, LOG_DEBUG, LOC +
455 QString("Rx %1 : %2").arg(token[0], extra.join(",")));
456
457 // Clear everything. Local devices will be rebuilt
458 m_view->Clear();
459 m_thumbExists.clear();
460
461 // Remove thumbs & images from image cache using supplied prefixes
462 for (const QString & url : std::as_const(extra))
464
465 // Refresh display
467 }
468 else if (token[0] == "IMAGE_SCAN_STATUS" && extra.size() == 3)
469 {
470 // Expects scanner id, scanned#, total#
471 UpdateScanProgress(extra[0], extra[1].toInt(), extra[2].toInt());
472 }
473 }
474 else if (event->type() == DialogCompletionEvent::kEventType)
475 {
476 auto *dce = (DialogCompletionEvent *)(event);
477
478 QString resultid = dce->GetId();
479 int buttonnum = dce->GetResult();
480
481 if (resultid == "FileRename")
482 {
483 QString newName = dce->GetResultText();
485 {
487 newName);
488 if (!err.isEmpty())
489 ShowOkPopup(err);
490 }
491 }
492 else if (resultid == "MakeDir")
493 {
495 {
496 // Prohibit subtrees
497 QString name = dce->GetResultText();
498 QString err = name.contains("/")
499 ? tr("Invalid Name")
501 QStringList(name));
502 if (!err.isEmpty())
503 ShowOkPopup(err);
504 }
505 }
506 else if (resultid == "SlideOrderMenu")
507 {
508 SlideOrderType slideOrder = kOrdered;
509
510 switch (buttonnum)
511 {
512 case 0: slideOrder = kOrdered; break;
513 case 1: slideOrder = kShuffle; break;
514 case 2: slideOrder = kRandom; break;
515 case 3: slideOrder = kSeasonal; break;
516 }
517 gCoreContext->SaveSetting("GallerySlideOrder", slideOrder);
518 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Order %1").arg(slideOrder));
519 }
520 else if (resultid == "ImageCaptionMenu")
521 {
522 ImageCaptionType captions = kNoCaption;
523
524 switch (buttonnum)
525 {
526 case 0: captions = kNameCaption; break;
527 case 1: captions = kDateCaption; break;
528 case 2: captions = kUserCaption; break;
529 case 3: captions = kNoCaption; break;
530 }
531 gCoreContext->SaveSetting("GalleryImageCaption", captions);
533 }
534 else if (resultid == "DirCaptionMenu")
535 {
536 ImageCaptionType captions = kNoCaption;
537
538 switch (buttonnum)
539 {
540 case 0: captions = kNameCaption; break;
541 case 1: captions = kDateCaption; break;
542 case 2: captions = kNoCaption; break;
543 }
544 gCoreContext->SaveSetting("GalleryDirCaption", captions);
546 }
547 else if (resultid == "Password")
548 {
549 QString password = dce->GetResultText();
550 m_editsAllowed = (password == gCoreContext->GetSetting("GalleryPassword"));
551 }
552 else if (buttonnum == 1)
553 {
554 // Confirm current file deletion
555 QString err;
556 if (resultid == "ConfirmDelete" && m_menuState.m_selected)
557 {
559 err = m_mgr.DeleteFiles(ids);
560 }
561 // Confirm marked file deletion
562 else if (resultid == "ConfirmDeleteMarked")
563 {
565 }
566 else
567 {
568 return;
569 }
570
571 if (!err.isEmpty())
572 ShowOkPopup(err);
573 }
574 }
575}
576
577
583void GalleryThumbView::RemoveImages(const QStringList &ids, bool deleted)
584{
585 for (const QString & id : std::as_const(ids))
586 {
587 // Remove image from view
588 QStringList urls = m_view->RemoveImage(id.toInt(), deleted);
589 // Cleanup url lookup
590 m_thumbExists.remove(id.toInt());
591
592 // Remove thumbs & images from image cache
593 for (const QString & url : std::as_const(urls))
594 {
595 LOG(VB_FILE, LOG_DEBUG, LOC +
596 QString("Clearing image cache of '%1'").arg(url));
597
599 }
600 }
601}
602
603
608{
609 // Detect any running BE scans
610 // Expects OK, scanner id, current#, total#
611 QStringList message = ImageManagerFe::ScanQuery();
612 if (message.size() == 4 && message[0] == "OK")
613 {
614 UpdateScanProgress(message[1], message[2].toInt(), message[3].toInt());
615 }
616
617 // Only receive events after device/scan status has been established
619
620 // Start at Root if devices exist. Otherwise go straight to SG node
622
623 LoadData(start);
624}
625
626
632{
634
635 // Load view for parent directory
636 if (m_view->LoadFromDb(parent))
637 {
638 m_imageList->SetVisible(true);
639 if (m_emptyText)
640 {
641 m_emptyText->SetVisible(false);
643 }
644
645 // Construct the buttonlist
647 }
648 else
649 {
651 m_imageList->SetVisible(false);
652 if (m_emptyText)
653 {
654 m_emptyText->SetVisible(true);
655 m_emptyText->SetText(tr("No images found.\n"
656 "Scan storage group using menu,\n"
657 "or insert/mount local media.\n"));
658 }
659 }
660}
661
662
667{
669 m_pendingMap.clear();
670
671 // Get parent & all children
672 ImageListK nodes = m_view->GetAllNodes();
673 ImagePtrK selected = m_view->GetSelected();
674
675 // go through the entire list and update
676 for (const ImagePtrK & im : std::as_const(nodes))
677 {
678 if (im)
679 {
680 // Data must be set by constructor: First item is automatically
681 // selected and must have data available for selection event, as
682 // subsequent reselection of same item will always fail.
683 auto *item = new MythUIButtonListItem(m_imageList, "",
684 QVariant::fromValue(im));
685
686 item->setCheckable(true);
687 item->setChecked(MythUIButtonListItem::NotChecked);
688
689 // assign and display all information about
690 // the current item, like title and subdirectory count
691 UpdateImageItem(item);
692
693 // Treat parent differently
694 if (im == nodes[0])
695 {
696 // Only non-root parents can ascend
697 if (im->m_id != GALLERY_DB_ID)
698 item->DisplayState("upfolder", "parenttype");
699 }
700 else if (im == selected)
701 {
702 // Reinstate the active button item. Note this would fail for parent
704 }
705 }
706 }
707}
708
709
715{
716 auto im = item->GetData().value<ImagePtrK >();
717 if (!im)
718 return;
719
720 // Allow themes to distinguish between roots, folders, pics, videos
721 switch (im->m_type)
722 {
723 case kDevice:
724 case kCloneDir:
725 case kDirectory:
726 if (im->m_dirCount > 0)
727 {
728 item->SetText(QString("%1/%2")
729 .arg(im->m_fileCount).arg(im->m_dirCount),
730 "childcount");
731 }
732 else
733 {
734 item->SetText(QString::number(im->m_fileCount), "childcount");
735 }
736
737 item->DisplayState(im->IsDevice() ? "device" : "subfolder", "buttontype");
738 break;
739
740 case kImageFile:
741 item->DisplayState("image", "buttontype");
742 break;
743
744 case kVideoFile:
745 item->DisplayState("video", "buttontype");
746 break;
747
748 default:
749 break;
750 }
751
752 // Allow theme to distinguish visible/hidden nodes
753 QString hideState = (im->m_isHidden) ? "hidden" : "visible";
754 item->DisplayState(hideState, "buttonstate");
755
756 // Caption
757 QString text;
759 im->IsFile() ? "GalleryImageCaption"
760 : "GalleryDirCaption");
761 switch (show)
762 {
763 case kNameCaption: text = m_mgr.CrumbName(*im); break;
764 case kDateCaption: text = m_mgr.ShortDateOf(im); break;
765 case kUserCaption: text = clean_comment(im->m_comment); break;
766 default:
767 case kNoCaption: text = ""; break;
768 }
769 item->SetText(text);
770
771 // Set marked state
773 = m_view->IsMarked(im->m_id)
776
777 item->setChecked(state);
778
779 // Thumbnails required
780 ImageIdList request;
781
782 if (im->m_thumbNails.size() == 1)
783 {
784 // Single thumbnail
785 QString url = CheckThumbnail(item, im, request, 0);
786
787 if (!url.isEmpty())
788 UpdateThumbnail(item, im, url, 0);
789 }
790 else
791 {
792 // Dir showing up to 4 thumbs. Set them all at same time
793 InfoMap thumbMap;
794 for (int index = 0; index < im->m_thumbNails.size(); ++index)
795 {
796 QString url = CheckThumbnail(item, im, request, index);
797 if (!url.isEmpty())
798 thumbMap.insert(QString("thumbimage%1").arg(index), url);
799 }
800 if (!thumbMap.isEmpty())
801 item->SetImageFromMap(thumbMap);
802 }
803
804 // Request creation/verification of unknown thumbnails.
805 if (!request.isEmpty())
806 m_mgr.CreateThumbnails(request, im->IsDirectory());
807}
808
809
822 ImageIdList &request, int index)
823{
824 ThumbPair thumb(im->m_thumbNails.at(index));
825 int id = thumb.first;
826
827 if (m_thumbExists.contains(id))
828 return thumb.second;
829
830 // Request BE thumbnail check if it is not already pending
831 if (!m_pendingMap.contains(id))
832 request << id;
833
834 // Note this button is awaiting an update
835 m_pendingMap.insert(id, qMakePair(item, index));
836
837 return "";
838}
839
840
849 const ImagePtrK& im, const QString &url,
850 int index)
851{
852 if (im->m_thumbNails.size() == 1)
853 {
854 // Pics, dirs & videos use separate widgets
855 switch (im->m_type)
856 {
857 case kImageFile: button->SetImage(url); break;
858 case kVideoFile: button->SetImage(url, "videoimage"); break;
859 default: button->SetImage(url, "folderimage"); break;
860 }
861 }
862 else
863 {
864 // Dir with 4 thumbnails
865 button->SetImage(url, QString("thumbimage%1").arg(index));
866 }
867}
868
869
877void GalleryThumbView::UpdateScanProgress(const QString &scanner,
878 int current, int total)
879{
880 // Scan update
881 m_scanProgress.insert(scanner, qMakePair(current, total));
882
883 // Detect end of this scan
884 if (current >= total)
885 {
886 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Finished %1 %2/%3")
887 .arg(scanner).arg(current).arg(total));
888
889 // Mark inactive scanner
890 m_scanActive.remove(scanner);
891
892 // Detect end of last scan
893 if (m_scanActive.isEmpty())
894 {
896 {
899 }
901 {
904 }
905
906 m_scanProgress.clear();
907
908 return;
909 }
910 }
911 else
912 {
913 // Detect first scan update
914 if (m_scanActive.isEmpty())
915 {
916 // Show progressbar when first scan starts
918 {
921 }
924 }
925
926 if (!m_scanActive.contains(scanner))
927 {
928 LOG(VB_GUI, LOG_DEBUG, LOC + QString("Scan Started %1 %2/%3")
929 .arg(scanner).arg(current).arg(total));
930
931 // Mark active scanner
932 m_scanActive.insert(scanner);
933 }
934 }
935
936 // Aggregate all running scans
937 int currentAgg = 0;
938 int totalAgg = 0;
939 for (IntPair scan : std::as_const(m_scanProgress))
940 {
941 currentAgg += scan.first;
942 totalAgg += scan.second;
943 }
944
946 {
947 m_scanProgressBar->SetUsed(currentAgg);
948 m_scanProgressBar->SetTotal(totalAgg);
949 }
951 m_scanProgressText->SetText(tr("%L1 of %L3").arg(currentAgg).arg(totalAgg));
952}
953
954
959{
960 if (m_positionText)
962
963 if (m_captionText)
965
966 if (m_crumbsText)
968
971
974}
975
976
982{
983 auto im = item->GetData().value<ImagePtrK >();
984 if (im)
985 {
986 // update the position in the node list
987 m_view->Select(im->m_id);
988
989 // show the name/path of the image
990 if (m_crumbsText)
991 m_crumbsText->SetText(m_mgr.CrumbName(*im, true));
992
993 if (m_captionText)
994 {
995 // show the date & comment of non-root nodes
996 QStringList text;
997 if (im->m_id != GALLERY_DB_ID)
998 {
999 if (im->IsFile() || im->IsDevice())
1000 text << ImageManagerFe::LongDateOf(im);
1001
1002 QString comment = clean_comment(im->m_comment);
1003 if (!comment.isEmpty())
1004 text << comment;
1005 }
1006 m_captionText->SetText(text.join(" - "));
1007 }
1008
1009 if (m_hideFilterText)
1010 {
1011 m_hideFilterText->SetText(m_mgr.GetVisibility() ? tr("Hidden") : "");
1012 }
1013
1014 if (m_typeFilterText)
1015 {
1016 QString text = "";
1017 switch (m_mgr.GetType())
1018 {
1019 case kPicAndVideo : text = ""; break;
1020 case kPicOnly : text = tr("Pictures"); break;
1021 case kVideoOnly : text = tr("Videos"); break;
1022 }
1024 }
1025
1026 // show the position of the image
1027 if (m_positionText)
1029
1030 // Update any file details information
1031 m_infoList.Update(im);
1032 }
1033}
1034
1035
1040{
1041 // Create the main menu
1042 auto *menu = new MythMenu(tr("Gallery Options"), this, "mainmenu");
1043
1044 // Menu options depend on the marked files and the current node
1046
1048 {
1049 if (m_editsAllowed)
1050 {
1052 MenuPaste(menu);
1055 }
1057 MenuShow(menu);
1058 if (!m_editsAllowed)
1059 menu->AddItem(tr("Enable Edits"), &GalleryThumbView::ShowPassword);
1060 }
1061
1062 // Depends on current status of backend scanner - string(number(isBackend()))
1063 if (m_scanActive.contains("1"))
1064 menu->AddItem(tr("Stop Scan"), &GalleryThumbView::StopScan);
1065 else
1066 menu->AddItem(tr("Scan Storage Group"), &GalleryThumbView::StartScan);
1067
1068 menu->AddItem(tr("Settings"), &GalleryThumbView::ShowSettings);
1069
1070 auto *popup = new MythDialogBox(menu, &m_popupStack, "menuPopup");
1071 if (popup->Create())
1072 m_popupStack.AddScreen(popup);
1073 else
1074 delete popup;
1075}
1076
1077
1083{
1084 ImagePtrK parent = m_view->GetParent();
1085
1086 if (m_menuState.m_childCount == 0 || parent.isNull())
1087 return;
1088
1089 QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1090 auto *menu = new MythMenu(title, this, "markmenu");
1091
1092 // Mark/unmark selected
1093 if (m_menuState.m_selected->IsFile())
1094 {
1096 menu->AddItem(tr("Unmark File"), &GalleryThumbView::UnmarkItem);
1097 else
1098 menu->AddItem(tr("Mark File"), &GalleryThumbView::MarkItem);
1099 }
1100 // Cannot mark/unmark parent dir from this level
1101 else if (!m_menuState.m_selected->IsDevice()
1102 && m_menuState.m_selected != parent)
1103 {
1105 menu->AddItem(tr("Unmark Directory"), &GalleryThumbView::UnmarkItem);
1106 else
1107 menu->AddItem(tr("Mark Directory"), &GalleryThumbView::MarkItem);
1108 }
1109
1110 if (parent->m_id != GALLERY_DB_ID)
1111 {
1112 // Mark All if unmarked files exist
1114 menu->AddItem(tr("Mark All"), &GalleryThumbView::MarkAll);
1115
1116 // Unmark All if marked files exist
1117 if (!m_menuState.m_markedId.isEmpty())
1118 {
1119 menu->AddItem(tr("Unmark All"), &GalleryThumbView::UnmarkAll);
1120 menu->AddItem(tr("Invert Marked"), &GalleryThumbView::MarkInvertAll);
1121 }
1122 }
1123
1124 if (menu->IsEmpty())
1125 delete menu;
1126 else
1127 mainMenu->AddItem(tr("Mark"), nullptr, menu);
1128}
1129
1130
1136{
1137 // Can only copy/move into non-root dirs
1138 if (m_menuState.m_selected->IsDirectory()
1140 {
1141 // Operate on current marked files, if any
1143 if (files.isEmpty())
1145 if (files.isEmpty())
1146 return;
1147
1148 QString title = tr("%L1 marked").arg(files.size());
1149
1150 auto *menu = new MythMenu(title, this, "pastemenu");
1151
1152 menu->AddItem(tr("Move Marked Into"), &GalleryThumbView::Move);
1153 menu->AddItem(tr("Copy Marked Into"), qOverload<>(&GalleryThumbView::Copy));
1154
1155 mainMenu->AddItem(tr("Paste"), nullptr, menu);
1156 }
1157}
1158
1159
1165{
1166 // Operate on marked files, if any, otherwise selected node
1167 if (!m_menuState.m_markedId.isEmpty())
1168 {
1169 QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1170
1171 auto *menu = new MythMenu(title, this, "");
1172
1173 menu->AddItem(tr("Rotate Marked CW"), &GalleryThumbView::RotateCWMarked);
1174 menu->AddItem(tr("Rotate Marked CCW"), &GalleryThumbView::RotateCCWMarked);
1175 menu->AddItem(tr("Flip Marked Horizontal"), &GalleryThumbView::FlipHorizontalMarked);
1176 menu->AddItem(tr("Flip Marked Vertical"), &GalleryThumbView::FlipVerticalMarked);
1177 menu->AddItem(tr("Reset Marked to Exif"), &GalleryThumbView::ResetExifMarked);
1178
1179 mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1180 }
1181 else if (m_menuState.m_selected->IsFile())
1182 {
1183 auto *menu = new MythMenu(m_menuState.m_selected->m_baseName, this, "");
1184
1185 menu->AddItem(tr("Rotate CW"), &GalleryThumbView::RotateCW);
1186 menu->AddItem(tr("Rotate CCW"), &GalleryThumbView::RotateCCW);
1187 menu->AddItem(tr("Flip Horizontal"), &GalleryThumbView::FlipHorizontal);
1188 menu->AddItem(tr("Flip Vertical"), &GalleryThumbView::FlipVertical);
1189 menu->AddItem(tr("Reset to Exif"), &GalleryThumbView::ResetExif);
1190
1191 mainMenu->AddItem(tr("Transforms"), nullptr, menu);
1192 }
1193}
1194
1195
1201{
1202 MythMenu *menu = nullptr;
1203 ImagePtrK selected = m_menuState.m_selected;
1204
1205 // Operate on current marked files, if any
1206 if (!m_menuState.m_markedId.empty())
1207 {
1208 QString title = tr("%L1 marked").arg(m_menuState.m_markedId.size());
1209
1210 menu = new MythMenu(title, this, "actionmenu");
1211
1212 // Only offer Hide/Unhide if relevant
1214 menu->AddItem(tr("Hide Marked"), &GalleryThumbView::HideMarked);
1216 menu->AddItem(tr("Unhide Marked"), &GalleryThumbView::UnhideMarked);
1217
1218 menu->AddItem(tr("Delete Marked"), &GalleryThumbView::DeleteMarked);
1219 }
1220 else
1221 {
1222 // Operate on selected file/dir
1223 menu = new MythMenu(selected->m_baseName, this, "actionmenu");
1224
1225 // Prohibit actions on devices and parent dirs
1226 if (!selected->IsDevice() && selected != m_view->GetParent())
1227 {
1228 if (selected->m_isHidden)
1229 menu->AddItem(tr("Unhide"), &GalleryThumbView::Unhide);
1230 else
1231 menu->AddItem(tr("Hide"), &GalleryThumbView::HideItem);
1232
1233 menu->AddItem(tr("Use as Cover"), &GalleryThumbView::SetCover);
1234 menu->AddItem(tr("Delete"), &GalleryThumbView::DeleteItem);
1235 menu->AddItem(tr("Rename"), &GalleryThumbView::ShowRenameInput);
1236 }
1237 else if (selected->m_userThumbnail)
1238 {
1239 menu->AddItem(tr("Reset Cover"), &GalleryThumbView::ResetCover);
1240 }
1241 }
1242
1243 // Can only mkdir in a non-root dir
1244 if (selected->IsDirectory()
1245 && selected->m_id != GALLERY_DB_ID)
1246 menu->AddItem(tr("Create Directory"), &GalleryThumbView::MakeDir);
1247
1248 // Only show import command on root, when defined
1249 if (selected->m_id == GALLERY_DB_ID
1250 && !gCoreContext->GetSetting("GalleryImportCmd").isEmpty())
1251 menu->AddItem(tr("Import"), &GalleryThumbView::Import);
1252
1253 // Only show eject when devices (excluding import) exist
1254 if (selected->IsDevice() && selected->IsLocal())
1255 menu->AddItem(tr("Eject media"), &GalleryThumbView::Eject);
1256
1257 if (menu->IsEmpty())
1258 delete menu;
1259 else
1260 mainMenu->AddItem(tr("Actions"), nullptr, menu);
1261}
1262
1263
1269{
1270 int order = gCoreContext->GetNumSetting("GallerySlideOrder", kOrdered);
1271
1272 QString ordering;
1273 switch (order)
1274 {
1275 case kShuffle : ordering = tr("Shuffled"); break;
1276 case kRandom : ordering = tr("Random"); break;
1277 case kSeasonal : ordering = tr("Seasonal"); break;
1278 default:
1279 case kOrdered : ordering = tr("Ordered"); break;
1280 }
1281
1282 auto *menu = new MythMenu(tr("Slideshow") + " (" + ordering + ")",
1283 this, "SlideshowMenu");
1284
1285 // Use selected dir or parent, if image selected
1286 if (m_menuState.m_selected->IsDirectory())
1287 {
1288 if (m_menuState.m_selected->m_fileCount > 0)
1289 menu->AddItem(tr("Directory"), &GalleryThumbView::Slideshow);
1290
1291 if (m_menuState.m_selected->m_dirCount > 0)
1292 menu->AddItem(tr("Recursive"), &GalleryThumbView::RecursiveSlideshow);
1293 }
1294 else
1295 {
1296 menu->AddItem(tr("Current Directory"), &GalleryThumbView::Slideshow);
1297 }
1298
1299 auto *orderMenu = new MythMenu(tr("Slideshow Order"), this, "SlideOrderMenu");
1300
1301 orderMenu->AddItem(tr("Ordered"), nullptr, nullptr, order == kOrdered);
1302 orderMenu->AddItem(tr("Shuffled"), nullptr, nullptr, order == kShuffle);
1303 orderMenu->AddItem(tr("Random"), nullptr, nullptr, order == kRandom);
1304 orderMenu->AddItem(tr("Seasonal"), nullptr, nullptr, order == kSeasonal);
1305
1306 menu->AddItem(tr("Change Order"), nullptr, orderMenu);
1307
1308 if (gCoreContext->GetBoolSetting("GalleryRepeat", false))
1309 menu->AddItem(tr("Turn Repeat Off"), &GalleryThumbView::RepeatOff);
1310 else
1311 menu->AddItem(tr("Turn Repeat On"), &GalleryThumbView::RepeatOn);
1312
1313 mainMenu->AddItem(tr("Slideshow"), nullptr, menu);
1314}
1315
1316
1322{
1323 auto *menu = new MythMenu(tr("Show Options"), this, "showmenu");
1324
1325 int type = m_mgr.GetType();
1326 if (type == kPicAndVideo)
1327 {
1328 menu->AddItem(tr("Hide Pictures"), &GalleryThumbView::HidePictures);
1329 menu->AddItem(tr("Hide Videos"), &GalleryThumbView::HideVideos);
1330 }
1331 else
1332 {
1333 menu->AddItem(type == kPicOnly ? tr("Show Videos") : tr("Show Pictures"),
1335 }
1336
1337 int show = gCoreContext->GetNumSetting("GalleryImageCaption");
1338 auto *captionMenu = new MythMenu(tr("Image Captions"), this,
1339 "ImageCaptionMenu");
1340
1341 captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1342 captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1343 captionMenu->AddItem(tr("Comment"), nullptr, nullptr, show == kUserCaption);
1344 captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1345
1346 menu->AddItem(tr("Image Captions"), nullptr, captionMenu);
1347
1348 show = gCoreContext->GetNumSetting("GalleryDirCaption");
1349 captionMenu = new MythMenu(tr("Directory Captions"), this, "DirCaptionMenu");
1350
1351 captionMenu->AddItem(tr("Name"), nullptr, nullptr, show == kNameCaption);
1352 captionMenu->AddItem(tr("Date"), nullptr, nullptr, show == kDateCaption);
1353 captionMenu->AddItem(tr("None"), nullptr, nullptr, show == kNoCaption);
1354
1355 menu->AddItem(tr("Directory Captions"), nullptr, captionMenu);
1356
1357 if (m_editsAllowed)
1358 {
1359 if (m_mgr.GetVisibility())
1360 menu->AddItem(tr("Hide Hidden Items"), &GalleryThumbView::HideHidden);
1361 else
1362 menu->AddItem(tr("Show Hidden Items"), &GalleryThumbView::ShowHidden);
1363 }
1364
1365 if (m_zoomLevel > 0)
1366 menu->AddItem(tr("Zoom Out"), &GalleryThumbView::ZoomOut);
1367 if (m_zoomLevel < m_zoomWidgets.size() - 1)
1368 menu->AddItem(tr("Zoom In"), &GalleryThumbView::ZoomIn);
1369
1370 QString details = m_infoList.GetState() == kNoInfo
1371 ? tr("Show Details") : tr("Hide Details");
1372
1373 menu->AddItem(details, &GalleryThumbView::ShowDetails);
1374
1375 mainMenu->AddItem(tr("Show"), nullptr, menu);
1376}
1377
1378
1384{
1385 // Only update selection if image is currently displayed
1386 if (m_view->Select(id, -1))
1388}
1389
1390
1396{
1397 if (!item)
1398 return;
1399
1400 auto im = item->GetData().value<ImagePtrK>();
1401 if (!im)
1402 return;
1403
1404 switch (im->m_type)
1405 {
1406 case kDevice:
1407 case kCloneDir:
1408 case kDirectory:
1409 if (im == m_view->GetParent())
1410 DirSelectUp();
1411 else
1412 DirSelectDown();
1413 break;
1414
1415 case kImageFile:
1416 case kVideoFile:
1418 };
1419}
1420
1421
1427{
1428 QString err = m_mgr.ScanImagesAction(start);
1429 if (!err.isEmpty())
1430 ShowOkPopup(err);
1431}
1432
1433
1439{
1440 ImagePtrK selected = m_view->GetSelected();
1441 if (!selected)
1442 return;
1443
1445 auto *slide = new GallerySlideView(mainStack, "galleryslideview",
1447 if (slide->Create())
1448 {
1449 mainStack->AddScreen(slide);
1450
1451 // Update selected item when slideshow exits
1452 connect(slide, &GallerySlideView::ImageSelected,
1454
1455 if (selected->IsDirectory())
1456 {
1457 // Show selected dir
1458 slide->Start(mode, selected->m_id);
1459 }
1460 else
1461 {
1462 // Show current dir starting at selection
1463 slide->Start(mode, selected->m_parentId, selected->m_id);
1464 }
1465 }
1466 else
1467 {
1468 delete slide;
1469 }
1470}
1471
1472
1477{
1478 ImagePtrK im = m_view->GetParent();
1479 if (im)
1480 {
1481 LOG(VB_GUI, LOG_DEBUG, LOC +
1482 QString("Going up from %1").arg(im->m_filePath));
1483
1484 // Select the upfolder in the higher dir
1485 m_view->Select(im->m_id);
1486
1487 // Create tree rooted at parent of the kUpFolder directory node
1488 LoadData(im->m_parentId);
1489 }
1490 return true;
1491}
1492
1493
1498{
1500 if (im)
1501 {
1502 LOG(VB_GUI, LOG_DEBUG, LOC +
1503 QString("Going down to %1").arg(im->m_filePath));
1504
1505 // Create tree rooted at selected item
1506 LoadData(im->m_id);
1507 }
1508}
1509
1510
1516{
1518 if (im)
1519 {
1520 // Mark/unmark selected item
1521 m_view->Mark(im->m_id, mark);
1522
1523 // Redisplay buttonlist as a parent dir may have been unmarked
1525 }
1526}
1527
1528
1534{
1535 if (mark)
1536 m_view->MarkAll();
1537 else
1539
1540 // Redisplay buttonlist
1542}
1543
1544
1549{
1551
1552 // Redisplay buttonlist
1554}
1555
1556
1562{
1564 if (im && m_editsAllowed)
1565 {
1566 ImageIdList ids;
1567 ids.append(im->m_id);
1568 QString err = m_mgr.ChangeOrientation(transform, ids);
1569 if (!err.isEmpty())
1570 ShowOkPopup(err);
1571 }
1572}
1573
1574
1580{
1581 QString err = m_mgr.ChangeOrientation(transform, m_menuState.m_markedId);
1582 if (!err.isEmpty())
1583 ShowOkPopup(err);
1584}
1585
1586
1592{
1594 {
1595 ImageIdList ids;
1596 ids.append(m_menuState.m_selected->m_id);
1597
1598 QString err = m_mgr.HideFiles(hide, ids);
1599 if (!err.isEmpty())
1600 {
1601 ShowOkPopup(err);
1602 }
1603 else if (hide && !m_mgr.GetVisibility())
1604 {
1605 // Unmark invisible file
1606 m_view->Mark(m_menuState.m_selected->m_id, false);
1607 }
1608 }
1609}
1610
1611
1617{
1618 QString err = m_mgr.HideFiles(hide, m_menuState.m_markedId);
1619 if (!err.isEmpty())
1620 {
1621 ShowOkPopup(err);
1622 }
1623 else if (hide && !m_mgr.GetVisibility())
1624 {
1625 // Unmark invisible files
1626 for (int id : std::as_const(m_menuState.m_markedId))
1627 m_view->Mark(id, false);
1628 }
1629}
1630
1631
1636{
1638 ShowDialog(tr("Do you want to delete\n%1 ?")
1639 .arg(m_menuState.m_selected->m_baseName), "ConfirmDelete");
1640}
1641
1642
1647{
1648 ShowDialog(tr("Do you want to delete all marked files ?"),
1649 "ConfirmDeleteMarked");
1650}
1651
1652
1657{
1658 // Show settings dialog
1659 auto *config = new GallerySettings(m_editsAllowed);
1661 auto *ssd = new StandardSettingDialog(mainStack, "gallerysettings", config);
1662 if (!ssd->Create())
1663 {
1664 delete ssd;
1665 return;
1666 }
1667
1668 mainStack->AddScreen(ssd);
1669
1670 // Effect setting changes when dialog saves on exit
1671
1672 connect(config, &GallerySettings::ClearDbPressed,
1674
1675 connect(config, &GallerySettings::OrderChanged,
1676 this, [this]()
1677 {
1678 // Update db view, reset cover cache & reload
1679 int sortIm = gCoreContext->GetNumSetting("GalleryImageOrder");
1680 int sortDir = gCoreContext->GetNumSetting("GalleryDirOrder");
1681 m_mgr.SetSortOrder(sortIm, sortDir);
1682 m_view->ClearCache();
1684 });
1685
1686 connect(config, &GallerySettings::DateChanged,
1687 this, [this]()
1688 {
1689 QString date = gCoreContext->GetSetting("GalleryDateFormat");
1690 m_mgr.SetDateFormat(date);
1692 });
1693
1694 connect(config, &GallerySettings::ExclusionsChanged,
1695 this, [this]()
1696 {
1697 // Request rescan
1698 QString exclusions = gCoreContext->GetSetting("GalleryIgnoreFilter");
1699 m_view->ClearCache();
1700 ImageManagerFe::IgnoreDirs(exclusions);
1701 });
1702}
1703
1704
1710{
1711 gCoreContext->SaveBoolSetting("GalleryShowHidden", show);
1712
1713 // Update Db(s)
1715
1716 // Reset dir thumbnail cache
1717 m_view->ClearCache();;
1718
1720}
1721
1722
1728void GalleryThumbView::ShowDialog(const QString& msg, const QString& event)
1729{
1730 auto *popup = new MythConfirmationDialog(&m_popupStack, msg, true);
1731
1732 if (popup->Create())
1733 {
1734 popup->SetReturnEvent(this, event);
1735 m_popupStack.AddScreen(popup);
1736 }
1737 else
1738 {
1739 delete popup;
1740 }
1741}
1742
1743
1748{
1750 {
1751 QString base = QFileInfo(m_menuState.m_selected->m_baseName).completeBaseName();
1752 QString msg = tr("Enter a new name:");
1753 auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone,
1754 false, base);
1755 if (popup->Create())
1756 {
1757 popup->SetReturnEvent(this, "FileRename");
1758 m_popupStack.AddScreen(popup);
1759 }
1760 else
1761 {
1762 delete popup;
1763 }
1764 }
1765}
1766
1767
1772{
1774}
1775
1776
1781{
1782 QString msg = tr("Enter password:");
1783 auto *popup = new MythTextInputDialog(&m_popupStack, msg, FilterNone, true);
1784 if (popup->Create())
1785 {
1786 popup->SetReturnEvent(this, "Password");
1787 m_popupStack.AddScreen(popup);
1788 }
1789 else
1790 {
1791 delete popup;
1792 }
1793}
1794
1795
1800{
1801 gCoreContext->SaveSetting("GalleryShowType", type);
1802
1803 // Update Db(s)
1805
1806 // Reset dir thumbnail cache
1807 m_view->ClearCache();
1808
1810}
1811
1812
1818{
1820 {
1821 QString err = reset ? m_mgr.SetCover(m_menuState.m_selected->m_id, 0)
1822 : m_mgr.SetCover(m_menuState.m_selected->m_parentId,
1823 m_menuState.m_selected->m_id);
1824 if (!err.isEmpty())
1825 ShowOkPopup(err);
1826 }
1827}
1828
1829
1834{
1835 SelectZoomWidget(-1);
1837}
1838
1839
1844{
1847}
1848
1849
1855{
1856 m_zoomLevel += change;
1857
1858 // constrain to zoom levels supported by theme
1859 m_zoomLevel = std::max(m_zoomLevel, 0);
1860 if (m_zoomLevel >= m_zoomWidgets.size())
1861 m_zoomLevel = m_zoomWidgets.size() - 1;
1862
1863 // Store any requested change, but not constraining adjustments
1864 // Thus, changing to a theme with fewer zoom levels will not overwrite the
1865 // setting
1866 if (change != 0)
1867 gCoreContext->SaveSetting("GalleryZoomLevel", m_zoomLevel);
1868
1869 // dump the current list widget
1870 if (m_imageList)
1871 {
1872 m_imageList->SetVisible(false);
1873 disconnect(m_imageList, nullptr, this, nullptr);
1874 }
1875
1876 // initialise new list widget
1878
1879 m_imageList->SetVisible(true);
1881
1882 // Monitor list actions (after focus events have been ignored)
1887}
1888
1889
1894{
1895 auto *popup = new MythTextInputDialog(&m_popupStack,
1896 tr("Enter name of new directory"),
1897 FilterNone, false);
1898 if (popup->Create())
1899 {
1900 popup->SetReturnEvent(this, "MakeDir");
1901 m_popupStack.AddScreen(popup);
1902 }
1903 else
1904 {
1905 delete popup;
1906 }
1907}
1908
1909
1914{
1916 if (dir)
1917 m_mgr.CloseDevices(dir->m_device, true);
1918}
1919
1920
1930void GalleryThumbView::Copy(bool deleteAfter)
1931{
1932 // Destination must be a dir
1934 if (!destDir || destDir->IsFile())
1935 return;
1936
1937 // Use current markings, if any. Otherwise use previous markings
1939 if (markedIds.isEmpty())
1940 {
1941 markedIds = m_menuState.m_prevMarkedId;
1942 if (markedIds.isEmpty())
1943 {
1944 ShowOkPopup(tr("No files specified"));
1945 return;
1946 }
1947 }
1948
1949 // Get all files/dirs in subtree(s). Only files are copied
1950 ImageList files;
1951 ImageList dirs;
1952 m_mgr.GetDescendants(markedIds, files, dirs);
1953
1954 if (dirs.isEmpty() && files.isEmpty())
1955 {
1956 ShowOkPopup(tr("No images"));
1957 // Nothing to clean up
1958 return;
1959 }
1960
1961 // Child dirs appear before their subdirs. If no dirs, images are all direct children
1962 ImagePtrK aChild = dirs.isEmpty() ? files[0] : dirs[0];
1963
1964 // Determine parent path including trailing /
1965 int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
1966
1967 // Update filepaths for Db & generate URLs for filesystem copy
1968 // Only copy files, destination dirs will be created automatically
1970 for (const ImagePtr & im : std::as_const(files))
1971 {
1972 // Replace base path with destination path
1973 im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath,
1974 im->m_filePath.mid(basePathSize));
1975
1976 transfers.insert(im, m_mgr.BuildTransferUrl(im->m_filePath,
1977 destDir->IsLocal()));
1978 }
1979
1980 // Create progress dialog
1981 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1982 auto *progress = new MythUIProgressDialog(tr("Copying files"), popupStack,
1983 "copydialog");
1984 if (progress->Create())
1985 popupStack->AddScreen(progress, false);
1986 else
1987 {
1988 delete progress;
1989 progress = nullptr;
1990 }
1991
1992 // Copy files in a servant thread
1993 TransferThread copy(transfers, false, progress);
1995 TransferThread::ImageSet failed = copy.GetResult();
1996
1997 if (progress)
1998 progress->Close();
1999
2000 if (!failed.isEmpty())
2001 ShowOkPopup(tr("Failed to copy %L1/%Ln file(s)", nullptr, transfers.size())
2002 .arg(failed.size()));
2003
2004 // Don't update Db for files that failed
2005 for (const ImagePtrK & im : std::as_const(failed))
2006 transfers.remove(im);
2007
2008 ImageListK newImages = transfers.keys();
2009
2010 // Include dirs
2011 QStringList dirPaths;
2012 for (const ImagePtr & im : std::as_const(dirs))
2013 {
2014 QString relPath = im->m_filePath.mid(basePathSize);
2015
2016 dirPaths << relPath;
2017
2018 // Replace base path with destination path
2019 im->m_filePath = ImageManagerFe::ConstructPath(destDir->m_filePath, relPath);
2020
2021 // Append dirs so that hidden state & cover is preserved for new dirs
2022 // Pre-existing dirs will take precedance over these.
2023 newImages.append(im);
2024 }
2025
2026 // Copy empty dirs as well (will fail for non-empty dirs)
2027 if (!dirPaths.isEmpty())
2028 m_mgr.MakeDir(destDir->m_id, dirPaths, false);
2029
2030 if (!newImages.isEmpty())
2031 {
2032 // Update Db
2033 m_mgr.CreateImages(destDir->m_id, newImages);
2034
2035 if (deleteAfter)
2036 {
2037 // Delete files/dirs that have been successfully copied
2038 // Will fail for dirs containing images that failed to copy
2039 ImageIdList ids;
2040 for (const ImagePtrK & im : std::as_const(newImages))
2041 ids << im->m_id;
2042
2043 m_mgr.DeleteFiles(ids);
2044 }
2045 }
2046}
2047
2048
2059{
2060 // Destination must be a dir
2062 if (!destDir || destDir->IsFile())
2063 return;
2064
2065 // Use current markings, if any. Otherwise use previous markings
2067 if (markedIds.isEmpty())
2068 {
2069 markedIds = m_menuState.m_prevMarkedId;
2070 if (markedIds.isEmpty())
2071 {
2072 ShowOkPopup(tr("No files specified"));
2073 return;
2074 }
2075 }
2076
2077 // Note UI mandates that transferees are either all local or all remote
2078 if (destDir->IsLocal() != ImageItem::IsLocalId(markedIds[0]))
2079 {
2080 // Moves between hosts require copy/delete
2081 Copy(true);
2082 return;
2083 }
2084
2085 // Get marked images. Each file and dir will be renamed
2086 ImageList files;
2087 ImageList dirs;
2088 if (m_mgr.GetImages(markedIds, files, dirs) <= 0)
2089 {
2090 ShowOkPopup(tr("No images specified"));
2091 // Nothing to clean up
2092 return;
2093 }
2094 ImageList images;
2095 if (!dirs.isEmpty())
2096 images += dirs;
2097 if (!files.isEmpty())
2098 images += files;
2099
2100 // Determine parent from first dir or pic
2101 ImagePtr aChild = images[0];
2102
2103 // Determine parent path including trailing /
2104 // Note UI mandates that transferees all have same parent.
2105 int basePathSize = aChild->m_filePath.size() - aChild->m_baseName.size();
2106 QString parentPath = aChild->m_filePath.left(basePathSize);
2107
2108 // Determine destination URLs
2110 for (const QSharedPointer<ImageItem> & im : std::as_const(images))
2111 {
2112 // Replace base path with destination path
2113 QString newPath = ImageManagerFe::ConstructPath(destDir->m_filePath,
2114 im->m_filePath.mid(basePathSize));
2115
2116 transfers.insert(im, m_mgr.BuildTransferUrl(newPath, aChild->IsLocal()));
2117 }
2118
2119 // Create progress dialog
2120 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
2121 auto *progress = new MythUIProgressDialog(tr("Moving files"), popupStack,
2122 "movedialog");
2123
2124 if (progress->Create())
2125 popupStack->AddScreen(progress, false);
2126 else
2127 {
2128 delete progress;
2129 progress = nullptr;
2130 }
2131
2132 // Move files in a servant thread
2133 TransferThread move(transfers, true, progress);
2134 WaitUntilDone(move);
2135 TransferThread::ImageSet failed = move.GetResult();
2136
2137 if (progress)
2138 progress->Close();
2139
2140 if (!failed.isEmpty())
2141 ShowOkPopup(tr("Failed to move %L1/%Ln file(s)", nullptr, transfers.size())
2142 .arg(failed.size()));
2143
2144 // Don't update Db for files that failed
2145 for (const ImagePtrK & im : std::as_const(failed))
2146 transfers.remove(im);
2147
2148 if (!transfers.isEmpty())
2149 {
2150 ImageListK moved = transfers.keys();
2151
2152 // Unmark moved files
2153 for (const ImagePtrK & im : std::as_const(moved))
2154 m_view->Mark(im->m_id, false);
2155
2156 // Update Db
2157 m_mgr.MoveDbImages(destDir, moved, parentPath);
2158 }
2159}
2160
2161
2166{
2167 QString path = m_mgr.CreateImport();
2168 if (path.isEmpty())
2169 {
2170 ShowOkPopup(tr("Failed to create temporary directory."));
2171 return;
2172 }
2173
2174 // Replace placeholder in command
2175 QString cmd = gCoreContext->GetSetting("GalleryImportCmd");
2176 cmd.replace("%TMPDIR%", path);
2177
2178 // Run command in a separate thread
2179 MythUIBusyDialog *busy =
2180 ShowBusyPopup(tr("Running Import command.\nPlease wait..."));
2181
2182 ShellThread thread(cmd, path);
2183 WaitUntilDone(thread);
2184
2185 if (busy)
2186 busy->Close();
2187
2188 int error = thread.GetResult();
2189 if (error != 0)
2190 ShowOkPopup(tr("Import command failed.\nError: %1").arg(error));
2191
2192 // Rescan local devices
2193 QString err = m_mgr.ScanImagesAction(true, true);
2194 if (!err.isEmpty())
2195 LOG(VB_GENERAL, LOG_ERR, LOC + err);
2196}
2197
2199{
2200 gCoreContext->SaveSetting("GalleryRepeat", on);
2201}
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:178
bool isFinished(void) const
Definition: mthread.cpp:240
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:265
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:191
QString objectName(void) const
Definition: mthread.cpp:225
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