MythTV master
mythuifilebrowser.cpp
Go to the documentation of this file.
1#include <chrono>
2#include <utility>
3
4#include <QCoreApplication>
5#include <QFileInfo>
6#include <QImageReader>
7#include <QString>
8#include <QStringList>
9#include <QTimer>
10#include <QUrl>
11
14
15#include "mythdialogbox.h"
16#include "mythfontproperties.h"
17#include "mythmainwindow.h"
18#include "mythuibutton.h"
19#include "mythuibuttonlist.h"
20#include "mythuifilebrowser.h"
21#include "mythuiimage.h"
22#include "mythuistatetype.h"
23#include "mythuitext.h"
24#include "mythuiutils.h"
25
27MFileInfo::MFileInfo(const QString& fileName, QString sgDir, bool isDir, qint64 size)
28{
29 init(fileName, std::move(sgDir), isDir, size);
30}
31
32void MFileInfo::init(const QString& fileName, QString sgDir, bool isDir,
33 qint64 size)
34{
36 m_isRemote = false;
37 m_isParentDir = false;
38
39 if (fileName.startsWith("myth://"))
40 {
41 QUrl qurl(fileName);
42 m_hostName = qurl.host();
43 m_storageGroup = qurl.userName();
44 m_storageGroupDir = std::move(sgDir);
45 m_subDir = qurl.path();
46
47 if (!qurl.fragment().isEmpty())
48 m_subDir += "#" + qurl.fragment();
49
50 if (m_subDir.startsWith("/"))
51 m_subDir.remove(0, 1);
52
53 m_isRemote = true;
54
55 m_isDir = isDir;
56 m_isFile = !isDir;
57 m_size = size;
58 }
59
60 if (!fileName.isEmpty())
61 QFileInfo::setFile(fileName);
62}
63
65 : QFileInfo(other)
66{
67 QString sgDir = other.storageGroupDir();
68 bool isDir = other.isDir();
69 qint64 size = other.size();
70 init(other.filePath(), sgDir, isDir, size);
71}
72
74{
75 if (this == &other)
76 return *this;
77
78 QString sgDir = other.storageGroupDir();
79 bool isDir = other.isDir();
80 qint64 size = other.size();
81 init(other.fileName(), sgDir, isDir, size);
82
83 return *this;
84}
85
86QString MFileInfo::fileName(void) const
87{
88 if (m_isRemote)
89 return m_fileName;
90 return QFileInfo::fileName();
91}
92
93QString MFileInfo::filePath(void) const
94{
95 if (m_isRemote)
96 return m_fileName;
97 return QFileInfo::filePath();
98}
99
100bool MFileInfo::isDir(void) const
101{
102 if (m_isRemote)
103 return m_isDir;
104 return QFileInfo::isDir();
105}
106
107bool MFileInfo::isFile(void) const
108{
109 if (m_isRemote)
110 return m_isFile;
111 return QFileInfo::isFile();
112}
113
115{
116 if (m_isRemote)
117 return m_isParentDir;
118 return (QFileInfo::fileName() == "..");
119}
120
122{
123 if (m_isRemote)
124 return false;
125 return QFileInfo::isExecutable();
126}
127
129{
130 if (m_isRemote)
131 return m_fileName;
132 return QFileInfo::absoluteFilePath();
133}
134
135qint64 MFileInfo::size(void) const
136{
137 if (m_isRemote)
138 return m_size;
139 return QFileInfo::size();
140}
141
143
152 const QString &startPath)
153 : MythScreenType(parent, "mythuifilebrowser"),
154 m_previewTimer(new QTimer(this))
155{
156 SetPath(startPath);
157
158 m_nameFilter.clear();
159 m_nameFilter << "*";
160
161 m_previewTimer->setSingleShot(true);
163}
164
165void MythUIFileBrowser::SetPath(const QString &startPath)
166{
167 if (startPath.startsWith("myth://"))
168 {
169 m_isRemote = true;
170
171 QUrl qurl(startPath);
172
173 if (!qurl.path().isEmpty())
174 {
175 // Force browing of remote SG's to start at their root
177 0,
178 "",
179 qurl.userName());
180
181 }
182 else
183 {
184 m_baseDirectory = startPath;
185
186 if (m_baseDirectory.endsWith("/"))
187 m_baseDirectory.remove(m_baseDirectory.length() - 1, 1);
188 }
189
190 m_subDirectory = "";
192 }
193 else
194 {
195 m_isRemote = false;
196 m_baseDirectory = "";
197 m_subDirectory = startPath;
198 }
199}
200
202{
203 if (!CopyWindowFromBase("MythFileBrowser", this))
204 return false;
205
206 m_fileList = dynamic_cast<MythUIButtonList *>(GetChild("filelist"));
207 m_locationEdit = dynamic_cast<MythUITextEdit *>(GetChild("location"));
208 m_okButton = dynamic_cast<MythUIButton *>(GetChild("ok"));
209 m_cancelButton = dynamic_cast<MythUIButton *>(GetChild("cancel"));
210 m_backButton = dynamic_cast<MythUIButton *>(GetChild("back"));
211 m_homeButton = dynamic_cast<MythUIButton *>(GetChild("home"));
212 m_previewImage = dynamic_cast<MythUIImage *>(GetChild("preview"));
213 m_infoText = dynamic_cast<MythUIText *>(GetChild("info"));
214 m_filenameText = dynamic_cast<MythUIText *>(GetChild("filename"));
215 m_fullpathText = dynamic_cast<MythUIText *>(GetChild("fullpath"));
216
218 {
219 LOG(VB_GENERAL, LOG_ERR, "MythUIFileBrowser: Your theme is missing"
220 " some UI elements! Bailing out.");
221 return false;
222 }
223
231
232 if (m_backButton)
234
235 if (m_homeButton)
237
240
241 return true;
242}
243
244void MythUIFileBrowser::SetReturnEvent(QObject *retobject,
245 const QString &resultid)
246{
247 m_retObject = retobject;
248 m_id = resultid;
249}
250
252{
253 if (m_previewImage)
255}
256
258{
259 if (!item)
260 return;
261
262 if (m_previewImage)
264
265 auto finfo = item->GetData().value<MFileInfo>();
266
267 if (finfo.isParentDir())
268 {
269 if (m_infoText)
270 m_infoText->Reset();
271
272 if (m_filenameText)
274
275 if (m_fullpathText)
277 }
278 else
279 {
280 if (IsImage(finfo.suffix()) && m_previewImage)
281 {
282 m_previewImage->SetFilename(finfo.absoluteFilePath());
283 m_previewTimer->start(250ms);
284 }
285
286 if (m_infoText)
287 m_infoText->SetText(FormatSize(finfo.size()));
288
289 if (m_filenameText)
290 m_filenameText->SetText(finfo.fileName());
291
292 if (m_fullpathText)
293 m_fullpathText->SetText(finfo.absoluteFilePath());
294 }
295}
296
298{
299 if (!item)
300 return;
301
302 auto finfo = item->GetData().value<MFileInfo>();
303
304 if (finfo.isFile())
305 {
306 if (m_retObject)
307 {
308 auto *dce = new DialogCompletionEvent(m_id, 0, finfo.filePath(),
309 item->GetData());
310 QCoreApplication::postEvent(m_retObject, dce);
311 }
312
313 Close();
314 return;
315 }
316
317 if (!finfo.isDir())
318 return;
319
320 if (finfo.isParentDir())
321 {
322 backPressed();
323 }
324 else
325 {
326 if (finfo.isRemote())
327 {
328 m_subDirectory = finfo.subDir();
329 m_storageGroupDir = finfo.storageGroupDir();
330 }
331 else
332 {
333 m_subDirectory = finfo.filePath();
335 }
336 }
337
339}
340
341bool MythUIFileBrowser::IsImage(QString extension)
342{
343 if (extension.isEmpty())
344 return false;
345
346 extension = extension.toLower();
347
348 QList<QByteArray> formats = QImageReader::supportedImageFormats();
349
350 return formats.contains(extension.toLatin1());
351}
352
354{
355 QString newPath = m_locationEdit->GetText();
356
357 SetPath(newPath);
358
360}
361
363{
364 if (m_isRemote)
365 {
367
368 if (m_subDirectory.startsWith(m_baseDirectory))
369 {
370 m_subDirectory.remove(0, m_baseDirectory.length());
371
372 if (m_subDirectory.startsWith("/"))
373 m_subDirectory.remove(0, 1);
374 }
375
377 }
378 else
379 {
380 // move up one directory
381 int pos = m_subDirectory.lastIndexOf('/');
382
383 if (pos > 0)
384 m_subDirectory = m_subDirectory.left(pos);
385 else
386 m_subDirectory = "/";
387 }
388
390}
391
393{
394 if (m_isRemote)
395 {
396 m_subDirectory = "";
398 }
399 else
400 {
401 m_subDirectory = qEnvironmentVariable("HOME");
402 }
403
405}
406
408{
410
411 if (m_retObject)
412 {
413 QString selectedPath = m_locationEdit->GetText();
414 QVariant vData;
415 if (item != nullptr)
416 vData = item->GetData();
417 auto *dce = new DialogCompletionEvent(m_id, 0, selectedPath,
418 vData);
419 QCoreApplication::postEvent(m_retObject, dce);
420 }
421
422 Close();
423}
424
426{
427 Close();
428}
429
431{
432 m_fileList->Reset();
433
434 if (m_isRemote)
436 else
438}
439
441{
442 QStringList sgdirlist;
443 QString sgdir;
444 QStringList slist;
445
446 if (!m_baseDirectory.endsWith("/"))
447 m_baseDirectory.append("/");
448
449 QString dirURL = QString("%1%2").arg(m_baseDirectory, m_subDirectory);
450
451 if (!GetRemoteFileList(m_baseDirectory, sgdir, sgdirlist))
452 {
453 LOG(VB_GENERAL, LOG_ERR, "GetRemoteFileList failed to get "
454 "Storage Group dirs");
455 return;
456 }
457
458 if ((sgdirlist.size() == 1) &&
459 (sgdirlist[0].startsWith("sgdir::")))
460 {
461 QStringList tokens = sgdirlist[0].split("::");
462
463 m_storageGroupDir = tokens[1];
464 }
465
466 if (!GetRemoteFileList(dirURL, m_storageGroupDir, slist))
467 {
468 LOG(VB_GENERAL, LOG_ERR,
469 QString("GetRemoteFileList failed for '%1' in '%2' SG dir")
470 .arg(dirURL, m_storageGroupDir));
471 return;
472 }
473
474 m_locationEdit->SetText(dirURL);
475
476 QString displayName;
477 QString dataName;
478 QString type;
479
480 if ((sgdirlist.size() > 1 && !m_storageGroupDir.isEmpty()) ||
481 (!m_subDirectory.isEmpty()))
482 {
483 displayName = tr("Parent");
484 type = "upfolder";
485
487
488 if (!m_subDirectory.isEmpty())
489 {
491
492 int pos = m_parentDir.lastIndexOf('/');
493
494 if (pos > 0)
495 m_parentDir = m_parentDir.left(pos);
496 }
497
498
501
502 if (m_subDirectory.isEmpty() && m_parentDir == m_baseDirectory)
503 {
504 finfo.setSGDir("");
505 m_parentSGDir = "";
506 }
507
508 auto *item = new MythUIButtonListItem(m_fileList, displayName,
509 QVariant::fromValue(finfo));
510
511 item->SetText(QString("0"), "filesize");
512 item->SetText(m_parentDir, "fullpath");
513 item->DisplayState(type, "nodetype");
514
515 if (m_backButton)
517 }
518 else
519 {
520 if (m_backButton)
521 m_backButton->SetEnabled(false);
522 }
523
524 for (const auto & line : std::as_const(slist))
525 {
526 QStringList tokens = line.split("::");
527
528 if (tokens.size() < 2)
529 {
530 LOG(VB_GENERAL, LOG_ERR, QString("failed to parse '%1'.").arg(line));
531 continue;
532 }
533
534 displayName = tokens[1];
535
536 if (tokens[0] == "sgdir")
537 dataName = m_baseDirectory;
538 else if (m_subDirectory.isEmpty())
539 {
540 dataName = QString("%1%2").arg(m_baseDirectory, displayName);
541 }
542 else
543 {
544 dataName = QString("%1%2/%3")
545 .arg(m_baseDirectory, m_subDirectory, displayName);
546 }
547
548 MFileInfo finfo(dataName, m_storageGroupDir);
549
550 if ((tokens[0] == "dir") &&
551 ((m_typeFilter & (QDir::Dirs | QDir::AllDirs)) != 0))
552 {
553 type = "folder";
554 finfo.setIsDir(true);
556 finfo.setSize(0);
557 }
558 else if ((tokens[0] == "sgdir") &&
559 ((m_typeFilter & (QDir::Dirs | QDir::AllDirs)) != 0))
560 {
561 type = "folder";
562 finfo.setIsDir(true);
563 finfo.setSGDir(displayName);
564 finfo.setSize(0);
565 }
566 else if ((tokens[0] == "file") &&
567 ((m_typeFilter & QDir::Files) != 0))
568 {
569 finfo.setIsDir(false);
570 finfo.setSize(tokens[2].toInt());
571
572 if (IsImage(finfo.suffix()))
573 type = "image";
574 else
575 type = "file";
576 }
577 else
578 {
579 // unknown type or filtered out
580 continue;
581 }
582
583 auto *item = new MythUIButtonListItem(m_fileList, displayName,
584 QVariant::fromValue(finfo));
585
586 if (finfo.size())
587 item->SetText(FormatSize(finfo.size()), "filesize");
588
589 if (type == "image")
590 item->SetImage(dataName);
591
592 item->SetText(dataName, "fullpath");
593 item->DisplayState(type, "nodetype");
594 }
595}
596
598{
599 QDir d;
600
601 d.setPath(m_subDirectory);
602 d.setNameFilters(m_nameFilter);
603 d.setFilter(m_typeFilter);
604 d.setSorting(QDir::Name | QDir::DirsFirst | QDir::IgnoreCase);
605
606 if (!d.exists())
607 {
608 LOG(VB_GENERAL, LOG_ERR,
609 "MythUIFileBrowser: current directory does not exist!");
611 m_subDirectory = "/";
612 d.setPath("/");
613 }
614
615 QFileInfoList list = d.entryInfoList();
616 bool showBackButton = false;
617
618 if (list.isEmpty())
619 {
620 auto *item = new MythUIButtonListItem(m_fileList,
621 tr("Parent Directory"));
622 item->DisplayState("upfolder", "nodetype");
623 }
624 else
625 {
626 for (const auto & fi : std::as_const(list))
627 {
628 MFileInfo finfo(fi.filePath());
629
630 if (finfo.fileName() == ".")
631 continue;
632
633 QString displayName = finfo.fileName();
634 QString type;
635
636 if (displayName == "..")
637 {
638 if (m_subDirectory.endsWith("/"))
639 continue;
640
641 displayName = tr("Parent");
642 type = "upfolder";
643 showBackButton = true;
644 }
645 else if (finfo.isDir())
646 {
647 type = "folder";
648 }
649 else if (finfo.isExecutable())
650 {
651 type = "executable";
652 }
653 else if (finfo.isFile())
654 {
655 type = "file";
656 }
657
658 auto *item = new MythUIButtonListItem(m_fileList, displayName,
659 QVariant::fromValue(finfo));
660
661 if (IsImage(finfo.suffix()))
662 {
663 item->SetImage(finfo.absoluteFilePath());
664 type = "image";
665 }
666
667 item->SetText(FormatSize(finfo.size()), "filesize");
668 item->SetText(finfo.absoluteFilePath(), "fullpath");
669 item->DisplayState(type, "nodetype");
670 }
671 }
672
673 if (m_backButton)
674 m_backButton->SetEnabled(showBackButton);
675
677}
678
679QString MythUIFileBrowser::FormatSize(int64_t size)
680{
681 QString filesize("%L1 %2");
682
683 if (size < 1000000)
684 filesize = filesize.arg((double)size / 1000.0, 0, 'f', 0).arg("KB");
685 else if (size < 1000000000)
686 filesize = filesize.arg((double)size / 1000000.0, 0, 'f', 1).arg("MB");
687 else
688 filesize = filesize.arg((double)size / 1000000000.0, 0, 'f', 1).arg("GB");
689
690 return filesize;
691}
692
694 const QString &sgDir,
695 QStringList &list)
696{
697 QUrl qurl(url);
698 QString storageGroup = qurl.userName();
699
700 list.clear();
701
702 if (storageGroup.isEmpty())
703 storageGroup = "Default";
704
705 list << "QUERY_SG_GETFILELIST";
706 list << qurl.host();
707 list << storageGroup;
708
709 QString path = sgDir + qurl.path();
710
711 if (!qurl.fragment().isEmpty())
712 path += "#" + qurl.fragment();
713
714 list << path;
715 list << "0";
716
717 bool ok = gCoreContext->SendReceiveStringList(list);
718
719 if ((list.size() == 1) && (list[0] == "EMPTY LIST"))
720 list.clear();
721
722 return ok;
723
724}
725
726/* vim: set expandtab tabstop=4 shiftwidth=4: */
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
QString m_fileName
QString m_storageGroupDir
bool isFile(void) const
void setIsDir(bool isDir)
QString m_subDir
QString m_hostName
bool isParentDir(void) const
MFileInfo & operator=(const MFileInfo &other)
QString fileName(void) const
MFileInfo(const QString &fileName="", QString sgDir="", bool isDir=false, qint64 size=0)
void init(const QString &fileName="", QString sgDir="", bool isDir=false, qint64 size=0)
void setSize(qint64 size)
qint64 size(void) const
void setSGDir(QString sgDir)
QString m_storageGroup
bool isExecutable(void) const
QString absoluteFilePath(void) const
bool isDir(void) const
QString storageGroupDir(void) const
QString filePath(void) const
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
virtual void Close()
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
MythUIButtonListItem * GetItemCurrent() const
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)
A single button widget.
Definition: mythuibutton.h:22
void Clicked()
MythUIButton * m_homeButton
static bool IsImage(QString extension)
MythUIFileBrowser(MythScreenStack *parent, const QString &startPath)
Browse a local filesystem or remote Storage Group Returns the selected file.
bool Create(void) override
MythUIImage * m_previewImage
void SetPath(const QString &startPath)
void updateRemoteFileList(void)
QDir::Filters m_typeFilter
void PathClicked(MythUIButtonListItem *item)
MythUITextEdit * m_locationEdit
MythUIText * m_infoText
MythUIButtonList * m_fileList
MythUIButton * m_cancelButton
MythUIText * m_fullpathText
MythUIButton * m_okButton
MythUIButton * m_backButton
void SetReturnEvent(QObject *retobject, const QString &resultid)
MythUIText * m_filenameText
static QString FormatSize(int64_t size)
void PathSelected(MythUIButtonListItem *item)
QStringList m_nameFilter
void updateLocalFileList(void)
static bool GetRemoteFileList(const QString &url, const QString &sgDir, QStringList &list)
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:98
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
void Reset(void) override
Reset the image back to the default defined in the theme.
A text entry and edit widget.
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
All purpose text widget, displays a text string.
Definition: mythuitext.h:29
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 SetEnabled(bool enable)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:138
void LosingFocus(void)
static bool CopyWindowFromBase(const QString &windowname, MythScreenType *win)
static const iso6937table * d
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189