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(FileAssociations::file_association fa) :
50  m_fa(std::move(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_fn(&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_fn(&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_fn(&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 (auto & obj : m_objects)
127  obj->blockSignals(false);
128  }
129 
130  private:
131  using list_type = std::vector<QObject *>;
132 
133  private:
134  list_type m_objects;
135  };
136 
137  struct UIDToFAPair
138  {
139  using UID_type = unsigned int;
140 
141  UIDToFAPair() = default;
142 
143  UIDToFAPair(UID_type uid, FileAssociationWrap *assoc) :
144  m_uid(uid), m_fileAssoc(assoc) {}
145 
146  UID_type m_uid {0};
147  FileAssociationWrap *m_fileAssoc {nullptr};
148  };
149 
150 
151  bool operator<(const UIDToFAPair &lhs, const UIDToFAPair &rhs)
152  {
153  if (lhs.m_fileAssoc && rhs.m_fileAssoc)
154  return QString::localeAwareCompare(lhs.m_fileAssoc->GetExtension(),
155  rhs.m_fileAssoc->GetExtension()) < 0;
156 
157  return rhs.m_fileAssoc;
158  }
159 }
160 
162 
164 {
165  public:
166  using UIReadyList_type = std::vector<UIDToFAPair>;
167 
168  public:
170  {
172  }
173 
175  {
176  for (auto & fa : m_fileAssociations)
177  delete fa.second;
178  }
179 
181  {
182  for (auto & fa : m_fileAssociations)
183  fa.second->CommitChanges();
184  }
185 
186  bool AddExtension(const QString& newExtension, UIDToFAPair::UID_type &new_id)
187  {
188  if (newExtension.length())
189  {
190  new_id = ++m_nextFAID;
191  m_fileAssociations.insert(FA_collection::value_type(new_id,
192  new FileAssociationWrap(newExtension)));
193  return true;
194  }
195 
196  return false;
197  }
198 
199  bool DeleteExtension(UIDToFAPair::UID_type uid)
200  {
201  auto p = m_fileAssociations.find(uid);
202  if (p != m_fileAssociations.end())
203  {
204  p->second->MarkForDeletion();
205 
206  return true;
207  }
208 
209  return false;
210  }
211 
212  // Returns a list sorted by extension
214  {
215  UIReadyList_type ret;
216  std::transform(m_fileAssociations.begin(), m_fileAssociations.end(),
217  std::back_inserter(ret), fa_col_ent_2_UIDFAPair());
218  auto deleted = std::remove_if(ret.begin(),
220 
221  if (deleted != ret.end())
222  ret.erase(deleted, ret.end());
223 
224  std::sort(ret.begin(), ret.end());
225 
226  return ret;
227  }
228 
229  static FileAssociationWrap *GetCurrentFA(MythUIButtonList *buttonList)
230  {
231  MythUIButtonListItem *item = buttonList->GetItemCurrent();
232  if (item)
233  {
234  UIDToFAPair key = item->GetData().value<UIDToFAPair>();
235  if (key.m_fileAssoc)
236  {
237  return key.m_fileAssoc;
238  }
239  }
240 
241  return nullptr;
242  }
243 
244  void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
245  {
246  m_selectionOverride = new_sel;
247  }
248 
249  UIDToFAPair::UID_type GetSelectionOverride() const
250  {
251  return m_selectionOverride;
252  }
253 
254  private:
255  using FA_collection = std::map<UIDToFAPair::UID_type, FileAssociationWrap *>;
256 
257  private:
259  {
260  UIDToFAPair operator()(
261  const FileAssocDialogPrivate::FA_collection::value_type &from)
262  {
263  return {from.first, from.second};
264  }
265  };
266 
267  template <FileAssociationWrap::FA_State against>
269  {
270  bool operator()(const UIDToFAPair &item)
271  {
272  return item.m_fileAssoc && item.m_fileAssoc->GetState() == against;
273  }
274  };
275 
277  {
278  using tmp_fa_list = std::vector<UIDToFAPair>;
279 
280  const FileAssociations::association_list &fa_list =
282  tmp_fa_list tmp_fa;
283  tmp_fa.reserve(fa_list.size());
284 
285  for (const auto & fa : fa_list)
286  {
287  tmp_fa.push_back(UIDToFAPair(++m_nextFAID,
288  new FileAssociationWrap(fa)));
289  }
290 
291  std::shuffle(tmp_fa.begin(), tmp_fa.end(),
292  std::mt19937(std::random_device()()));
293 
294  for (auto fa : tmp_fa)
295  {
296  m_fileAssociations.insert(FA_collection::value_type(fa.m_uid,
297  fa.m_fileAssoc));
298  }
299 
300  if (m_fileAssociations.empty())
301  {
302  LOG(VB_GENERAL, LOG_ERR,
303  QString("%1: Couldn't get any filetypes from your database.")
304  .arg(__FILE__));
305  }
306  }
307 
308  private:
310  UIDToFAPair::UID_type m_nextFAID {0};
311  UIDToFAPair::UID_type m_selectionOverride {0};
312 };
313 
315 
317  const QString &lname) :
318  MythScreenType(screenParent, lname),
319  m_private(new FileAssocDialogPrivate)
320 {
321 }
322 
324 {
325  delete m_private;
326 }
327 
329 {
330  if (!LoadWindowFromXML("video-ui.xml", "file_associations", this))
331  return false;
332 
333  bool err = false;
334  UIUtilE::Assign(this, m_extensionList, "extension_select", &err);
335  UIUtilE::Assign(this, m_commandEdit, "command", &err);
336  UIUtilE::Assign(this, m_ignoreCheck, "ignore_check", &err);
337  UIUtilE::Assign(this, m_defaultCheck, "default_check", &err);
338 
339  UIUtilE::Assign(this, m_doneButton, "done_button", &err);
340  UIUtilE::Assign(this, m_newButton, "new_button", &err);
341  UIUtilE::Assign(this, m_deleteButton, "delete_button", &err);
342 
343  if (err)
344  {
345  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'file_associations'");
346  return false;
347  }
348 
349  connect(m_extensionList, SIGNAL(itemSelected(MythUIButtonListItem *)),
351  connect(m_commandEdit, SIGNAL(valueChanged()),
352  SLOT(OnPlayerCommandChanged()));
353  connect(m_defaultCheck, SIGNAL(valueChanged()), SLOT(OnUseDefaltChanged()));
354  connect(m_ignoreCheck, SIGNAL(valueChanged()), SLOT(OnIgnoreChanged()));
355 
356  connect(m_doneButton, SIGNAL(Clicked()), SLOT(OnDonePressed()));
357  connect(m_newButton, SIGNAL(Clicked()),
358  SLOT(OnNewExtensionPressed()));
359  connect(m_deleteButton, SIGNAL(Clicked()), SLOT(OnDeletePressed()));
360 
361  m_extensionList->SetHelpText(tr("Select a file extension from this list "
362  "to modify or delete its settings."));
363  m_commandEdit->SetHelpText(tr("The command to use when playing this kind "
364  "of file. To use MythTV's Internal player, "
365  "use \"Internal\" as the player. For all other "
366  "players, you can use %s to substitute the filename."));
367  m_ignoreCheck->SetHelpText(tr("When checked, this will cause the file extension "
368  "to be ignored in scans of your library."));
369  m_defaultCheck->SetHelpText(tr("When checked, this will cause the global player "
370  "settings to override this one."));
371  m_doneButton->SetHelpText(tr("Save and exit this screen."));
372  m_newButton->SetHelpText(tr("Create a new file extension."));
373  m_deleteButton->SetHelpText(tr("Delete this file extension."));
374 
375  UpdateScreen();
376 
377  BuildFocusList();
378 
379  return true;
380 }
381 
383 {
384  (void) item;
385  UpdateScreen();
386 }
387 
389 {
392  SetDefault(m_defaultCheck->GetBooleanCheckState());
393 }
394 
396 {
399  SetIgnore(m_ignoreCheck->GetBooleanCheckState());
400 }
401 
403 {
406  SetCommand(m_commandEdit->GetText());
407 }
408 
410 {
412  Close();
413 }
414 
416 {
418  if (item)
419  {
420  UIDToFAPair key = item->GetData().value<UIDToFAPair>();
421  if (key.m_fileAssoc && m_private->DeleteExtension(key.m_uid))
422  delete item;
423  }
424 
425  UpdateScreen();
426 }
427 
429 {
430  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
431 
432  QString message = tr("Enter the new extension:");
433 
434  auto *newextdialog = new MythTextInputDialog(popupStack, message);
435 
436  if (newextdialog->Create())
437  popupStack->AddScreen(newextdialog);
438 
439  connect(newextdialog, SIGNAL(haveResult(QString)),
440  SLOT(OnNewExtensionComplete(QString)));
441 }
442 
443 void FileAssocDialog::OnNewExtensionComplete(const QString& newExtension)
444 {
445  UIDToFAPair::UID_type new_sel = 0;
446  if (m_private->AddExtension(newExtension, new_sel))
447  {
449  UpdateScreen(true);
450  }
451 }
452 
453 void FileAssocDialog::UpdateScreen(bool useSelectionOverride /* = false*/)
454 {
455  BlockSignalsGuard bsg;
456  bsg.Block(m_extensionList);
457  bsg.Block(m_commandEdit);
458  bsg.Block(m_defaultCheck);
459  bsg.Block(m_ignoreCheck);
460 
463 
464  if (tmp_list.empty())
465  {
466  m_extensionList->SetVisible(false);
467  m_commandEdit->SetVisible(false);
468  m_defaultCheck->SetVisible(false);
469  m_ignoreCheck->SetVisible(false);
470  m_deleteButton->SetVisible(false);
471  }
472  else
473  {
474  UIDToFAPair::UID_type selected_id = 0;
476  if (current_item)
477  {
478  UIDToFAPair key = current_item->GetData().value<UIDToFAPair>();
479  if (key.m_fileAssoc)
480  {
481  selected_id = key.m_uid;
482  }
483  }
484 
485  if (useSelectionOverride)
486  selected_id = m_private->GetSelectionOverride();
487 
490 
491  for (auto & fad : tmp_list)
492  {
493  if (fad.m_fileAssoc)
494  {
495  // No memory leak. MythUIButtonListItem adds the new
496  // item into m_extensionList.
497  auto *new_item = new MythUIButtonListItem(m_extensionList,
498  fad.m_fileAssoc->GetExtension(),
499  QVariant::fromValue(fad));
500  if (selected_id && fad.m_uid == selected_id)
501  m_extensionList->SetItemCurrent(new_item);
502  }
503  }
504 
505  current_item = m_extensionList->GetItemCurrent();
506  if (current_item)
507  {
508  UIDToFAPair key = current_item->GetData().value<UIDToFAPair>();
509  if (key.m_fileAssoc)
510  {
511  m_commandEdit->SetVisible(true);
512  m_commandEdit->SetText(key.m_fileAssoc->GetCommand());
513 
514  m_defaultCheck->SetVisible(true);
515  m_defaultCheck->SetCheckState(key.m_fileAssoc->GetDefault() ?
517 
518  m_ignoreCheck->SetVisible(true);
519  m_ignoreCheck->SetCheckState(key.m_fileAssoc->GetIgnore() ?
521 
522  m_deleteButton->SetVisible(true);
523  }
524  }
525  }
526 
527  BuildFocusList();
528 }
529 
530 Q_DECLARE_METATYPE(UIDToFAPair)
~FileAssocDialog() override
FA_collection m_fileAssociations
MythUICheckBox * m_ignoreCheck
void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
bool Create() override
const association_list & getList() const
Definition: dbaccess.cpp:806
void OnNewExtensionComplete(const QString &newExtension)
MythScreenStack * GetStack(const QString &stackname)
std::vector< file_association > association_list
Definition: dbaccess.h:154
bool DeleteExtension(UIDToFAPair::UID_type uid)
MythUIButton * m_doneButton
std::map< UIDToFAPair::UID_type, FileAssociationWrap * > FA_collection
Q_DECLARE_METATYPE(GrabberScript *)
bool operator<(const ParentalLevel &lhs, const ParentalLevel &rhs)
void BuildFocusList(void)
bool AddExtension(const QString &newExtension, UIDToFAPair::UID_type &new_id)
virtual void Close()
static FileAssociationWrap * GetCurrentFA(MythUIButtonList *buttonList)
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:801
#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:831
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
std::vector< UIDToFAPair > UIReadyList_type
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
MythUIButtonListItem * GetItemCurrent() const
void OnNewExtensionPressed()
bool operator()(const UIDToFAPair &item)