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