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