MythTV master
videofileassoc.cpp
Go to the documentation of this file.
1// C++
2#include <algorithm>
3#include <functional> //mem_fun
4#include <iterator>
5#include <map>
6#include <random>
7#include <utility>
8#include <vector>
9
10// MythTV
20
21// MythFrontend
22#include "videofileassoc.h"
23
24namespace
25{
26 template <typename T, typename Inst, typename FuncType>
27 void assign_if_changed_notify(T &oldVal, const T &newVal, Inst *inst,
28 FuncType func)
29 {
30 if (oldVal != newVal)
31 {
32 oldVal = newVal;
33 func(inst);
34 }
35 }
36
38 {
39 public:
40 enum FA_State : std::uint8_t {
43 efsSAVE
44 };
45
46 public:
47 explicit FileAssociationWrap(const QString &new_extension) : m_state(efsSAVE)
48 {
49 m_fa.extension = new_extension;
50 }
51
53 m_fa(std::move(fa)) {}
54
55 unsigned int GetIDx(void) const { return m_fa.id; }
56 QString GetExtension(void) const { return m_fa.extension; }
57 QString GetCommand(void) const { return m_fa.playcommand; }
58 bool GetDefault(void) const { return m_fa.use_default; }
59 bool GetIgnore(void) const { return m_fa.ignore; }
60
61 FA_State GetState() const { return m_state; }
62
64 {
65 switch (m_state)
66 {
67 case efsDELETE:
68 {
70 m_fa.id = 0;
71 m_state = efsNONE;
72 break;
73 }
74 case efsSAVE:
75 {
77 {
78 m_state = efsNONE;
79 }
80 break;
81 }
82 case efsNONE:
83 default: {}
84 }
85 }
86
88 {
89 m_state = efsDELETE;
90 }
91
92 void SetDefault(bool yes_or_no)
93 {
94 assign_if_changed_notify(m_fa.use_default, yes_or_no, this,
95 std::mem_fn(&FileAssociationWrap::SetChanged));
96 }
97
98 void SetIgnore(bool yes_or_no)
99 {
100 assign_if_changed_notify(m_fa.ignore, yes_or_no, this,
101 std::mem_fn(&FileAssociationWrap::SetChanged));
102 }
103
104 void SetCommand(const QString &new_command)
105 {
106 assign_if_changed_notify(m_fa.playcommand, new_command, this,
107 std::mem_fn(&FileAssociationWrap::SetChanged));
108 }
109
110 private:
111 void SetChanged() { m_state = efsSAVE; }
112
113 private:
115 FA_State m_state {efsNONE};
116 };
117
119 {
120 public:
121 void Block(QObject *o)
122 {
123 o->blockSignals(true);
124 m_objects.push_back(o);
125 }
126
128 {
129 for (auto & obj : m_objects)
130 obj->blockSignals(false);
131 }
132
133 private:
134 using list_type = std::vector<QObject *>;
135
136 private:
138 };
139
141 {
142 using UID_type = unsigned int;
143
144 UIDToFAPair() = default;
145
147 m_uid(uid), m_fileAssoc(assoc) {}
148
149 UID_type m_uid {0};
150 FileAssociationWrap *m_fileAssoc {nullptr};
151 };
152
153
154 bool operator<(const UIDToFAPair lhs, const UIDToFAPair rhs)
155 {
156 if (lhs.m_fileAssoc && rhs.m_fileAssoc)
157 return QString::localeAwareCompare(lhs.m_fileAssoc->GetExtension(),
158 rhs.m_fileAssoc->GetExtension()) < 0;
159
160 return rhs.m_fileAssoc;
161 }
162}
163
165
167{
168 public:
169 using UIReadyList_type = std::vector<UIDToFAPair>;
170
171 public:
173 {
175 }
176
178 {
179 for (auto & fa : m_fileAssociations)
180 delete fa.second;
181 }
182
184 {
185 for (auto & fa : m_fileAssociations)
186 fa.second->CommitChanges();
187 }
188
189 bool AddExtension(const QString& newExtension, UIDToFAPair::UID_type &new_id)
190 {
191 if (!newExtension.isEmpty())
192 {
193 new_id = ++m_nextFAID;
194 m_fileAssociations.insert(FA_collection::value_type(new_id,
195 new FileAssociationWrap(newExtension)));
196 return true;
197 }
198
199 return false;
200 }
201
202 bool DeleteExtension(UIDToFAPair::UID_type uid)
203 {
204 auto p = m_fileAssociations.find(uid);
205 if (p != m_fileAssociations.end())
206 {
207 p->second->MarkForDeletion();
208
209 return true;
210 }
211
212 return false;
213 }
214
215 // Returns a list sorted by extension
217 {
219 std::transform(m_fileAssociations.begin(), m_fileAssociations.end(),
220 std::back_inserter(ret), fa_col_ent_2_UIDFAPair());
221 auto deleted = std::remove_if(ret.begin(),
223
224 if (deleted != ret.end())
225 ret.erase(deleted, ret.end());
226
227 std::sort(ret.begin(), ret.end());
228
229 return ret;
230 }
231
232 static FileAssociationWrap *GetCurrentFA(MythUIButtonList *buttonList)
233 {
234 MythUIButtonListItem *item = buttonList->GetItemCurrent();
235 if (item)
236 {
237 auto key = item->GetData().value<UIDToFAPair>();
238 if (key.m_fileAssoc)
239 {
240 return key.m_fileAssoc;
241 }
242 }
243
244 return nullptr;
245 }
246
247 void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
248 {
249 m_selectionOverride = new_sel;
250 }
251
252 UIDToFAPair::UID_type GetSelectionOverride() const
253 {
254 return m_selectionOverride;
255 }
256
257 private:
258 using FA_collection = std::map<UIDToFAPair::UID_type, FileAssociationWrap *>;
259
260 private:
262 {
263 UIDToFAPair operator()(
264 const FileAssocDialogPrivate::FA_collection::value_type from)
265 {
266 return {from.first, from.second};
267 }
268 };
269
270 template <FileAssociationWrap::FA_State against>
272 {
273 bool operator()(const UIDToFAPair item)
274 {
275 return item.m_fileAssoc && item.m_fileAssoc->GetState() == against;
276 }
277 };
278
280 {
281 using tmp_fa_list = std::vector<UIDToFAPair>;
282
285 tmp_fa_list tmp_fa;
286 tmp_fa.reserve(fa_list.size());
287
288 auto newpair = [this](const auto & fa)
289 { return UIDToFAPair(++m_nextFAID, new FileAssociationWrap(fa)); };
290 std::transform(fa_list.cbegin(), fa_list.cend(), std::back_inserter(tmp_fa), newpair);
291
292 std::shuffle(tmp_fa.begin(), tmp_fa.end(),
293 std::mt19937(std::random_device()()));
294
295 for (const auto& fa : tmp_fa)
296 {
297 m_fileAssociations.insert(FA_collection::value_type(fa.m_uid,
298 fa.m_fileAssoc));
299 }
300
301 if (m_fileAssociations.empty())
302 {
303 LOG(VB_GENERAL, LOG_ERR,
304 QString("%1: Couldn't get any filetypes from your database.")
305 .arg(__FILE__));
306 }
307 }
308
309 private:
311 UIDToFAPair::UID_type m_nextFAID {0};
312 UIDToFAPair::UID_type m_selectionOverride {0};
313};
314
316
318 const QString &lname) :
319 MythScreenType(screenParent, lname),
320 m_private(new FileAssocDialogPrivate)
321{
322}
323
325{
326 delete m_private;
327}
328
330{
331 if (!LoadWindowFromXML("video-ui.xml", "file_associations", this))
332 return false;
333
334 bool err = false;
335 UIUtilE::Assign(this, m_extensionList, "extension_select", &err);
336 UIUtilE::Assign(this, m_commandEdit, "command", &err);
337 UIUtilE::Assign(this, m_ignoreCheck, "ignore_check", &err);
338 UIUtilE::Assign(this, m_defaultCheck, "default_check", &err);
339
340 UIUtilE::Assign(this, m_doneButton, "done_button", &err);
341 UIUtilE::Assign(this, m_newButton, "new_button", &err);
342 UIUtilE::Assign(this, m_deleteButton, "delete_button", &err);
343
344 if (err)
345 {
346 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'file_associations'");
347 return false;
348 }
349
356
361
362 m_extensionList->SetHelpText(tr("Select a file extension from this list "
363 "to modify or delete its settings."));
364 m_commandEdit->SetHelpText(tr("The command to use when playing this kind "
365 "of file. To use MythTV's Internal player, "
366 "use \"Internal\" as the player. For all other "
367 "players, you can use %s to substitute the filename."));
368 m_ignoreCheck->SetHelpText(tr("When checked, this will cause the file extension "
369 "to be ignored in scans of your library."));
370 m_defaultCheck->SetHelpText(tr("When checked, this will cause the global player "
371 "settings to override this one."));
372 m_doneButton->SetHelpText(tr("Save and exit this screen."));
373 m_newButton->SetHelpText(tr("Create a new file extension."));
374 m_deleteButton->SetHelpText(tr("Delete this file extension."));
375
376 UpdateScreen();
377
379
380 return true;
381}
382
384{
385 UpdateScreen();
386}
387
389{
390 FileAssociationWrap *wrap = FileAssocDialogPrivate::GetCurrentFA(m_extensionList);
391 if (wrap != nullptr)
392 wrap->SetDefault(m_defaultCheck->GetBooleanCheckState());
393}
394
396{
397 FileAssociationWrap *wrap = FileAssocDialogPrivate::GetCurrentFA(m_extensionList);
398 if (wrap != nullptr)
399 wrap->SetIgnore(m_ignoreCheck->GetBooleanCheckState());
400}
401
403{
404 FileAssociationWrap *wrap = FileAssocDialogPrivate::GetCurrentFA(m_extensionList);
405 if (wrap != nullptr)
406 wrap->SetCommand(m_commandEdit->GetText());
407}
408
410{
412 Close();
413}
414
416{
418 if (item)
419 {
420 auto 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, &MythTextInputDialog::haveResult,
441}
442
443void 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
453void 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 {
471 }
472 else
473 {
474 UIDToFAPair::UID_type selected_id = 0;
476 if (current_item)
477 {
478 auto 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 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
492 for (auto & fad : tmp_list)
493 {
494 if (fad.m_fileAssoc)
495 {
496 // No memory leak. MythUIButtonListItem adds the new
497 // item into m_extensionList.
498 auto *new_item = new MythUIButtonListItem(m_extensionList,
499 fad.m_fileAssoc->GetExtension(),
500 QVariant::fromValue(fad));
501 if (selected_id && fad.m_uid == selected_id)
503 }
504 }
505
506 current_item = m_extensionList->GetItemCurrent();
507 if (current_item)
508 {
509 auto key = current_item->GetData().value<UIDToFAPair>();
510 if (key.m_fileAssoc)
511 {
513 m_commandEdit->SetText(key.m_fileAssoc->GetCommand());
514
516 m_defaultCheck->SetCheckState(key.m_fileAssoc->GetDefault() ?
518
520 m_ignoreCheck->SetCheckState(key.m_fileAssoc->GetIgnore() ?
522
524 }
525 }
526 }
527
529}
530
531Q_DECLARE_METATYPE(UIDToFAPair)
void SetSelectionOverride(UIDToFAPair::UID_type new_sel)
static FileAssociationWrap * GetCurrentFA(MythUIButtonList *buttonList)
bool DeleteExtension(UIDToFAPair::UID_type uid)
UIDToFAPair::UID_type m_selectionOverride
std::vector< UIDToFAPair > UIReadyList_type
UIDToFAPair::UID_type m_nextFAID
std::map< UIDToFAPair::UID_type, FileAssociationWrap * > FA_collection
bool AddExtension(const QString &newExtension, UIDToFAPair::UID_type &new_id)
UIReadyList_type GetUIReadyList()
UIDToFAPair::UID_type GetSelectionOverride() const
FA_collection m_fileAssociations
MythUIButton * m_doneButton
void OnNewExtensionPressed() const
MythUITextEdit * m_commandEdit
void UpdateScreen(bool useSelectionOverride=false)
void OnFASelected(MythUIButtonListItem *item)
MythUIButton * m_deleteButton
MythUIButton * m_newButton
void OnNewExtensionComplete(const QString &newExtension)
void OnPlayerCommandChanged()
MythUICheckBox * m_ignoreCheck
class FileAssocDialogPrivate * m_private
MythUICheckBox * m_defaultCheck
bool Create() override
~FileAssocDialog() override
MythUIButtonList * m_extensionList
FileAssocDialog(MythScreenStack *screenParent, const QString &lname)
bool remove(unsigned int id)
Definition: dbaccess.cpp:806
static FileAssociations & getFileAssociation()
Definition: dbaccess.cpp:836
const association_list & getList() const
Definition: dbaccess.cpp:811
std::vector< file_association > association_list
Definition: dbaccess.h:154
MythScreenStack * GetStack(const QString &Stackname)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
virtual void Close()
Dialog prompting the user to enter a text string.
void haveResult(QString)
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
MythUIButtonListItem * GetItemCurrent() const
void SetItemCurrent(MythUIButtonListItem *item)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
void itemSelected(MythUIButtonListItem *item)
void Clicked()
void SetCheckState(MythUIStateType::StateType state)
bool GetBooleanCheckState(void) const
void valueChanged()
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void valueChanged()
virtual void SetVisible(bool visible)
void SetHelpText(const QString &text)
Definition: mythuitype.h:177
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
FileAssociationWrap(FileAssociations::file_association fa)
Q_DECLARE_METATYPE(std::chrono::seconds)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
bool operator<(const UIDToFAPair lhs, const UIDToFAPair rhs)
void assign_if_changed_notify(T &oldVal, const T &newVal, Inst *inst, FuncType func)
STL namespace.
UIDToFAPair operator()(const FileAssocDialogPrivate::FA_collection::value_type from)
bool operator()(const UIDToFAPair item)
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
UIDToFAPair(UID_type uid, FileAssociationWrap *assoc)