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(QString fileName, QString sgDir, bool isDir, qint64 size)
26 {
27  init(std::move(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  QString sgDir = other.storageGroupDir();
65  bool isDir = other.isDir();
66  qint64 size = other.size();
67  init(other.fileName(), sgDir, isDir, size);
68 
69  return *this;
70 }
71 
72 QString MFileInfo::fileName(void) const
73 {
74  if (m_isRemote)
75  return m_fileName;
76  return QFileInfo::fileName();
77 }
78 
79 QString MFileInfo::filePath(void) const
80 {
81  if (m_isRemote)
82  return m_fileName;
83  return QFileInfo::filePath();
84 }
85 
86 bool MFileInfo::isDir(void) const
87 {
88  if (m_isRemote)
89  return m_isDir;
90  return QFileInfo::isDir();
91 }
92 
93 bool MFileInfo::isFile(void) const
94 {
95  if (m_isRemote)
96  return m_isFile;
97  return QFileInfo::isFile();
98 }
99 
100 bool MFileInfo::isParentDir(void) const
101 {
102  if (m_isRemote)
103  return m_isParentDir;
104  return (QFileInfo::fileName() == "..");
105 }
106 
107 bool MFileInfo::isExecutable(void) const
108 {
109  if (m_isRemote)
110  return false;
111  return QFileInfo::isExecutable();
112 }
113 
114 QString MFileInfo::absoluteFilePath(void) const
115 {
116  if (m_isRemote)
117  return m_fileName;
118  return QFileInfo::absoluteFilePath();
119 }
120 
121 qint64 MFileInfo::size(void) const
122 {
123  if (m_isRemote)
124  return m_size;
125  return QFileInfo::size();
126 }
127 
129 
138  const QString &startPath)
139  : MythScreenType(parent, "mythuifilebrowser")
140 {
141  SetPath(startPath);
142 
143  m_nameFilter.clear();
144  m_nameFilter << "*";
145 
146  m_previewTimer = new QTimer(this);
147  m_previewTimer->setSingleShot(true);
148  connect(m_previewTimer, SIGNAL(timeout()), SLOT(LoadPreview()));
149 }
150 
151 void MythUIFileBrowser::SetPath(const QString &startPath)
152 {
153  if (startPath.startsWith("myth://"))
154  {
155  m_isRemote = true;
156 
157  QUrl qurl(startPath);
158 
159  if (!qurl.path().isEmpty())
160  {
161  // Force browing of remote SG's to start at their root
162  m_baseDirectory = gCoreContext->GenMythURL(qurl.host(),
163  0,
164  "",
165  qurl.userName());
166 
167  }
168  else
169  {
170  m_baseDirectory = startPath;
171 
172  if (m_baseDirectory.endsWith("/"))
173  m_baseDirectory.remove(m_baseDirectory.length() - 1, 1);
174  }
175 
176  m_subDirectory = "";
177  m_storageGroupDir = "";
178  }
179  else
180  {
181  m_isRemote = false;
182  m_baseDirectory = "";
183  m_subDirectory = startPath;
184  }
185 }
186 
188 {
189  if (!CopyWindowFromBase("MythFileBrowser", this))
190  return false;
191 
192  m_fileList = dynamic_cast<MythUIButtonList *>(GetChild("filelist"));
193  m_locationEdit = dynamic_cast<MythUITextEdit *>(GetChild("location"));
194  m_okButton = dynamic_cast<MythUIButton *>(GetChild("ok"));
195  m_cancelButton = dynamic_cast<MythUIButton *>(GetChild("cancel"));
196  m_backButton = dynamic_cast<MythUIButton *>(GetChild("back"));
197  m_homeButton = dynamic_cast<MythUIButton *>(GetChild("home"));
198  m_previewImage = dynamic_cast<MythUIImage *>(GetChild("preview"));
199  m_infoText = dynamic_cast<MythUIText *>(GetChild("info"));
200  m_filenameText = dynamic_cast<MythUIText *>(GetChild("filename"));
201  m_fullpathText = dynamic_cast<MythUIText *>(GetChild("fullpath"));
202 
204  {
205  LOG(VB_GENERAL, LOG_ERR, "MythUIFileBrowser: Your theme is missing"
206  " some UI elements! Bailing out.");
207  return false;
208  }
209 
210  connect(m_fileList, SIGNAL(itemClicked(MythUIButtonListItem *)),
212  connect(m_fileList, SIGNAL(itemSelected(MythUIButtonListItem *)),
214  connect(m_locationEdit, SIGNAL(LosingFocus()), SLOT(editLostFocus()));
215  connect(m_okButton, SIGNAL(Clicked()), SLOT(OKPressed()));
216  connect(m_cancelButton, SIGNAL(Clicked()), SLOT(cancelPressed()));
217 
218  if (m_backButton)
219  connect(m_backButton, SIGNAL(Clicked()), SLOT(backPressed()));
220 
221  if (m_homeButton)
222  connect(m_homeButton, SIGNAL(Clicked()), SLOT(homePressed()));
223 
224  BuildFocusList();
225  updateFileList();
226 
227  return true;
228 }
229 
230 void MythUIFileBrowser::SetReturnEvent(QObject *retobject,
231  const QString &resultid)
232 {
233  m_retObject = retobject;
234  m_id = resultid;
235 }
236 
238 {
239  if (m_previewImage)
240  m_previewImage->Load();
241 }
242 
244 {
245  if (!item)
246  return;
247 
248  if (m_previewImage)
250 
251  MFileInfo finfo = item->GetData().value<MFileInfo>();
252 
253  if (finfo.isParentDir())
254  {
255  if (m_infoText)
256  m_infoText->Reset();
257 
258  if (m_filenameText)
260 
261  if (m_fullpathText)
263  }
264  else
265  {
266  if (IsImage(finfo.suffix()) && m_previewImage)
267  {
269  m_previewTimer->start(250);
270  }
271 
272  if (m_infoText)
273  m_infoText->SetText(FormatSize(finfo.size()));
274 
275  if (m_filenameText)
276  m_filenameText->SetText(finfo.fileName());
277 
278  if (m_fullpathText)
280  }
281 }
282 
284 {
285  if (!item)
286  return;
287 
288  MFileInfo finfo = item->GetData().value<MFileInfo>();
289 
290  if (finfo.isFile())
291  {
292  if (m_retObject)
293  {
294  DialogCompletionEvent *dce =
295  new DialogCompletionEvent(m_id, 0, finfo.filePath(),
296  item->GetData());
297  QCoreApplication::postEvent(m_retObject, dce);
298  }
299 
300  Close();
301  return;
302  }
303 
304  if (!finfo.isDir())
305  return;
306 
307  if (finfo.isParentDir())
308  {
309  backPressed();
310  }
311  else
312  {
313  if (finfo.isRemote())
314  {
315  m_subDirectory = finfo.subDir();
317  }
318  else
319  {
320  m_subDirectory = finfo.filePath();
321  m_storageGroupDir = "";
322  }
323  }
324 
325  updateFileList();
326 }
327 
328 bool MythUIFileBrowser::IsImage(QString extension)
329 {
330  if (extension.isEmpty())
331  return false;
332 
333  extension = extension.toLower();
334 
335  QList<QByteArray> formats = QImageReader::supportedImageFormats();
336 
337  return formats.contains(extension.toLatin1());
338 }
339 
341 {
342  QString newPath = m_locationEdit->GetText();
343 
344  SetPath(newPath);
345 
346  updateFileList();
347 }
348 
350 {
351  if (m_isRemote)
352  {
354 
355  if (m_subDirectory.startsWith(m_baseDirectory))
356  {
357  m_subDirectory.remove(0, m_baseDirectory.length());
358 
359  if (m_subDirectory.startsWith("/"))
360  m_subDirectory.remove(0, 1);
361  }
362 
364  }
365  else
366  {
367  // move up one directory
368  int pos = m_subDirectory.lastIndexOf('/');
369 
370  if (pos > 0)
371  m_subDirectory = m_subDirectory.left(pos);
372  else
373  m_subDirectory = "/";
374  }
375 
376  updateFileList();
377 }
378 
380 {
381  if (m_isRemote)
382  {
383  m_subDirectory = "";
384  m_storageGroupDir = "";
385  }
386  else
387  {
388  char *home = getenv("HOME");
389  m_subDirectory = home;
390  }
391 
392  updateFileList();
393 }
394 
396 {
398  MFileInfo finfo = item->GetData().value<MFileInfo>();
399 
400  if (m_retObject)
401  {
402  QString selectedPath = m_locationEdit->GetText();
404  selectedPath,
405  item->GetData());
406  QCoreApplication::postEvent(m_retObject, dce);
407  }
408 
409  Close();
410 }
411 
413 {
414  Close();
415 }
416 
418 {
419  m_fileList->Reset();
420 
421  if (m_isRemote)
423  else
425 }
426 
428 {
429  QStringList sgdirlist;
430  QString sgdir;
431  QStringList slist;
432 
433  if (!m_baseDirectory.endsWith("/"))
434  m_baseDirectory.append("/");
435 
436  QString dirURL = QString("%1%2").arg(m_baseDirectory)
437  .arg(m_subDirectory);
438 
439  if (!GetRemoteFileList(m_baseDirectory, sgdir, sgdirlist))
440  {
441  LOG(VB_GENERAL, LOG_ERR, "GetRemoteFileList failed to get "
442  "Storage Group dirs");
443  return;
444  }
445 
446  if ((sgdirlist.size() == 1) &&
447  (sgdirlist[0].startsWith("sgdir::")))
448  {
449  QStringList tokens = sgdirlist[0].split("::");
450 
451  m_storageGroupDir = tokens[1];
452  }
453 
454  if (!GetRemoteFileList(dirURL, m_storageGroupDir, slist))
455  {
456  LOG(VB_GENERAL, LOG_ERR,
457  QString("GetRemoteFileList failed for '%1' in '%2' SG dir")
458  .arg(dirURL).arg(m_storageGroupDir));
459  return;
460  }
461 
462  m_locationEdit->SetText(dirURL);
463 
464  QString displayName;
465  QString dataName;
466  QString type;
467 
468  if ((sgdirlist.size() > 1 && !m_storageGroupDir.isEmpty()) ||
469  (!m_subDirectory.isEmpty()))
470  {
471  displayName = tr("Parent");
472  type = "upfolder";
473 
475 
476  if (!m_subDirectory.isEmpty())
477  {
478  m_parentDir += "/" + m_subDirectory;
479 
480  int pos = m_parentDir.lastIndexOf('/');
481 
482  if (pos > 0)
483  m_parentDir = m_parentDir.left(pos);
484  }
485 
486 
489 
490  if (m_subDirectory.isEmpty() && m_parentDir == m_baseDirectory)
491  {
492  finfo.setSGDir("");
493  m_parentSGDir = "";
494  }
495 
497  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  MythUIButtonListItem *item =
574  new MythUIButtonListItem(m_fileList, displayName,
575  qVariantFromValue(finfo));
576 
577  if (finfo.size())
578  item->SetText(FormatSize(finfo.size()), "filesize");
579 
580  if (type == "image")
581  item->SetImage(dataName);
582 
583  item->SetText(dataName, "fullpath");
584  item->DisplayState(type, "nodetype");
585 
586  ++it;
587  }
588 }
589 
591 {
592  QDir d;
593 
594  d.setPath(m_subDirectory);
595  d.setNameFilters(m_nameFilter);
596  d.setFilter(m_typeFilter);
597  d.setSorting(QDir::Name | QDir::DirsFirst | QDir::IgnoreCase);
598 
599  if (!d.exists())
600  {
601  LOG(VB_GENERAL, LOG_ERR,
602  "MythUIFileBrowser: current directory does not exist!");
603  m_locationEdit->SetText("/");
604  m_subDirectory = "/";
605  d.setPath("/");
606  }
607 
608  QFileInfoList list = d.entryInfoList();
609  bool showBackButton = false;
610 
611  if (list.isEmpty())
612  {
614  tr("Parent Directory"));
615  item->DisplayState("upfolder", "nodetype");
616  }
617  else
618  {
619  QFileInfoList::const_iterator it = list.begin();
620  const QFileInfo *fi;
621 
622  while (it != list.end())
623  {
624  fi = &(*it);
625  MFileInfo finfo(fi->filePath());
626 
627  if (finfo.fileName() == ".")
628  {
629  ++it;
630  continue;
631  }
632 
633  QString displayName = finfo.fileName();
634  QString type;
635 
636  if (displayName == "..")
637  {
638  if (m_subDirectory.endsWith("/"))
639  {
640  ++it;
641  continue;
642  }
643 
644  displayName = tr("Parent");
645  type = "upfolder";
646  showBackButton = true;
647  }
648  else if (finfo.isDir())
649  {
650  type = "folder";
651  }
652  else if (finfo.isExecutable())
653  {
654  type = "executable";
655  }
656  else if (finfo.isFile())
657  {
658  type = "file";
659  }
660 
661  MythUIButtonListItem *item =
662  new MythUIButtonListItem(m_fileList, displayName,
663  qVariantFromValue(finfo));
664 
665  if (IsImage(finfo.suffix()))
666  {
667  item->SetImage(finfo.absoluteFilePath());
668  type = "image";
669  }
670 
671  item->SetText(FormatSize(finfo.size()), "filesize");
672  item->SetText(finfo.absoluteFilePath(), "fullpath");
673  item->DisplayState(type, "nodetype");
674 
675  ++it;
676  }
677  }
678 
679  if (m_backButton)
680  m_backButton->SetEnabled(showBackButton);
681 
683 }
684 
686 {
687  QString filesize("%L1 %2");
688 
689  if (size < 1000000)
690  filesize = filesize.arg((double)size / 1000.0, 0, 'f', 0).arg("KB");
691  else if (size < 1000000000)
692  filesize = filesize.arg((double)size / 1000000.0, 0, 'f', 1).arg("MB");
693  else
694  filesize = filesize.arg((double)size / 1000000000.0, 0, 'f', 1).arg("GB");
695 
696  return filesize;
697 }
698 
699 bool MythUIFileBrowser::GetRemoteFileList(const QString &url,
700  const QString &sgDir,
701  QStringList &list)
702 {
703  QUrl qurl(url);
704  QString storageGroup = qurl.userName();
705 
706  list.clear();
707 
708  if (storageGroup.isEmpty())
709  storageGroup = "Default";
710 
711  list << "QUERY_SG_GETFILELIST";
712  list << qurl.host();
713  list << storageGroup;
714 
715  QString path = sgDir + qurl.path();
716 
717  if (!qurl.fragment().isEmpty())
718  path += "#" + qurl.fragment();
719 
720  list << path;
721  list << "0";
722 
723  bool ok = gCoreContext->SendReceiveStringList(list);
724 
725  if ((list.size() == 1) && (list[0] == "EMPTY LIST"))
726  list.clear();
727 
728  return ok;
729 
730 }
731 
732 /* 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
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:136
bool Create(void) override
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
bool isRemote(void)
MythUIButton * m_cancelButton
MFileInfo(QString fileName="", QString sgDir="", bool isDir=false, qint64 size=0)
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)
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
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
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:84
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)
void SetText(const QString &text, const QString &name="", const QString &state="")
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
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
void DisplayState(const QString &state, const QString &name)
bool isFile(void) const
QString GetText(void) const
bool GetRemoteFileList(const QString &url, const QString &sgDir, QStringList &list)
MythUIButtonListItem * GetItemCurrent() const