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  qVariantFromValue(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  dataName = QString("%1%2").arg(m_baseDirectory)
532  .arg(displayName);
533  else
534  dataName = QString("%1%2/%3").arg(m_baseDirectory)
535  .arg(m_subDirectory).arg(displayName);
536 
537  MFileInfo finfo(dataName, m_storageGroupDir);
538 
539  if ((tokens[0] == "dir") &&
540  ((m_typeFilter & (QDir::Dirs | QDir::AllDirs)) != 0))
541  {
542  type = "folder";
543  finfo.setIsDir(true);
545  finfo.setSize(0);
546  }
547  else if ((tokens[0] == "sgdir") &&
548  ((m_typeFilter & (QDir::Dirs | QDir::AllDirs)) != 0))
549  {
550  type = "folder";
551  finfo.setIsDir(true);
552  finfo.setSGDir(displayName);
553  finfo.setSize(0);
554  }
555  else if ((tokens[0] == "file") &&
556  ((m_typeFilter & QDir::Files) != 0))
557  {
558  finfo.setIsDir(false);
559  finfo.setSize(tokens[2].toInt());
560 
561  if (IsImage(finfo.suffix()))
562  type = "image";
563  else
564  type = "file";
565  }
566  else
567  {
568  // unknown type or filtered out
569  ++it;
570  continue;
571  }
572 
573  auto *item = new MythUIButtonListItem(m_fileList, displayName,
574  qVariantFromValue(finfo));
575 
576  if (finfo.size())
577  item->SetText(FormatSize(finfo.size()), "filesize");
578 
579  if (type == "image")
580  item->SetImage(dataName);
581 
582  item->SetText(dataName, "fullpath");
583  item->DisplayState(type, "nodetype");
584 
585  ++it;
586  }
587 }
588 
590 {
591  QDir d;
592 
593  d.setPath(m_subDirectory);
594  d.setNameFilters(m_nameFilter);
595  d.setFilter(m_typeFilter);
596  d.setSorting(QDir::Name | QDir::DirsFirst | QDir::IgnoreCase);
597 
598  if (!d.exists())
599  {
600  LOG(VB_GENERAL, LOG_ERR,
601  "MythUIFileBrowser: current directory does not exist!");
602  m_locationEdit->SetText("/");
603  m_subDirectory = "/";
604  d.setPath("/");
605  }
606 
607  QFileInfoList list = d.entryInfoList();
608  bool showBackButton = false;
609 
610  if (list.isEmpty())
611  {
612  auto *item = new MythUIButtonListItem(m_fileList,
613  tr("Parent Directory"));
614  item->DisplayState("upfolder", "nodetype");
615  }
616  else
617  {
618  QFileInfoList::const_iterator it = list.begin();
619 
620  while (it != list.end())
621  {
622  const QFileInfo *fi = &(*it);
623  MFileInfo finfo(fi->filePath());
624 
625  if (finfo.fileName() == ".")
626  {
627  ++it;
628  continue;
629  }
630 
631  QString displayName = finfo.fileName();
632  QString type;
633 
634  if (displayName == "..")
635  {
636  if (m_subDirectory.endsWith("/"))
637  {
638  ++it;
639  continue;
640  }
641 
642  displayName = tr("Parent");
643  type = "upfolder";
644  showBackButton = true;
645  }
646  else if (finfo.isDir())
647  {
648  type = "folder";
649  }
650  else if (finfo.isExecutable())
651  {
652  type = "executable";
653  }
654  else if (finfo.isFile())
655  {
656  type = "file";
657  }
658 
659  auto *item = new MythUIButtonListItem(m_fileList, displayName,
660  qVariantFromValue(finfo));
661 
662  if (IsImage(finfo.suffix()))
663  {
664  item->SetImage(finfo.absoluteFilePath());
665  type = "image";
666  }
667 
668  item->SetText(FormatSize(finfo.size()), "filesize");
669  item->SetText(finfo.absoluteFilePath(), "fullpath");
670  item->DisplayState(type, "nodetype");
671 
672  ++it;
673  }
674  }
675 
676  if (m_backButton)
677  m_backButton->SetEnabled(showBackButton);
678 
680 }
681 
683 {
684  QString filesize("%L1 %2");
685 
686  if (size < 1000000)
687  filesize = filesize.arg((double)size / 1000.0, 0, 'f', 0).arg("KB");
688  else if (size < 1000000000)
689  filesize = filesize.arg((double)size / 1000000.0, 0, 'f', 1).arg("MB");
690  else
691  filesize = filesize.arg((double)size / 1000000000.0, 0, 'f', 1).arg("GB");
692 
693  return filesize;
694 }
695 
696 bool MythUIFileBrowser::GetRemoteFileList(const QString &url,
697  const QString &sgDir,
698  QStringList &list)
699 {
700  QUrl qurl(url);
701  QString storageGroup = qurl.userName();
702 
703  list.clear();
704 
705  if (storageGroup.isEmpty())
706  storageGroup = "Default";
707 
708  list << "QUERY_SG_GETFILELIST";
709  list << qurl.host();
710  list << storageGroup;
711 
712  QString path = sgDir + qurl.path();
713 
714  if (!qurl.fragment().isEmpty())
715  path += "#" + qurl.fragment();
716 
717  list << path;
718  list << "0";
719 
720  bool ok = gCoreContext->SendReceiveStringList(list);
721 
722  if ((list.size() == 1) && (list[0] == "EMPTY LIST"))
723  list.clear();
724 
725  return ok;
726 
727 }
728 
729 /* 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(int 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
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
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:37
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:132
bool isFile(void) const
QString GetText(void) const
static bool GetRemoteFileList(const QString &url, const QString &sgDir, QStringList &list)
MythUIButtonListItem * GetItemCurrent() const