MythTV  master
videofileassoc.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 #include <functional> //mem_fun
3 #include <iterator>
4 #include <map>
5 #include <random>
6 #include <utility>
7 #include <vector>
8 
9 #include "mythlogging.h"
10 #include "mythmainwindow.h"
11 #include "mythdialogbox.h"
12 #include "mythuibuttonlist.h"
13 #include "mythuitextedit.h"
14 #include "mythuicheckbox.h"
15 #include "mythuibutton.h"
16 #include "dbaccess.h"
17 #include "videoutils.h"
18 
19 #include "videofileassoc.h"
20 
21 namespace
22 {
23  template <typename T, typename Inst, typename FuncType>
24  void assign_if_changed_notify(T &oldVal, const T &newVal, Inst *inst,
25  FuncType func)
26  {
27  if (oldVal != newVal)
28  {
29  oldVal = newVal;
30  func(inst);
31  }
32  }
33 
34  class FileAssociationWrap
35  {
36  public:
37  enum FA_State {
38  efsNONE,
39  efsDELETE,
40  efsSAVE
41  };
42 
43  public:
44  explicit FileAssociationWrap(const QString &new_extension) : m_state(efsSAVE)
45  {
46  m_fa.extension = new_extension;
47  }
48 
49  explicit FileAssociationWrap(const FileAssociations::file_association &fa) :
50  m_fa(fa) {}
51 
52  unsigned int GetIDx(void) const { return m_fa.id; }
53  QString GetExtension(void) const { return m_fa.extension; }
54  QString GetCommand(void) const { return m_fa.playcommand; }
55  bool GetDefault(void) const { return m_fa.use_default; }
56  bool GetIgnore(void) const { return m_fa.ignore; }
57 
58  FA_State GetState() const { return m_state; }
59 
60  void CommitChanges()
61  {
62  switch (m_state)
63  {
64  case efsDELETE:
65  {
67  m_fa.id = 0;
68  m_state = efsNONE;
69  break;
70  }
71  case efsSAVE:
72  {
74  {
75  m_state = efsNONE;
76  }
77  break;
78  }
79  case efsNONE:
80  default: {}
81  }
82  }
83 
84  void MarkForDeletion()
85  {
86  m_state = efsDELETE;
87  }
88 
89  void SetDefault(bool yes_or_no)
90  {
91  assign_if_changed_notify(m_fa.use_default, yes_or_no, this,
92  std::mem_fun(&FileAssociationWrap::SetChanged));
93  }
94 
95  void SetIgnore(bool yes_or_no)
96  {
97  assign_if_changed_notify(m_fa.ignore, yes_or_no, this,
98  std::mem_fun(&FileAssociationWrap::SetChanged));
99  }
100 
101  void SetCommand(const QString &new_command)
102  {
103  assign_if_changed_notify(m_fa.playcommand, new_command, this,
104  std::mem_fun(&FileAssociationWrap::SetChanged));
105  }
106 
107  private:
108  void SetChanged() { m_state = efsSAVE; }
109 
110  private:
112  FA_State m_state {efsNONE};
113  };
114 
115  class BlockSignalsGuard
116  {
117  public:
118  void Block(QObject *o)
119  {
120  o->blockSignals(true);
121  m_objects.push_back(o);
122  }
123 
124  ~BlockSignalsGuard()
125  {
126  for (list_type::iterator p = m_objects.begin();
127  p != m_objects.end(); ++p)
128  {
129  (*p)->blockSignals(false);
130  }
131  }
132 
133  private:
134  typedef std::vector<QObject *> list_type;
135 
136  private:
137  list_type m_objects;
138  };
139 
140  struct UIDToFAPair
141  {
142  typedef unsigned int UID_type;
143 
144  UIDToFAPair() = default;
145 
146  UIDToFAPair(UID_type uid, FileAssociationWrap *assoc) :
147  m_uid(uid), m_file_assoc(assoc) {}
148 
149  UID_type m_uid {0};
150  FileAssociationWrap *m_file_assoc {nullptr};
151  };
152 
153 
154  bool operator<(const UIDToFAPair &lhs, const UIDToFAPair &rhs)
155  {
156  if (lhs.m_file_assoc && rhs.m_file_assoc)
157  return QString::localeAwareCompare(lhs.m_file_assoc->GetExtension(),
158  rhs.m_file_assoc->GetExtension()) < 0;
159 
160  return rhs.m_file_assoc;
161  }
162 }
163 
165 
167 {
168  public:
169  typedef std::vector<UIDToFAPair> UIReadyList_type;
170 
171  public:
173  {
175  }
176 
178  {
179  for (FA_collection::iterator p = m_fileAssociations.begin();
180  p != m_fileAssociations.end(); ++p)
181  {
182  delete p->second;
183  }
184  }
185 
187  {
188  for (FA_collection::iterator p = m_fileAssociations.begin();
189  p != m_fileAssociations.end(); ++p)
190  {
191  p->second->CommitChanges();
192  }
193  }
194 
195  bool AddExtension(const QString& newExtension, UIDToFAPair::UID_type &new_id)
196  {
197  if (newExtension.length())
198  {
199  new_id = ++m_nextFAID;
200  m_fileAssociations.insert(FA_collection::value_type(new_id,
201  new FileAssociationWrap(newExtension)));
202  return true;
203  }
204 
205  return false;
206  }
207 
208  bool DeleteExtension(UIDToFAPair::UID_type uid)
209  {
210  FA_collection::iterator p = m_fileAssociations.find(uid);
211  if (p != m_fileAssociations.end())
212  {
213  p->second->MarkForDeletion();
214 
215  return true;
216  }
217 
218  return false;
219  }
220 
221  // Returns a list sorted by extension
223  {
224  UIReadyList_type ret;
225  std::transform(m_fileAssociations.begin(), m_fileAssociations.end(),
226  std::back_inserter(ret), fa_col_ent_2_UIDFAPair());
227  UIReadyList_type::iterator deleted = std::remove_if(ret.begin(),
229 
230  if (deleted != ret.end())
231  ret.erase(deleted, ret.end());
232 
233  std::sort(ret.begin(), ret.end());
234 
235  return ret;
236  }
237 
238  FileAssociationWrap *GetCurrentFA(MythUIButtonList *buttonList)
239  {
240  MythUIButtonListItem *item = buttonList->GetItemCurrent();
241  if (item)
242  {
243  UIDToFAPair key = item->GetData().value<UIDToFAPair>();
244  if (key.m_file_assoc)
245  {
246  return key.m_file_assoc;
247  }
248  }
249 
250  return nullptr;
251  }
252 
253  void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
254  {
255  m_selectionOverride = new_sel;
256  }
257 
258  UIDToFAPair::UID_type GetSelectionOverride() const
259  {
260  return m_selectionOverride;
261  }
262 
263  private:
264  typedef std::map<UIDToFAPair::UID_type, FileAssociationWrap *>
266 
267  private:
269  {
270  UIDToFAPair operator()(
271  const FileAssocDialogPrivate::FA_collection::value_type &from)
272  {
273  return {from.first, from.second};
274  }
275  };
276 
277  template <FileAssociationWrap::FA_State against>
279  {
280  bool operator()(const UIDToFAPair &item)
281  {
282  return item.m_file_assoc && item.m_file_assoc->GetState() == against;
283  }
284  };
285 
287  {
288  typedef std::vector<UIDToFAPair> tmp_fa_list;
289 
290  const FileAssociations::association_list &fa_list =
292  tmp_fa_list tmp_fa;
293  tmp_fa.reserve(fa_list.size());
294 
295  for (FileAssociations::association_list::const_iterator p =
296  fa_list.begin(); p != fa_list.end(); ++p)
297  {
298  tmp_fa.push_back(UIDToFAPair(++m_nextFAID,
299  new FileAssociationWrap(*p)));
300  }
301 
302  std::shuffle(tmp_fa.begin(), tmp_fa.end(),
303  std::mt19937(std::random_device()()));
304 
305  for (tmp_fa_list::const_iterator p = tmp_fa.begin(); p != tmp_fa.end();
306  ++p)
307  {
308  m_fileAssociations.insert(FA_collection::value_type(p->m_uid,
309  p->m_file_assoc));
310  }
311 
312  if (m_fileAssociations.empty())
313  {
314  LOG(VB_GENERAL, LOG_ERR,
315  QString("%1: Couldn't get any filetypes from your database.")
316  .arg(__FILE__));
317  }
318  }
319 
320  private:
322  UIDToFAPair::UID_type m_nextFAID {0};
323  UIDToFAPair::UID_type m_selectionOverride {0};
324 };
325 
327 
329  const QString &lname) :
330  MythScreenType(screenParent, lname),
331  m_private(new FileAssocDialogPrivate)
332 {
333 }
334 
336 {
337  delete m_private;
338 }
339 
341 {
342  if (!LoadWindowFromXML("video-ui.xml", "file_associations", this))
343  return false;
344 
345  bool err = false;
346  UIUtilE::Assign(this, m_extensionList, "extension_select", &err);
347  UIUtilE::Assign(this, m_commandEdit, "command", &err);
348  UIUtilE::Assign(this, m_ignoreCheck, "ignore_check", &err);
349  UIUtilE::Assign(this, m_defaultCheck, "default_check", &err);
350 
351  UIUtilE::Assign(this, m_doneButton, "done_button", &err);
352  UIUtilE::Assign(this, m_newButton, "new_button", &err);
353  UIUtilE::Assign(this, m_deleteButton, "delete_button", &err);
354 
355  if (err)
356  {
357  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'file_associations'");
358  return false;
359  }
360 
361  connect(m_extensionList, SIGNAL(itemSelected(MythUIButtonListItem *)),
363  connect(m_commandEdit, SIGNAL(valueChanged()),
364  SLOT(OnPlayerCommandChanged()));
365  connect(m_defaultCheck, SIGNAL(valueChanged()), SLOT(OnUseDefaltChanged()));
366  connect(m_ignoreCheck, SIGNAL(valueChanged()), SLOT(OnIgnoreChanged()));
367 
368  connect(m_doneButton, SIGNAL(Clicked()), SLOT(OnDonePressed()));
369  connect(m_newButton, SIGNAL(Clicked()),
370  SLOT(OnNewExtensionPressed()));
371  connect(m_deleteButton, SIGNAL(Clicked()), SLOT(OnDeletePressed()));
372 
373  m_extensionList->SetHelpText(tr("Select a file extension from this list "
374  "to modify or delete its settings."));
375  m_commandEdit->SetHelpText(tr("The command to use when playing this kind "
376  "of file. To use MythTV's Internal player, "
377  "use \"Internal\" as the player. For all other "
378  "players, you can use %s to substitute the filename."));
379  m_ignoreCheck->SetHelpText(tr("When checked, this will cause the file extension "
380  "to be ignored in scans of your library."));
381  m_defaultCheck->SetHelpText(tr("When checked, this will cause the global player "
382  "settings to override this one."));
383  m_doneButton->SetHelpText(tr("Save and exit this screen."));
384  m_newButton->SetHelpText(tr("Create a new file extension."));
385  m_deleteButton->SetHelpText(tr("Delete this file extension."));
386 
387  UpdateScreen();
388 
389  BuildFocusList();
390 
391  return true;
392 }
393 
395 {
396  (void) item;
397  UpdateScreen();
398 }
399 
401 {
404  SetDefault(m_defaultCheck->GetBooleanCheckState());
405 }
406 
408 {
411  SetIgnore(m_ignoreCheck->GetBooleanCheckState());
412 }
413 
415 {
418  SetCommand(m_commandEdit->GetText());
419 }
420 
422 {
424  Close();
425 }
426 
428 {
430  if (item)
431  {
432  UIDToFAPair key = item->GetData().value<UIDToFAPair>();
433  if (key.m_file_assoc && m_private->DeleteExtension(key.m_uid))
434  delete item;
435  }
436 
437  UpdateScreen();
438 }
439 
441 {
442  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
443 
444  QString message = tr("Enter the new extension:");
445 
446  MythTextInputDialog *newextdialog =
447  new MythTextInputDialog(popupStack, message);
448 
449  if (newextdialog->Create())
450  popupStack->AddScreen(newextdialog);
451 
452  connect(newextdialog, SIGNAL(haveResult(QString)),
453  SLOT(OnNewExtensionComplete(QString)));
454 }
455 
456 void FileAssocDialog::OnNewExtensionComplete(QString newExtension)
457 {
458  UIDToFAPair::UID_type new_sel = 0;
459  if (m_private->AddExtension(std::move(newExtension), new_sel))
460  {
462  UpdateScreen(true);
463  }
464 }
465 
466 void FileAssocDialog::UpdateScreen(bool useSelectionOverride /* = false*/)
467 {
468  BlockSignalsGuard bsg;
469  bsg.Block(m_extensionList);
470  bsg.Block(m_commandEdit);
471  bsg.Block(m_defaultCheck);
472  bsg.Block(m_ignoreCheck);
473 
476 
477  if (tmp_list.empty())
478  {
479  m_extensionList->SetVisible(false);
480  m_commandEdit->SetVisible(false);
481  m_defaultCheck->SetVisible(false);
482  m_ignoreCheck->SetVisible(false);
483  m_deleteButton->SetVisible(false);
484  }
485  else
486  {
487  UIDToFAPair::UID_type selected_id = 0;
489  if (current_item)
490  {
491  UIDToFAPair key = current_item->GetData().value<UIDToFAPair>();
492  if (key.m_file_assoc)
493  {
494  selected_id = key.m_uid;
495  }
496  }
497 
498  if (useSelectionOverride)
499  selected_id = m_private->GetSelectionOverride();
500 
503 
504  for (FileAssocDialogPrivate::UIReadyList_type::iterator p =
505  tmp_list.begin(); p != tmp_list.end(); ++p)
506  {
507  if (p->m_file_assoc)
508  {
509  // No memory leak. MythUIButtonListItem adds the new
510  // item into m_extensionList.
511  MythUIButtonListItem *new_item =
513  p->m_file_assoc->GetExtension(),
514  QVariant::fromValue(*p));
515  if (selected_id && p->m_uid == selected_id)
516  m_extensionList->SetItemCurrent(new_item);
517  }
518  }
519 
520  current_item = m_extensionList->GetItemCurrent();
521  if (current_item)
522  {
523  UIDToFAPair key = current_item->GetData().value<UIDToFAPair>();
524  if (key.m_file_assoc)
525  {
526  m_commandEdit->SetVisible(true);
527  m_commandEdit->SetText(key.m_file_assoc->GetCommand());
528 
529  m_defaultCheck->SetVisible(true);
530  m_defaultCheck->SetCheckState(key.m_file_assoc->GetDefault() ?
532 
533  m_ignoreCheck->SetVisible(true);
534  m_ignoreCheck->SetCheckState(key.m_file_assoc->GetIgnore() ?
536 
537  m_deleteButton->SetVisible(true);
538  }
539  }
540  }
541 
542  BuildFocusList();
543 }
544 
545 Q_DECLARE_METATYPE(UIDToFAPair)
FA_collection m_fileAssociations
MythUICheckBox * m_ignoreCheck
void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
bool Create() override
std::vector< file_association > association_list
Definition: dbaccess.h:153
const association_list & getList() const
Definition: dbaccess.cpp:811
FileAssociationWrap * GetCurrentFA(MythUIButtonList *buttonList)
MythScreenStack * GetStack(const QString &stackname)
bool DeleteExtension(UIDToFAPair::UID_type uid)
MythUIButton * m_doneButton
void OnNewExtensionComplete(QString newExtension)
Q_DECLARE_METATYPE(GrabberScript *)
bool operator<(const ParentalLevel &lhs, const ParentalLevel &rhs)
std::vector< UIDToFAPair > UIReadyList_type
void BuildFocusList(void)
bool AddExtension(const QString &newExtension, UIDToFAPair::UID_type &new_id)
virtual void Close()
bool Create(void) override
bool GetBooleanCheckState(void) const
virtual void SetVisible(bool visible)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void SetHelpText(const QString &text)
Definition: mythuitype.h:150
MythUICheckBox * m_defaultCheck
bool remove(unsigned int id)
Definition: dbaccess.cpp:806
#define GetState(a, b)
UIDToFAPair operator()(const FileAssocDialogPrivate::FA_collection::value_type &from)
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
UIDToFAPair::UID_type GetSelectionOverride() const
MythUITextEdit * m_commandEdit
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
void SetText(const QString &text, bool moveCursor=true)
static FileAssociations & getFileAssociation()
Definition: dbaccess.cpp:836
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
MythMainWindow * GetMythMainWindow(void)
UIDToFAPair::UID_type m_nextFAID
Dialog prompting the user to enter a text string.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
MythUIButtonList * m_extensionList
UIDToFAPair::UID_type m_selectionOverride
MythUIButton * m_newButton
void OnPlayerCommandChanged()
void SetCheckState(MythUIStateType::StateType state)
void OnFASelected(MythUIButtonListItem *item)
void UpdateScreen(bool useSelectionOverride=false)
void SetItemCurrent(MythUIButtonListItem *item)
UIReadyList_type GetUIReadyList()
class FileAssocDialogPrivate * m_private
Screen in which all other widgets are contained and rendered.
FileAssocDialog(MythScreenStack *screenParent, const QString &lname)
MythUIButton * m_deleteButton
QString GetText(void) const
std::map< UIDToFAPair::UID_type, FileAssociationWrap * > FA_collection
MythUIButtonListItem * GetItemCurrent() const
void OnNewExtensionPressed()
bool operator()(const UIDToFAPair &item)